Compare commits
234 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 07745aa6bc | |||
| 45afaba63e | |||
| ee2f18349f | |||
| 8829a3cbe9 | |||
| b210e39c7e | |||
| 4d4500f061 | |||
| 79860a0c04 | |||
| d53fcdc616 | |||
| 5e6a094ac6 | |||
| 757bf4e916 | |||
| 7bb57b3ab9 | |||
| cb22b5804d | |||
| bfbced3859 | |||
| c090ee9b83 | |||
| b6caf069d0 | |||
| 5b4449eea0 | |||
| 7dc17a3653 | |||
| 598bf799de | |||
| 4774a37d48 | |||
| d9a1e6f4b1 | |||
| 72dae8e9ad | |||
| b2d7fb9443 | |||
| bf945e36bd | |||
| 2bcfe93dd2 | |||
| 195ef743c6 | |||
| 8478eb9aa0 | |||
| 47f57d20cd | |||
| 8f8eb06d6b | |||
| 7d6803f325 | |||
| f9b7a8b92b | |||
| 7ee0c047e5 | |||
| bfbfe17644 | |||
| 046940f949 | |||
| 2950ddcf4f | |||
| 3d441b4ad3 | |||
| caa5e8983b | |||
| 59b7944d8e | |||
| 648eb8fdd1 | |||
| f160516772 | |||
| 4beb7780f8 | |||
| d44b6d40e9 | |||
| 8fd9f5a8cb | |||
| 414af2f55e | |||
| 8624be6bcf | |||
| 4aa26b613b | |||
| 57cb062d48 | |||
| b4b6404b65 | |||
| 989dd2be50 | |||
| 50b8baddec | |||
| 2e00c3db0b | |||
| 232260787e | |||
| 54054c4aee | |||
| 1a7843a6b5 | |||
| ed4ba4d0d3 | |||
| 849d6118d0 | |||
| 364138d408 | |||
| 32c8da06ca | |||
| 2df4c5825f | |||
| 527cbc3ae0 | |||
| 4fee8744d6 | |||
| 8f8862d23d | |||
| 836f36fd8e | |||
| a1326cb5ed | |||
| 4bfda8851c | |||
| f02f15cbfb | |||
| 30182684d5 | |||
| 7df80af30b | |||
| 6b2e867107 | |||
| d333090b09 | |||
| 8ae7763ac5 | |||
| 51ac86be79 | |||
| 552078c8a2 | |||
| 4b4ede733f | |||
| d07e8bc747 | |||
| 8850d78e5a | |||
| 4fbf4e7d42 | |||
| d6e1a670b9 | |||
| 1a32c8827e | |||
| 784b5156df | |||
| 6e3336afb1 | |||
| d0ddee3623 | |||
| 9cf55033d4 | |||
| 7371efde17 | |||
| 156269a53f | |||
| ed7e5fb482 | |||
| 2e973a4591 | |||
| f1bd82d301 | |||
| f51bfa314d | |||
| 7b3410c7b1 | |||
| 6b0e7d51b1 | |||
| 4e5659efad | |||
| 66f10adde8 | |||
| 459c4d7fcc | |||
| 03d9efb790 | |||
| 33bd6099ee | |||
| 850a1d326b | |||
| f0bb19d48d | |||
| a56eac0131 | |||
| d3201809ab | |||
| 3628a0e241 | |||
| 07badd3df5 | |||
| f63091ce05 | |||
| 617b2978c0 | |||
| 364e96ad1e | |||
| 9170e5ca65 | |||
| cbe71f221c | |||
| c039c01407 | |||
| 629ba41f5f | |||
| 198fa67cc1 | |||
| e7830e3d55 | |||
| 90bfb25f41 | |||
| 037cb00caa | |||
| f002124c38 | |||
| ac42f9f599 | |||
| 88d4640d3f | |||
| d2da01041c | |||
| 75c17b820f | |||
| 2dc4bbf8cd | |||
| c9a5abc3be | |||
| 7ea468a9a6 | |||
| c6223520e0 | |||
| 176cbf9861 | |||
| 00315a6f2d | |||
| 2145b2f8e2 | |||
| 31155e7bc9 | |||
| 2d07208d0f | |||
| 35ad7e16c6 | |||
| 27c52fe6d5 | |||
| e153f63a23 | |||
| 2cbbc360c0 | |||
| 8a4a42777f | |||
| 352d6b5ef9 | |||
| cc49b0efdc | |||
| e71ee988bf | |||
| 937a5b2c2d | |||
| 92221e19c3 | |||
| e5c920864c | |||
| 6f63682bdf | |||
| 7623eddcd7 | |||
| d1dee1e908 | |||
| 8b302f8d21 | |||
| dfeebd6b6b | |||
| df53ce9f6e | |||
| 9bb2c00b6d | |||
| 641253c4c6 | |||
| 6ce49b7130 | |||
| 804ad0cece | |||
| 6c60d14c05 | |||
| def2d2bcc9 | |||
| 9bca705dc3 | |||
| 2dcc271dff | |||
| 33415fa1af | |||
| 10315e0e29 | |||
| f321cc81c8 | |||
| 68b685f580 | |||
| c87c79b937 | |||
| de3dfcdc43 | |||
| 657ddc0fa1 | |||
| 1da01489f1 | |||
| 64a27614ff | |||
| 6c5acfc8bb | |||
| 884ee5b587 | |||
| c2320a3c1e | |||
| ab23030051 | |||
| 699c692f6c | |||
| 0c85e26950 | |||
| 2cd7e737cd | |||
| a1adbe2b34 | |||
| a144c2b7ea | |||
| 201c056ffa | |||
| 2e95168610 | |||
| b18c05cbb9 | |||
| 102a4c6110 | |||
| b6c0ef3d19 | |||
| ea3065dbc2 | |||
| 534c3ad329 | |||
| 90a7d807a1 | |||
| 4baafb5e2f | |||
| f911765cdb | |||
| d0a36c79d5 | |||
| ffea5320a0 | |||
| 0b54e41a40 | |||
| 26c67f770a | |||
| f6623db0e6 | |||
| d7844ee8e8 | |||
| 1c8970bbf2 | |||
| eb70849a30 | |||
| 77ce3c9067 | |||
| 56cd226a27 | |||
| a1acdc0977 | |||
| 1a624c957b | |||
| 1c9eeb2aca | |||
| 45f06eeea7 | |||
| 66a53b486a | |||
| fc45f5b48f | |||
| c7105a22e6 | |||
| e1fc7cad44 | |||
| afeeaeb984 | |||
| 2bc60b87f2 | |||
| 4ad9ecb7db | |||
| 3ec7b82b85 | |||
| 48666def6b | |||
| 5732921fb6 | |||
| b86c11e1dc | |||
| 1bae4b219a | |||
| 70ff73f805 | |||
| a9636c5038 | |||
| a406b9f7ef | |||
| f291345111 | |||
| c947ccb8be | |||
| 28c6b0d4bb | |||
| 19317a9cc2 | |||
| d67da6425e | |||
| 901f41358f | |||
| 061922aea2 | |||
| 5f7b6a5498 | |||
| 2ef9d5eed9 | |||
| 9fba57d5db | |||
| e9074e3b3f | |||
| f053ba714f | |||
| b6598f09a3 | |||
| b9d7b95304 | |||
| 3db540f786 | |||
| 071b99c6c6 | |||
| 3a3d96be0a | |||
| a03fbab4d5 | |||
| cc3b119288 | |||
| 1a750d1b85 | |||
| 5b3ae9b9fd | |||
| 81471d80cd | |||
| eb14db4deb | |||
| 50d989363a | |||
| e52a99bd71 | |||
| a7a7c86657 |
@@ -4,6 +4,7 @@ on:
|
||||
push:
|
||||
paths:
|
||||
- .github/workflows/ci-ifcopenshell-conda-daily.yml
|
||||
- conda/**
|
||||
schedule:
|
||||
# ┌───────────── minute (0 - 59)
|
||||
# │ ┌───────────── hour (0 - 23)
|
||||
@@ -53,11 +54,13 @@ jobs:
|
||||
sudo mv -v MacOSX10.13.sdk /opt/ && \
|
||||
ls /opt/
|
||||
- uses: seanmiddleditch/gha-setup-ninja@master
|
||||
- uses: conda-incubator/setup-miniconda@v2 # https://github.com/conda-incubator/setup-miniconda
|
||||
- uses: mamba-org/provision-with-micromamba@main # https://github.com/mamba-org/provision-with-micromamba
|
||||
with:
|
||||
activate-environment: conda-build
|
||||
python-version: ${{ matrix.pyver.distver }}
|
||||
channels: conda-forge
|
||||
channel-priority: strict
|
||||
environment-file: conda/environment.yml
|
||||
extra-specs: |
|
||||
python=${{ matrix.pyver.distver }}
|
||||
- name: build, test and upload ifcopenshell
|
||||
if: ${{ matrix.platform.upload == 'true' }}
|
||||
run: |
|
||||
|
||||
@@ -10,5 +10,4 @@ dependencies:
|
||||
- ripgrep
|
||||
- pip
|
||||
- pip:
|
||||
- --extra-index-url https://lief.s3-website.fr-par.scw.cloud/latest
|
||||
- lief==0.13.0.dev0
|
||||
- lief==0.13
|
||||
|
||||
@@ -36,7 +36,7 @@ requirements:
|
||||
run:
|
||||
- python
|
||||
- boost-cpp
|
||||
- occt
|
||||
- {{ pin_compatible('occt', max_pin='x.x.x') }}
|
||||
- libxml2
|
||||
- cgal-cpp
|
||||
- hdf5
|
||||
|
||||
@@ -59,6 +59,10 @@ import multiprocessing
|
||||
import platform
|
||||
import sysconfig
|
||||
|
||||
# @todo temporary for expired mpfr.org certificate on 2023-04-08
|
||||
import ssl
|
||||
ssl._create_default_https_context = ssl._create_unverified_context
|
||||
|
||||
from urllib.request import urlretrieve
|
||||
|
||||
|
||||
|
||||
@@ -236,6 +236,11 @@ endif
|
||||
cp -r dist/working/IfcOpenShell-0.7.0/src/ifc5d/ifc5d dist/blenderbim/libs/site/packages/
|
||||
# Provides IFCCityJSON functionality
|
||||
cp -r dist/working/IfcOpenShell-0.7.0/src/ifccityjson dist/blenderbim/libs/site/packages/
|
||||
# Provides IfcGit functionality
|
||||
cd dist/working && . env/bin/activate && $(PIP) install GitPython --target=./site-packages
|
||||
cp -r dist/working/site-packages/smmap dist/blenderbim/libs/site/packages/
|
||||
cp -r dist/working/site-packages/gitdb dist/blenderbim/libs/site/packages/
|
||||
cp -r dist/working/site-packages/git dist/blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Provides Mustache templating in construction documentation
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
import blenderbim
|
||||
import importlib
|
||||
from . import handler, ui, prop, operator, helper
|
||||
|
||||
@@ -70,6 +71,7 @@ modules = {
|
||||
"covetool": None,
|
||||
"augin": None,
|
||||
"debug": None,
|
||||
"ifcgit": None,
|
||||
# Uncomment this line to enable loading of the demo module. Happy hacking!
|
||||
# The name "demo" must correlate to a folder name in `bim/module/`.
|
||||
# "demo": None,
|
||||
@@ -171,7 +173,11 @@ def register():
|
||||
|
||||
def unregister():
|
||||
for cls in reversed(classes):
|
||||
bpy.utils.unregister_class(cls)
|
||||
if getattr(cls, "is_registered", None) is None:
|
||||
bpy.utils.unregister_class(cls)
|
||||
elif cls.is_registered:
|
||||
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_post.remove(handler.ensureIfcExported)
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
text, tspan { /* 2.5mm */ fill: black; stroke: none; font-family: 'OpenGost Type B TT', 'DejaVu Sans Condensed', 'Liberation Sans', 'Arial Narrow', 'Arial'; font-size: 4.13px; }
|
||||
.cut { fill: black; stroke: black; stroke-linecap: 'round'; stroke-width: 0.35; fill-rule: evenodd; }
|
||||
.projection { fill: white; stroke: black; stroke-linecap: 'round'; stroke-width: 0.25; }
|
||||
.surface { stroke: none; fill: #fff; fill-rule: evenodd; }
|
||||
.annotation { fill: none; stroke: black; stroke-linecap: 'round'; stroke-width: 0.25; }
|
||||
.IfcAnnotation { fill: none; stroke: black; stroke-linecap: 'round'; stroke-width: 0.25; }
|
||||
.IfcGeographicElement { fill: none; stroke: black; stroke-linecap: 'round'; stroke-width: 1; }
|
||||
@@ -96,4 +96,12 @@
|
||||
<path d="M 7.5 10 L 10 0 L 15 10" class="annotation" style="stroke-width:1; fill: white;" />
|
||||
</g>
|
||||
</marker>
|
||||
<filter x="0" y="0" width="1" height="1" id="fill-background">
|
||||
<feFlood flood-color="white" result="bg" />
|
||||
<feMerge>
|
||||
<feMergeNode in="bg"/>
|
||||
<feMergeNode in="SourceGraphic"/>
|
||||
</feMerge>
|
||||
</filter>
|
||||
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 5.6 KiB |
@@ -17,16 +17,49 @@
|
||||
<line x1="0" y1="0" x2="0" y2="3" style="stroke:black; stroke-width:0.25" />
|
||||
</pattern>
|
||||
<pattern id="square1" width="1" height="1" patternUnits="userSpaceOnUse">
|
||||
<line x1="0" y1="0" x2="0" y2="1" style="stroke:black; stroke-width:0.25" />
|
||||
<line x1="0" y1="0" x2="1" y2="0" style="stroke:black; stroke-width:0.25" />
|
||||
<path style="stroke: black; stroke-width: 0.25; fill: none;" d="M 0 0 1 0 1 1" />
|
||||
</pattern>
|
||||
<pattern id="square2" width="2" height="2" patternUnits="userSpaceOnUse">
|
||||
<line x1="0" y1="0" x2="0" y2="2" style="stroke:black; stroke-width:0.25" />
|
||||
<line x1="0" y1="0" x2="2" y2="0" style="stroke:black; stroke-width:0.25" />
|
||||
<path style="stroke: black; stroke-width: 0.25; fill: none;" d="M 0 0 2 0 2 2" />
|
||||
</pattern>
|
||||
<pattern id="square3" width="3" height="3" patternUnits="userSpaceOnUse">
|
||||
<line x1="0" y1="0" x2="0" y2="3" style="stroke:black; stroke-width:0.25" />
|
||||
<line x1="0" y1="0" x2="3" y2="0" style="stroke:black; stroke-width:0.25" />
|
||||
<path style="stroke: black; stroke-width: 0.25; fill: none;" d="M 0 0 3 0 3 3" />
|
||||
</pattern>
|
||||
<pattern id="recth1" width="2" height="1" patternUnits="userSpaceOnUse">
|
||||
<path style="stroke: black; stroke-width: 0.25; fill: none;" d="M 0 0 2 0 2 1" />
|
||||
</pattern>
|
||||
<pattern id="recth2" width="4" height="2" patternUnits="userSpaceOnUse">
|
||||
<path style="stroke: black; stroke-width: 0.25; fill: none;" d="M 0 0 4 0 4 2" />
|
||||
</pattern>
|
||||
<pattern id="recth3" width="6" height="3" patternUnits="userSpaceOnUse">
|
||||
<path style="stroke: black; stroke-width: 0.25; fill: none;" d="M 0 0 6 0 6 3" />
|
||||
</pattern>
|
||||
<pattern id="rectv1" width="1" height="2" patternUnits="userSpaceOnUse">
|
||||
<path style="stroke: black; stroke-width: 0.25; fill: none;" d="M 0 0 1 0 1 2" />
|
||||
</pattern>
|
||||
<pattern id="rectv2" width="2" height="4" patternUnits="userSpaceOnUse">
|
||||
<path style="stroke: black; stroke-width: 0.25; fill: none;" d="M 0 0 2 0 2 4" />
|
||||
</pattern>
|
||||
<pattern id="rectv3" width="3" height="6" patternUnits="userSpaceOnUse">
|
||||
<path style="stroke: black; stroke-width: 0.25; fill: none;" d="M 0 0 3 0 3 6" />
|
||||
</pattern>
|
||||
<pattern id="tile1" width="2" height="2" patternUnits="userSpaceOnUse">
|
||||
<path style="stroke: black; stroke-width: 0.15; fill: none;" d="M 0 0 2 0 2 1 0 1 M 1 1 1 2" />
|
||||
</pattern>
|
||||
<pattern id="tile2" width="4" height="4" patternUnits="userSpaceOnUse">
|
||||
<path style="stroke: black; stroke-width: 0.15; fill: none;" d="M 0 0 4 0 4 2 0 2 M 2 2 2 4" />
|
||||
</pattern>
|
||||
<pattern id="tile3" width="6" height="6" patternUnits="userSpaceOnUse">
|
||||
<path style="stroke: black; stroke-width: 0.15; fill: none;" d="M 0 0 6 0 6 3 0 3 M 3 3 3 6" />
|
||||
</pattern>
|
||||
<pattern id="board1" width="4" height="2" patternUnits="userSpaceOnUse">
|
||||
<path style="stroke: black; stroke-width: 0.15; fill: none;" d="M 0 0 4 0 4 1 0 1 M 2 1 2 2" />
|
||||
</pattern>
|
||||
<pattern id="board2" width="8" height="4" patternUnits="userSpaceOnUse">
|
||||
<path style="stroke: black; stroke-width: 0.15; fill: none;" d="M 0 0 8 0 8 2 0 2 M 4 2 4 4" />
|
||||
</pattern>
|
||||
<pattern id="board3" width="12" height="6" patternUnits="userSpaceOnUse">
|
||||
<path style="stroke: black; stroke-width: 0.15; fill: none;" d="M 0 0 12 0 12 3 0 3 M 6 3 6 6" />
|
||||
</pattern>
|
||||
<pattern id="crosshatch1" width="1" height="1" patternTransform="rotate(45 0 0)" patternUnits="userSpaceOnUse">
|
||||
<line x1="0" y1="0" x2="0" y2="1" style="stroke:black; stroke-width:0.25" />
|
||||
|
Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 69 KiB |
@@ -0,0 +1,44 @@
|
||||
<?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="triangle-tag">
|
||||
<path style="fill: white; stroke: black; stroke-width: 0.25;" d="M 3.1409425e-7,-7.1069207 5.7650623,2.8784603 l -11.5301246,-6e-7 z" />
|
||||
</g>
|
||||
<g id="hexagon-tag">
|
||||
<path style="fill: white; stroke: black; stroke-width: 0.25;" d="m 0.0161133,-6.0583333 5.2339915,3.8366023 -2.031433,6.1633977 c 0,0 -6.4894866,-0.027414 -6.4894866,-0.027414 l -1.97929,-6.1803395 5.2662181,-3.7922467" />
|
||||
</g>
|
||||
<g id="capsule-tag">
|
||||
<rect x="-6" y="-2.5" width="12" height="5" rx="2.5" ry="2.5" fill="white" stroke="black" style="stroke-width: 0.25;" />
|
||||
</g>
|
||||
<g id="circle-tag">
|
||||
<circle r="5" fill="white" stroke="black" style="stroke-width: 0.25;" />
|
||||
</g>
|
||||
<g id="door-tag">
|
||||
<circle r="5" fill="white" stroke="black" style="stroke-width: 0.25;" />
|
||||
<text y="-1.75" class="regular" text-anchor="middle" dominant-baseline="middle" data-type="text-template"></text>
|
||||
<line x1="-5" x2="5" style="stroke: black; stroke-width: 0.25;" />
|
||||
<text y="2.5" class="regular" text-anchor="middle" dominant-baseline="middle" data-type="text-template"></text>
|
||||
</g>
|
||||
<g id="space-tag">
|
||||
<text y="-5" class="large" text-anchor="middle" dominant-baseline="middle" data-type="text-template"></text>
|
||||
<rect x="-6" y="-2.5" width="12" height="5" fill="white" stroke="black" style="stroke-width: 0.25;" />
|
||||
<text class="regular" text-anchor="middle" dominant-baseline="middle" data-type="text-template"></text>
|
||||
<text y="5" class="regular" text-anchor="middle" dominant-baseline="middle" data-type="text-template"></text>
|
||||
</g>
|
||||
<g id="elevation-arrow">
|
||||
<path style="fill: black;" d="M -7.07106 0 0 -7.07106 7.07106 0" />
|
||||
</g>
|
||||
<g id="elevation-tag">
|
||||
<circle r="5" fill="white" stroke="black" style="stroke-width: 0.25;" />
|
||||
<line x1="-5" y1="0" x2="5" y2="0" style="stroke: black; stroke-width: 0.25;" />
|
||||
</g>
|
||||
<g id="section-arrow">
|
||||
<path style="fill: black;" d="M -7.07106 0 0 -7.07106 7.07106 0" />
|
||||
</g>
|
||||
<g id="section-tag">
|
||||
<circle r="5" fill="white" stroke="black" style="stroke-width: 0.25;" />
|
||||
<line x1="-5" y1="0" x2="5" y2="0" style="stroke: black; stroke-width: 0.25;" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 4.4 KiB |
@@ -5,20 +5,24 @@ FILE_NAME('EPset_Drawing.ifc','2020-01-01T00:00:00',(),(),'EPset_Drawing','EPset
|
||||
FILE_SCHEMA(('IFC4'));
|
||||
ENDSEC;
|
||||
DATA;
|
||||
#1=IFCPROPERTYSETTEMPLATE('2JhNIvqZrFnAgxfhK0XVQX',$,'EPset_Drawing','',.PSET_OCCURRENCEDRIVEN.,'IfcAnnotation',(#2,#3,#4,#5,#6,#7,#8,#9,#10,#11,#12,#13,#14,#15));
|
||||
#1=IFCPROPERTYSETTEMPLATE('2JhNIvqZrFnAgxfhK0XVQX',$,'EPset_Drawing','',.PSET_OCCURRENCEDRIVEN.,'IfcAnnotation',(#2,#3,#4,#5,#6,#7,#8,#9,#10,#11,#12,#13,#14,#15,#16,#17,#18,#19));
|
||||
#2=IFCSIMPLEPROPERTYTEMPLATE('23JavTMk98ZxXhrUEnjAcf',$,'TargetView','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#3=IFCSIMPLEPROPERTYTEMPLATE('1yVWUt5H9DAOuu0OaMMLpe',$,'Scale','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#4=IFCSIMPLEPROPERTYTEMPLATE('3gsuPBtU93b8f0gg1pjkq6',$,'HumanScale','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#3=IFCSIMPLEPROPERTYTEMPLATE('1yVWUt5H9DAOuu0OaMMLpe',$,'Scale','The scale of this drawing represented as a numerator and denominator, such as 1/100',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#4=IFCSIMPLEPROPERTYTEMPLATE('3gsuPBtU93b8f0gg1pjkq6',$,'HumanScale','The scale of this drawing in human readable format, such as 1:100',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#5=IFCSIMPLEPROPERTYTEMPLATE('0AK5C2UpL4$eaac2LszAx$',$,'HasUnderlay','',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
|
||||
#6=IFCSIMPLEPROPERTYTEMPLATE('2j2ZEZR8X5tONm7kli5hM6',$,'HasLinework','',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
|
||||
#7=IFCSIMPLEPROPERTYTEMPLATE('1ttChRysH9UuEX2FeMj5Hu',$,'HasAnnotation','',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
|
||||
#8=IFCSIMPLEPROPERTYTEMPLATE('2NPPxuABv1huDTVh32TFgw',$,'GlobalReferencing','',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
|
||||
#9=IFCSIMPLEPROPERTYTEMPLATE('10hT_1zrzEbRRKMXYAWvtD',$,'Metadata','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
|
||||
#10=IFCSIMPLEPROPERTYTEMPLATE('3Z0BXPSG5CWgtI33ioV7aj',$,'Include','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
|
||||
#11=IFCSIMPLEPROPERTYTEMPLATE('1RVts_g3PAw98PJA2yL3bO',$,'Exclude','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
|
||||
#10=IFCSIMPLEPROPERTYTEMPLATE('3Z0BXPSG5CWgtI33ioV7aj',$,'Include','Selector expression to include ifc elements in the drawing',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
|
||||
#11=IFCSIMPLEPROPERTYTEMPLATE('1RVts_g3PAw98PJA2yL3bO',$,'Exclude','Selector expression to exclude ifc elements in the drawing',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
|
||||
#12=IFCSIMPLEPROPERTYTEMPLATE('0c1$8NpYDEaBiJrj16jHIo',$,'Stylesheet','',.P_SINGLEVALUE.,'IfcURIReference',$,$,$,$,$,.READWRITE.);
|
||||
#13=IFCSIMPLEPROPERTYTEMPLATE('3mRF52q81FQB$h4oTh7M45',$,'Markers','',.P_SINGLEVALUE.,'IfcURIReference',$,$,$,$,$,.READWRITE.);
|
||||
#14=IFCSIMPLEPROPERTYTEMPLATE('1rhr_0N3LDtuORcEJP0KXM',$,'Symbols','',.P_SINGLEVALUE.,'IfcURIReference',$,$,$,$,$,.READWRITE.);
|
||||
#15=IFCSIMPLEPROPERTYTEMPLATE('2sHDBuW7P4TROy$hL2w7ct',$,'Patterns','',.P_SINGLEVALUE.,'IfcURIReference',$,$,$,$,$,.READWRITE.);
|
||||
#16=IFCSIMPLEPROPERTYTEMPLATE('1$xfo9EVb26QLqmPll2_RK',$,'MetricPrecision','',.P_SINGLEVALUE.,'IfcReal',$,$,$,$,$,.READWRITE.);
|
||||
#17=IFCSIMPLEPROPERTYTEMPLATE('38uAtrp9nD_901NO42zd$7',$,'ImperialPrecision','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#18=IFCSIMPLEPROPERTYTEMPLATE('1MX0uffTL6TOvtvEJpxFmk',$,'DecimalPlaces','',.P_SINGLEVALUE.,'IfcInteger',$,$,$,$,$,.READWRITE.);
|
||||
#19=IFCSIMPLEPROPERTYTEMPLATE('0joEq0Rd10cxweEh0NeHT6',$,'JoinCriteria','Comma separated selection keys which determine what cut objects are to be joined.',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
|
||||
ENDSEC;
|
||||
END-ISO-10303-21;
|
||||
|
||||
@@ -5,18 +5,30 @@ FILE_NAME('Psets_BBIM_Annotation.ifc','2020-01-01T00:00:00',(),(),'Psets_BBIM_An
|
||||
FILE_SCHEMA(('IFC4'));
|
||||
ENDSEC;
|
||||
DATA;
|
||||
#1=IFCPROPERTYSETTEMPLATE('3VuPUwdCD2Qx3XDDRs0R1N',$,'EPset_Annotation','',.PSET_OCCURRENCEDRIVEN.,'IfcAnnotation',(#2,#3,#4));
|
||||
#1=IFCPROPERTYSETTEMPLATE('3VuPUwdCD2Qx3XDDRs0R1N',$,'EPset_Annotation','',.PSET_OCCURRENCEDRIVEN.,'IfcAnnotation,IfcTypeProduct',(#2,#3,#4));
|
||||
#2=IFCSIMPLEPROPERTYTEMPLATE('2P7JN79n96Q9pElZ83LKe4',$,'ZIndex','',.P_SINGLEVALUE.,'IfcInteger',$,$,$,$,$,.READWRITE.);
|
||||
#3=IFCSIMPLEPROPERTYTEMPLATE('1Wpx_r2xj1_9w5JpI0QRJy',$,'Symbol','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#4=IFCSIMPLEPROPERTYTEMPLATE('3q0oxMUKP47vZ4jnyG$dDb',$,'Classes','Classes separarated by spaces that end up in classes for this element in svg. Can be used to specify the text font size: small - 1.8mm; regular - 2.5mm; large - 3.5mm; header - 5mm; title - 7mm. By default regular size is used.',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#5=IFCPROPERTYSETTEMPLATE('0iKwujnQL9IevVQato8f7Z',$,'BBIM_Batting','',.PSET_OCCURRENCEDRIVEN.,'IfcAnnotation',(#6,#7));
|
||||
#5=IFCPROPERTYSETTEMPLATE('0iKwujnQL9IevVQato8f7Z',$,'BBIM_Batting','',.PSET_OCCURRENCEDRIVEN.,'IfcAnnotation,IfcTypeProduct',(#6,#7));
|
||||
#6=IFCSIMPLEPROPERTYTEMPLATE('0t2LEesGT1QRQtrIZUAR8L',$,'Thickness','Batting thickness',.P_SINGLEVALUE.,'IfcPositiveLengthMeasure',$,$,$,$,$,.READWRITE.);
|
||||
#7=IFCSIMPLEPROPERTYTEMPLATE('082PndS6v2kBOiJoSboMnh',$,'Reverse pattern direction','Reverse batting pattern (swap starting and ending points)',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
|
||||
#8=IFCPROPERTYSETTEMPLATE('1Dx2EiZnP67xotInXpwz90',$,'BBIM_Section','',.PSET_OCCURRENCEDRIVEN.,'IfcAnnotation',(#9,#10,#11,#12,#13));
|
||||
#8=IFCPROPERTYSETTEMPLATE('1Dx2EiZnP67xotInXpwz90',$,'BBIM_Section','',.PSET_OCCURRENCEDRIVEN.,'IfcAnnotation,IfcTypeProduct',(#9,#10,#11,#12,#13));
|
||||
#9=IFCSIMPLEPROPERTYTEMPLATE('2a_9s8spHDc9dZHtgg71XL',$,'ShowStartArrow','Display start arrow.',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
|
||||
#10=IFCSIMPLEPROPERTYTEMPLATE('3i6SH_GbT7zhKea$wVE56A',$,'StartArrowSymbol','Custom symbol for the start of the section marker arrow. Need to make sure it''s present in "symbols.svg".',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#11=IFCSIMPLEPROPERTYTEMPLATE('1DtsPn5a9FG8$zXHDDMavY',$,'ShowEndArrow','Display end arrow.',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
|
||||
#12=IFCSIMPLEPROPERTYTEMPLATE('2$6U0mLI9AiPRWeBabdY3u',$,'EndArrowSymbol','Custom symbol for the end of the section marker arrow. Need to make sure it''s present in "symbols.svg".',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#13=IFCSIMPLEPROPERTYTEMPLATE('1naFqntIL7igCY7hCaE7kq',$,'HasConnectedSectionLine','Connect or disconnect section markers with line (by default = True).',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
|
||||
#14=IFCPROPERTYSETTEMPLATE('3V8oZ8YRD3_O7uR5vcUleS',$,'BBIM_Documentation','',.PSET_OCCURRENCEDRIVEN.,'IfcProject',(#15,#16,#17,#18,#19,#20,#21,#22));
|
||||
#15=IFCSIMPLEPROPERTYTEMPLATE('0ulAhgk3v9qfGlDILauR6J',$,'SheetsDir','Default sheets directory',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#16=IFCSIMPLEPROPERTYTEMPLATE('2yvlVKiQXASucfH40deCvu',$,'LayoutsDir','Default layouts directory',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#17=IFCSIMPLEPROPERTYTEMPLATE('2kXZqXicL3jRwsOnLM_0ho',$,'TitleblocksDir','Default titleblocks directory',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#18=IFCSIMPLEPROPERTYTEMPLATE('1lmOfo9Bf9beMBJkCZKf5Y',$,'DrawingsDir','Default drawings directory',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#19=IFCSIMPLEPROPERTYTEMPLATE('30cOKpb4D9CADKM04$N9X3',$,'StylesheetPath','Default stylesheet CSS',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#20=IFCSIMPLEPROPERTYTEMPLATE('23tejOrxj859R_eCRp1AFP',$,'MarkersPath','Default markers SVG',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#21=IFCSIMPLEPROPERTYTEMPLATE('1UDakJ5_f7kBhggNSW4$h5',$,'SymbolsPath','Default symbols SVG',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#22=IFCSIMPLEPROPERTYTEMPLATE('0d53LEtgLDQxnv__NfgH7i',$,'PatternsPath','Default patterns SVG',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#23=IFCPROPERTYSETTEMPLATE('0I9merLinF5Ap$aZwaclgm',$,'BBIM_Dimension','',.PSET_OCCURRENCEDRIVEN.,'IfcAnnotation,IfcTypeProduct',(#24,#25));
|
||||
#24=IFCSIMPLEPROPERTYTEMPLATE('1rL2AbQsXD8RbpoWH5pYOV',$,'ShowDescriptionOnly','Hide the measurement values and show only annotation description',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
|
||||
#25=IFCSIMPLEPROPERTYTEMPLATE('0SVyOfB0rC2xNfdRYf3XvY',$,'SuppressZeroInches','Suppress 0 inch values in dimension annotation text (for example: 12'' - 0" -> 12'')',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
|
||||
ENDSEC;
|
||||
END-ISO-10303-21;
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
<?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;" />
|
||||
<text text-anchor="middle" dominant-baseline="middle" data-type="text-template"></text>
|
||||
<line x1="-5" y1="1.75" x2="5" y2="1.75" style="stroke: black; stroke-width: 0.25;" />
|
||||
<text y="5" text-anchor="middle" dominant-baseline="middle" data-type="text-template"></text>
|
||||
</g>
|
||||
<g id="elevation-arrow">
|
||||
<path style="fill: black;" d="M -7.07106 0 0 -7.07106 7.07106 0" />
|
||||
</g>
|
||||
<g id="elevation-tag">
|
||||
<circle r="5" fill="white" stroke="black" style="stroke-width: 0.25;" />
|
||||
<line x1="-5" y1="0" x2="5" y2="0" style="stroke: black; stroke-width: 0.25;" />
|
||||
</g>
|
||||
<g id="section-arrow">
|
||||
<path style="fill: black;" d="M -7.07106 0 0 -7.07106 7.07106 0" />
|
||||
</g>
|
||||
<g id="section-tag">
|
||||
<circle r="5" fill="white" stroke="black" style="stroke-width: 0.25;" />
|
||||
<line x1="-5" y1="0" x2="5" y2="0" style="stroke: black; stroke-width: 0.25;" />
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.4 KiB |
@@ -45,7 +45,6 @@ class IfcExporter:
|
||||
self.set_header()
|
||||
IfcStore.update_cache()
|
||||
if bpy.context.scene.BIMProjectProperties.is_authoring:
|
||||
self.sync_deletions()
|
||||
self.sync_all_objects()
|
||||
self.sync_edited_objects()
|
||||
extension = self.ifc_export_settings.output_file.split(".")[-1].lower()
|
||||
@@ -87,19 +86,6 @@ class IfcExporter:
|
||||
self.get_application_name(), self.get_application_version()
|
||||
)
|
||||
|
||||
def sync_deletions(self):
|
||||
results = []
|
||||
for ifc_definition_id in IfcStore.deleted_ids:
|
||||
try:
|
||||
product = self.file.by_id(ifc_definition_id)
|
||||
if hasattr(product, "GlobalId"):
|
||||
results.append(product.GlobalId)
|
||||
except:
|
||||
continue
|
||||
ifcopenshell.api.run("root.remove_product", self.file, **{"product": product})
|
||||
IfcStore.deleted_ids.clear()
|
||||
return results
|
||||
|
||||
def sync_all_objects(self):
|
||||
results = []
|
||||
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(self.file)
|
||||
@@ -126,7 +112,7 @@ class IfcExporter:
|
||||
continue
|
||||
try:
|
||||
if isinstance(obj, bpy.types.Material):
|
||||
if self.has_changed_shading(obj):
|
||||
if tool.Ifc.has_changed_shading(obj):
|
||||
blenderbim.core.style.update_style_colours(tool.Ifc, tool.Style, obj=obj)
|
||||
else:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
@@ -149,10 +135,6 @@ class IfcExporter:
|
||||
checksum = obj.data.BIMMeshProperties.material_checksum
|
||||
return checksum != str([s.id() for s in tool.Geometry.get_styles(obj) if s])
|
||||
|
||||
def has_changed_shading(self, obj):
|
||||
checksum = obj.BIMMaterialProperties.shading_checksum
|
||||
return checksum != repr(np.array(obj.diffuse_color).tobytes())
|
||||
|
||||
def sync_object_placement(self, obj):
|
||||
element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
if not tool.Ifc.is_moved(obj):
|
||||
|
||||
@@ -105,16 +105,16 @@ def active_material_index_callback(obj, data):
|
||||
refresh_ui_data()
|
||||
|
||||
|
||||
def subscribe_to(object, data_path, callback):
|
||||
def subscribe_to(obj, data_path, callback):
|
||||
try:
|
||||
subscribe_to = object.path_resolve(data_path, False)
|
||||
subscribe_to = obj.path_resolve(data_path, False)
|
||||
except:
|
||||
return
|
||||
bpy.msgbus.subscribe_rna(
|
||||
key=subscribe_to,
|
||||
owner=object,
|
||||
owner=obj,
|
||||
args=(
|
||||
object,
|
||||
obj,
|
||||
data_path,
|
||||
),
|
||||
notify=callback,
|
||||
@@ -235,6 +235,11 @@ def setDefaultProperties(scene):
|
||||
ifcopenshell.api.owner.settings.get_application = get_application
|
||||
# TODO: Move to drawing module
|
||||
if len(bpy.context.scene.DocProperties.drawing_styles) == 0:
|
||||
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="0")
|
||||
|
||||
drawing_style = bpy.context.scene.DocProperties.drawing_styles.add()
|
||||
drawing_style.name = "Technical"
|
||||
drawing_style.render_type = "VIEWPORT"
|
||||
@@ -297,8 +302,4 @@ def setDefaultProperties(scene):
|
||||
RasterStyleProperty.OVERLAY_SHOW_RELATIONSHIP_LINES.value: 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")
|
||||
AuthoringData.type_thumbnails = {}
|
||||
|
||||
@@ -24,6 +24,7 @@ import zipfile
|
||||
import tempfile
|
||||
import ifcopenshell
|
||||
import blenderbim.bim.handler
|
||||
import blenderbim.tool as tool
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
@@ -35,7 +36,6 @@ class IfcStore:
|
||||
cache_path = None
|
||||
id_map = {}
|
||||
guid_map = {}
|
||||
deleted_ids = set()
|
||||
edited_objs = set()
|
||||
pset_template_path = ""
|
||||
pset_template_file = None
|
||||
@@ -62,7 +62,6 @@ class IfcStore:
|
||||
IfcStore.cache_path = None
|
||||
IfcStore.id_map = {}
|
||||
IfcStore.guid_map = {}
|
||||
IfcStore.deleted_ids = set()
|
||||
IfcStore.edited_objs = set()
|
||||
IfcStore.pset_template_path = ""
|
||||
IfcStore.pset_template_file = None
|
||||
@@ -277,23 +276,6 @@ class IfcStore:
|
||||
data = {"id": element.id(), "obj": obj.name}
|
||||
IfcStore.commit_link_element(data)
|
||||
|
||||
@staticmethod
|
||||
def delete_element(element):
|
||||
IfcStore.deleted_ids.add(element.id())
|
||||
if IfcStore.history:
|
||||
data = {"id": element.id()}
|
||||
IfcStore.history[-1]["operations"].append(
|
||||
{"rollback": IfcStore.rollback_delete_element, "commit": IfcStore.commit_delete_element, "data": data}
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def rollback_delete_element(data):
|
||||
IfcStore.deleted_ids.remove(data["id"])
|
||||
|
||||
@staticmethod
|
||||
def commit_delete_element(data):
|
||||
IfcStore.deleted_ids.add(data["id"])
|
||||
|
||||
@staticmethod
|
||||
def link_element(element, obj):
|
||||
existing_obj = IfcStore.id_map.get(element.id(), None)
|
||||
@@ -371,7 +353,7 @@ class IfcStore:
|
||||
def unlink_element(element=None, obj=None):
|
||||
if element is None:
|
||||
try:
|
||||
element = IfcStore.get_file().by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
@@ -60,7 +60,6 @@ class MaterialCreator:
|
||||
|
||||
def create(self, element, obj, mesh):
|
||||
self.mesh = mesh
|
||||
self.obj = obj
|
||||
if (hasattr(element, "Representation") and not element.Representation) or (
|
||||
hasattr(element, "RepresentationMaps") and not element.RepresentationMaps
|
||||
):
|
||||
@@ -140,11 +139,11 @@ class MaterialCreator:
|
||||
def assign_material_slots_to_faces(self):
|
||||
if "ios_materials" not in self.mesh or not self.mesh["ios_materials"]:
|
||||
return
|
||||
if len(self.obj.material_slots) == 1:
|
||||
if len(self.mesh.materials) == 1:
|
||||
return
|
||||
material_to_slot = {}
|
||||
for i, material in enumerate(self.mesh["ios_materials"]):
|
||||
slot_index = self.obj.material_slots.find(self.styles[material].name)
|
||||
slot_index = self.mesh.materials.find(self.styles[material].name)
|
||||
material_to_slot[i] = slot_index
|
||||
|
||||
if len(self.mesh.polygons) == len(self.mesh["ios_material_ids"]):
|
||||
@@ -242,6 +241,8 @@ class IfcImporter:
|
||||
self.profile_code("Create native elements")
|
||||
self.create_elements()
|
||||
self.profile_code("Create elements")
|
||||
self.create_generic_elements(self.annotations)
|
||||
self.profile_code("Create annotations")
|
||||
self.create_grids()
|
||||
self.profile_code("Create grids")
|
||||
self.create_spatial_elements()
|
||||
@@ -321,10 +322,13 @@ class IfcImporter:
|
||||
if isinstance(self.elements, set):
|
||||
self.elements = list(self.elements)
|
||||
# TODO: enable filtering for annotations
|
||||
self.annotations = set(self.file.by_type("IfcAnnotation"))
|
||||
self.annotations = set([a for a in self.file.by_type("IfcAnnotation") if not a.HasAssignments])
|
||||
else:
|
||||
self.elements = self.file.by_type("IfcElement")
|
||||
self.annotations = set(self.file.by_type("IfcAnnotation"))
|
||||
if self.file.schema in ("IFC2X3", "IFC4"):
|
||||
self.elements = self.file.by_type("IfcElement") + self.file.by_type("IfcProxy")
|
||||
else:
|
||||
self.elements = self.file.by_type("IfcElement")
|
||||
self.annotations = set([a for a in self.file.by_type("IfcAnnotation") if not a.HasAssignments])
|
||||
|
||||
self.elements = [e for e in self.elements if not e.is_a("IfcFeatureElement")]
|
||||
if self.ifc_import_settings.is_coordinating:
|
||||
@@ -335,23 +339,7 @@ class IfcImporter:
|
||||
if self.ifc_import_settings.has_filter or offset or offset_limit < len(self.elements):
|
||||
self.element_types = set([ifcopenshell.util.element.get_type(e) for e in self.elements])
|
||||
else:
|
||||
if self.file.schema == "IFC2X3":
|
||||
self.element_types = set(
|
||||
self.file.by_type("IfcElementType")
|
||||
+ self.file.by_type("IfcDoorStyle")
|
||||
+ self.file.by_type("IfcWindowStyle")
|
||||
)
|
||||
elif self.file.schema == "IFC4":
|
||||
self.element_types = set(
|
||||
self.file.by_type("IfcElementType")
|
||||
+ self.file.by_type("IfcDoorStyle")
|
||||
+ self.file.by_type("IfcWindowStyle")
|
||||
+ self.file.by_type("IfcSpatialElementType")
|
||||
)
|
||||
else:
|
||||
self.element_types = set(
|
||||
self.file.by_type("IfcElementType") + self.file.by_type("IfcSpatialElementType")
|
||||
)
|
||||
self.element_types = set(self.file.by_type("IfcTypeProduct"))
|
||||
|
||||
if self.ifc_import_settings.has_filter and self.ifc_import_settings.should_filter_spatial_elements:
|
||||
self.spatial_elements = self.get_spatial_elements_filtered_by_elements(self.elements)
|
||||
@@ -921,9 +909,6 @@ class IfcImporter:
|
||||
|
||||
if mesh:
|
||||
pass
|
||||
elif element.is_a("IfcAnnotation") and element.ObjectType == "DRAWING":
|
||||
mesh = self.create_camera(element, shape)
|
||||
tool.Loader.link_mesh(shape, mesh)
|
||||
elif element.is_a("IfcAnnotation") and self.is_curve_annotation(element) and shape:
|
||||
mesh = self.create_curve(element, shape)
|
||||
tool.Loader.link_mesh(shape, mesh)
|
||||
@@ -1834,54 +1819,6 @@ class IfcImporter:
|
||||
):
|
||||
return representation.Items[0].MappingTarget
|
||||
|
||||
def create_camera(self, element, shape):
|
||||
if hasattr(shape, "geometry"):
|
||||
geometry = shape.geometry
|
||||
else:
|
||||
geometry = shape
|
||||
|
||||
v = geometry.verts
|
||||
x = [v[i] for i in range(0, len(v), 3)]
|
||||
y = [v[i + 1] for i in range(0, len(v), 3)]
|
||||
z = [v[i + 2] for i in range(0, len(v), 3)]
|
||||
width = max(x) - min(x)
|
||||
height = max(y) - min(y)
|
||||
depth = max(z) - min(z)
|
||||
|
||||
camera = bpy.data.cameras.new(tool.Loader.get_mesh_name(geometry))
|
||||
camera.type = "ORTHO"
|
||||
camera.ortho_scale = width if width > height else height
|
||||
camera.clip_end = depth
|
||||
|
||||
if width > height:
|
||||
camera.BIMCameraProperties.raster_x = 1000
|
||||
camera.BIMCameraProperties.raster_y = round(1000 * (height / width))
|
||||
else:
|
||||
camera.BIMCameraProperties.raster_x = round(1000 * (width / height))
|
||||
camera.BIMCameraProperties.raster_y = 1000
|
||||
|
||||
psets = ifcopenshell.util.element.get_psets(element)
|
||||
pset = psets.get("EPset_Drawing")
|
||||
if pset:
|
||||
if "TargetView" in pset:
|
||||
camera.BIMCameraProperties.target_view = pset["TargetView"]
|
||||
if "Scale" in pset:
|
||||
valid_scales = [
|
||||
i[0] for i in get_diagram_scales(None, bpy.context) if pset["Scale"] == i[0].split("|")[-1]
|
||||
]
|
||||
if valid_scales:
|
||||
camera.BIMCameraProperties.diagram_scale = valid_scales[0]
|
||||
else:
|
||||
camera.BIMCameraProperties.diagram_scale = "CUSTOM"
|
||||
camera.BIMCameraProperties.custom_diagram_scale = pset["Scale"]
|
||||
if "HasUnderlay" in pset:
|
||||
camera.BIMCameraProperties.has_underlay = pset["HasUnderlay"]
|
||||
if "HasLinework" in pset:
|
||||
camera.BIMCameraProperties.has_linework = pset["HasLinework"]
|
||||
if "HasAnnotation" in pset:
|
||||
camera.BIMCameraProperties.has_annotation = pset["HasAnnotation"]
|
||||
return camera
|
||||
|
||||
def create_curve(self, element, shape):
|
||||
if hasattr(shape, "geometry"):
|
||||
geometry = shape.geometry
|
||||
|
||||
@@ -59,9 +59,13 @@ class MaterialAttributesData:
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.data = {"attributes": cls.attributes()}
|
||||
cls.data = {"ifc_definition_id": cls.ifc_definition_id(), "attributes": cls.attributes()}
|
||||
cls.is_loaded = True
|
||||
|
||||
@classmethod
|
||||
def ifc_definition_id(cls):
|
||||
return bpy.context.active_object.active_material.BIMObjectProperties.ifc_definition_id
|
||||
|
||||
@classmethod
|
||||
def attributes(cls):
|
||||
results = []
|
||||
|
||||
@@ -50,7 +50,20 @@ class EnableEditingAttributes(bpy.types.Operator):
|
||||
oprops = obj.BIMObjectProperties
|
||||
props = obj.BIMAttributeProperties
|
||||
props.attributes.clear()
|
||||
blenderbim.bim.helper.import_attributes2(tool.Ifc.get().by_id(oprops.ifc_definition_id), props.attributes)
|
||||
|
||||
def callback(name, prop, data):
|
||||
if name in ("RefLatitude", "RefLongitude"):
|
||||
new = props.attributes.add()
|
||||
new.name = name
|
||||
new.is_null = data[name] is None
|
||||
new.is_optional = True
|
||||
new.data_type = "string"
|
||||
new.ifc_class = data["type"]
|
||||
new.string_value = "" if new.is_null else json.dumps(data[name])
|
||||
|
||||
blenderbim.bim.helper.import_attributes2(
|
||||
tool.Ifc.get().by_id(oprops.ifc_definition_id), props.attributes, callback=callback
|
||||
)
|
||||
props.is_editing_attributes = True
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -87,7 +100,19 @@ class EditAttributes(bpy.types.Operator, Operator):
|
||||
obj = bpy.data.materials.get(self.obj)
|
||||
props = obj.BIMAttributeProperties
|
||||
product = tool.Ifc.get_entity(obj)
|
||||
attributes = blenderbim.bim.helper.export_attributes(props.attributes)
|
||||
|
||||
def callback(attributes, prop):
|
||||
if prop.name in ("RefLatitude", "RefLongitude"):
|
||||
if prop.is_null:
|
||||
attributes[prop.name] = None
|
||||
else:
|
||||
try:
|
||||
attributes[prop.name] = json.loads(prop.string_value)
|
||||
except:
|
||||
attributes[prop.name] = None
|
||||
return True
|
||||
|
||||
attributes = blenderbim.bim.helper.export_attributes(props.attributes, callback=callback)
|
||||
ifcopenshell.api.run("attribute.edit_attributes", self.file, product=product, attributes=attributes)
|
||||
bpy.ops.bim.disable_editing_attributes(obj=obj.name, obj_type=self.obj_type)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -94,4 +94,10 @@ class BIM_PT_material_attributes(Panel):
|
||||
def draw(self, context):
|
||||
if not MaterialAttributesData.is_loaded:
|
||||
MaterialAttributesData.load()
|
||||
elif (
|
||||
context.active_object.active_material.BIMObjectProperties.ifc_definition_id
|
||||
!= MaterialAttributesData.data["ifc_definition_id"]
|
||||
):
|
||||
MaterialAttributesData.load()
|
||||
|
||||
draw_ui(context, self.layout, "Material", MaterialAttributesData.data["attributes"])
|
||||
|
||||
@@ -218,7 +218,7 @@ class EditBcfTopicName(bpy.types.Operator):
|
||||
props = context.scene.BCFProperties
|
||||
blender_topic = props.active_topic
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
topic = bcfxml.topics[blender_topic.name].topic
|
||||
topic.title = blender_topic.title
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -233,7 +233,7 @@ class EditBcfTopic(bpy.types.Operator):
|
||||
blender_topic = props.active_topic
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
topic = bcfxml.topics[blender_topic.name].topic
|
||||
topic.title = blender_topic.title or None
|
||||
topic.priority = blender_topic.priority or None
|
||||
topic.due_date = blender_topic.due_date or None
|
||||
@@ -940,7 +940,7 @@ class ActivateBcfViewpoint(bpy.types.Operator):
|
||||
context.area.type = old
|
||||
for global_id in exception_global_ids:
|
||||
obj = IfcStore.get_element(global_id)
|
||||
if obj:
|
||||
if obj and bpy.context.view_layer.objects.get(obj.name):
|
||||
obj.hide_set(True)
|
||||
else:
|
||||
objs = []
|
||||
|
||||
@@ -119,7 +119,7 @@ class Loader:
|
||||
|
||||
class LoadProjectSpaceBoundaries(bpy.types.Operator):
|
||||
bl_idname = "bim.load_project_space_boundaries"
|
||||
bl_label = "Load all project space boundaries"
|
||||
bl_label = "Load All Project Space Boundaries"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
@@ -131,7 +131,7 @@ class LoadProjectSpaceBoundaries(bpy.types.Operator):
|
||||
|
||||
class LoadBoundary(bpy.types.Operator):
|
||||
bl_idname = "bim.load_boundary"
|
||||
bl_label = "Load boundary"
|
||||
bl_label = "Load Boundary"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
boundary_id: bpy.props.IntProperty()
|
||||
|
||||
@@ -147,7 +147,7 @@ class LoadBoundary(bpy.types.Operator):
|
||||
|
||||
class LoadSpaceBoundaries(bpy.types.Operator):
|
||||
bl_idname = "bim.load_space_boundaries"
|
||||
bl_label = "Load selected space boundaries"
|
||||
bl_label = "Load Selected Space Boundaries"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
@@ -168,7 +168,7 @@ def get_element_boundaries(element):
|
||||
|
||||
class SelectRelatedElementBoundaries(bpy.types.Operator):
|
||||
bl_idname = "bim.select_related_element_boundaries"
|
||||
bl_label = "Select related element space boundaries"
|
||||
bl_label = "Select Related Element Space Boundaries"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
related_element: bpy.props.IntProperty()
|
||||
|
||||
@@ -185,7 +185,7 @@ class SelectRelatedElementBoundaries(bpy.types.Operator):
|
||||
|
||||
class SelectRelatedElementTypeBoundaries(bpy.types.Operator):
|
||||
bl_idname = "bim.select_related_element_type_boundaries"
|
||||
bl_label = "Select related element type space boundaries"
|
||||
bl_label = "Select Related Element Type Space Boundaries"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
related_element: bpy.props.IntProperty()
|
||||
|
||||
@@ -208,7 +208,7 @@ class SelectRelatedElementTypeBoundaries(bpy.types.Operator):
|
||||
|
||||
class SelectSpaceBoundaries(bpy.types.Operator):
|
||||
bl_idname = "bim.select_space_boundaries"
|
||||
bl_label = "Select all space boundaries"
|
||||
bl_label = "Select All Space Boundaries"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
@@ -224,7 +224,7 @@ class SelectSpaceBoundaries(bpy.types.Operator):
|
||||
|
||||
class SelectProjectBoundaries(bpy.types.Operator):
|
||||
bl_idname = "bim.select_project_space_boundaries"
|
||||
bl_label = "Select all project space boundaries"
|
||||
bl_label = "Select All Project Space Boundaries"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
@@ -260,7 +260,7 @@ def get_colour(ifc_boundary):
|
||||
|
||||
class ColourByRelatedBuildingElement(bpy.types.Operator):
|
||||
bl_idname = "bim.colour_by_related_building_element"
|
||||
bl_label = "Apply colour based on related building elements"
|
||||
bl_label = "Apply Colour Based on Related Building Elements"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
@@ -308,7 +308,7 @@ EDITABLE_ATTRIBUTES = {
|
||||
|
||||
class EnableEditingBoundary(bpy.types.Operator):
|
||||
bl_idname = "bim.enable_editing_boundary"
|
||||
bl_label = "Edit boundary relations"
|
||||
bl_label = "Edit Boundary Relations"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
@@ -327,7 +327,7 @@ class EnableEditingBoundary(bpy.types.Operator):
|
||||
|
||||
class DisableEditingBoundary(bpy.types.Operator):
|
||||
bl_idname = "bim.disable_editing_boundary"
|
||||
bl_label = "Disable editing boundary relations"
|
||||
bl_label = "Disable Editing Boundary Relations"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
@@ -340,7 +340,7 @@ class DisableEditingBoundary(bpy.types.Operator):
|
||||
|
||||
class EditBoundaryAttributes(bpy.types.Operator):
|
||||
bl_idname = "bim.edit_boundary_attributes"
|
||||
bl_label = "Disable editing boundary relations"
|
||||
bl_label = "Disable Editing Boundary Relations"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
@@ -361,7 +361,7 @@ class EditBoundaryAttributes(bpy.types.Operator):
|
||||
|
||||
class UpdateBoundaryGeometry(bpy.types.Operator):
|
||||
bl_idname = "bim.update_boundary_geometry"
|
||||
bl_label = "Update boundary geometry"
|
||||
bl_label = "Update Boundary Geometry"
|
||||
bl_description = """
|
||||
Update boundary connection geometry from mesh.
|
||||
Mesh must lie on a single plane. It should look like a face or a face with holes.
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
|
||||
import bpy
|
||||
from blenderbim.bim.module.model.data import AuthoringData
|
||||
from blenderbim.bim.module.model.root import ConstrTypeEntityNotFound
|
||||
from bpy.types import PropertyGroup
|
||||
from math import pi
|
||||
|
||||
@@ -32,3 +31,4 @@ class BIMCadProperties(PropertyGroup):
|
||||
gable_roof_edge_angle: bpy.props.FloatProperty(
|
||||
name="Gable Roof Edge Angle", default=pi / 2, soft_min=0, soft_max=pi / 2, subtype="ANGLE"
|
||||
)
|
||||
gable_roof_separate_verts: bpy.props.BoolProperty(name="Separate Verts", default=True)
|
||||
|
||||
@@ -156,7 +156,7 @@ class CadTool(WorkSpaceTool):
|
||||
row = layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_R")
|
||||
row.operator("bim.hotkey", text="Set gable roof angle").hotkey = "S_R"
|
||||
row.operator("bim.cad_hotkey", text="Set Gable Roof Angle").hotkey = "S_R"
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
@@ -229,6 +229,7 @@ class CadHotkey(bpy.types.Operator):
|
||||
and bpy.context.active_object.BIMRoofProperties.is_editing_path
|
||||
):
|
||||
self.layout.row().prop(props, "gable_roof_edge_angle")
|
||||
self.layout.row().prop(props, "gable_roof_separate_verts")
|
||||
|
||||
elif self.hotkey == "S_V":
|
||||
if not self.is_profile():
|
||||
@@ -285,7 +286,9 @@ class CadHotkey(bpy.types.Operator):
|
||||
and RoofData.data["parameters"]
|
||||
and bpy.context.active_object.BIMRoofProperties.is_editing_path
|
||||
):
|
||||
bpy.ops.bim.set_gable_roof_edge_angle(angle=self.props.gable_roof_edge_angle)
|
||||
bpy.ops.bim.set_gable_roof_edge_angle(
|
||||
angle=self.props.gable_roof_edge_angle, separate_verts=self.props.gable_roof_separate_verts
|
||||
)
|
||||
|
||||
def hotkey_S_T(self):
|
||||
bpy.ops.bim.cad_mitre()
|
||||
|
||||
@@ -149,7 +149,7 @@ class ContractCostItem(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
class RemoveCostItem(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.remove_cost_item"
|
||||
bl_label = "Remove Cost item"
|
||||
bl_label = "Remove Cost Item"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
cost_item: bpy.props.IntProperty()
|
||||
|
||||
|
||||
@@ -17,11 +17,12 @@
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
from . import ui, prop, operator, handler, gizmos
|
||||
from . import ui, prop, operator, handler, gizmos, workspace
|
||||
|
||||
classes = (
|
||||
operator.ActivateDrawing,
|
||||
operator.ActivateDrawingStyle,
|
||||
operator.ActivateView,
|
||||
operator.ActivateModel,
|
||||
operator.AddAnnotation,
|
||||
operator.AddDrawing,
|
||||
operator.AddDrawingStyle,
|
||||
@@ -30,6 +31,7 @@ classes = (
|
||||
operator.AddSchedule,
|
||||
operator.AddScheduleToSheet,
|
||||
operator.AddSheet,
|
||||
operator.AddTextLiteral,
|
||||
operator.BuildSchedule,
|
||||
operator.CleanWireframes,
|
||||
operator.ContractSheet,
|
||||
@@ -40,32 +42,32 @@ classes = (
|
||||
operator.DisableEditingSchedules,
|
||||
operator.DisableEditingSheets,
|
||||
operator.DisableEditingText,
|
||||
operator.AddTextLiteral,
|
||||
operator.RemoveTextLiteral,
|
||||
operator.DuplicateDrawing,
|
||||
operator.EditAssignedProduct,
|
||||
operator.EditSheet,
|
||||
operator.EditText,
|
||||
operator.EditTextPopup,
|
||||
operator.EditVectorStyle,
|
||||
operator.EnableEditingAssignedProduct,
|
||||
operator.EnableEditingText,
|
||||
operator.ExpandSheet,
|
||||
operator.LoadDrawings,
|
||||
operator.LoadSchedules,
|
||||
operator.LoadSheets,
|
||||
operator.OpenDrawing,
|
||||
operator.OpenSchedule,
|
||||
operator.OpenSheet,
|
||||
operator.OpenView,
|
||||
operator.RemoveDrawing,
|
||||
operator.RemoveDrawingFromSheet,
|
||||
operator.RemoveDrawingStyle,
|
||||
operator.RemoveDrawingStyleAttribute,
|
||||
operator.RemoveSchedule,
|
||||
operator.RemoveSheet,
|
||||
operator.RemoveTextLiteral,
|
||||
operator.SelectAllDrawings,
|
||||
operator.ResizeText,
|
||||
operator.SaveDrawingStyle,
|
||||
operator.SelectDocIfcFile,
|
||||
operator.SelectAssignedProduct,
|
||||
operator.SelectDocIfcFile,
|
||||
prop.Variable,
|
||||
prop.Drawing,
|
||||
prop.Schedule,
|
||||
@@ -76,6 +78,7 @@ classes = (
|
||||
prop.Literal,
|
||||
prop.BIMTextProperties,
|
||||
prop.BIMAssignedProductProperties,
|
||||
prop.BIMAnnotationProperties,
|
||||
ui.BIM_PT_camera,
|
||||
ui.BIM_PT_drawing_underlay,
|
||||
ui.BIM_PT_annotation_utilities,
|
||||
@@ -91,11 +94,15 @@ classes = (
|
||||
gizmos.DimensionLabelGizmo,
|
||||
gizmos.ExtrusionGuidesGizmo,
|
||||
gizmos.ExtrusionWidget,
|
||||
workspace.Hotkey,
|
||||
)
|
||||
|
||||
|
||||
def register():
|
||||
if not bpy.app.background:
|
||||
bpy.utils.register_tool(workspace.AnnotationTool, after={"bim.bim_tool"}, separator=True, group=True)
|
||||
bpy.types.Scene.DocProperties = bpy.props.PointerProperty(type=prop.DocProperties)
|
||||
bpy.types.Scene.BIMAnnotationProperties = bpy.props.PointerProperty(type=prop.BIMAnnotationProperties)
|
||||
bpy.types.Camera.BIMCameraProperties = bpy.props.PointerProperty(type=prop.BIMCameraProperties)
|
||||
bpy.types.Object.BIMAssignedProductProperties = bpy.props.PointerProperty(type=prop.BIMAssignedProductProperties)
|
||||
bpy.types.Object.BIMTextProperties = bpy.props.PointerProperty(type=prop.BIMTextProperties)
|
||||
@@ -105,7 +112,10 @@ def register():
|
||||
|
||||
|
||||
def unregister():
|
||||
if not bpy.app.background:
|
||||
bpy.utils.unregister_tool(workspace.AnnotationTool)
|
||||
del bpy.types.Scene.DocProperties
|
||||
del bpy.types.Scene.BIMAnnotationProperties
|
||||
del bpy.types.Camera.BIMCameraProperties
|
||||
del bpy.types.Object.BIMAssignedProductProperties
|
||||
del bpy.types.Object.BIMTextProperties
|
||||
|
||||
@@ -77,66 +77,35 @@ class Annotator:
|
||||
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
|
||||
# default order = bot left, top left, bot right, top right
|
||||
# therefore we redefine the order
|
||||
face_verts = [0, 2, 3, 1]
|
||||
|
||||
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
|
||||
verts_world_space = Annotator.get_placeholder_coords()
|
||||
verts_local = [obj.matrix_world.inverted() @ v for v in verts_world_space]
|
||||
bm = tool.Blender.get_bmesh_for_mesh(obj.data, clean=True)
|
||||
new_verts = [bm.verts.new(v) for v in verts_local]
|
||||
|
||||
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
|
||||
bm.faces.new([new_verts[i] for i in face_verts])
|
||||
tool.Blender.apply_bmesh(obj.data, bm, obj)
|
||||
return obj
|
||||
|
||||
@staticmethod
|
||||
@@ -144,15 +113,15 @@ class Annotator:
|
||||
camera = tool.Ifc.get_object(drawing)
|
||||
co1, _, _, _ = Annotator.get_placeholder_coords(camera)
|
||||
matrix_world = camera.matrix_world.copy()
|
||||
matrix_world[0][3] = co1.x
|
||||
matrix_world[1][3] = co1.y
|
||||
matrix_world[2][3] = co1.z
|
||||
matrix_world.translation = co1
|
||||
collection = camera.users_collection[0]
|
||||
|
||||
if object_type == "TEXT":
|
||||
obj = bpy.data.objects.new(object_type, None)
|
||||
obj.matrix_world = matrix_world
|
||||
collection.objects.link(obj)
|
||||
return obj
|
||||
|
||||
elif object_type in ("TEXT_LEADER", "SECTION_LEVEL"):
|
||||
data = bpy.data.curves.new(object_type, type="CURVE")
|
||||
data.dimensions = "3D"
|
||||
@@ -161,17 +130,20 @@ class Annotator:
|
||||
obj.matrix_world = matrix_world
|
||||
collection.objects.link(obj)
|
||||
return obj
|
||||
|
||||
if object_type != "ANGLE":
|
||||
for obj in collection.objects:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if element and element.ObjectType == object_type and obj.type == object_type.upper():
|
||||
return obj
|
||||
|
||||
if data_type == "mesh":
|
||||
data = bpy.data.meshes.new(object_type)
|
||||
elif data_type == "curve":
|
||||
data = bpy.data.curves.new(object_type, type="CURVE")
|
||||
data.dimensions = "3D"
|
||||
data.resolution_u = 2
|
||||
|
||||
obj = bpy.data.objects.new(object_type, data)
|
||||
obj.matrix_world = matrix_world
|
||||
collection.objects.link(obj)
|
||||
@@ -182,14 +154,13 @@ class Annotator:
|
||||
if not camera:
|
||||
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 = camera.data.ortho_scale / 4
|
||||
|
||||
res_x = bpy.context.scene.render.resolution_x
|
||||
res_y = bpy.context.scene.render.resolution_y
|
||||
if res_x > res_y:
|
||||
y *= res_y / res_x
|
||||
|
||||
y_offset = camera.matrix_world.to_quaternion() @ Vector((0, y, 0))
|
||||
x_offset = camera.matrix_world.to_quaternion() @ Vector((y / 2, 0, 0))
|
||||
return (
|
||||
|
||||
@@ -16,10 +16,12 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
import bpy
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.representation
|
||||
import blenderbim.tool as tool
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def refresh():
|
||||
@@ -27,7 +29,9 @@ def refresh():
|
||||
SheetsData.is_loaded = False
|
||||
SchedulesData.is_loaded = False
|
||||
DrawingsData.is_loaded = False
|
||||
AnnotationData.is_loaded = False
|
||||
DecoratorData.data = {}
|
||||
DecoratorData.cut_cache = {}
|
||||
|
||||
|
||||
class ProductAssignmentsData:
|
||||
@@ -56,12 +60,36 @@ class SheetsData:
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.data = {"total_sheets": cls.total_sheets()}
|
||||
cls.data = {
|
||||
"has_saved_ifc": cls.has_saved_ifc(),
|
||||
"total_sheets": cls.total_sheets(),
|
||||
"titleblocks": cls.titleblocks(),
|
||||
}
|
||||
cls.is_loaded = True
|
||||
|
||||
@classmethod
|
||||
def has_saved_ifc(cls):
|
||||
return os.path.isfile(tool.Ifc.get_path())
|
||||
|
||||
@classmethod
|
||||
def total_sheets(cls):
|
||||
return len([d for d in tool.Ifc.get().by_type("IfcDocumentInformation") if d.Scope == "DOCUMENTATION"])
|
||||
return len([d for d in tool.Ifc.get().by_type("IfcDocumentInformation") if d.Scope == "SHEET"])
|
||||
|
||||
@classmethod
|
||||
def titleblocks(cls):
|
||||
files = Path(os.path.join(bpy.context.scene.BIMProperties.data_dir, "templates", "titleblocks")).glob("*.svg")
|
||||
files = [str(f.stem) for f in files]
|
||||
|
||||
if tool.Ifc.get():
|
||||
project = tool.Ifc.get().by_type("IfcProject")[0]
|
||||
titleblocks_dir = ifcopenshell.util.element.get_pset(project, "BBIM_Documentation", "TitleblocksDir")
|
||||
if not titleblocks_dir:
|
||||
titleblocks_dir = bpy.context.scene.DocProperties.titleblocks_dir
|
||||
titleblocks_dir = tool.Ifc.resolve_uri(titleblocks_dir)
|
||||
if os.path.exists(titleblocks_dir):
|
||||
files.extend([str(f.stem) for f in Path(titleblocks_dir).glob("*.svg")])
|
||||
|
||||
return [(f, f, "") for f in sorted(list(set(files)))]
|
||||
|
||||
|
||||
class DrawingsData:
|
||||
@@ -70,9 +98,17 @@ class DrawingsData:
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.data = {"total_drawings": cls.total_drawings(), "location_hint": cls.location_hint()}
|
||||
cls.data = {
|
||||
"has_saved_ifc": cls.has_saved_ifc(),
|
||||
"total_drawings": cls.total_drawings(),
|
||||
"location_hint": cls.location_hint(),
|
||||
}
|
||||
cls.is_loaded = True
|
||||
|
||||
@classmethod
|
||||
def has_saved_ifc(cls):
|
||||
return os.path.isfile(tool.Ifc.get_path())
|
||||
|
||||
@classmethod
|
||||
def total_drawings(cls):
|
||||
return len([e for e in tool.Ifc.get().by_type("IfcAnnotation") if e.ObjectType == "DRAWING"])
|
||||
@@ -94,9 +130,13 @@ class SchedulesData:
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.data = {"total_schedules": cls.total_schedules()}
|
||||
cls.data = {"has_saved_ifc": cls.has_saved_ifc(), "total_schedules": cls.total_schedules()}
|
||||
cls.is_loaded = True
|
||||
|
||||
@classmethod
|
||||
def has_saved_ifc(cls):
|
||||
return os.path.isfile(tool.Ifc.get_path())
|
||||
|
||||
@classmethod
|
||||
def total_schedules(cls):
|
||||
return len([d for d in tool.Ifc.get().by_type("IfcDocumentInformation") if d.Scope == "SCHEDULE"])
|
||||
@@ -114,6 +154,7 @@ FONT_SIZES = {
|
||||
class DecoratorData:
|
||||
# stores 1 type of data per object
|
||||
data = {}
|
||||
cut_cache = {}
|
||||
|
||||
# used by Ifc Annotations with ObjectType = "BATTING"
|
||||
@classmethod
|
||||
@@ -171,7 +212,7 @@ class DecoratorData:
|
||||
cls.data[obj.name] = display_data
|
||||
return display_data
|
||||
|
||||
# used by Ifc Annotations with ObjectType = "TEXT"
|
||||
# used by Ifc Annotations with ObjectType = "TEXT" / "TEXT_LEADER"
|
||||
@classmethod
|
||||
def get_ifc_text_data(cls, obj):
|
||||
"""returns font size in mm for current ifc text object"""
|
||||
@@ -180,7 +221,7 @@ class DecoratorData:
|
||||
return result
|
||||
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element or not element.is_a("IfcAnnotation") or element.ObjectType not in ["TEXT", "TEXT_LEADER"]:
|
||||
if not element or not tool.Drawing.is_annotation_object_type(element, ["TEXT", "TEXT_LEADER"]):
|
||||
return None
|
||||
|
||||
props = obj.BIMTextProperties
|
||||
@@ -218,15 +259,16 @@ class DecoratorData:
|
||||
cls.data[obj.name] = text_data
|
||||
return text_data
|
||||
|
||||
# used by Ifc Annotations with ObjectType = "DIMENSION"
|
||||
# used by Ifc Annotations with ObjectType = "DIMENSION" / "DIAMETER"
|
||||
@classmethod
|
||||
def get_dimension_style(cls, obj):
|
||||
def get_dimension_data(cls, obj):
|
||||
result = cls.data.get(obj.name, None)
|
||||
if result is not None:
|
||||
return result
|
||||
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element or not element.is_a("IfcAnnotation") or element.ObjectType != "DIMENSION":
|
||||
supported_object_types = ("DIMENSION", "DIAMETER")
|
||||
if not element or not element.is_a("IfcAnnotation") or element.ObjectType not in supported_object_types:
|
||||
return None
|
||||
|
||||
dimension_style = "arrow"
|
||||
@@ -234,5 +276,39 @@ class DecoratorData:
|
||||
if classes and "oblique" in classes.lower().split():
|
||||
dimension_style = "oblique"
|
||||
|
||||
cls.data[obj.name] = dimension_style
|
||||
return dimension_style
|
||||
pset_data = ifcopenshell.util.element.get_pset(element, "BBIM_Dimension") or {}
|
||||
show_description_only = pset_data.get("ShowDescriptionOnly", False)
|
||||
suppress_zero_inches = pset_data.get("SuppressZeroInches", False)
|
||||
|
||||
dimension_data = {
|
||||
"dimension_style": dimension_style,
|
||||
"show_description_only": show_description_only,
|
||||
"suppress_zero_inches": suppress_zero_inches,
|
||||
}
|
||||
cls.data[obj.name] = dimension_data
|
||||
return dimension_data
|
||||
|
||||
|
||||
class AnnotationData:
|
||||
data = {}
|
||||
is_loaded = False
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.is_loaded = True
|
||||
cls.props = bpy.context.scene.BIMAnnotationProperties
|
||||
cls.data["relating_types"] = cls.get_relating_types()
|
||||
|
||||
@classmethod
|
||||
def get_relating_types(cls):
|
||||
object_type = cls.props.object_type
|
||||
relating_types = []
|
||||
for relating_type in tool.Ifc.get().by_type("IfcTypeProduct"):
|
||||
if tool.Drawing.is_annotation_object_type(relating_type, object_type):
|
||||
relating_types.append(relating_type)
|
||||
|
||||
enum_items = [(str(e.id()), e.Name or "Unnamed", e.Description or "") for e in relating_types]
|
||||
|
||||
# item to create anootations without relating types
|
||||
enum_items.insert(0, ("0", "-", ""))
|
||||
return enum_items
|
||||
|
||||
@@ -18,10 +18,9 @@
|
||||
|
||||
import bpy
|
||||
import blf
|
||||
import math
|
||||
import gpu, bgl
|
||||
import gpu
|
||||
from bpy import types
|
||||
from mathutils import Vector, Matrix
|
||||
from mathutils import Vector
|
||||
from mathutils import geometry
|
||||
from bpy_extras import view3d_utils
|
||||
from blenderbim.bim.module.drawing.shaders import DotsGizmoShader, ExtrusionGuidesShader, BaseLinesShader
|
||||
@@ -273,7 +272,7 @@ class CustomGizmo:
|
||||
gpu.matrix.multiply_matrix(self.matrix_world)
|
||||
batch.draw()
|
||||
|
||||
bgl.glDisable(bgl.GL_BLEND)
|
||||
gpu.state.blend_set("NONE")
|
||||
|
||||
|
||||
class OffsetHandle:
|
||||
@@ -500,7 +499,7 @@ class ExtrusionWidget(types.GizmoGroup):
|
||||
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.alpha = gz.alpha_highlight = 0.75
|
||||
gz.use_draw_modal = True
|
||||
gz.target_set_prop("depth", prop, "value")
|
||||
gz.scale_value = scale_value
|
||||
|
||||
@@ -23,8 +23,7 @@ from bpy.app.handlers import persistent
|
||||
|
||||
@persistent
|
||||
def toggleDecorationsOnLoad(*args):
|
||||
toggle = bpy.context.scene.DocProperties.should_draw_decorations
|
||||
if toggle:
|
||||
if bpy.context.scene.DocProperties.should_draw_decorations:
|
||||
decoration.DecorationsHandler.install(bpy.context)
|
||||
else:
|
||||
decoration.DecorationsHandler.uninstall()
|
||||
|
||||
@@ -120,9 +120,9 @@ class BoundingBox:
|
||||
|
||||
# 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):
|
||||
def format_distance(
|
||||
value, isArea=False, hide_units=True, precision=None, decimal_places=None, suppress_zero_inches=False
|
||||
):
|
||||
s_code = "\u00b2" # Superscript two THIS IS LEGACY (but being kept for when Area Measurements are re-implimented)
|
||||
|
||||
# Get Scene Unit Settings
|
||||
@@ -131,7 +131,7 @@ def format_distance(value, isArea=False, hide_units=True):
|
||||
unit_length = bpy.context.scene.unit_settings.length_unit
|
||||
|
||||
toInches = 39.3700787401574887
|
||||
inPerFoot = 11.999
|
||||
inPerFoot = 11.9999
|
||||
|
||||
if isArea:
|
||||
toInches = 1550
|
||||
@@ -141,8 +141,7 @@ def format_distance(value, isArea=False, hide_units=True):
|
||||
|
||||
# Imperial Formatting
|
||||
if unit_system == "IMPERIAL":
|
||||
precision = bpy.context.scene.BIMProperties.imperial_precision
|
||||
if precision == "NONE":
|
||||
if not precision:
|
||||
precision = 256
|
||||
elif precision == "1":
|
||||
precision = 1
|
||||
@@ -186,44 +185,50 @@ def format_distance(value, isArea=False, hide_units=True):
|
||||
inches = 0
|
||||
|
||||
if not isArea:
|
||||
add_inches = bool(inches) or not suppress_zero_inches
|
||||
tx_dist = ""
|
||||
if feet:
|
||||
tx_dist += str(feet) + "'"
|
||||
if feet and inches:
|
||||
if feet and add_inches:
|
||||
tx_dist += " - "
|
||||
if inches:
|
||||
if add_inches:
|
||||
tx_dist += str(inches)
|
||||
if inches and frac:
|
||||
if add_inches and frac:
|
||||
tx_dist += " "
|
||||
if frac:
|
||||
tx_dist += str(frac) + "/" + str(base)
|
||||
if inches or frac:
|
||||
if add_inches or frac:
|
||||
tx_dist += '"'
|
||||
else:
|
||||
tx_dist = str("%1.3f" % (value * toInches / inPerFoot)) + " sq. ft."
|
||||
|
||||
# METRIC FORMATTING
|
||||
elif unit_system == "METRIC":
|
||||
precision = bpy.context.scene.BIMProperties.metric_precision
|
||||
if precision != 0:
|
||||
if precision:
|
||||
value = precision * round(float(value) / precision)
|
||||
|
||||
if decimal_places:
|
||||
fmt = "%1." + str(decimal_places) + "f"
|
||||
|
||||
# Meters
|
||||
if unit_length == "METERS":
|
||||
fmt = "%1.3f"
|
||||
if not decimal_places:
|
||||
fmt = "%1.3f"
|
||||
if hide_units is False:
|
||||
fmt += " m"
|
||||
tx_dist = fmt % value
|
||||
# Centimeters
|
||||
elif unit_length == "CENTIMETERS":
|
||||
fmt = "%1.1f"
|
||||
if not decimal_places:
|
||||
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 not decimal_places:
|
||||
fmt = "%1.0f"
|
||||
if hide_units is False:
|
||||
fmt += " mm"
|
||||
d_mm = value * (1000)
|
||||
@@ -231,20 +236,21 @@ def format_distance(value, isArea=False, hide_units=True):
|
||||
|
||||
# Otherwise Use Adaptive Units
|
||||
else:
|
||||
if round(value, 2) >= 1.0:
|
||||
if round(value, 2) >= 1.0 and not decimal_places:
|
||||
fmt = "%1.3f"
|
||||
if hide_units is False:
|
||||
fmt += " m"
|
||||
tx_dist = fmt % value
|
||||
else:
|
||||
if round(value, 2) >= 0.01:
|
||||
if round(value, 2) >= 0.01 and not decimal_places:
|
||||
fmt = "%1.1f"
|
||||
if hide_units is False:
|
||||
fmt += " cm"
|
||||
d_cm = value * (100)
|
||||
tx_dist = fmt % d_cm
|
||||
else:
|
||||
fmt = "%1.0f"
|
||||
if not decimal_places:
|
||||
fmt = "%1.0f"
|
||||
if hide_units is False:
|
||||
fmt += " mm"
|
||||
d_mm = value * (1000)
|
||||
|
||||
@@ -25,7 +25,7 @@ import blenderbim.tool as tool
|
||||
import blenderbim.core.drawing as core
|
||||
import blenderbim.bim.module.drawing.annotation as annotation
|
||||
import blenderbim.bim.module.drawing.decoration as decoration
|
||||
from blenderbim.bim.module.drawing.data import DrawingsData, DecoratorData
|
||||
from blenderbim.bim.module.drawing.data import DrawingsData, DecoratorData, SheetsData, AnnotationData
|
||||
from blenderbim.bim.module.drawing.data import refresh as refresh_drawing_data
|
||||
from pathlib import Path
|
||||
from blenderbim.bim.prop import Attribute, StrProperty
|
||||
@@ -44,20 +44,14 @@ from bpy.props import (
|
||||
|
||||
|
||||
diagram_scales_enum = []
|
||||
titleblocks_enum = []
|
||||
sheets_enum = []
|
||||
vector_styles_enum = []
|
||||
|
||||
|
||||
def purge():
|
||||
global diagram_scales_enum
|
||||
global titleblocks_enum
|
||||
global sheets_enum
|
||||
global vector_styles_enum
|
||||
diagram_scales_enum = []
|
||||
titleblocks_enum = []
|
||||
sheets_enum = []
|
||||
vector_styles_enum = []
|
||||
|
||||
|
||||
def update_target_view(self, context):
|
||||
@@ -86,7 +80,7 @@ def update_diagram_scale(self, context):
|
||||
)
|
||||
except:
|
||||
return
|
||||
pset = ifcopenshell.util.element.get_psets(element).get("EPset_Drawing")
|
||||
pset = ifcopenshell.util.element.get_pset(element, "EPset_Drawing")
|
||||
if pset:
|
||||
pset = tool.Ifc.get().by_id(pset["id"])
|
||||
else:
|
||||
@@ -191,20 +185,14 @@ def update_layer(self, context, name, value):
|
||||
ifcopenshell.api.run("pset.edit_pset", tool.Ifc.get(), pset=pset, properties={name: value})
|
||||
|
||||
|
||||
def getTitleblocks(self, context):
|
||||
global titleblocks_enum
|
||||
if len(titleblocks_enum) < 1:
|
||||
titleblocks_enum.clear()
|
||||
files = Path(os.path.join(context.scene.BIMProperties.data_dir, "templates", "titleblocks")).glob("*.svg")
|
||||
files = sorted([str(f.stem) for f in files])
|
||||
titleblocks_enum.extend([(f, f, "") for f in files])
|
||||
return titleblocks_enum
|
||||
def get_titleblocks(self, context):
|
||||
if not SheetsData.is_loaded:
|
||||
SheetsData.load()
|
||||
return SheetsData.data["titleblocks"]
|
||||
|
||||
|
||||
def refreshTitleblocks(self, context):
|
||||
global titleblocks_enum
|
||||
titleblocks_enum.clear()
|
||||
getTitleblocks(self, context)
|
||||
def update_titleblocks(self, context):
|
||||
SheetsData.data["titleblocks"] = SheetsData.titleblocks()
|
||||
|
||||
|
||||
def toggleDecorations(self, context):
|
||||
@@ -220,16 +208,6 @@ def toggleDecorations(self, context):
|
||||
decoration.DecorationsHandler.uninstall()
|
||||
|
||||
|
||||
def getVectorStyles(self, context):
|
||||
global vector_styles_enum
|
||||
if len(vector_styles_enum) < 1:
|
||||
sheets_enum.clear()
|
||||
for filename in Path(os.path.join(context.scene.BIMProperties.data_dir, "styles")).glob("*.css"):
|
||||
f = str(filename.stem)
|
||||
vector_styles_enum.append((f, f, ""))
|
||||
return vector_styles_enum
|
||||
|
||||
|
||||
class Variable(PropertyGroup):
|
||||
name: StringProperty(name="Name")
|
||||
prop_key: StringProperty(name="Property Key")
|
||||
@@ -239,6 +217,7 @@ class Drawing(PropertyGroup):
|
||||
ifc_definition_id: IntProperty(name="IFC Definition ID")
|
||||
name: StringProperty(name="Name", update=update_drawing_name)
|
||||
target_view: StringProperty(name="Target View")
|
||||
is_selected: BoolProperty(name="Is Selected", default=True)
|
||||
|
||||
|
||||
class Schedule(PropertyGroup):
|
||||
@@ -248,19 +227,9 @@ class Schedule(PropertyGroup):
|
||||
|
||||
|
||||
class Sheet(PropertyGroup):
|
||||
def set_name(self, new):
|
||||
old = self.get("name")
|
||||
path = os.path.join(bpy.context.scene.BIMProperties.data_dir, "sheets")
|
||||
if old and os.path.isfile(os.path.join(path, old + ".svg")):
|
||||
os.rename(os.path.join(path, old + ".svg"), os.path.join(path, new + ".svg"))
|
||||
self["name"] = new
|
||||
|
||||
def get_name(self):
|
||||
return self.get("name")
|
||||
|
||||
ifc_definition_id: IntProperty(name="IFC Definition ID")
|
||||
identification: StringProperty(name="Identification")
|
||||
name: StringProperty(name="Name", get=get_name, set=set_name)
|
||||
name: StringProperty(name="Name")
|
||||
is_sheet: BoolProperty(name="Is Sheet", default=False)
|
||||
reference_type: StringProperty(name="Reference Type")
|
||||
is_expanded: BoolProperty(name="Is Expanded", default=False)
|
||||
@@ -278,7 +247,6 @@ class DrawingStyle(PropertyGroup):
|
||||
name="Render Type",
|
||||
default="VIEWPORT",
|
||||
)
|
||||
vector_style: EnumProperty(items=getVectorStyles, name="Vector Style")
|
||||
include_query: StringProperty(name="Include Query")
|
||||
exclude_query: StringProperty(name="Exclude Query")
|
||||
attributes: CollectionProperty(name="Attributes", type=StrProperty)
|
||||
@@ -337,7 +305,7 @@ class DocProperties(PropertyGroup):
|
||||
current_drawing_index: IntProperty(name="Current Drawing Index")
|
||||
schedules: CollectionProperty(name="Schedules", type=Schedule)
|
||||
active_schedule_index: IntProperty(name="Active Schedule Index")
|
||||
titleblock: EnumProperty(items=getTitleblocks, name="Titleblock", update=refreshTitleblocks)
|
||||
titleblock: EnumProperty(items=get_titleblocks, name="Titleblock", update=update_titleblocks)
|
||||
is_editing_sheets: BoolProperty(name="Is Editing Sheets", default=False)
|
||||
sheets: CollectionProperty(name="Sheets", type=Sheet)
|
||||
active_sheet_index: IntProperty(name="Active Sheet Index")
|
||||
@@ -347,9 +315,23 @@ class DocProperties(PropertyGroup):
|
||||
decorations_colour: FloatVectorProperty(
|
||||
name="Decorations Colour", subtype="COLOR", default=(1, 1, 1, 1), min=0.0, max=1.0, size=4
|
||||
)
|
||||
sheets_dir: StringProperty(default=os.path.join("sheets") + os.path.sep, name="Default Sheets Directory")
|
||||
layouts_dir: StringProperty(default=os.path.join("layouts") + os.path.sep, name="Default Layouts Directory")
|
||||
titleblocks_dir: StringProperty(
|
||||
default=os.path.join("layouts", "titleblocks") + os.path.sep, name="Default Titleblocks Directory"
|
||||
)
|
||||
drawings_dir: StringProperty(default=os.path.join("drawings") + os.path.sep, name="Default Drawings Directory")
|
||||
stylesheet_path: StringProperty(
|
||||
default=os.path.join("drawings", "assets", "default.css"), name="Default Stylesheet"
|
||||
)
|
||||
markers_path: StringProperty(default=os.path.join("drawings", "assets", "markers.svg"), name="Default Markers")
|
||||
symbols_path: StringProperty(default=os.path.join("drawings", "assets", "symbols.svg"), name="Default Symbols")
|
||||
patterns_path: StringProperty(default=os.path.join("drawings", "assets", "patterns.svg"), name="Default Patterns")
|
||||
|
||||
|
||||
class BIMCameraProperties(PropertyGroup):
|
||||
calculate_shapely_surfaces: BoolProperty(name="Calculate Shapely Surfaces", default=False)
|
||||
calculate_svgfill_surfaces: BoolProperty(name="Calculate SVGFill Surfaces", default=False)
|
||||
has_underlay: BoolProperty(name="Underlay", default=False, update=update_has_underlay)
|
||||
has_linework: BoolProperty(name="Linework", default=True, update=update_has_linework)
|
||||
has_annotation: BoolProperty(name="Annotation", default=True, update=update_has_annotation)
|
||||
@@ -471,3 +453,57 @@ class BIMTextProperties(PropertyGroup):
|
||||
class BIMAssignedProductProperties(PropertyGroup):
|
||||
is_editing_product: BoolProperty(name="Is Editing Product", default=False)
|
||||
relating_product: PointerProperty(name="Relating Product", type=bpy.types.Object)
|
||||
|
||||
|
||||
# ObjectType: annotation_name, description, icon, data_type
|
||||
# fmt: off
|
||||
ANNOTATION_TYPES_DATA = {
|
||||
"DIMENSION": ("Dimension", "Add dimensions annotation.\nMeasurement values can be hidden through ShowDescriptionOnly property\nof BBIM_Dimension property set", "FIXED_SIZE", "curve"),
|
||||
"ANGLE": ("Angle", "", "DRIVER_ROTATIONAL_DIFFERENCE", "curve"),
|
||||
"RADIUS": ("Radius", "", "FORWARD", "curve"),
|
||||
"DIAMETER": ("Diameter", "Add diameter annotation.\nMeasurement values can be hidden through ShowDescriptionOnly property\nof BBIM_Dimension property set", "ARROW_LEFTRIGHT", "curve"),
|
||||
"TEXT": ("Text", "", "SMALL_CAPS", "empty"),
|
||||
"TEXT_LEADER": ("Leader", "", "TRACKING_BACKWARDS", "curve"),
|
||||
"STAIR_ARROW": ("Stair Arrow", "Add stair arrow annotation.\nIf you have IfcStairFlight object selected, it will be used as a reference for the annotation", "SCREEN_BACK", "curve"),
|
||||
"HIDDEN_LINE": ("Hidden", "", "CON_TRACKTO", "mesh"),
|
||||
"PLAN_LEVEL": ("Level (Plan)", "", "SORTBYEXT", "curve"),
|
||||
"SECTION_LEVEL": ("Level (Section)", "", "TRIA_DOWN", "curve"),
|
||||
"BREAKLINE": ("Breakline", "", "FCURVE", "mesh"),
|
||||
"LINEWORK": ("Line", "", "MESH_MONKEY", "mesh"),
|
||||
"BATTING": ("Batting", "Add batting annotation.\nThickness could be changed through Thickness property of BBIM_Batting property set", "FORCE_FORCE", "mesh"),
|
||||
"FILL_AREA": ("Fill Area", "", "NODE_TEXTURE", "mesh"),
|
||||
"FALL": ("Fall", "", "SORT_ASC", "curve"),
|
||||
}
|
||||
# fmt: on
|
||||
|
||||
annotation_classes = [(x, *ANNOTATION_TYPES_DATA[x][:3], i) for i, x in enumerate(ANNOTATION_TYPES_DATA)]
|
||||
|
||||
|
||||
def get_annotation_data_prop(prop_name):
|
||||
def function(self, context):
|
||||
if not AnnotationData.is_loaded:
|
||||
AnnotationData.load()
|
||||
return AnnotationData.data[prop_name]
|
||||
|
||||
return function
|
||||
|
||||
|
||||
def update_annotation_object_type(self, context):
|
||||
self.relating_type_id = "0"
|
||||
# changing enum doesn't trigger refresh by itself
|
||||
AnnotationData.is_loaded = False
|
||||
|
||||
|
||||
class BIMAnnotationProperties(PropertyGroup):
|
||||
object_type: bpy.props.EnumProperty(
|
||||
name="Annotation Object Type", items=annotation_classes, default="TEXT", update=update_annotation_object_type
|
||||
)
|
||||
relating_type_id: bpy.props.EnumProperty(
|
||||
name="Relating Annotation Type", items=get_annotation_data_prop("relating_types")
|
||||
)
|
||||
create_representation_for_type: bpy.props.BoolProperty(
|
||||
name="Create Representation For Type",
|
||||
default=False,
|
||||
description='Whether "Add type" should define a representation for the type \n'
|
||||
"or allow occurences to have their own",
|
||||
)
|
||||
|
||||
@@ -16,10 +16,177 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bgl
|
||||
from mathutils import Matrix
|
||||
from gpu.types import GPUShader
|
||||
from gpu_extras.batch import batch_for_shader
|
||||
import gpu
|
||||
|
||||
|
||||
BASE_DEF_GLSL = """
|
||||
#define PI 3.141592653589793
|
||||
#define MAX_POINTS 64
|
||||
#define CIRCLE_SEGS 12
|
||||
#define SMOOTH_WIDTH 1.0
|
||||
#define lineSmooth true
|
||||
"""
|
||||
|
||||
# since `bgl` is deprecated, creating smoothing lines became tricky
|
||||
# Notes for creating shaders with smoothed lines:
|
||||
# in geom shader - use triangle_strip, DEFAULT_SETUP, do_edge_verts or do_vertex to emit vertices
|
||||
# in frag shader - use lineWidth uniform, smoothline flaot in, smoothing shader code from base shader
|
||||
# mind the vertex limit since emitting vertices for smoothed lines produces twice as much vertices
|
||||
|
||||
BASE_LIB_GLSL = """
|
||||
// TODO: redefine as macor instead
|
||||
uniform vec2 winsize;
|
||||
uniform float lineWidth;
|
||||
#define half_winsize (winsize / 2)
|
||||
// convert camera to window
|
||||
#define C2W(v) vec4(v.x * half_winsize.x / v.w, v.y * half_winsize.y / v.w, v.z / v.w, 1)
|
||||
// convert window to camera
|
||||
#define W2C(v) vec4(v.x * v.w / half_winsize.x, v.y * v.w / half_winsize.y, v.z * v.w, 1)
|
||||
|
||||
void emitSegment(vec4 p0, vec4 p1) {
|
||||
gl_Position = p0;
|
||||
EmitVertex();
|
||||
gl_Position = p1;
|
||||
EmitVertex();
|
||||
EndPrimitive();
|
||||
}
|
||||
|
||||
void emitTriangle(vec4 p1, vec4 p2, vec4 p3) {
|
||||
gl_Position = p1;
|
||||
EmitVertex();
|
||||
gl_Position = p2;
|
||||
EmitVertex();
|
||||
gl_Position = p3;
|
||||
EmitVertex();
|
||||
EndPrimitive();
|
||||
}
|
||||
|
||||
#define matCLIP2WIN() vec4(winsize.x/2, winsize.y/2, 1, 1)
|
||||
#define CLIP2WIN(v) (clip2win * (v) / (v).w)
|
||||
#define matWIN2CLIP() vec4(2/winsize.x, 2/winsize.y, 1, 1)
|
||||
#define WIN2CLIP(v) (win2clip * (v) * (v).w)
|
||||
#define DEFAULT_SETUP() vec4 clip2win = matCLIP2WIN(), win2clip = matWIN2CLIP(); vec2 EDGE_DIR
|
||||
|
||||
out float smoothline;
|
||||
|
||||
void arrow_head(in vec4 dir, in float size, in float angle, out vec4 head[3]) {
|
||||
vec4 nose = dir * size;
|
||||
float c = cos(angle), s = sin(angle);
|
||||
head[0] = nose;
|
||||
head[1] = vec4(mat2(c, -s, +s, c) * nose.xy, 0, 0);
|
||||
head[2] = vec4(mat2(c, +s, -s, c) * nose.xy, 0, 0);
|
||||
}
|
||||
|
||||
void circle_head(in float size, out vec4 head[CIRCLE_SEGS]) {
|
||||
float angle_d = PI * 2 / CIRCLE_SEGS;
|
||||
for(int i = 0; i<CIRCLE_SEGS; i++) {
|
||||
float angle = angle_d * i;
|
||||
head[i] = vec4(cos(angle), sin(angle), 0, 0) * size;
|
||||
}
|
||||
}
|
||||
|
||||
bool check_counterclockwise(in vec4 A, in vec4 B, in vec4 C) {
|
||||
return (C.y-A.y) * (B.x-A.x) > (B.y-A.y) * (C.x-A.x);
|
||||
}
|
||||
|
||||
void angle_circle_head(
|
||||
in vec4 circle_start, in float circle_angle,
|
||||
in bool counterclockwise,
|
||||
out vec4 head[CIRCLE_SEGS+1], out float angle_segs) {
|
||||
|
||||
// 1 added to CIRCLE_SEGS because we're number of vertices
|
||||
// for n segments is n+1
|
||||
|
||||
float angle_d;
|
||||
angle_d = PI * 2 / CIRCLE_SEGS; // 30d
|
||||
// need to bottom clamp it to 1, otherwise it causes Blender crash at extruding the curve
|
||||
angle_segs = max(1, ceil(circle_angle / angle_d));
|
||||
angle_d = circle_angle / angle_segs;
|
||||
|
||||
for(int i = 0; i < (angle_segs + 1); i++) {
|
||||
float angle = angle_d * i;
|
||||
if (counterclockwise) {
|
||||
head[i] = vec4(
|
||||
circle_start.x * cos(-angle) + circle_start.y * sin(-angle),
|
||||
circle_start.x * -sin(-angle) + circle_start.y * cos(-angle),
|
||||
0, 0
|
||||
);
|
||||
} else {
|
||||
head[i] = vec4(
|
||||
circle_start.x * cos(angle) + circle_start.y * sin(angle),
|
||||
circle_start.x * -sin(angle) + circle_start.y * cos(angle),
|
||||
0, 0
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void cross_head(in vec4 dir, in float size, out vec4 head[3]) {
|
||||
vec4 nose = dir * size;
|
||||
float c = cos(PI/2), s = sin(PI/2);
|
||||
head[0] = nose;
|
||||
head[1] = vec4(mat2(c, -s, +s, c) * nose.xy, 0, 0);
|
||||
head[2] = vec4(mat2(c, +s, -s, c) * nose.xy, 0, 0);
|
||||
}
|
||||
|
||||
// do_vertex_util is custom EmitVertex method to support line smoothing
|
||||
// `e` - edge xy direction in win/clip space (directions are invariant in both spaces)
|
||||
#define do_vertex(pos, e) (do_vertex_util(pos, vec2(-(e).y, (e).x) / winsize.xy))
|
||||
#define do_vertex_win(pos, e) ( do_vertex( WIN2CLIP( pos ), e ) )
|
||||
|
||||
// if vertex is shared by two segments of the line still need to emit it twice
|
||||
// to avoid smoothing artifacts
|
||||
// don't forget to initialize `vec2 EDGE_DIR` for macro to work
|
||||
// `pos0` / `pos1` - vertex position in clip space
|
||||
#define do_edge_verts(pos0, pos1) ( EDGE_DIR = normalize( ( (pos1) - (pos0) ).xy ), do_vertex( pos0, EDGE_DIR ), do_vertex( pos1, EDGE_DIR) )
|
||||
#define do_edge_verts_win(pos0, pos1) ( do_edge_verts( WIN2CLIP(pos0), WIN2CLIP(pos1) ) )
|
||||
void do_vertex_util(vec4 pos, vec2 ofs)
|
||||
{
|
||||
float final_line_width = lineWidth + SMOOTH_WIDTH * float(lineSmooth);
|
||||
ofs *= final_line_width;
|
||||
|
||||
smoothline = final_line_width * 0.5;
|
||||
gl_Position = pos;
|
||||
gl_Position.xy += ofs * pos.w;
|
||||
EmitVertex();
|
||||
|
||||
smoothline = -final_line_width * 0.5;
|
||||
gl_Position = pos;
|
||||
gl_Position.xy -= ofs * pos.w;
|
||||
EmitVertex();
|
||||
}
|
||||
|
||||
// geometry utils
|
||||
void triangle_head(in vec4 side, in vec4 dir, in float length, in float width, in float radius, out vec4 head[5]) {
|
||||
vec4 nose = side * length;
|
||||
vec4 ear = dir * width;
|
||||
head[0] = side * -radius;
|
||||
head[1] = nose * -.5;
|
||||
head[2] = vec4(0) + ear;
|
||||
head[3] = nose * .5;
|
||||
head[4] = side * radius;
|
||||
}
|
||||
|
||||
void do_triangle_head(vec4 pos_w, vec4 head[5]) {
|
||||
DEFAULT_SETUP();
|
||||
do_edge_verts_win(pos_w + head[0], pos_w + head[1]);
|
||||
do_edge_verts_win(pos_w + head[1], pos_w + head[2]);
|
||||
do_edge_verts_win(pos_w + head[2], pos_w + head[3]);
|
||||
do_edge_verts_win(pos_w + head[3], pos_w + head[4]);
|
||||
}
|
||||
|
||||
void do_circle_head(vec4 pos_w, vec4 head[CIRCLE_SEGS]) {
|
||||
DEFAULT_SETUP();
|
||||
for(int i=0; i<CIRCLE_SEGS-1; i++) {
|
||||
do_edge_verts_win(pos_w + head[i], pos_w + head[i+1]);
|
||||
EmitVertex();
|
||||
}
|
||||
do_edge_verts_win( pos_w + head[CIRCLE_SEGS-1], pos_w + head[0] );
|
||||
}
|
||||
|
||||
"""
|
||||
|
||||
|
||||
class BaseShader:
|
||||
@@ -28,7 +195,7 @@ class BaseShader:
|
||||
|
||||
The Geometry shader works in clipping coords (aftre projecting before division and window scaling).
|
||||
To make window-scale geometry, vectors should be calculated in window space and than back-projected to clipping spae.
|
||||
Provide `winSize` uniform vector with halfsize of window, and use W2C and C2W macros.
|
||||
Provide `half_winsize` uniform vector with halfsize of window, and use W2C and C2W macros.
|
||||
|
||||
Replace glsl code in derived classes.
|
||||
Beware of unused attributes and uniforms: glsl compiler will optimize them out and blender fail with an exception.
|
||||
@@ -43,9 +210,7 @@ class BaseShader:
|
||||
|
||||
TYPE = None # should be LINES|POINTS|etc
|
||||
|
||||
DEF_GLSL = """
|
||||
#define PI 3.141592653589793
|
||||
"""
|
||||
DEF_GLSL = BASE_DEF_GLSL
|
||||
|
||||
VERT_GLSL = """
|
||||
uniform mat4 viewMatrix;
|
||||
@@ -63,46 +228,45 @@ class BaseShader:
|
||||
"""
|
||||
|
||||
# prepended to geom_glsl
|
||||
LIB_GLSL = """
|
||||
uniform vec2 winSize;
|
||||
// convert camera to window
|
||||
#define C2W(v) vec4(v.x * winSize.x / v.w, v.y * winSize.y / v.w, v.z / v.w, 1)
|
||||
// convert window to camera
|
||||
#define W2C(v) vec4(v.x * v.w / winSize.x, v.y * v.w / winSize.y, v.z * v.w, 1)
|
||||
|
||||
void emitSegment(vec4 p0, vec4 p1) {
|
||||
gl_Position = p0;
|
||||
EmitVertex();
|
||||
gl_Position = p1;
|
||||
EmitVertex();
|
||||
EndPrimitive();
|
||||
}
|
||||
|
||||
void emitTriangle(vec4 p1, vec4 p2, vec4 p3) {
|
||||
gl_Position = p1;
|
||||
EmitVertex();
|
||||
gl_Position = p2;
|
||||
EmitVertex();
|
||||
gl_Position = p3;
|
||||
EmitVertex();
|
||||
EndPrimitive();
|
||||
}
|
||||
"""
|
||||
LIB_GLSL = BASE_LIB_GLSL
|
||||
|
||||
GEOM_GLSL = """
|
||||
uniform float viewportDrawingScale;
|
||||
|
||||
layout(lines) in;
|
||||
layout(triangle_strip, max_vertices = 4) out;
|
||||
|
||||
void main() {
|
||||
// default setup for macro to work
|
||||
vec4 clip2win = matCLIP2WIN();
|
||||
vec4 win2clip = matWIN2CLIP();
|
||||
vec2 EDGE_DIR;
|
||||
|
||||
vec4 p0 = gl_in[0].gl_Position, p1 = gl_in[1].gl_Position;
|
||||
do_edge_verts(p0, p1);
|
||||
EndPrimitive();
|
||||
}
|
||||
"""
|
||||
|
||||
FRAG_GLSL = """
|
||||
uniform vec4 color;
|
||||
uniform float lineWidth;
|
||||
|
||||
in float smoothline;
|
||||
out vec4 fragColor;
|
||||
|
||||
void main() {
|
||||
vec2 co = gl_FragCoord.xy;
|
||||
|
||||
fragColor = color;
|
||||
if (lineSmooth) {
|
||||
fragColor.a *= clamp((lineWidth + SMOOTH_WIDTH) * 0.5 - abs(smoothline), 0.0, 1.0);
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
# NB: libcode arg doesn't work
|
||||
# TODO: rename to .shader
|
||||
self.prog = GPUShader(
|
||||
vertexcode=self.VERT_GLSL,
|
||||
fragcode=self.FRAG_GLSL,
|
||||
@@ -120,27 +284,29 @@ class BaseShader:
|
||||
self.prog.bind()
|
||||
|
||||
def glenable(self):
|
||||
bgl.glEnable(bgl.GL_BLEND)
|
||||
bgl.glBlendFunc(bgl.GL_SRC_ALPHA, bgl.GL_ONE_MINUS_SRC_ALPHA)
|
||||
bgl.glBlendEquation(bgl.GL_FUNC_ADD)
|
||||
|
||||
# bgl.glEnable(bgl.GL_DEPTH_TEST)
|
||||
# bgl.glDepthFunc(bgl.GL_LEQUAL)
|
||||
# bgl.glDepthMask(True)
|
||||
gpu.state.blend_set("ALPHA")
|
||||
gpu.state.depth_test_set("LESS_EQUAL")
|
||||
|
||||
def uniform_region(self, ctx):
|
||||
region = ctx.region
|
||||
region3d = ctx.region_data
|
||||
try:
|
||||
self.prog.uniform_float("viewMatrix", region3d.perspective_matrix)
|
||||
except ValueError: # unused uniform
|
||||
pass
|
||||
try:
|
||||
self.prog.uniform_float("winSize", (region.width / 2, region.height / 2))
|
||||
except ValueError: # unused uniform
|
||||
pass
|
||||
|
||||
uniform_floats = {
|
||||
"viewMatrix": region3d.perspective_matrix,
|
||||
"winsize": (region.width, region.height),
|
||||
"lineWidth": 2.5,
|
||||
}
|
||||
|
||||
for name, value in uniform_floats.items():
|
||||
try:
|
||||
self.prog.uniform_float(name, value)
|
||||
# TODO: shouldn't just try'n'catch them
|
||||
# because they may indicate errors in code
|
||||
except ValueError: # unused uniform
|
||||
pass
|
||||
|
||||
|
||||
# TODO: add smoothing if this shades is going to be used
|
||||
class BaseLinesShader(BaseShader):
|
||||
"""Draws line segments with gaps around vertices at endpoints"""
|
||||
|
||||
@@ -185,7 +351,6 @@ class BaseLinesShader(BaseShader):
|
||||
|
||||
def glenable(self):
|
||||
super().glenable()
|
||||
bgl.glEnable(bgl.GL_LINE_SMOOTH)
|
||||
|
||||
|
||||
class GizmoShader(BaseShader):
|
||||
@@ -207,6 +372,7 @@ class GizmoShader(BaseShader):
|
||||
"""
|
||||
|
||||
|
||||
# TODO: add smoothing if this shader is going to be used
|
||||
class DotsGizmoShader(GizmoShader):
|
||||
"""Draws circles of radius 1 around points"""
|
||||
|
||||
@@ -274,24 +440,26 @@ class ExtrusionGuidesShader(GizmoShader):
|
||||
uniform mat4 ModelViewProjectionMatrix;
|
||||
|
||||
layout(lines) in;
|
||||
layout(line_strip, max_vertices=10) out;
|
||||
layout(triangle_strip, max_vertices=MAX_POINTS) out;
|
||||
|
||||
void main() {
|
||||
vec4 p0 = gl_in[0].gl_Position, p1 = gl_in[1].gl_Position;
|
||||
vec4 p0w = C2W(p0), p1w = C2W(p1);
|
||||
// default setup for macro to work
|
||||
vec2 EDGE_DIR;
|
||||
|
||||
emitSegment(p0, p1);
|
||||
vec4 p0 = gl_in[0].gl_Position, p1 = gl_in[1].gl_Position;
|
||||
do_edge_verts(p0, p1);
|
||||
EndPrimitive();
|
||||
|
||||
vec4 bx = ModelViewProjectionMatrix[0] * CROSS_SIZE;
|
||||
vec4 by = ModelViewProjectionMatrix[1] * CROSS_SIZE;
|
||||
|
||||
emitSegment(p0 - bx, p0 + bx);
|
||||
emitSegment(p0 - by, p0 + by);
|
||||
emitSegment(p1 - bx, p1 + bx);
|
||||
emitSegment(p1 - by, p1 + by);
|
||||
do_edge_verts(p0 - bx, p0 + bx);
|
||||
EndPrimitive();
|
||||
do_edge_verts(p0 - by, p0 + by);
|
||||
EndPrimitive();
|
||||
do_edge_verts(p1 - bx, p1 + bx);
|
||||
EndPrimitive();
|
||||
do_edge_verts(p1 - by, p1 + by);
|
||||
EndPrimitive();
|
||||
}
|
||||
"""
|
||||
|
||||
def glenable(self):
|
||||
super().glenable()
|
||||
bgl.glEnable(bgl.GL_LINE_SMOOTH)
|
||||
|
||||
@@ -17,14 +17,15 @@
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
import bpy
|
||||
import uuid
|
||||
import shutil
|
||||
import ntpath
|
||||
import pystache
|
||||
import urllib.parse
|
||||
import xml.etree.ElementTree as ET
|
||||
import blenderbim.tool as tool
|
||||
import ifcopenshell.util.geolocation
|
||||
from shutil import copy
|
||||
from xml.dom import minidom
|
||||
|
||||
|
||||
@@ -33,22 +34,29 @@ class SheetBuilder:
|
||||
self.data_dir = None
|
||||
self.scale = "NTS"
|
||||
|
||||
def create(self, sheet_path, titleblock_name):
|
||||
def create(self, layout_path, titleblock_name):
|
||||
root = ET.Element("svg")
|
||||
root.attrib["xmlns"] = "http://www.w3.org/2000/svg"
|
||||
root.attrib["xmlns:xlink"] = "http://www.w3.org/1999/xlink"
|
||||
root.attrib["id"] = "root"
|
||||
root.attrib["version"] = "1.1"
|
||||
|
||||
view_root = ET.parse(
|
||||
os.path.join(self.data_dir, "templates", "titleblocks", titleblock_name + ".svg")
|
||||
).getroot()
|
||||
sheet_dir = os.path.dirname(layout_path)
|
||||
ootb_titleblock_path = os.path.join(self.data_dir, "templates", "titleblocks", titleblock_name + ".svg")
|
||||
titleblock_path = tool.Ifc.resolve_uri(tool.Drawing.get_default_titleblock_path(titleblock_name))
|
||||
|
||||
os.makedirs(sheet_dir, exist_ok=True)
|
||||
os.makedirs(os.path.dirname(titleblock_path), exist_ok=True)
|
||||
if not os.path.exists(titleblock_path):
|
||||
shutil.copy(ootb_titleblock_path, titleblock_path)
|
||||
|
||||
view_root = ET.parse(titleblock_path).getroot()
|
||||
view_width = self.convert_to_mm(view_root.attrib.get("width"))
|
||||
view_height = self.convert_to_mm(view_root.attrib.get("height"))
|
||||
view = ET.SubElement(root, "g")
|
||||
view.attrib["data-type"] = "titleblock"
|
||||
titleblock = ET.SubElement(view, "image")
|
||||
titleblock.attrib["xlink:href"] = "../templates/titleblocks/" + titleblock_name + ".svg"
|
||||
titleblock.attrib["xlink:href"] = os.path.relpath(titleblock_path, sheet_dir)
|
||||
titleblock.attrib["x"] = "0"
|
||||
titleblock.attrib["y"] = "0"
|
||||
titleblock.attrib["width"] = str(view_width)
|
||||
@@ -58,26 +66,25 @@ class SheetBuilder:
|
||||
root.attrib["height"] = "{}mm".format(view_height)
|
||||
root.attrib["viewBox"] = "0 0 {} {}".format(view_width, view_height)
|
||||
|
||||
with open(sheet_path, "w") as f:
|
||||
with open(layout_path, "w") as f:
|
||||
f.write(minidom.parseString(ET.tostring(root)).toprettyxml(indent=" "))
|
||||
|
||||
def add_drawing(self, reference, drawing, sheet):
|
||||
filename = drawing.Name
|
||||
sheet_name = os.path.splitext(os.path.basename(tool.Drawing.get_document_uri(sheet)))[0]
|
||||
sheet_dir = os.path.join(self.data_dir, "sheets")
|
||||
drawing_dir = os.path.join(self.data_dir, "diagrams")
|
||||
sheet_path = os.path.join(sheet_dir, sheet_name + ".svg")
|
||||
drawing_path = os.path.join(drawing_dir, filename + ".svg")
|
||||
underlay_path = os.path.join(drawing_dir, filename + "-underlay.png")
|
||||
layout_path = tool.Drawing.get_document_uri(sheet, "LAYOUT")
|
||||
layout_dir = os.path.dirname(layout_path)
|
||||
|
||||
if not os.path.isfile(sheet_path):
|
||||
drawing_path = tool.Drawing.get_document_uri(tool.Drawing.get_drawing_reference(drawing))
|
||||
underlay_path = os.path.splitext(drawing_path)[0] + "-underlay.png"
|
||||
|
||||
if not os.path.exists(layout_path) or not os.path.exists(drawing_path):
|
||||
raise FileNotFoundError
|
||||
|
||||
ET.register_namespace("", "http://www.w3.org/2000/svg")
|
||||
ET.register_namespace("xlink", "http://www.w3.org/1999/xlink")
|
||||
|
||||
sheet_tree = ET.parse(sheet_path)
|
||||
sheet_root = sheet_tree.getroot()
|
||||
layout_tree = ET.parse(layout_path)
|
||||
layout_root = layout_tree.getroot()
|
||||
|
||||
view_tree = ET.parse(drawing_path)
|
||||
view_root = view_tree.getroot()
|
||||
@@ -85,7 +92,7 @@ class SheetBuilder:
|
||||
# The view is placed into a group with a background image element.
|
||||
# Although the foreground SVG already has a background, it is duplicated
|
||||
# here to accommodate browsers which do not nest images.
|
||||
view = ET.SubElement(sheet_root, "g")
|
||||
view = ET.SubElement(layout_root, "g")
|
||||
view.attrib["data-type"] = "drawing"
|
||||
view.attrib["data-id"] = str(reference.id())
|
||||
view.attrib["data-drawing"] = drawing.GlobalId
|
||||
@@ -95,7 +102,7 @@ class SheetBuilder:
|
||||
if os.path.isfile(underlay_path):
|
||||
background = ET.SubElement(view, "image")
|
||||
background.attrib["data-type"] = "background"
|
||||
background.attrib["xlink:href"] = os.path.relpath(underlay_path, sheet_dir)
|
||||
background.attrib["xlink:href"] = os.path.relpath(underlay_path, layout_dir)
|
||||
background.attrib["x"] = "30"
|
||||
background.attrib["y"] = "30"
|
||||
background.attrib["width"] = str(view_width)
|
||||
@@ -104,84 +111,96 @@ class SheetBuilder:
|
||||
if os.path.isfile(drawing_path):
|
||||
foreground = ET.SubElement(view, "image")
|
||||
foreground.attrib["data-type"] = "foreground"
|
||||
foreground.attrib["xlink:href"] = os.path.relpath(drawing_path, sheet_dir)
|
||||
foreground.attrib["xlink:href"] = os.path.relpath(drawing_path, layout_dir)
|
||||
foreground.attrib["x"] = "30"
|
||||
foreground.attrib["y"] = "30"
|
||||
foreground.attrib["width"] = str(view_width)
|
||||
foreground.attrib["height"] = str(view_height)
|
||||
|
||||
self.add_view_title(30, view_height + 35, view)
|
||||
sheet_tree.write(sheet_path)
|
||||
self.add_view_title(30, view_height + 35, view, layout_dir)
|
||||
layout_tree.write(layout_path)
|
||||
|
||||
def remove_drawing(self, reference, sheet):
|
||||
ET.register_namespace("", "http://www.w3.org/2000/svg")
|
||||
|
||||
sheet_path = tool.Drawing.get_document_uri(sheet)
|
||||
sheet_tree = ET.parse(sheet_path)
|
||||
sheet_root = sheet_tree.getroot()
|
||||
layout_path = tool.Drawing.get_document_uri(sheet, "LAYOUT")
|
||||
layout_tree = ET.parse(layout_path)
|
||||
layout_root = layout_tree.getroot()
|
||||
|
||||
for g in sheet_root.findall("{http://www.w3.org/2000/svg}g"):
|
||||
for g in layout_root.findall("{http://www.w3.org/2000/svg}g"):
|
||||
if g.attrib.get("data-id") == str(reference.id()):
|
||||
sheet_root.remove(g)
|
||||
layout_root.remove(g)
|
||||
break
|
||||
|
||||
sheet_tree.write(sheet_path)
|
||||
layout_tree.write(layout_path)
|
||||
|
||||
def add_schedule(self, reference, schedule, sheet):
|
||||
view_path = tool.Drawing.get_document_uri(schedule)
|
||||
view_path = tool.Drawing.get_path_with_ext(tool.Drawing.get_document_uri(schedule), "svg")
|
||||
if not os.path.exists(view_path):
|
||||
tool.Drawing.create_svg_schedule(schedule)
|
||||
schedule_name = os.path.splitext(os.path.basename(view_path))[0]
|
||||
sheet_path = tool.Drawing.get_document_uri(sheet)
|
||||
layout_path = tool.Drawing.get_document_uri(sheet, "LAYOUT")
|
||||
layout_dir = os.path.dirname(layout_path)
|
||||
|
||||
ET.register_namespace("", "http://www.w3.org/2000/svg")
|
||||
ET.register_namespace("xlink", "http://www.w3.org/1999/xlink")
|
||||
|
||||
sheet_tree = ET.parse(sheet_path)
|
||||
sheet_root = sheet_tree.getroot()
|
||||
layout_tree = ET.parse(layout_path)
|
||||
layout_root = layout_tree.getroot()
|
||||
|
||||
view_tree = ET.parse(view_path)
|
||||
view_root = view_tree.getroot()
|
||||
view_width = self.convert_to_mm(view_root.attrib.get("width"))
|
||||
view_height = self.convert_to_mm(view_root.attrib.get("height"))
|
||||
|
||||
view = ET.SubElement(sheet_root, "g")
|
||||
view = ET.SubElement(layout_root, "g")
|
||||
view.attrib["data-type"] = "schedule"
|
||||
view.attrib["data-id"] = str(reference.id())
|
||||
view.attrib["data-schedule"] = str(schedule.id())
|
||||
|
||||
foreground = ET.SubElement(view, "image")
|
||||
foreground.attrib["data-type"] = "table"
|
||||
foreground.attrib["xlink:href"] = "../schedules/{}.svg".format(schedule_name)
|
||||
foreground.attrib["xlink:href"] = os.path.relpath(view_path, layout_dir)
|
||||
foreground.attrib["x"] = "30"
|
||||
foreground.attrib["y"] = "30"
|
||||
foreground.attrib["width"] = str(view_width)
|
||||
foreground.attrib["height"] = str(view_height)
|
||||
|
||||
self.add_view_title(30, view_height + 35, view)
|
||||
sheet_tree.write(sheet_path)
|
||||
self.add_view_title(30, view_height + 35, view, layout_dir)
|
||||
layout_tree.write(layout_path)
|
||||
|
||||
def add_view_title(self, x, y, parent):
|
||||
title_tree = ET.parse(os.path.join(self.data_dir, "templates", "view-title.svg"))
|
||||
def add_view_title(self, x, y, parent, layout_dir):
|
||||
title_path = os.path.join(layout_dir, "assets", "view-title.svg")
|
||||
os.makedirs(os.path.dirname(title_path), exist_ok=True)
|
||||
if not os.path.exists(title_path):
|
||||
ootb_title = os.path.join(bpy.context.scene.BIMProperties.data_dir, "assets", "view-title.svg")
|
||||
shutil.copy(ootb_title, title_path)
|
||||
|
||||
title_tree = ET.parse(title_path)
|
||||
title_root = title_tree.getroot()
|
||||
title = ET.SubElement(parent, "image")
|
||||
title.attrib["data-type"] = "view-title"
|
||||
title.attrib["xlink:href"] = "../templates/view-title.svg"
|
||||
title.attrib["xlink:href"] = os.path.relpath(title_path, layout_dir)
|
||||
title.attrib["x"] = str(x)
|
||||
title.attrib["y"] = str(y)
|
||||
title.attrib["width"] = str(self.convert_to_mm(title_root.attrib.get("width")))
|
||||
title.attrib["height"] = str(self.convert_to_mm(title_root.attrib.get("height")))
|
||||
|
||||
def build(self, sheet):
|
||||
sheet_name = os.path.splitext(os.path.basename(tool.Drawing.get_document_uri(sheet)))[0]
|
||||
os.makedirs(os.path.join(self.data_dir, "build", sheet_name), exist_ok=True)
|
||||
self.references = {"SHEET": None, "RASTER": []}
|
||||
|
||||
sheet_path = os.path.join(self.data_dir, "sheets", f"{sheet_name}.svg")
|
||||
layout_path = tool.Drawing.get_document_uri(sheet, "LAYOUT")
|
||||
self.layout_dir = os.path.dirname(layout_path)
|
||||
|
||||
sheet_path = tool.Ifc.resolve_uri(tool.Drawing.get_default_sheet_path(sheet[0], sheet.Name))
|
||||
self.sheets_dir = os.path.dirname(sheet_path)
|
||||
|
||||
os.makedirs(self.sheets_dir, exist_ok=True)
|
||||
|
||||
ET.register_namespace("", "http://www.w3.org/2000/svg")
|
||||
ET.register_namespace("xlink", "http://www.w3.org/1999/xlink")
|
||||
|
||||
tree = ET.parse(sheet_path)
|
||||
tree = ET.parse(layout_path)
|
||||
root = tree.getroot()
|
||||
|
||||
self.defs = ET.Element("defs")
|
||||
@@ -189,11 +208,15 @@ class SheetBuilder:
|
||||
|
||||
self.build_titleblock(root, sheet)
|
||||
self.build_drawings(root, sheet)
|
||||
self.build_schedules(root)
|
||||
self.build_schedules(root, sheet)
|
||||
|
||||
with open(os.path.join(self.data_dir, "build", sheet_name, f"{sheet_name}.svg"), "wb") as output:
|
||||
with open(sheet_path, "wb") as output:
|
||||
tree.write(output)
|
||||
|
||||
self.references["SHEET"] = sheet_path
|
||||
|
||||
return self.references
|
||||
|
||||
def build_titleblock(self, root, sheet):
|
||||
titleblock = root.findall('{http://www.w3.org/2000/svg}g[@data-type="titleblock"]')[0]
|
||||
image = titleblock.findall("{http://www.w3.org/2000/svg}image")[0]
|
||||
@@ -208,8 +231,6 @@ class SheetBuilder:
|
||||
titleblock.remove(image)
|
||||
|
||||
def build_drawings(self, root, sheet):
|
||||
sheet_name = os.path.splitext(os.path.basename(tool.Drawing.get_document_uri(sheet)))[0]
|
||||
|
||||
for view in root.findall('{http://www.w3.org/2000/svg}g[@data-type="drawing"]'):
|
||||
reference = tool.Ifc.get().by_id(int(view.attrib["data-id"]))
|
||||
drawing = tool.Ifc.get().by_id(view.attrib["data-drawing"])
|
||||
@@ -232,12 +253,15 @@ class SheetBuilder:
|
||||
view.append(self.parse_embedded_svg(foreground, {}))
|
||||
|
||||
if background is not None:
|
||||
background_path = os.path.join(self.data_dir, "sheets", self.get_href(background))
|
||||
copy(background_path, os.path.join(self.data_dir, "build", sheet_name))
|
||||
background_path = os.path.join(self.layout_dir, self.get_href(background))
|
||||
raster_path = os.path.join(self.sheets_dir, os.path.basename(background_path))
|
||||
shutil.copy(background_path, raster_path)
|
||||
self.references["RASTER"].append(raster_path)
|
||||
|
||||
if view_title is not None:
|
||||
foreground_path = self.get_href(foreground)
|
||||
data = reference.get_info()
|
||||
data.update({"Sheet" + k: v for k, v in sheet.get_info().items()})
|
||||
if not data["Name"]:
|
||||
data["Name"] = ntpath.basename(foreground_path)[0:-4]
|
||||
data["Scale"] = tool.Drawing.get_drawing_human_scale(drawing)
|
||||
@@ -246,7 +270,7 @@ class SheetBuilder:
|
||||
for image in images:
|
||||
view.remove(image)
|
||||
|
||||
def build_schedules(self, root):
|
||||
def build_schedules(self, root, sheet):
|
||||
for view in root.findall('{http://www.w3.org/2000/svg}g[@data-type="schedule"]'):
|
||||
reference = tool.Ifc.get().by_id(int(view.attrib["data-id"]))
|
||||
schedule = tool.Ifc.get().by_id(int(view.attrib["data-schedule"]))
|
||||
@@ -268,6 +292,7 @@ class SheetBuilder:
|
||||
if view_title is not None:
|
||||
path = self.get_href(table)
|
||||
data = reference.get_info()
|
||||
data.update({"Sheet" + k: v for k, v in sheet.get_info().items()})
|
||||
if not data["Name"]:
|
||||
data["Name"] = schedule.Name or "Unnamed"
|
||||
view.append(self.parse_embedded_svg(view_title, data))
|
||||
@@ -298,7 +323,7 @@ class SheetBuilder:
|
||||
self.defs.append(clip_path)
|
||||
|
||||
svg_path = self.get_href(image)
|
||||
with open(os.path.join(self.data_dir, "sheets", svg_path), "r") as template:
|
||||
with open(os.path.join(self.layout_dir, svg_path), "r") as template:
|
||||
embedded = ET.fromstring(pystache.render(template.read(), data))
|
||||
# viewBox should not be nested
|
||||
embedded.attrib["viewBox"] = ""
|
||||
@@ -314,6 +339,39 @@ class SheetBuilder:
|
||||
group.append(child)
|
||||
return group
|
||||
|
||||
def change_titleblock(self, sheet, titleblock_name):
|
||||
ootb_titleblock_path = os.path.join(self.data_dir, "templates", "titleblocks", titleblock_name + ".svg")
|
||||
titleblock_path = tool.Drawing.get_default_titleblock_path(titleblock_name)
|
||||
sheet_path = tool.Drawing.get_document_uri(sheet, "LAYOUT")
|
||||
sheet_dir = os.path.dirname(sheet_path)
|
||||
|
||||
os.makedirs(sheet_dir, exist_ok=True)
|
||||
os.makedirs(os.path.dirname(titleblock_path), exist_ok=True)
|
||||
if not os.path.exists(titleblock_path):
|
||||
shutil.copy(ootb_titleblock_path, titleblock_path)
|
||||
|
||||
ET.register_namespace("", "http://www.w3.org/2000/svg")
|
||||
ET.register_namespace("xlink", "http://www.w3.org/1999/xlink")
|
||||
|
||||
view_root = ET.parse(ootb_titleblock_path).getroot()
|
||||
view_width = self.convert_to_mm(view_root.attrib.get("width"))
|
||||
view_height = self.convert_to_mm(view_root.attrib.get("height"))
|
||||
|
||||
sheet_tree = ET.parse(sheet_path)
|
||||
root = sheet_tree.getroot()
|
||||
|
||||
titleblock = sheet_tree.findall('{http://www.w3.org/2000/svg}g[@data-type="titleblock"]')[0]
|
||||
image = titleblock.findall("{http://www.w3.org/2000/svg}image[@{http://www.w3.org/1999/xlink}href]")[0]
|
||||
image.attrib["{http://www.w3.org/1999/xlink}href"] = os.path.relpath(titleblock_path, sheet_dir)
|
||||
image.attrib["width"] = str(view_width)
|
||||
image.attrib["height"] = str(view_height)
|
||||
|
||||
root.attrib["width"] = "{}mm".format(view_width)
|
||||
root.attrib["height"] = "{}mm".format(view_height)
|
||||
root.attrib["viewBox"] = "0 0 {} {}".format(view_width, view_height)
|
||||
|
||||
sheet_tree.write(sheet_path)
|
||||
|
||||
def convert_to_mm(self, value):
|
||||
# CSS is what defines these possibilities
|
||||
# https://www.w3.org/TR/SVG/refs.html#ref-css-values-3
|
||||
|
||||
@@ -21,6 +21,7 @@ import re
|
||||
import bpy
|
||||
import math
|
||||
import bmesh
|
||||
import shutil
|
||||
import pystache
|
||||
import mathutils
|
||||
import xml.etree.ElementTree as ET
|
||||
@@ -65,10 +66,9 @@ class SvgWriter:
|
||||
self.scale = 1 / 100 # 1:100
|
||||
self.camera_width = None
|
||||
self.camera_height = None
|
||||
self.related_paths = None
|
||||
self.resource_paths = {}
|
||||
|
||||
def create_blank_svg(self, output_path):
|
||||
self.define_related_paths() # making sure all paths are defined
|
||||
self.calculate_scale()
|
||||
self.svg = svgwrite.Drawing(
|
||||
output_path,
|
||||
@@ -85,35 +85,24 @@ class SvgWriter:
|
||||
self.svg.save(pretty=True)
|
||||
|
||||
def draw_underlay(self, image):
|
||||
self.svg.add(
|
||||
self.svg.image(
|
||||
os.path.join("..", "diagrams", os.path.basename(image)),
|
||||
width=self.width,
|
||||
height=self.height,
|
||||
)
|
||||
)
|
||||
self.svg.add(self.svg.image(os.path.basename(image), width=self.width, height=self.height))
|
||||
return self
|
||||
|
||||
def define_related_paths(self, **related_paths):
|
||||
if not self.related_paths:
|
||||
self.related_paths = {}
|
||||
|
||||
if not related_paths:
|
||||
related_paths = {
|
||||
"Stylesheet": os.path.join(self.data_dir, "styles", f"default.css"),
|
||||
"Markers": os.path.join(self.data_dir, "templates", "markers.svg"),
|
||||
"Symbols": os.path.join(self.data_dir, "templates", "symbols.svg"),
|
||||
"Patterns": os.path.join(self.data_dir, "templates", "patterns.svg"),
|
||||
}
|
||||
for path_name in list(related_paths.keys()):
|
||||
if path_name in self.related_paths:
|
||||
del related_paths[path_name]
|
||||
|
||||
for path_name in related_paths:
|
||||
uri = related_paths[path_name]
|
||||
custom_path = tool.Ifc.resolve_uri(uri)
|
||||
if custom_path:
|
||||
self.related_paths[path_name] = custom_path
|
||||
def setup_drawing_resource_paths(self, element):
|
||||
pset = ifcopenshell.util.element.get_pset(element, "EPset_Drawing")
|
||||
for resource in ("Stylesheet", "Markers", "Symbols", "Patterns"):
|
||||
resource_path = pset.get(resource)
|
||||
if not resource_path:
|
||||
self.resource_paths[resource] = None
|
||||
continue
|
||||
resource_path = tool.Ifc.resolve_uri(resource_path)
|
||||
os.makedirs(os.path.dirname(resource_path), exist_ok=True)
|
||||
if not os.path.exists(resource_path):
|
||||
resource_basename = os.path.basename(resource_path)
|
||||
ootb_resource = os.path.join(bpy.context.scene.BIMProperties.data_dir, "assets", resource_basename)
|
||||
if os.path.exists(ootb_resource):
|
||||
shutil.copy(ootb_resource, resource_path)
|
||||
self.resource_paths[resource] = resource_path
|
||||
|
||||
def define_boilerplate(self):
|
||||
self.add_stylesheet()
|
||||
@@ -132,33 +121,43 @@ class SvgWriter:
|
||||
self.height = self.raw_height * self.svg_scale
|
||||
|
||||
def add_stylesheet(self):
|
||||
with open(self.related_paths["Stylesheet"], "r") as stylesheet:
|
||||
if not self.resource_paths["Stylesheet"] or not os.path.exists(self.resource_paths["Stylesheet"]):
|
||||
return
|
||||
with open(self.resource_paths["Stylesheet"], "r") as stylesheet:
|
||||
self.svg.defs.add(self.svg.style(stylesheet.read()))
|
||||
|
||||
def add_markers(self):
|
||||
tree = ET.parse(self.related_paths["Markers"])
|
||||
if not self.resource_paths["Markers"] or not os.path.exists(self.resource_paths["Markers"]):
|
||||
return
|
||||
tree = ET.parse(self.resource_paths["Markers"])
|
||||
root = tree.getroot()
|
||||
for child in root:
|
||||
self.svg.defs.add(External(child))
|
||||
|
||||
def add_symbols(self):
|
||||
tree = ET.parse(self.related_paths["Symbols"])
|
||||
if not self.resource_paths["Symbols"] or not os.path.exists(self.resource_paths["Symbols"]):
|
||||
return
|
||||
tree = ET.parse(self.resource_paths["Symbols"])
|
||||
root = tree.getroot()
|
||||
for child in root:
|
||||
self.svg.defs.add(External(child))
|
||||
|
||||
def find_xml_symbol_by_id(self, id):
|
||||
tree = ET.parse(self.related_paths["Symbols"])
|
||||
tree = ET.parse(self.resource_paths["Symbols"])
|
||||
xml_symbol = tree.find(f'.//*[@id="{id}"]')
|
||||
return External(xml_symbol) if xml_symbol else None
|
||||
|
||||
def add_patterns(self):
|
||||
tree = ET.parse(self.related_paths["Patterns"])
|
||||
if not self.resource_paths["Patterns"] or not os.path.exists(self.resource_paths["Patterns"]):
|
||||
return
|
||||
tree = ET.parse(self.resource_paths["Patterns"])
|
||||
root = tree.getroot()
|
||||
for child in root:
|
||||
self.svg.defs.add(External(child))
|
||||
|
||||
def draw_annotations(self, annotations):
|
||||
def draw_annotations(self, annotations, precision, decimal_places):
|
||||
self.precision = precision
|
||||
self.decimal_places = decimal_places
|
||||
for element in annotations:
|
||||
obj = tool.Ifc.get_object(element)
|
||||
if not obj or element.ObjectType == "DRAWING":
|
||||
@@ -228,18 +227,14 @@ class SvgWriter:
|
||||
# TODO: allow metric to be configurable
|
||||
rl = (matrix_world @ points[0].co.xyz).z
|
||||
if bpy.context.scene.unit_settings.system == "IMPERIAL":
|
||||
rl = helper.format_distance(rl)
|
||||
rl = helper.format_distance(rl, precision=self.precision, decimal_places=self.decimal_places)
|
||||
else:
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
rl /= unit_scale
|
||||
rl = ifcopenshell.util.geolocation.auto_z2e(tool.Ifc.get(), rl)
|
||||
rl *= unit_scale
|
||||
rl = "{:.3f}m".format(rl)
|
||||
text_style = {
|
||||
"text-anchor": "start",
|
||||
"alignment-baseline": "baseline",
|
||||
"dominant-baseline": "baseline",
|
||||
}
|
||||
text_style = self.get_box_alignment_parameters("bottom-left")
|
||||
self.svg.add(self.svg.text(f"RL +{rl}", insert=tuple(text_position), class_="SECTIONLEVEL", **text_style))
|
||||
if tag:
|
||||
self.svg.add(self.svg.text(tag, insert=(text_position[0], text_position[1] - 5), **text_style))
|
||||
@@ -268,11 +263,7 @@ class SvgWriter:
|
||||
"UP",
|
||||
insert=tuple(text_position),
|
||||
class_="STAIR",
|
||||
**{
|
||||
"text-anchor": "middle",
|
||||
"alignment-baseline": "middle",
|
||||
"dominant-baseline": "middle",
|
||||
},
|
||||
**self.get_box_alignment_parameters("center"),
|
||||
)
|
||||
)
|
||||
|
||||
@@ -296,16 +287,13 @@ class SvgWriter:
|
||||
)
|
||||
line["stroke-dasharray"] = "12.5, 3, 3, 3"
|
||||
axis_tag = tool.Ifc.get_entity(obj).Name
|
||||
text_style = self.get_box_alignment_parameters("center")
|
||||
self.svg.add(
|
||||
self.svg.text(
|
||||
axis_tag,
|
||||
insert=tuple(start * self.svg_scale),
|
||||
class_="GRID",
|
||||
**{
|
||||
"text-anchor": "middle",
|
||||
"alignment-baseline": "middle",
|
||||
"dominant-baseline": "middle",
|
||||
},
|
||||
**text_style,
|
||||
)
|
||||
)
|
||||
self.svg.add(
|
||||
@@ -313,11 +301,7 @@ class SvgWriter:
|
||||
axis_tag,
|
||||
insert=tuple(end * self.svg_scale),
|
||||
class_="GRID",
|
||||
**{
|
||||
"text-anchor": "middle",
|
||||
"alignment-baseline": "middle",
|
||||
"dominant-baseline": "middle",
|
||||
},
|
||||
**text_style,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -356,7 +340,7 @@ class SvgWriter:
|
||||
def get_attribute_classes(self, obj):
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
global_id = "GlobalId-{}".format(element.GlobalId)
|
||||
predefined_type = "PredefinedType-" + self.canonicalise_class_name(
|
||||
predefined_type = "PredefinedType-" + tool.Drawing.canonicalise_class_name(
|
||||
str(ifcopenshell.util.element.get_predefined_type(element))
|
||||
)
|
||||
classes = [global_id, element.is_a(), predefined_type]
|
||||
@@ -366,12 +350,11 @@ class SvgWriter:
|
||||
for key in self.metadata:
|
||||
value = ifcopenshell.util.selector.get_element_value(element, key)
|
||||
if value:
|
||||
classes.append(self.canonicalise_class_name(key) + "-" + self.canonicalise_class_name(str(value)))
|
||||
classes.append(
|
||||
tool.Drawing.canonicalise_class_name(key) + "-" + tool.Drawing.canonicalise_class_name(str(value))
|
||||
)
|
||||
return classes
|
||||
|
||||
def canonicalise_class_name(self, name):
|
||||
return re.sub("[^0-9a-zA-Z]+", "", name)
|
||||
|
||||
def draw_line_annotation(self, obj):
|
||||
# TODO: properly scope these offsets
|
||||
x_offset = self.raw_width / 2
|
||||
@@ -574,11 +557,7 @@ class SvgWriter:
|
||||
|
||||
reference_id, sheet_id = self.get_reference_and_sheet_id_from_annotation(tool.Ifc.get_entity(obj))
|
||||
text_position = symbol_position_svg
|
||||
text_style = {
|
||||
"text-anchor": "middle",
|
||||
"alignment-baseline": "middle",
|
||||
"dominant-baseline": "middle",
|
||||
}
|
||||
text_style = self.get_box_alignment_parameters("center")
|
||||
self.svg.add(
|
||||
self.svg.text(
|
||||
reference_id,
|
||||
@@ -611,11 +590,7 @@ class SvgWriter:
|
||||
|
||||
reference_id, sheet_id = self.get_reference_and_sheet_id_from_annotation(tool.Ifc.get_entity(obj))
|
||||
text_position = symbol_position_svg
|
||||
text_style = {
|
||||
"text-anchor": "middle",
|
||||
"alignment-baseline": "middle",
|
||||
"dominant-baseline": "middle",
|
||||
}
|
||||
text_style = self.get_box_alignment_parameters("center")
|
||||
self.svg.add(
|
||||
self.svg.text(
|
||||
reference_id, insert=(text_position[0], text_position[1] - 2.5), class_="ELEVATION", **text_style
|
||||
@@ -642,6 +617,43 @@ class SvgWriter:
|
||||
return (reference_id, sheet_id)
|
||||
return ("-", "-")
|
||||
|
||||
def get_box_alignment_parameters(self, box_alignment):
|
||||
"""Convenience method to get svg parameters for text alignment
|
||||
in a readable way.
|
||||
|
||||
Metehod expecting values like:
|
||||
|
||||
`top-left`, `top-middle`, `top-right`,
|
||||
|
||||
`middle-left`, `center`, `middle-right`,
|
||||
|
||||
`bottom-left`, `bottom-middle`, `bottom-right`
|
||||
"""
|
||||
# reference for alignment values:
|
||||
# https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/text-anchor
|
||||
# https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/alignment-baseline
|
||||
vertical_alignment = {
|
||||
"top": "hanging",
|
||||
"bottom": "baseline",
|
||||
"center": "middle",
|
||||
"middle": "middle",
|
||||
}
|
||||
alignment_baseline = vertical_alignment[next(align for align in vertical_alignment if align in box_alignment)]
|
||||
horizontal_alignment = {
|
||||
"left": "start",
|
||||
"right": "end",
|
||||
"center": "middle",
|
||||
"middle": "middle",
|
||||
}
|
||||
text_anchor = horizontal_alignment[next(align for align in horizontal_alignment if align in box_alignment)]
|
||||
|
||||
# using dominant-baseline because we plan to use <tspan> subtags
|
||||
# otherwise alignment-baseline would be sufficient
|
||||
return {
|
||||
"dominant-baseline": alignment_baseline,
|
||||
"text-anchor": text_anchor,
|
||||
}
|
||||
|
||||
def draw_text_annotation(self, text_obj, position):
|
||||
x_offset = self.raw_width / 2
|
||||
y_offset = self.raw_height / 2
|
||||
@@ -653,16 +665,17 @@ class SvgWriter:
|
||||
text_position = Vector(((x_offset + text_position.x), (y_offset - text_position.y)))
|
||||
text_position_svg = text_position * self.svg_scale
|
||||
|
||||
local_x_axis = text_obj.matrix_world.to_quaternion() @ Vector((1, 0, 0))
|
||||
projected_x_axis = self.project_point_onto_camera(position + local_x_axis)
|
||||
angle = math.degrees(
|
||||
(Vector((x_offset + projected_x_axis.x, y_offset - projected_x_axis.y)) - text_position).angle_signed(
|
||||
Vector((1, 0))
|
||||
)
|
||||
)
|
||||
def get_basis_vector(matrix, i=0):
|
||||
"""returns basis vector for i in world space, unaffected by object scale"""
|
||||
return matrix.inverted()[i].to_3d().normalized()
|
||||
|
||||
text_dir_world_x_axis = get_basis_vector(text_obj.matrix_world)
|
||||
text_dir = (self.camera.matrix_world.inverted().to_quaternion() @ text_dir_world_x_axis).to_2d().normalized()
|
||||
angle = math.degrees(-text_dir.angle_signed(Vector((1, 0))))
|
||||
|
||||
transform = "rotate({}, {}, {})".format(angle, *text_position_svg)
|
||||
classes_str = " ".join(self.get_attribute_classes(text_obj))
|
||||
classes = self.get_attribute_classes(text_obj)
|
||||
classes_str = " ".join(classes)
|
||||
|
||||
symbol = tool.Drawing.get_annotation_symbol(element)
|
||||
template_text_fields = []
|
||||
@@ -686,34 +699,6 @@ class SvgWriter:
|
||||
if not symbol_svg or not template_text_fields:
|
||||
self.svg.add(self.svg.use(f"#{symbol}", insert=text_position_svg))
|
||||
|
||||
def get_box_alignment_parameters(box_alignment):
|
||||
# reference for alignment values:
|
||||
# https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/text-anchor
|
||||
# https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/alignment-baseline
|
||||
vertical_alignment = {
|
||||
"top": "hanging",
|
||||
"bottom": "baseline",
|
||||
"center": "middle",
|
||||
"middle": "middle",
|
||||
}
|
||||
alignment_baseline = vertical_alignment[
|
||||
next(align for align in vertical_alignment if align in box_alignment)
|
||||
]
|
||||
horizontal_alignment = {
|
||||
"left": "start",
|
||||
"right": "end",
|
||||
"center": "middle",
|
||||
"middle": "middle",
|
||||
}
|
||||
text_anchor = horizontal_alignment[next(align for align in horizontal_alignment if align in box_alignment)]
|
||||
|
||||
# using dominant-baseline because we plan to use <tspan> subtags
|
||||
# otherwise alignment-baseline would be sufficient
|
||||
return {
|
||||
"dominant-baseline": alignment_baseline,
|
||||
"text-anchor": text_anchor,
|
||||
}
|
||||
|
||||
for text_literal in text_literals:
|
||||
# after pretty indentation some redundant spaces can occur in svg tags
|
||||
# this is why we apply "font-size: 0;" to the text tag to remove those spaces
|
||||
@@ -721,22 +706,32 @@ class SvgWriter:
|
||||
# ref: https://github.com/IfcOpenShell/IfcOpenShell/issues/2833#issuecomment-1471584960
|
||||
|
||||
text = tool.Drawing.replace_text_literal_variables(text_literal.Literal, product)
|
||||
text_tag = self.svg.text(
|
||||
"",
|
||||
**{
|
||||
"transform": transform,
|
||||
"style": "font-size: 0;",
|
||||
},
|
||||
**get_box_alignment_parameters(text_literal.BoxAlignment),
|
||||
)
|
||||
self.svg.add(text_tag)
|
||||
attribs = {
|
||||
"transform": transform,
|
||||
"style": "font-size: 0;",
|
||||
}
|
||||
|
||||
def add_text_tag(add_fill_bg):
|
||||
text_tag = self.svg.text(
|
||||
"",
|
||||
**(attribs | {"filter": "url(#fill-background)"}) if add_fill_bg else attribs,
|
||||
**self.get_box_alignment_parameters(text_literal.BoxAlignment),
|
||||
)
|
||||
self.svg.add(text_tag)
|
||||
|
||||
text_lines = text.replace("\\n", "\n").split("\n")
|
||||
|
||||
for line_number, text_line in enumerate(text_lines):
|
||||
# position has to be inserted at tspan to avoid x offset between tspans
|
||||
tspan = self.svg.tspan(text_line, class_=classes_str, insert=text_position_svg)
|
||||
# doing it here and not in tspan constructor because constructor adds unnecessary spaces
|
||||
tspan.update({"dy": f"{line_number}em"})
|
||||
text_tag.add(tspan)
|
||||
|
||||
if "fill-bg" in classes:
|
||||
add_text_tag(True)
|
||||
add_text_tag(False)
|
||||
|
||||
for line_number, text_line in enumerate(text.replace("\\n", "\n").split("\n")):
|
||||
# position has to be inserted at tspan to avoid x offset between tspans
|
||||
t_span = self.svg.tspan(text_line, class_=classes_str, insert=text_position_svg)
|
||||
# doing it here and not in tspan constructor because constructor adds unnecessary spaces
|
||||
t_span.update({"dy": f"{line_number}em"})
|
||||
text_tag.add(t_span)
|
||||
|
||||
def draw_break_annotations(self, obj):
|
||||
x_offset = self.raw_width / 2
|
||||
@@ -784,29 +779,24 @@ class SvgWriter:
|
||||
)
|
||||
)
|
||||
# TODO: allow metric to be configurable
|
||||
rl = ((matrix_world @ points[0].co).xyz + obj.location).z
|
||||
rl = (matrix_world @ points[0].co).z
|
||||
if bpy.context.scene.unit_settings.system == "IMPERIAL":
|
||||
rl = helper.format_distance(rl)
|
||||
rl = helper.format_distance(rl, precision=self.precision, decimal_places=self.decimal_places)
|
||||
else:
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
rl /= unit_scale
|
||||
rl = ifcopenshell.util.geolocation.auto_z2e(tool.Ifc.get(), rl)
|
||||
rl *= unit_scale
|
||||
rl = "{:.3f}m".format(rl)
|
||||
if projected_points[0].x > projected_points[-1].x:
|
||||
text_anchor = "end"
|
||||
else:
|
||||
text_anchor = "start"
|
||||
|
||||
box_alignment = "bottom-left" if projected_points[0].x <= projected_points[-1].x else "bottom-right"
|
||||
text_style = self.get_box_alignment_parameters(box_alignment)
|
||||
self.svg.add(
|
||||
self.svg.text(
|
||||
"RL +{}".format(rl),
|
||||
insert=tuple(text_position),
|
||||
class_="PLANLEVEL",
|
||||
**{
|
||||
"text-anchor": text_anchor,
|
||||
"alignment-baseline": "baseline",
|
||||
"dominant-baseline": "baseline",
|
||||
},
|
||||
**text_style,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -922,12 +912,7 @@ class SvgWriter:
|
||||
text_offset = (text_position - center_position).xy.normalized() * 5
|
||||
text_position += text_offset
|
||||
|
||||
text_style = {
|
||||
"text-anchor": "middle",
|
||||
"alignment-baseline": "middle",
|
||||
"dominant-baseline": "middle",
|
||||
}
|
||||
|
||||
text_style = self.get_box_alignment_parameters("center")
|
||||
angle_text = abs(round(math.degrees(angle), 3))
|
||||
if is_reflex:
|
||||
angle_text = 360 - angle_text
|
||||
@@ -988,14 +973,9 @@ class SvgWriter:
|
||||
)
|
||||
text_position += text_offset
|
||||
|
||||
text_style = {
|
||||
"text-anchor": "middle",
|
||||
"alignment-baseline": "middle",
|
||||
"dominant-baseline": "middle",
|
||||
}
|
||||
|
||||
text_style = self.get_box_alignment_parameters("center")
|
||||
radius = (points[-1].co - points[-2].co).length
|
||||
radius = helper.format_distance(radius)
|
||||
radius = helper.format_distance(radius, precision=self.precision, decimal_places=self.decimal_places)
|
||||
tag = element.Description or f"R{radius}"
|
||||
|
||||
self.svg.add(self.svg.text(tag, insert=tuple(text_position), class_="RADIUS", **text_style))
|
||||
@@ -1051,7 +1031,7 @@ class SvgWriter:
|
||||
elif element.ObjectType == "SLOPE_FRACTION":
|
||||
if angle == 90:
|
||||
return "-"
|
||||
return f"{helper.format_distance(rise)} / {helper.format_distance(run)}"
|
||||
return f"{helper.format_distance(rise, precision=self.precision, decimal_places=self.decimal_places)} / {helper.format_distance(run, precision=self.precision, decimal_places=self.decimal_places)}"
|
||||
elif element.ObjectType == "SLOPE_PERCENT":
|
||||
if angle == 90:
|
||||
return "-"
|
||||
@@ -1066,19 +1046,16 @@ class SvgWriter:
|
||||
)
|
||||
text_position += text_offset
|
||||
|
||||
text_style = {
|
||||
"text-anchor": "middle",
|
||||
"alignment-baseline": "middle",
|
||||
"dominant-baseline": "middle",
|
||||
}
|
||||
|
||||
text_style = self.get_box_alignment_parameters("center")
|
||||
self.svg.add(self.svg.text(tag, insert=tuple(text_position), class_="RADIUS", **text_style))
|
||||
|
||||
def draw_diameter_annotations(self, obj):
|
||||
classes = self.get_attribute_classes(obj)
|
||||
matrix_world = obj.matrix_world
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
text_override = element.Description
|
||||
dimension_text = element.Description
|
||||
dimension_data = DecoratorData.get_dimension_data(obj)
|
||||
|
||||
for spline in obj.data.splines:
|
||||
points = self.get_spline_points(spline)
|
||||
for i, p in enumerate(points):
|
||||
@@ -1087,22 +1064,35 @@ class SvgWriter:
|
||||
v0_global = matrix_world @ points[i].co.xyz
|
||||
v1_global = matrix_world @ points[i + 1].co.xyz
|
||||
self.draw_dimension_annotation(
|
||||
v0_global, v1_global, classes, text_override, text_format=lambda x: "D" + x
|
||||
v0_global,
|
||||
v1_global,
|
||||
classes,
|
||||
dimension_text,
|
||||
text_format=lambda x: "D" + x,
|
||||
show_description_only=dimension_data["show_description_only"],
|
||||
suppress_zero_inches=dimension_data["suppress_zero_inches"],
|
||||
)
|
||||
|
||||
def draw_dimension_annotations(self, obj):
|
||||
classes = self.get_attribute_classes(obj)
|
||||
matrix_world = obj.matrix_world
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
text_override = element.Description
|
||||
dimension_text = element.Description
|
||||
dimension_data = DecoratorData.get_dimension_data(obj)
|
||||
|
||||
for spline in obj.data.splines:
|
||||
points = self.get_spline_points(spline)
|
||||
for i, p in enumerate(points):
|
||||
if i + 1 >= len(points):
|
||||
continue
|
||||
for i in range(len(points) - 1):
|
||||
v0_global = matrix_world @ points[i].co.xyz
|
||||
v1_global = matrix_world @ points[i + 1].co.xyz
|
||||
self.draw_dimension_annotation(v0_global, v1_global, classes, text_override)
|
||||
self.draw_dimension_annotation(
|
||||
v0_global,
|
||||
v1_global,
|
||||
classes,
|
||||
dimension_text=dimension_text,
|
||||
show_description_only=dimension_data["show_description_only"],
|
||||
suppress_zero_inches=dimension_data["suppress_zero_inches"],
|
||||
)
|
||||
|
||||
def draw_measureit_arch_dimension_annotations(self):
|
||||
try:
|
||||
@@ -1116,46 +1106,56 @@ class SvgWriter:
|
||||
Vector(coord[0]), Vector(coord[1]), ["IfcAnnotation", "PredefinedType-DIMENSION"]
|
||||
)
|
||||
|
||||
def draw_dimension_annotation(self, v0_global, v1_global, classes, text_override=None, text_format=lambda x: x):
|
||||
x_offset = self.raw_width / 2
|
||||
y_offset = self.raw_height / 2
|
||||
def draw_dimension_annotation(
|
||||
self,
|
||||
v0_global,
|
||||
v1_global,
|
||||
classes,
|
||||
dimension_text=None,
|
||||
text_format=lambda x: x,
|
||||
show_description_only=False,
|
||||
suppress_zero_inches=False,
|
||||
):
|
||||
offset = Vector([self.raw_width, self.raw_height]) / 2
|
||||
v0 = self.project_point_onto_camera(v0_global)
|
||||
v1 = self.project_point_onto_camera(v1_global)
|
||||
start = Vector(((x_offset + v0.x), (y_offset - v0.y)))
|
||||
end = Vector(((x_offset + v1.x), (y_offset - v1.y)))
|
||||
start = (offset + v0.xy * Vector((1, -1))) * self.svg_scale
|
||||
end = (offset + v1.xy * Vector((1, -1))) * self.svg_scale
|
||||
mid = ((end - start) / 2) + start
|
||||
vector = end - start
|
||||
perpendicular = Vector((vector.y, -vector.x)).normalized()
|
||||
dimension = (v1_global - v0_global).length
|
||||
dimension = helper.format_distance(dimension)
|
||||
sheet_dimension = ((end * self.svg_scale) - (start * self.svg_scale)).length
|
||||
if sheet_dimension < 5: # annotation can't fit
|
||||
# offset text to right of marker
|
||||
text_position = (end * self.svg_scale) + perpendicular + (3 * vector.normalized())
|
||||
else:
|
||||
text_position = (mid * self.svg_scale) + perpendicular
|
||||
dimension = helper.format_distance(
|
||||
dimension,
|
||||
precision=self.precision,
|
||||
decimal_places=self.decimal_places,
|
||||
suppress_zero_inches=suppress_zero_inches,
|
||||
)
|
||||
sheet_dimension = (end - start).length
|
||||
|
||||
# if annotation can't fit offset text to the right of marker
|
||||
text_position = mid if sheet_dimension > 5 else (end + (3 * vector.normalized()))
|
||||
rotation = math.degrees(vector.angle_signed(Vector((1, 0))))
|
||||
line = self.svg.add(
|
||||
self.svg.line(
|
||||
start=tuple(start * self.svg_scale), end=tuple(end * self.svg_scale), class_=" ".join(classes)
|
||||
)
|
||||
)
|
||||
if text_override is not None:
|
||||
text = text_override
|
||||
else:
|
||||
text = str(dimension)
|
||||
text = text_format(text)
|
||||
self.svg.add(
|
||||
self.svg.text(
|
||||
text,
|
||||
insert=tuple(text_position),
|
||||
|
||||
line = self.svg.line(start=start, end=end, class_=" ".join(classes))
|
||||
self.svg.add(line)
|
||||
|
||||
def create_text_tag(text, text_position, box_alignment):
|
||||
text_kwargs = {"transform": "rotate({} {} {})".format(rotation, text_position.x, text_position.y)}
|
||||
return self.svg.text(
|
||||
text_format(text),
|
||||
insert=text_position,
|
||||
class_="DIMENSION",
|
||||
**{
|
||||
"transform": "rotate({} {} {})".format(rotation, text_position.x, text_position.y),
|
||||
"text-anchor": "middle",
|
||||
},
|
||||
**(text_kwargs | self.get_box_alignment_parameters(box_alignment)),
|
||||
)
|
||||
)
|
||||
|
||||
if not show_description_only:
|
||||
self.svg.add(create_text_tag(str(dimension), text_position + perpendicular, "bottom-middle"))
|
||||
if dimension_text:
|
||||
self.svg.add(create_text_tag(dimension_text, text_position - perpendicular, "top-middle"))
|
||||
|
||||
elif show_description_only and dimension_text:
|
||||
self.svg.add(create_text_tag(dimension_text, text_position + perpendicular, "bottom-middle"))
|
||||
|
||||
def project_point_onto_camera(self, point):
|
||||
# TODO is this needlessly complex?
|
||||
|
||||
@@ -62,6 +62,11 @@ class BIM_PT_camera(Panel):
|
||||
row.prop(props, "has_annotation", icon="MOD_EDGESPLIT")
|
||||
row.prop(dprops, "should_use_annotation_cache", text="", icon="FILE_REFRESH")
|
||||
|
||||
row = layout.row()
|
||||
row.prop(props, "calculate_shapely_surfaces")
|
||||
row = layout.row()
|
||||
row.prop(props, "calculate_svgfill_surfaces")
|
||||
|
||||
row = layout.row()
|
||||
row.prop(dprops, "should_extract")
|
||||
|
||||
@@ -84,7 +89,7 @@ class BIM_PT_camera(Panel):
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.operator("bim.create_drawing", text="Create Drawing", icon="OUTPUT")
|
||||
op = row.operator("bim.open_view", icon="URL", text="")
|
||||
op = row.operator("bim.open_drawing", icon="URL", text="")
|
||||
op.view = context.active_object.name.split("/")[1]
|
||||
|
||||
|
||||
@@ -123,9 +128,6 @@ class BIM_PT_drawing_underlay(Panel):
|
||||
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")
|
||||
@@ -158,6 +160,14 @@ class BIM_PT_drawings(Panel):
|
||||
if not DrawingsData.is_loaded:
|
||||
DrawingsData.load()
|
||||
|
||||
if not DrawingsData.data["has_saved_ifc"]:
|
||||
row = self.layout.row()
|
||||
row.label(text="Project Not Yet Saved", icon="ERROR")
|
||||
row = self.layout.row()
|
||||
op = row.operator("export_ifc.bim", icon="EXPORT", text="Save Project")
|
||||
op.should_save_as = False
|
||||
return
|
||||
|
||||
self.props = context.scene.DocProperties
|
||||
|
||||
if not self.props.is_editing_drawings:
|
||||
@@ -185,9 +195,11 @@ class BIM_PT_drawings(Panel):
|
||||
).drawing = active_drawing.ifc_definition_id
|
||||
col = row.column()
|
||||
col.alignment = "RIGHT"
|
||||
op = row.operator("bim.open_view", icon="URL", text="")
|
||||
op = row.operator("bim.select_all_drawings", icon="SELECT_SUBTRACT", text="")
|
||||
op = row.operator("bim.open_drawing", icon="URL", text="")
|
||||
op.view = active_drawing.name
|
||||
op = row.operator("bim.activate_view", icon="OUTLINER_OB_CAMERA", text="")
|
||||
row.operator("bim.activate_model", icon="VIEW3D", text="")
|
||||
op = row.operator("bim.activate_drawing", icon="OUTLINER_OB_CAMERA", text="")
|
||||
op.drawing = active_drawing.ifc_definition_id
|
||||
row.operator("bim.create_drawing", text="", icon="OUTPUT")
|
||||
self.layout.template_list(
|
||||
@@ -220,6 +232,14 @@ class BIM_PT_schedules(Panel):
|
||||
if not SchedulesData.is_loaded:
|
||||
SchedulesData.load()
|
||||
|
||||
if not SchedulesData.data["has_saved_ifc"]:
|
||||
row = self.layout.row()
|
||||
row.label(text="Project Not Yet Saved", icon="ERROR")
|
||||
row = self.layout.row()
|
||||
op = row.operator("export_ifc.bim", icon="EXPORT", text="Save Project")
|
||||
op.should_save_as = False
|
||||
return
|
||||
|
||||
self.props = context.scene.DocProperties
|
||||
|
||||
if not self.props.is_editing_schedules:
|
||||
@@ -263,6 +283,14 @@ class BIM_PT_sheets(Panel):
|
||||
if not SheetsData.is_loaded:
|
||||
SheetsData.load()
|
||||
|
||||
if not SheetsData.data["has_saved_ifc"]:
|
||||
row = self.layout.row()
|
||||
row.label(text="Project Not Yet Saved", icon="ERROR")
|
||||
row = self.layout.row()
|
||||
op = row.operator("export_ifc.bim", icon="EXPORT", text="Save Project")
|
||||
op.should_save_as = False
|
||||
return
|
||||
|
||||
self.props = context.scene.DocProperties
|
||||
|
||||
if not self.props.is_editing_sheets:
|
||||
@@ -280,6 +308,7 @@ class BIM_PT_sheets(Panel):
|
||||
active_sheet = self.props.sheets[self.props.active_sheet_index]
|
||||
row = self.layout.row(align=True)
|
||||
row.alignment = "RIGHT"
|
||||
row.operator("bim.edit_sheet", icon="GREASEPENCIL", text="")
|
||||
row.operator("bim.open_sheet", icon="URL", text="")
|
||||
row.operator("bim.add_drawing_to_sheet", icon="IMAGE_PLANE", text="")
|
||||
row.operator("bim.add_schedule_to_sheet", icon="PRESET_NEW", text="")
|
||||
@@ -343,7 +372,7 @@ class BIM_PT_text(Panel):
|
||||
element = tool.Ifc.get_entity(context.active_object)
|
||||
if not element:
|
||||
return
|
||||
return element.is_a("IfcAnnotation") and element.ObjectType in ["TEXT", "TEXT_LEADER"]
|
||||
return tool.Drawing.is_annotation_object_type(element, ["TEXT", "TEXT_LEADER"])
|
||||
|
||||
def draw(self, context):
|
||||
obj = context.active_object
|
||||
@@ -470,6 +499,7 @@ class BIM_PT_annotation_utilities(Panel):
|
||||
op = row.operator("bim.add_annotation", text="Batting", icon="FORCE_FORCE")
|
||||
op.object_type = "BATTING"
|
||||
op.data_type = "mesh"
|
||||
op.description = "Add batting annotation.\nThickness could be changed through Thickness property of BBIM_Batting property set"
|
||||
op = row.operator("bim.add_annotation", text="Fill Area", icon="NODE_TEXTURE")
|
||||
op.object_type = "FILL_AREA"
|
||||
|
||||
@@ -487,6 +517,8 @@ class BIM_UL_drawinglist(bpy.types.UIList):
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
if item:
|
||||
row = layout.row(align=True)
|
||||
selected_icon = "CHECKBOX_HLT" if item.is_selected else "CHECKBOX_DEHLT"
|
||||
row.prop(item, "is_selected", text="", icon=selected_icon)
|
||||
icon = "UV_FACESEL"
|
||||
if item.target_view == "ELEVATION_VIEW":
|
||||
icon = "UV_VERTEXSEL"
|
||||
@@ -503,26 +535,35 @@ class BIM_UL_drawinglist(bpy.types.UIList):
|
||||
|
||||
class BIM_UL_sheets(bpy.types.UIList):
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
if item:
|
||||
row = layout.row(align=True)
|
||||
|
||||
if item.is_sheet:
|
||||
if item.is_expanded:
|
||||
row.operator(
|
||||
"bim.contract_sheet", text="", emboss=False, icon="DISCLOSURE_TRI_DOWN"
|
||||
).sheet = item.ifc_definition_id
|
||||
else:
|
||||
row.operator(
|
||||
"bim.expand_sheet", text="", emboss=False, icon="DISCLOSURE_TRI_RIGHT"
|
||||
).sheet = item.ifc_definition_id
|
||||
else:
|
||||
row.label(text="", icon="BLANK1")
|
||||
if item.reference_type == "DRAWING":
|
||||
row.label(text="", icon="IMAGE_DATA")
|
||||
elif item.reference_type == "SCHEDULE":
|
||||
row.label(text="", icon="LONGDISPLAY")
|
||||
|
||||
name = "{} - {}".format(item.identification or "X", item.name or "Unnamed")
|
||||
row.label(text=name)
|
||||
else:
|
||||
if not item:
|
||||
layout.label(text="", translate=False)
|
||||
return
|
||||
|
||||
row = layout.row(align=True)
|
||||
if item.is_sheet:
|
||||
if item.is_expanded:
|
||||
row.operator(
|
||||
"bim.contract_sheet", text="", emboss=False, icon="DISCLOSURE_TRI_DOWN"
|
||||
).sheet = item.ifc_definition_id
|
||||
else:
|
||||
row.operator(
|
||||
"bim.expand_sheet", text="", emboss=False, icon="DISCLOSURE_TRI_RIGHT"
|
||||
).sheet = item.ifc_definition_id
|
||||
|
||||
row.label(text=f"{item.identification} - {item.name}")
|
||||
else:
|
||||
row.label(text="", icon="BLANK1")
|
||||
if item.reference_type == "DRAWING":
|
||||
row.label(text="", icon="IMAGE_DATA")
|
||||
elif item.reference_type == "SCHEDULE":
|
||||
row.label(text="", icon="LONGDISPLAY")
|
||||
elif item.reference_type == "TITLEBLOCK":
|
||||
row.label(text="", icon="MENU_PANEL")
|
||||
elif item.reference_type == "REVISION":
|
||||
row.label(text="", icon="RECOVER_LAST")
|
||||
|
||||
if item.identification:
|
||||
name = f"{item.identification} - {item.name or 'Unnamed'}"
|
||||
else:
|
||||
name = item.name or "Unnamed"
|
||||
row.label(text=name)
|
||||
|
||||
@@ -0,0 +1,296 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2023 @Andrej730
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
import os
|
||||
import bpy
|
||||
import blenderbim.tool as tool
|
||||
from blenderbim.bim.helper import prop_with_search
|
||||
from bpy.types import WorkSpaceTool
|
||||
|
||||
# from blenderbim.bim.module.model.data import AuthoringData, RailingData, RoofData
|
||||
from blenderbim.bim.module.drawing.prop import ANNOTATION_TYPES_DATA
|
||||
from blenderbim.bim.module.drawing.data import DecoratorData, AnnotationData
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
import blenderbim.bim.handler
|
||||
|
||||
|
||||
# declaring it here to avoid circular import problems
|
||||
class Operator:
|
||||
def execute(self, context):
|
||||
IfcStore.execute_ifc_operator(self, context)
|
||||
blenderbim.bim.handler.refresh_ui_data()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AnnotationTool(WorkSpaceTool):
|
||||
bl_space_type = "VIEW_3D"
|
||||
bl_context_mode = "OBJECT"
|
||||
|
||||
bl_idname = "bim.annotation_tool"
|
||||
bl_label = "Annotation Tool"
|
||||
bl_description = "Gives you Annotation related superpowers"
|
||||
bl_icon = os.path.join(os.path.dirname(__file__), "ops.authoring.annotation")
|
||||
bl_widget = None
|
||||
# https://docs.blender.org/api/current/bpy.types.KeyMapItems.html
|
||||
bl_keymap = tool.Blender.get_default_selection_keypmap() + (
|
||||
("bim.annotation_hotkey", {"type": "A", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_A")]}),
|
||||
("bim.annotation_hotkey", {"type": "C", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_C")]}),
|
||||
("bim.annotation_hotkey", {"type": "E", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_E")]}),
|
||||
("bim.annotation_hotkey", {"type": "F", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_F")]}),
|
||||
("bim.annotation_hotkey", {"type": "G", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_G")]}),
|
||||
("bim.annotation_hotkey", {"type": "K", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_K")]}),
|
||||
("bim.annotation_hotkey", {"type": "M", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_M")]}),
|
||||
("bim.annotation_hotkey", {"type": "O", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_O")]}),
|
||||
("bim.annotation_hotkey", {"type": "Q", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_Q")]}),
|
||||
("bim.annotation_hotkey", {"type": "R", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_R")]}),
|
||||
("bim.annotation_hotkey", {"type": "T", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_T")]}),
|
||||
("bim.annotation_hotkey", {"type": "V", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_V")]}),
|
||||
("bim.annotation_hotkey", {"type": "X", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_X")]}),
|
||||
("bim.annotation_hotkey", {"type": "Y", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_Y")]}),
|
||||
("bim.annotation_hotkey", {"type": "D", "value": "PRESS", "alt": True}, {"properties": [("hotkey", "A_D")]}),
|
||||
("bim.annotation_hotkey", {"type": "E", "value": "PRESS", "alt": True}, {"properties": [("hotkey", "A_E")]}),
|
||||
("bim.annotation_hotkey", {"type": "O", "value": "PRESS", "alt": True}, {"properties": [("hotkey", "A_O")]}),
|
||||
)
|
||||
|
||||
def draw_settings(context, layout, ws_tool):
|
||||
# Unlike operators, Blender doesn't treat workspace tools as a class, so we'll create our own.
|
||||
AnnotationToolUI.draw(context, layout)
|
||||
|
||||
|
||||
def add_layout_hotkey_operator(layout, text, hotkey, description):
|
||||
modifiers = {
|
||||
"A": "EVENT_ALT",
|
||||
"S": "EVENT_SHIFT",
|
||||
}
|
||||
modifier, key = hotkey.split("_")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.label(text="", icon=modifiers[modifier])
|
||||
row.label(text="", icon=f"EVENT_{key}")
|
||||
|
||||
op = row.operator("bim.annotation_hotkey", text=text)
|
||||
op.hotkey = hotkey
|
||||
op.description = description
|
||||
return op, row
|
||||
|
||||
|
||||
# TODO: move to operator
|
||||
def create_annotation_type(context):
|
||||
# just empty to store parameters
|
||||
props = context.scene.BIMAnnotationProperties
|
||||
object_type = props.object_type
|
||||
create_representation = props.create_representation_for_type
|
||||
drawing = tool.Ifc.get_entity(bpy.context.scene.camera)
|
||||
|
||||
if props.create_representation_for_type:
|
||||
obj = tool.Drawing.create_annotation_object(drawing, object_type)
|
||||
else:
|
||||
obj = bpy.data.objects.new(object_type, None)
|
||||
|
||||
obj.name = f"{object_type}_TYPE"
|
||||
obj.location = context.scene.cursor.location
|
||||
tool.Drawing.ensure_annotation_in_drawing_plane(obj)
|
||||
|
||||
drawing = tool.Ifc.get_entity(context.scene.camera)
|
||||
ifc_context = tool.Drawing.get_annotation_context(tool.Drawing.get_drawing_target_view(drawing))
|
||||
|
||||
element = tool.Drawing.run_root_assign_class(
|
||||
obj=obj,
|
||||
ifc_class="IfcTypeProduct",
|
||||
predefined_type=object_type,
|
||||
should_add_representation=create_representation,
|
||||
context=ifc_context,
|
||||
ifc_representation_class=tool.Drawing.get_ifc_representation_class(object_type),
|
||||
)
|
||||
element.ApplicableOccurrence = f"IfcAnnotation/{object_type}"
|
||||
|
||||
tool.Blender.select_and_activate_single_object(context, obj)
|
||||
|
||||
|
||||
# TODO: move to operator
|
||||
def create_annotation_occurence(context):
|
||||
# object_type = context.scene.BIMAnnotationProperties.object_type
|
||||
props = context.scene.BIMAnnotationProperties
|
||||
relating_type = tool.Ifc.get().by_id(int(props.relating_type_id))
|
||||
object_type = props.object_type
|
||||
|
||||
drawing = tool.Ifc.get_entity(context.scene.camera)
|
||||
obj = tool.Drawing.create_annotation_object(drawing, object_type)
|
||||
obj.name = relating_type.Name
|
||||
ifc_context = tool.Drawing.get_annotation_context(tool.Drawing.get_drawing_target_view(drawing))
|
||||
relating_type_repr = tool.Drawing.get_annotation_representation(relating_type)
|
||||
element = tool.Drawing.run_root_assign_class(
|
||||
obj=obj,
|
||||
ifc_class="IfcAnnotation",
|
||||
predefined_type=object_type,
|
||||
should_add_representation=not bool(relating_type_repr),
|
||||
context=ifc_context,
|
||||
ifc_representation_class=tool.Drawing.get_ifc_representation_class(object_type),
|
||||
)
|
||||
|
||||
blenderbim.core.type.assign_type(tool.Ifc, tool.Type, element=element, type=relating_type)
|
||||
|
||||
tool.Ifc.run("group.assign_group", group=tool.Drawing.get_drawing_group(drawing), products=[element])
|
||||
tool.Collector.assign(obj)
|
||||
tool.Blender.select_and_activate_single_object(context, obj)
|
||||
|
||||
|
||||
def create_annotation():
|
||||
props = bpy.context.scene.BIMAnnotationProperties
|
||||
create_type_occurence = props.relating_type_id != "0"
|
||||
if create_type_occurence:
|
||||
create_annotation_occurence(bpy.context)
|
||||
else:
|
||||
object_type = props.object_type
|
||||
bpy.ops.bim.add_annotation(object_type=object_type, data_type=ANNOTATION_TYPES_DATA[object_type][-1])
|
||||
|
||||
|
||||
class AnnotationToolUI:
|
||||
@classmethod
|
||||
def draw(cls, context, layout):
|
||||
cls.layout = layout
|
||||
cls.props = context.scene.BIMAnnotationProperties
|
||||
|
||||
row = cls.layout.row(align=True)
|
||||
if not tool.Ifc.get():
|
||||
row.label(text="No IFC Project", icon="ERROR")
|
||||
return
|
||||
|
||||
if not AnnotationData.is_loaded:
|
||||
AnnotationData.load()
|
||||
|
||||
cls.draw_type_selection_interface()
|
||||
|
||||
if context.active_object and context.selected_objects:
|
||||
cls.draw_edit_object_interface(context)
|
||||
elif not context.selected_objects:
|
||||
cls.draw_create_object_interface()
|
||||
|
||||
@classmethod
|
||||
def draw_create_object_interface(cls):
|
||||
row = cls.layout.row(align=True)
|
||||
op, row = add_layout_hotkey_operator(cls.layout, "Add Type", "S_C", "Create a new annotation type")
|
||||
selected_icon = "CHECKBOX_HLT" if cls.props.create_representation_for_type else "CHECKBOX_DEHLT"
|
||||
row.prop(cls.props, "create_representation_for_type", text="", icon=selected_icon)
|
||||
|
||||
@classmethod
|
||||
def draw_edit_object_interface(cls, context):
|
||||
if DecoratorData.get_ifc_text_data(bpy.context.object):
|
||||
add_layout_hotkey_operator(cls.layout, "Edit Text", "S_E", "")
|
||||
|
||||
@classmethod
|
||||
def draw_header_interface(cls):
|
||||
cls.draw_type_selection_interface()
|
||||
|
||||
@classmethod
|
||||
def draw_basic_annotation_tool_interface(cls):
|
||||
cls.draw_type_selection_interface()
|
||||
|
||||
@classmethod
|
||||
def draw_type_selection_interface(cls):
|
||||
# shared by both sidebar and header
|
||||
object_type = cls.props.object_type
|
||||
|
||||
row = cls.layout.row(align=True)
|
||||
row.label(text="", icon="FILE_VOLUME")
|
||||
prop_with_search(row, cls.props, "object_type", text="")
|
||||
|
||||
row = cls.layout.row(align=True)
|
||||
row.label(text="", icon="FILE_3D")
|
||||
prop_with_search(row, cls.props, "relating_type_id", text="")
|
||||
|
||||
create_type_occurence = cls.props.relating_type_id != "0"
|
||||
label = "Add Type Occurence" if create_type_occurence else "Add Annotation"
|
||||
add_layout_hotkey_operator(cls.layout, label, "S_A", "Create a new annotation")
|
||||
|
||||
if object_type in ("TEXT", "STAIR_ARROW"):
|
||||
add_layout_hotkey_operator(
|
||||
cls.layout,
|
||||
"Bulk Tag",
|
||||
"S_T",
|
||||
"Create new annotations and automatically adjust them to the selected objects",
|
||||
)
|
||||
add_layout_hotkey_operator(
|
||||
cls.layout, "Readjust", "S_G", "Readjust tags based on the products they are assigned to"
|
||||
)
|
||||
|
||||
|
||||
class Hotkey(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.annotation_hotkey"
|
||||
bl_label = "Hotkey"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
hotkey: bpy.props.StringProperty()
|
||||
description: bpy.props.StringProperty()
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return tool.Ifc.get()
|
||||
|
||||
@classmethod
|
||||
def description(cls, context, operator):
|
||||
return operator.description or ""
|
||||
|
||||
def _execute(self, context):
|
||||
self.props = context.scene.BIMAnnotationProperties
|
||||
getattr(self, f"hotkey_{self.hotkey}")()
|
||||
|
||||
def invoke(self, context, event):
|
||||
# https://blender.stackexchange.com/questions/276035/how-do-i-make-operators-remember-their-property-values-when-called-from-a-hotkey
|
||||
self.props = context.scene.BIMAnnotationProperties
|
||||
return self.execute(context)
|
||||
|
||||
def draw(self, context):
|
||||
pass
|
||||
|
||||
def hotkey_S_T(self):
|
||||
props = bpy.context.scene.BIMAnnotationProperties
|
||||
object_type = props.object_type
|
||||
|
||||
related_objects = bpy.context.selected_objects
|
||||
for related_object in related_objects:
|
||||
create_annotation()
|
||||
obj = bpy.context.active_object
|
||||
bpy.ops.object.mode_set(mode="OBJECT")
|
||||
tool.Drawing.setup_annotation_object(obj, object_type, related_object)
|
||||
|
||||
def hotkey_S_A(self):
|
||||
create_annotation()
|
||||
|
||||
def hotkey_S_E(self):
|
||||
if not bpy.context.object:
|
||||
return
|
||||
|
||||
if DecoratorData.get_ifc_text_data(bpy.context.object):
|
||||
bpy.ops.bim.edit_text_popup()
|
||||
|
||||
def hotkey_S_G(self):
|
||||
for obj in bpy.context.selected_objects:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element or not element.is_a("IfcAnnotation"):
|
||||
continue
|
||||
|
||||
related_product = tool.Drawing.get_assigned_product(element)
|
||||
if not related_product:
|
||||
continue
|
||||
related_object = tool.Ifc.get_object(related_product)
|
||||
|
||||
tool.Drawing.setup_annotation_object(obj, element.ObjectType, related_object)
|
||||
|
||||
def hotkey_S_C(self):
|
||||
create_annotation_type(bpy.context)
|
||||
@@ -27,6 +27,8 @@ classes = (
|
||||
operator.OverrideDelete,
|
||||
operator.OverrideDuplicateMove,
|
||||
operator.OverrideDuplicateMoveLinked,
|
||||
operator.OverrideDuplicateMoveLinkedMacro,
|
||||
operator.OverrideDuplicateMoveMacro,
|
||||
operator.OverrideJoin,
|
||||
operator.OverrideModeSetEdit,
|
||||
operator.OverrideModeSetObject,
|
||||
@@ -52,6 +54,11 @@ addon_keymaps = []
|
||||
|
||||
|
||||
def register():
|
||||
operator.OverrideDuplicateMoveMacro.define("BIM_OT_override_object_duplicate_move")
|
||||
operator.OverrideDuplicateMoveMacro.define("TRANSFORM_OT_translate")
|
||||
operator.OverrideDuplicateMoveLinkedMacro.define("BIM_OT_override_object_duplicate_move_linked")
|
||||
operator.OverrideDuplicateMoveLinkedMacro.define("TRANSFORM_OT_translate")
|
||||
|
||||
bpy.types.Object.BIMGeometryProperties = bpy.props.PointerProperty(type=prop.BIMObjectGeometryProperties)
|
||||
bpy.types.Scene.BIMGeometryProperties = bpy.props.PointerProperty(type=prop.BIMGeometryProperties)
|
||||
bpy.types.OBJECT_PT_transform.append(ui.BIM_PT_transform)
|
||||
@@ -60,9 +67,8 @@ def register():
|
||||
if wm.keyconfigs.addon:
|
||||
km = wm.keyconfigs.addon.keymaps.new(name="Object Mode", space_type="EMPTY")
|
||||
kmi = km.keymap_items.new("bim.override_object_join", "J", "PRESS", ctrl=True)
|
||||
kmi = km.keymap_items.new("bim.override_object_duplicate_move", "D", "PRESS", shift=True)
|
||||
kmi.properties.is_interactive = True
|
||||
kmi = km.keymap_items.new("bim.override_object_duplicate_move_linked", "D", "PRESS", alt=True)
|
||||
kmi = km.keymap_items.new("bim.override_object_duplicate_move_macro", "D", "PRESS", shift=True)
|
||||
kmi = km.keymap_items.new("bim.override_object_duplicate_move_linked_macro", "D", "PRESS", alt=True)
|
||||
kmi = km.keymap_items.new("bim.override_paste_buffer", "V", "PRESS", ctrl=True)
|
||||
kmi = km.keymap_items.new("bim.override_mode_set_edit", "TAB", "PRESS")
|
||||
kmi = km.keymap_items.new("bim.override_object_delete", "X", "PRESS")
|
||||
@@ -72,6 +78,9 @@ def register():
|
||||
km = wm.keyconfigs.addon.keymaps.new(name="Mesh", space_type="EMPTY")
|
||||
kmi = km.keymap_items.new("bim.override_mode_set_object", "TAB", "PRESS")
|
||||
|
||||
km = wm.keyconfigs.addon.keymaps.new(name="Curve", space_type="EMPTY")
|
||||
kmi = km.keymap_items.new("bim.override_mode_set_object", "TAB", "PRESS")
|
||||
|
||||
km = wm.keyconfigs.addon.keymaps.new(name="Outliner", space_type="OUTLINER")
|
||||
kmi = km.keymap_items.new("bim.override_paste_buffer", "V", "PRESS", ctrl=True)
|
||||
kmi = km.keymap_items.new("bim.override_outliner_delete", "X", "PRESS")
|
||||
|
||||
@@ -189,7 +189,8 @@ class UpdateRepresentation(bpy.types.Operator, Operator):
|
||||
ifcopenshell.api.run("boundary.assign_connection_geometry", tool.Ifc.get(), **settings)
|
||||
return
|
||||
|
||||
core.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj)
|
||||
if tool.Ifc.is_moved(obj):
|
||||
core.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj)
|
||||
|
||||
if material and material.is_a() in ["IfcMaterialProfileSet", "IfcMaterialLayerSet"]:
|
||||
# These objects are parametrically based on an axis and should not be modified as a mesh
|
||||
@@ -250,6 +251,20 @@ class UpdateRepresentation(bpy.types.Operator, Operator):
|
||||
|
||||
obj.data.BIMMeshProperties.ifc_definition_id = int(new_representation.id())
|
||||
obj.data.name = f"{old_representation.ContextOfItems.id()}/{new_representation.id()}"
|
||||
|
||||
# TODO: In simple scenarios, a type has a ShapeRepresentation of ID
|
||||
# 123. This is then mapped through mapped representations by
|
||||
# occurrences, with no cartesian transformation. In this case, the mesh
|
||||
# data is 100% shared and therefore all have the same mesh name
|
||||
# referencing ID 123. (i.e. the local origins are shared). However, in
|
||||
# complex scenarios, occurrences may have their own cartesian
|
||||
# transformation (via MappingTarget). This will mean that occurrences
|
||||
# will not share the same mesh data and will instead reference a
|
||||
# different ShapeRepresentation ID. In this scenario, we have to
|
||||
# propagate the obj.data back to the type itself and all sibling
|
||||
# occurrences and accommodate their individual cartesian
|
||||
# transformations.
|
||||
|
||||
core.remove_representation(tool.Ifc, tool.Geometry, obj=obj, representation=old_representation)
|
||||
if obj.data.BIMMeshProperties.ifc_parameters:
|
||||
core.get_representation_ifc_parameters(tool.Geometry, obj=obj)
|
||||
@@ -345,52 +360,7 @@ class CopyRepresentation(bpy.types.Operator, Operator):
|
||||
return r.MappedRepresentation
|
||||
|
||||
|
||||
class OverrideDeleteTrait:
|
||||
def delete_ifc_object(self, obj):
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element:
|
||||
return
|
||||
if element.is_a("IfcAnnotation") and element.ObjectType == "DRAWING":
|
||||
return blenderbim.core.drawing.remove_drawing(tool.Ifc, tool.Drawing, drawing=element)
|
||||
IfcStore.delete_element(element)
|
||||
if obj.users_collection and obj.users_collection[0].name == obj.name:
|
||||
parent = ifcopenshell.util.element.get_aggregate(element)
|
||||
if not parent:
|
||||
parent = ifcopenshell.util.element.get_container(element)
|
||||
if parent:
|
||||
parent_obj = tool.Ifc.get_object(parent)
|
||||
if parent_obj:
|
||||
parent_collection = bpy.data.collections.get(parent_obj.name)
|
||||
for child in obj.users_collection[0].children:
|
||||
parent_collection.children.link(child)
|
||||
bpy.data.collections.remove(obj.users_collection[0])
|
||||
if getattr(element, "FillsVoids", None):
|
||||
self.remove_filling(element)
|
||||
if element.is_a("IfcOpeningElement"):
|
||||
if element.HasFillings:
|
||||
for rel in element.HasFillings:
|
||||
self.remove_filling(rel.RelatedBuildingElement)
|
||||
else:
|
||||
if element.VoidsElements:
|
||||
self.delete_opening_element(element)
|
||||
else:
|
||||
if getattr(element, "HasOpenings", None):
|
||||
for rel in element.HasOpenings:
|
||||
self.delete_opening_element(rel.RelatedOpeningElement)
|
||||
for port in ifcopenshell.util.system.get_ports(element):
|
||||
self.remove_port(port)
|
||||
|
||||
def delete_opening_element(self, element):
|
||||
bpy.ops.bim.remove_opening(opening_id=element.id())
|
||||
|
||||
def remove_filling(self, element):
|
||||
bpy.ops.bim.remove_filling(filling=element.id())
|
||||
|
||||
def remove_port(self, port):
|
||||
blenderbim.core.system.remove_port(tool.Ifc, tool.System, port=port)
|
||||
|
||||
|
||||
class OverrideDelete(bpy.types.Operator, OverrideDeleteTrait):
|
||||
class OverrideDelete(bpy.types.Operator):
|
||||
bl_idname = "bim.override_object_delete"
|
||||
bl_label = "IFC Delete"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
@@ -419,18 +389,16 @@ class OverrideDelete(bpy.types.Operator, OverrideDeleteTrait):
|
||||
|
||||
def _execute(self, context):
|
||||
for obj in context.selected_objects:
|
||||
self.delete_ifc_object(obj)
|
||||
try:
|
||||
obj.name
|
||||
if tool.Ifc.get_entity(obj):
|
||||
tool.Geometry.delete_ifc_object(obj)
|
||||
else:
|
||||
bpy.data.objects.remove(obj)
|
||||
except:
|
||||
pass
|
||||
# Required otherwise gizmos are still visible
|
||||
context.view_layer.objects.active = None
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class OverrideOutlinerDelete(bpy.types.Operator, OverrideDeleteTrait):
|
||||
class OverrideOutlinerDelete(bpy.types.Operator):
|
||||
bl_idname = "bim.override_outliner_delete"
|
||||
bl_label = "IFC Delete"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
@@ -481,8 +449,7 @@ class OverrideOutlinerDelete(bpy.types.Operator, OverrideDeleteTrait):
|
||||
objects_to_delete.add(bpy.data.objects.get(item.name))
|
||||
for obj in objects_to_delete:
|
||||
# This is the only difference
|
||||
self.delete_ifc_object(obj)
|
||||
bpy.data.objects.remove(obj)
|
||||
tool.Geometry.delete_ifc_object(obj)
|
||||
return {"FINISHED"}
|
||||
|
||||
def get_collection_objects_and_children(self, collection):
|
||||
@@ -498,6 +465,12 @@ class OverrideOutlinerDelete(bpy.types.Operator, OverrideDeleteTrait):
|
||||
return {"objects": objects, "children": children}
|
||||
|
||||
|
||||
class OverrideDuplicateMoveMacro(bpy.types.Macro):
|
||||
bl_idname = "bim.override_object_duplicate_move_macro"
|
||||
bl_label = "IFC Duplicate Objects"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
|
||||
class OverrideDuplicateMove(bpy.types.Operator):
|
||||
bl_idname = "bim.override_object_duplicate_move"
|
||||
bl_label = "IFC Duplicate Objects"
|
||||
@@ -529,8 +502,6 @@ class OverrideDuplicateMove(bpy.types.Operator):
|
||||
new_obj.select_set(True)
|
||||
if new_active_obj:
|
||||
context.view_layer.objects.active = new_active_obj
|
||||
if self.is_interactive:
|
||||
bpy.ops.transform.translate("INVOKE_DEFAULT")
|
||||
return {"FINISHED"}
|
||||
|
||||
def _execute(self, context):
|
||||
@@ -551,17 +522,26 @@ class OverrideDuplicateMove(bpy.types.Operator):
|
||||
# Copy the actual class
|
||||
new = blenderbim.core.root.copy_class(tool.Ifc, tool.Collector, tool.Geometry, tool.Root, obj=new_obj)
|
||||
if new:
|
||||
array_pset = ifcopenshell.util.element.get_pset(new, "BBIM_Array")
|
||||
if array_pset:
|
||||
array_pset = tool.Ifc.get().by_id(array_pset["id"])
|
||||
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), product=new, pset=array_pset)
|
||||
old_to_new[tool.Ifc.get_entity(obj)] = [new]
|
||||
# Recreate decompositions
|
||||
tool.Root.recreate_decompositions(relationships, old_to_new)
|
||||
if self.is_interactive:
|
||||
bpy.ops.transform.translate("INVOKE_DEFAULT")
|
||||
blenderbim.bim.handler.purge_module_data()
|
||||
|
||||
|
||||
class OverrideDuplicateMoveLinkedMacro(bpy.types.Macro):
|
||||
bl_idname = "bim.override_object_duplicate_move_linked_macro"
|
||||
bl_label = "IFC Duplicate Linked"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
|
||||
class OverrideDuplicateMoveLinked(bpy.types.Operator):
|
||||
bl_idname = "bim.override_object_duplicate_move_linked"
|
||||
bl_label = "IFC Duplicate Linked"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
@@ -586,7 +566,6 @@ class OverrideDuplicateMoveLinked(bpy.types.Operator):
|
||||
new_obj.select_set(True)
|
||||
if new_active_obj:
|
||||
context.view_layer.objects.active = new_active_obj
|
||||
bpy.ops.transform.translate("INVOKE_DEFAULT")
|
||||
return {"FINISHED"}
|
||||
|
||||
def _execute(self, context):
|
||||
@@ -596,6 +575,8 @@ class OverrideDuplicateMoveLinked(bpy.types.Operator):
|
||||
old_to_new = {}
|
||||
for obj in context.selected_objects:
|
||||
new_obj = obj.copy()
|
||||
if obj.data:
|
||||
new_obj.data = obj.data.copy()
|
||||
if obj == context.active_object:
|
||||
self.new_active_obj = new_obj
|
||||
for collection in obj.users_collection:
|
||||
@@ -605,10 +586,13 @@ class OverrideDuplicateMoveLinked(bpy.types.Operator):
|
||||
# Copy the actual class
|
||||
new = blenderbim.core.root.copy_class(tool.Ifc, tool.Collector, tool.Geometry, tool.Root, obj=new_obj)
|
||||
if new:
|
||||
array_pset = ifcopenshell.util.element.get_pset(new, "BBIM_Array")
|
||||
if array_pset:
|
||||
array_pset = tool.Ifc.get().by_id(array_pset["id"])
|
||||
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), product=new, pset=array_pset)
|
||||
old_to_new[tool.Ifc.get_entity(obj)] = new
|
||||
# Recreate decompositions
|
||||
tool.Root.recreate_decompositions(relationships, old_to_new)
|
||||
bpy.ops.transform.translate("INVOKE_DEFAULT")
|
||||
blenderbim.bim.handler.purge_module_data()
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -616,6 +600,12 @@ class OverrideDuplicateMoveLinked(bpy.types.Operator):
|
||||
class OverrideJoin(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.override_object_join"
|
||||
bl_label = "IFC Join"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
if not tool.Ifc.get():
|
||||
return bpy.ops.object.join()
|
||||
return self.execute(context)
|
||||
|
||||
def _execute(self, context):
|
||||
if not tool.Ifc.get():
|
||||
@@ -638,7 +628,7 @@ class OverrideJoin(bpy.types.Operator, Operator):
|
||||
continue
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if element:
|
||||
tool.Ifc.delete(element)
|
||||
ifcopenshell.api.run("root.remove_product", tool.Ifc.get(), product=element)
|
||||
bpy.ops.object.join()
|
||||
bpy.ops.bim.update_representation(obj=self.target.name, ifc_representation_class="")
|
||||
elif representation.RepresentationType == "SweptSolid":
|
||||
@@ -649,7 +639,7 @@ class OverrideJoin(bpy.types.Operator, Operator):
|
||||
continue
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
|
||||
# Non IFC elements cannot be join since we cannot guarantee SweptSolid compliance
|
||||
# Non IFC elements cannot be joined since we cannot guarantee SweptSolid compliance
|
||||
if not element:
|
||||
obj.select_set(False)
|
||||
continue
|
||||
@@ -664,6 +654,9 @@ class OverrideJoin(bpy.types.Operator, Operator):
|
||||
|
||||
for item in obj_rep.Items:
|
||||
copied_item = ifcopenshell.util.element.copy_deep(tool.Ifc.get(), item)
|
||||
for style in item.StyledByItem:
|
||||
copied_style = ifcopenshell.util.element.copy(tool.Ifc.get(), style)
|
||||
copied_style.Item = copied_item
|
||||
if copied_item.Position:
|
||||
position = ifcopenshell.util.placement.get_axis2placement(copied_item.Position)
|
||||
else:
|
||||
@@ -676,7 +669,7 @@ class OverrideJoin(bpy.types.Operator, Operator):
|
||||
tool.Ifc.get().createIfcDirection([float(n) for n in position[:, 0][:3]]),
|
||||
)
|
||||
items.append(copied_item)
|
||||
tool.Ifc.delete(element)
|
||||
ifcopenshell.api.run("root.remove_product", tool.Ifc.get(), product=element)
|
||||
representation.Items = items
|
||||
bpy.ops.object.join()
|
||||
core.switch_representation(
|
||||
@@ -696,13 +689,14 @@ class OverrideJoin(bpy.types.Operator, Operator):
|
||||
continue
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if element:
|
||||
tool.Ifc.delete(element)
|
||||
ifcopenshell.api.run("root.remove_product", tool.Ifc.get(), product=element)
|
||||
bpy.ops.object.join()
|
||||
|
||||
|
||||
class OverridePasteBuffer(bpy.types.Operator):
|
||||
bl_idname = "bim.override_paste_buffer"
|
||||
bl_label = "IFC Paste BIM Objects"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
bpy.ops.view3d.pastebuffer()
|
||||
@@ -721,10 +715,11 @@ class OverrideModeSetEdit(bpy.types.Operator):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
objs = context.selected_objects or [context.active_object]
|
||||
objs = context.selected_objects or ([context.active_object] if context.active_object else [])
|
||||
active_obj = context.active_object
|
||||
|
||||
if context.active_object:
|
||||
context.active_object.select_set(True)
|
||||
edited_objs = []
|
||||
for obj in objs:
|
||||
if not obj:
|
||||
continue
|
||||
@@ -742,26 +737,43 @@ class OverrideModeSetEdit(bpy.types.Operator):
|
||||
obj.select_set(False)
|
||||
continue
|
||||
|
||||
if obj.data.BIMMeshProperties.ifc_definition_id:
|
||||
representation = tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id)
|
||||
if representation.RepresentationType in ("Tessellation", "Brep"):
|
||||
if element.HasOpenings:
|
||||
# Mesh elements with openings must disable openings
|
||||
# so that you can edit the original topology.
|
||||
core.switch_representation(
|
||||
tool.Ifc,
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
apply_openings=False,
|
||||
)
|
||||
obj.data.BIMMeshProperties.mesh_checksum = tool.Geometry.get_mesh_checksum(obj.data)
|
||||
if not context.selected_objects:
|
||||
return {"FINISHED"}
|
||||
bpy.ops.object.mode_set(mode="EDIT", toggle=True)
|
||||
representation = tool.Geometry.get_active_representation(obj)
|
||||
if not representation:
|
||||
continue
|
||||
|
||||
if tool.Geometry.is_meshlike(representation):
|
||||
if getattr(element, "HasOpenings", None):
|
||||
# Mesh elements with openings must disable openings
|
||||
# so that you can edit the original topology.
|
||||
core.switch_representation(
|
||||
tool.Ifc,
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
apply_openings=False,
|
||||
)
|
||||
obj.data.BIMMeshProperties.mesh_checksum = tool.Geometry.get_mesh_checksum(obj.data)
|
||||
else:
|
||||
obj.select_set(False)
|
||||
continue
|
||||
|
||||
if not context.selected_objects or len(context.selected_objects) != len(objs):
|
||||
# We are trying to edit at least one non-mesh-like object : Display a hint to the user
|
||||
self.report({"INFO"}, "Only mesh-compatible representations may be edited in edit mode.")
|
||||
|
||||
if context.active_object not in context.selected_objects:
|
||||
# The active object is non-mesh-like. Set a valid object (or None) as active
|
||||
context.view_layer.objects.active = context.selected_objects[0] if context.selected_objects else None
|
||||
if context.active_object:
|
||||
bpy.ops.object.mode_set(mode="EDIT", toggle=True)
|
||||
else:
|
||||
# restore the selection if nothing worked
|
||||
for obj in objs:
|
||||
obj.select_set(True)
|
||||
context.view_layer.objects.active = active_obj
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
@@ -783,7 +795,7 @@ class OverrideModeSetObject(bpy.types.Operator):
|
||||
for obj in self.edited_objs:
|
||||
if self.should_save:
|
||||
bpy.ops.bim.update_representation(obj=obj.name, ifc_representation_class="")
|
||||
if tool.Ifc.get_entity(obj).HasOpenings:
|
||||
if getattr(tool.Ifc.get_entity(obj), "HasOpenings", False):
|
||||
self.reload_representation(obj)
|
||||
else:
|
||||
self.reload_representation(obj)
|
||||
@@ -806,10 +818,17 @@ class OverrideModeSetObject(bpy.types.Operator):
|
||||
)
|
||||
|
||||
def draw(self, context):
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(self, "should_save")
|
||||
if self.is_valid:
|
||||
row = self.layout.row()
|
||||
row.prop(self, "should_save")
|
||||
else:
|
||||
row = self.layout.row()
|
||||
row.label(text="No Geometry Found: Object will revert to previous state.")
|
||||
|
||||
def invoke(self, context, event):
|
||||
self.is_valid = True
|
||||
self.should_save = True
|
||||
|
||||
bpy.ops.object.mode_set(mode="EDIT", toggle=True)
|
||||
|
||||
if not tool.Ifc.get():
|
||||
@@ -829,13 +848,15 @@ class OverrideModeSetObject(bpy.types.Operator):
|
||||
continue
|
||||
|
||||
if obj.data.BIMMeshProperties.ifc_definition_id:
|
||||
if not tool.Geometry.has_geometric_data(obj):
|
||||
self.is_valid = False
|
||||
self.should_save = False
|
||||
representation = tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id)
|
||||
if representation.RepresentationType in (
|
||||
"Tessellation",
|
||||
"Brep",
|
||||
if tool.Geometry.is_meshlike(
|
||||
representation
|
||||
) and obj.data.BIMMeshProperties.mesh_checksum != tool.Geometry.get_mesh_checksum(obj.data):
|
||||
self.edited_objs.append(obj)
|
||||
elif element.HasOpenings:
|
||||
elif getattr(element, "HasOpenings", None):
|
||||
self.unchanged_objs_with_openings.append(obj)
|
||||
|
||||
if self.edited_objs:
|
||||
|
||||
@@ -49,7 +49,7 @@ class BIM_OT_cityjson2ifc(Operator):
|
||||
|
||||
class BIM_OT_find_cityjson_lod(Operator):
|
||||
bl_idname = "bim.find_cityjson_lod"
|
||||
bl_label = "Find LODs in CityJSON file"
|
||||
bl_label = "Find LODs in CityJSON File"
|
||||
bl_context = "scene"
|
||||
|
||||
def execute(self, context):
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation,
|
||||
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# 2023 Bruno Postle <bruno@postle.net>, Bruno Perdigão <brunoperdigao@tutanota.com>,
|
||||
# Massimo Fabbro <maxfb87@yahoo.it>
|
||||
|
||||
import bpy
|
||||
from . import ui, prop, operator
|
||||
|
||||
|
||||
classes = (
|
||||
operator.AddFileToRepo,
|
||||
operator.CommitChanges,
|
||||
operator.CreateRepo,
|
||||
operator.DiscardUncommitted,
|
||||
operator.DisplayRevision,
|
||||
operator.DisplayUncommitted,
|
||||
operator.Merge,
|
||||
operator.RefreshGit,
|
||||
operator.SwitchRevision,
|
||||
prop.IfcGitListItem,
|
||||
prop.IfcGitProperties,
|
||||
ui.IFCGIT_PT_panel,
|
||||
ui.COMMIT_UL_List,
|
||||
)
|
||||
|
||||
|
||||
def register():
|
||||
bpy.types.Scene.IfcGitProperties = bpy.props.PointerProperty(type=prop.IfcGitProperties)
|
||||
|
||||
|
||||
def unregister():
|
||||
del bpy.types.Scene.IfcGitProperties
|
||||
@@ -0,0 +1,123 @@
|
||||
import bpy
|
||||
import os
|
||||
import shutil
|
||||
|
||||
# import tool
|
||||
import blenderbim.tool as tool
|
||||
|
||||
|
||||
def refresh():
|
||||
IfcGitData.is_loaded = False
|
||||
|
||||
|
||||
class IfcGitData:
|
||||
|
||||
data = {}
|
||||
is_loaded = False
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.data = {
|
||||
"repo": cls.repo(),
|
||||
"branch_names": cls.branch_names(),
|
||||
"path_ifc": cls.path_ifc(),
|
||||
"branches_by_hexsha": cls.branches_by_hexsha(),
|
||||
"tags_by_hexsha": cls.tags_by_hexsha(),
|
||||
"name_ifc": cls.name_ifc(),
|
||||
"dir_name": cls.dir_name(),
|
||||
"base_name": cls.base_name(),
|
||||
"is_dirty": cls.is_dirty(),
|
||||
"commit": cls.commit(),
|
||||
"current_revision": cls.current_revision(),
|
||||
"git_exe": cls.git_exe(),
|
||||
"ifcmerge_exe": cls.ifcmerge_exe(),
|
||||
}
|
||||
cls.is_loaded = True
|
||||
|
||||
@classmethod
|
||||
def repo(cls):
|
||||
if bool(tool.Ifc.get()):
|
||||
path_ifc = tool.Ifc.get_path()
|
||||
if os.path.isfile(path_ifc):
|
||||
return tool.IfcGit.repo_from_path(path_ifc)
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def branch_names(cls):
|
||||
return []
|
||||
|
||||
@classmethod
|
||||
def path_ifc(cls):
|
||||
path_ifc = tool.Ifc.get_path()
|
||||
if os.path.isfile(path_ifc):
|
||||
return tool.Ifc.get_path()
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def branches_by_hexsha(cls):
|
||||
try:
|
||||
if tool.IfcGitRepo.repo.branches:
|
||||
return tool.IfcGit.branches_by_hexsha(tool.IfcGitRepo.repo)
|
||||
except:
|
||||
return {}
|
||||
|
||||
@classmethod
|
||||
def tags_by_hexsha(cls):
|
||||
if tool.IfcGitRepo.repo:
|
||||
return tool.IfcGit.tags_by_hexsha(tool.IfcGitRepo.repo)
|
||||
return {}
|
||||
|
||||
@classmethod
|
||||
def name_ifc(cls):
|
||||
if bool(tool.Ifc.get()):
|
||||
path_ifc = tool.Ifc.get_path()
|
||||
if tool.IfcGitRepo.repo and os.path.isfile(path_ifc):
|
||||
working_dir = tool.IfcGitRepo.repo.working_dir
|
||||
return os.path.relpath(path_ifc, working_dir)
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def dir_name(cls):
|
||||
if bool(tool.Ifc.get()):
|
||||
path_ifc = tool.Ifc.get_path()
|
||||
if os.path.isfile(path_ifc):
|
||||
return os.path.dirname(path_ifc)
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def base_name(cls):
|
||||
if bool(tool.Ifc.get()):
|
||||
path_ifc = tool.Ifc.get_path()
|
||||
if os.path.isfile(path_ifc):
|
||||
return os.path.basename(path_ifc)
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def is_dirty(cls):
|
||||
if cls.repo() and cls.git_exe():
|
||||
path_ifc = tool.Ifc.get_path()
|
||||
if os.path.isfile(path_ifc):
|
||||
return cls.repo().is_dirty(path=path_ifc)
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def commit(cls):
|
||||
props = bpy.context.scene.IfcGitProperties
|
||||
if len(props.ifcgit_commits) > 0:
|
||||
item = props.ifcgit_commits[props.commit_index]
|
||||
if cls.repo():
|
||||
return cls.repo().commit(rev=item.hexsha)
|
||||
|
||||
@classmethod
|
||||
def current_revision(cls):
|
||||
props = bpy.context.scene.IfcGitProperties
|
||||
if len(props.ifcgit_commits) > 0:
|
||||
return tool.IfcGitRepo.repo.commit()
|
||||
|
||||
@classmethod
|
||||
def git_exe(cls):
|
||||
return shutil.which("git")
|
||||
|
||||
@classmethod
|
||||
def ifcmerge_exe(cls):
|
||||
return shutil.which("ifcmerge")
|
||||
@@ -0,0 +1,194 @@
|
||||
import os
|
||||
import re
|
||||
import bpy
|
||||
import blenderbim.core.ifcgit as core
|
||||
import blenderbim.tool as tool
|
||||
from blenderbim.bim.module.ifcgit.data import IfcGitData, refresh
|
||||
|
||||
|
||||
class CreateRepo(bpy.types.Operator):
|
||||
"""Initialise a Git repository"""
|
||||
|
||||
bl_label = "Create Git repository"
|
||||
bl_idname = "ifcgit.createrepo"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
path_ifc = IfcGitData.data["path_ifc"]
|
||||
if not os.path.isfile(path_ifc):
|
||||
return False
|
||||
if IfcGitData.data["repo"]:
|
||||
# repo already exists
|
||||
return False
|
||||
if re.match("^/home/[^/]+/?$", os.path.dirname(path_ifc)):
|
||||
# don't make ${HOME} a repo
|
||||
return False
|
||||
return True
|
||||
|
||||
def execute(self, context):
|
||||
|
||||
core.create_repo(tool.IfcGit, tool.Ifc)
|
||||
refresh()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AddFileToRepo(bpy.types.Operator):
|
||||
"""Add a file to a repository"""
|
||||
|
||||
bl_label = "Add file to repository"
|
||||
bl_idname = "ifcgit.addfile"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
path_ifc = IfcGitData.data["path_ifc"]
|
||||
if not os.path.isfile(path_ifc):
|
||||
return False
|
||||
if not IfcGitData.data["repo"]:
|
||||
# repo doesn't exist
|
||||
return False
|
||||
return True
|
||||
|
||||
def execute(self, context):
|
||||
|
||||
core.add_file(tool.IfcGit, tool.Ifc)
|
||||
refresh()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class DiscardUncommitted(bpy.types.Operator):
|
||||
"""Discard saved changes and update to HEAD"""
|
||||
|
||||
bl_label = "Discard uncommitted changes"
|
||||
bl_idname = "ifcgit.discard"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
def execute(self, context):
|
||||
|
||||
core.discard_uncomitted(tool.IfcGit, tool.Ifc)
|
||||
refresh()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class CommitChanges(bpy.types.Operator):
|
||||
"""Commit current saved changes"""
|
||||
|
||||
bl_label = "Commit changes"
|
||||
bl_idname = "ifcgit.commit_changes"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
props = context.scene.IfcGitProperties
|
||||
repo = IfcGitData.data["repo"]
|
||||
if props.commit_message == "":
|
||||
return False
|
||||
if (
|
||||
repo
|
||||
and repo.head.is_detached
|
||||
and (
|
||||
not tool.is_valid_ref_format(props.new_branch_name)
|
||||
or props.new_branch_name in [branch.name for branch in repo.branches]
|
||||
)
|
||||
):
|
||||
cls.poll_message_set(
|
||||
"The new branch name is invalid, please insert a valid branch name (eg. with no spaces, ...)"
|
||||
)
|
||||
return False
|
||||
return True
|
||||
|
||||
def execute(self, context):
|
||||
|
||||
repo = IfcGitData.data["repo"]
|
||||
core.commit_changes(tool.IfcGit, tool.Ifc, repo, context)
|
||||
bpy.ops.ifcgit.refresh()
|
||||
refresh()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class RefreshGit(bpy.types.Operator):
|
||||
"""Refresh revision list"""
|
||||
|
||||
bl_label = ""
|
||||
bl_idname = "ifcgit.refresh"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
repo = IfcGitData.data["repo"]
|
||||
if repo != None and repo.heads:
|
||||
return True
|
||||
return False
|
||||
|
||||
def execute(self, context):
|
||||
|
||||
repo = IfcGitData.data["repo"]
|
||||
core.refresh_revision_list(tool.IfcGit, repo, tool.Ifc)
|
||||
refresh()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class DisplayRevision(bpy.types.Operator):
|
||||
"""Colourise objects by selected revision"""
|
||||
|
||||
bl_label = ""
|
||||
bl_idname = "ifcgit.display_revision"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
def execute(self, context):
|
||||
|
||||
core.colourise_revision(tool.IfcGit, context)
|
||||
refresh()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class DisplayUncommitted(bpy.types.Operator):
|
||||
"""Colourise uncommitted objects"""
|
||||
|
||||
bl_label = "Show uncommitted changes"
|
||||
bl_idname = "ifcgit.display_uncommitted"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
def execute(self, context):
|
||||
|
||||
repo = IfcGitData.data["repo"]
|
||||
core.colourise_uncommitted(tool.IfcGit, tool.Ifc, repo)
|
||||
refresh()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class SwitchRevision(bpy.types.Operator):
|
||||
"""Switches the repository to the selected revision and reloads the IFC file"""
|
||||
|
||||
bl_label = ""
|
||||
bl_idname = "ifcgit.switch_revision"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
def execute(self, context):
|
||||
|
||||
core.switch_revision(tool.IfcGit, tool.Ifc)
|
||||
refresh()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class Merge(bpy.types.Operator):
|
||||
"""Merges the selected branch into working branch"""
|
||||
|
||||
bl_label = "Merge this branch"
|
||||
bl_idname = "ifcgit.merge"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
if IfcGitData.data["ifcmerge_exe"]:
|
||||
return True
|
||||
return False
|
||||
|
||||
def execute(self, context):
|
||||
|
||||
if core.merge_branch(tool.IfcGit, tool.Ifc, self):
|
||||
refresh()
|
||||
return {"FINISHED"}
|
||||
else:
|
||||
return {"CANCELLED"}
|
||||
@@ -0,0 +1,72 @@
|
||||
import bpy
|
||||
from bpy.types import PropertyGroup
|
||||
from bpy.props import (
|
||||
StringProperty,
|
||||
BoolProperty,
|
||||
CollectionProperty,
|
||||
IntProperty,
|
||||
EnumProperty,
|
||||
)
|
||||
from blenderbim.bim.module.ifcgit.data import IfcGitData, refresh
|
||||
|
||||
|
||||
def git_branches(self, context):
|
||||
"""branches enum"""
|
||||
|
||||
# NOTE "Python must keep a reference to the strings returned by
|
||||
# the callback or Blender will misbehave or even crash"
|
||||
IfcGitData.data["branch_names"] = sorted([branch.name for branch in IfcGitData.data["repo"].heads])
|
||||
|
||||
if "main" in IfcGitData.data["branch_names"]:
|
||||
IfcGitData.data["branch_names"].remove("main")
|
||||
IfcGitData.data["branch_names"] = ["main"] + IfcGitData.data["branch_names"]
|
||||
|
||||
return [(myname, myname, myname) for myname in IfcGitData.data["branch_names"]]
|
||||
|
||||
|
||||
def update_revlist(self, context):
|
||||
"""wrapper to trigger update of the revision list"""
|
||||
|
||||
bpy.ops.ifcgit.refresh()
|
||||
props = context.scene.IfcGitProperties
|
||||
props.commit_index = 0
|
||||
|
||||
|
||||
class IfcGitListItem(PropertyGroup):
|
||||
"""Group of properties representing an item in the list."""
|
||||
|
||||
hexsha: StringProperty(
|
||||
name="Git hash",
|
||||
description="checksum for this commit",
|
||||
default="Uncommitted data!",
|
||||
)
|
||||
relevant: BoolProperty(
|
||||
name="Is relevant",
|
||||
description="does this commit reference our ifc file",
|
||||
default=False,
|
||||
)
|
||||
|
||||
|
||||
class IfcGitProperties(PropertyGroup):
|
||||
|
||||
ifcgit_commits: CollectionProperty(type=IfcGitListItem, name="List of git items")
|
||||
commit_index: IntProperty(name="Index for my_list", default=0)
|
||||
commit_message: StringProperty(
|
||||
name="Commit message",
|
||||
description="A human readable description of these changes",
|
||||
default="",
|
||||
)
|
||||
new_branch_name: StringProperty(
|
||||
name="New branch name",
|
||||
description="A short name used to refer to this branch",
|
||||
default="",
|
||||
)
|
||||
display_branch: EnumProperty(items=git_branches, update=update_revlist)
|
||||
ifcgit_filter: EnumProperty(
|
||||
items=[
|
||||
("all", "All", "All revisions"),
|
||||
("tagged", "Tagged", "Tagged revisions"),
|
||||
("relevant", "Relevant", "Revisions for this project"),
|
||||
],
|
||||
update=update_revlist,
|
||||
)
|
||||
@@ -0,0 +1,166 @@
|
||||
import bpy
|
||||
import time
|
||||
from blenderbim.bim.module.ifcgit.data import IfcGitData, refresh
|
||||
|
||||
|
||||
class IFCGIT_PT_panel(bpy.types.Panel):
|
||||
"""Scene Properties panel to interact with IFC repository data"""
|
||||
|
||||
bl_label = "IFC Git"
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_parent_id = "BIM_PT_project_info"
|
||||
|
||||
def draw(self, context):
|
||||
|
||||
if not IfcGitData.is_loaded:
|
||||
IfcGitData.load()
|
||||
|
||||
layout = self.layout
|
||||
path_ifc = IfcGitData.data["path_ifc"]
|
||||
|
||||
if not IfcGitData.data["git_exe"]:
|
||||
row = layout.row()
|
||||
row.label(text="Git is not installed", icon="ERROR")
|
||||
return
|
||||
|
||||
props = context.scene.IfcGitProperties
|
||||
|
||||
# TODO if file isn't saved, offer to save to disk
|
||||
|
||||
row = layout.row()
|
||||
if path_ifc:
|
||||
if IfcGitData.data["repo"]:
|
||||
name_ifc = IfcGitData.data["name_ifc"]
|
||||
row.label(text=IfcGitData.data["repo"].working_dir, icon="SYSTEM")
|
||||
if name_ifc in IfcGitData.data["repo"].untracked_files:
|
||||
row.operator(
|
||||
"ifcgit.addfile",
|
||||
text="Add '" + name_ifc + "' to repository",
|
||||
icon="FILE",
|
||||
)
|
||||
else:
|
||||
row.label(text=name_ifc, icon="FILE")
|
||||
else:
|
||||
row.operator(
|
||||
"ifcgit.createrepo",
|
||||
text="Create '" + IfcGitData.data["dir_name"] + "' repository",
|
||||
icon="SYSTEM",
|
||||
)
|
||||
row.label(text=IfcGitData.data["base_name"], icon="FILE")
|
||||
return
|
||||
else:
|
||||
row.label(text="No Git repository found", icon="SYSTEM")
|
||||
row.label(text="No IFC project saved", icon="FILE")
|
||||
return
|
||||
|
||||
is_dirty = IfcGitData.data["is_dirty"]
|
||||
|
||||
if is_dirty:
|
||||
row = layout.row()
|
||||
row.label(text="Saved changes have not been committed", icon="ERROR")
|
||||
|
||||
row = layout.row()
|
||||
row.operator("ifcgit.display_uncommitted", icon="SELECT_DIFFERENCE")
|
||||
row.operator("ifcgit.discard", icon="TRASH")
|
||||
|
||||
row = layout.row()
|
||||
row.prop(props, "commit_message")
|
||||
|
||||
if IfcGitData.data["repo"].head.is_detached:
|
||||
row = layout.row()
|
||||
row.label(text="HEAD is detached, commit will create a branch", icon="ERROR")
|
||||
row.prop(props, "new_branch_name")
|
||||
|
||||
row = layout.row()
|
||||
row.operator("ifcgit.commit_changes", icon="GREASEPENCIL")
|
||||
|
||||
row = layout.row()
|
||||
if IfcGitData.data["repo"].head.is_detached:
|
||||
row.label(text="Working branch: Detached HEAD")
|
||||
else:
|
||||
row.label(text="Working branch: " + IfcGitData.data["repo"].active_branch.name)
|
||||
|
||||
grouped = layout.row()
|
||||
column = grouped.column()
|
||||
row = column.row()
|
||||
row.prop(props, "display_branch", text="Browse branch")
|
||||
row.prop(props, "ifcgit_filter", text="Filter revisions")
|
||||
|
||||
row = column.row()
|
||||
row.template_list(
|
||||
"COMMIT_UL_List",
|
||||
"The_List",
|
||||
props,
|
||||
"ifcgit_commits",
|
||||
props,
|
||||
"commit_index",
|
||||
)
|
||||
column = grouped.column()
|
||||
row = column.row()
|
||||
row.operator("ifcgit.refresh", icon="FILE_REFRESH")
|
||||
|
||||
if not is_dirty:
|
||||
|
||||
row = column.row()
|
||||
row.operator("ifcgit.display_revision", icon="SELECT_DIFFERENCE")
|
||||
|
||||
row = column.row()
|
||||
row.operator("ifcgit.switch_revision", icon="CURRENT_FILE")
|
||||
|
||||
# TODO operator to tag selected
|
||||
|
||||
row = column.row()
|
||||
row.operator("ifcgit.merge", icon="EXPERIMENTAL", text="")
|
||||
|
||||
if not props.ifcgit_commits:
|
||||
return
|
||||
|
||||
item = props.ifcgit_commits[props.commit_index]
|
||||
commit = IfcGitData.data["commit"]
|
||||
if not item.relevant:
|
||||
row = layout.row()
|
||||
row.label(text="Revision unrelated to current IFC project", icon="ERROR")
|
||||
|
||||
box = layout.box()
|
||||
column = box.column(align=True)
|
||||
row = column.row()
|
||||
row.label(text=commit.hexsha)
|
||||
row = column.row()
|
||||
row.label(text=commit.author.name + " <" + commit.author.email + ">")
|
||||
row = column.row()
|
||||
row.label(text=commit.message)
|
||||
|
||||
|
||||
class COMMIT_UL_List(bpy.types.UIList):
|
||||
"""List of Git commits"""
|
||||
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
|
||||
|
||||
props = context.scene.IfcGitProperties
|
||||
|
||||
current_revision = IfcGitData.data["current_revision"]
|
||||
|
||||
# TODO Figure how this "item" can be acesse in "data.py"
|
||||
# so it's possible to move the ".commit"
|
||||
commit = IfcGitData.data["repo"].commit(rev=item.hexsha)
|
||||
|
||||
lookup = IfcGitData.data["branches_by_hexsha"]
|
||||
refs = ""
|
||||
if item.hexsha in lookup:
|
||||
for branch in lookup[item.hexsha]:
|
||||
if branch.name == props.display_branch:
|
||||
refs = "[" + branch.name + "] "
|
||||
|
||||
lookup = IfcGitData.data["tags_by_hexsha"]
|
||||
if item.hexsha in lookup:
|
||||
for tag in lookup[item.hexsha]:
|
||||
refs += "{" + tag.name + "} "
|
||||
|
||||
if commit == current_revision:
|
||||
layout.label(text="[HEAD] " + refs + commit.message, icon="DECORATE_KEYFRAME")
|
||||
else:
|
||||
layout.label(text=refs + commit.message, icon="DECORATE_ANIMATE")
|
||||
layout.label(text=time.strftime("%c", time.localtime(commit.committed_date)))
|
||||
@@ -24,6 +24,7 @@ import ifcopenshell.util.attribute
|
||||
import ifcopenshell.util.representation
|
||||
import blenderbim.bim.helper
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.core.style
|
||||
import blenderbim.core.material as core
|
||||
import blenderbim.bim.module.model.profile as model_profile
|
||||
from blenderbim.bim.module.material.prop import purge as material_prop_purge
|
||||
@@ -515,7 +516,7 @@ class EditAssignedMaterial(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
class EnableEditingMaterialSetItemProfile(bpy.types.Operator):
|
||||
bl_idname = "bim.enable_editing_material_set_item_profile"
|
||||
bl_label = "Enable Editing Material Set Item"
|
||||
bl_label = "Enable Editing Material Set Item Profile"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
obj: bpy.props.StringProperty()
|
||||
material_set_item: bpy.props.IntProperty()
|
||||
@@ -531,7 +532,7 @@ class EnableEditingMaterialSetItemProfile(bpy.types.Operator):
|
||||
|
||||
class DisableEditingMaterialSetItemProfile(bpy.types.Operator):
|
||||
bl_idname = "bim.disable_editing_material_set_item_profile"
|
||||
bl_label = "Disable Editing Material Set Item"
|
||||
bl_label = "Disable Editing Material Set Item Profile"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
obj: bpy.props.StringProperty()
|
||||
|
||||
@@ -666,8 +667,12 @@ class CopyMaterial(bpy.types.Operator, tool.Ifc.Operator):
|
||||
def _execute(self, context):
|
||||
blender_material = context.active_object.active_material
|
||||
material = tool.Ifc.get_entity(blender_material)
|
||||
|
||||
if tool.Ifc.has_changed_shading(blender_material):
|
||||
blenderbim.core.style.update_style_colours(tool.Ifc, tool.Style, obj=blender_material)
|
||||
|
||||
copied_material = ifcopenshell.api.run("material.copy_material", tool.Ifc.get(), material=material)
|
||||
copied_blender_material = bpy.data.materials.new(blender_material.name)
|
||||
copied_blender_material = blender_material.copy()
|
||||
copied_style = self.get_style(copied_material)
|
||||
tool.Ifc.link(copied_material, copied_blender_material)
|
||||
if copied_style:
|
||||
|
||||
@@ -196,7 +196,7 @@ class BIM_PT_object_material(Panel):
|
||||
else:
|
||||
row = self.layout.row(align=True)
|
||||
if ObjectMaterialData.data["set_item_name"] == "profile":
|
||||
row.prop(self.mprops, "profiles", icon="ITALIC", text="")
|
||||
prop_with_search(row, self.mprops, "profiles", icon="ITALIC", text="")
|
||||
prop_with_search(row, self.props, "material", icon="MATERIAL", text="")
|
||||
op = row.operator(f"bim.add_{ObjectMaterialData.data['set_item_name']}", icon="ADD", text="")
|
||||
setattr(op, f"{ObjectMaterialData.data['set_item_name']}_set", ObjectMaterialData.data["set"]["id"])
|
||||
@@ -230,11 +230,11 @@ class BIM_PT_object_material(Panel):
|
||||
draw_attributes(self.props.material_set_item_attributes, box)
|
||||
|
||||
row = box.row()
|
||||
row.prop(self.props, "material_set_item_material", icon="MATERIAL", text="Material")
|
||||
prop_with_search(row, self.props, "material_set_item_material", icon="MATERIAL", text="Material")
|
||||
|
||||
if ObjectMaterialData.data["set_item_name"] == "profile":
|
||||
row = box.row()
|
||||
row.prop(self.mprops, "profiles", icon="ITALIC", text="Profile")
|
||||
prop_with_search(row, self.mprops, "profiles", icon="ITALIC", text="Profile")
|
||||
|
||||
def draw_read_only_set_item_ui(self, set_item, index, is_first=False, is_last=False):
|
||||
if ObjectMaterialData.data["material_class"] == "IfcMaterialList":
|
||||
|
||||
@@ -47,6 +47,9 @@ classes = (
|
||||
array.EnableEditingArray,
|
||||
array.RemoveArray,
|
||||
array.SelectArrayParent,
|
||||
array.Input3DCursorXArray,
|
||||
array.Input3DCursorYArray,
|
||||
array.Input3DCursorZArray,
|
||||
product.AddConstrTypeInstance,
|
||||
product.AddEmptyType,
|
||||
product.AlignProduct,
|
||||
@@ -93,9 +96,6 @@ classes = (
|
||||
slab.ResetVertex,
|
||||
slab.SetArcIndex,
|
||||
space.GenerateSpace,
|
||||
prop.ConstrTypeInfo,
|
||||
prop.ConstrClassInfo,
|
||||
prop.ConstrBrowserState,
|
||||
prop.BIMModelProperties,
|
||||
prop.BIMArrayProperties,
|
||||
prop.BIMStairProperties,
|
||||
@@ -113,7 +113,6 @@ classes = (
|
||||
ui.BIM_PT_railing,
|
||||
ui.BIM_PT_roof,
|
||||
ui.LaunchTypeManager,
|
||||
ui.HelpConstrTypes,
|
||||
ui.BIM_MT_model,
|
||||
grid.BIM_OT_add_object,
|
||||
stair.BIM_OT_add_object,
|
||||
@@ -149,6 +148,7 @@ classes = (
|
||||
railing.AddRailing,
|
||||
railing.CancelEditingRailing,
|
||||
railing.FinishEditingRailing,
|
||||
railing.FlipRailingPathOrder,
|
||||
railing.EnableEditingRailing,
|
||||
railing.CancelEditingRailingPath,
|
||||
railing.FinishEditingRailingPath,
|
||||
|
||||
@@ -22,19 +22,28 @@ import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.util.element
|
||||
import blenderbim.tool as tool
|
||||
from mathutils import Vector
|
||||
from mathutils import Vector, Matrix
|
||||
|
||||
|
||||
class AddArray(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.add_array"
|
||||
bl_label = "Add Array"
|
||||
bl_options = {"REGISTER"}
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
obj = context.active_object
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
|
||||
array = {"children": [], "count": 1, "x": 0.0, "y": 0.0, "z": 0.0, "use_local_space": True}
|
||||
array = {
|
||||
"children": [],
|
||||
"count": 1,
|
||||
"x": 0.0,
|
||||
"y": 0.0,
|
||||
"z": 0.0,
|
||||
"use_local_space": True,
|
||||
"sync_children": False,
|
||||
"method": "OFFSET",
|
||||
}
|
||||
|
||||
pset = ifcopenshell.util.element.get_pset(element, "BBIM_Array")
|
||||
|
||||
@@ -58,7 +67,7 @@ class AddArray(bpy.types.Operator, tool.Ifc.Operator):
|
||||
class DisableEditingArray(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.disable_editing_array"
|
||||
bl_label = "Disable Editing Array"
|
||||
bl_options = {"REGISTER"}
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
context.active_object.BIMArrayProperties.is_editing = -1
|
||||
@@ -68,7 +77,7 @@ class DisableEditingArray(bpy.types.Operator, tool.Ifc.Operator):
|
||||
class EnableEditingArray(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.enable_editing_array"
|
||||
bl_label = "Enable Editing Array"
|
||||
bl_options = {"REGISTER"}
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
item: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
@@ -81,6 +90,8 @@ class EnableEditingArray(bpy.types.Operator, tool.Ifc.Operator):
|
||||
props.y = data["y"]
|
||||
props.z = data["z"]
|
||||
props.use_local_space = data.get("use_local_space", False)
|
||||
props.sync_children = data.get("sync_children", False)
|
||||
props.method = data.get("method", "OFFSET")
|
||||
props.is_editing = self.item
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -88,7 +99,7 @@ class EnableEditingArray(bpy.types.Operator, tool.Ifc.Operator):
|
||||
class EditArray(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.edit_array"
|
||||
bl_label = "Edit Array"
|
||||
bl_options = {"REGISTER"}
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
item: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
@@ -105,6 +116,8 @@ class EditArray(bpy.types.Operator, tool.Ifc.Operator):
|
||||
"y": props.y,
|
||||
"z": props.z,
|
||||
"use_local_space": props.use_local_space,
|
||||
"sync_children": props.sync_children,
|
||||
"method": props.method,
|
||||
}
|
||||
|
||||
props.is_editing = -1
|
||||
@@ -125,7 +138,7 @@ class EditArray(bpy.types.Operator, tool.Ifc.Operator):
|
||||
class RemoveArray(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.remove_array"
|
||||
bl_label = "Remove Array"
|
||||
bl_options = {"REGISTER"}
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
item: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
@@ -173,3 +186,48 @@ class SelectArrayParent(bpy.types.Operator):
|
||||
context.view_layer.objects.active = obj
|
||||
obj.select_set(True)
|
||||
return {"FINISHED"}
|
||||
|
||||
class Input3DCursorXArray(bpy.types.Operator):
|
||||
bl_idname = "bim.input_cursor_x_array"
|
||||
bl_label = "Get 3d Cursor X Input for Array"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
obj = context.active_object
|
||||
props = obj.BIMArrayProperties
|
||||
cursor = context.scene.cursor
|
||||
if props.use_local_space:
|
||||
props.x = (Matrix.inverted(obj.matrix_world) @ cursor.matrix.col[3]).x
|
||||
else:
|
||||
props.x = cursor.location.x - obj.location.x
|
||||
return {"FINISHED"}
|
||||
|
||||
class Input3DCursorYArray(bpy.types.Operator):
|
||||
bl_idname = "bim.input_cursor_y_array"
|
||||
bl_label = "Get 3d Cursor Y Input for Array"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
obj = context.active_object
|
||||
props = obj.BIMArrayProperties
|
||||
cursor = context.scene.cursor
|
||||
if props.use_local_space:
|
||||
props.y = (Matrix.inverted(obj.matrix_world) @ cursor.matrix.col[3]).y
|
||||
else:
|
||||
props.y = cursor.location.y - obj.location.y
|
||||
return {"FINISHED"}
|
||||
|
||||
class Input3DCursorZArray(bpy.types.Operator):
|
||||
bl_idname = "bim.input_cursor_z_array"
|
||||
bl_label = "Get 3d Cursor Z Input for Array"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
obj = context.active_object
|
||||
props = obj.BIMArrayProperties
|
||||
cursor = context.scene.cursor
|
||||
if props.use_local_space:
|
||||
props.z = (Matrix.inverted(obj.matrix_world) @ cursor.matrix.col[3]).z
|
||||
else:
|
||||
props.z = cursor.location.z - obj.location.z
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -24,8 +24,6 @@ import ifcopenshell
|
||||
import ifcopenshell.util.element
|
||||
from ifcopenshell.util.doc import get_entity_doc, get_predefined_type_doc
|
||||
import blenderbim.tool as tool
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.module.model.root import ConstrTypeEntityNotFound
|
||||
|
||||
|
||||
def refresh():
|
||||
@@ -49,9 +47,8 @@ class AuthoringData:
|
||||
def load(cls):
|
||||
cls.is_loaded = True
|
||||
cls.props = bpy.context.scene.BIMModelProperties
|
||||
cls.load_ifc_classes()
|
||||
cls.load_relating_types()
|
||||
cls.load_relating_types_browser()
|
||||
cls.data["ifc_classes"] = cls.ifc_classes()
|
||||
cls.data["relating_type_id"] = cls.relating_type_id()
|
||||
cls.data["type_class"] = cls.type_class()
|
||||
cls.data["type_predefined_type"] = cls.type_predefined_type()
|
||||
cls.data["total_types"] = cls.total_types()
|
||||
@@ -64,6 +61,7 @@ class AuthoringData:
|
||||
cls.data["has_visible_openings"] = cls.has_visible_openings()
|
||||
cls.data["active_class"] = cls.active_class()
|
||||
cls.data["active_material_usage"] = cls.active_material_usage()
|
||||
cls.data["active_representation_type"] = cls.active_representation_type()
|
||||
|
||||
@classmethod
|
||||
def type_class(cls):
|
||||
@@ -99,23 +97,11 @@ class AuthoringData:
|
||||
|
||||
@classmethod
|
||||
def type_thumbnail(cls):
|
||||
if not cls.data["relating_types_ids"]:
|
||||
if not cls.data["relating_type_id"]:
|
||||
return 0
|
||||
element = tool.Ifc.get().by_id(int(cls.props.relating_type_id))
|
||||
return cls.type_thumbnails.get(element.id(), None) or 0
|
||||
|
||||
@classmethod
|
||||
def load_ifc_classes(cls):
|
||||
cls.data["ifc_classes"] = cls.ifc_classes()
|
||||
|
||||
@classmethod
|
||||
def load_relating_types(cls):
|
||||
cls.data["relating_types_ids"] = cls.relating_types()
|
||||
|
||||
@classmethod
|
||||
def load_relating_types_browser(cls):
|
||||
cls.data["relating_types_ids_browser"] = cls.relating_types_browser()
|
||||
|
||||
@classmethod
|
||||
def total_types(cls):
|
||||
type_class = cls.props.type_class
|
||||
@@ -187,169 +173,45 @@ class AuthoringData:
|
||||
if element:
|
||||
return tool.Model.get_usage_type(element)
|
||||
|
||||
@classmethod
|
||||
def active_representation_type(cls):
|
||||
if bpy.context.active_object:
|
||||
representation = tool.Geometry.get_active_representation(bpy.context.active_object)
|
||||
if representation:
|
||||
return representation.RepresentationType
|
||||
|
||||
@classmethod
|
||||
def ifc_classes(cls):
|
||||
results = []
|
||||
classes = {
|
||||
e.is_a()
|
||||
for e in tool.Ifc.get().by_type("IfcElementType")
|
||||
+ tool.Ifc.get().by_type("IfcDoorStyle")
|
||||
+ tool.Ifc.get().by_type("IfcWindowStyle")
|
||||
+ tool.Ifc.get().by_type("IfcSpaceType")
|
||||
e.is_a() for e in (tool.Ifc.get().by_type("IfcElementType") + tool.Ifc.get().by_type("IfcSpaceType"))
|
||||
}
|
||||
|
||||
if tool.Ifc.get_schema() in ("IFC2X3", "IFC4"):
|
||||
classes.update(
|
||||
{e.is_a() for e in (tool.Ifc.get().by_type("IfcDoorStyle") + tool.Ifc.get().by_type("IfcWindowStyle"))}
|
||||
)
|
||||
results.extend([(c, c, "") for c in sorted(classes)])
|
||||
return results
|
||||
|
||||
@classmethod
|
||||
def constr_class_entities(cls, ifc_class=None):
|
||||
def relating_type_id(cls):
|
||||
ifc_classes = cls.data["ifc_classes"]
|
||||
if not ifc_classes:
|
||||
return []
|
||||
results = []
|
||||
if ifc_class is None:
|
||||
ifc_class = cls.props.ifc_class
|
||||
ifc_class = cls.props.ifc_class
|
||||
if not ifc_class and ifc_classes:
|
||||
ifc_class = ifc_classes[0][0]
|
||||
if ifc_class:
|
||||
elements = sorted(tool.Ifc.get().by_type(ifc_class), key=lambda s: s.Name or "Unnamed")
|
||||
results.extend(elements)
|
||||
return results
|
||||
return [
|
||||
(str(e.id()), e.Name or "Unnamed", e.Description or "")
|
||||
for e in results
|
||||
]
|
||||
return []
|
||||
|
||||
@classmethod
|
||||
def relating_types(cls, ifc_class=None):
|
||||
return [
|
||||
(str(e.id()), e.Name or "Unnamed", e.Description or "")
|
||||
for e in cls.constr_class_entities(ifc_class=ifc_class)
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def relating_types_browser(cls):
|
||||
if cls.data["ifc_classes"]:
|
||||
return cls.relating_types(ifc_class=cls.props.ifc_class_browser)
|
||||
|
||||
@classmethod
|
||||
def new_constr_class_info(cls, ifc_class):
|
||||
if ifc_class not in cls.props.constr_classes:
|
||||
cls.props.constr_classes.add().name = ifc_class
|
||||
return cls.props.constr_classes[ifc_class]
|
||||
|
||||
@classmethod
|
||||
def assetize_constr_class(cls, ifc_class=None):
|
||||
selected_ifc_class = cls.props.ifc_class
|
||||
selected_relating_type_id = cls.props.relating_type_id
|
||||
if ifc_class is None:
|
||||
ifc_class = cls.props.ifc_class_browser
|
||||
if cls.constr_class_info(ifc_class) is None:
|
||||
cls.new_constr_class_info(ifc_class)
|
||||
constr_class_occurrences = cls.constr_class_entities(ifc_class)
|
||||
constr_classes = cls.props.constr_classes
|
||||
|
||||
for constr_class_entity in constr_class_occurrences:
|
||||
if (
|
||||
ifc_class not in constr_classes
|
||||
or constr_class_entity.Name not in constr_classes[ifc_class].constr_types
|
||||
):
|
||||
obj = tool.Ifc.get_object(constr_class_entity)
|
||||
cls.assetize_object(obj, ifc_class, constr_class_entity)
|
||||
cls.constr_class_info(ifc_class).fully_loaded = True
|
||||
cls.props.updating = True
|
||||
cls.props.ifc_class = selected_ifc_class
|
||||
cls.props.relating_type_id = selected_relating_type_id
|
||||
cls.props.updating = False
|
||||
|
||||
@classmethod
|
||||
def assetize_object(cls, obj, ifc_class, ifc_class_entity, from_selection=False):
|
||||
relating_type_id = ifc_class_entity.id()
|
||||
to_be_deleted = False
|
||||
if obj.type == "EMPTY":
|
||||
kwargs = {}
|
||||
if not from_selection:
|
||||
kwargs.update({"ifc_class": ifc_class, "relating_type_id": relating_type_id})
|
||||
new_obj = cls.new_relating_type(**kwargs)
|
||||
if new_obj is not None:
|
||||
to_be_deleted = True
|
||||
obj = new_obj
|
||||
obj.hide_set(True)
|
||||
obj.asset_mark()
|
||||
obj.asset_generate_preview()
|
||||
blender33_or_above = bpy.app.version >= (3, 3, 0)
|
||||
interval = 1e-4
|
||||
|
||||
def wait_for_asset_previews_generation(check_interval_seconds=interval):
|
||||
if blender33_or_above and bpy.app.is_job_running("RENDER_PREVIEW"):
|
||||
return check_interval_seconds
|
||||
else:
|
||||
if ifc_class not in cls.props.constr_classes:
|
||||
cls.props.constr_classes.add().name = ifc_class
|
||||
constr_class_info = cls.props.constr_classes[ifc_class]
|
||||
# relating_type = cls.relating_type_name_by_id(ifc_class, relating_type_id)
|
||||
if str(relating_type_id) not in constr_class_info.constr_types:
|
||||
constr_class_info.constr_types.add().name = str(relating_type_id)
|
||||
relating_type_info = constr_class_info.constr_types[str(relating_type_id)]
|
||||
relating_type_info.object = obj
|
||||
relating_type_info.icon_id = obj.preview.icon_id
|
||||
|
||||
if to_be_deleted:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if element:
|
||||
tool.Ifc.delete(element)
|
||||
tool.Ifc.unlink(obj=obj)
|
||||
for collection in obj.users_collection:
|
||||
collection.objects.unlink(obj)
|
||||
return None
|
||||
|
||||
first_interval = 0 if blender33_or_above else interval
|
||||
bpy.app.timers.register(wait_for_asset_previews_generation, first_interval=first_interval)
|
||||
|
||||
@classmethod
|
||||
def assetize_relating_type_from_selection(cls, browser=False):
|
||||
ifc_class = cls.props.ifc_class_browser if browser else cls.props.ifc_class
|
||||
relating_type_id = cls.props.relating_type_id_browser if browser else cls.props.relating_type_id
|
||||
constr_class_occurrences = cls.constr_class_entities(ifc_class=ifc_class)
|
||||
constr_class_occurrences = [
|
||||
entity for entity in constr_class_occurrences if entity.id() == int(relating_type_id)
|
||||
]
|
||||
if len(constr_class_occurrences) == 0:
|
||||
raise ConstrTypeEntityNotFound()
|
||||
constr_class_entity = constr_class_occurrences[0]
|
||||
if (obj := tool.Ifc.get_object(constr_class_entity)) is None:
|
||||
raise ConstrTypeEntityNotFound()
|
||||
cls.assetize_object(obj, ifc_class, constr_class_entity, from_selection=True)
|
||||
|
||||
@staticmethod
|
||||
def constr_class_info(ifc_class):
|
||||
props = bpy.context.scene.BIMModelProperties
|
||||
return props.constr_classes[ifc_class] if ifc_class in props.constr_classes else None
|
||||
|
||||
@classmethod
|
||||
def new_relating_type(cls, ifc_class=None, relating_type_id=None):
|
||||
if ifc_class is None:
|
||||
bpy.ops.bim.add_constr_type_instance(
|
||||
ifc_class=cls.props.ifc_class, relating_type_id=int(cls.props.relating_type_id)
|
||||
)
|
||||
else:
|
||||
cls.props.updating = True
|
||||
cls.props.ifc_class = ifc_class
|
||||
cls.props.relating_type_id = str(relating_type_id)
|
||||
cls.props.updating = False
|
||||
bpy.ops.bim.add_constr_type_instance()
|
||||
return bpy.context.selected_objects[-1]
|
||||
|
||||
@staticmethod
|
||||
def relating_type_name_by_id(ifc_class, relating_type_id):
|
||||
file = IfcStore.get_file()
|
||||
try:
|
||||
constr_class_entity = file.by_id(int(relating_type_id))
|
||||
except (RuntimeError, ValueError):
|
||||
return None
|
||||
return constr_class_entity.Name if constr_class_entity.is_a() == ifc_class else None
|
||||
|
||||
@classmethod
|
||||
def relating_type_id_by_name(cls, ifc_class, relating_type):
|
||||
relating_types = [ct[0] for ct in cls.relating_types(ifc_class=ifc_class) if ct[1] == relating_type]
|
||||
return None if len(relating_types) == 0 else relating_types[0]
|
||||
|
||||
|
||||
class ArrayData:
|
||||
data = {}
|
||||
|
||||
@@ -17,25 +17,22 @@
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import gpu
|
||||
import bgl
|
||||
import bmesh
|
||||
import blenderbim.tool as tool
|
||||
from math import pi, degrees, sin, cos, radians
|
||||
from math import sin, cos, radians
|
||||
from bpy.types import SpaceView3D
|
||||
from mathutils import Vector, Matrix
|
||||
from gpu.types import GPUShader, GPUBatch, GPUIndexBuf, GPUVertBuf, GPUVertFormat
|
||||
from gpu_extras.batch import batch_for_shader
|
||||
|
||||
|
||||
white = (1, 1, 1, 1)
|
||||
lightgrey = (0.7, 0.7, 0.7, 1)
|
||||
green = (0.545, 0.863, 0, 1)
|
||||
red = (1, 0.2, 0.322, 1)
|
||||
blue = (0.157, 0.565, 1, 1)
|
||||
grey = (0.2, 0.2, 0.2, 1)
|
||||
ERROR_ELEMENTS_COLOR = (1, 0.2, 0.322, 1) # RED
|
||||
UNSPECIAL_ELEMENT_COLOR = (0.2, 0.2, 0.2, 1) # GREY
|
||||
|
||||
faces_color = (0.494, 0.540, 0.593, 1)
|
||||
preview_edges_color = (0.130, 0.141, 0.371, 1)
|
||||
|
||||
def transparent_color(color, alpha=0.1):
|
||||
color = [i for i in color]
|
||||
color[3] = alpha
|
||||
return color
|
||||
|
||||
|
||||
def bm_check_vertex_in_groups(vertex, deform_layer, groups):
|
||||
@@ -71,13 +68,11 @@ class ProfileDecorator:
|
||||
pass
|
||||
cls.installed = None
|
||||
|
||||
def create_batch(self, shader_type, content_pos, color, indices=None, bind=True):
|
||||
batch = batch_for_shader(self.shader, shader_type, {"pos": content_pos}, indices=indices)
|
||||
# TODO: what's bind is for?
|
||||
if bind:
|
||||
self.shader.bind()
|
||||
self.shader.uniform_float("color", color)
|
||||
batch.draw(self.shader)
|
||||
def draw_batch(self, shader_type, content_pos, color, indices=None):
|
||||
shader = self.line_shader if shader_type == "LINES" else self.shader
|
||||
batch = batch_for_shader(shader, shader_type, {"pos": content_pos}, indices=indices)
|
||||
shader.uniform_float("color", color)
|
||||
batch.draw(shader)
|
||||
|
||||
def draw_faces(self, bm, vertices_coords):
|
||||
"""mutates original bm (triangulates it)
|
||||
@@ -87,9 +82,15 @@ class ProfileDecorator:
|
||||
bmesh.ops.triangulate(traingulated_bm, faces=traingulated_bm.faces)
|
||||
|
||||
face_indices = [[v.index for v in f.verts] for f in traingulated_bm.faces]
|
||||
self.create_batch("TRIS", vertices_coords, faces_color, face_indices)
|
||||
faces_color = transparent_color(self.model_props.decorator_color_special)
|
||||
self.draw_batch("TRIS", vertices_coords, faces_color, face_indices)
|
||||
|
||||
def __call__(self, context, get_custom_bmesh=None, draw_faces=False, exit_edit_mode_callback=None):
|
||||
self.model_props = context.scene.BIMModelProperties
|
||||
selected_elements_color = self.model_props.decorator_color_selected
|
||||
unselected_elements_color = self.model_props.decorator_color_unselected
|
||||
special_elements_color = self.model_props.decorator_color_special
|
||||
|
||||
obj = context.active_object
|
||||
|
||||
if obj.mode != "EDIT":
|
||||
@@ -103,20 +104,8 @@ class ProfileDecorator:
|
||||
else:
|
||||
bm = bmesh.from_edit_mesh(obj.data)
|
||||
|
||||
def gl_init(use_bgl=False):
|
||||
# TODO: remove as deprecated?
|
||||
if use_bgl:
|
||||
bgl.glLineWidth(2)
|
||||
bgl.glPointSize(6)
|
||||
bgl.glEnable(bgl.GL_BLEND)
|
||||
bgl.glEnable(bgl.GL_LINE_SMOOTH)
|
||||
else:
|
||||
gpu.state.line_width_set(2)
|
||||
gpu.state.point_size_set(6)
|
||||
gpu.state.blend_set("ALPHA")
|
||||
bgl.glEnable(bgl.GL_LINE_SMOOTH)
|
||||
|
||||
gl_init(True)
|
||||
gpu.state.point_size_set(6)
|
||||
gpu.state.blend_set("ALPHA")
|
||||
|
||||
### Actually drawing
|
||||
all_vertices = []
|
||||
@@ -202,22 +191,31 @@ class ProfileDecorator:
|
||||
unselected_edges.append(edge_indices)
|
||||
|
||||
### Actually drawing
|
||||
# 3D_POLYLINE_UNIFORM_COLOR is good for smoothed lines since `bgl.enable(GL_LINE_SMOOTH)` is deprecated
|
||||
self.line_shader = gpu.shader.from_builtin("3D_POLYLINE_UNIFORM_COLOR")
|
||||
self.line_shader.bind()
|
||||
# POLYLINE_UNIFORM_COLOR specific uniforms
|
||||
self.line_shader.uniform_float("viewportSize", (context.region.width, context.region.height))
|
||||
self.line_shader.uniform_float("lineWidth", 2.0)
|
||||
|
||||
# general shader
|
||||
self.shader = gpu.shader.from_builtin("3D_UNIFORM_COLOR")
|
||||
self.shader.bind()
|
||||
|
||||
# Draw faces
|
||||
if draw_faces:
|
||||
self.draw_faces(bm, all_vertices)
|
||||
|
||||
self.create_batch("LINES", all_vertices, lightgrey, unselected_edges)
|
||||
self.create_batch("LINES", all_vertices, green, selected_edges)
|
||||
self.create_batch("LINES", all_vertices, grey, arc_edges)
|
||||
self.create_batch("LINES", all_vertices, preview_edges_color, preview_edges)
|
||||
self.create_batch("LINES", all_vertices, blue, roof_angle_edges)
|
||||
self.draw_batch("LINES", all_vertices, transparent_color(unselected_elements_color), unselected_edges)
|
||||
self.draw_batch("LINES", all_vertices, selected_elements_color, selected_edges)
|
||||
self.draw_batch("LINES", all_vertices, UNSPECIAL_ELEMENT_COLOR, arc_edges)
|
||||
self.draw_batch("LINES", all_vertices, special_elements_color, preview_edges)
|
||||
self.draw_batch("LINES", all_vertices, special_elements_color, roof_angle_edges)
|
||||
|
||||
self.create_batch("POINTS", unselected_vertices, lightgrey)
|
||||
self.create_batch("POINTS", error_vertices, red)
|
||||
self.create_batch("POINTS", special_vertices, blue)
|
||||
self.create_batch("POINTS", selected_vertices, green)
|
||||
self.draw_batch("POINTS", unselected_vertices, transparent_color(unselected_elements_color, 0.5))
|
||||
self.draw_batch("POINTS", error_vertices, ERROR_ELEMENTS_COLOR)
|
||||
self.draw_batch("POINTS", special_vertices, self.model_props.decorator_color_special)
|
||||
self.draw_batch("POINTS", selected_vertices, self.model_props.decorator_color_selected)
|
||||
|
||||
# Draw arcs
|
||||
arc_centroids = []
|
||||
@@ -242,9 +240,9 @@ class ProfileDecorator:
|
||||
arc_centroids.append(tuple(centroid))
|
||||
arc_segments.append(tool.Cad.create_arc_segments(pts=points, num_verts=17, make_edges=True))
|
||||
|
||||
self.create_batch("POINTS", arc_centroids, grey, bind=False)
|
||||
self.draw_batch("POINTS", arc_centroids, UNSPECIAL_ELEMENT_COLOR)
|
||||
for verts, edges in arc_segments:
|
||||
self.create_batch("LINES", verts, blue, edges, bind=False)
|
||||
self.draw_batch("LINES", verts, special_elements_color, edges)
|
||||
|
||||
# Draw circles
|
||||
circle_centroids = []
|
||||
@@ -263,9 +261,9 @@ class ProfileDecorator:
|
||||
segments = [[list(matrix @ Vector(v)) for v in segments[0]], segments[1]]
|
||||
circle_segments.append(segments)
|
||||
|
||||
self.create_batch("POINTS", circle_centroids, grey, bind=False)
|
||||
self.draw_batch("POINTS", circle_centroids, UNSPECIAL_ELEMENT_COLOR)
|
||||
for verts, edges in circle_segments:
|
||||
self.create_batch("LINES", verts, blue, edges, bind=False)
|
||||
self.draw_batch("LINES", verts, special_elements_color, edges)
|
||||
|
||||
def create_matrix(self, p, x, y, z):
|
||||
return Matrix([x, y, z, p]).to_4x4().transposed()
|
||||
|
||||
@@ -43,6 +43,7 @@ def update_door_modifier_representation(context):
|
||||
props = obj.BIMDoorProperties
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
ifc_file = tool.Ifc.get()
|
||||
sliding_door = "SLIDING" in props.door_type
|
||||
|
||||
representation_data = {
|
||||
"operation_type": props.door_type,
|
||||
@@ -87,13 +88,37 @@ def update_door_modifier_representation(context):
|
||||
model_representation = ifcopenshell.api.run("geometry.add_door_representation", ifc_file, **representation_data)
|
||||
tool.Model.replace_object_ifc_representation(body, obj, model_representation)
|
||||
|
||||
# PLAN_VIEW representation
|
||||
plan = ifcopenshell.util.representation.get_context(ifc_file, "Plan", "Body", "PLAN_VIEW")
|
||||
if plan:
|
||||
representation_data["context"] = plan
|
||||
# Body/PLAN_VIEW representation
|
||||
plan_body = ifcopenshell.util.representation.get_context(ifc_file, "Plan", "Body", "PLAN_VIEW")
|
||||
if plan_body:
|
||||
representation_data["context"] = plan_body
|
||||
plan_representation = ifcopenshell.api.run("geometry.add_door_representation", ifc_file, **representation_data)
|
||||
tool.Model.replace_object_ifc_representation(plan, obj, plan_representation)
|
||||
tool.Model.replace_object_ifc_representation(plan_body, obj, plan_representation)
|
||||
|
||||
# Annotation/PLAN_VIEW representation
|
||||
plan_annotation = ifcopenshell.util.representation.get_context(ifc_file, "Plan", "Annotation", "PLAN_VIEW")
|
||||
if plan_annotation:
|
||||
if not sliding_door:
|
||||
# only sliding doors have Annotation/PLAN_VIEW
|
||||
# for other types we just check for old representation and remove it if it's there
|
||||
old_representation = ifcopenshell.util.representation.get_representation(
|
||||
element, "Plan", "Annotation", "PLAN_VIEW"
|
||||
)
|
||||
if old_representation:
|
||||
core.remove_representation(
|
||||
tool.Ifc,
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=old_representation,
|
||||
)
|
||||
else:
|
||||
representation_data["context"] = plan_annotation
|
||||
plan_representation = ifcopenshell.api.run(
|
||||
"geometry.add_door_representation", ifc_file, **representation_data
|
||||
)
|
||||
tool.Model.replace_object_ifc_representation(plan_annotation, obj, plan_representation)
|
||||
|
||||
if plan_body or plan_annotation:
|
||||
# adding switch representation at the end instead of changing order of representations
|
||||
# to prevent #2744
|
||||
core.switch_representation(
|
||||
@@ -263,13 +288,17 @@ def update_door_modifier_bmesh(context):
|
||||
door_type = props.door_type
|
||||
double_swing_door = "DOUBLE_SWING" in door_type
|
||||
double_door = "DOUBLE_DOOR" in door_type
|
||||
sliding_door = "SLIDING" in door_type
|
||||
|
||||
# lining params
|
||||
lining_depth = props.lining_depth * si_conversion
|
||||
lining_thickness_default = props.lining_thickness * si_conversion
|
||||
lining_offset = props.lining_offset * si_conversion
|
||||
lining_to_panel_offset_x = props.lining_to_panel_offset_x * si_conversion
|
||||
lining_to_panel_offset_y = props.lining_to_panel_offset_y * si_conversion
|
||||
lining_to_panel_offset_x = (
|
||||
props.lining_to_panel_offset_x * si_conversion if not sliding_door else lining_thickness_default
|
||||
)
|
||||
panel_depth = props.panel_depth * si_conversion
|
||||
lining_to_panel_offset_y = props.lining_to_panel_offset_y * si_conversion if not sliding_door else -panel_depth
|
||||
|
||||
transom_thickness = props.transom_thickness * si_conversion / 2
|
||||
transfom_offset = props.transom_offset * si_conversion
|
||||
@@ -278,10 +307,10 @@ def update_door_modifier_bmesh(context):
|
||||
window_lining_height = overall_height - transfom_offset - transom_thickness
|
||||
|
||||
side_lining_thickness = lining_thickness_default
|
||||
panel_lining_overlap_x = max(lining_thickness_default - lining_to_panel_offset_x, 0)
|
||||
panel_lining_overlap_x = max(lining_thickness_default - lining_to_panel_offset_x, 0) if not sliding_door else 0
|
||||
|
||||
top_lining_thickness = transom_thickness or lining_thickness_default
|
||||
panel_top_lining_overlap_x = max(top_lining_thickness - lining_to_panel_offset_x, 0)
|
||||
panel_top_lining_overlap_x = max(top_lining_thickness - lining_to_panel_offset_x, 0) if not sliding_door else 0
|
||||
door_opening_width = overall_width - lining_to_panel_offset_x * 2
|
||||
if double_swing_door:
|
||||
side_lining_thickness = side_lining_thickness - panel_lining_overlap_x
|
||||
@@ -296,7 +325,6 @@ def update_door_modifier_bmesh(context):
|
||||
casing_depth = props.casing_depth * si_conversion
|
||||
|
||||
# panel params
|
||||
panel_depth = props.panel_depth * si_conversion
|
||||
panel_width = door_opening_width * props.panel_width_ratio
|
||||
frame_depth = props.frame_depth * si_conversion
|
||||
frame_thickness = props.frame_thickness * si_conversion
|
||||
@@ -389,7 +417,7 @@ def update_door_modifier_bmesh(context):
|
||||
panel_position = V(lining_to_panel_offset_x, lining_to_panel_offset_y, threshold_thickness)
|
||||
|
||||
if double_door:
|
||||
# TODO: keep a little space between doors for readibility?
|
||||
# keeping a little space between doors for readibility
|
||||
double_door_offset = 0.001 * si_conversion
|
||||
panel_size.x = panel_size.x / 2 - double_door_offset
|
||||
door_verts.extend(create_bm_door_panel(panel_size, panel_position, "LEFT"))
|
||||
@@ -516,7 +544,7 @@ class AddDoor(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
class CancelEditingDoor(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.cancel_editing_door"
|
||||
bl_label = "Cancel editing Door"
|
||||
bl_label = "Cancel Editing Door"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
def _execute(self, context):
|
||||
@@ -545,7 +573,7 @@ class CancelEditingDoor(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
class FinishEditingDoor(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.finish_editing_door"
|
||||
bl_label = "Finish editing door"
|
||||
bl_label = "Finish Editing Door"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
def _execute(self, context):
|
||||
|
||||
@@ -60,9 +60,6 @@ def load_post(*args):
|
||||
product.regenerate_profile_usage,
|
||||
)
|
||||
|
||||
IfcStore.add_element_listener(wall.element_listener)
|
||||
IfcStore.add_element_listener(profile.element_listener)
|
||||
|
||||
ifcopenshell.api.add_post_listener(
|
||||
"geometry.add_representation", "BlenderBIM.DumbWall.CalculateQuantities", wall.calculate_quantities
|
||||
)
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
|
||||
import bpy
|
||||
import gpu
|
||||
import bgl
|
||||
import bmesh
|
||||
import logging
|
||||
import numpy as np
|
||||
@@ -36,7 +35,6 @@ from bpy.types import Operator
|
||||
from bpy.types import SpaceView3D
|
||||
from bpy.props import FloatProperty
|
||||
from bpy_extras.object_utils import AddObjectHelper, object_data_add
|
||||
from gpu.types import GPUShader, GPUBatch, GPUIndexBuf, GPUVertBuf, GPUVertFormat
|
||||
from gpu_extras.batch import batch_for_shader
|
||||
|
||||
|
||||
@@ -217,8 +215,9 @@ class FilledOpeningGenerator:
|
||||
|
||||
def generate_opening_from_filling(self, filling, filling_obj, voided_obj):
|
||||
thickness = voided_obj.dimensions[1] + 0.1 + 0.1
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
shape_builder = ifcopenshell.util.shape_builder.ShapeBuilder(tool.Ifc.get())
|
||||
ifc_file = tool.Ifc.get()
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(ifc_file)
|
||||
shape_builder = ifcopenshell.util.shape_builder.ShapeBuilder(ifc_file)
|
||||
|
||||
profile = None
|
||||
filling_type = ifcopenshell.util.element.get_type(filling)
|
||||
@@ -227,47 +226,59 @@ class FilledOpeningGenerator:
|
||||
filling_type, "Model", "Profile", "ELEVATION_VIEW"
|
||||
)
|
||||
filling_obj = tool.Ifc.get_object(filling_type)
|
||||
context = ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Model", "Body", "MODEL_VIEW")
|
||||
context = ifcopenshell.util.representation.get_context(ifc_file, "Model", "Body", "MODEL_VIEW")
|
||||
|
||||
if profile:
|
||||
curve_3d = ifcopenshell.util.representation.resolve_representation(profile).Items[0]
|
||||
|
||||
def get_curve_2d_from_3d(curve_3d):
|
||||
ifc_segments = [shape_builder.deep_copy(s) for s in curve_3d.Segments]
|
||||
ifc_points = ifc_file.createIfcCartesianPointList2D([Vector(p).xz for p in curve_3d.Points.CoordList])
|
||||
ifc_curve = ifc_file.createIfcIndexedPolyCurve(Points=ifc_points, Segments=ifc_segments)
|
||||
return ifc_curve
|
||||
|
||||
extrusion = shape_builder.extrude(
|
||||
ifcopenshell.util.representation.resolve_representation(profile).Items[0],
|
||||
get_curve_2d_from_3d(curve_3d),
|
||||
magnitude=thickness / unit_scale,
|
||||
position=Vector([0.0, -0.1 / unit_scale, 0.0]),
|
||||
extrusion_vector=Vector([0.0, 1.0, 0.0]),
|
||||
position_x_axis=Vector((1, 0, 0)),
|
||||
position_z_axis=Vector((0, -1, 0)),
|
||||
extrusion_vector=Vector((0, 0, -1)),
|
||||
)
|
||||
return shape_builder.get_representation(context, [extrusion])
|
||||
|
||||
x, y, z = filling_obj.dimensions
|
||||
opening_position = Vector([0.0, -0.1 / unit_scale, 0.0])
|
||||
opening_size = Vector([x, 0, z]) / unit_scale
|
||||
opening_size = Vector([x, z]) / unit_scale
|
||||
|
||||
# Windows and doors can have a casing that overlaps the wall
|
||||
# but shouldn't affect the size of the opening.
|
||||
# So we shouldn't use object dimensions in that case. More: #2784
|
||||
# Just keeping it for windows and doors for now to be safe
|
||||
x_redefined, z_redefined = False, False
|
||||
has_width_attribute, has_height_attribute = False, False
|
||||
if filling.is_a() in ["IfcWindow", "IfcDoor"]:
|
||||
if filling.OverallWidth:
|
||||
opening_size.x = filling.OverallWidth
|
||||
x_redefined = True
|
||||
has_width_attribute = True
|
||||
if filling.OverallHeight:
|
||||
opening_size.z = filling.OverallHeight
|
||||
z_redefined = True
|
||||
opening_size.y = filling.OverallHeight
|
||||
has_height_attribute = True
|
||||
|
||||
# making sure if min_x or min_z != 0 to shift the opening accordingly
|
||||
# to prevent something like #2784
|
||||
if not x_redefined:
|
||||
if not has_width_attribute:
|
||||
opening_position.x = min(v[0] for v in filling_obj.bound_box)
|
||||
|
||||
if not z_redefined:
|
||||
if not has_height_attribute:
|
||||
opening_position.z = min(v[2] for v in filling_obj.bound_box)
|
||||
|
||||
extrusion = shape_builder.extrude(
|
||||
shape_builder.rectangle(size=opening_size),
|
||||
magnitude=thickness / unit_scale,
|
||||
position=opening_position,
|
||||
extrusion_vector=Vector([0.0, 1.0, 0.0]),
|
||||
position_z_axis=Vector((0.0, -1.0, 0.0)),
|
||||
position_x_axis=Vector((1.0, 0.0, 0.0)),
|
||||
extrusion_vector=Vector((0.0, 0.0, -1.0)),
|
||||
)
|
||||
|
||||
return shape_builder.get_representation(context, [extrusion])
|
||||
@@ -325,15 +336,13 @@ class RecalculateFill(bpy.types.Operator, tool.Ifc.Operator):
|
||||
for building_element in decomposed_building_elements:
|
||||
building_obj = tool.Ifc.get_object(building_element)
|
||||
if building_obj and building_obj.data:
|
||||
body = ifcopenshell.util.representation.get_representation(
|
||||
building_element, "Model", "Body", "MODEL_VIEW"
|
||||
)
|
||||
if body:
|
||||
representation = tool.Geometry.get_active_representation(building_obj)
|
||||
if representation:
|
||||
blenderbim.core.geometry.switch_representation(
|
||||
tool.Ifc,
|
||||
tool.Geometry,
|
||||
obj=building_obj,
|
||||
representation=body,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
@@ -709,13 +718,12 @@ class EditOpenings(Operator, tool.Ifc.Operator):
|
||||
if opening_obj:
|
||||
if tool.Ifc.is_edited(opening_obj):
|
||||
tool.Geometry.run_geometry_update_representation(obj=opening_obj)
|
||||
building_objs.add(obj)
|
||||
building_objs.update(self.get_all_building_objects_of_similar_openings(opening))
|
||||
elif tool.Ifc.is_moved(opening_obj):
|
||||
blenderbim.core.geometry.edit_object_placement(
|
||||
tool.Ifc, tool.Geometry, tool.Surveyor, obj=opening_obj
|
||||
)
|
||||
building_objs.add(obj)
|
||||
building_objs.add(obj)
|
||||
building_objs.update(self.get_all_building_objects_of_similar_openings(opening))
|
||||
tool.Ifc.unlink(element=opening, obj=opening_obj)
|
||||
bpy.data.objects.remove(opening_obj)
|
||||
|
||||
@@ -759,6 +767,7 @@ class EditOpenings(Operator, tool.Ifc.Operator):
|
||||
return results
|
||||
|
||||
|
||||
# TODO: merge with ProfileDecorator?
|
||||
class DecorationsHandler:
|
||||
installed = None
|
||||
|
||||
@@ -777,27 +786,38 @@ class DecorationsHandler:
|
||||
pass
|
||||
cls.installed = None
|
||||
|
||||
def draw_batch(self, shader_type, content_pos, color, indices=None):
|
||||
shader = self.line_shader if shader_type == "LINES" else self.shader
|
||||
batch = batch_for_shader(shader, shader_type, {"pos": content_pos}, indices=indices)
|
||||
shader.uniform_float("color", color)
|
||||
batch.draw(shader)
|
||||
|
||||
def __call__(self, context):
|
||||
bgl.glLineWidth(2)
|
||||
bgl.glPointSize(6)
|
||||
bgl.glEnable(bgl.GL_BLEND)
|
||||
bgl.glEnable(bgl.GL_LINE_SMOOTH)
|
||||
self.model_props = context.scene.BIMModelProperties
|
||||
selected_elements_color = self.model_props.decorator_color_selected
|
||||
unselected_elements_color = self.model_props.decorator_color_unselected
|
||||
special_elements_color = self.model_props.decorator_color_special
|
||||
|
||||
def transparent_color(color, alpha=0.1):
|
||||
color = [i for i in color]
|
||||
color[3] = alpha
|
||||
return color
|
||||
|
||||
gpu.state.point_size_set(6)
|
||||
gpu.state.blend_set("ALPHA")
|
||||
|
||||
for opening in context.scene.BIMModelProperties.openings:
|
||||
obj = opening.obj
|
||||
|
||||
if not obj:
|
||||
continue
|
||||
|
||||
white = (1, 1, 1, 1)
|
||||
white_t = (1, 1, 1, 0.1)
|
||||
green = (0.545, 0.863, 0, 1)
|
||||
red = (1, 0.2, 0.322, 1)
|
||||
red_t = (1, 0.2, 0.322, 0.1)
|
||||
blue = (0.157, 0.565, 1, 1)
|
||||
blue_t = (0.157, 0.565, 1, 0.1)
|
||||
grey = (0.2, 0.2, 0.2, 1)
|
||||
self.line_shader = gpu.shader.from_builtin("3D_POLYLINE_UNIFORM_COLOR")
|
||||
self.line_shader.bind() # required to be able to change uniforms of the shader
|
||||
# POLYLINE_UNIFORM_COLOR specific uniforms
|
||||
self.line_shader.uniform_float("viewportSize", (context.region.width, context.region.height))
|
||||
self.line_shader.uniform_float("lineWidth", 2.0)
|
||||
|
||||
# general shader
|
||||
self.shader = gpu.shader.from_builtin("3D_UNIFORM_COLOR")
|
||||
|
||||
verts = []
|
||||
@@ -829,41 +849,22 @@ class DecorationsHandler:
|
||||
else:
|
||||
unselected_edges.append(edge_indices)
|
||||
|
||||
batch = batch_for_shader(self.shader, "LINES", {"pos": verts}, indices=unselected_edges)
|
||||
self.shader.bind()
|
||||
self.shader.uniform_float("color", white)
|
||||
batch.draw(self.shader)
|
||||
|
||||
batch = batch_for_shader(self.shader, "LINES", {"pos": verts}, indices=selected_edges)
|
||||
self.shader.uniform_float("color", green)
|
||||
batch.draw(self.shader)
|
||||
|
||||
batch = batch_for_shader(self.shader, "POINTS", {"pos": unselected_vertices})
|
||||
self.shader.uniform_float("color", white)
|
||||
batch.draw(self.shader)
|
||||
|
||||
batch = batch_for_shader(self.shader, "POINTS", {"pos": selected_vertices})
|
||||
self.shader.uniform_float("color", green)
|
||||
batch.draw(self.shader)
|
||||
self.draw_batch("LINES", verts, transparent_color(unselected_elements_color, 0.5), unselected_edges)
|
||||
self.draw_batch("LINES", verts, selected_elements_color, selected_edges)
|
||||
self.draw_batch("POINTS", unselected_vertices, unselected_elements_color)
|
||||
self.draw_batch("POINTS", selected_vertices, selected_elements_color)
|
||||
else:
|
||||
bm = bmesh.new()
|
||||
bm.from_mesh(obj.data)
|
||||
|
||||
verts = [tuple(obj.matrix_world @ v.co) for v in bm.verts]
|
||||
edges = [tuple([v.index for v in e.verts]) for e in bm.edges]
|
||||
|
||||
batch = batch_for_shader(self.shader, "LINES", {"pos": verts}, indices=edges)
|
||||
self.shader.bind()
|
||||
self.shader.uniform_float("color", green if obj in context.selected_objects else blue)
|
||||
batch.draw(self.shader)
|
||||
color = selected_elements_color if obj in context.selected_objects else special_elements_color
|
||||
self.draw_batch("LINES", verts, color, edges)
|
||||
|
||||
obj.data.calc_loop_triangles()
|
||||
tris = [tuple(t.vertices) for t in obj.data.loop_triangles]
|
||||
|
||||
batch = batch_for_shader(self.shader, "TRIS", {"pos": verts}, indices=tris)
|
||||
self.shader.bind()
|
||||
self.shader.uniform_float("color", blue_t)
|
||||
batch.draw(self.shader)
|
||||
self.draw_batch("TRIS", verts, transparent_color(special_elements_color), tris)
|
||||
|
||||
if "HalfSpaceSolid" in obj.name:
|
||||
# Arrow shape
|
||||
@@ -876,10 +877,8 @@ class DecorationsHandler:
|
||||
tuple(obj.matrix_world @ Vector((0, -0.05, 0.45))),
|
||||
]
|
||||
edges = [(0, 1), (1, 2), (1, 3), (1, 4), (1, 5)]
|
||||
batch = batch_for_shader(self.shader, "LINES", {"pos": verts}, indices=edges)
|
||||
self.shader.bind()
|
||||
self.shader.uniform_float("color", green if obj in context.selected_objects else blue)
|
||||
batch.draw(self.shader)
|
||||
color = selected_elements_color if obj in context.selected_objects else special_elements_color
|
||||
self.draw_batch("LINES", verts, color, edges)
|
||||
|
||||
if obj.mode != "EDIT":
|
||||
bm.free()
|
||||
|
||||
@@ -402,7 +402,7 @@ class MirrorElements(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.mirror_elements"
|
||||
bl_label = "Mirror Elements"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Faux-mirrors the selected object by an active empty along a mirror plane."
|
||||
bl_description = "Faux-mirrors the selected objects by an active empty along a mirror plane"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
@@ -429,6 +429,13 @@ class MirrorElements(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
mirror.select_set(False)
|
||||
|
||||
if not context.selected_objects:
|
||||
self.report(
|
||||
{"INFO"},
|
||||
"At least two objects must be selected: an object to be mirrored, and a mirror axis as the active object.",
|
||||
)
|
||||
return {"FINISHED"}
|
||||
|
||||
bpy.ops.bim.override_object_duplicate_move(is_interactive=False)
|
||||
|
||||
for obj in context.selected_objects:
|
||||
|
||||
@@ -31,64 +31,10 @@ import blenderbim.core.geometry
|
||||
from math import pi, degrees, inf
|
||||
from mathutils import Vector, Matrix, Quaternion
|
||||
from blenderbim.bim.module.geometry.helper import Helper
|
||||
from blenderbim.bim.module.model.wall import DumbWallRecalculator
|
||||
from blenderbim.bim.module.model.decorator import ProfileDecorator
|
||||
|
||||
|
||||
def element_listener(element, obj):
|
||||
blenderbim.bim.handler.subscribe_to(obj, "mode", mode_callback)
|
||||
|
||||
|
||||
def mode_callback(obj, data):
|
||||
for obj in set(bpy.context.selected_objects + [bpy.context.active_object]):
|
||||
if (
|
||||
not obj.data
|
||||
or not isinstance(obj.data, (bpy.types.Mesh, bpy.types.Curve, bpy.types.TextCurve))
|
||||
or not obj.BIMObjectProperties.ifc_definition_id
|
||||
or not bpy.context.scene.BIMProjectProperties.is_authoring
|
||||
):
|
||||
return
|
||||
product = tool.Ifc.get().by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
parametric = ifcopenshell.util.element.get_psets(product).get("EPset_Parametric")
|
||||
if not parametric or parametric["Engine"] != "BlenderBIM.DumbProfile":
|
||||
return
|
||||
if obj.mode == "EDIT":
|
||||
tool.Ifc.edit(obj)
|
||||
bm = bmesh.from_edit_mesh(obj.data)
|
||||
bmesh.ops.dissolve_limit(bm, angle_limit=pi / 180 * 1, verts=bm.verts, edges=bm.edges)
|
||||
bmesh.update_edit_mesh(obj.data)
|
||||
else:
|
||||
material_usage = ifcopenshell.util.element.get_material(product)
|
||||
x, y = obj.dimensions[0:2]
|
||||
if not material_usage.CardinalPoint:
|
||||
new_origin = obj.matrix_world @ (Vector(obj.bound_box[0]) + (Vector((x, y, 0)) / 2))
|
||||
elif material_usage.CardinalPoint == 1:
|
||||
new_origin = obj.matrix_world @ Vector(obj.bound_box[4])
|
||||
elif material_usage.CardinalPoint == 2:
|
||||
new_origin = obj.matrix_world @ (Vector(obj.bound_box[0]) + (Vector((x, 0, 0)) / 2))
|
||||
elif material_usage.CardinalPoint == 3:
|
||||
new_origin = obj.matrix_world @ Vector(obj.bound_box[0])
|
||||
elif material_usage.CardinalPoint == 4:
|
||||
new_origin = obj.matrix_world @ (Vector(obj.bound_box[4]) + (Vector((0, y, 0)) / 2))
|
||||
elif material_usage.CardinalPoint == 5:
|
||||
new_origin = obj.matrix_world @ (Vector(obj.bound_box[0]) + (Vector((x, y, 0)) / 2))
|
||||
elif material_usage.CardinalPoint == 6:
|
||||
new_origin = obj.matrix_world @ (Vector(obj.bound_box[0]) + (Vector((0, y, 0)) / 2))
|
||||
elif material_usage.CardinalPoint == 7:
|
||||
new_origin = obj.matrix_world @ Vector(obj.bound_box[7])
|
||||
elif material_usage.CardinalPoint == 8:
|
||||
new_origin = obj.matrix_world @ (Vector(obj.bound_box[3]) + (Vector((x, 0, 0)) / 2))
|
||||
elif material_usage.CardinalPoint == 9:
|
||||
new_origin = obj.matrix_world @ Vector(obj.bound_box[3])
|
||||
if (obj.matrix_world.translation - new_origin).length < 0.001:
|
||||
return
|
||||
obj.data.transform(
|
||||
Matrix.Translation(
|
||||
(obj.matrix_world.inverted().to_quaternion() @ (obj.matrix_world.translation - new_origin))
|
||||
)
|
||||
)
|
||||
obj.matrix_world.translation = new_origin
|
||||
|
||||
|
||||
class DumbProfileGenerator:
|
||||
def __init__(self, relating_type):
|
||||
self.relating_type = relating_type
|
||||
@@ -314,7 +260,7 @@ class DumbProfileJoiner:
|
||||
body[1 if connection == "ATEND" else 0] = intersect
|
||||
self.recreate_profile(element1, profile1, axis, body)
|
||||
|
||||
def set_depth(self, profile1, length):
|
||||
def set_depth(self, profile1, si_length):
|
||||
element1 = tool.Ifc.get_entity(profile1)
|
||||
if not element1:
|
||||
return
|
||||
@@ -324,8 +270,6 @@ class DumbProfileJoiner:
|
||||
axis1 = self.get_profile_axis(profile1)
|
||||
axis = copy.deepcopy(axis1)
|
||||
body = copy.deepcopy(axis1)
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
si_length = unit_scale * length
|
||||
end = profile1.matrix_world @ Vector((0, 0, si_length))
|
||||
axis[1] = end
|
||||
body[1] = end
|
||||
@@ -892,15 +836,24 @@ class Rotate90(bpy.types.Operator, tool.Ifc.Operator):
|
||||
return context.selected_objects
|
||||
|
||||
def _execute(self, context):
|
||||
objs = []
|
||||
profile_objs = []
|
||||
layer2_objs = []
|
||||
for obj in context.selected_objects:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
usage = tool.Model.get_usage_type(element)
|
||||
if usage == "PROFILE":
|
||||
profile_objs.append(obj)
|
||||
elif usage == "LAYER2":
|
||||
layer2_objs.append(obj)
|
||||
if element.ConnectedTo or element.ConnectedFrom:
|
||||
objs.append(obj)
|
||||
ifcopenshell.api.run("geometry.disconnect_path", tool.Ifc.get(), element=element, connection_type="ATSTART")
|
||||
ifcopenshell.api.run("geometry.disconnect_path", tool.Ifc.get(), element=element, connection_type="ATEND")
|
||||
ifcopenshell.api.run("geometry.disconnect_path", tool.Ifc.get(), element=element, connection_type="ATPATH")
|
||||
rotate_matrix = Matrix.Rotation(pi / 2, 4, self.axis)
|
||||
obj.matrix_world @= rotate_matrix
|
||||
bpy.context.view_layer.update()
|
||||
DumbProfileRecalculator().recalculate(objs)
|
||||
DumbProfileRecalculator().recalculate(profile_objs)
|
||||
DumbWallRecalculator().recalculate(layer2_objs)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -1022,5 +975,5 @@ class EditExtrusionAxis(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bpy.context.view_layer.update()
|
||||
|
||||
joiner = DumbProfileJoiner()
|
||||
joiner.set_depth(obj, depth / self.unit_scale)
|
||||
joiner.set_depth(obj, depth)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -21,7 +21,6 @@ import ifcopenshell
|
||||
import blenderbim.tool as tool
|
||||
from blenderbim.bim.prop import ObjProperty
|
||||
from blenderbim.bim.module.model.data import AuthoringData
|
||||
from blenderbim.bim.module.model.root import ConstrTypeEntityNotFound
|
||||
from bpy.types import PropertyGroup, NodeTree
|
||||
from math import pi
|
||||
|
||||
@@ -38,16 +37,10 @@ def get_type_class(self, context):
|
||||
return AuthoringData.data["type_class"]
|
||||
|
||||
|
||||
def get_relating_type(self, context):
|
||||
def get_relating_type_id(self, context):
|
||||
if not AuthoringData.is_loaded:
|
||||
AuthoringData.load()
|
||||
return AuthoringData.data["relating_types_ids"]
|
||||
|
||||
|
||||
def get_relating_type_browser(self, context):
|
||||
if not AuthoringData.is_loaded:
|
||||
AuthoringData.load()
|
||||
return AuthoringData.data["relating_types_ids_browser"]
|
||||
return AuthoringData.data["relating_type_id"]
|
||||
|
||||
|
||||
def get_type_predefined_type(self, context):
|
||||
@@ -56,33 +49,9 @@ def get_type_predefined_type(self, context):
|
||||
return AuthoringData.data["type_predefined_type"]
|
||||
|
||||
|
||||
def update_icon_id(self, context, browser=False):
|
||||
if context == "lost_context" or (context.region is not None and context.region.type != "TOOL_HEADER"):
|
||||
ifc_class = self.ifc_class_browser if browser else self.ifc_class
|
||||
relating_type_id = self.relating_type_id_browser if browser else self.relating_type_id
|
||||
# relating_type = AuthoringData.relating_type_name_by_id(ifc_class, relating_type_id)
|
||||
|
||||
if ifc_class not in self.constr_classes or relating_type_id not in self.constr_classes[ifc_class].constr_types:
|
||||
try:
|
||||
AuthoringData.assetize_relating_type_from_selection(browser=browser)
|
||||
except ConstrTypeEntityNotFound:
|
||||
return
|
||||
|
||||
def set_icon(update_interval_seconds=1e-4):
|
||||
if (
|
||||
ifc_class not in self.constr_classes
|
||||
or relating_type_id not in self.constr_classes[ifc_class].constr_types
|
||||
):
|
||||
return update_interval_seconds
|
||||
else:
|
||||
self.icon_id = self.constr_classes[ifc_class].constr_types[relating_type_id].icon_id
|
||||
|
||||
bpy.app.timers.register(set_icon)
|
||||
|
||||
|
||||
def update_ifc_class(self, context):
|
||||
bpy.ops.bim.load_type_thumbnails(ifc_class=self.ifc_class)
|
||||
AuthoringData.data["relating_types_ids"] = AuthoringData.relating_types()
|
||||
AuthoringData.data["relating_type_id"] = AuthoringData.relating_types()
|
||||
AuthoringData.data["type_thumbnail"] = AuthoringData.type_thumbnail()
|
||||
|
||||
|
||||
@@ -95,21 +64,8 @@ def update_type_class(self, context):
|
||||
AuthoringData.data["type_predefined_type"] = AuthoringData.type_predefined_type()
|
||||
|
||||
|
||||
def update_ifc_class_browser(self, context):
|
||||
if context.region is not None and context.region.type != "TOOL_HEADER":
|
||||
AuthoringData.load_ifc_classes()
|
||||
AuthoringData.load_relating_types_browser()
|
||||
if self.updating:
|
||||
return
|
||||
ifc_class = self.ifc_class_browser
|
||||
constr_class_info = AuthoringData.constr_class_info(ifc_class)
|
||||
|
||||
if constr_class_info is None or not constr_class_info.fully_loaded:
|
||||
AuthoringData.assetize_constr_class(ifc_class)
|
||||
|
||||
|
||||
def update_relating_type(self, context):
|
||||
AuthoringData.load_relating_types()
|
||||
def update_relating_type_id(self, context):
|
||||
AuthoringData.data["relating_type_id"] = AuthoringData.relating_type_id()
|
||||
AuthoringData.data["type_thumbnail"] = AuthoringData.type_thumbnail()
|
||||
|
||||
|
||||
@@ -117,81 +73,20 @@ def update_type_page(self, context):
|
||||
AuthoringData.data["paginated_relating_types"] = AuthoringData.paginated_relating_types()
|
||||
|
||||
|
||||
def update_relating_type_browser(self, context):
|
||||
AuthoringData.load_relating_types_browser()
|
||||
if not self.updating:
|
||||
update_icon_id(self, context, browser=True)
|
||||
|
||||
|
||||
def update_relating_type_by_name(self, context):
|
||||
AuthoringData.load_relating_types()
|
||||
relating_type_id = AuthoringData.relating_type_id_by_name(self.ifc_class, self.relating_type)
|
||||
if relating_type_id is not None:
|
||||
self.relating_type_id = relating_type_id
|
||||
|
||||
|
||||
def get_constr_class_info(props, ifc_class):
|
||||
return props.constr_classes[ifc_class] if ifc_class in props.constr_classes else None
|
||||
|
||||
|
||||
def update_preview_multiple(self, context):
|
||||
if context.region is not None and context.region.type != "TOOL_HEADER":
|
||||
if self.preview_multiple_constr_types:
|
||||
ifc_class = self.ifc_class
|
||||
constr_class_info = get_constr_class_info(self, ifc_class)
|
||||
if constr_class_info is None or not constr_class_info.fully_loaded:
|
||||
AuthoringData.assetize_constr_class(ifc_class)
|
||||
else:
|
||||
update_relating_type(self, context)
|
||||
|
||||
|
||||
class ConstrTypeInfo(PropertyGroup):
|
||||
name: bpy.props.StringProperty(name="Construction type ID")
|
||||
icon_id: bpy.props.IntProperty(name="Icon ID")
|
||||
object: bpy.props.PointerProperty(name="Object", type=bpy.types.Object)
|
||||
|
||||
|
||||
class ConstrClassInfo(PropertyGroup):
|
||||
name: bpy.props.StringProperty(name="Construction class")
|
||||
constr_types: bpy.props.CollectionProperty(type=ConstrTypeInfo)
|
||||
fully_loaded: bpy.props.BoolProperty(default=False)
|
||||
|
||||
|
||||
class ConstrBrowserState(PropertyGroup):
|
||||
cursor_x: bpy.props.IntProperty()
|
||||
cursor_y: bpy.props.IntProperty()
|
||||
window_x: bpy.props.IntProperty()
|
||||
window_y: bpy.props.IntProperty()
|
||||
far_away_x: bpy.props.IntProperty(default=10) # lower left corner to temporarily warp the mouse
|
||||
far_away_y: bpy.props.IntProperty(default=10) # useful to close popup operators
|
||||
updating: bpy.props.BoolProperty()
|
||||
update_delay: bpy.props.FloatProperty(default=3e-2)
|
||||
|
||||
|
||||
class BIMModelProperties(PropertyGroup):
|
||||
ifc_class: bpy.props.EnumProperty(items=get_ifc_class, name="Construction Class", update=update_ifc_class)
|
||||
ifc_class_browser: bpy.props.EnumProperty(
|
||||
items=get_ifc_class, name="Construction Class", update=update_ifc_class_browser
|
||||
)
|
||||
relating_type: bpy.props.StringProperty(update=update_relating_type_by_name)
|
||||
relating_type_id: bpy.props.EnumProperty(
|
||||
items=get_relating_type, name="Construction Type", update=update_relating_type
|
||||
)
|
||||
relating_type_id_browser: bpy.props.EnumProperty(
|
||||
items=get_relating_type_browser, name="Construction Type", update=update_relating_type_browser
|
||||
items=get_relating_type_id, name="Construction Type", update=update_relating_type_id
|
||||
)
|
||||
icon_id: bpy.props.IntProperty()
|
||||
preview_multiple_constr_types: bpy.props.BoolProperty(default=False, update=update_preview_multiple)
|
||||
updating: bpy.props.BoolProperty(default=False)
|
||||
occurrence_name_style: bpy.props.EnumProperty(
|
||||
items=[("CLASS", "By Class", ""), ("TYPE", "By Type", ""), ("CUSTOM", "Custom", "")],
|
||||
name="Occurrence Name Style",
|
||||
)
|
||||
occurrence_name_function: bpy.props.StringProperty(name="Occurrence Name Function")
|
||||
getter_enum = {"ifc_class": get_ifc_class, "relating_type": get_relating_type}
|
||||
constr_classes: bpy.props.CollectionProperty(type=ConstrClassInfo)
|
||||
constr_browser_state: bpy.props.PointerProperty(type=ConstrBrowserState)
|
||||
extrusion_depth: bpy.props.FloatProperty(default=42.0)
|
||||
getter_enum = {"ifc_class": get_ifc_class, "relating_type": get_relating_type_id}
|
||||
extrusion_depth: bpy.props.FloatProperty(default=42.0, subtype="DISTANCE")
|
||||
cardinal_point: bpy.props.EnumProperty(
|
||||
items=(
|
||||
# TODO: complain to buildingSMART
|
||||
@@ -218,7 +113,7 @@ class BIMModelProperties(PropertyGroup):
|
||||
name="Cardinal Point",
|
||||
default="5",
|
||||
)
|
||||
length: bpy.props.FloatProperty(default=42.0)
|
||||
length: bpy.props.FloatProperty(default=42.0, subtype="DISTANCE")
|
||||
openings: bpy.props.CollectionProperty(type=ObjProperty)
|
||||
x: bpy.props.FloatProperty(name="X", default=0.5)
|
||||
y: bpy.props.FloatProperty(name="Y", default=0.5)
|
||||
@@ -246,13 +141,40 @@ class BIMModelProperties(PropertyGroup):
|
||||
type_class: bpy.props.EnumProperty(items=get_type_class, name="IFC Class", update=update_type_class)
|
||||
type_predefined_type: bpy.props.EnumProperty(items=get_type_predefined_type, name="Predefined Type", default=None)
|
||||
type_name: bpy.props.StringProperty(name="Name", default="TYPEX")
|
||||
decorator_color_selected: bpy.props.FloatVectorProperty(
|
||||
name="Selected Elements Color",
|
||||
subtype="COLOR",
|
||||
default=(0.545, 0.863, 0, 1), # green
|
||||
min=0.0,
|
||||
max=1.0,
|
||||
size=4,
|
||||
description="Color of selected verts/edges (used in profile editing mode)",
|
||||
)
|
||||
decorator_color_unselected: bpy.props.FloatVectorProperty(
|
||||
name="Not Selected Elements Color",
|
||||
subtype="COLOR",
|
||||
default=(1, 1, 1, 1), # green
|
||||
min=0.0,
|
||||
max=1.0,
|
||||
size=4,
|
||||
description="Color of not selected verts/edges (used in profile editing mode)",
|
||||
)
|
||||
decorator_color_special: bpy.props.FloatVectorProperty(
|
||||
name="Special Elements Color",
|
||||
subtype="COLOR",
|
||||
default=(0.157, 0.565, 1, 1), # blue
|
||||
min=0.0,
|
||||
max=1.0,
|
||||
size=4,
|
||||
description="Color of special selected verts/edges (openings, preview verts/edges in roof editing, verts with arcs/circles in profile editing)",
|
||||
)
|
||||
|
||||
|
||||
class BIMArrayProperties(PropertyGroup):
|
||||
is_editing: bpy.props.IntProperty(
|
||||
default=-1, description="Currently edited array index. -1 if not in array editing mode."
|
||||
)
|
||||
count: bpy.props.IntProperty(name="Count", default=0)
|
||||
count: bpy.props.IntProperty(name="Count", default=0, min=0)
|
||||
x: bpy.props.FloatProperty(name="X", default=0)
|
||||
y: bpy.props.FloatProperty(name="Y", default=0)
|
||||
z: bpy.props.FloatProperty(name="Z", default=0)
|
||||
@@ -261,6 +183,16 @@ class BIMArrayProperties(PropertyGroup):
|
||||
description="Use local space for array items offset instead of world space",
|
||||
default=True,
|
||||
)
|
||||
method: bpy.props.EnumProperty(
|
||||
items=(("OFFSET", "Offset", ""), ("DISTRIBUTE", "Distribute", "")),
|
||||
name="Method",
|
||||
default="OFFSET",
|
||||
)
|
||||
sync_children: bpy.props.BoolProperty(
|
||||
name="Sync Children",
|
||||
description="Regenerate all children based on the parent object",
|
||||
default=False,
|
||||
)
|
||||
|
||||
|
||||
class BIMStairProperties(PropertyGroup):
|
||||
@@ -342,6 +274,7 @@ class BIMWindowProperties(PropertyGroup):
|
||||
)
|
||||
|
||||
# number of panels and default mullion/transom values
|
||||
# fmt: off
|
||||
window_types_panels = {
|
||||
"SINGLE_PANEL": (1, ((0, 0 ), (0, 0 ))),
|
||||
"DOUBLE_PANEL_HORIZONTAL": (2, ((0, 0 ), (0.45, 0 ))),
|
||||
@@ -353,6 +286,7 @@ class BIMWindowProperties(PropertyGroup):
|
||||
"TRIPLE_PANEL_HORIZONTAL": (3, ((0, 0 ), (0.3, 0.6))),
|
||||
"TRIPLE_PANEL_VERTICAL": (3, ((0.2, 0.4), (0, 0 ))),
|
||||
}
|
||||
# fmt: on
|
||||
|
||||
window_added_previously: bpy.props.BoolProperty(default=False)
|
||||
is_editing: bpy.props.IntProperty(default=-1)
|
||||
@@ -455,7 +389,9 @@ class BIMDoorProperties(PropertyGroup):
|
||||
("DOUBLE_SWING_LEFT", "DOUBLE_SWING_LEFT", ""),
|
||||
("DOUBLE_SWING_RIGHT", "DOUBLE_SWING_RIGHT", ""),
|
||||
("DOUBLE_DOOR_SINGLE_SWING", "DOUBLE_DOOR_SINGLE_SWING", ""),
|
||||
("DOUBLE_DOOR_DOUBLE_SWING", "DOUBLE_DOOR_DOUBLE_SWING", ""),
|
||||
("SLIDING_TO_LEFT", "SLIDING_TO_LEFT", ""),
|
||||
("SLIDING_TO_RIGHT", "SLIDING_TO_RIGHT", ""),
|
||||
("DOUBLE_DOOR_SLIDING", "DOUBLE_DOOR_SLIDING", ""),
|
||||
)
|
||||
|
||||
door_added_previously: bpy.props.BoolProperty(default=False)
|
||||
@@ -525,10 +461,12 @@ class BIMDoorProperties(PropertyGroup):
|
||||
"lining_depth": self.lining_depth,
|
||||
"lining_thickness": self.lining_thickness,
|
||||
"lining_offset": self.lining_offset,
|
||||
"lining_to_panel_offset_x": self.lining_to_panel_offset_x,
|
||||
"lining_to_panel_offset_y": self.lining_to_panel_offset_y,
|
||||
}
|
||||
|
||||
if "SLIDING" not in self.door_type:
|
||||
kwargs["lining_to_panel_offset_x"] = self.lining_to_panel_offset_x
|
||||
kwargs["lining_to_panel_offset_y"] = self.lining_to_panel_offset_y
|
||||
|
||||
kwargs["transom_thickness"] = self.transom_thickness
|
||||
if self.transom_thickness:
|
||||
kwargs["transom_offset"] = self.transom_offset
|
||||
@@ -556,7 +494,18 @@ class BIMDoorProperties(PropertyGroup):
|
||||
|
||||
|
||||
class BIMRailingProperties(PropertyGroup):
|
||||
railing_types = (("FRAMELESS_PANEL", "FRAMELESS_PANEL", ""),)
|
||||
railing_types = (
|
||||
("FRAMELESS_PANEL", "FRAMELESS_PANEL", ""),
|
||||
("WALL_MOUNTED_HANDRAIL", "WALL_MOUNTED_HANDRAIL", ""),
|
||||
)
|
||||
cap_types = (
|
||||
("none", "none", ""),
|
||||
("180", "180", ""),
|
||||
("to_wall", "to_wall", ""),
|
||||
("to_floor", "to_floor", ""),
|
||||
("to_end_post", "to_end_post", ""),
|
||||
("to_end_post_and_floor", "to_end_post_and_floor", ""),
|
||||
)
|
||||
|
||||
railing_added_previously: bpy.props.BoolProperty(default=False)
|
||||
is_editing: bpy.props.IntProperty(default=-1)
|
||||
@@ -567,13 +516,42 @@ class BIMRailingProperties(PropertyGroup):
|
||||
thickness: bpy.props.FloatProperty(name="Thickness", default=0.050)
|
||||
spacing: bpy.props.FloatProperty(name="Spacing", default=0.050)
|
||||
|
||||
# wall mounted handrail specific properties
|
||||
use_manual_supports: bpy.props.BoolProperty(
|
||||
name="Use Manual Supports",
|
||||
default=False,
|
||||
description="If enabled, supports are added on every vertex on the edges of the railing path.\n"
|
||||
"If disabled, supports are added automatically based on the support spacing",
|
||||
)
|
||||
support_spacing: bpy.props.FloatProperty(
|
||||
name="Support Spacing", default=1.0, description="Distance between supports if automatic supports are used"
|
||||
)
|
||||
railing_diameter: bpy.props.FloatProperty(name="Railing Diameter", default=0.050)
|
||||
clear_width: bpy.props.FloatProperty(
|
||||
name="Clear Width", default=0.040, description="Clear width between the railing and the wall"
|
||||
)
|
||||
terminal_type: bpy.props.EnumProperty(name="Terminal Type", items=cap_types, default="180")
|
||||
|
||||
def get_general_kwargs(self):
|
||||
return {
|
||||
base_kwargs = {
|
||||
"railing_type": self.railing_type,
|
||||
"height": self.height,
|
||||
"thickness": self.thickness,
|
||||
"spacing": self.spacing,
|
||||
}
|
||||
additional_kwargs = {}
|
||||
if self.railing_type == "FRAMELESS_PANEL":
|
||||
additional_kwargs = {
|
||||
"thickness": self.thickness,
|
||||
"spacing": self.spacing,
|
||||
}
|
||||
elif self.railing_type == "WALL_MOUNTED_HANDRAIL":
|
||||
additional_kwargs = {
|
||||
"railing_diameter": self.railing_diameter,
|
||||
"clear_width": self.clear_width,
|
||||
"use_manual_supports": self.use_manual_supports,
|
||||
"support_spacing": self.support_spacing,
|
||||
"terminal_type": self.terminal_type,
|
||||
}
|
||||
return base_kwargs | additional_kwargs
|
||||
|
||||
|
||||
class BIMRoofProperties(PropertyGroup):
|
||||
|
||||
@@ -18,14 +18,11 @@
|
||||
|
||||
|
||||
import bpy
|
||||
from bpy.types import Operator
|
||||
import bmesh
|
||||
|
||||
import ifcopenshell
|
||||
from ifcopenshell.util.shape_builder import V
|
||||
import blenderbim
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.core.geometry as core
|
||||
from blenderbim.bim.helper import convert_property_group_from_si
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.module.model.door import bm_sort_out_geom
|
||||
@@ -41,6 +38,15 @@ import json
|
||||
# https://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/lexical/IfcRailingType.htm
|
||||
|
||||
|
||||
NON_SI_RAILING_PROPS = (
|
||||
"is_editing",
|
||||
"railing_type",
|
||||
"railing_added_previously",
|
||||
"use_manual_supports",
|
||||
"terminal_type",
|
||||
)
|
||||
|
||||
|
||||
def bm_split_edge_at_offset(edge, offset):
|
||||
v0, v1 = edge.verts
|
||||
|
||||
@@ -82,7 +88,31 @@ def update_railing_modifier_ifc_data(context):
|
||||
"Height": props.height,
|
||||
},
|
||||
)
|
||||
tool.Ifc.edit(obj)
|
||||
|
||||
if props.railing_type == "WALL_MOUNTED_HANDRAIL":
|
||||
body = ifcopenshell.util.representation.get_context(ifc_file, "Model", "Body", "MODEL_VIEW")
|
||||
railing_path = [Vector(v) for v in RailingData.data["parameters"]["data_dict"]["path_data"]["verts"]]
|
||||
representation_data = {
|
||||
"railing_type": props.railing_type,
|
||||
"context": body,
|
||||
"railing_path": railing_path,
|
||||
"use_manual_supports": props.use_manual_supports,
|
||||
"support_spacing": props.support_spacing,
|
||||
"railing_diameter": props.railing_diameter,
|
||||
"clear_width": props.clear_width,
|
||||
"terminal_type": props.terminal_type,
|
||||
"height": props.height,
|
||||
}
|
||||
model_representation = ifcopenshell.api.run(
|
||||
"geometry.add_railing_representation", ifc_file, **representation_data
|
||||
)
|
||||
tool.Model.replace_object_ifc_representation(body, obj, model_representation)
|
||||
|
||||
# hacky way to ensure tha ifc representation won't get tessellated at project save
|
||||
IfcStore.edited_objs.discard(obj)
|
||||
|
||||
elif props.railing_type == "FRAMELESS_PANEL":
|
||||
tool.Ifc.edit(obj)
|
||||
|
||||
|
||||
def update_bbim_railing_pset(element, railing_data):
|
||||
@@ -118,77 +148,114 @@ def update_railing_modifier_bmesh(context):
|
||||
tool.Blender.apply_bmesh(obj.data, bm)
|
||||
return
|
||||
|
||||
# generating the entire railing
|
||||
height = props.height * si_conversion
|
||||
thickness = props.thickness * si_conversion
|
||||
spacing = props.spacing * si_conversion
|
||||
if props.railing_type != "FRAMELESS_PANEL":
|
||||
return
|
||||
|
||||
# spacing
|
||||
# split each edge in 3 segments by 0.5 * spacing by x-y plane
|
||||
main_edges = bm.edges[:]
|
||||
for main_edge in main_edges:
|
||||
bm_split_edge_at_offset(main_edge, spacing)
|
||||
def generate_frameless_panel_railing():
|
||||
# generating FRAMELESS_PANEL railing
|
||||
height = props.height * si_conversion
|
||||
thickness = props.thickness * si_conversion
|
||||
spacing = props.spacing * si_conversion
|
||||
|
||||
# thickness
|
||||
# keep track of translated verts so we won't translate the same
|
||||
# vert twice
|
||||
edge_dissolving_verts = []
|
||||
for main_edge in main_edges:
|
||||
v0, v1 = main_edge.verts
|
||||
edge_dissolving_verts.extend([v0, v1])
|
||||
# spacing
|
||||
# split each edge in 3 segments by 0.5 * spacing by x-y plane
|
||||
main_edges = bm.edges[:]
|
||||
for main_edge in main_edges:
|
||||
bm_split_edge_at_offset(main_edge, spacing)
|
||||
|
||||
edge_dir = ((v1.co - v0.co) * V(1, 1, 0)).normalized()
|
||||
ortho_vector = edge_dir.cross(V(0, 0, 1))
|
||||
# thickness
|
||||
# keep track of translated verts so we won't translate the same
|
||||
# vert twice
|
||||
edge_dissolving_verts = []
|
||||
for main_edge in main_edges:
|
||||
v0, v1 = main_edge.verts
|
||||
edge_dissolving_verts.extend([v0, v1])
|
||||
|
||||
extruded_geom = bmesh.ops.extrude_edge_only(bm, edges=[main_edge])["geom"]
|
||||
edge_dir = ((v1.co - v0.co) * V(1, 1, 0)).normalized()
|
||||
ortho_vector = edge_dir.cross(V(0, 0, 1))
|
||||
|
||||
extruded_geom = bmesh.ops.extrude_edge_only(bm, edges=[main_edge])["geom"]
|
||||
extruded_verts = bm_sort_out_geom(extruded_geom)["verts"]
|
||||
bmesh.ops.translate(bm, vec=ortho_vector * (-thickness / 2), verts=extruded_verts)
|
||||
|
||||
extruded_geom = bmesh.ops.extrude_edge_only(bm, edges=[main_edge])["geom"]
|
||||
extruded_verts = bm_sort_out_geom(extruded_geom)["verts"]
|
||||
bmesh.ops.translate(bm, vec=ortho_vector * (thickness / 2), verts=extruded_verts)
|
||||
|
||||
# dissolve middle edge
|
||||
bmesh.ops.dissolve_edges(bm, edges=[main_edge])
|
||||
|
||||
# height
|
||||
extruded_geom = bmesh.ops.extrude_face_region(bm, geom=bm.faces)["geom"]
|
||||
extruded_verts = bm_sort_out_geom(extruded_geom)["verts"]
|
||||
bmesh.ops.translate(bm, vec=ortho_vector * (-thickness / 2), verts=extruded_verts)
|
||||
extrusion_vector = Vector((0, 0, 1)) * height
|
||||
bmesh.ops.translate(bm, vec=extrusion_vector, verts=extruded_verts)
|
||||
|
||||
extruded_geom = bmesh.ops.extrude_edge_only(bm, edges=[main_edge])["geom"]
|
||||
extruded_verts = bm_sort_out_geom(extruded_geom)["verts"]
|
||||
bmesh.ops.translate(bm, vec=ortho_vector * (thickness / 2), verts=extruded_verts)
|
||||
# dissolve middle edges
|
||||
edges_to_dissolve = []
|
||||
verts_to_dissolve = []
|
||||
for v in edge_dissolving_verts:
|
||||
for e in v.link_edges:
|
||||
other_vert = e.other_vert(v)
|
||||
if other_vert in extruded_verts:
|
||||
edges_to_dissolve.append(e)
|
||||
verts_to_dissolve.append(other_vert)
|
||||
bmesh.ops.dissolve_edges(bm, edges=edges_to_dissolve)
|
||||
bmesh.ops.dissolve_verts(bm, verts=verts_to_dissolve)
|
||||
|
||||
# dissolve middle edge
|
||||
bmesh.ops.dissolve_edges(bm, edges=[main_edge])
|
||||
# to remove unnecessary verts in 0 spacing case
|
||||
bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.0001)
|
||||
|
||||
# height
|
||||
extruded_geom = bmesh.ops.extrude_face_region(bm, geom=bm.faces)["geom"]
|
||||
extruded_verts = bm_sort_out_geom(extruded_geom)["verts"]
|
||||
extrusion_vector = Vector((0, 0, 1)) * height
|
||||
bmesh.ops.translate(bm, vec=extrusion_vector, verts=extruded_verts)
|
||||
tool.Blender.apply_bmesh(obj.data, bm)
|
||||
|
||||
# dissolve middle edges
|
||||
edges_to_dissolve = []
|
||||
verts_to_dissolve = []
|
||||
for v in edge_dissolving_verts:
|
||||
for e in v.link_edges:
|
||||
other_vert = e.other_vert(v)
|
||||
if other_vert in extruded_verts:
|
||||
edges_to_dissolve.append(e)
|
||||
verts_to_dissolve.append(other_vert)
|
||||
bmesh.ops.dissolve_edges(bm, edges=edges_to_dissolve)
|
||||
bmesh.ops.dissolve_verts(bm, verts=verts_to_dissolve)
|
||||
|
||||
# to remove unnecessary verts in 0 spacing case
|
||||
bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.0001)
|
||||
|
||||
tool.Blender.apply_bmesh(obj.data, bm)
|
||||
generate_frameless_panel_railing()
|
||||
|
||||
|
||||
def get_path_data(obj):
|
||||
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
if obj.mode == "EDIT":
|
||||
# otherwise mesh may not contain all changes
|
||||
# added in edit mode
|
||||
obj.update_from_editmode()
|
||||
|
||||
mesh = obj.data
|
||||
path_data = dict()
|
||||
path_data["edges"] = [e.vertices[:] for e in mesh.edges]
|
||||
path_data["verts"] = [v.co / si_conversion for v in mesh.vertices]
|
||||
|
||||
if not path_data["edges"] or not path_data["verts"]:
|
||||
bm = tool.Blender.get_bmesh_for_mesh(obj.data)
|
||||
end_points = [v for v in bm.verts if len(v.link_edges) == 1]
|
||||
if not end_points:
|
||||
return None
|
||||
|
||||
# if we have some previous data then we try to match
|
||||
# start or end of the path with the previous path
|
||||
previous_data = False
|
||||
if previous_data:
|
||||
previous_start = previous_data[0]
|
||||
previous_end = previous_data[-1]
|
||||
|
||||
potential_start = min([(v, (v.co - previous_start).length) for v in end_points], key=lambda v_data: v_data[1])
|
||||
potential_end = min([(v, (v.co - previous_end).length) for v in end_points], key=lambda v_data: v_data[1])
|
||||
|
||||
if potential_start[1] < potential_end[1]:
|
||||
start_point = potential_start[0]
|
||||
else:
|
||||
start_point = next(v for v in end_points if v != potential_start[0])
|
||||
else:
|
||||
start_point = min(end_points, key=lambda v: v.index)
|
||||
|
||||
# walking through the path
|
||||
# to make sure all verts and in consequent order
|
||||
edge = start_point.link_edges[0]
|
||||
v = edge.other_vert(start_point)
|
||||
points = [start_point.co, v.co]
|
||||
segments = [(0, 1)]
|
||||
i = 2
|
||||
|
||||
other_edge = lambda edges, edge: next(e for e in edges if e != edge)
|
||||
|
||||
while len(link_edges := v.link_edges) != 1:
|
||||
link_edges = v.link_edges
|
||||
edge = other_edge(link_edges, edge)
|
||||
v = edge.other_vert(v)
|
||||
points.append(v.co)
|
||||
segments.append((i - 1, i))
|
||||
i += 1
|
||||
|
||||
path_data = {"edges": segments, "verts": [p / si_conversion for p in points]}
|
||||
|
||||
return path_data
|
||||
|
||||
|
||||
@@ -248,8 +315,7 @@ class AddRailing(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
# need to make sure all default props will have correct units
|
||||
if not props.railing_added_previously:
|
||||
skip_props = ("is_editing", "railing_type", "railing_added_previously")
|
||||
convert_property_group_from_si(props, skip_props=skip_props)
|
||||
convert_property_group_from_si(props, skip_props=NON_SI_RAILING_PROPS)
|
||||
|
||||
railing_data = props.get_general_kwargs()
|
||||
path_data = get_path_data(obj)
|
||||
@@ -289,8 +355,7 @@ class EnableEditingRailing(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
# need to make sure all props that weren't used before
|
||||
# will have correct units
|
||||
skip_props = ("is_editing", "railing_type", "railing_added_previously")
|
||||
skip_props += tuple(data.keys())
|
||||
skip_props = NON_SI_RAILING_PROPS + tuple(data.keys())
|
||||
convert_property_group_from_si(props, skip_props=skip_props)
|
||||
|
||||
props.is_editing = 1
|
||||
@@ -299,7 +364,7 @@ class EnableEditingRailing(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
class CancelEditingRailing(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.cancel_editing_railing"
|
||||
bl_label = "Cancel editing Railing"
|
||||
bl_label = "Cancel Editing Railing"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
def _execute(self, context):
|
||||
@@ -328,7 +393,7 @@ class CancelEditingRailing(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
class FinishEditingRailing(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.finish_editing_railing"
|
||||
bl_label = "Finish editing railing"
|
||||
bl_label = "Finish Editing Railing"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
def _execute(self, context):
|
||||
@@ -349,6 +414,37 @@ class FinishEditingRailing(bpy.types.Operator, tool.Ifc.Operator):
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class FlipRailingPathOrder(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.flip_railing_path_order"
|
||||
bl_label = "Flip Railing Path Order"
|
||||
bl_description = "Can be useful to maintain railing supports direction"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
obj = context.active_object
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
props = obj.BIMRailingProperties
|
||||
|
||||
if not RailingData.is_loaded:
|
||||
RailingData.load()
|
||||
path_data = RailingData.data["parameters"]["data_dict"]["path_data"]
|
||||
|
||||
# flip the vertex order and edges
|
||||
path_data["verts"] = path_data["verts"][::-1]
|
||||
last_vert_i = len(path_data["verts"]) - 1
|
||||
edges = []
|
||||
for edge in path_data["edges"][::-1]:
|
||||
edge = [abs(vi - last_vert_i) for vi in edge[::-1]]
|
||||
edges.append(edge)
|
||||
|
||||
railing_data = props.get_general_kwargs()
|
||||
railing_data["path_data"] = path_data
|
||||
|
||||
update_bbim_railing_pset(element, railing_data)
|
||||
update_railing_modifier_ifc_data(context)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class EnableEditingRailingPath(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.enable_editing_railing_path"
|
||||
bl_label = "Enable Editing Railing Path"
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
from bpy.types import Operator
|
||||
import bmesh
|
||||
|
||||
import ifcopenshell
|
||||
@@ -29,20 +28,18 @@ from blenderbim.bim.module.model.data import RoofData, refresh
|
||||
from blenderbim.bim.module.model.decorator import ProfileDecorator
|
||||
|
||||
import json
|
||||
from math import tan, radians, degrees, atan
|
||||
from math import tan, pi
|
||||
from mathutils import Vector, Matrix
|
||||
from bpypolyskel import bpypolyskel
|
||||
import shapely
|
||||
from pprint import pprint
|
||||
from itertools import chain
|
||||
from math import pi
|
||||
|
||||
# reference:
|
||||
# https://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/lexical/IfcRoof.htm
|
||||
# https://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/lexical/IfcRoofType.htm
|
||||
|
||||
|
||||
# create read only property in blender operator
|
||||
NON_SI_ROOF_PROPS = ("is_editing", "roof_type", "roof_added_previously", "generation_method", "angle")
|
||||
|
||||
|
||||
def float_is_zero(f):
|
||||
@@ -116,10 +113,13 @@ def generate_hiped_roof_bmesh(bm, mode="ANGLE", height=1.0, angle=pi / 18, mutat
|
||||
|
||||
original_geometry_data = dict()
|
||||
angle_layer = bm.edges.layers.float.get("BBIM_gable_roof_angles")
|
||||
separate_verts_layer = bm.edges.layers.int.get("BBIM_gable_roof_separate_verts")
|
||||
if angle_layer:
|
||||
original_geometry_data["edges"] = [(set(bm_get_indices(e.verts)), e[angle_layer]) for e in bm.edges]
|
||||
original_geometry_data["edges"] = [
|
||||
(set(bm_get_indices(e.verts)), e[angle_layer], e[separate_verts_layer]) for e in bm.edges
|
||||
]
|
||||
else:
|
||||
original_geometry_data["edges"] = [(set(bm_get_indices(e.verts)), None) for e in bm.edges]
|
||||
original_geometry_data["edges"] = [(set(bm_get_indices(e.verts)), None, None) for e in bm.edges]
|
||||
|
||||
original_geometry_data["verts"] = {v.index: v.co.copy() for v in bm.verts}
|
||||
footprint_z = bm.verts[:][0].co.z
|
||||
@@ -212,6 +212,8 @@ def generate_hiped_roof_bmesh(bm, mode="ANGLE", height=1.0, angle=pi / 18, mutat
|
||||
footprint_edges = []
|
||||
footprint_verts = set()
|
||||
verts_to_change = {}
|
||||
verts_to_rip = []
|
||||
bottom_chords_to_remove = []
|
||||
|
||||
# find footprint edges
|
||||
for edge in bm.edges:
|
||||
@@ -227,7 +229,7 @@ def generate_hiped_roof_bmesh(bm, mode="ANGLE", height=1.0, angle=pi / 18, mutat
|
||||
# iterate over edges from original geometry
|
||||
# if their angle was redefined by user - apply the changes to the related vertices
|
||||
# to match the requested angle
|
||||
for old_edge_verts, defined_angle in original_geometry_data["edges"]:
|
||||
for old_edge_verts, defined_angle, separate_verts in original_geometry_data["edges"]:
|
||||
if not defined_angle:
|
||||
continue
|
||||
|
||||
@@ -240,14 +242,38 @@ def generate_hiped_roof_bmesh(bm, mode="ANGLE", height=1.0, angle=pi / 18, mutat
|
||||
|
||||
verts_to_move = find_other_polygon_verts(identical_edge)
|
||||
for v in verts_to_move:
|
||||
vert_co = verts_to_change.get(v, v.co)
|
||||
new_vert_co = change_angle(vert_co, edge_verts_remaped, defined_angle)
|
||||
verts_to_change[v] = new_vert_co
|
||||
if not separate_verts:
|
||||
vert_co = verts_to_change.get(v, v.co)
|
||||
new_vert_co = change_angle(vert_co, edge_verts_remaped, defined_angle)
|
||||
verts_to_change[v] = new_vert_co
|
||||
else:
|
||||
vert_co = v.co
|
||||
new_vert_co = change_angle(vert_co, edge_verts_remaped, defined_angle)
|
||||
verts_to_rip.append([v, new_vert_co, identical_edge])
|
||||
|
||||
if defined_angle >= pi / 2:
|
||||
bottom_chords_to_remove.append(identical_edge)
|
||||
|
||||
def separate_vert(bm, vert, edge, new_co):
|
||||
face = next(f for f in vert.link_faces if edge in f.edges)
|
||||
new_v = bmesh.utils.face_vert_separate(face, vert)
|
||||
new_v.co = new_co
|
||||
new_edge = bm.edges.new((vert, new_v))
|
||||
|
||||
for cur_edge in face.edges:
|
||||
if cur_edge == edge:
|
||||
continue
|
||||
bmesh.ops.contextual_create(bm, geom=[new_edge, cur_edge])
|
||||
|
||||
# apply all changes once at the end
|
||||
for v in verts_to_change:
|
||||
v.co = verts_to_change[v]
|
||||
|
||||
for v, new_co, edge in verts_to_rip:
|
||||
separate_vert(bm, v, edge, new_co)
|
||||
|
||||
bmesh.ops.delete(bm, geom=bottom_chords_to_remove, context="EDGES")
|
||||
|
||||
extrusion_geom = bmesh.ops.extrude_face_region(bm, geom=bm.faces)["geom"]
|
||||
extruded_verts = bm_sort_out_geom(extrusion_geom)["verts"]
|
||||
bmesh.ops.translate(bm, vec=[0.0, 0.0, 0.1], verts=extruded_verts)
|
||||
@@ -289,9 +315,6 @@ def update_roof_modifier_ifc_data(context):
|
||||
# occurences attributes
|
||||
# occurences = tool.Ifc.get_all_element_occurences(element)
|
||||
|
||||
# TODO: add Qto_RoofBaseQuantities, need to calculate GrossArea, NetArea, ProjectedArea
|
||||
# https://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/lexical/Qto_RoofBaseQuantities.htm
|
||||
|
||||
tool.Ifc.edit(obj)
|
||||
|
||||
|
||||
@@ -311,12 +334,14 @@ def update_roof_modifier_bmesh(context):
|
||||
RoofData.load()
|
||||
path_data = RoofData.data["parameters"]["data_dict"]["path_data"]
|
||||
angle_layer_data = path_data.get("gable_roof_angles", None)
|
||||
separate_verts_data = path_data.get("gable_roof_separate_verts", None)
|
||||
|
||||
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
# need to make sure we support edit mode
|
||||
# since users will probably be in edit mode when they'll be changing roof path
|
||||
bm = tool.Blender.get_bmesh_for_mesh(obj.data, clean=True)
|
||||
angle_layer = bm.edges.layers.float.new("BBIM_gable_roof_angles")
|
||||
separate_verts_layer = bm.edges.layers.int.new("BBIM_gable_roof_separate_verts")
|
||||
|
||||
# generating roof path
|
||||
new_verts = [bm.verts.new(Vector(v) * si_conversion) for v in path_data["verts"]]
|
||||
@@ -325,6 +350,7 @@ def update_roof_modifier_bmesh(context):
|
||||
e = path_data["edges"][i]
|
||||
edge = bm.edges.new((new_verts[e[0]], new_verts[e[1]]))
|
||||
edge[angle_layer] = angle_layer_data[i] if angle_layer_data else 0
|
||||
edge[separate_verts_layer] = separate_verts_data[i] if separate_verts_data else 0
|
||||
new_edges.append(edge)
|
||||
|
||||
if props.is_editing_path:
|
||||
@@ -349,12 +375,15 @@ def get_path_data(obj):
|
||||
bm_mesh_clean_up(bm)
|
||||
|
||||
angle_layer = bm.edges.layers.float.get("BBIM_gable_roof_angles")
|
||||
separate_verts_layer = bm.edges.layers.int.get("BBIM_gable_roof_separate_verts")
|
||||
|
||||
path_data = dict()
|
||||
path_data["edges"] = [bm_get_indices(e.verts) for e in bm.edges]
|
||||
path_data["verts"] = [v.co / si_conversion for v in bm.verts]
|
||||
if angle_layer:
|
||||
path_data["gable_roof_angles"] = [e[angle_layer] for e in bm.edges]
|
||||
if separate_verts_layer:
|
||||
path_data["gable_roof_separate_verts"] = [e[separate_verts_layer] for e in bm.edges]
|
||||
|
||||
if not path_data["edges"] or not path_data["verts"]:
|
||||
return None
|
||||
@@ -417,8 +446,7 @@ class AddRoof(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
# need to make sure all default props will have correct units
|
||||
if not props.roof_added_previously:
|
||||
skip_props = ("is_editing", "roof_type", "roof_added_previously", "generation_method")
|
||||
convert_property_group_from_si(props, skip_props=skip_props)
|
||||
convert_property_group_from_si(props, skip_props=NON_SI_ROOF_PROPS)
|
||||
|
||||
roof_data = props.get_general_kwargs()
|
||||
path_data = get_path_data(obj)
|
||||
@@ -459,8 +487,7 @@ class EnableEditingRoof(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
# need to make sure all props that weren't used before
|
||||
# will have correct units
|
||||
skip_props = ("is_editing", "roof_type", "roof_added_previously", "generation_method")
|
||||
skip_props += tuple(data.keys())
|
||||
skip_props = NON_SI_ROOF_PROPS + tuple(data.keys())
|
||||
convert_property_group_from_si(props, skip_props=skip_props)
|
||||
|
||||
props.is_editing = 1
|
||||
@@ -469,7 +496,7 @@ class EnableEditingRoof(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
class CancelEditingRoof(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.cancel_editing_roof"
|
||||
bl_label = "Cancel editing Roof"
|
||||
bl_label = "Cancel Editing Roof"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
def _execute(self, context):
|
||||
@@ -498,7 +525,7 @@ class CancelEditingRoof(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
class FinishEditingRoof(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.finish_editing_roof"
|
||||
bl_label = "Finish editing roof"
|
||||
bl_label = "Finish Editing Roof"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
def _execute(self, context):
|
||||
@@ -633,9 +660,10 @@ class RemoveRoof(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
class SetGableRoofEdgeAngle(bpy.types.Operator):
|
||||
bl_idname = "bim.set_gable_roof_edge_angle"
|
||||
bl_label = "Set gable roof edge angle"
|
||||
bl_label = "Set Gable Roof Edge Angle"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
angle: bpy.props.FloatProperty(name="Angle", default=90)
|
||||
separate_verts: bpy.props.BoolProperty(name="Separate Verts", default=True)
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
@@ -658,12 +686,17 @@ class SetGableRoofEdgeAngle(bpy.types.Operator):
|
||||
if "BBIM_gable_roof_angles" not in me.attributes:
|
||||
me.attributes.new("BBIM_gable_roof_angles", type="FLOAT", domain="EDGE")
|
||||
|
||||
if "BBIM_gable_roof_separate_verts" not in me.attributes:
|
||||
me.attributes.new("BBIM_gable_roof_separate_verts", type="INT", domain="EDGE")
|
||||
|
||||
angles_layer = bm.edges.layers.float["BBIM_gable_roof_angles"]
|
||||
separate_verts_layer = bm.edges.layers.int["BBIM_gable_roof_separate_verts"]
|
||||
|
||||
for e in bm.edges:
|
||||
if not e.select:
|
||||
continue
|
||||
e[angles_layer] = self.angle
|
||||
e[separate_verts_layer] = self.separate_verts
|
||||
|
||||
tool.Blender.apply_bmesh(me, bm)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -48,8 +48,3 @@ def sync_name(usecase_path, ifc_file, settings):
|
||||
collection.name = new_name
|
||||
obj.name = new_name
|
||||
blenderbim.bim.handler.refresh_ui_data()
|
||||
|
||||
|
||||
class ConstrTypeEntityNotFound(Exception):
|
||||
pass
|
||||
|
||||
|
||||
@@ -238,11 +238,11 @@ class DumbSlabPlaner:
|
||||
if not rel.is_a("IfcRelAssociatesMaterial"):
|
||||
continue
|
||||
for element in rel.RelatedObjects:
|
||||
self.change_thickness(element, thickness)
|
||||
self.change_thickness(element, total_thickness)
|
||||
else:
|
||||
for rel in inverse.AssociatedTo:
|
||||
for element in rel.RelatedObjects:
|
||||
self.change_thickness(element, thickness)
|
||||
self.change_thickness(element, total_thickness)
|
||||
|
||||
def regenerate_from_type(self, usecase_path, ifc_file, settings):
|
||||
obj = tool.Ifc.get_object(settings["related_object"])
|
||||
@@ -604,6 +604,7 @@ class EnableEditingExtrusionProfile(bpy.types.Operator, tool.Ifc.Operator):
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
|
||||
body = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
|
||||
body = ifcopenshell.util.representation.resolve_representation(body)
|
||||
extrusion = tool.Model.get_extrusion(body)
|
||||
|
||||
if extrusion.Position:
|
||||
@@ -636,8 +637,9 @@ class EditExtrusionProfile(bpy.types.Operator, tool.Ifc.Operator):
|
||||
obj = context.active_object
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
|
||||
representation = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
|
||||
extrusion = tool.Model.get_extrusion(representation)
|
||||
body = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
|
||||
body = ifcopenshell.util.representation.resolve_representation(body)
|
||||
extrusion = tool.Model.get_extrusion(body)
|
||||
if extrusion.Position:
|
||||
position = Matrix(ifcopenshell.util.placement.get_axis2placement(extrusion.Position).tolist())
|
||||
position[0][3] *= self.unit_scale
|
||||
@@ -670,37 +672,42 @@ class EditExtrusionProfile(bpy.types.Operator, tool.Ifc.Operator):
|
||||
tool.Ifc,
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=representation,
|
||||
representation=body,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
bpy.data.meshes.remove(profile_mesh)
|
||||
|
||||
# Only certain classes should have a footprint
|
||||
if element.is_a() not in ("IfcSlab", "IfcRamp"):
|
||||
return
|
||||
|
||||
footprint_context = ifcopenshell.util.representation.get_context(
|
||||
tool.Ifc.get(), "Plan", "FootPrint", "SKETCH_VIEW"
|
||||
)
|
||||
if footprint_context:
|
||||
curves = [profile.OuterCurve]
|
||||
if profile.is_a("IfcArbitraryProfileDefWithVoids"):
|
||||
curves.extend(profile.InnerCurves)
|
||||
new_footprint = ifcopenshell.api.run(
|
||||
"geometry.add_footprint_representation", tool.Ifc.get(), context=footprint_context, curves=curves
|
||||
if not footprint_context:
|
||||
return
|
||||
|
||||
curves = [profile.OuterCurve]
|
||||
if profile.is_a("IfcArbitraryProfileDefWithVoids"):
|
||||
curves.extend(profile.InnerCurves)
|
||||
new_footprint = ifcopenshell.api.run(
|
||||
"geometry.add_footprint_representation", tool.Ifc.get(), context=footprint_context, curves=curves
|
||||
)
|
||||
old_footprint = ifcopenshell.util.representation.get_representation(
|
||||
element, "Plan", "FootPrint", "SKETCH_VIEW"
|
||||
)
|
||||
if old_footprint:
|
||||
for inverse in tool.Ifc.get().get_inverse(old_footprint):
|
||||
ifcopenshell.util.element.replace_attribute(inverse, old_footprint, new_footprint)
|
||||
blenderbim.core.geometry.remove_representation(
|
||||
tool.Ifc, tool.Geometry, obj=obj, representation=old_footprint
|
||||
)
|
||||
old_footprint = ifcopenshell.util.representation.get_representation(
|
||||
element, "Plan", "FootPrint", "SKETCH_VIEW"
|
||||
else:
|
||||
ifcopenshell.api.run(
|
||||
"geometry.assign_representation", tool.Ifc.get(), product=element, representation=new_footprint
|
||||
)
|
||||
if old_footprint:
|
||||
for inverse in tool.Ifc.get().get_inverse(old_footprint):
|
||||
ifcopenshell.util.element.replace_attribute(inverse, old_footprint, new_footprint)
|
||||
blenderbim.core.geometry.remove_representation(
|
||||
tool.Ifc, tool.Geometry, obj=obj, representation=old_footprint
|
||||
)
|
||||
else:
|
||||
ifcopenshell.api.run(
|
||||
"geometry.assign_representation", tool.Ifc.get(), product=element, representation=new_footprint
|
||||
)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class ResetVertex(bpy.types.Operator):
|
||||
|
||||
@@ -287,6 +287,22 @@ def update_ifc_stair_props(obj):
|
||||
)
|
||||
tool.Ifc.edit(obj)
|
||||
|
||||
# update related annotation objects
|
||||
def get_elements_from_product(product):
|
||||
elements = []
|
||||
for rel in product.ReferencedBy:
|
||||
if not rel.is_a("IfcRelAssignsToProduct"):
|
||||
continue
|
||||
elements.extend(rel.RelatedObjects)
|
||||
return elements
|
||||
|
||||
stair_obj = obj
|
||||
for rel_element in get_elements_from_product(element):
|
||||
if not rel_element.is_a("IfcAnnotation") or rel_element.ObjectType != "STAIR_ARROW":
|
||||
continue
|
||||
if annotation_obj := tool.Ifc.get_object(rel_element):
|
||||
tool.Drawing.setup_annotation_object(annotation_obj, "STAIR_ARROW", stair_obj)
|
||||
|
||||
|
||||
class BIM_OT_add_clever_stair(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "mesh.add_clever_stair"
|
||||
@@ -368,7 +384,7 @@ class AddStair(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
class CancelEditingStair(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.cancel_editing_stair"
|
||||
bl_label = "Cancel editing Stair"
|
||||
bl_label = "Cancel Editing Stair"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
def _execute(self, context):
|
||||
@@ -388,7 +404,7 @@ class CancelEditingStair(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
class FinishEditingStair(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.finish_editing_stair"
|
||||
bl_label = "Finish editing stair"
|
||||
bl_label = "Finish Editing Stair"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
def _execute(self, context):
|
||||
|
||||
@@ -182,7 +182,7 @@ class UpdateDataFromSverchok(bpy.types.Operator, tool.Ifc.Operator):
|
||||
# removed the part that was relying on node graph to be opened at execution
|
||||
class ImportSverchokGraph(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.import_sverchok_graph"
|
||||
bl_label = "Import sverchok graph"
|
||||
bl_label = "Import Sverchok Graph"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
filepath: bpy.props.StringProperty(
|
||||
@@ -224,7 +224,7 @@ class ImportSverchokGraph(bpy.types.Operator, tool.Ifc.Operator):
|
||||
# removed the part that was relying on node graph to be opened at execution
|
||||
class ExportSverchokGraph(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.export_sverchok_graph"
|
||||
bl_label = "Export sverchok graph"
|
||||
bl_label = "Export Sverchok Graph"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
filepath: bpy.props.StringProperty(
|
||||
|
||||
@@ -137,59 +137,6 @@ class BIM_PT_authoring(Panel):
|
||||
row.operator("bim.align_wall", icon="ANCHOR_BOTTOM", text="Int.").align_type = "INTERIOR"
|
||||
|
||||
|
||||
class HelpConstrTypes(Operator):
|
||||
bl_idname = "bim.help_relating_types"
|
||||
bl_label = "Construction Types Help"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Click to read some contextual help"
|
||||
|
||||
def execute(self, context):
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
return context.window_manager.invoke_popup(self, width=510)
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.row().separator(factor=0.5)
|
||||
row = layout.row()
|
||||
row.alignment = "CENTER"
|
||||
row.label(text="BlenderBIM Help", icon="BLENDER")
|
||||
layout.row().separator(factor=0.5)
|
||||
|
||||
row = layout.row().row()
|
||||
row.label(text="Overview:", icon="KEYTYPE_MOVING_HOLD_VEC")
|
||||
self.draw_lines(layout, self.message_summary)
|
||||
layout.row().separator()
|
||||
|
||||
row = layout.row().row()
|
||||
row.label(text="Further support:", icon="KEYTYPE_MOVING_HOLD_VEC")
|
||||
layout.row().separator(factor=0.5)
|
||||
row = layout.row()
|
||||
op = row.operator("bim.open_upstream", text="Homepage", icon="HOME")
|
||||
op.page = "home"
|
||||
op = row.operator("bim.open_upstream", text="Docs", icon="DOCUMENTS")
|
||||
op.page = "docs"
|
||||
op = row.operator("bim.open_upstream", text="Wiki", icon="CURRENT_FILE")
|
||||
op.page = "wiki"
|
||||
op = row.operator("bim.open_upstream", text="Community", icon="COMMUNITY")
|
||||
op.page = "community"
|
||||
layout.row().separator()
|
||||
|
||||
def draw_lines(self, layout, lines):
|
||||
box = layout.box()
|
||||
for line in lines:
|
||||
row = box.row()
|
||||
row.label(text=f" {line}")
|
||||
|
||||
@property
|
||||
def message_summary(self):
|
||||
return [
|
||||
"The Construction Type Browser allows to preview and add new instances to the model.",
|
||||
"For further support, please click on the Documentation link below.",
|
||||
]
|
||||
|
||||
|
||||
class BIM_PT_array(bpy.types.Panel):
|
||||
bl_label = "IFC Array"
|
||||
bl_idname = "BIM_PT_array"
|
||||
@@ -224,21 +171,29 @@ class BIM_PT_array(bpy.types.Panel):
|
||||
row.operator("bim.edit_array", icon="CHECKMARK", text="").item = i
|
||||
row.operator("bim.disable_editing_array", icon="CANCEL", text="")
|
||||
row = box.row(align=True)
|
||||
row.prop(props, "use_local_space")
|
||||
row.prop(props, "method")
|
||||
row = box.row(align=True)
|
||||
row.prop(props, "use_local_space")
|
||||
row.prop(props, "sync_children")
|
||||
col = box.column()
|
||||
row = col.row(align=True)
|
||||
row.prop(props, "x")
|
||||
row.operator("bim.input_cursor_x_array", icon="CURSOR", text="")
|
||||
row = col.row(align=True)
|
||||
row.prop(props, "y")
|
||||
row.operator("bim.input_cursor_y_array", icon="CURSOR", text="")
|
||||
row = col.row(align=True)
|
||||
row.prop(props, "z")
|
||||
|
||||
row.operator("bim.input_cursor_z_array", icon="CURSOR", text="")
|
||||
else:
|
||||
row = box.row(align=True)
|
||||
row.label(text=f"{array['count']} Items", icon="MOD_ARRAY")
|
||||
name = f"{array['count']} Items ({array.get('method', 'OFFSET').capitalize()})"
|
||||
row.label(text=name, icon="MOD_ARRAY")
|
||||
row.operator("bim.enable_editing_array", icon="GREASEPENCIL", text="").item = i
|
||||
row.operator("bim.remove_array", icon="X", text="").item = i
|
||||
row = box.row(align=True)
|
||||
row.label(text=f"Use Local Space: {array.get('use_local_space', False)}")
|
||||
row = box.row(align=True)
|
||||
row.label(text=f"X: {array['x']}")
|
||||
icon = "EMPTY_ARROWS" if array.get("use_local_space", False) else "EMPTY_AXIS"
|
||||
row.label(text=f"X: {array['x']}", icon=icon)
|
||||
row.label(text=f"Y: {array['y']}")
|
||||
row.label(text=f"Z: {array['z']}")
|
||||
else:
|
||||
@@ -273,7 +228,7 @@ class BIM_PT_stair(bpy.types.Panel):
|
||||
stair_data = StairData.data["parameters"]["data_dict"]
|
||||
if props.is_editing != -1:
|
||||
row = self.layout.row(align=True)
|
||||
row.operator("bim.finish_editing_stair", icon="CHECKMARK", text="Finish editing")
|
||||
row.operator("bim.finish_editing_stair", icon="CHECKMARK", text="Finish Editing")
|
||||
row.operator("bim.cancel_editing_stair", icon="CANCEL", text="")
|
||||
row = self.layout.row(align=True)
|
||||
for prop_name in props.get_props_kwargs():
|
||||
@@ -373,7 +328,7 @@ class BIM_PT_window(bpy.types.Panel):
|
||||
|
||||
if props.is_editing != -1:
|
||||
row = self.layout.row(align=True)
|
||||
row.operator("bim.finish_editing_window", icon="CHECKMARK", text="Finish editing")
|
||||
row.operator("bim.finish_editing_window", icon="CHECKMARK", text="Finish Editing")
|
||||
row.operator("bim.cancel_editing_window", icon="CANCEL", text="")
|
||||
|
||||
general_props = props.get_general_kwargs()
|
||||
@@ -488,7 +443,7 @@ class BIM_PT_door(bpy.types.Panel):
|
||||
|
||||
if props.is_editing != -1:
|
||||
row = self.layout.row(align=True)
|
||||
row.operator("bim.finish_editing_door", icon="CHECKMARK", text="Finish editing")
|
||||
row.operator("bim.finish_editing_door", icon="CHECKMARK", text="Finish Editing")
|
||||
row.operator("bim.cancel_editing_door", icon="CANCEL", text="")
|
||||
|
||||
general_props = props.get_general_kwargs()
|
||||
@@ -572,7 +527,7 @@ class BIM_PT_railing(bpy.types.Panel):
|
||||
|
||||
if props.is_editing != -1:
|
||||
row = self.layout.row(align=True)
|
||||
row.operator("bim.finish_editing_railing", icon="CHECKMARK", text="Finish editing")
|
||||
row.operator("bim.finish_editing_railing", icon="CHECKMARK", text="Finish Editing")
|
||||
row.operator("bim.cancel_editing_railing", icon="CANCEL", text="")
|
||||
|
||||
general_props = props.get_general_kwargs()
|
||||
@@ -588,6 +543,9 @@ class BIM_PT_railing(bpy.types.Panel):
|
||||
else:
|
||||
row.operator("bim.enable_editing_railing", icon="GREASEPENCIL", text="")
|
||||
row.operator("bim.enable_editing_railing_path", icon="ANIM", text="")
|
||||
# TODO: good for preview but probably should move to .is_editing == -1
|
||||
# since it's writing to ifc
|
||||
row.operator("bim.flip_railing_path_order", icon="ARROW_LEFTRIGHT", text="")
|
||||
row.operator("bim.remove_railing", icon="X", text="")
|
||||
|
||||
box = self.layout.box()
|
||||
@@ -631,7 +589,7 @@ class BIM_PT_roof(bpy.types.Panel):
|
||||
|
||||
if props.is_editing != -1:
|
||||
row = self.layout.row(align=True)
|
||||
row.operator("bim.finish_editing_roof", icon="CHECKMARK", text="Finish editing")
|
||||
row.operator("bim.finish_editing_roof", icon="CHECKMARK", text="Finish Editing")
|
||||
row.operator("bim.cancel_editing_roof", icon="CANCEL", text="")
|
||||
|
||||
general_props = props.get_general_kwargs()
|
||||
@@ -656,7 +614,7 @@ class BIM_PT_roof(bpy.types.Panel):
|
||||
prop_value = round(prop_value, 5) if type(prop_value) is float else prop_value
|
||||
row = box.row(align=True)
|
||||
row.label(text=f"{props.bl_rna.properties[prop].name}")
|
||||
if prop == 'angle':
|
||||
if prop == "angle":
|
||||
prop_value = round(degrees(prop_value), 2)
|
||||
row.label(text=str(prop_value))
|
||||
else:
|
||||
|
||||
@@ -37,38 +37,6 @@ from mathutils import Vector, Matrix
|
||||
from blenderbim.bim.module.model.opening import FilledOpeningGenerator
|
||||
|
||||
|
||||
def element_listener(element, obj):
|
||||
blenderbim.bim.handler.subscribe_to(obj, "mode", mode_callback)
|
||||
|
||||
|
||||
def mode_callback(obj, data):
|
||||
for obj in set(bpy.context.selected_objects + [bpy.context.active_object]):
|
||||
if (
|
||||
not obj.data
|
||||
or not isinstance(obj.data, (bpy.types.Mesh, bpy.types.Curve, bpy.types.TextCurve))
|
||||
or not obj.BIMObjectProperties.ifc_definition_id
|
||||
or not bpy.context.scene.BIMProjectProperties.is_authoring
|
||||
):
|
||||
return
|
||||
product = IfcStore.get_file().by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
parametric = ifcopenshell.util.element.get_psets(product).get("EPset_Parametric")
|
||||
if not parametric or parametric["Engine"] != "BlenderBIM.DumbLayer2":
|
||||
return
|
||||
if obj.mode == "EDIT":
|
||||
tool.Ifc.edit(obj)
|
||||
bm = bmesh.from_edit_mesh(obj.data)
|
||||
bmesh.ops.dissolve_limit(bm, angle_limit=pi / 180 * 1, verts=bm.verts, edges=bm.edges)
|
||||
bmesh.update_edit_mesh(obj.data)
|
||||
else:
|
||||
new_origin = obj.matrix_world @ Vector(obj.bound_box[0])
|
||||
obj.data.transform(
|
||||
Matrix.Translation(
|
||||
(obj.matrix_world.inverted().to_quaternion() @ (obj.matrix_world.translation - new_origin))
|
||||
)
|
||||
)
|
||||
obj.matrix_world.translation = new_origin
|
||||
|
||||
|
||||
class JoinWall(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.join_wall"
|
||||
bl_label = "Join Wall"
|
||||
@@ -239,6 +207,7 @@ class ChangeExtrusionDepth(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
def _execute(self, context):
|
||||
layer2_objs = []
|
||||
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
for obj in context.selected_objects:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element:
|
||||
@@ -251,7 +220,7 @@ class ChangeExtrusionDepth(bpy.types.Operator, tool.Ifc.Operator):
|
||||
return
|
||||
x, y, z = extrusion.ExtrudedDirection.DirectionRatios
|
||||
x_angle = Vector((0, 1)).angle_signed(Vector((y, z)))
|
||||
extrusion.Depth = self.depth * (1 / cos(x_angle))
|
||||
extrusion.Depth = self.depth / si_conversion * (1 / cos(x_angle))
|
||||
if tool.Model.get_usage_type(element) == "LAYER2":
|
||||
for rel in element.ConnectedFrom:
|
||||
if rel.is_a() == "IfcRelConnectsElements":
|
||||
@@ -1028,7 +997,7 @@ class DumbWallJoiner:
|
||||
|
||||
self.recreate_wall(element1, wall1, axis, body)
|
||||
|
||||
def set_length(self, wall1, length):
|
||||
def set_length(self, wall1, si_length):
|
||||
element1 = tool.Ifc.get_entity(wall1)
|
||||
if not element1:
|
||||
return
|
||||
@@ -1038,8 +1007,6 @@ class DumbWallJoiner:
|
||||
axis1 = tool.Model.get_wall_axis(wall1)
|
||||
axis = copy.deepcopy(axis1["reference"])
|
||||
body = copy.deepcopy(axis1["reference"])
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
si_length = unit_scale * length
|
||||
end = (wall1.matrix_world @ Vector((si_length, 0, 0))).to_2d()
|
||||
axis[1] = end
|
||||
body[1] = end
|
||||
|
||||
@@ -69,10 +69,12 @@ def update_simple_openings(element, opening_width, opening_height):
|
||||
context = ifcopenshell.util.representation.get_context(ifc_file, "Model", "Body", "MODEL_VIEW")
|
||||
|
||||
extrusion = shape_builder.extrude(
|
||||
shape_builder.rectangle(size=Vector([opening_width, 0.0, opening_height])),
|
||||
shape_builder.rectangle(size=Vector([opening_width, 0.0, opening_height]).xz),
|
||||
magnitude=thickness / unit_scale,
|
||||
position=Vector([0.0, -0.1 / unit_scale, 0.0]),
|
||||
extrusion_vector=Vector([0.0, 1.0, 0.0]),
|
||||
position_x_axis=V(1, 0, 0),
|
||||
position_z_axis=V(0, -1, 0),
|
||||
extrusion_vector=V(0, 0, -1),
|
||||
)
|
||||
|
||||
new_representation = shape_builder.get_representation(context, extrusion)
|
||||
@@ -480,7 +482,7 @@ class AddWindow(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
class CancelEditingWindow(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.cancel_editing_window"
|
||||
bl_label = "Cancel editing Window"
|
||||
bl_label = "Cancel Editing Window"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
def _execute(self, context):
|
||||
@@ -508,7 +510,7 @@ class CancelEditingWindow(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
class FinishEditingWindow(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.finish_editing_window"
|
||||
bl_label = "Finish editing window"
|
||||
bl_label = "Finish Editing Window"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
def _execute(self, context):
|
||||
|
||||
@@ -68,7 +68,17 @@ class BimTool(WorkSpaceTool):
|
||||
|
||||
|
||||
def add_layout_hotkey_operator(layout, text, hotkey, description):
|
||||
op = layout.operator("bim.hotkey", text=text)
|
||||
modifiers = {
|
||||
"A": "EVENT_ALT",
|
||||
"S": "EVENT_SHIFT",
|
||||
}
|
||||
modifier, key = hotkey.split("_")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.label(text="", icon=modifiers[modifier])
|
||||
row.label(text="", icon=f"EVENT_{key}")
|
||||
|
||||
op = row.operator("bim.hotkey", text=text)
|
||||
op.hotkey = hotkey
|
||||
op.description = description
|
||||
return op
|
||||
@@ -101,7 +111,7 @@ class BimToolUI:
|
||||
|
||||
@classmethod
|
||||
def draw_create_object_interface(cls):
|
||||
if not AuthoringData.data["relating_types_ids"]:
|
||||
if not AuthoringData.data["relating_type_id"]:
|
||||
return
|
||||
if cls.props.ifc_class == "IfcWallType":
|
||||
row = cls.layout.row(align=True)
|
||||
@@ -132,10 +142,7 @@ class BimToolUI:
|
||||
row = cls.layout.row(align=True)
|
||||
row.prop(data=cls.props, property="rl2", text="RL")
|
||||
elif cls.props.ifc_class in ("IfcSpaceType"):
|
||||
row = cls.layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_G")
|
||||
add_layout_hotkey_operator(row, "Generate", "S_G", bpy.ops.bim.generate_space.__doc__)
|
||||
add_layout_hotkey_operator(cls.layout, "Generate", "S_G", bpy.ops.bim.generate_space.__doc__)
|
||||
|
||||
@classmethod
|
||||
def draw_edit_object_interface(cls, context):
|
||||
@@ -155,47 +162,19 @@ class BimToolUI:
|
||||
op = row.operator("bim.change_extrusion_x_angle", icon="FILE_REFRESH", text="")
|
||||
op.x_angle = cls.props.x_angle
|
||||
|
||||
row = cls.layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_E")
|
||||
row.operator("bim.hotkey", text="Extend").hotkey = "S_E"
|
||||
row = cls.layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_T")
|
||||
row.operator("bim.hotkey", text="Butt").hotkey = "S_T"
|
||||
row = cls.layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_Y")
|
||||
row.operator("bim.hotkey", text="Mitre").hotkey = "S_Y"
|
||||
row = cls.layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_M")
|
||||
add_layout_hotkey_operator(row, "Merge", "S_M", bpy.ops.bim.merge_wall.__doc__)
|
||||
row = cls.layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_F")
|
||||
add_layout_hotkey_operator(row, "Flip", "S_F", bpy.ops.bim.flip_wall.__doc__)
|
||||
row = cls.layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_K")
|
||||
add_layout_hotkey_operator(row, "Split", "S_K", bpy.ops.bim.split_wall.__doc__)
|
||||
row = cls.layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_R")
|
||||
row.operator("bim.hotkey", text="Rotate 90").hotkey = "S_R"
|
||||
row = cls.layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_G")
|
||||
add_layout_hotkey_operator(row, "Regen", "S_G", bpy.ops.bim.recalculate_wall.__doc__)
|
||||
add_layout_hotkey_operator(cls.layout, "Extend", "S_E", "")
|
||||
add_layout_hotkey_operator(cls.layout, "Butt", "S_T", "")
|
||||
add_layout_hotkey_operator(cls.layout, "Mitre", "S_Y", "")
|
||||
|
||||
add_layout_hotkey_operator(cls.layout, "Merge", "S_M", bpy.ops.bim.merge_wall.__doc__)
|
||||
add_layout_hotkey_operator(cls.layout, "Flip", "S_F", bpy.ops.bim.flip_wall.__doc__)
|
||||
add_layout_hotkey_operator(cls.layout, "Split", "S_K", bpy.ops.bim.split_wall.__doc__)
|
||||
add_layout_hotkey_operator(cls.layout, "Rotate 90", "S_R", bpy.ops.bim.rotate_90.__doc__)
|
||||
add_layout_hotkey_operator(cls.layout, "Regen", "S_G", bpy.ops.bim.recalculate_wall.__doc__)
|
||||
row.operator("bim.join_wall", icon="X", text="").join_type = ""
|
||||
|
||||
elif AuthoringData.data["active_material_usage"] == "LAYER3":
|
||||
# unnecessary check because BIM Tool is not available in EDIT mode?
|
||||
if context.active_object.mode == "OBJECT":
|
||||
row = cls.layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_E")
|
||||
row.operator("bim.hotkey", text="Edit Profile").hotkey = "S_E"
|
||||
add_layout_hotkey_operator(cls.layout, "Edit Profile", "S_E", "")
|
||||
|
||||
row = cls.layout.row(align=True)
|
||||
row.prop(data=cls.props, property="x_angle", text="X Angle")
|
||||
@@ -215,32 +194,26 @@ class BimToolUI:
|
||||
op = row.operator("bim.change_profile_depth", icon="FILE_REFRESH", text="")
|
||||
op.depth = cls.props.extrusion_depth
|
||||
|
||||
row = cls.layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_E")
|
||||
row.operator("bim.hotkey", text="Extend").hotkey = "S_E"
|
||||
row = cls.layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_ALT")
|
||||
row.label(text="", icon="EVENT_E")
|
||||
row.operator("bim.hotkey", text="Edit Axis").hotkey = "A_E"
|
||||
row = cls.layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_T")
|
||||
row.operator("bim.hotkey", text="Butt").hotkey = "S_T"
|
||||
row = cls.layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_Y")
|
||||
row.operator("bim.hotkey", text="Mitre").hotkey = "S_Y"
|
||||
row = cls.layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_R")
|
||||
row.operator("bim.hotkey", text="Rotate 90").hotkey = "S_R"
|
||||
row = cls.layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_G")
|
||||
add_layout_hotkey_operator(row, "Regen", "S_G", bpy.ops.bim.recalculate_profile.__doc__)
|
||||
add_layout_hotkey_operator(cls.layout, "Extend", "S_E", "")
|
||||
add_layout_hotkey_operator(cls.layout, "Edit Axis", "A_E", "")
|
||||
add_layout_hotkey_operator(cls.layout, "Butt", "S_T", "")
|
||||
add_layout_hotkey_operator(cls.layout, "Mitre", "S_Y", "")
|
||||
add_layout_hotkey_operator(cls.layout, "Rotate 90", "S_R", bpy.ops.bim.rotate_90.__doc__)
|
||||
add_layout_hotkey_operator(cls.layout, "Regen", "S_G", bpy.ops.bim.recalculate_profile.__doc__)
|
||||
row.operator("bim.extend_profile", icon="X", text="").join_type = ""
|
||||
|
||||
elif (
|
||||
(RailingData.is_loaded or not RailingData.load())
|
||||
and RailingData.data["parameters"]
|
||||
and not context.active_object.BIMRailingProperties.is_editing_path
|
||||
):
|
||||
# NOTE: should be above "active_representation_type" = "SweptSolid" check
|
||||
# because it could be a SweptSolid too
|
||||
add_layout_hotkey_operator(cls.layout, "Edit Railing Path", "S_E", "")
|
||||
|
||||
elif AuthoringData.data["active_representation_type"] == "SweptSolid":
|
||||
add_layout_hotkey_operator(cls.layout, "Edit Profile", "S_E", "")
|
||||
|
||||
elif AuthoringData.data["active_class"] in (
|
||||
"IfcWindow",
|
||||
"IfcWindowStandardCase",
|
||||
@@ -254,20 +227,11 @@ class BimToolUI:
|
||||
row = cls.layout.row(align=True)
|
||||
row.prop(data=cls.props, property="rl1", text="RL")
|
||||
|
||||
row = cls.layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_G")
|
||||
add_layout_hotkey_operator(row, "Regen", "S_G", bpy.ops.bim.recalculate_fill.__doc__)
|
||||
row = cls.layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_F")
|
||||
row.operator("bim.hotkey", text="Flip").hotkey = "S_F"
|
||||
add_layout_hotkey_operator(cls.layout, "Regen", "S_G", bpy.ops.bim.recalculate_fill.__doc__)
|
||||
add_layout_hotkey_operator(cls.layout, "Flip", "S_F", "")
|
||||
|
||||
elif AuthoringData.data["active_class"] in ("IfcSpace",):
|
||||
row = cls.layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_G")
|
||||
add_layout_hotkey_operator(row, "Regen", "S_G", bpy.ops.bim.generate_space.__doc__)
|
||||
add_layout_hotkey_operator(cls.layout, "Regen", "S_G", bpy.ops.bim.generate_space.__doc__)
|
||||
|
||||
elif AuthoringData.data["active_class"] in (
|
||||
"IfcCableCarrierSegmentType",
|
||||
@@ -275,39 +239,22 @@ class BimToolUI:
|
||||
"IfcDuctSegmentType",
|
||||
"IfcPipeSegmentType",
|
||||
):
|
||||
row = cls.layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="Extend", icon="EVENT_E")
|
||||
|
||||
elif (
|
||||
(RailingData.is_loaded or not RailingData.load())
|
||||
and RailingData.data["parameters"]
|
||||
and not context.active_object.BIMRailingProperties.is_editing_path
|
||||
):
|
||||
row = cls.layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_E")
|
||||
row.operator("bim.hotkey", text="Edit Railing Path").hotkey = "S_E"
|
||||
add_layout_hotkey_operator(cls.layout, "Extend", "S_E", "")
|
||||
|
||||
elif (
|
||||
(RoofData.is_loaded or not RoofData.load())
|
||||
and RoofData.data["parameters"]
|
||||
and not context.active_object.BIMRoofProperties.is_editing_path
|
||||
):
|
||||
row = cls.layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_E")
|
||||
row.operator("bim.hotkey", text="Edit Roof Path").hotkey = "S_E"
|
||||
add_layout_hotkey_operator(cls.layout, "Edit Roof Path", "S_E", "")
|
||||
|
||||
elif DecoratorData.get_ifc_text_data(bpy.context.object):
|
||||
row = cls.layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_E")
|
||||
row.operator("bim.hotkey", text="Edit text").hotkey = "S_E"
|
||||
add_layout_hotkey_operator(cls.layout, "Edit Text", "S_E", "")
|
||||
|
||||
row = cls.layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_O")
|
||||
|
||||
if len(context.selected_objects) == 2:
|
||||
row.operator("bim.add_opening", text="Apply Void")
|
||||
else:
|
||||
@@ -320,27 +267,14 @@ class BimToolUI:
|
||||
else:
|
||||
row.operator("bim.show_openings", icon="HIDE_OFF", text="")
|
||||
|
||||
row = cls.layout.row(align=True)
|
||||
row.label(text="Align")
|
||||
row = cls.layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="Align Exterior", icon="EVENT_X")
|
||||
row = cls.layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="Align Centerline", icon="EVENT_C")
|
||||
row = cls.layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="Align Interior", icon="EVENT_V")
|
||||
row = cls.layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="Mirror", icon="EVENT_M")
|
||||
cls.layout.row(align=True).label(text="Align")
|
||||
add_layout_hotkey_operator(cls.layout, "Align Exterior", "S_X", "")
|
||||
add_layout_hotkey_operator(cls.layout, "Align Centerline", "S_C", "")
|
||||
add_layout_hotkey_operator(cls.layout, "Align Interior", "S_V", "")
|
||||
add_layout_hotkey_operator(cls.layout, "Mirror", "S_M", bpy.ops.bim.mirror_elements.__doc__)
|
||||
|
||||
row = cls.layout.row(align=True)
|
||||
row.label(text="Mode")
|
||||
row = cls.layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_ALT")
|
||||
row.label(text="", icon="EVENT_O")
|
||||
add_layout_hotkey_operator(row, "Void", "A_O", "Show / edit openings")
|
||||
cls.layout.row(align=True).label(text="Mode")
|
||||
add_layout_hotkey_operator(cls.layout, "Void", "A_O", "Show / edit openings")
|
||||
row = cls.layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_ALT")
|
||||
row.label(text="", icon="EVENT_D")
|
||||
@@ -369,7 +303,7 @@ class BimToolUI:
|
||||
prop_with_search(row, cls.props, "ifc_class", text="")
|
||||
|
||||
row = cls.layout.row(align=True)
|
||||
if AuthoringData.data["relating_types_ids"]:
|
||||
if AuthoringData.data["relating_type_id"]:
|
||||
row.label(text="", icon="FILE_3D")
|
||||
prop_with_search(row, cls.props, "relating_type_id", text="")
|
||||
else:
|
||||
@@ -475,6 +409,16 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator):
|
||||
if not bpy.context.selected_objects:
|
||||
return
|
||||
|
||||
# NOTE: placing it before the other operations because railing can also be SweptSolid
|
||||
# and it might conflict with one of the conditions below
|
||||
if (
|
||||
(RailingData.is_loaded or not RailingData.load())
|
||||
and RailingData.data["parameters"]
|
||||
and not bpy.context.active_object.BIMRailingProperties.is_editing_path
|
||||
):
|
||||
bpy.ops.bim.enable_editing_railing_path()
|
||||
return
|
||||
|
||||
selected_usages = {}
|
||||
for obj in bpy.context.selected_objects:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
@@ -483,8 +427,12 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator):
|
||||
continue
|
||||
usage = tool.Model.get_usage_type(element)
|
||||
if not usage:
|
||||
obj.select_set(False)
|
||||
continue
|
||||
representation = tool.Geometry.get_active_representation(obj)
|
||||
if representation and representation.RepresentationType == "SweptSolid":
|
||||
usage = "SWEPTSOLID"
|
||||
else:
|
||||
obj.select_set(False)
|
||||
continue
|
||||
selected_usages.setdefault(usage, []).append(obj)
|
||||
|
||||
if len(bpy.context.selected_objects) == 1:
|
||||
@@ -498,7 +446,9 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator):
|
||||
elif self.active_material_usage == "PROFILE":
|
||||
# Extend PROFILE to cursor
|
||||
bpy.ops.bim.extend_profile(join_type="T")
|
||||
|
||||
else:
|
||||
# Edit SWEPTSOLID profile (assuming single profile for now)
|
||||
bpy.ops.bim.enable_editing_extrusion_profile()
|
||||
elif self.active_material_usage == "LAYER2" and selected_usages.get("PROFILE", []):
|
||||
# Extend PROFILEs to LAYER2
|
||||
[o.select_set(False) for o in selected_usages.get("LAYER3", [])]
|
||||
@@ -523,15 +473,6 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator):
|
||||
[o.select_set(False) for o in selected_usages.get("LAYER2", [])]
|
||||
bpy.ops.bim.extend_profile(join_type="T")
|
||||
|
||||
elif (
|
||||
(RailingData.is_loaded or not RailingData.load())
|
||||
and RailingData.data["parameters"]
|
||||
and not bpy.context.active_object.BIMRailingProperties.is_editing_path
|
||||
):
|
||||
# undo the unselection done above because railing has no usage type 🙃
|
||||
bpy.context.object.select_set(True)
|
||||
bpy.ops.bim.enable_editing_railing_path()
|
||||
|
||||
elif (
|
||||
(RoofData.is_loaded or not RoofData.load())
|
||||
and RoofData.data["parameters"]
|
||||
@@ -573,7 +514,13 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator):
|
||||
if self.active_material_usage == "LAYER2":
|
||||
bpy.ops.bim.merge_wall()
|
||||
else:
|
||||
bpy.ops.bim.mirror_elements()
|
||||
if len(bpy.context.selected_objects) == 1:
|
||||
self.report(
|
||||
{"INFO"},
|
||||
"At least two objects must be selected: an object to be mirrored, and a mirror axis as the active object.",
|
||||
)
|
||||
else:
|
||||
bpy.ops.bim.mirror_elements()
|
||||
|
||||
def hotkey_S_R(self):
|
||||
if not bpy.context.selected_objects:
|
||||
|
||||
@@ -102,7 +102,7 @@ class ExecuteIfcPatch(bpy.types.Operator):
|
||||
|
||||
class UpdateIfcPatchArguments(bpy.types.Operator):
|
||||
bl_idname = "bim.update_ifc_patch_arguments"
|
||||
bl_label = "Update IFC Patch arguments"
|
||||
bl_label = "Update IFC Patch Arguments"
|
||||
recipe: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
|
||||
@@ -812,7 +812,6 @@ class ExportIFC(bpy.types.Operator):
|
||||
bl_idname = "export_ifc.bim"
|
||||
bl_label = "Export IFC"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Export the IFC project"
|
||||
filename_ext = ".ifc"
|
||||
filter_glob: bpy.props.StringProperty(default="*.ifc;*.ifczip;*.ifcxml;*.ifcjson", options={"HIDDEN"})
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
@@ -892,6 +891,12 @@ class ExportIFC(bpy.types.Operator):
|
||||
blenderbim.bim.handler.purge_module_data()
|
||||
return {"FINISHED"}
|
||||
|
||||
@classmethod
|
||||
def description(cls, context, properties):
|
||||
if properties.should_save_as:
|
||||
return "Export the IFC project to a selected file"
|
||||
return "Export the IFC project to this file"
|
||||
|
||||
|
||||
class ImportIFC(bpy.types.Operator):
|
||||
bl_idname = "import_ifc.bim"
|
||||
|
||||
@@ -96,7 +96,10 @@ class MaterialPsetsData(Data):
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.data = {"psets": cls.psetqtos(tool.Ifc.get_entity(bpy.context.active_object.active_material))}
|
||||
cls.data = {
|
||||
"ifc_definition_id": bpy.context.active_object.active_material.BIMObjectProperties.ifc_definition_id,
|
||||
"psets": cls.psetqtos(tool.Ifc.get_entity(bpy.context.active_object.active_material)),
|
||||
}
|
||||
cls.is_loaded = True
|
||||
|
||||
|
||||
|
||||
@@ -295,9 +295,10 @@ class EditPset(bpy.types.Operator, Operator):
|
||||
name=props.active_pset_name,
|
||||
properties=properties,
|
||||
)
|
||||
bpy.ops.bim.load_cost_item_quantities()
|
||||
|
||||
if tool.Cost.has_schedules():
|
||||
tool.Cost.update_cost_items(pset=pset)
|
||||
bpy.ops.bim.disable_pset_editing(obj=self.obj, obj_type=self.obj_type)
|
||||
tool.Blender.update_viewport()
|
||||
|
||||
|
||||
class RemovePset(bpy.types.Operator, Operator):
|
||||
@@ -380,32 +381,21 @@ class CalculateQuantity(bpy.types.Operator):
|
||||
self.qto_calculator = QtoCalculator()
|
||||
obj = context.active_object
|
||||
prop = obj.PsetProperties.properties.get(self.prop)
|
||||
prop.metadata.float_value = self.calculate_quantity(obj, context)
|
||||
quantity = self.calculate_quantity(obj, context)
|
||||
|
||||
if quantity is None:
|
||||
self.report({"ERROR"}, "Could not calculate quantity")
|
||||
return {"CANCELLED"}
|
||||
|
||||
prop.metadata.float_value = quantity
|
||||
return {"FINISHED"}
|
||||
|
||||
def calculate_quantity(self, obj, context):
|
||||
quantity = self.qto_calculator.calculate_quantity(obj.PsetProperties.active_pset_name, self.prop, obj)
|
||||
prefix, name = self.get_blender_prefix_name(context)
|
||||
quantity = ifcopenshell.util.unit.convert(quantity, None, "METRE", prefix, name)
|
||||
if quantity is None:
|
||||
return
|
||||
return round(quantity, 3)
|
||||
|
||||
def get_prefix_name(self, value):
|
||||
if "/" in value:
|
||||
return value.split("/")
|
||||
return None, value
|
||||
|
||||
def get_blender_prefix_name(self, context):
|
||||
unit_settings = context.scene.unit_settings
|
||||
if unit_settings.system == "IMPERIAL":
|
||||
if unit_settings.length_unit == "INCHES":
|
||||
return None, "inch"
|
||||
elif unit_settings.length_unit == "FEET":
|
||||
return None, "foot"
|
||||
elif unit_settings.system == "METRIC":
|
||||
if unit_settings.length_unit == "METERS":
|
||||
return None, "METRE"
|
||||
return unit_settings.length_unit[0 : -len("METERS")], "METRE"
|
||||
|
||||
|
||||
class GuessQuantity(bpy.types.Operator):
|
||||
bl_idname = "bim.guess_quantity"
|
||||
@@ -422,35 +412,7 @@ class GuessQuantity(bpy.types.Operator):
|
||||
|
||||
def guess_quantity(self, obj, context):
|
||||
quantity = self.qto_calculator.guess_quantity(self.prop, [p.name for p in obj.PsetProperties.properties], obj)
|
||||
if "area" in self.prop.lower():
|
||||
if context.scene.BIMProperties.area_unit:
|
||||
prefix, name = self.get_prefix_name(context.scene.BIMProperties.area_unit)
|
||||
quantity = ifcopenshell.util.unit.convert(quantity, None, "SQUARE_METRE", prefix, name)
|
||||
elif "volume" in self.prop.lower():
|
||||
if context.scene.BIMProperties.volume_unit:
|
||||
prefix, name = self.get_prefix_name(context.scene.BIMProperties.volume_unit)
|
||||
quantity = ifcopenshell.util.unit.convert(quantity, None, "CUBIC_METRE", prefix, name)
|
||||
else:
|
||||
prefix, name = self.get_blender_prefix_name(context)
|
||||
quantity = ifcopenshell.util.unit.convert(quantity, None, "METRE", prefix, name)
|
||||
return round(quantity, 3)
|
||||
|
||||
def get_prefix_name(self, value):
|
||||
if "/" in value:
|
||||
return value.split("/")
|
||||
return None, value
|
||||
|
||||
def get_blender_prefix_name(self, context):
|
||||
unit_settings = context.scene.unit_settings
|
||||
if unit_settings.system == "IMPERIAL":
|
||||
if unit_settings.length_unit == "INCHES":
|
||||
return None, "inch"
|
||||
elif unit_settings.length_unit == "FEET":
|
||||
return None, "foot"
|
||||
elif unit_settings.system == "METRIC":
|
||||
if unit_settings.length_unit == "METERS":
|
||||
return None, "METRE"
|
||||
return unit_settings.length_unit[0 : -len("METERS")], "METRE"
|
||||
return round(quantity, 3) if quantity is not None else None
|
||||
|
||||
|
||||
class CopyPropertyToSelection(bpy.types.Operator, Operator):
|
||||
@@ -494,7 +456,7 @@ class BIM_OT_add_property_to_edit(bpy.types.Operator):
|
||||
|
||||
|
||||
class BIM_OT_remove_property_to_edit(bpy.types.Operator):
|
||||
bl_label = "Remove property to be renamed"
|
||||
bl_label = "Remove Property to Be Renamed"
|
||||
bl_idname = "bim.remove_property_to_edit"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
index: bpy.props.IntProperty()
|
||||
@@ -510,7 +472,7 @@ class BIM_OT_remove_property_to_edit(bpy.types.Operator):
|
||||
|
||||
|
||||
class BIM_OT_clear_list(bpy.types.Operator):
|
||||
bl_label = "Clear list of properties"
|
||||
bl_label = "Clear List of Properties"
|
||||
bl_idname = "bim.clear_list"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
option: bpy.props.StringProperty()
|
||||
@@ -558,7 +520,7 @@ class BIM_OT_rename_parameters(bpy.types.Operator):
|
||||
|
||||
|
||||
class BIM_OT_add_edit_custom_property(bpy.types.Operator):
|
||||
bl_label = "Add or edit a custom property"
|
||||
bl_label = "Add or Edit a Custom Property"
|
||||
bl_idname = "bim.add_edit_custom_property"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
index: bpy.props.IntProperty()
|
||||
@@ -615,7 +577,7 @@ class BIM_OT_add_edit_custom_property(bpy.types.Operator):
|
||||
|
||||
|
||||
class BIM_OT_bulk_remove_psets(bpy.types.Operator):
|
||||
bl_label = "Bulk remove psets from selected objects"
|
||||
bl_label = "Bulk Remove Psets from Selected Objects"
|
||||
bl_idname = "bim.bulk_remove_psets"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Bulk remove psets from selected objects"
|
||||
|
||||
@@ -26,10 +26,10 @@ from shapely.ops import unary_union
|
||||
import blenderbim.tool as tool
|
||||
import ifcopenshell
|
||||
from blenderbim.bim.module.pset.calc_quantity_function_mapper import mapper
|
||||
import blenderbim.bim
|
||||
|
||||
|
||||
class QtoCalculator:
|
||||
|
||||
def __init__(self):
|
||||
self.mapping_dict = {}
|
||||
for key in mapper.keys():
|
||||
@@ -46,6 +46,7 @@ class QtoCalculator:
|
||||
self.mapping_dict[key][item] = None
|
||||
|
||||
def calculate_quantity(self, qto_name, quantity_name, obj):
|
||||
"""calculates the value of the quantity in the project units"""
|
||||
string = "self.mapping_dict[qto_name][quantity_name](obj"
|
||||
if isinstance(mapper[qto_name][quantity_name], dict):
|
||||
args = mapper[qto_name][quantity_name]["args"]
|
||||
@@ -53,33 +54,53 @@ class QtoCalculator:
|
||||
args = ""
|
||||
string += args
|
||||
string += ")"
|
||||
return eval(string)
|
||||
value = eval(string)
|
||||
|
||||
return tool.Qto.convert_to_project_units(value, qto_name, quantity_name) or value
|
||||
|
||||
def guess_quantity(self, prop_name, alternative_prop_names, obj):
|
||||
"""guess the value of the quantity by name, returns the value in the project units"""
|
||||
prop_name = prop_name.lower()
|
||||
alternative_prop_names = [p.lower() for p in alternative_prop_names]
|
||||
value = None
|
||||
if "length" in prop_name and "width" not in alternative_prop_names and "height" not in alternative_prop_names:
|
||||
return self.get_linear_length(obj)
|
||||
value = self.get_linear_length(obj)
|
||||
elif "length" in prop_name:
|
||||
return self.get_length(obj)
|
||||
value = self.get_length(obj)
|
||||
elif "width" in prop_name and "length" not in alternative_prop_names:
|
||||
return self.get_length(obj)
|
||||
value = self.get_length(obj)
|
||||
elif "width" in prop_name:
|
||||
return self.get_width(obj)
|
||||
value = self.get_width(obj)
|
||||
elif "height" in prop_name or "depth" in prop_name:
|
||||
return self.get_height(obj)
|
||||
value = self.get_height(obj)
|
||||
elif "perimeter" in prop_name:
|
||||
return self.get_net_perimeter(obj)
|
||||
value = self.get_net_perimeter(obj)
|
||||
elif "area" in prop_name and ("footprint" in prop_name or "section" in prop_name or "floor" in prop_name):
|
||||
return self.get_net_footprint_area(obj)
|
||||
value = self.get_net_footprint_area(obj)
|
||||
elif "area" in prop_name and "side" in prop_name:
|
||||
return self.get_side_area(obj)
|
||||
value = self.get_side_area(obj)
|
||||
elif "area" in prop_name:
|
||||
return self.get_gross_surface_area(obj)
|
||||
value = self.get_gross_surface_area(obj)
|
||||
elif "volume" in prop_name and "gross" in prop_name:
|
||||
return self.get_gross_volume(obj)
|
||||
elif "volume" in prop_name :
|
||||
return self.get_net_volume(obj)
|
||||
value = self.get_gross_volume(obj)
|
||||
elif "volume" in prop_name:
|
||||
value = self.get_net_volume(obj)
|
||||
|
||||
if value is None:
|
||||
return
|
||||
|
||||
unit_type_keywords = {
|
||||
"length": "Q_LENGTH",
|
||||
"width": "Q_LENGTH",
|
||||
"height": "Q_LENGTH",
|
||||
"depth": "Q_LENGTH",
|
||||
"perimeter": "Q_LENGTH",
|
||||
"area": "Q_AREA",
|
||||
"volume": "Q_VOLUME",
|
||||
}
|
||||
|
||||
unit_type = next(unit_type_keywords[k] for k in unit_type_keywords if k in prop_name)
|
||||
return tool.Qto.convert_to_project_units(value, quantity_type=unit_type) or value
|
||||
|
||||
def get_units(self, o, vg_index):
|
||||
return len([v for v in o.data.vertices if vg_index in [g.group for g in v.groups]])
|
||||
@@ -100,7 +121,7 @@ class QtoCalculator:
|
||||
x = (Vector(o.bound_box[4]) - Vector(o.bound_box[0])).length
|
||||
y = (Vector(o.bound_box[3]) - Vector(o.bound_box[0])).length
|
||||
z = (Vector(o.bound_box[1]) - Vector(o.bound_box[0])).length
|
||||
if self.get_object_main_axis(o) == "x" or main_axis=="x":
|
||||
if self.get_object_main_axis(o) == "x" or main_axis == "x":
|
||||
return max(x, y)
|
||||
if self.get_object_main_axis(o) == "z":
|
||||
return max(z, x)
|
||||
@@ -226,7 +247,10 @@ class QtoCalculator:
|
||||
decompositions = ifcopenshell.util.element.get_decomposition(element)
|
||||
finish_floor_height = 0
|
||||
for decomposition in decompositions:
|
||||
if decomposition.get_info()['PredefinedType'] == 'FLOORING' and decomposition.get_info()['type'] == 'IfcCovering' :
|
||||
if (
|
||||
decomposition.get_info()["PredefinedType"] == "FLOORING"
|
||||
and decomposition.get_info()["type"] == "IfcCovering"
|
||||
):
|
||||
floor_obj = tool.Ifc.get_object(decomposition)
|
||||
new_finish_floor_height = self.get_height(floor_obj)
|
||||
if new_finish_floor_height > finish_floor_height:
|
||||
@@ -239,7 +263,10 @@ class QtoCalculator:
|
||||
decompositions = ifcopenshell.util.element.get_decomposition(element)
|
||||
finish_ceiling_height = 0
|
||||
for decomposition in decompositions:
|
||||
if decomposition.get_info()['PredefinedType'] == 'CEILING' and decomposition.get_info()['type'] == 'IfcCovering' :
|
||||
if (
|
||||
decomposition.get_info()["PredefinedType"] == "CEILING"
|
||||
and decomposition.get_info()["type"] == "IfcCovering"
|
||||
):
|
||||
ceiling_obj = tool.Ifc.get_object(decomposition)
|
||||
new_finish_ceiling_height = self.get_height(ceiling_obj)
|
||||
if new_finish_ceiling_height > finish_ceiling_height:
|
||||
@@ -247,8 +274,6 @@ class QtoCalculator:
|
||||
|
||||
return finish_ceiling_height
|
||||
|
||||
|
||||
|
||||
def get_net_perimeter(self, o):
|
||||
parsed_edges = []
|
||||
shared_edges = []
|
||||
@@ -276,9 +301,9 @@ class QtoCalculator:
|
||||
pass
|
||||
|
||||
def get_rectangular_perimeter(self, obj):
|
||||
length = self.get_length(obj, main_axis='x')
|
||||
length = self.get_length(obj, main_axis="x")
|
||||
height = self.get_height(obj)
|
||||
return (length+height)*2
|
||||
return (length + height) * 2
|
||||
|
||||
def get_lowest_polygons(self, o):
|
||||
lowest_polygons = []
|
||||
@@ -297,7 +322,6 @@ class QtoCalculator:
|
||||
return lowest_polygons
|
||||
|
||||
def get_highest_polygons(self, o):
|
||||
|
||||
highest_polygons = []
|
||||
highest_z = None
|
||||
for polygon in o.data.polygons:
|
||||
@@ -327,8 +351,8 @@ class QtoCalculator:
|
||||
total_net_floor_area = self.get_net_footprint_area(obj)
|
||||
|
||||
for decomposition in decompositions:
|
||||
decomposition_type = decomposition.get_info()['type']
|
||||
if decomposition_type == 'IfcColumn' or decomposition_type == 'IfcColumn':
|
||||
decomposition_type = decomposition.get_info()["type"]
|
||||
if decomposition_type == "IfcColumn" or decomposition_type == "IfcColumn":
|
||||
decomposition_obj = tool.Ifc.get_object(decomposition)
|
||||
net_footprint_obj_area = self.get_net_footprint_area(decomposition_obj)
|
||||
total_net_floor_area -= net_footprint_obj_area
|
||||
@@ -343,9 +367,9 @@ class QtoCalculator:
|
||||
total_gross_ceiling_area = 0
|
||||
|
||||
for decomposition in decompositions:
|
||||
decomposition_type = decomposition.get_info()['type']
|
||||
decomposition_predefined_type = decomposition.get_info()['PredefinedType']
|
||||
if decomposition_type == 'IfcCovering' and decomposition_predefined_type == 'CEILING':
|
||||
decomposition_type = decomposition.get_info()["type"]
|
||||
decomposition_predefined_type = decomposition.get_info()["PredefinedType"]
|
||||
if decomposition_type == "IfcCovering" and decomposition_predefined_type == "CEILING":
|
||||
decomposition_obj = tool.Ifc.get_object(decomposition)
|
||||
total_gross_ceiling_area += self.get_gross_footprint_area(decomposition_obj)
|
||||
|
||||
@@ -359,13 +383,13 @@ class QtoCalculator:
|
||||
total_net_ceiling_area = 0
|
||||
|
||||
for decomposition in decompositions:
|
||||
decomposition_type = decomposition.get_info()['type']
|
||||
decomposition_predefined_type = decomposition.get_info()['PredefinedType']
|
||||
if decomposition_type == 'IfcCovering' and decomposition_predefined_type == 'CEILING':
|
||||
decomposition_type = decomposition.get_info()["type"]
|
||||
decomposition_predefined_type = decomposition.get_info()["PredefinedType"]
|
||||
if decomposition_type == "IfcCovering" and decomposition_predefined_type == "CEILING":
|
||||
decomposition_obj = tool.Ifc.get_object(decomposition)
|
||||
total_net_ceiling_area += self.get_net_footprint_area(decomposition_obj)
|
||||
|
||||
if decomposition_type == 'IfcWall' or decomposition_type == 'IfcColumn':
|
||||
if decomposition_type == "IfcWall" or decomposition_type == "IfcColumn":
|
||||
decomposition_obj = tool.Ifc.get_object(decomposition)
|
||||
total_net_ceiling_area -= self.get_net_roofprint_area(decomposition_obj)
|
||||
|
||||
@@ -379,8 +403,8 @@ class QtoCalculator:
|
||||
total_space_net_volume = self.get_gross_volume(obj)
|
||||
|
||||
for decomposition in decompositions:
|
||||
decomposition_type = decomposition.get_info()['type']
|
||||
if decomposition_type == 'IfcWall' or decomposition_type == 'IfcColumn':
|
||||
decomposition_type = decomposition.get_info()["type"]
|
||||
if decomposition_type == "IfcWall" or decomposition_type == "IfcColumn":
|
||||
decomposition_obj = tool.Ifc.get_object(decomposition)
|
||||
total_space_net_volume -= self.get_net_volume(decomposition_obj)
|
||||
|
||||
@@ -541,33 +565,37 @@ class QtoCalculator:
|
||||
or material.is_a("IfcMaterialProfileSet")
|
||||
or material.is_a("IfcMaterialConstituentSet")
|
||||
):
|
||||
return
|
||||
return
|
||||
|
||||
if material.is_a('IfcMaterial'):
|
||||
if material.is_a("IfcMaterial"):
|
||||
material_mass_density = ifcopenshell.util.element.get_pset(material, "Pset_MaterialCommon", "MassDensity")
|
||||
return material_mass_density
|
||||
|
||||
if material.is_a('IfcMaterialLayerSetUsage'):
|
||||
if material.is_a("IfcMaterialLayerSetUsage"):
|
||||
material_layers = material.ForLayerSet.MaterialLayers
|
||||
densities = []
|
||||
thicknesses = []
|
||||
obj_mass_density = 0
|
||||
for material_layer in material_layers:
|
||||
material_mass_density = ifcopenshell.util.element.get_pset(material_layer.Material, "Pset_MaterialCommon", "MassDensity")
|
||||
material_mass_density = ifcopenshell.util.element.get_pset(
|
||||
material_layer.Material, "Pset_MaterialCommon", "MassDensity"
|
||||
)
|
||||
if material_mass_density is None:
|
||||
return
|
||||
densities.append(material_mass_density)
|
||||
thickness = material_layer.LayerThickness
|
||||
thicknesses.append(thickness)
|
||||
obj_mass_density = obj_mass_density + (material_mass_density* thickness)
|
||||
obj_mass_density = obj_mass_density + (material_mass_density * thickness)
|
||||
total_thickness = sum(thicknesses)
|
||||
obj_mass_density = obj_mass_density/total_thickness
|
||||
obj_mass_density = obj_mass_density / total_thickness
|
||||
return obj_mass_density
|
||||
|
||||
if material.is_a('IfcMaterialProfileSetUsage'):
|
||||
if material.is_a("IfcMaterialProfileSetUsage"):
|
||||
material_profiles = material.ForProfileSet.MaterialProfiles
|
||||
if len(material_profiles) == 1:
|
||||
material_mass_density = ifcopenshell.util.element.get_pset(material_profiles[0].Material, "Pset_MaterialCommon", "MassDensity")
|
||||
material_mass_density = ifcopenshell.util.element.get_pset(
|
||||
material_profiles[0].Material, "Pset_MaterialCommon", "MassDensity"
|
||||
)
|
||||
return material_mass_density
|
||||
else:
|
||||
return
|
||||
@@ -639,8 +667,8 @@ class QtoCalculator:
|
||||
for opening in openings:
|
||||
opening_id = opening.RelatedOpeningElement.GlobalId
|
||||
ifc_opening_element = ifc.by_guid(opening_id)
|
||||
#bl_opening_obj = tool.Ifc.get_object(ifc_opening_element)
|
||||
#mesh = bpy.data.meshes.new('myMesh')
|
||||
# bl_opening_obj = tool.Ifc.get_object(ifc_opening_element)
|
||||
# mesh = bpy.data.meshes.new('myMesh')
|
||||
mesh = self.get_gross_element_mesh(ifc_opening_element)
|
||||
|
||||
bl_opening_obj = bpy.data.objects.new("MyObject", mesh)
|
||||
@@ -656,8 +684,12 @@ class QtoCalculator:
|
||||
|
||||
bl_OBB_opening_object = self.get_OBB_object(bl_opening_obj)
|
||||
opening_area = self.get_lateral_area(
|
||||
#self.get_OBB_object(bl_opening_obj), angle_z1=angle_z1, angle_z2=angle_z2, exclude_end_areas=True
|
||||
bl_OBB_opening_object, angle_z1=angle_z1, angle_z2=angle_z2, exclude_end_areas=True, main_axis = 'x',
|
||||
# self.get_OBB_object(bl_opening_obj), angle_z1=angle_z1, angle_z2=angle_z2, exclude_end_areas=True
|
||||
bl_OBB_opening_object,
|
||||
angle_z1=angle_z1,
|
||||
angle_z2=angle_z2,
|
||||
exclude_end_areas=True,
|
||||
main_axis="x",
|
||||
)
|
||||
if opening_area >= min_area:
|
||||
total_opening_area += opening_area
|
||||
@@ -732,16 +764,16 @@ class QtoCalculator:
|
||||
if not self.has_openings(obj):
|
||||
return self.get_net_side_area(obj)
|
||||
|
||||
gross_side_area = self.get_lateral_area(obj, exclude_end_areas = True, subtract_openings = False, main_axis = 'x') / 2
|
||||
gross_side_area = self.get_lateral_area(obj, exclude_end_areas=True, subtract_openings=False, main_axis="x") / 2
|
||||
|
||||
return gross_side_area
|
||||
|
||||
def get_net_side_area(self, obj):
|
||||
net_side_area = self.get_lateral_area(obj, exclude_end_areas = True, main_axis = 'x') / 2
|
||||
net_side_area = self.get_lateral_area(obj, exclude_end_areas=True, main_axis="x") / 2
|
||||
return net_side_area
|
||||
|
||||
def get_outer_surface_area(self, obj):
|
||||
outer_surface_area = self.get_lateral_area(obj, exclude_end_areas = True, angle_z1 = 0, angle_z2 = 360)
|
||||
outer_surface_area = self.get_lateral_area(obj, exclude_end_areas=True, angle_z1=0, angle_z2=360)
|
||||
return outer_surface_area
|
||||
|
||||
def get_end_area(self, obj):
|
||||
@@ -751,7 +783,7 @@ class QtoCalculator:
|
||||
|
||||
gross_obj.matrix_world = obj.matrix_world
|
||||
|
||||
end_area = self.get_lateral_area(gross_obj, exclude_side_areas = True) / 2
|
||||
end_area = self.get_lateral_area(gross_obj, exclude_side_areas=True) / 2
|
||||
|
||||
self.delete_obj(gross_obj)
|
||||
self.delete_mesh(gross_mesh)
|
||||
@@ -774,7 +806,7 @@ class QtoCalculator:
|
||||
ifc = tool.Ifc.get()
|
||||
ifc_element = ifc.by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
|
||||
# if len(openings := ifc_element.HasOpenings) != 0:
|
||||
# if len(openings := ifc_element.HasOpenings) != 0:
|
||||
if len(openings := self.has_openings(obj)) != 0:
|
||||
for opening in openings:
|
||||
if opening.RelatedOpeningElement.PredefinedType == "OPENING":
|
||||
@@ -813,8 +845,8 @@ class QtoCalculator:
|
||||
|
||||
if angle_to_z_axis < angle:
|
||||
# offset the raycast, otherwise the raycast will always collide with the object.
|
||||
offset = polygon.center+Vector((0,0,0.01))
|
||||
if ignore_internal and obj.ray_cast(offset, (0,0,1))[0]:
|
||||
offset = polygon.center + Vector((0, 0, 0.01))
|
||||
if ignore_internal and obj.ray_cast(offset, (0, 0, 1))[0]:
|
||||
continue
|
||||
area += polygon.area
|
||||
|
||||
@@ -895,7 +927,8 @@ class QtoCalculator:
|
||||
|
||||
# add object to scene collection and then hide them.
|
||||
collection.objects.get(new_OBB_object.name, collection.objects.link(new_OBB_object))
|
||||
new_OBB_object.hide_set(True)
|
||||
if bpy.context.view_layer.objects.get(new_OBB_object.name):
|
||||
new_OBB_object.hide_set(True)
|
||||
|
||||
return new_OBB_object
|
||||
|
||||
@@ -941,14 +974,15 @@ class QtoCalculator:
|
||||
new_AABB_object = bpy.data.objects.new(f"OBB_{ifc_id}", aabb_mesh)
|
||||
new_AABB_object.matrix_world = obj.matrix_world
|
||||
|
||||
# create new collection for QtoCalculator
|
||||
# create new collection for QtoCalculator
|
||||
collection = bpy.data.collections.get("QtoCalculator", bpy.data.collections.new("QtoCalculator"))
|
||||
if not bpy.context.scene.collection.children.get(collection.name):
|
||||
bpy.context.scene.collection.children.link(collection)
|
||||
|
||||
# add object to scene collection and then hide them.
|
||||
collection.objects.link(new_AABB_object)
|
||||
new_AABB_object.hide_set(True)
|
||||
if bpy.context.view_layer.objects.get(new_AABB_object.name):
|
||||
new_AABB_object.hide_set(True)
|
||||
|
||||
return new_AABB_object
|
||||
|
||||
@@ -992,7 +1026,8 @@ class QtoCalculator:
|
||||
bpy.ops.mesh.select_all(action="SELECT")
|
||||
bpy.ops.mesh.bisect(plane_co=plane_co_neg, plane_no=plane_no_neg, use_fill=True, clear_outer=True)
|
||||
bpy.ops.object.editmode_toggle()
|
||||
bis_obj.hide_set(True)
|
||||
if bpy.context.view_layer.objects.get(bis_obj.name):
|
||||
bis_obj.hide_set(True)
|
||||
|
||||
return bis_obj
|
||||
|
||||
@@ -1095,12 +1130,7 @@ class QtoCalculator:
|
||||
return 0
|
||||
|
||||
# touching polygons should be coplanar:
|
||||
plane_intersection = mathutils.geometry.intersect_plane_plane(
|
||||
center1,
|
||||
normal1,
|
||||
center2,
|
||||
normal2
|
||||
)
|
||||
plane_intersection = mathutils.geometry.intersect_plane_plane(center1, normal1, center2, normal2)
|
||||
|
||||
# sometimes coplanar planes will interesect far off into the distance. This is a crude way of filtering out those intersections.
|
||||
if plane_intersection[0] is None or (plane_intersection[0] - center1).magnitude > 20:
|
||||
@@ -1209,15 +1239,16 @@ class QtoCalculator:
|
||||
bpy.data.meshes.remove(mesh)
|
||||
|
||||
def delete_obj(self, obj):
|
||||
bpy.data.objects.remove(obj, do_unlink = True)
|
||||
bpy.data.objects.remove(obj, do_unlink=True)
|
||||
|
||||
|
||||
# # Following code is here temporarily to test newly created functions:
|
||||
|
||||
#qto = QtoCalculator()
|
||||
#o = bpy.context.active_object
|
||||
#sel = bpy.context.selected_objects
|
||||
# qto = QtoCalculator()
|
||||
# o = bpy.context.active_object
|
||||
# sel = bpy.context.selected_objects
|
||||
#
|
||||
#nl = '\n'
|
||||
# nl = '\n'
|
||||
# print(
|
||||
# f"get_linear_length: {qto.get_linear_length(o)}{nl}{nl}"
|
||||
# f"get_width: {qto.get_width(o)}{nl}{nl}"
|
||||
@@ -1242,5 +1273,3 @@ class QtoCalculator:
|
||||
# f"get_touching_objects(o, ['IfcElement']): {qto.get_touching_objects(o, ['IfcElement'])}{nl}{nl}"
|
||||
# #f"get_contact_area: {qto.get_contact_area(o)}{nl}{nl}"
|
||||
# )
|
||||
|
||||
|
||||
|
||||
@@ -268,6 +268,11 @@ class BIM_PT_material_psets(Panel):
|
||||
def draw(self, context):
|
||||
if not MaterialPsetsData.is_loaded:
|
||||
MaterialPsetsData.load()
|
||||
elif (
|
||||
context.active_object.active_material.BIMObjectProperties.ifc_definition_id
|
||||
!= MaterialPsetsData.data["ifc_definition_id"]
|
||||
):
|
||||
MaterialPsetsData.load()
|
||||
|
||||
props = context.active_object.active_material.PsetProperties
|
||||
row = self.layout.row(align=True)
|
||||
|
||||
@@ -161,7 +161,7 @@ class EnableEditingPropTemplate(bpy.types.Operator):
|
||||
|
||||
class DeletePropEnum(bpy.types.Operator):
|
||||
bl_idname = "bim.delete_prop_enum"
|
||||
bl_label = "delete property enumeration"
|
||||
bl_label = "Delete Property Enumeration"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
index: bpy.props.IntProperty()
|
||||
|
||||
@@ -173,7 +173,7 @@ class DeletePropEnum(bpy.types.Operator):
|
||||
|
||||
class AddPropEnum(bpy.types.Operator):
|
||||
bl_idname = "bim.add_prop_enum"
|
||||
bl_label = "add property enumeration"
|
||||
bl_label = "Add Property Enumeration"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
index: bpy.props.IntProperty()
|
||||
|
||||
|
||||
@@ -186,7 +186,7 @@ class AssignBaseQto(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
class CalculateAllQuantities(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.calculate_all_quantities"
|
||||
bl_label = "Calculate all quantities"
|
||||
bl_label = "Calculate All Quantities"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Calculate all possible quantities and assign them to selected object"
|
||||
|
||||
@@ -195,5 +195,5 @@ class CalculateAllQuantities(bpy.types.Operator, tool.Ifc.Operator):
|
||||
return tool.Ifc.get() and context.selected_objects
|
||||
|
||||
def _execute(self, context):
|
||||
core.calculate_objects_base_quantities(tool.Ifc, tool.Qto, QtoCalculator(), selected_objects = context.selected_objects)
|
||||
core.calculate_objects_base_quantities(tool.Ifc, tool.Cost, tool.Qto, QtoCalculator(), selected_objects = context.selected_objects)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -47,7 +47,7 @@ class LoadResourceProperties(bpy.types.Operator):
|
||||
|
||||
class AddResource(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.add_resource"
|
||||
bl_label = "Add resource"
|
||||
bl_label = "Add Resource"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
ifc_class: bpy.props.StringProperty()
|
||||
parent_resource: bpy.props.IntProperty()
|
||||
@@ -74,7 +74,7 @@ class EnableEditingResource(bpy.types.Operator):
|
||||
|
||||
class DisableEditingResource(bpy.types.Operator):
|
||||
bl_idname = "bim.disable_editing_resource"
|
||||
bl_label = "Disable Editing Resources"
|
||||
bl_label = "Disable Editing Resource"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
@@ -161,7 +161,7 @@ class UnassignResource(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
class EnableEditingResourceTime(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.enable_editing_resource_time"
|
||||
bl_label = "Enable Editing Resource Usage"
|
||||
bl_label = "Enable Editing Resource Time"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
resource: bpy.props.IntProperty()
|
||||
|
||||
@@ -267,7 +267,7 @@ class EditResourceCostValue(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
class EnableEditingResourceBaseQuantity(bpy.types.Operator):
|
||||
bl_idname = "bim.enable_editing_resource_base_quantity"
|
||||
bl_label = "Enable Editing Resource Quantity"
|
||||
bl_label = "Enable Editing Resource Base Quantity"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
resource: bpy.props.IntProperty()
|
||||
|
||||
|
||||
@@ -168,9 +168,14 @@ class UnlinkObject(bpy.types.Operator):
|
||||
objects = context.selected_objects
|
||||
for obj in objects:
|
||||
if obj.BIMObjectProperties.ifc_definition_id:
|
||||
if obj in IfcStore.edited_objs:
|
||||
IfcStore.edited_objs.remove(obj)
|
||||
IfcStore.unlink_element(obj=obj)
|
||||
if obj.data:
|
||||
obj.data = obj.data.copy()
|
||||
for material_slot in obj.material_slots:
|
||||
if material_slot.material:
|
||||
material_slot.material = material_slot.material.copy()
|
||||
blenderbim.core.style.unlink_style(tool.Ifc, tool.Style, obj=material_slot.material)
|
||||
blenderbim.core.material.unlink_material(tool.Ifc, obj=material_slot.material)
|
||||
if "Ifc" in obj.name and "/" in obj.name:
|
||||
|
||||
@@ -20,7 +20,6 @@ import bpy
|
||||
from . import ui, prop, operator
|
||||
|
||||
classes = (
|
||||
operator.HighlightProductRelatedTask,
|
||||
operator.ExpandAllTasks,
|
||||
operator.ContractAllTasks,
|
||||
operator.AddSummaryTask,
|
||||
@@ -113,6 +112,8 @@ classes = (
|
||||
operator.VisualiseWorkScheduleDateRange,
|
||||
operator.LoadTaskAnimationColors,
|
||||
operator.DisableEditingTaskAnimationColors,
|
||||
operator.LoadProductTasks,
|
||||
operator.HighlightTask,
|
||||
prop.WorkPlan,
|
||||
prop.BIMWorkPlanProperties,
|
||||
prop.Task,
|
||||
@@ -140,6 +141,8 @@ classes = (
|
||||
ui.BIM_PT_4D_Tools,
|
||||
ui.BIM_PT_Task_Bar_Creator,
|
||||
ui.BIM_UL_animation_colors,
|
||||
ui.BIM_UL_product_input_tasks,
|
||||
ui.BIM_UL_product_output_tasks,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ def parse_duration_as_blender_props(dt, simplify=True):
|
||||
seconds = getattr(dt, "seconds", 0)
|
||||
hours, seconds = divmod(seconds, 3600)
|
||||
minutes, seconds = divmod(seconds, 60)
|
||||
days = getattr(dt, "days",0)
|
||||
days = getattr(dt, "days", 0)
|
||||
months = int(getattr(dt, "months", 0))
|
||||
years = int(getattr(dt, "years", 0))
|
||||
return {
|
||||
|
||||
@@ -170,7 +170,7 @@ class EnableEditingWorkSchedule(bpy.types.Operator):
|
||||
|
||||
class EnableEditingWorkScheduleTasks(bpy.types.Operator):
|
||||
bl_idname = "bim.enable_editing_work_schedule_tasks"
|
||||
bl_label = "Enable Editing Tasks"
|
||||
bl_label = "Enable Editing Work Schedule Tasks"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
work_schedule: bpy.props.IntProperty()
|
||||
|
||||
@@ -454,11 +454,8 @@ class UnassignProcess(bpy.types.Operator):
|
||||
)
|
||||
else:
|
||||
core.unassign_input_products(
|
||||
tool.Ifc,
|
||||
tool.Sequence,
|
||||
tool.Spatial,
|
||||
task=tool.Ifc.get().by_id(self.task)
|
||||
)
|
||||
tool.Ifc, tool.Sequence, tool.Spatial, task=tool.Ifc.get().by_id(self.task)
|
||||
)
|
||||
elif self.related_object_type == "CONTROL":
|
||||
pass # TODO
|
||||
return {"FINISHED"}
|
||||
@@ -606,7 +603,7 @@ class ImportP6XER(bpy.types.Operator, ImportHelper):
|
||||
|
||||
class ImportPP(bpy.types.Operator, ImportHelper):
|
||||
bl_idname = "import_pp.bim"
|
||||
bl_label = "Import Powerproject pp"
|
||||
bl_label = "Import Powerproject .pp"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
filename_ext = ".pp"
|
||||
filter_glob: bpy.props.StringProperty(default="*.pp", options={"HIDDEN"})
|
||||
@@ -964,7 +961,7 @@ class EditSequenceTimeLag(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
class DisableEditingSequence(bpy.types.Operator):
|
||||
bl_idname = "bim.disable_editing_sequence"
|
||||
bl_label = "Disable Editing Sequence Attributes"
|
||||
bl_label = "Disable Editing Sequence"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
@@ -1006,6 +1003,7 @@ class VisualiseWorkScheduleDate(bpy.types.Operator):
|
||||
core.visualise_work_schedule_date(tool.Sequence, work_schedule=tool.Ifc.get().by_id(self.work_schedule))
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class GuessDateRange(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.guess_date_range"
|
||||
bl_label = "Guess Work Schedule Date Range"
|
||||
@@ -1024,7 +1022,10 @@ class VisualiseWorkScheduleDateRange(bpy.types.Operator):
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
has_start, has_finish = bpy.context.scene.BIMWorkScheduleProperties.visualisation_start, bpy.context.scene.BIMWorkScheduleProperties.visualisation_finish
|
||||
has_start, has_finish = (
|
||||
bpy.context.scene.BIMWorkScheduleProperties.visualisation_start,
|
||||
bpy.context.scene.BIMWorkScheduleProperties.visualisation_finish,
|
||||
)
|
||||
return bool(has_start and has_finish) and not "-" in (has_start, has_finish)
|
||||
|
||||
def execute(self, context):
|
||||
@@ -1106,7 +1107,7 @@ class BlenderBIM_DatePicker(bpy.types.Operator):
|
||||
|
||||
|
||||
class BlenderBIM_DatePickerSetDate(bpy.types.Operator):
|
||||
bl_label = "set date"
|
||||
bl_label = "Set Date"
|
||||
bl_idname = "bim.datepicker_setdate"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
selected_date: bpy.props.StringProperty()
|
||||
@@ -1117,7 +1118,7 @@ class BlenderBIM_DatePickerSetDate(bpy.types.Operator):
|
||||
|
||||
|
||||
class BlenderBIM_RedrawDatePicker(bpy.types.Operator):
|
||||
bl_label = "redraw datepicker window"
|
||||
bl_label = "Redraw Datepicker Window"
|
||||
bl_idname = "bim.redraw_datepicker"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
action: bpy.props.StringProperty()
|
||||
@@ -1230,20 +1231,9 @@ class CalculateTaskDuration(bpy.types.Operator, tool.Ifc.Operator):
|
||||
core.calculate_task_duration(tool.Ifc, tool.Sequence, task=tool.Ifc.get().by_id(self.task))
|
||||
|
||||
|
||||
class HighlightProductRelatedTask(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.highlight_product_related_task"
|
||||
bl_label = "Highlights the related task"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Finds the related Task"
|
||||
product_type: bpy.props.StringProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
core.highlight_product_related_task(tool.Sequence, tool.Spatial, product_type=self.product_type)
|
||||
|
||||
|
||||
class ExpandAllTasks(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.expand_all_tasks"
|
||||
bl_label = "Expands all tasks"
|
||||
bl_label = "Expands All Tasks"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Finds the related Task"
|
||||
product_type: bpy.props.StringProperty()
|
||||
@@ -1254,7 +1244,7 @@ class ExpandAllTasks(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
class ContractAllTasks(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.contract_all_tasks"
|
||||
bl_label = "Expands all tasks"
|
||||
bl_label = "Expands All Tasks"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Finds the related Task"
|
||||
product_type: bpy.props.StringProperty()
|
||||
@@ -1286,13 +1276,14 @@ class LoadTaskAnimationColors(bpy.types.Operator):
|
||||
|
||||
class DisableEditingTaskAnimationColors(bpy.types.Operator):
|
||||
bl_idname = "bim.disable_editing_task_animation_colors"
|
||||
bl_label = "Disable Editing Colors"
|
||||
bl_label = "Disable Editing Task Animation Colors"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
core.disable_editing_task_animation_colors(tool.Sequence)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class CopyTask(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.duplicate_task"
|
||||
bl_label = "Copy Task"
|
||||
@@ -1300,4 +1291,39 @@ class CopyTask(bpy.types.Operator, tool.Ifc.Operator):
|
||||
task: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
core.duplicate_task(tool.Ifc, tool.Sequence, task=tool.Ifc.get().by_id(self.task))
|
||||
core.duplicate_task(tool.Ifc, tool.Sequence, task=tool.Ifc.get().by_id(self.task))
|
||||
|
||||
|
||||
class LoadProductTasks(bpy.types.Operator):
|
||||
bl_idname = "bim.load_product_tasks"
|
||||
bl_label = "Load Product Tasks"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
if not tool.Ifc.get():
|
||||
return False
|
||||
if not context.active_object:
|
||||
return False
|
||||
if not context.active_object.BIMObjectProperties.ifc_definition_id:
|
||||
return False
|
||||
return True
|
||||
|
||||
def execute(self, context):
|
||||
core.load_product_tasks(
|
||||
tool.Sequence, product=tool.Ifc.get().by_id(context.active_object.BIMObjectProperties.ifc_definition_id)
|
||||
)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class HighlightTask(bpy.types.Operator):
|
||||
bl_idname = "bim.highlight_task"
|
||||
bl_label = "Highlight Task"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
task: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
r = core.highlight_task(tool.Sequence, task=tool.Ifc.get().by_id(self.task))
|
||||
if isinstance(r, str):
|
||||
self.report({"WARNING"}, r)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -96,6 +96,14 @@ def update_active_task_outputs(self, context):
|
||||
bpy.ops.bim.load_task_outputs()
|
||||
|
||||
|
||||
def update_active_task_resources(self, context):
|
||||
bpy.ops.bim.load_task_resources()
|
||||
|
||||
|
||||
def update_active_task_inputs(self, context):
|
||||
bpy.ops.bim.load_task_inputs()
|
||||
|
||||
|
||||
def updateTaskName(self, context):
|
||||
props = context.scene.BIMWorkScheduleProperties
|
||||
if not props.is_task_update_enabled or self.name == "Unnamed":
|
||||
@@ -389,9 +397,13 @@ class BIMWorkScheduleProperties(PropertyGroup):
|
||||
active_task_input_index: IntProperty(name="Active Task Input Index")
|
||||
task_outputs: CollectionProperty(name="Task Outputs", type=TaskProduct)
|
||||
active_task_output_index: IntProperty(name="Active Task Output Index")
|
||||
is_nested_task_outputs: BoolProperty(
|
||||
name="Is Nested Task Outputs", default=False, update=update_active_task_outputs
|
||||
)
|
||||
show_nested_outputs: BoolProperty(name="Show Nested Tasks", default=False, update=update_active_task_outputs)
|
||||
show_nested_resources: BoolProperty(name="Show Nested Tasks", default=False, update=update_active_task_resources)
|
||||
show_nested_inputs: BoolProperty(name="Show Nested Tasks", default=False, update=update_active_task_inputs)
|
||||
product_input_tasks: CollectionProperty(name="Product Task Inputs", type=TaskProduct)
|
||||
product_output_tasks: CollectionProperty(name="Product Task Outputs", type=TaskProduct)
|
||||
active_product_output_task_index: IntProperty(name="Active Product Output Task Index")
|
||||
active_product_input_task_index: IntProperty(name="Active Product Input Task Index")
|
||||
|
||||
|
||||
class BIMTaskTreeProperties(PropertyGroup):
|
||||
|
||||
@@ -433,6 +433,8 @@ class BIM_PT_task_icom(Panel):
|
||||
op = row2.operator("bim.select_task_related_inputs", icon="RESTRICT_SELECT_OFF", text="")
|
||||
op.task = task.ifc_definition_id
|
||||
|
||||
row2 = col.row()
|
||||
row2.prop(self.props, "show_nested_inputs", text="Show Nested")
|
||||
row2 = col.row()
|
||||
row2.template_list("BIM_UL_task_inputs", "", self.props, "task_inputs", self.props, "active_task_input_index")
|
||||
|
||||
@@ -456,6 +458,9 @@ class BIM_PT_task_icom(Panel):
|
||||
op.related_object_type = "RESOURCE"
|
||||
op.resource = self.props.task_resources[self.props.active_task_resource_index].ifc_definition_id
|
||||
|
||||
row2 = col.row()
|
||||
row2.prop(self.props, "show_nested_resources", text="Show Nested")
|
||||
|
||||
row2 = col.row()
|
||||
row2.template_list(
|
||||
"BIM_UL_task_resources", "", self.props, "task_resources", self.props, "active_task_resource_index"
|
||||
@@ -481,7 +486,7 @@ class BIM_PT_task_icom(Panel):
|
||||
op = row2.operator("bim.select_task_related_products", icon="RESTRICT_SELECT_OFF", text="Select")
|
||||
op.task = task.ifc_definition_id
|
||||
row2 = col.row()
|
||||
row2.prop(self.props, "is_nested_task_outputs", text="Show Nested")
|
||||
row2.prop(self.props, "show_nested_outputs", text="Show Nested")
|
||||
row2 = col.row()
|
||||
row2.template_list(
|
||||
"BIM_UL_task_outputs", "", self.props, "task_outputs", self.props, "active_task_output_index"
|
||||
@@ -534,6 +539,26 @@ class BIM_UL_task_outputs(UIList):
|
||||
row.prop(item, "name", emboss=False, text="")
|
||||
|
||||
|
||||
class BIM_UL_product_input_tasks(UIList):
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
if item:
|
||||
row = layout.row(align=True)
|
||||
op = row.operator("bim.highlight_task", text="", icon="STYLUS_PRESSURE")
|
||||
op.task = item.ifc_definition_id
|
||||
row.split(factor=0.8)
|
||||
row.label(text=item.name)
|
||||
|
||||
|
||||
class BIM_UL_product_output_tasks(UIList):
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
if item:
|
||||
row = layout.row(align=True)
|
||||
op = row.operator("bim.highlight_task", text="", icon="STYLUS_PRESSURE")
|
||||
op.task = item.ifc_definition_id
|
||||
row.split(factor=0.8)
|
||||
row.label(text=item.name)
|
||||
|
||||
|
||||
class BIM_UL_tasks(UIList):
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
if item:
|
||||
@@ -798,14 +823,32 @@ class BIM_PT_4D_Tools(Panel):
|
||||
bl_category = "4D/5D Toolkit"
|
||||
|
||||
def draw(self, context):
|
||||
self.props = context.scene.BIMWorkScheduleProperties
|
||||
row = self.layout.row()
|
||||
row.operator(
|
||||
"bim.highlight_product_related_task", text="Find Input-related Task ", icon="STYLUS_PRESSURE"
|
||||
).product_type = "Input"
|
||||
row = self.layout.row()
|
||||
row.operator(
|
||||
"bim.highlight_product_related_task", text="Go to Output-related Task ", icon="STYLUS_PRESSURE"
|
||||
).product_type = "Output"
|
||||
row.operator("bim.load_product_tasks", text="Load Tasks", icon="FILE_REFRESH")
|
||||
|
||||
grid = self.layout.grid_flow(columns=2, even_columns=True)
|
||||
col1 = grid.column()
|
||||
col1.label(text="Product Input Tasks")
|
||||
col1.template_list(
|
||||
"BIM_UL_product_input_tasks",
|
||||
"",
|
||||
self.props,
|
||||
"product_input_tasks",
|
||||
self.props,
|
||||
"active_product_input_task_index",
|
||||
)
|
||||
|
||||
col2 = grid.column()
|
||||
col2.label(text="Product Output Tasks")
|
||||
col2.template_list(
|
||||
"BIM_UL_product_output_tasks",
|
||||
"",
|
||||
self.props,
|
||||
"product_output_tasks",
|
||||
self.props,
|
||||
"active_product_output_task_index",
|
||||
)
|
||||
|
||||
|
||||
class BIM_PT_Task_Bar_Creator(Panel):
|
||||
|
||||
@@ -34,6 +34,7 @@ class StylesData:
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.data = {"style_types": cls.style_types(), "total_styles": cls.total_styles()}
|
||||
cls.is_loaded = True
|
||||
|
||||
@classmethod
|
||||
def style_types(cls):
|
||||
@@ -55,7 +56,12 @@ class StyleAttributesData:
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.data = {"attributes": cls.get_attributes()}
|
||||
cls.data = {"ifc_style_id": cls.ifc_style_id(), "attributes": cls.get_attributes()}
|
||||
cls.is_loaded = True
|
||||
|
||||
@classmethod
|
||||
def ifc_style_id(cls):
|
||||
return bpy.context.active_object.active_material.BIMMaterialProperties.ifc_style_id
|
||||
|
||||
@classmethod
|
||||
def get_attributes(cls):
|
||||
|
||||
@@ -23,8 +23,6 @@ from blenderbim.bim.module.style.data import StylesData, StyleAttributesData
|
||||
from bl_ui.properties_material import MaterialButtonsPanel
|
||||
|
||||
|
||||
|
||||
|
||||
class BIM_PT_styles(Panel):
|
||||
bl_label = "IFC Styles"
|
||||
bl_idname = "BIM_PT_styles"
|
||||
@@ -93,7 +91,7 @@ class BIM_PT_style(MaterialButtonsPanel, Panel):
|
||||
row.operator("bim.remove_style", icon="X", text="").style = props.ifc_style_id
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(mat, "diffuse_color", text="Color")
|
||||
|
||||
|
||||
else:
|
||||
row.operator("bim.add_style", icon="ADD")
|
||||
|
||||
@@ -117,6 +115,11 @@ class BIM_PT_style_attributes(Panel):
|
||||
def draw(self, context):
|
||||
if not StyleAttributesData.is_loaded:
|
||||
StyleAttributesData.load()
|
||||
elif (
|
||||
context.active_object.active_material.BIMMaterialProperties.ifc_style_id
|
||||
!= StyleAttributesData.data["ifc_style_id"]
|
||||
):
|
||||
StyleAttributesData.load()
|
||||
|
||||
obj = context.active_object.active_material
|
||||
mprops = obj.BIMMaterialProperties
|
||||
|
||||
@@ -64,19 +64,50 @@ class UnassignType(bpy.types.Operator):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
def exclude_callback(attribute):
|
||||
return attribute.is_a("IfcProfileDef") and attribute.ProfileName
|
||||
|
||||
self.file = IfcStore.get_file()
|
||||
related_objects = (
|
||||
[bpy.data.objects.get(self.related_object)] if self.related_object else context.selected_objects
|
||||
)
|
||||
for related_object in related_objects:
|
||||
oprops = related_object.BIMObjectProperties
|
||||
ifcopenshell.api.run(
|
||||
"type.unassign_type",
|
||||
self.file,
|
||||
**{
|
||||
"related_object": self.file.by_id(oprops.ifc_definition_id),
|
||||
},
|
||||
)
|
||||
objs = [bpy.data.objects.get(self.related_object)] if self.related_object else context.selected_objects
|
||||
for obj in objs:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element or element.is_a("IfcElementType"):
|
||||
continue
|
||||
ifcopenshell.api.run("type.unassign_type", self.file, related_object=element)
|
||||
|
||||
active_representation = tool.Geometry.get_active_representation(obj)
|
||||
active_context = active_representation.ContextOfItems
|
||||
new_active_representation = None
|
||||
|
||||
if element.Representation:
|
||||
representations = []
|
||||
for representation in element.Representation.Representations:
|
||||
resolved_representation = ifcopenshell.util.representation.resolve_representation(representation)
|
||||
if representation == resolved_representation:
|
||||
representations.append(representation)
|
||||
else:
|
||||
# We must unmap representations.
|
||||
copied_representation = ifcopenshell.util.element.copy_deep(
|
||||
tool.Ifc.get(),
|
||||
resolved_representation,
|
||||
exclude=["IfcGeometricRepresentationContext"],
|
||||
exclude_callback=exclude_callback,
|
||||
)
|
||||
representations.append(copied_representation)
|
||||
if representation.ContextOfItems == active_context:
|
||||
new_active_representation = copied_representation
|
||||
element.Representation.Representations = representations
|
||||
|
||||
if new_active_representation:
|
||||
blenderbim.core.geometry.switch_representation(
|
||||
tool.Ifc,
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=new_active_representation,
|
||||
should_reload=False,
|
||||
is_global=False,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -197,6 +228,7 @@ class AddType(bpy.types.Operator, tool.Ifc.Operator):
|
||||
context=body,
|
||||
ifc_representation_class=None,
|
||||
)
|
||||
|
||||
elif template in ("LAYERSET_AXIS2", "LAYERSET_AXIS3"):
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(ifc_file)
|
||||
obj = bpy.data.objects.new(name, None)
|
||||
@@ -230,6 +262,7 @@ class AddType(bpy.types.Operator, tool.Ifc.Operator):
|
||||
elif template == "LAYERSET_AXIS3":
|
||||
axis = "AXIS3"
|
||||
ifcopenshell.api.run("pset.edit_pset", ifc_file, pset=pset, properties={"LayerSetDirection": axis})
|
||||
|
||||
elif template == "PROFILESET":
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(ifc_file)
|
||||
obj = bpy.data.objects.new(name, None)
|
||||
|
||||
@@ -20,6 +20,7 @@ import blenderbim.tool as tool
|
||||
import blenderbim.bim.module.type.prop as type_prop
|
||||
from bpy.types import Panel
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.helper import prop_with_search
|
||||
from blenderbim.bim.module.type.data import TypeData
|
||||
|
||||
|
||||
@@ -69,7 +70,7 @@ class BIM_PT_type(Panel):
|
||||
|
||||
row.prop(props, "relating_type_class", text="")
|
||||
if type_prop.get_relating_type(None, context):
|
||||
row.prop(props, "relating_type", text="")
|
||||
prop_with_search(row, props, "relating_type", text="")
|
||||
row.operator("bim.assign_type", icon="CHECKMARK", text="")
|
||||
else:
|
||||
row.label(text="No Types Found")
|
||||
|
||||
@@ -101,6 +101,7 @@ class SelectIfcFile(bpy.types.Operator, IFCFileSelector):
|
||||
context.window_manager.fileselect_add(self)
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
|
||||
class ReloadSelectedIfcFile(bpy.types.Operator, IFCFileSelector):
|
||||
bl_idname = "bim.reload_selected_ifc_file"
|
||||
bl_label = "Reload selected IFC File"
|
||||
@@ -114,6 +115,7 @@ class ReloadSelectedIfcFile(bpy.types.Operator, IFCFileSelector):
|
||||
context.scene.BIMProperties.ifc_file = context.scene.BIMProperties.ifc_file
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class SelectDataDir(bpy.types.Operator):
|
||||
bl_idname = "bim.select_data_dir"
|
||||
bl_label = "Select Data Directory"
|
||||
@@ -211,6 +213,11 @@ class BIM_OT_add_section_plane(bpy.types.Operator):
|
||||
|
||||
def create_section_compare_node(self):
|
||||
group = bpy.data.node_groups.new("Section Compare", type="ShaderNodeTree")
|
||||
group.inputs.new("NodeSocketFloat", "Value")
|
||||
group.inputs["Value"].default_value = 1.0 # Mandatory multiplier for the last node group
|
||||
group.inputs.new("NodeSocketVector", "Vector")
|
||||
group.outputs.new("NodeSocketFloat", "Value")
|
||||
group.outputs.new("NodeSocketFloat", "Line Decorator")
|
||||
group_input = group.nodes.new(type="NodeGroupInput")
|
||||
group_input.location = 0, 50
|
||||
|
||||
@@ -222,6 +229,12 @@ class BIM_OT_add_section_plane(bpy.types.Operator):
|
||||
greater.inputs[1].default_value = 0
|
||||
greater.location = 400, 0
|
||||
|
||||
compare = group.nodes.new(type="ShaderNodeMath")
|
||||
compare.operation = "COMPARE"
|
||||
compare.inputs[1].default_value = 0
|
||||
compare.inputs[2].default_value = 0.04
|
||||
compare.location = 400, -200
|
||||
|
||||
multiply = group.nodes.new(type="ShaderNodeMath")
|
||||
multiply.operation = "MULTIPLY"
|
||||
multiply.inputs[0].default_value = 1
|
||||
@@ -230,57 +243,68 @@ class BIM_OT_add_section_plane(bpy.types.Operator):
|
||||
group_output = group.nodes.new(type="NodeGroupOutput")
|
||||
group_output.location = 800, 0
|
||||
|
||||
group.links.new(group_input.outputs[""], multiply.inputs[0])
|
||||
group.links.new(group_input.outputs[""], separate_xyz.inputs[0])
|
||||
group.links.new(group_input.outputs["Value"], multiply.inputs[0])
|
||||
group.links.new(group_input.outputs["Vector"], separate_xyz.inputs[0])
|
||||
group.links.new(separate_xyz.outputs[2], greater.inputs[0])
|
||||
group.links.new(greater.outputs[0], multiply.inputs[1])
|
||||
group.links.new(multiply.outputs[0], group_output.inputs[""])
|
||||
group.links.new(multiply.outputs[0], group_output.inputs["Value"])
|
||||
group.links.new(separate_xyz.outputs[2], compare.inputs[0])
|
||||
group.links.new(compare.outputs[0], group_output.inputs["Line Decorator"])
|
||||
|
||||
def create_section_override_node(self, obj, context):
|
||||
group = bpy.data.node_groups.new("Section Override", type="ShaderNodeTree")
|
||||
group.inputs.new("NodeSocketShader", "Shader")
|
||||
group.outputs.new("NodeSocketShader", "Shader")
|
||||
links = group.links
|
||||
nodes = group.nodes
|
||||
|
||||
group_input = nodes.new(type="NodeGroupInput")
|
||||
group_output = nodes.new(type="NodeGroupOutput")
|
||||
group_output.location = 600, 250
|
||||
group_output.location = 800, 250
|
||||
|
||||
backfacing_mix = nodes.new(type="ShaderNodeMixShader")
|
||||
backfacing_mix.location = group_output.location - Vector((400, 350))
|
||||
mix_decorator = group.nodes.new(type="ShaderNodeMixShader")
|
||||
mix_decorator.name = "Line Decorator Mix"
|
||||
mix_decorator.location = group_output.location - Vector((200, 0))
|
||||
|
||||
mix_section = group.nodes.new(type="ShaderNodeMixShader")
|
||||
mix_section.name = "Section Mix"
|
||||
mix_section.inputs[0].default_value = 1 # Directly pass input shader when there is no cutaway
|
||||
mix_section.location = mix_decorator.location - Vector((200, 200))
|
||||
|
||||
transparent = nodes.new(type="ShaderNodeBsdfTransparent")
|
||||
transparent.location = mix_section.location - Vector((200, 100))
|
||||
|
||||
mix_backfacing = nodes.new(type="ShaderNodeMixShader")
|
||||
mix_backfacing.location = mix_section.location - Vector((200, 0))
|
||||
|
||||
group_input.location = mix_backfacing.location - Vector((200, 50))
|
||||
|
||||
backfacing = nodes.new(type="ShaderNodeNewGeometry")
|
||||
backfacing.location = backfacing_mix.location + Vector((-200, 200))
|
||||
group_input.location = backfacing_mix.location - Vector((200, 50))
|
||||
backfacing.location = mix_backfacing.location + Vector((-200, 200))
|
||||
|
||||
emission = nodes.new(type="ShaderNodeEmission")
|
||||
emission.inputs[0].default_value = list(context.scene.BIMProperties.section_plane_colour) + [1]
|
||||
emission.location = backfacing_mix.location - Vector((200, 150))
|
||||
|
||||
transparent = nodes.new(type="ShaderNodeBsdfTransparent")
|
||||
transparent.location = group_output.location - Vector((400, 100))
|
||||
|
||||
section_mix = group.nodes.new(type="ShaderNodeMixShader")
|
||||
section_mix.name = "Section Mix"
|
||||
section_mix.inputs[0].default_value = 1 # Directly pass input shader when there is no cutaway
|
||||
section_mix.location = group_output.location - Vector((200, 0))
|
||||
emission.location = mix_backfacing.location - Vector((200, 150))
|
||||
|
||||
cut_obj = nodes.new(type="ShaderNodeTexCoord")
|
||||
cut_obj.object = obj
|
||||
cut_obj.location = group_output.location - Vector((800, 150))
|
||||
cut_obj.location = backfacing.location - Vector((200, 200))
|
||||
|
||||
section_compare = nodes.new(type="ShaderNodeGroup")
|
||||
section_compare.node_tree = bpy.data.node_groups.get("Section Compare")
|
||||
section_compare.name = "Last Section Compare"
|
||||
section_compare.location = group_output.location - Vector((600, 0))
|
||||
section_compare.location = backfacing.location + Vector((0, 200))
|
||||
|
||||
links.new(cut_obj.outputs["Object"], section_compare.inputs[1])
|
||||
links.new(backfacing.outputs["Backfacing"], backfacing_mix.inputs[0])
|
||||
links.new(group_input.outputs[""], backfacing_mix.inputs[1])
|
||||
links.new(emission.outputs["Emission"], backfacing_mix.inputs[2])
|
||||
links.new(section_compare.outputs[0], section_mix.inputs[0])
|
||||
links.new(transparent.outputs["BSDF"], section_mix.inputs[1])
|
||||
links.new(backfacing_mix.outputs["Shader"], section_mix.inputs[2])
|
||||
links.new(section_mix.outputs["Shader"], group_output.inputs[""])
|
||||
links.new(backfacing.outputs["Backfacing"], mix_backfacing.inputs[0])
|
||||
links.new(group_input.outputs["Shader"], mix_backfacing.inputs[1])
|
||||
links.new(emission.outputs["Emission"], mix_backfacing.inputs[2])
|
||||
links.new(section_compare.outputs["Value"], mix_section.inputs[0])
|
||||
links.new(transparent.outputs[0], mix_section.inputs[1])
|
||||
links.new(mix_backfacing.outputs["Shader"], mix_section.inputs[2])
|
||||
links.new(section_compare.outputs["Line Decorator"], mix_decorator.inputs[0])
|
||||
links.new(mix_section.outputs["Shader"], mix_decorator.inputs[1])
|
||||
links.new(mix_decorator.outputs["Shader"], group_output.inputs["Shader"])
|
||||
|
||||
def append_obj_to_section_override_node(self, obj):
|
||||
group = bpy.data.node_groups.get("Section Override")
|
||||
@@ -695,7 +719,7 @@ class BIM_OT_enum_property_search(bpy.types.Operator):
|
||||
|
||||
class EditBlenderCollection(bpy.types.Operator):
|
||||
bl_idname = "bim.edit_blender_collection"
|
||||
bl_label = "Add or Remove blender collection item"
|
||||
bl_label = "Add or Remove Blender Collection Item"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
option: bpy.props.StringProperty(description="add or remove item from collection")
|
||||
collection: bpy.props.StringProperty(description="collection to be edited")
|
||||
|
||||
@@ -173,6 +173,18 @@ def update_section_color(self, context):
|
||||
pass
|
||||
|
||||
|
||||
def update_section_line_decorator(self, context):
|
||||
compare_node_group = bpy.data.node_groups.get("Section Compare")
|
||||
if compare_node_group is None:
|
||||
return
|
||||
for node in compare_node_group.nodes:
|
||||
if not hasattr(node, "operation"):
|
||||
continue
|
||||
if node.operation == "COMPARE":
|
||||
node.inputs[2].default_value = self.section_line_decorator_width
|
||||
break
|
||||
|
||||
|
||||
class StrProperty(PropertyGroup):
|
||||
pass
|
||||
|
||||
@@ -313,13 +325,20 @@ class BIMProperties(PropertyGroup):
|
||||
last_transaction: StringProperty(name="Last Transaction")
|
||||
should_section_selected_objects: BoolProperty(name="Section Selected Objects", default=False)
|
||||
section_plane_colour: FloatVectorProperty(
|
||||
name="Temporary Section Cutaway Colour",
|
||||
name="Cutaway Colour",
|
||||
subtype="COLOR",
|
||||
default=(1, 0, 0),
|
||||
min=0.0,
|
||||
max=1.0,
|
||||
update=update_section_color,
|
||||
)
|
||||
section_line_decorator_width: FloatProperty(
|
||||
name="Line Decorator Width",
|
||||
default=0.04,
|
||||
min=0.0,
|
||||
soft_max=1.0,
|
||||
update=update_section_line_decorator,
|
||||
)
|
||||
area_unit: EnumProperty(
|
||||
default="SQUARE_METRE",
|
||||
items=[
|
||||
@@ -353,22 +372,6 @@ class BIMProperties(PropertyGroup):
|
||||
],
|
||||
name="IFC Volume Unit",
|
||||
)
|
||||
metric_precision: FloatProperty(default=0, name="Drawing Metric Precision")
|
||||
imperial_precision: EnumProperty(
|
||||
items=[
|
||||
("NONE", "No rounding", ""),
|
||||
("1", 'Nearest 1"', ""),
|
||||
("1/2", 'Nearest 1/2"', ""),
|
||||
("1/4", 'Nearest 1/4"', ""),
|
||||
("1/8", 'Nearest 1/8"', ""),
|
||||
("1/16", 'Nearest 1/16"', ""),
|
||||
("1/32", 'Nearest 1/32"', ""),
|
||||
("1/64", 'Nearest 1/64"', ""),
|
||||
("1/128", 'Nearest 1/128"', ""),
|
||||
("1/256", 'Nearest 1/256"', ""),
|
||||
],
|
||||
name="Drawing Imperial Precision",
|
||||
)
|
||||
|
||||
|
||||
class IfcParameter(PropertyGroup):
|
||||
|
||||
@@ -78,11 +78,9 @@ class BIM_PT_section_plane(Panel):
|
||||
layout.use_property_split = True
|
||||
props = context.scene.BIMProperties
|
||||
|
||||
row = layout.row()
|
||||
row.prop(props, "should_section_selected_objects")
|
||||
|
||||
row = layout.row()
|
||||
row.prop(props, "section_plane_colour")
|
||||
layout.prop(props, "should_section_selected_objects")
|
||||
layout.prop(props, "section_plane_colour")
|
||||
layout.prop(props, "section_line_decorator_width")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.operator("bim.add_section_plane")
|
||||
@@ -175,6 +173,12 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
|
||||
|
||||
row = self.layout.row()
|
||||
row.prop(context.scene.DocProperties, "decorations_colour")
|
||||
row = self.layout.row()
|
||||
row.prop(context.scene.BIMModelProperties, "decorator_color_selected")
|
||||
row = self.layout.row()
|
||||
row.prop(context.scene.BIMModelProperties, "decorator_color_unselected")
|
||||
row = self.layout.row()
|
||||
row.prop(context.scene.BIMModelProperties, "decorator_color_special")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(context.scene.BIMProperties, "schema_dir")
|
||||
@@ -184,6 +188,23 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
|
||||
row.prop(context.scene.BIMProperties, "data_dir")
|
||||
row.operator("bim.select_data_dir", icon="FILE_FOLDER", text="")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(context.scene.DocProperties, "sheets_dir")
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(context.scene.DocProperties, "layouts_dir")
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(context.scene.DocProperties, "titleblocks_dir")
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(context.scene.DocProperties, "drawings_dir")
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(context.scene.DocProperties, "stylesheet_path")
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(context.scene.DocProperties, "markers_path")
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(context.scene.DocProperties, "symbols_path")
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(context.scene.DocProperties, "patterns_path")
|
||||
|
||||
row = layout.row()
|
||||
row.operator("bim.configure_visibility")
|
||||
|
||||
@@ -198,11 +219,6 @@ def ifc_units(self, context):
|
||||
row.prop(props, "area_unit")
|
||||
row = layout.row()
|
||||
row.prop(props, "volume_unit")
|
||||
row = layout.row()
|
||||
if scene.unit_settings.system == "IMPERIAL":
|
||||
row.prop(props, "imperial_precision")
|
||||
else:
|
||||
row.prop(props, "metric_precision")
|
||||
|
||||
|
||||
# Scene panel groups
|
||||
|
||||
@@ -45,13 +45,13 @@ def disable_editing_assigned_product(drawing, obj=None):
|
||||
def edit_assigned_product(ifc, drawing, obj=None, product=None):
|
||||
element = ifc.get_entity(obj)
|
||||
existing_product = drawing.get_assigned_product(element)
|
||||
if existing_product == product:
|
||||
return
|
||||
if existing_product:
|
||||
ifc.run("drawing.unassign_product", relating_product=existing_product, related_object=element)
|
||||
if product:
|
||||
ifc.run("drawing.assign_product", relating_product=product, related_object=element)
|
||||
drawing.update_text_value(obj)
|
||||
if existing_product != product:
|
||||
if existing_product:
|
||||
ifc.run("drawing.unassign_product", relating_product=existing_product, related_object=element)
|
||||
if product:
|
||||
ifc.run("drawing.assign_product", relating_product=product, related_object=element)
|
||||
drawing.update_text_value(obj)
|
||||
|
||||
drawing.disable_editing_assigned_product(obj)
|
||||
|
||||
|
||||
@@ -66,26 +66,71 @@ def disable_editing_sheets(drawing):
|
||||
|
||||
def add_sheet(ifc, drawing, titleblock=None):
|
||||
sheet = ifc.run("document.add_information")
|
||||
layout = ifc.run("document.add_reference", information=sheet)
|
||||
titleblock_reference = ifc.run("document.add_reference", information=sheet)
|
||||
identification = drawing.generate_sheet_identification()
|
||||
identification = drawing.ensure_unique_identification(identification)
|
||||
attributes = {"Identification": identification, "Name": "UNTITLED", "Scope": "DOCUMENTATION"}
|
||||
if ifc.get_schema() == "IFC2X3":
|
||||
attributes["DocumentId"] = attributes["Identification"]
|
||||
del attributes["Identification"]
|
||||
attributes = {"DocumentId": identification, "Name": "UNTITLED", "Scope": "SHEET"}
|
||||
else:
|
||||
attributes = {"Identification": identification, "Name": "UNTITLED", "Scope": "SHEET"}
|
||||
ifc.run("document.edit_information", information=sheet, attributes=attributes)
|
||||
ifc.run(
|
||||
"document.edit_reference",
|
||||
reference=layout,
|
||||
attributes={"Location": drawing.get_default_layout_path(identification, "UNTITLED"), "Description": "LAYOUT"},
|
||||
)
|
||||
ifc.run(
|
||||
"document.edit_reference",
|
||||
reference=titleblock_reference,
|
||||
attributes={"Location": drawing.get_default_titleblock_path(titleblock), "Description": "TITLEBLOCK"},
|
||||
)
|
||||
drawing.create_svg_sheet(sheet, titleblock)
|
||||
drawing.import_sheets()
|
||||
|
||||
|
||||
def open_sheet(drawing, sheet=None):
|
||||
drawing.open_svg(drawing.get_document_uri(sheet))
|
||||
drawing.open_svg(drawing.get_document_uri(sheet, "LAYOUT"))
|
||||
|
||||
|
||||
def remove_sheet(ifc, drawing, sheet=None):
|
||||
for reference in drawing.get_document_references(sheet):
|
||||
if drawing.get_reference_description(reference) in ("LAYOUT", "SHEET", "REVISION", "RASTER"):
|
||||
uri = ifc.resolve_uri(drawing.get_document_uri(reference))
|
||||
if drawing.does_file_exist(uri):
|
||||
drawing.delete_file(uri)
|
||||
ifc.run("document.remove_information", information=sheet)
|
||||
drawing.import_sheets()
|
||||
|
||||
|
||||
def rename_sheet(ifc, drawing, sheet=None, identification=None, name=None):
|
||||
ifc.run(
|
||||
"document.edit_information", information=sheet, attributes={"Identification": identification, "Name": name}
|
||||
)
|
||||
for reference in drawing.get_document_references(sheet):
|
||||
description = drawing.get_reference_description(reference)
|
||||
if description == "SHEET":
|
||||
old_location = drawing.get_reference_location(reference)
|
||||
new_location = drawing.get_default_sheet_path(identification, name)
|
||||
if old_location != new_location:
|
||||
ifc.run("document.edit_reference", reference=reference, attributes={"Location": new_location})
|
||||
old_location = ifc.resolve_uri(old_location)
|
||||
if drawing.does_file_exist(old_location):
|
||||
drawing.move_file(old_location, ifc.resolve_uri(new_location))
|
||||
elif description == "LAYOUT":
|
||||
old_location = drawing.get_reference_location(reference)
|
||||
new_location = drawing.get_default_layout_path(identification, name)
|
||||
if old_location != new_location:
|
||||
ifc.run("document.edit_reference", reference=reference, attributes={"Location": new_location})
|
||||
old_location = ifc.resolve_uri(old_location)
|
||||
if drawing.does_file_exist(old_location):
|
||||
drawing.move_file(old_location, ifc.resolve_uri(new_location))
|
||||
|
||||
|
||||
def rename_reference(ifc, reference=None, identification=None):
|
||||
ifc.run("document.edit_reference", reference=reference, attributes={"Identification": identification})
|
||||
|
||||
|
||||
def load_schedules(drawing):
|
||||
drawing.import_schedules()
|
||||
drawing.enable_editing_schedules()
|
||||
@@ -98,13 +143,13 @@ def disable_editing_schedules(drawing):
|
||||
def add_schedule(ifc, drawing, uri=None):
|
||||
schedule = ifc.run("document.add_information")
|
||||
reference = ifc.run("document.add_reference", information=schedule)
|
||||
name = drawing.get_path_filename(uri)
|
||||
if ifc.get_schema() == "IFC2X3":
|
||||
attributes = {"DocumentId": "X", "Name": "UNTITLED", "Scope": "SCHEDULE"}
|
||||
ifc.run("document.edit_information", information=schedule, attributes=attributes)
|
||||
ifc.run("document.edit_reference", reference=reference, attributes={"Location": uri})
|
||||
attributes = {"DocumentId": "X", "Name": name, "Scope": "SCHEDULE"}
|
||||
else:
|
||||
attributes = {"Identification": "X", "Name": "UNTITLED", "Scope": "SCHEDULE", "Location": uri}
|
||||
ifc.run("document.edit_information", information=schedule, attributes=attributes)
|
||||
attributes = {"Identification": "X", "Name": name, "Scope": "SCHEDULE"}
|
||||
ifc.run("document.edit_information", information=schedule, attributes=attributes)
|
||||
ifc.run("document.edit_reference", reference=reference, attributes={"Location": uri})
|
||||
drawing.import_schedules()
|
||||
|
||||
|
||||
@@ -114,7 +159,7 @@ def remove_schedule(ifc, drawing, schedule=None):
|
||||
|
||||
|
||||
def open_schedule(drawing, schedule=None):
|
||||
drawing.open_spreadsheet(drawing.get_schedule_location(schedule))
|
||||
drawing.open_spreadsheet(drawing.get_document_uri(schedule))
|
||||
|
||||
|
||||
def update_schedule_name(ifc, drawing, schedule=None, name=None):
|
||||
@@ -148,19 +193,39 @@ def add_drawing(ifc, collector, drawing, target_view=None, location_hint=None):
|
||||
ifc.run("group.assign_group", group=group, products=[element])
|
||||
collector.assign(camera)
|
||||
pset = ifc.run("pset.add_pset", product=element, name="EPset_Drawing")
|
||||
if drawing.get_unit_system() == "METRIC":
|
||||
scale = "1/100"
|
||||
human_scale = "1:100"
|
||||
else:
|
||||
scale = "1/96"
|
||||
human_scale = "1/8\"=1'-0\""
|
||||
ifc.run(
|
||||
"pset.edit_pset",
|
||||
pset=pset,
|
||||
properties={
|
||||
"TargetView": target_view,
|
||||
"Scale": "1/100",
|
||||
"HumanScale": "1:100",
|
||||
"Scale": scale,
|
||||
"HumanScale": human_scale,
|
||||
"HasUnderlay": False,
|
||||
"HasLinework": True,
|
||||
"HasAnnotation": True,
|
||||
"GlobalReferencing": True,
|
||||
"Stylesheet": drawing.get_default_drawing_resource_path("Stylesheet"),
|
||||
"Markers": drawing.get_default_drawing_resource_path("Markers"),
|
||||
"Symbols": drawing.get_default_drawing_resource_path("Symbols"),
|
||||
"Patterns": drawing.get_default_drawing_resource_path("Patterns"),
|
||||
},
|
||||
)
|
||||
information = ifc.run("document.add_information")
|
||||
uri = drawing.get_default_drawing_path(drawing_name)
|
||||
reference = ifc.run("document.add_reference", information=information)
|
||||
if ifc.get_schema() == "IFC2X3":
|
||||
attributes = {"DocumentId": "X", "Name": drawing_name, "Scope": "DRAWING"}
|
||||
else:
|
||||
attributes = {"Identification": "X", "Name": drawing_name, "Scope": "DRAWING"}
|
||||
ifc.run("document.edit_information", information=information, attributes=attributes)
|
||||
ifc.run("document.edit_reference", reference=reference, attributes={"Location": uri})
|
||||
ifc.run("document.assign_document", product=element, document=reference)
|
||||
drawing.import_drawings()
|
||||
|
||||
|
||||
@@ -182,6 +247,21 @@ def duplicate_drawing(ifc, drawing_tool, drawing=None, should_duplicate_annotati
|
||||
drawing_tool.copy_representation(annotation, new_annotation)
|
||||
ifc.run("group.unassign_group", group=group, product=new_annotation)
|
||||
ifc.run("group.assign_group", group=new_group, products=[new_annotation])
|
||||
|
||||
old_reference = drawing_tool.get_drawing_document(new_drawing)
|
||||
ifc.run("document.unassign_document", product=new_drawing, document=old_reference)
|
||||
|
||||
information = ifc.run("document.add_information")
|
||||
uri = drawing_tool.get_default_drawing_path(drawing_name)
|
||||
reference = ifc.run("document.add_reference", information=information)
|
||||
if ifc.get_schema() == "IFC2X3":
|
||||
attributes = {"DocumentId": "X", "Name": drawing_name, "Scope": "DRAWING"}
|
||||
else:
|
||||
attributes = {"Identification": "X", "Name": drawing_name, "Scope": "DRAWING"}
|
||||
ifc.run("document.edit_information", information=information, attributes=attributes)
|
||||
ifc.run("document.edit_reference", reference=reference, attributes={"Location": uri})
|
||||
ifc.run("document.assign_document", product=new_drawing, document=reference)
|
||||
|
||||
drawing_tool.import_drawings()
|
||||
return new_drawing
|
||||
|
||||
@@ -190,16 +270,24 @@ def remove_drawing(ifc, drawing_tool, drawing=None):
|
||||
collection = drawing_tool.get_drawing_collection(drawing)
|
||||
if collection:
|
||||
drawing_tool.delete_collection(collection)
|
||||
group = drawing_tool.get_drawing_group(drawing)
|
||||
if group:
|
||||
drawing_tool.delete_drawing_elements(drawing_tool.get_group_elements(group))
|
||||
ifc.run("group.remove_group", group=group)
|
||||
|
||||
for reference in drawing_tool.get_drawing_references(drawing):
|
||||
reference_obj = ifc.get_object(reference)
|
||||
if reference_obj:
|
||||
drawing_tool.delete_object(reference_obj)
|
||||
ifc.run("root.remove_product", product=reference)
|
||||
ifc.run("root.remove_product", product=drawing)
|
||||
|
||||
information = drawing_tool.get_reference_document(drawing_tool.get_drawing_document(drawing))
|
||||
uri = ifc.resolve_uri(drawing_tool.get_document_uri(information))
|
||||
if drawing_tool.does_file_exist(uri):
|
||||
drawing_tool.delete_file(uri)
|
||||
ifc.run("document.remove_information", information=information)
|
||||
|
||||
group = drawing_tool.get_drawing_group(drawing)
|
||||
if group:
|
||||
drawing_tool.delete_drawing_elements(drawing_tool.get_group_elements(group))
|
||||
ifc.run("group.remove_group", group=group)
|
||||
|
||||
drawing_tool.import_drawings()
|
||||
|
||||
|
||||
@@ -213,11 +301,31 @@ def update_drawing_name(ifc, drawing_tool, drawing=None, name=None):
|
||||
if collection:
|
||||
drawing_tool.set_drawing_collection_name(group, collection)
|
||||
|
||||
reference = drawing_tool.get_drawing_document(drawing)
|
||||
information = drawing_tool.get_reference_document(reference)
|
||||
ifc.run("document.edit_information", information=information, attributes={"Name": name})
|
||||
old_location = drawing_tool.get_reference_location(reference)
|
||||
new_location = drawing_tool.get_default_drawing_path(name)
|
||||
if old_location != new_location:
|
||||
ifc.run("document.edit_reference", reference=reference, attributes={"Location": new_location})
|
||||
resolved_old_location = ifc.resolve_uri(old_location)
|
||||
if drawing_tool.does_file_exist(resolved_old_location):
|
||||
drawing_tool.move_file(resolved_old_location, ifc.resolve_uri(new_location))
|
||||
|
||||
for reference in drawing_tool.get_references_with_location(old_location):
|
||||
ifc.run("document.edit_reference", reference=reference, attributes={"Location": new_location})
|
||||
sheet = drawing_tool.get_reference_document(reference)
|
||||
if not sheet:
|
||||
uri = ifc.resolve_uri(drawing_tool.get_document_uri(sheet, "LAYOUT"))
|
||||
if drawing_tool.does_file_exist(uri):
|
||||
drawing_tool.update_embedded_svg_location(uri, old_location, new_location)
|
||||
|
||||
|
||||
def add_annotation(ifc, collector, drawing_tool, drawing=None, object_type=None):
|
||||
context = drawing_tool.get_annotation_context(drawing_tool.get_drawing_target_view(drawing))
|
||||
context = drawing_tool.get_annotation_context(target_view := drawing_tool.get_drawing_target_view(drawing))
|
||||
if not context:
|
||||
return "No annotation context for drawing"
|
||||
return f"No annotation context Annotation/{target_view} for drawing"
|
||||
|
||||
drawing_tool.show_decorations()
|
||||
obj = drawing_tool.create_annotation_object(drawing, object_type)
|
||||
element = ifc.get_entity(obj)
|
||||
@@ -237,7 +345,7 @@ def add_annotation(ifc, collector, drawing_tool, drawing=None, object_type=None)
|
||||
|
||||
def build_schedule(drawing, schedule=None):
|
||||
drawing.create_svg_schedule(schedule)
|
||||
drawing.open_svg(drawing.get_document_uri(schedule))
|
||||
drawing.open_svg(drawing.get_path_with_ext(drawing.get_document_uri(schedule), "svg"))
|
||||
|
||||
|
||||
def sync_references(ifc, collector, drawing_tool, drawing=None):
|
||||
@@ -257,11 +365,9 @@ def sync_references(ifc, collector, drawing_tool, drawing=None):
|
||||
should_create_annotation = False
|
||||
|
||||
if annotation:
|
||||
if ifc.is_deleted(annotation):
|
||||
if reference_obj and (ifc.is_moved(reference_obj) or ifc.is_edited(reference_obj)):
|
||||
should_delete_existing_annotation = True
|
||||
elif reference_obj and (ifc.is_moved(reference_obj) or ifc.is_edited(reference_obj)):
|
||||
should_delete_existing_annotation = True
|
||||
elif not reference_obj and ifc.is_deleted(reference_element):
|
||||
elif not reference_obj:
|
||||
should_delete_existing_annotation = True
|
||||
|
||||
if reference_obj and (should_delete_existing_annotation or not annotation):
|
||||
@@ -287,8 +393,8 @@ def sync_references(ifc, collector, drawing_tool, drawing=None):
|
||||
drawing_tool.sync_object_representation(reference_obj)
|
||||
|
||||
|
||||
def select_assigned_product(drawing):
|
||||
drawing.select_assigned_product()
|
||||
def select_assigned_product(drawing, context):
|
||||
drawing.select_assigned_product(context)
|
||||
|
||||
|
||||
def activate_drawing_view(ifc, drawing_tool, drawing):
|
||||
@@ -296,4 +402,4 @@ def activate_drawing_view(ifc, drawing_tool, drawing):
|
||||
if not camera:
|
||||
camera = drawing_tool.import_drawing(drawing)
|
||||
drawing_tool.import_annotations_in_group(drawing_tool.get_drawing_group(drawing))
|
||||
drawing_tool.activate_view(camera)
|
||||
drawing_tool.activate_drawing(camera)
|
||||
|
||||
@@ -106,6 +106,7 @@ def switch_representation(
|
||||
data = existing_data
|
||||
|
||||
geometry.change_object_data(obj, data, is_global=is_global)
|
||||
geometry.record_object_materials(obj)
|
||||
|
||||
if should_reload and existing_data:
|
||||
geometry.delete_data(existing_data)
|
||||
@@ -123,20 +124,20 @@ def remove_representation(ifc, geometry, obj=None, representation=None):
|
||||
otherwise it will replace object with empty."""
|
||||
|
||||
element = ifc.get_entity(obj)
|
||||
type = geometry.get_element_type(element)
|
||||
element_type = geometry.get_element_type(element)
|
||||
data = None
|
||||
if type and (geometry.is_mapped_representation(representation) or geometry.is_type_product(element)):
|
||||
if element_type and (geometry.is_mapped_representation(representation) or geometry.is_type_product(element)):
|
||||
representation = geometry.resolve_mapped_representation(representation)
|
||||
data = geometry.get_representation_data(representation)
|
||||
if data and geometry.has_data_users(data):
|
||||
for element in geometry.get_elements_of_type(type):
|
||||
for element in geometry.get_elements_of_type(element_type):
|
||||
obj = ifc.get_object(element)
|
||||
if obj:
|
||||
obj = geometry.replace_object_with_empty(obj)
|
||||
obj = ifc.get_object(type)
|
||||
obj = ifc.get_object(element_type)
|
||||
if obj:
|
||||
obj = geometry.replace_object_with_empty(obj)
|
||||
ifc.run("geometry.unassign_representation", product=type, representation=representation)
|
||||
ifc.run("geometry.unassign_representation", product=element_type, representation=representation)
|
||||
ifc.run("geometry.remove_representation", representation=representation)
|
||||
else:
|
||||
data = geometry.get_representation_data(representation)
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
def create_repo(ifcgit, ifc):
|
||||
path_ifc = ifc.get_path()
|
||||
path_dir = ifcgit.get_path_dir(path_ifc)
|
||||
ifcgit.init_repo(path_dir)
|
||||
|
||||
|
||||
def add_file(ifcgit, ifc):
|
||||
path_ifc = ifc.get_path()
|
||||
repo = ifcgit.repo_from_path(path_ifc)
|
||||
ifcgit.add_file_to_repo(repo, path_ifc)
|
||||
|
||||
|
||||
def discard_uncomitted(ifcgit, ifc):
|
||||
path_ifc = ifc.get_path()
|
||||
# NOTE this is calling the git binary in a subprocess
|
||||
ifcgit.git_checkout(path_ifc)
|
||||
ifcgit.load_project(path_ifc)
|
||||
|
||||
|
||||
def commit_changes(ifcgit, ifc, repo, context):
|
||||
path_ifc = ifc.get_path()
|
||||
ifcgit.git_commit(path_ifc)
|
||||
|
||||
if repo.head.is_detached:
|
||||
ifcgit.create_new_branch()
|
||||
|
||||
|
||||
def refresh_revision_list(ifcgit, repo, ifc):
|
||||
ifcgit.clear_commits_list()
|
||||
path_ifc = ifc.get_path()
|
||||
lookup = ifcgit.tags_by_hexsha(repo)
|
||||
ifcgit.get_commits_list(path_ifc, lookup)
|
||||
|
||||
|
||||
def colourise_revision(ifcgit, context):
|
||||
|
||||
step_ids = ifcgit.get_revisions_step_ids()
|
||||
if not step_ids:
|
||||
return
|
||||
modified_shape_object_step_ids = ifcgit.get_modified_shape_object_step_ids(step_ids)
|
||||
final_step_ids = ifcgit.update_step_ids(step_ids, modified_shape_object_step_ids)
|
||||
ifcgit.colourise(final_step_ids)
|
||||
|
||||
|
||||
def colourise_uncommitted(ifcgit, ifc, repo):
|
||||
path_ifc = ifc.get_path()
|
||||
step_ids = ifcgit.ifc_diff_ids(repo, None, "HEAD", path_ifc)
|
||||
ifcgit.colourise(step_ids)
|
||||
|
||||
|
||||
def switch_revision(ifcgit, ifc):
|
||||
# FIXME bad things happen when switching to a revision that predates current project
|
||||
|
||||
path_ifc = ifc.get_path()
|
||||
ifcgit.switch_to_revision_item()
|
||||
ifcgit.load_project(path_ifc)
|
||||
|
||||
|
||||
def merge_branch(ifcgit, ifc, operator):
|
||||
path_ifc = ifc.get_path()
|
||||
ifcgit.config_ifcmerge()
|
||||
ifcgit.execute_merge(path_ifc, operator)
|
||||