mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 09:21:46 +00:00
Compare commits
491 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1eef746f60 | |||
| 1696ca5e08 | |||
| c011458ea2 | |||
| abf809f9dd | |||
| 9257816e62 | |||
| d47125b28f | |||
| b7453f584e | |||
| 9c4fa666c9 | |||
| a970120fac | |||
| 72907cd3f0 | |||
| ae7c9f35a6 | |||
| 0b111084b0 | |||
| 2ee0dcaf64 | |||
| 70eabead92 | |||
| 5a30d9fb9b | |||
| 3126db342d | |||
| c0db03438f | |||
| 1542217117 | |||
| 5b4010c67d | |||
| c18d96f0e3 | |||
| f9173700a6 | |||
| 7100a7fa8e | |||
| 0078d691d3 | |||
| fd626e37a0 | |||
| e62d7ad9ef | |||
| 80eebc03d3 | |||
| 62fea990d9 | |||
| 7ab01e6ac0 | |||
| 74a025b81c | |||
| ca8ed87f2a | |||
| 9fbcb3900f | |||
| 1533283822 | |||
| 6beca6382f | |||
| a1caef5b4f | |||
| 7da96b4951 | |||
| 66c275f617 | |||
| 52a8fcd86e | |||
| 3ba7ecdb95 | |||
| 36f7625cee | |||
| ee82714423 | |||
| 281147367e | |||
| 0beea4255a | |||
| b03c4da7bf | |||
| c0402687b5 | |||
| c6fa7161b6 | |||
| 64516a173a | |||
| f507e3e8bd | |||
| 2f15d73b30 | |||
| 53cc6f0bd6 | |||
| 7680652c28 | |||
| e8b1d00e6d | |||
| 6ec7e48423 | |||
| ccf09b962a | |||
| 485d41ae09 | |||
| b67c5a0828 | |||
| 7b92057227 | |||
| a980ce1bfa | |||
| 51b7f24a00 | |||
| 136e1c3b3f | |||
| c094517a10 | |||
| b61a11d5b7 | |||
| 012da8fcb9 | |||
| d64c1358d7 | |||
| 0252701f7a | |||
| 6fed9bf795 | |||
| b088a763d8 | |||
| 104fe56612 | |||
| 9e0760b8d2 | |||
| f26986645a | |||
| a86456392b | |||
| 61de0ad64c | |||
| 243eee9ebf | |||
| f23ac034c7 | |||
| 363ede9801 | |||
| 18d26df24e | |||
| 0df62d5746 | |||
| f5031035c7 | |||
| fc50bdd3a0 | |||
| 3117d5b2e7 | |||
| e6dc1d1acd | |||
| bd8afabce3 | |||
| 01da8498f2 | |||
| a34b8d06ec | |||
| 5764cfcbb9 | |||
| 1ad2465ad4 | |||
| f28e28abc0 | |||
| 97e321dd12 | |||
| fc4b36e759 | |||
| 832cdce934 | |||
| e45974df62 | |||
| bba118ed19 | |||
| 709590401c | |||
| ab74d1456b | |||
| 4457da9042 | |||
| d501118eb4 | |||
| 0a03d20133 | |||
| f3f56c0891 | |||
| a689b31279 | |||
| 4d0be19720 | |||
| fb7591caef | |||
| 8efaf2fbcb | |||
| d41068e132 | |||
| 4e65f143da | |||
| 40656297a6 | |||
| f88835b87b | |||
| 8f18db64cf | |||
| fdd473234a | |||
| 569f56f713 | |||
| 61607f9de2 | |||
| 40b34f6a33 | |||
| 89b0674b08 | |||
| 37e4f98857 | |||
| e89a9cd01c | |||
| 20b63f530a | |||
| 183230b7f5 | |||
| 7e2b40176c | |||
| 3aff29dec1 | |||
| 2ce32c6521 | |||
| 9d84172f15 | |||
| 8341931a62 | |||
| 81a4ff711c | |||
| 2da86c170f | |||
| 41a176cb6d | |||
| 10e96239a1 | |||
| 897dfb1e33 | |||
| c1d972f7bf | |||
| d9196882dc | |||
| 580f8f6218 | |||
| e177f5f051 | |||
| 1a5c501af0 | |||
| 12c10f98a3 | |||
| e3a7f76276 | |||
| 046ce2b8e7 | |||
| 6dab4501ad | |||
| 2dc9dc2000 | |||
| addce9f0ea | |||
| 4a368938c3 | |||
| a0f68baefc | |||
| 3decb48ed0 | |||
| 2d330f4c90 | |||
| 1c996013b6 | |||
| 65cb47e2d8 | |||
| e9d0b8ab66 | |||
| 0bb9bcd9fb | |||
| 997ebe48df | |||
| 0cdf72fde1 | |||
| 97c6b99d57 | |||
| ed5d28de54 | |||
| f5c102402f | |||
| 7aa04611a1 | |||
| 5c7f14cc3c | |||
| 9e1249d8ee | |||
| 6cb7234cb5 | |||
| aab6a21bf3 | |||
| c72f960ed4 | |||
| b63b4ce326 | |||
| 71113b56bf | |||
| ef57c209a0 | |||
| e99c154cb2 | |||
| 520860bdb9 | |||
| 103e8a545b | |||
| d35c5c433f | |||
| 7dc98612e4 | |||
| 3853258b27 | |||
| 6825d26700 | |||
| 900c35e9fe | |||
| 7aafa87b52 | |||
| e719055d9a | |||
| a956038bfc | |||
| 5c2ae98c09 | |||
| 99d9c0f5bf | |||
| 2fbb9c569b | |||
| 4a9d1ff038 | |||
| bcf5f07718 | |||
| a3572f3372 | |||
| 6cd9161986 | |||
| 982f4a4965 | |||
| c5aab16696 | |||
| 6e1b18ba33 | |||
| 2b3d547c99 | |||
| a34be6ae5d | |||
| dfc5a280a4 | |||
| 6a2bbfa85c | |||
| ddef4820d0 | |||
| e6b05b547b | |||
| 5e8ec48d98 | |||
| 9c21304629 | |||
| 1278f98df6 | |||
| d31b470e83 | |||
| 1ecb25684e | |||
| 5999976d37 | |||
| 51e5f843ba | |||
| 3d787338fb | |||
| de67648f3e | |||
| afa803ea62 | |||
| 3a0aaee03b | |||
| a5353e02b5 | |||
| 1acc743370 | |||
| 375f4f2a86 | |||
| d4b88fd422 | |||
| df6cc74a87 | |||
| 80846061f2 | |||
| 80f9590410 | |||
| 16cda28d16 | |||
| 24ea024a0d | |||
| e90d7ccdb1 | |||
| 1a3bf513a7 | |||
| a855c40426 | |||
| c8ede38edb | |||
| 704e27ecca | |||
| 438ddc17f5 | |||
| a8dfed50e4 | |||
| 8cb99a77cf | |||
| f9b6e6f098 | |||
| 1365b76149 | |||
| 47cc3158fd | |||
| bac683e105 | |||
| 840818d681 | |||
| 206933a2f2 | |||
| ff209b4c11 | |||
| 4bc987924d | |||
| f26cfc93ea | |||
| 4bbb6cd2df | |||
| 9411a1d94b | |||
| 6a287ced7c | |||
| fb02ac34d4 | |||
| 81fd84223a | |||
| a8f1a28ac2 | |||
| d2f4f0d8c2 | |||
| 1c17cccc45 | |||
| 061930bc8e | |||
| 28cc168b3b | |||
| 88f53f7931 | |||
| 755fa81817 | |||
| 36fab12847 | |||
| 32393136d2 | |||
| c7b3331496 | |||
| 7c0c934b4f | |||
| 62944e04e9 | |||
| 3a93b15b55 | |||
| 83cd00271b | |||
| 820686a39b | |||
| 4fe896a3b6 | |||
| 975b1c444c | |||
| c3f2046491 | |||
| 3a1557bedc | |||
| a71b5e1f11 | |||
| 4806b9a260 | |||
| 0d67c43e16 | |||
| e2b52e7ed9 | |||
| 3c53067567 | |||
| 5ad7d4bb2a | |||
| 3803942c5f | |||
| 678f50f17d | |||
| 04fe2bdf2e | |||
| ed990c3e83 | |||
| 741c78cd6c | |||
| 2b88c542aa | |||
| 1f6b55e639 | |||
| 9f3fb13cde | |||
| 42f1d9c160 | |||
| f0875835c1 | |||
| 0828a89b66 | |||
| a8ca66b425 | |||
| 92b03c6b59 | |||
| d316efe902 | |||
| 77e4e939ce | |||
| fe7504edf2 | |||
| a8f1ab1923 | |||
| 1b5f9497d4 | |||
| a7bbb35c5e | |||
| 1787f064f6 | |||
| d1c24ebba4 | |||
| cc6dacb286 | |||
| c67eb60fd5 | |||
| 59c482489f | |||
| 7a0963617e | |||
| e4ec1dc457 | |||
| b035129788 | |||
| 78be8114ac | |||
| 18ad025c5d | |||
| 5825e0c71b | |||
| df64ca4cfb | |||
| fc1f5496a0 | |||
| 5a9dbc5a36 | |||
| eaeeede287 | |||
| e78b3a3977 | |||
| a8ca8217ea | |||
| 77c2e30733 | |||
| 5a3a387b8c | |||
| f68f60832c | |||
| b05417a4d8 | |||
| c2a4e30fae | |||
| 1a0280928b | |||
| dda16c847d | |||
| 47c6c2ce9a | |||
| 3758c454c9 | |||
| 4b6424e0e1 | |||
| d71887d846 | |||
| d61945a575 | |||
| 45e6ae6708 | |||
| ebbf30a88c | |||
| e5769766af | |||
| 64b9d2e5e3 | |||
| 3213ffe760 | |||
| a851689bca | |||
| 98f2bd66b6 | |||
| 1c12a98b68 | |||
| a3859385c4 | |||
| c7394faf51 | |||
| 3907ae656c | |||
| 5fc31be8aa | |||
| 7976fb4d09 | |||
| 01635ecd8a | |||
| f6657113b6 | |||
| 480f1a91a6 | |||
| 4865b62ea9 | |||
| 9a440899be | |||
| 3ee6bdf207 | |||
| 1f43ec8709 | |||
| d030582fbb | |||
| 60a8d9b2fd | |||
| a5de5d8c0e | |||
| b506e7dbbc | |||
| db8f1a9b70 | |||
| 032f52d434 | |||
| 1003e8de91 | |||
| c6ef402b60 | |||
| 5805653431 | |||
| 186da73135 | |||
| 528481e774 | |||
| 886ab52556 | |||
| 8196f31d82 | |||
| de3b844829 | |||
| fa4e19eba7 | |||
| 1488ae40ee | |||
| aa004ef0c6 | |||
| 0c946fae36 | |||
| f297739836 | |||
| b33e58f40b | |||
| 30d9743ae7 | |||
| c974306636 | |||
| 602176815a | |||
| 7652144184 | |||
| 0fd94d291b | |||
| 3c9f09f246 | |||
| d60fbd2c09 | |||
| 9d869ebb6d | |||
| 971e508e3d | |||
| 7020caf682 | |||
| 7cd7ad1281 | |||
| 586707dd28 | |||
| 4bb46fa607 | |||
| 7d5beb6c80 | |||
| ecc0263062 | |||
| 39c5dc6ea4 | |||
| e6d6ab6da7 | |||
| d7c3b5a990 | |||
| 3409cd7c29 | |||
| 91176349f7 | |||
| 6634025346 | |||
| a771061495 | |||
| 2629172ff4 | |||
| 36b746da7a | |||
| d3aa2e87c9 | |||
| c6090ef9b3 | |||
| 6354a11645 | |||
| 11ec8e04c2 | |||
| 8a9998bc65 | |||
| 065bda604e | |||
| 471aeab439 | |||
| f2c757b1bb | |||
| f6e72ec06f | |||
| 9dd82036ba | |||
| ff2fa70776 | |||
| e4b1f56f6d | |||
| 0d7a789ed8 | |||
| d73e77c5ed | |||
| dddbf64e93 | |||
| 1d5891fde7 | |||
| c5de64de51 | |||
| a43b2a2ae8 | |||
| 7ab92dfa0b | |||
| ff81286874 | |||
| 51ad31f5a3 | |||
| 6336db0069 | |||
| 7d7122ee4c | |||
| 7d852f9e51 | |||
| 605097de76 | |||
| 07638dac27 | |||
| 6f39648077 | |||
| 90c19f8454 | |||
| 929aa3a49b | |||
| 83e97b7205 | |||
| 5d583741b9 | |||
| d3d2c3608b | |||
| a0edd66b92 | |||
| c8c0c9a621 | |||
| 00c010e04d | |||
| 1d2c9780da | |||
| adec3853e8 | |||
| d3251c5ca1 | |||
| 68f8d8798a | |||
| 4c793be5f7 | |||
| 812eb5eaef | |||
| 6c2be8f886 | |||
| 301d4fecf8 | |||
| 604dfa8cf4 | |||
| 8bb4ab91b8 | |||
| 0cdd9a0cde | |||
| 4768ea23e4 | |||
| 659cbe69c3 | |||
| e22f4dd001 | |||
| 394b2003b3 | |||
| d72ac7827b | |||
| 610ec7d8ad | |||
| 8f41ae0c1b | |||
| f8113d4857 | |||
| 72169d06ad | |||
| 2b78871c01 | |||
| 1ce254cc9a | |||
| 3b07c086a8 | |||
| ff7b101b71 | |||
| 92405d819b | |||
| c367989ebd | |||
| 7cae099fb4 | |||
| 7dcccf9ea5 | |||
| 535e974cc3 | |||
| 629aff1e50 | |||
| 628b6830db | |||
| 1b2ab4e3f0 | |||
| f97752c368 | |||
| d2d5db9bf6 | |||
| a8554d9e56 | |||
| 11a38af901 | |||
| 27a4976b9d | |||
| eb54f5a0cb | |||
| a4a6dce6de | |||
| 4184e25c68 | |||
| d98b0766c2 | |||
| b8990dd0a4 | |||
| 2198201e0b | |||
| 53e58848f3 | |||
| 9c367d1702 | |||
| db18a81035 | |||
| d1d6b00f92 | |||
| 32fc147a7c | |||
| d98054234c | |||
| d6460026f2 | |||
| 88ba6514e8 | |||
| 894a688bd8 | |||
| c69ead4893 | |||
| e68fdb2db7 | |||
| ae9372c3d3 | |||
| 0365c6d43b | |||
| 18b6969221 | |||
| a1935acb3d | |||
| 9548c5e657 | |||
| 762299124f | |||
| f682bf5688 | |||
| f7ed3a8727 | |||
| c124506e41 | |||
| 273cdc6ea8 | |||
| 0ef6ea6bd4 | |||
| 607d9a06ec | |||
| 1b472ca5f2 | |||
| 18568d6ba7 | |||
| bfb59dd387 | |||
| 38cf88d5d2 | |||
| 7ca6f9d7a1 | |||
| 5874508263 | |||
| 954de793dd | |||
| df74d5efbe | |||
| f7b9a7bfb5 | |||
| 63eaa88d3d | |||
| f518521ea1 | |||
| 5e1ee22bcd | |||
| ca09ff2ef0 | |||
| 8643aff800 | |||
| 2e88993654 | |||
| b77849d279 | |||
| c7e288466f | |||
| bb48d4aac1 | |||
| 2b99cc1e58 | |||
| ca286f8d19 | |||
| 73a3cc2cc5 | |||
| 169709ed04 | |||
| 2e746242ce | |||
| 951b8e2b15 | |||
| ede3edcf1e | |||
| 184c9f87b2 |
@@ -39,7 +39,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
pyver: [py37, py39, py310]
|
||||
pyver: [py39, py310]
|
||||
config:
|
||||
- {
|
||||
name: "Windows Build",
|
||||
|
||||
@@ -86,7 +86,15 @@ src/blenderbim/blenderbim/libs
|
||||
|
||||
# blenderbim test temp files
|
||||
src/blenderbim/test/files/temp
|
||||
src/blenderbim/drawings
|
||||
src/blenderbim/layouts
|
||||
|
||||
# ifcopenshell swig and compiled files
|
||||
src/ifcopenshell-python/ifcopenshell/_ifcopenshell_wrapper.so
|
||||
src/ifcopenshell-python/ifcopenshell/ifcopenshell_wrapper.py
|
||||
|
||||
# apple
|
||||
.DS_Store
|
||||
|
||||
# Brickschema
|
||||
src/blenderbim/blenderbim/bim/schema/Brick.ttl
|
||||
@@ -1,9 +1,12 @@
|
||||
|
||||
IfcOpenShell
|
||||
============
|
||||
<p align="center">
|
||||
<img src="https://github.com/IfcOpenShell/IfcOpenShell/assets/79010126/9e540403-c79f-446a-88c6-7c1ebc6ba864">
|
||||
</p>
|
||||
|
||||
IfcOpenShell is an open source ([LGPL]) software library for working with Industry Foundation Classes ([IFC]). Complete
|
||||
parsing support is provided for [IFC2x3 TC1], [IFC4 Add2 TC1], IFC4x1, IFC4x3, and IFC4x3. Extensive geometric support
|
||||
parsing support is provided for [IFC2x3 TC1], [IFC4 Add2 TC1], IFC4x1, IFC4x2, and IFC4x3. Extensive geometric support
|
||||
is implemented for the IFC releases [IFC2x3 TC1] and [IFC4 Add2 TC1]. Extending with support for arbitrary IFC schemas
|
||||
is possible at compile-time when using C++ and at run-time when using Python.
|
||||
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
# Dockerfile for Running AWS Lambda Function with Python and IfcOpenShell
|
||||
|
||||
# Base image: Python 3.9 from AWS's public container registry
|
||||
FROM public.ecr.aws/docker/library/python:3.9 AS build
|
||||
|
||||
# Set the location of your Lambda function code
|
||||
ARG FUNCTION_DIR="/var/task"
|
||||
|
||||
# Install necessary packages
|
||||
RUN apt-get -y update && apt-get -y install unzip curl
|
||||
|
||||
# Install AWS Lambda runtime interface client
|
||||
RUN pip install --target ${FUNCTION_DIR} awslambdaric
|
||||
|
||||
# Set the IfcOpenShell build version (check available builds at: https://blenderbim.org/docs-python/ifcopenshell-python/installation.html)
|
||||
ARG IFC_OPENSHELL_BUILD="39-v0.7.0-476ab50"
|
||||
|
||||
# Download and extract IfcOpenShell
|
||||
RUN curl https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-${IFC_OPENSHELL_BUILD}-linux64.zip -O && \
|
||||
unzip ifcopenshell-python-${IFC_OPENSHELL_BUILD}-linux64.zip && \
|
||||
mv ifcopenshell ${FUNCTION_DIR} && \
|
||||
rm ifcopenshell-python-${IFC_OPENSHELL_BUILD}-linux64.zip
|
||||
|
||||
# Copy the requirements file and install dependencies
|
||||
COPY requirements.txt .
|
||||
RUN pip install -r requirements.txt
|
||||
|
||||
# Copy the Lambda function code
|
||||
COPY ./example_handler ${FUNCTION_DIR}/example_handler
|
||||
|
||||
# Set the working directory
|
||||
WORKDIR ${FUNCTION_DIR}
|
||||
|
||||
# Set the entrypoint for the Lambda function
|
||||
ENTRYPOINT [ "/usr/local/bin/python", "-m", "awslambdaric" ]
|
||||
|
||||
# Set the Python import path to the Lambda function handler
|
||||
# This path is relative to the root of the Lambda function code (FUNCTION_DIR)
|
||||
# Lambda invocation will look for this path
|
||||
CMD ["example_handler.extract_wall_psets_handler"]
|
||||
@@ -0,0 +1,27 @@
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.element
|
||||
import boto3
|
||||
|
||||
s3 = boto3.client('s3')
|
||||
|
||||
def extract_wall_psets_handler(event, context):
|
||||
print("Hello from lambda")
|
||||
print("Event: {}".format(event))
|
||||
print("Context: {}".format(context))
|
||||
|
||||
filename = event['body']['filename']
|
||||
|
||||
s3.download_file('my_ifc_files_bucket', filename, f'/tmp/{filename}')
|
||||
ifc_file = ifcopenshell.open(f'/tmp/{filename}')
|
||||
walls = ifc_file.by_type('IfcWall')
|
||||
|
||||
psets = []
|
||||
for wall in walls:
|
||||
wall_data = ifcopenshell.util.element.get_psets(wall)
|
||||
wall_data['id'] = wall.GlobalId
|
||||
psets.append(wall_data)
|
||||
|
||||
return {
|
||||
'statusCode': 200,
|
||||
'body': psets
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
boto3
|
||||
+68
-11
@@ -21,7 +21,7 @@ cmake_minimum_required(VERSION 3.1.3)
|
||||
set(CMAKE_CXX_STANDARD 14)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON) # not necessary, but encouraged
|
||||
|
||||
project (IfcOpenShell VERSION 0.7.0)
|
||||
project(IfcOpenShell VERSION 0.7.0)
|
||||
|
||||
cmake_policy(SET CMP0048 NEW)
|
||||
cmake_policy(SET CMP0074 NEW)
|
||||
@@ -54,12 +54,14 @@ OPTION(USE_VLD "Use Visual Leak Detector for debugging memory leaks, MSVC-only."
|
||||
OPTION(USE_MMAP "Adds a command line options to parse IFC files from memory mapped files using Boost.Iostreams" OFF)
|
||||
OPTION(BUILD_SHARED_LIBS "Build IfcParse and IfcGeom as shared libs (SO/DLL)." OFF)
|
||||
OPTION(MSVC_PARALLEL_BUILD "Multi-threaded compilation in Microsoft Visual Studio (/MP)" OFF)
|
||||
OPTION(NO_WARN "Disable all warnings" OFF)
|
||||
OPTION(WASM_BUILD OFF)
|
||||
if (${HAS_MAX})
|
||||
OPTION(BUILD_IFCMAX "Build IfcMax, a 3ds Max plug-in, Windows-only." OFF)
|
||||
endif()
|
||||
if (${BUILD_CONVERT})
|
||||
OPTION(GLTF_SUPPORT "Build IfcConvert with glTF support (requires json.hpp)." OFF)
|
||||
OPTION(USD_SUPPORT "Build IfcConvert with USD support (requires pixar's USD library)." OFF)
|
||||
endif()
|
||||
OPTION(USERSPACE_PYTHON_PREFIX "Installs IfcPython for the current user only instead of system-wide." OFF)
|
||||
OPTION(ADD_COMMIT_SHA "Add commit sha and branch in version number, warning results in many rebuilds, requires git" OFF)
|
||||
@@ -72,6 +74,14 @@ if(MSVC AND MSVC_PARALLEL_BUILD)
|
||||
add_definitions("/MP")
|
||||
endif()
|
||||
|
||||
if(NO_WARN)
|
||||
if(MSVC)
|
||||
add_compile_options("/w")
|
||||
else()
|
||||
add_compile_options("-w")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# QtViewer requires Qt5
|
||||
OPTION(BUILD_QTVIEWER "Build IfcOpenShell Qt GUI Viewer" OFF)
|
||||
include(GNUInstallDirs)
|
||||
@@ -137,11 +147,6 @@ UNIFY_ENVVARS_AND_CACHE(PYTHON_EXECUTABLE)
|
||||
UNIFY_ENVVARS_AND_CACHE(HDF5_INCLUDE_DIR)
|
||||
UNIFY_ENVVARS_AND_CACHE(HDF5_LIBRARY_DIR)
|
||||
UNIFY_ENVVARS_AND_CACHE(HDF5_LIBRARIES)
|
||||
endif()
|
||||
UNIFY_ENVVARS_AND_CACHE(BOOST_ROOT)
|
||||
UNIFY_ENVVARS_AND_CACHE(BOOST_LIBRARYDIR)
|
||||
|
||||
if (NOT MINIMAL_BUILD)
|
||||
UNIFY_ENVVARS_AND_CACHE(CGAL_INCLUDE_DIR)
|
||||
UNIFY_ENVVARS_AND_CACHE(CGAL_LIBRARY_DIR)
|
||||
UNIFY_ENVVARS_AND_CACHE(GMP_INCLUDE_DIR)
|
||||
@@ -149,6 +154,8 @@ UNIFY_ENVVARS_AND_CACHE(GMP_LIBRARY_DIR)
|
||||
UNIFY_ENVVARS_AND_CACHE(MPFR_INCLUDE_DIR)
|
||||
UNIFY_ENVVARS_AND_CACHE(MPFR_LIBRARY_DIR)
|
||||
endif()
|
||||
UNIFY_ENVVARS_AND_CACHE(BOOST_ROOT)
|
||||
UNIFY_ENVVARS_AND_CACHE(BOOST_LIBRARYDIR)
|
||||
|
||||
if(WASM_BUILD)
|
||||
# when using the nix/build-all.py build script we should not
|
||||
@@ -170,6 +177,54 @@ add_definitions(-DWITH_GLTF)
|
||||
set(SWIG_DEFINES ${SWIG_DEFINES} -DWITH_GLTF)
|
||||
endif()
|
||||
|
||||
# Add USD support to serializers
|
||||
if(NOT MINIMAL_BUILD AND USD_SUPPORT)
|
||||
UNIFY_ENVVARS_AND_CACHE(USD_INCLUDE_DIR)
|
||||
UNIFY_ENVVARS_AND_CACHE(USD_LIBRARY_DIR)
|
||||
|
||||
if("${USD_INCLUDE_DIR}" STREQUAL "")
|
||||
find_path(USD_INCLUDE_DIR pxr.h
|
||||
[PATHS
|
||||
/usr/include/pxr
|
||||
/usr/local/include/pxr
|
||||
]
|
||||
REQUIRED
|
||||
)
|
||||
if(USD_INCLUDE_DIR)
|
||||
message(STATUS "Found USD include files in: ${USD_INCLUDE_DIR}")
|
||||
else()
|
||||
message(FATAL_ERROR "Unable to find USD include directory, specify USD_INCLUDE_DIR manually.")
|
||||
endif()
|
||||
else()
|
||||
set(USD_INCLUDE_DIR ${USD_INCLUDE_DIR} CACHE FILEPATH "USD header files")
|
||||
message(STATUS "Looking for USD include files in: ${USD_INCLUDE_DIR}")
|
||||
endif()
|
||||
|
||||
set(USD_LIBRARIES
|
||||
usd_usd
|
||||
usd_usdGeom
|
||||
usd_usdShade
|
||||
usd_usdLux
|
||||
usd_vt
|
||||
usd_sdf
|
||||
usd_tf
|
||||
usd_gf
|
||||
)
|
||||
|
||||
find_library(USD_LIBRARY
|
||||
NAMES ${USD_LIBRARIES}
|
||||
PATHS ${USD_LIBRARY_DIR})
|
||||
if(USD_LIBRARY)
|
||||
message(STATUS "USD libraries ${USD_LIBRARIES} found in: ${USD_LIBRARY_DIR}")
|
||||
link_directories(${USD_LIBRARY_DIR})
|
||||
else()
|
||||
message(FATAL_ERROR "Unable to find USD libraries in: ${USD_LIBRARY_DIR}")
|
||||
endif()
|
||||
|
||||
add_definitions(-DWITH_USD)
|
||||
set(SWIG_DEFINES ${SWIG_DEFINES} -DWITH_USD)
|
||||
endif()
|
||||
|
||||
# Set INSTALL_RPATH for target
|
||||
MACRO(SET_INSTALL_RPATHS _target _paths)
|
||||
SET(${_target}_rpaths "")
|
||||
@@ -633,6 +688,7 @@ ENDIF()
|
||||
|
||||
INCLUDE_DIRECTORIES(${INCLUDE_DIRECTORIES} ${OCC_INCLUDE_DIR} ${OPENCOLLADA_INCLUDE_DIRS}
|
||||
${Boost_INCLUDE_DIRS} ${LIBXML2_INCLUDE_DIR} ${JSON_INCLUDE_DIR} ${HDF5_INCLUDE_DIR}
|
||||
${USD_INCLUDE_DIR}
|
||||
)
|
||||
|
||||
function(files_for_ifc_version IFC_VERSION RESULT_NAME)
|
||||
@@ -841,9 +897,9 @@ endforeach()
|
||||
add_library(Serializers ${SERIALIZERS_FILES})
|
||||
set_target_properties(Serializers PROPERTIES COMPILE_FLAGS "-DIFC_GEOM_EXPORTS ${CONVERT_PRECISION}" VERSION "0.6.0" SOVERSION "0.6")
|
||||
|
||||
TARGET_LINK_LIBRARIES(Serializers ${SERIALIZER_SCHEMA_LIBRARIES} ${OPENCOLLADA_LIBRARIES})
|
||||
TARGET_LINK_LIBRARIES(Serializers ${SERIALIZER_SCHEMA_LIBRARIES} ${OPENCOLLADA_LIBRARIES} ${USD_LIBRARIES})
|
||||
|
||||
endif(BUILD_CONVERT or BUILD_IFCPYTHON)
|
||||
endif(BUILD_CONVERT OR BUILD_IFCPYTHON)
|
||||
|
||||
if (BUILD_CONVERT)
|
||||
|
||||
@@ -854,7 +910,7 @@ set(IFCCONVERT_FILES ${IFCCONVERT_CPP_FILES} ${IFCCONVERT_H_FILES})
|
||||
ADD_EXECUTABLE(IfcConvert ${IFCCONVERT_FILES})
|
||||
set_target_properties(IfcConvert PROPERTIES COMPILE_FLAGS "${CONVERT_PRECISION}")
|
||||
|
||||
TARGET_LINK_LIBRARIES(IfcConvert ${IFCOPENSHELL_LIBRARIES} ${OPENCASCADE_LIBRARIES} ${Boost_LIBRARIES} ${HDF5_LIBRARIES})
|
||||
TARGET_LINK_LIBRARIES(IfcConvert ${IFCOPENSHELL_LIBRARIES} ${OPENCASCADE_LIBRARIES} ${Boost_LIBRARIES} ${HDF5_LIBRARIES} ${USD_LIBRARIES})
|
||||
|
||||
if ((NOT WIN32) AND BUILD_SHARED_LIBS)
|
||||
# Only set RPATHs when building shared libraries (i.e. IfcParse and
|
||||
@@ -897,7 +953,7 @@ find_package (Git)
|
||||
if (GIT_FOUND)
|
||||
message("git found: ${GIT_EXECUTABLE} with version ${GIT_VERSION_STRING}")
|
||||
execute_process(
|
||||
COMMAND ${GIT_EXECUTABLE} branch --contains HEAD
|
||||
COMMAND ${GIT_EXECUTABLE} branch -a --contains HEAD
|
||||
OUTPUT_VARIABLE git_branches
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
)
|
||||
@@ -906,7 +962,8 @@ if (GIT_FOUND)
|
||||
string(REPLACE "*" "" git_branch_candidate_temp "${git_branch_candidate}")
|
||||
string(STRIP "${git_branch_candidate_temp}" git_branch_candidate_2)
|
||||
if (NOT git_branch_candidate_2 MATCHES "^HEAD$")
|
||||
set(git_branch ${git_branch_candidate_2})
|
||||
string(REPLACE "/" ";" git_branch_candidate_2_list "${git_branch_candidate_2}")
|
||||
list(GET git_branch_candidate_2_list -1 git_branch)
|
||||
endif()
|
||||
endforeach()
|
||||
execute_process(
|
||||
|
||||
+1
-1
@@ -41,4 +41,4 @@ cmake -G Ninja \
|
||||
|
||||
ninja
|
||||
|
||||
ninja install -j 2
|
||||
ninja install -j 1
|
||||
|
||||
+2
-2
@@ -24,7 +24,7 @@ requirements:
|
||||
host:
|
||||
- python
|
||||
- boost-cpp
|
||||
- occt
|
||||
- occt ==7.7.0
|
||||
- libxml2
|
||||
- cgal-cpp
|
||||
- hdf5
|
||||
@@ -37,7 +37,7 @@ requirements:
|
||||
run:
|
||||
- python
|
||||
- boost-cpp
|
||||
- {{ pin_compatible('occt', max_pin='x.x.x') }}
|
||||
- occt ==7.7.0
|
||||
- libxml2
|
||||
- cgal-cpp
|
||||
- hdf5
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
# This CITATION.cff file was generated with cffinit.
|
||||
# Visit https://bit.ly/cffinit to generate yours today!
|
||||
|
||||
cff-version: 1.2.0
|
||||
title: bcf
|
||||
message: >-
|
||||
If you use this software, please cite it using the
|
||||
metadata from this file.
|
||||
type: software
|
||||
authors:
|
||||
- name: "IfcOpenShell contributors"
|
||||
repository-code: >-
|
||||
https://github.com/IfcOpenShell/IfcOpenShell/tree/v0.7.0/src/bcf
|
||||
abstract: >-
|
||||
Library to read and write BCF-XML and query OpenCDE
|
||||
BCF-API modules
|
||||
keywords:
|
||||
- BCF
|
||||
- IFC
|
||||
license: LGPL-3.0-or-later
|
||||
@@ -11,7 +11,7 @@ name = "bcf-client"
|
||||
description = "BCF-XML file handler."
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.8"
|
||||
keywords = ["IFC", "BCF", "BIM", "eingineering"]
|
||||
keywords = ["IFC", "BCF", "BIM"]
|
||||
dependencies = [
|
||||
"xsdata",
|
||||
"numpy",
|
||||
|
||||
@@ -275,6 +275,7 @@ class TopicHandler:
|
||||
"""
|
||||
new_viewpoint = VisualizationInfoHandler.create_new(element, self._xml_handler)
|
||||
self.add_visinfo_handler(new_viewpoint)
|
||||
return new_viewpoint
|
||||
|
||||
def add_viewpoint_from_point_and_guids(self, position: NDArray[np.float_], *guids: str) -> None:
|
||||
"""Add a viewpoint pointing at an XYZ point in space
|
||||
@@ -287,6 +288,7 @@ class TopicHandler:
|
||||
position, *guids, xml_handler=self._xml_handler
|
||||
)
|
||||
self.add_visinfo_handler(vi_handler)
|
||||
return vi_handler
|
||||
|
||||
def add_visinfo_handler(self, new_viewpoint: VisualizationInfoHandler) -> None:
|
||||
self.viewpoints[new_viewpoint.guid + ".bcfv"] = new_viewpoint
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
# This CITATION.cff file was generated with cffinit.
|
||||
# Visit https://bit.ly/cffinit to generate yours today!
|
||||
|
||||
cff-version: 1.2.0
|
||||
title: BlenderBIM Add-on
|
||||
message: >-
|
||||
If you use this software, please cite it using the
|
||||
metadata from this file.
|
||||
type: software
|
||||
authors:
|
||||
- name: "IfcOpenShell contributors"
|
||||
repository-code: >-
|
||||
https://github.com/IfcOpenShell/IfcOpenShell/tree/v0.7.0/src/blenderbim
|
||||
url: 'https://blenderbim.org/'
|
||||
repository-artifact: 'https://blenderbim.org/download.html'
|
||||
abstract: >-
|
||||
Add-on to Blender providing a graphical native IFC
|
||||
authoring platform
|
||||
keywords:
|
||||
- IFC
|
||||
- Blender
|
||||
- NativeIFC
|
||||
license: GPL-3.0-or-later
|
||||
commit: 1fb6dac
|
||||
version: v0.0.230506
|
||||
date-released: '2023-05-23'
|
||||
+104
-101
@@ -36,12 +36,8 @@ endif
|
||||
endif
|
||||
|
||||
VERSION:=`date '+%y%m%d'`
|
||||
PYVERSION:=py37
|
||||
PYVERSION:=py310
|
||||
|
||||
ifeq ($(PYVERSION), py37)
|
||||
PYLIBDIR:=python3.7
|
||||
PYNUMBER:=37
|
||||
endif
|
||||
ifeq ($(PYVERSION), py39)
|
||||
PYLIBDIR:=python3.9
|
||||
PYNUMBER:=39
|
||||
@@ -52,123 +48,100 @@ PYNUMBER:=310
|
||||
endif
|
||||
|
||||
ifeq ($(PLATFORM), linux)
|
||||
ifeq ($(PYVERSION), py37)
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.7.5/download/linux-64/hpp-fcl-1.7.5-py37h5f1835d_0.tar.bz2
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.5/download/linux-64/eigenpy-2.6.5-py37h95e2c48_0.tar.bz2
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/linux-64/boost-1.74.0-py37h0379df6_3.tar.bz2
|
||||
LXML_URL:=https://files.pythonhosted.org/packages/30/c0/d0526314971fc661b083ab135747dc68446a3022686da8c16d25fcf6ef07/lxml-4.6.3-cp37-cp37m-manylinux2014_x86_64.whl
|
||||
SHAPELY_URL:=https://files.pythonhosted.org/packages/1d/a4/931d0780f31f3ea8c4f9ef6464a2825137c5241e6707a5fb03bef760a7eb/shapely-2.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
|
||||
PILLOW_URL:=https://files.pythonhosted.org/packages/ed/d5/c2e84e1e36ab8ebea033921d5886a056c77e18bab5ab1051fcc22de2e8a2/Pillow-9.2.0-cp37-cp37m-manylinux_2_28_x86_64.whl
|
||||
endif
|
||||
ifeq ($(PYVERSION), py39)
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.7.5/download/linux-64/hpp-fcl-1.7.5-py39hbcdfc36_0.tar.bz2
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.5/download/linux-64/eigenpy-2.6.5-py39h5aed9d1_0.tar.bz2
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/linux-64/boost-1.74.0-py39h5472131_3.tar.bz2
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/2.3.4/download/linux-64/hpp-fcl-2.3.4-py39h40a70d0_0.conda
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/3.1.0/download/linux-64/eigenpy-3.1.0-py39hdfdd6bb_0.conda
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/linux-64/boost-1.78.0-py39h7c9e3ff_4.tar.bz2
|
||||
QHULL_URL:=https://anaconda.org/conda-forge/qhull/2020.2/download/linux-64/qhull-2020.2-h4bd325d_2.tar.bz2
|
||||
LXML_URL:=https://files.pythonhosted.org/packages/19/d9/a69c6aff5673554df48120565a14a50eaa41d29ae03b02faa0b023666318/lxml-4.6.3-cp39-cp39-manylinux2014_x86_64.whl
|
||||
SHAPELY_URL:=https://files.pythonhosted.org/packages/2d/f2/8ec281d357e8bb7d08dc8d727f0e4c8ef3dae7d3fa75c69c8e452bb82d50/shapely-2.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
|
||||
PILLOW_URL:=https://files.pythonhosted.org/packages/01/61/3ff85fb4bb596ce3d223c8fcf93c8df5c12bc8899dfb4fb3cb1c5b20dd5f/Pillow-9.2.0-cp39-cp39-manylinux_2_28_x86_64.whl
|
||||
endif
|
||||
ifeq ($(PYVERSION), py310)
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.8.0/download/linux-64/hpp-fcl-1.8.0-py310hdaf7e41_1.tar.bz2
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.11/download/linux-64/eigenpy-2.6.11-py310hf3e5c9c_0.tar.bz2
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/linux-64/boost-1.74.0-py310h7c3ba0c_5.tar.bz2
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/2.3.4/download/linux-64/hpp-fcl-2.3.4-py310h995690b_0.conda
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/3.1.0/download/linux-64/eigenpy-3.1.0-py310hf02b7e0_0.conda
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/linux-64/boost-1.78.0-py310hc4a4660_4.tar.bz2
|
||||
QHULL_URL:=https://anaconda.org/conda-forge/qhull/2020.2/download/linux-64/qhull-2020.2-h4bd325d_2.tar.bz2
|
||||
LXML_URL:=https://files.pythonhosted.org/packages/25/1e/19b46d8e8881fe0df2e20945d51919eeb1817836d62a90efa8506530e45c/lxml-4.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl
|
||||
SHAPELY_URL:=https://files.pythonhosted.org/packages/a8/a5/403728b5614b28083f6424dfdefec5fcf58068495fb03bb08532671c642f/shapely-2.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
|
||||
PILLOW_URL:=https://files.pythonhosted.org/packages/f6/51/320986ebd6d46a0e95c2240468ced73153b691ce07617078bcdf30c609ec/Pillow-9.2.0-cp310-cp310-manylinux_2_28_x86_64.whl
|
||||
endif
|
||||
ASSIMP_URL:=https://anaconda.org/conda-forge/assimp/5.0.1/download/linux-64/assimp-5.0.1-hedfc422_6.tar.bz2
|
||||
OCTOMAP_URL:=https://anaconda.org/conda-forge/octomap/1.9.7/download/linux-64/octomap-1.9.7-h4bd325d_0.tar.bz2
|
||||
OCTOMAP_URL:=https://anaconda.org/conda-forge/octomap/1.9.8/download/linux-64/octomap-1.9.8-h924138e_0.tar.bz2
|
||||
BOOSTCPP_URL:=https://anaconda.org/conda-forge/boost-cpp/1.78.0/download/linux-64/boost-cpp-1.78.0-h6582d0a_3.conda
|
||||
ZLIB_URL:=https://anaconda.org/conda-forge/zlib/1.2.11/download/linux-64/zlib-1.2.11-h516909a_1010.tar.bz2
|
||||
endif
|
||||
|
||||
ifeq ($(PLATFORM), macos)
|
||||
ifeq ($(PYVERSION), py37)
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.7.5/download/osx-64/hpp-fcl-1.7.5-py37h2d7f23a_0.tar.bz2
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.5/download/osx-64/eigenpy-2.6.5-py37h0695097_0.tar.bz2
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/osx-64/boost-1.74.0-py37hd79e0ac_3.tar.bz2
|
||||
LXML_URL:=https://files.pythonhosted.org/packages/1e/3e/f0abc15d5dac50939bccc589aae336d5ead4c72e7ad1039a2e0f3630ea92/lxml-4.6.3-cp37-cp37m-macosx_10_9_x86_64.whl
|
||||
SHAPELY_URL:=https://files.pythonhosted.org/packages/e6/7d/4923f27c340339e1c896c77cafc8ed672c8d381a025bbab6c6ddcba27e8f/shapely-2.0.1-cp37-cp37m-macosx_10_9_x86_64.whl
|
||||
PILLOW_URL:=https://files.pythonhosted.org/packages/88/49/c26fc3b5b0e82bdc9d8751d6b939da29327b0d98f7c3b95a575cbfed2743/Pillow-9.2.0-cp37-cp37m-macosx_10_10_x86_64.whl
|
||||
endif
|
||||
ifeq ($(PYVERSION), py39)
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.7.5/download/osx-64/hpp-fcl-1.7.5-py39h1e32b98_0.tar.bz2
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.5/download/osx-64/eigenpy-2.6.5-py39h5405915_0.tar.bz2
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/osx-64/boost-1.74.0-py39ha641261_3.tar.bz2
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/2.3.4/download/osx-64/hpp-fcl-2.3.4-py39hd85b194_0.conda
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/3.1.0/download/osx-64/eigenpy-3.1.0-py39hc4d6e28_0.conda
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/osx-64/boost-1.78.0-py39h953a6b8_4.tar.bz2
|
||||
QHULL_URL:=https://anaconda.org/conda-forge/qhull/2020.2/download/osx-64/qhull-2020.2-h940c156_2.tar.bz2
|
||||
LXML_URL:=https://files.pythonhosted.org/packages/b8/74/a71f7ad72e8db54ce899efab84507b801660750cbbfa6a39e6717557d36a/lxml-4.6.3-cp39-cp39-macosx_10_9_x86_64.whl
|
||||
SHAPELY_URL:=https://files.pythonhosted.org/packages/36/a4/7e542a209f862f967d7cb8e939eff155f4294a27d17e16441fb8bdd51a2c/shapely-2.0.1-cp39-cp39-macosx_10_9_x86_64.whl
|
||||
PILLOW_URL:=https://files.pythonhosted.org/packages/88/7a/ddfe28b485b623361457d4783007c1f9ba83a87f93e7fec32f64793efb6c/Pillow-9.2.0-cp39-cp39-macosx_10_10_x86_64.whl
|
||||
endif
|
||||
ifeq ($(PYVERSION), py310)
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.8.0/download/osx-64/hpp-fcl-1.8.0-py310h651ac30_1.tar.bz2
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.11/download/osx-64/eigenpy-2.6.11-py310hc03097c_0.tar.bz2
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/osx-64/boost-1.74.0-py310h509978a_5.tar.bz2
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/2.3.4/download/osx-64/hpp-fcl-2.3.4-py310h1db6f5f_0.conda
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/3.1.0/download/osx-64/eigenpy-3.1.0-py310h43da829_0.conda
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/osx-64/boost-1.78.0-py310h3e792ce_4.tar.bz2
|
||||
QHULL_URL:=https://anaconda.org/conda-forge/qhull/2020.2/download/osx-64/qhull-2020.2-h940c156_2.tar.bz2
|
||||
LXML_URL:=https://files.pythonhosted.org/packages/a1/44/17b7dac7a18807d30e2fe10c3328c152808f5464565e230bfd0e77f178c6/lxml-4.8.0-cp310-cp310-macosx_10_15_x86_64.whl
|
||||
SHAPELY_URL:=https://files.pythonhosted.org/packages/1f/2a/dc3353c2431cf53e8d04bb8fba27e584410ca3435c9c85f76d71bf0c0e80/shapely-2.0.1-cp310-cp310-macosx_10_9_x86_64.whl
|
||||
PILLOW_URL:=https://files.pythonhosted.org/packages/d8/60/b13c00d403f34110e96c1b5c0afa73ce461efe3fe960c3a7e3e7fe190d82/Pillow-9.2.0-cp310-cp310-macosx_10_10_x86_64.whl
|
||||
endif
|
||||
ASSIMP_URL:=https://anaconda.org/conda-forge/assimp/5.0.1/download/osx-64/assimp-5.0.1-h1224e73_6.tar.bz2
|
||||
OCTOMAP_URL:=https://anaconda.org/conda-forge/octomap/1.9.7/download/osx-64/octomap-1.9.7-h940c156_0.tar.bz2
|
||||
OCTOMAP_URL:=https://anaconda.org/conda-forge/octomap/1.9.8/download/osx-64/octomap-1.9.8-hb8565cd_0.tar.bz2
|
||||
BOOSTCPP_URL:=https://anaconda.org/conda-forge/boost-cpp/1.78.0/download/osx-64/boost-cpp-1.78.0-hf5ba120_3.conda
|
||||
ZLIB_URL:=https://anaconda.org/conda-forge/zlib/1.2.11/download/osx-64/zlib-1.2.11-h7795811_1010.tar.bz2
|
||||
endif
|
||||
|
||||
ifeq ($(PLATFORM), macosm1)
|
||||
ifeq ($(PYVERSION), py37)
|
||||
# Warning: py37 not supported on Apple M1
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.7.5/download/osx-64/hpp-fcl-1.7.5-py37h2d7f23a_0.tar.bz2
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.5/download/osx-64/eigenpy-2.6.5-py37h0695097_0.tar.bz2
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/osx-64/boost-1.74.0-py37hd79e0ac_3.tar.bz2
|
||||
LXML_URL:=https://anaconda.org/conda-forge/lxml/4.9.1/download/osx-64/lxml-4.9.1-py37h994c40b_0.tar.bz2
|
||||
SHAPELY_URL:=https://files.pythonhosted.org/packages/e6/7d/4923f27c340339e1c896c77cafc8ed672c8d381a025bbab6c6ddcba27e8f/shapely-2.0.1-cp37-cp37m-macosx_10_9_x86_64.whl
|
||||
PILLOW_URL:=https://files.pythonhosted.org/packages/aa/bc/21097cd891dd2fa02f2b3d767e02e883e026482e59d29975d1bc30024aa3/Pillow-9.2.0-cp39-cp39-macosx_11_0_arm64.whl
|
||||
endif
|
||||
ifeq ($(PYVERSION), py39)
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.7.5/download/osx-arm64/hpp-fcl-1.7.5-py39ha69f3c1_2.tar.bz2
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.7/download/osx-arm64/eigenpy-2.6.7-py39ha69f3c1_0.tar.bz2
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/osx-arm64/boost-1.78.0-py39h2550fe3_0.tar.bz2
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/2.3.4/download/osx-arm64/hpp-fcl-2.3.4-py39hc34188a_0.conda
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/3.1.0/download/osx-arm64/eigenpy-3.1.0-py39h13cfc01_0.conda
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/osx-arm64/boost-1.78.0-py39h99de9ae_4.tar.bz2
|
||||
QHULL_URL:=https://anaconda.org/conda-forge/qhull/2020.2/download/osx-arm64/qhull-2020.2-hc021e02_2.tar.bz2
|
||||
LXML_URL:=https://anaconda.org/conda-forge/lxml/4.9.1/download/osx-arm64/lxml-4.9.1-py39h9eb174b_0.tar.bz2
|
||||
SHAPELY_URL:=https://files.pythonhosted.org/packages/ea/aa/45fbd031edf3149cb767d8b9f9db45d5faf0324d743c6b8fb0298cc022d0/shapely-2.0.1-cp39-cp39-macosx_11_0_arm64.whl
|
||||
PILLOW_URL:=https://files.pythonhosted.org/packages/aa/bc/21097cd891dd2fa02f2b3d767e02e883e026482e59d29975d1bc30024aa3/Pillow-9.2.0-cp39-cp39-macosx_11_0_arm64.whl
|
||||
endif
|
||||
ifeq ($(PYVERSION), py310)
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.8.0/download/osx-arm64/hpp-fcl-1.8.0-py310h5699539_2.tar.bz2
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.9/download/osx-arm64/eigenpy-2.6.9-py310habfc766_1.tar.bz2
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/osx-arm64/boost-1.78.0-py310h4566fd1_0.tar.bz2
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/2.3.4/download/osx-arm64/hpp-fcl-2.3.4-py310h46fc4cd_0.conda
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/3.1.0/download/osx-arm64/eigenpy-3.1.0-py310ha2643af_0.conda
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/osx-arm64/boost-1.78.0-py310h629746b_4.tar.bz2
|
||||
QHULL_URL:=https://anaconda.org/conda-forge/qhull/2020.2/download/osx-arm64/qhull-2020.2-hc021e02_2.tar.bz2
|
||||
LXML_URL:=https://anaconda.org/conda-forge/lxml/4.9.1/download/osx-arm64/lxml-4.9.1-py310h02f21da_0.tar.bz2
|
||||
SHAPELY_URL:=https://files.pythonhosted.org/packages/ec/41/d59208743e737184e1b403e95a937aebb022b8459e99efbcd5208fc8be46/shapely-2.0.1-cp310-cp310-macosx_11_0_arm64.whl
|
||||
PILLOW_URL:=https://files.pythonhosted.org/packages/0c/5f/117b653cad585f3aedfe0de996c292e67d4b020ed77f652e5a6c8c24f908/Pillow-9.2.0-cp310-cp310-macosx_11_0_arm64.whl
|
||||
endif
|
||||
ASSIMP_URL:=https://anaconda.org/conda-forge/assimp/5.0.1/download/osx-arm64/assimp-5.0.1-h0f81e16_7.tar.bz2
|
||||
OCTOMAP_URL:=https://anaconda.org/conda-forge/octomap/1.9.7/download/osx-arm64/octomap-1.9.7-hc021e02_0.tar.bz2
|
||||
OCTOMAP_URL:=https://anaconda.org/conda-forge/octomap/1.9.8/download/osx-arm64/octomap-1.9.8-hffc8910_0.tar.bz2
|
||||
BOOSTCPP_URL:=https://anaconda.org/conda-forge/boost-cpp/1.78.0/download/osx-arm64/boost-cpp-1.78.0-h9ed8d21_3.conda
|
||||
ZLIB_URL:=https://anaconda.org/conda-forge/zlib/1.2.11/download/osx-arm64/zlib-1.2.11-h90dfc92_1014.tar.bz2
|
||||
endif
|
||||
|
||||
ifeq ($(PLATFORM), win)
|
||||
ifeq ($(PYVERSION), py37)
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.7.5/download/win-64/hpp-fcl-1.7.5-py37h839d6b1_0.tar.bz2
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.5/download/win-64/eigenpy-2.6.5-py37h2c32e34_0.tar.bz2
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/win-64/boost-1.74.0-py37h3b38789_3.tar.bz2
|
||||
LXML_URL:=https://files.pythonhosted.org/packages/9e/5e/171ee9d40a600f565fe691ec5bf7596247ec62cfb2edc00c91afe8ea837b/lxml-4.6.3-cp37-cp37m-win_amd64.whl
|
||||
SHAPELY_URL:=https://files.pythonhosted.org/packages/e2/87/b8b8d8d57b429b01aa56b7d723075c09f33c988b8091bb6f790c83436909/shapely-2.0.1-cp37-cp37m-win_amd64.whl
|
||||
PILLOW_URL:=https://files.pythonhosted.org/packages/69/f5/9e802159d78b2eaf26bf1f8b94648605993f5ca7247ac8870f065063fc40/Pillow-9.2.0-cp37-cp37m-win_amd64.whl
|
||||
endif
|
||||
ifeq ($(PYVERSION), py39)
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.7.5/download/win-64/hpp-fcl-1.7.5-py39h2e7c763_0.tar.bz2
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.5/download/win-64/eigenpy-2.6.5-py39h3ce40e6_0.tar.bz2
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/win-64/boost-1.74.0-py39hefe7e4c_3.tar.bz2
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/2.3.4/download/win-64/hpp-fcl-2.3.4-py39h39b25cb_0.conda
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/3.1.0/download/win-64/eigenpy-3.1.0-py39hb6c915b_0.conda
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/win-64/boost-1.78.0-py39hea4d8d1_4.tar.bz2
|
||||
LXML_URL:=https://files.pythonhosted.org/packages/72/d4/426ecb8849c47c3e370c87aa0ac05d85768df917ffea27fcd6686a5e6495/lxml-4.6.3-cp39-cp39-win_amd64.whl
|
||||
SHAPELY_URL:=https://files.pythonhosted.org/packages/a7/ae/eab645c4075093584b7a65ab71cb8ff4563a015bd935c9b55dba14b2c1eb/shapely-2.0.1-cp39-cp39-win_amd64.whl
|
||||
PILLOW_URL:=https://files.pythonhosted.org/packages/19/3f/b4d4bcf05dbcbe07f2e9613a8f4180c297395e73a91d8ad22c32c6624f8c/Pillow-9.2.0-cp39-cp39-win_amd64.whl
|
||||
endif
|
||||
ifeq ($(PYVERSION), py310)
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.8.0/download/win-64/hpp-fcl-1.8.0-py310hc5a3c62_1.tar.bz2
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.11/download/win-64/eigenpy-2.6.11-py310hbd43d28_0.tar.bz2
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/win-64/boost-1.74.0-py310hc781a3c_5.tar.bz2
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/2.3.4/download/win-64/hpp-fcl-2.3.4-py310heb59267_0.conda
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/3.1.0/download/win-64/eigenpy-3.1.0-py310hd146abe_0.conda
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/win-64/boost-1.78.0-py310h220cb41_4.tar.bz2
|
||||
LXML_URL:=https://files.pythonhosted.org/packages/f6/71/65c80a4caa1617a4c6e8fe1500cebb179db96232e2f623bfe6a1f4294e39/lxml-4.8.0-cp310-cp310-win_amd64.whl
|
||||
SHAPELY_URL:=https://files.pythonhosted.org/packages/81/8a/7ac076a86b2632f1872284c5e60ed5f2fc26094875a85b35d9fa17b52504/shapely-2.0.1-cp310-cp310-win_amd64.whl
|
||||
PILLOW_URL:=https://files.pythonhosted.org/packages/02/55/67a3c17b9e7d972ed8c246f104da99ca4f3ea42fba566697e479011b84b6/Pillow-9.2.0-cp310-cp310-win_amd64.whl
|
||||
endif
|
||||
ASSIMP_URL:=https://anaconda.org/conda-forge/assimp/5.0.1/download/win-64/assimp-5.0.1-hc2aa0de_6.tar.bz2
|
||||
OCTOMAP_URL:=https://anaconda.org/conda-forge/octomap/1.9.7/download/win-64/octomap-1.9.7-h5362a0b_0.tar.bz2
|
||||
OCTOMAP_URL:=https://anaconda.org/conda-forge/octomap/1.9.8/download/win-64/octomap-1.9.8-h91493d7_0.tar.bz2
|
||||
BOOSTCPP_URL:=https://anaconda.org/conda-forge/boost-cpp/1.78.0/download/win-64/boost-cpp-1.78.0-h9f4b32c_3.conda
|
||||
ZLIB_URL:=https://anaconda.org/conda-forge/zlib/1.2.11/download/win-64/zlib-1.2.11-h62dcd97_1010.tar.bz2
|
||||
endif
|
||||
|
||||
@@ -190,7 +163,7 @@ endif
|
||||
cp -r blenderbim/* dist/blenderbim/
|
||||
|
||||
# Provides IfcOpenShell Python functionality
|
||||
cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-$(PYNUMBER)-v0.7.0-476ab50-$(PLATFORM)64.zip
|
||||
cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-$(PYNUMBER)-v0.7.0-fc50bdd-$(PLATFORM)64.zip
|
||||
cd dist/working && unzip ifcopenshell-python*
|
||||
cp -r dist/working/ifcopenshell dist/blenderbim/libs/site/packages/
|
||||
|
||||
@@ -206,9 +179,7 @@ endif
|
||||
# IfcOpenBot sometimes lags behind, so we hotfix the Python utilities
|
||||
cp -r dist/working/IfcOpenShell-0.7.0/src/ifcopenshell-python/ifcopenshell/util/* dist/blenderbim/libs/site/packages/ifcopenshell/util/
|
||||
cp -r dist/working/IfcOpenShell-0.7.0/src/ifcopenshell-python/ifcopenshell/api/* dist/blenderbim/libs/site/packages/ifcopenshell/api/
|
||||
cp dist/working/IfcOpenShell-0.7.0/src/ifcopenshell-python/ifcopenshell/entity_instance.py dist/blenderbim/libs/site/packages/ifcopenshell/
|
||||
cp dist/working/IfcOpenShell-0.7.0/src/ifcopenshell-python/ifcopenshell/file.py dist/blenderbim/libs/site/packages/ifcopenshell/
|
||||
cp dist/working/IfcOpenShell-0.7.0/src/ifcopenshell-python/ifcopenshell/validate.py dist/blenderbim/libs/site/packages/ifcopenshell/
|
||||
cp dist/working/IfcOpenShell-0.7.0/src/ifcopenshell-python/ifcopenshell/*.py dist/blenderbim/libs/site/packages/ifcopenshell/
|
||||
cp dist/working/IfcOpenShell-0.7.0/src/ifcopenshell-python/ifcopenshell/express/rule_executor.py dist/blenderbim/libs/site/packages/ifcopenshell/express/
|
||||
# Provides bcf functionality
|
||||
cp -r dist/working/IfcOpenShell-0.7.0/src/bcf/src/bcf dist/blenderbim/libs/site/packages/
|
||||
@@ -408,14 +379,16 @@ endif
|
||||
cd dist/working && . env/bin/activate && $(PIP) install cjio --target=./site-packages
|
||||
# Provides express rule validation for ifcopenshell.validate
|
||||
cd dist/working && . env/bin/activate && $(PIP) install pytest --target=./site-packages
|
||||
cd dist/working/site-packages/ && rm -r *dist-info*
|
||||
# Provides Brickschema functionality
|
||||
cd dist/working && . env/bin/activate && $(PIP) install brickschema[persistence] --target=./site-packages
|
||||
cp -r dist/working/site-packages/* dist/blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by IFCClash
|
||||
mkdir dist/working
|
||||
cd dist/working && wget $(HPPFCL_URL)
|
||||
cd dist/working && tar -xf hpp-fcl*
|
||||
cd dist/working && unzip hpp-fcl*
|
||||
cd dist/working && tar -I zstd -xvf pkg*
|
||||
ifeq ($(PLATFORM), linux)
|
||||
cp -r dist/working/lib/$(PYLIBDIR)/site-packages/hppfcl dist/blenderbim/libs/site/packages/
|
||||
cp -r dist/working/lib/*.so* dist/blenderbim/libs/
|
||||
@@ -424,6 +397,10 @@ ifeq ($(PLATFORM), macos)
|
||||
cp -r dist/working/lib/$(PYLIBDIR)/site-packages/hppfcl dist/blenderbim/libs/site/packages/
|
||||
cp -r dist/working/lib/*.dylib dist/blenderbim/libs/
|
||||
endif
|
||||
ifeq ($(PLATFORM), macosm1)
|
||||
cp -r dist/working/lib/$(PYLIBDIR)/site-packages/hppfcl dist/blenderbim/libs/site/packages/
|
||||
cp -r dist/working/lib/*.dylib dist/blenderbim/libs/
|
||||
endif
|
||||
ifeq ($(PLATFORM), win)
|
||||
cp -r dist/working/Lib/site-packages/hppfcl dist/blenderbim/libs/site/packages/
|
||||
cp -r dist/working/Library/bin/*.dll dist/blenderbim/libs/site/packages/hppfcl/
|
||||
@@ -440,21 +417,45 @@ endif
|
||||
ifeq ($(PLATFORM), macos)
|
||||
cp -r dist/working/lib/*.dylib dist/blenderbim/libs/
|
||||
endif
|
||||
ifeq ($(PLATFORM), macosm1)
|
||||
cp -r dist/working/lib/*.dylib dist/blenderbim/libs/
|
||||
endif
|
||||
ifeq ($(PLATFORM), win)
|
||||
cp -r dist/working/Library/bin/*.dll dist/blenderbim/libs/site/packages/hppfcl/
|
||||
endif
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by hpp-fcl
|
||||
# Required by hpp-fcl except on Windows
|
||||
ifneq ($(PLATFORM), win)
|
||||
mkdir dist/working
|
||||
cd dist/working && wget $(EIGENPY_URL)
|
||||
cd dist/working && tar -xf eigenpy*
|
||||
cd dist/working && wget $(QHULL_URL)
|
||||
cd dist/working && tar -xf qhull*
|
||||
ifeq ($(PLATFORM), linux)
|
||||
cp -r dist/working/lib/*.so* dist/blenderbim/libs/
|
||||
endif
|
||||
ifeq ($(PLATFORM), macos)
|
||||
cp -r dist/working/lib/*.dylib dist/blenderbim/libs/
|
||||
endif
|
||||
ifeq ($(PLATFORM), macosm1)
|
||||
cp -r dist/working/lib/*.dylib dist/blenderbim/libs/
|
||||
endif
|
||||
rm -rf dist/working
|
||||
endif
|
||||
|
||||
# Required by hpp-fcl
|
||||
mkdir dist/working
|
||||
cd dist/working && wget $(EIGENPY_URL)
|
||||
cd dist/working && unzip eigenpy*
|
||||
cd dist/working && tar -I zstd -xvf pkg*
|
||||
ifeq ($(PLATFORM), linux)
|
||||
cp -r dist/working/lib/*.so* dist/blenderbim/libs/
|
||||
endif
|
||||
ifeq ($(PLATFORM), macos)
|
||||
cp -r dist/working/lib/*.dylib dist/blenderbim/libs/
|
||||
endif
|
||||
ifeq ($(PLATFORM), macosm1)
|
||||
cp -r dist/working/lib/*.dylib dist/blenderbim/libs/
|
||||
endif
|
||||
ifeq ($(PLATFORM), win)
|
||||
cp -r dist/working/Library/bin/*.dll dist/blenderbim/libs/site/packages/hppfcl/
|
||||
endif
|
||||
@@ -470,6 +471,9 @@ endif
|
||||
ifeq ($(PLATFORM), macos)
|
||||
cp -r dist/working/lib/*.dylib dist/blenderbim/libs/
|
||||
endif
|
||||
ifeq ($(PLATFORM), macosm1)
|
||||
cp -r dist/working/lib/*.dylib dist/blenderbim/libs/
|
||||
endif
|
||||
ifeq ($(PLATFORM), win)
|
||||
# Uh, do nothing, apparently? No DLLs are shipped.
|
||||
endif
|
||||
@@ -485,11 +489,33 @@ endif
|
||||
ifeq ($(PLATFORM), macos)
|
||||
cp -r dist/working/lib/*.dylib dist/blenderbim/libs/
|
||||
endif
|
||||
ifeq ($(PLATFORM), macosm1)
|
||||
cp -r dist/working/lib/*.dylib dist/blenderbim/libs/
|
||||
endif
|
||||
ifeq ($(PLATFORM), win)
|
||||
cp -r dist/working/Library/bin/*.dll dist/blenderbim/libs/site/packages/hppfcl/
|
||||
endif
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by hpp-fcl
|
||||
mkdir dist/working
|
||||
cd dist/working && wget $(BOOSTCPP_URL)
|
||||
cd dist/working && unzip boost-cpp*
|
||||
cd dist/working && tar -I zstd -xvf pkg*
|
||||
ifeq ($(PLATFORM), linux)
|
||||
cp -r dist/working/lib/libboost_serialization.so* dist/blenderbim/libs/
|
||||
endif
|
||||
ifeq ($(PLATFORM), macos)
|
||||
cp -r dist/working/lib/libboost_serialization.dylib dist/blenderbim/libs/
|
||||
endif
|
||||
ifeq ($(PLATFORM), macosm1)
|
||||
cp -r dist/working/lib/libboost_serialization.dylib dist/blenderbim/libs/
|
||||
endif
|
||||
ifeq ($(PLATFORM), win)
|
||||
cp -r dist/working/Library/bin/boost_serialization.dll dist/blenderbim/libs/site/packages/hppfcl/
|
||||
endif
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by hpp-fcl
|
||||
mkdir dist/working
|
||||
cd dist/working && wget $(ZLIB_URL)
|
||||
@@ -500,6 +526,9 @@ endif
|
||||
ifeq ($(PLATFORM), macos)
|
||||
cp -r dist/working/lib/*.dylib dist/blenderbim/libs/
|
||||
endif
|
||||
ifeq ($(PLATFORM), macosm1)
|
||||
cp -r dist/working/lib/*.dylib dist/blenderbim/libs/
|
||||
endif
|
||||
ifeq ($(PLATFORM), win)
|
||||
cp -r dist/working/Library/bin/*.dll dist/blenderbim/libs/site/packages/hppfcl/
|
||||
endif
|
||||
@@ -618,37 +647,11 @@ endif
|
||||
|
||||
# Provides Brickschema functionality
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://files.pythonhosted.org/packages/ca/37/8309da9a72407d2f5eb5489c197ac3cfe96ee3ccba1902d2a0ecc7843f92/brickschema-0.5.1.tar.gz
|
||||
cd dist/working && tar -xzvf brickschema*
|
||||
# This is an evil hack because we don't want to bundle flask
|
||||
echo "" > dist/working/brickschema-0.5.1/brickschema/web.py
|
||||
cd dist/working/brickschema-0.5.1/ && cp -r brickschema ../../blenderbim/libs/site/packages/
|
||||
# For now lets bundle the latest nightly schema
|
||||
cd dist/working && wget https://github.com/BrickSchema/Brick/releases/download/nightly/Brick.ttl
|
||||
cd dist/working && cp Brick.ttl ../blenderbim/bim/schema/Brick.ttl
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by brickschema
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://files.pythonhosted.org/packages/c7/22/37bb938be8e5c20d443b0c9ba0a243573b671b01739efb0a19f81bc5b470/pyshacl-0.17.2.tar.gz
|
||||
cd dist/working && tar -xzvf pyshacl*
|
||||
cd dist/working/pyshacl-0.17.2/ && cp -r pyshacl ../../blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by brickschema
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://files.pythonhosted.org/packages/d2/a7/be8244688bfcee37c23733ab4fe8e6afa6d4403bd2674a3ae7bd2cecc77b/rdflib-6.0.2.tar.gz
|
||||
cd dist/working && tar -xzvf rdflib*
|
||||
cd dist/working/rdflib-6.0.2/ && cp -r rdflib ../../blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by brickschema
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://files.pythonhosted.org/packages/7d/c7/208aece36279e4f1236e437119358786e39530ecc1719d4e1afeddba5288/owlrl-6.0.2.tar.gz
|
||||
cd dist/working && tar -xzvf owlrl*
|
||||
cd dist/working/owlrl-6.0.2/ && cp -r owlrl ../../blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by brickschema
|
||||
# This is a bit of a dodgy one, it should be provided by setuptools which Blender doesn't ship.
|
||||
mkdir dist/working
|
||||
|
||||
@@ -111,6 +111,7 @@ classes = [
|
||||
prop.PsetQto,
|
||||
prop.GlobalId,
|
||||
prop.BIMObjectProperties,
|
||||
prop.BIMCollectionProperties,
|
||||
prop.BIMMaterialProperties,
|
||||
prop.BIMMeshProperties,
|
||||
ui.BIM_PT_section_plane,
|
||||
@@ -158,6 +159,7 @@ def register():
|
||||
bpy.app.handlers.load_post.append(handler.loadIfcStore)
|
||||
bpy.app.handlers.save_post.append(handler.ensureIfcExported)
|
||||
bpy.types.Scene.BIMProperties = bpy.props.PointerProperty(type=prop.BIMProperties)
|
||||
bpy.types.Collection.BIMCollectionProperties = bpy.props.PointerProperty(type=prop.BIMCollectionProperties)
|
||||
bpy.types.Object.BIMObjectProperties = bpy.props.PointerProperty(type=prop.BIMObjectProperties)
|
||||
bpy.types.Material.BIMObjectProperties = bpy.props.PointerProperty(type=prop.BIMObjectProperties)
|
||||
bpy.types.Material.BIMMaterialProperties = bpy.props.PointerProperty(type=prop.BIMMaterialProperties)
|
||||
@@ -168,6 +170,7 @@ def register():
|
||||
bpy.types.SCENE_PT_unit.append(ui.ifc_units)
|
||||
if hasattr(bpy.types, "UI_MT_button_context_menu"):
|
||||
bpy.types.UI_MT_button_context_menu.append(ui.draw_custom_context_menu)
|
||||
bpy.types.STATUSBAR_HT_header.append(ui.draw_statusbar)
|
||||
|
||||
for mod in modules.values():
|
||||
mod.register()
|
||||
@@ -184,6 +187,7 @@ def unregister():
|
||||
bpy.app.handlers.load_post.remove(handler.loadIfcStore)
|
||||
bpy.app.handlers.save_post.remove(handler.ensureIfcExported)
|
||||
del bpy.types.Scene.BIMProperties
|
||||
del bpy.types.Collection.BIMCollectionProperties
|
||||
del bpy.types.Object.BIMObjectProperties
|
||||
del bpy.types.Material.BIMObjectProperties
|
||||
del bpy.types.Material.BIMMaterialProperties
|
||||
@@ -194,6 +198,7 @@ def unregister():
|
||||
bpy.types.SCENE_PT_unit.remove(ui.ifc_units)
|
||||
if hasattr(bpy.types, "UI_MT_button_context_menu"):
|
||||
bpy.types.UI_MT_button_context_menu.remove(ui.draw_custom_context_menu)
|
||||
bpy.types.STATUSBAR_HT_header.remove(ui.draw_statusbar)
|
||||
|
||||
for mod in reversed(list(modules.values())):
|
||||
mod.unregister()
|
||||
|
||||
@@ -0,0 +1,755 @@
|
||||
{
|
||||
"Technical": {
|
||||
"render_type": "VIEWPORT",
|
||||
"raster_style": {
|
||||
"bpy.data.worlds[0].color": [
|
||||
1.0,
|
||||
1.0,
|
||||
1.0
|
||||
],
|
||||
"scene.render.bake_bias": 0.0010000000474974513,
|
||||
"scene.render.bake_margin": 16,
|
||||
"scene.render.bake_margin_type": "ADJACENT_FACES",
|
||||
"scene.render.bake_samples": 256,
|
||||
"scene.render.bake_type": "NORMALS",
|
||||
"scene.render.bake_user_scale": 0.0,
|
||||
"scene.render.border_max_x": 1.0,
|
||||
"scene.render.border_max_y": 1.0,
|
||||
"scene.render.border_min_x": 0.0,
|
||||
"scene.render.border_min_y": 0.0,
|
||||
"scene.render.dither_intensity": 1.0,
|
||||
"scene.render.engine": "BLENDER_WORKBENCH",
|
||||
"scene.render.film_transparent": false,
|
||||
"scene.render.filter_size": 1.5,
|
||||
"scene.render.fps": 24,
|
||||
"scene.render.fps_base": 1.0,
|
||||
"scene.render.frame_map_new": 100,
|
||||
"scene.render.frame_map_old": 100,
|
||||
"scene.render.hair_subdiv": 0,
|
||||
"scene.render.hair_type": "STRAND",
|
||||
"scene.render.line_thickness": 1.0,
|
||||
"scene.render.line_thickness_mode": "ABSOLUTE",
|
||||
"scene.render.metadata_input": "SCENE",
|
||||
"scene.render.motion_blur_shutter": 0.5,
|
||||
"scene.render.pixel_aspect_x": 1.0,
|
||||
"scene.render.pixel_aspect_y": 1.0,
|
||||
"scene.render.preview_pixel_size": "AUTO",
|
||||
"scene.render.resolution_percentage": 100,
|
||||
"scene.render.resolution_x": 1000,
|
||||
"scene.render.resolution_y": 1000,
|
||||
"scene.render.sequencer_gl_preview": "SOLID",
|
||||
"scene.render.simplify_child_particles": 1.0,
|
||||
"scene.render.simplify_child_particles_render": 1.0,
|
||||
"scene.render.simplify_gpencil": false,
|
||||
"scene.render.simplify_gpencil_antialiasing": true,
|
||||
"scene.render.simplify_gpencil_modifier": true,
|
||||
"scene.render.simplify_gpencil_onplay": false,
|
||||
"scene.render.simplify_gpencil_shader_fx": true,
|
||||
"scene.render.simplify_gpencil_tint": true,
|
||||
"scene.render.simplify_gpencil_view_fill": true,
|
||||
"scene.render.simplify_shadows": 1.0,
|
||||
"scene.render.simplify_shadows_render": 1.0,
|
||||
"scene.render.simplify_subdivision": 6,
|
||||
"scene.render.simplify_subdivision_render": 6,
|
||||
"scene.render.simplify_volumes": 1.0,
|
||||
"scene.render.stamp_font_size": 12,
|
||||
"scene.render.stamp_note_text": "",
|
||||
"scene.render.threads": 20,
|
||||
"scene.render.threads_mode": "AUTO",
|
||||
"scene.render.use_bake_clear": true,
|
||||
"scene.render.use_bake_lores_mesh": false,
|
||||
"scene.render.use_bake_multires": false,
|
||||
"scene.render.use_bake_selected_to_active": false,
|
||||
"scene.render.use_bake_user_scale": false,
|
||||
"scene.render.use_border": false,
|
||||
"scene.render.use_compositing": true,
|
||||
"scene.render.use_crop_to_border": false,
|
||||
"scene.render.use_file_extension": true,
|
||||
"scene.render.use_freestyle": false,
|
||||
"scene.render.use_high_quality_normals": false,
|
||||
"scene.render.use_lock_interface": false,
|
||||
"scene.render.use_motion_blur": false,
|
||||
"scene.render.use_multiview": false,
|
||||
"scene.render.use_overwrite": true,
|
||||
"scene.render.use_persistent_data": false,
|
||||
"scene.render.use_placeholder": false,
|
||||
"scene.render.use_render_cache": false,
|
||||
"scene.render.use_sequencer": true,
|
||||
"scene.render.use_sequencer_override_scene_strip": false,
|
||||
"scene.render.use_simplify": false,
|
||||
"scene.render.use_single_layer": false,
|
||||
"scene.render.use_stamp": false,
|
||||
"scene.render.use_stamp_camera": true,
|
||||
"scene.render.use_stamp_date": true,
|
||||
"scene.render.use_stamp_filename": true,
|
||||
"scene.render.use_stamp_frame": true,
|
||||
"scene.render.use_stamp_frame_range": false,
|
||||
"scene.render.use_stamp_hostname": false,
|
||||
"scene.render.use_stamp_labels": true,
|
||||
"scene.render.use_stamp_lens": false,
|
||||
"scene.render.use_stamp_marker": false,
|
||||
"scene.render.use_stamp_memory": false,
|
||||
"scene.render.use_stamp_note": false,
|
||||
"scene.render.use_stamp_render_time": true,
|
||||
"scene.render.use_stamp_scene": true,
|
||||
"scene.render.use_stamp_sequencer_strip": false,
|
||||
"scene.render.use_stamp_time": true,
|
||||
"scene.render.views_format": "STEREO_3D",
|
||||
"scene.view_settings.exposure": 0.0,
|
||||
"scene.view_settings.gamma": 1.0,
|
||||
"scene.view_settings.look": "None",
|
||||
"scene.view_settings.use_curve_mapping": false,
|
||||
"scene.view_settings.view_transform": "Standard",
|
||||
"scene.display.shading.aov_name": "",
|
||||
"scene.display.shading.background_color": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"scene.display.shading.background_type": "THEME",
|
||||
"scene.display.shading.cavity_ridge_factor": 1.0,
|
||||
"scene.display.shading.cavity_type": "BOTH",
|
||||
"scene.display.shading.cavity_valley_factor": 1.0,
|
||||
"scene.display.shading.color_type": "SINGLE",
|
||||
"scene.display.shading.curvature_ridge_factor": 1.0,
|
||||
"scene.display.shading.curvature_valley_factor": 1.0,
|
||||
"scene.display.shading.light": "FLAT",
|
||||
"scene.display.shading.object_outline_color": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"scene.display.shading.render_pass": "COMBINED",
|
||||
"scene.display.shading.shadow_intensity": 0.5,
|
||||
"scene.display.shading.show_backface_culling": false,
|
||||
"scene.display.shading.show_cavity": false,
|
||||
"scene.display.shading.show_object_outline": true,
|
||||
"scene.display.shading.show_shadows": false,
|
||||
"scene.display.shading.show_specular_highlight": true,
|
||||
"scene.display.shading.show_xray": false,
|
||||
"scene.display.shading.show_xray_wireframe": false,
|
||||
"scene.display.shading.single_color": [
|
||||
1.0,
|
||||
1.0,
|
||||
1.0
|
||||
],
|
||||
"scene.display.shading.studio_light": "Default",
|
||||
"scene.display.shading.studiolight_background_alpha": 0.0,
|
||||
"scene.display.shading.studiolight_background_blur": 0.0,
|
||||
"scene.display.shading.studiolight_intensity": 0.0,
|
||||
"scene.display.shading.studiolight_rotate_z": 0.0,
|
||||
"scene.display.shading.type": "SOLID",
|
||||
"scene.display.shading.use_compositor": "DISABLED",
|
||||
"scene.display.shading.use_dof": false,
|
||||
"scene.display.shading.use_scene_lights": false,
|
||||
"scene.display.shading.use_scene_lights_render": false,
|
||||
"scene.display.shading.use_scene_world": false,
|
||||
"scene.display.shading.use_scene_world_render": false,
|
||||
"scene.display.shading.use_studiolight_view_rotation": true,
|
||||
"scene.display.shading.use_world_space_lighting": false,
|
||||
"scene.display.shading.wireframe_color_type": "MATERIAL",
|
||||
"scene.display.shading.xray_alpha": 0.5,
|
||||
"scene.display.shading.xray_alpha_wireframe": 0.0,
|
||||
"scene.display.light_direction": [
|
||||
0.5,
|
||||
0.5,
|
||||
0.5
|
||||
],
|
||||
"scene.display.matcap_ssao_attenuation": 1.0,
|
||||
"scene.display.matcap_ssao_distance": 0.20000000298023224,
|
||||
"scene.display.matcap_ssao_samples": 16,
|
||||
"scene.display.render_aa": "8",
|
||||
"scene.display.shadow_focus": 0.0,
|
||||
"scene.display.shadow_shift": 0.10000000149011612,
|
||||
"scene.display.viewport_aa": "FXAA",
|
||||
"space.overlay.backwire_opacity": 0.5,
|
||||
"space.overlay.bone_wire_alpha": 1.0,
|
||||
"space.overlay.display_handle": "SELECTED",
|
||||
"space.overlay.fade_inactive_alpha": 0.4000000059604645,
|
||||
"space.overlay.gpencil_fade_layer": 0.5,
|
||||
"space.overlay.gpencil_fade_objects": 0.5,
|
||||
"space.overlay.gpencil_grid_opacity": 0.5,
|
||||
"space.overlay.gpencil_vertex_paint_opacity": 1.0,
|
||||
"space.overlay.grid_lines": 16,
|
||||
"space.overlay.grid_scale": 1.0,
|
||||
"space.overlay.grid_subdivisions": 10,
|
||||
"space.overlay.normals_constant_screen_size": 7.0,
|
||||
"space.overlay.normals_length": 0.10000000149011612,
|
||||
"space.overlay.sculpt_curves_cage_opacity": 0.5,
|
||||
"space.overlay.sculpt_mode_face_sets_opacity": 1.0,
|
||||
"space.overlay.sculpt_mode_mask_opacity": 0.75,
|
||||
"space.overlay.show_annotation": true,
|
||||
"space.overlay.show_axis_x": false,
|
||||
"space.overlay.show_axis_y": false,
|
||||
"space.overlay.show_axis_z": false,
|
||||
"space.overlay.show_bones": true,
|
||||
"space.overlay.show_cursor": true,
|
||||
"space.overlay.show_curve_normals": false,
|
||||
"space.overlay.show_edge_bevel_weight": true,
|
||||
"space.overlay.show_edge_crease": true,
|
||||
"space.overlay.show_edge_seams": true,
|
||||
"space.overlay.show_edge_sharp": true,
|
||||
"space.overlay.show_edges": false,
|
||||
"space.overlay.show_extra_edge_angle": false,
|
||||
"space.overlay.show_extra_edge_length": false,
|
||||
"space.overlay.show_extra_face_angle": false,
|
||||
"space.overlay.show_extra_face_area": false,
|
||||
"space.overlay.show_extra_indices": false,
|
||||
"space.overlay.show_extras": true,
|
||||
"space.overlay.show_face_center": false,
|
||||
"space.overlay.show_face_normals": false,
|
||||
"space.overlay.show_face_orientation": false,
|
||||
"space.overlay.show_faces": true,
|
||||
"space.overlay.show_fade_inactive": false,
|
||||
"space.overlay.show_floor": false,
|
||||
"space.overlay.show_freestyle_edge_marks": true,
|
||||
"space.overlay.show_freestyle_face_marks": true,
|
||||
"space.overlay.show_look_dev": false,
|
||||
"space.overlay.show_motion_paths": true,
|
||||
"space.overlay.show_object_origins": false,
|
||||
"space.overlay.show_object_origins_all": false,
|
||||
"space.overlay.show_occlude_wire": false,
|
||||
"space.overlay.show_onion_skins": false,
|
||||
"space.overlay.show_ortho_grid": true,
|
||||
"space.overlay.show_outline_selected": true,
|
||||
"space.overlay.show_overlays": true,
|
||||
"space.overlay.show_paint_wire": false,
|
||||
"space.overlay.show_relationship_lines": false,
|
||||
"space.overlay.show_sculpt_curves_cage": true,
|
||||
"space.overlay.show_sculpt_face_sets": true,
|
||||
"space.overlay.show_sculpt_mask": true,
|
||||
"space.overlay.show_split_normals": false,
|
||||
"space.overlay.show_stats": false,
|
||||
"space.overlay.show_statvis": false,
|
||||
"space.overlay.show_text": true,
|
||||
"space.overlay.show_vertex_normals": false,
|
||||
"space.overlay.show_viewer_attribute": true,
|
||||
"space.overlay.show_weight": false,
|
||||
"space.overlay.show_wireframes": true,
|
||||
"space.overlay.show_wpaint_contours": false,
|
||||
"space.overlay.show_xray_bone": false,
|
||||
"space.overlay.texture_paint_mode_opacity": 1.0,
|
||||
"space.overlay.use_debug_freeze_view_culling": false,
|
||||
"space.overlay.use_gpencil_canvas_xray": false,
|
||||
"space.overlay.use_gpencil_edit_lines": true,
|
||||
"space.overlay.use_gpencil_fade_gp_objects": false,
|
||||
"space.overlay.use_gpencil_fade_layers": false,
|
||||
"space.overlay.use_gpencil_fade_objects": false,
|
||||
"space.overlay.use_gpencil_grid": false,
|
||||
"space.overlay.use_gpencil_multiedit_line_only": false,
|
||||
"space.overlay.use_gpencil_onion_skin": false,
|
||||
"space.overlay.use_gpencil_show_directions": false,
|
||||
"space.overlay.use_gpencil_show_material_name": false,
|
||||
"space.overlay.use_normals_constant_screen_size": false,
|
||||
"space.overlay.vertex_opacity": 1.0,
|
||||
"space.overlay.vertex_paint_mode_opacity": 1.0,
|
||||
"space.overlay.viewer_attribute_opacity": 1.0,
|
||||
"space.overlay.weight_paint_mode_opacity": 1.0,
|
||||
"space.overlay.wireframe_opacity": 1.0,
|
||||
"space.overlay.wireframe_threshold": 0.0,
|
||||
"space.overlay.xray_alpha_bone": 0.0
|
||||
}
|
||||
},
|
||||
"Shaded": {
|
||||
"render_type": "VIEWPORT",
|
||||
"raster_style": {
|
||||
"bpy.data.worlds[0].color": [
|
||||
1.0,
|
||||
1.0,
|
||||
1.0
|
||||
],
|
||||
"scene.render.bake_bias": 0.0010000000474974513,
|
||||
"scene.render.bake_margin": 16,
|
||||
"scene.render.bake_margin_type": "ADJACENT_FACES",
|
||||
"scene.render.bake_samples": 256,
|
||||
"scene.render.bake_type": "NORMALS",
|
||||
"scene.render.bake_user_scale": 0.0,
|
||||
"scene.render.border_max_x": 1.0,
|
||||
"scene.render.border_max_y": 1.0,
|
||||
"scene.render.border_min_x": 0.0,
|
||||
"scene.render.border_min_y": 0.0,
|
||||
"scene.render.dither_intensity": 1.0,
|
||||
"scene.render.engine": "BLENDER_WORKBENCH",
|
||||
"scene.render.film_transparent": false,
|
||||
"scene.render.filter_size": 1.5,
|
||||
"scene.render.fps": 24,
|
||||
"scene.render.fps_base": 1.0,
|
||||
"scene.render.frame_map_new": 100,
|
||||
"scene.render.frame_map_old": 100,
|
||||
"scene.render.hair_subdiv": 0,
|
||||
"scene.render.hair_type": "STRAND",
|
||||
"scene.render.line_thickness": 1.0,
|
||||
"scene.render.line_thickness_mode": "ABSOLUTE",
|
||||
"scene.render.metadata_input": "SCENE",
|
||||
"scene.render.motion_blur_shutter": 0.5,
|
||||
"scene.render.pixel_aspect_x": 1.0,
|
||||
"scene.render.pixel_aspect_y": 1.0,
|
||||
"scene.render.preview_pixel_size": "AUTO",
|
||||
"scene.render.resolution_percentage": 100,
|
||||
"scene.render.resolution_x": 1000,
|
||||
"scene.render.resolution_y": 1000,
|
||||
"scene.render.sequencer_gl_preview": "SOLID",
|
||||
"scene.render.simplify_child_particles": 1.0,
|
||||
"scene.render.simplify_child_particles_render": 1.0,
|
||||
"scene.render.simplify_gpencil": false,
|
||||
"scene.render.simplify_gpencil_antialiasing": true,
|
||||
"scene.render.simplify_gpencil_modifier": true,
|
||||
"scene.render.simplify_gpencil_onplay": false,
|
||||
"scene.render.simplify_gpencil_shader_fx": true,
|
||||
"scene.render.simplify_gpencil_tint": true,
|
||||
"scene.render.simplify_gpencil_view_fill": true,
|
||||
"scene.render.simplify_shadows": 1.0,
|
||||
"scene.render.simplify_shadows_render": 1.0,
|
||||
"scene.render.simplify_subdivision": 6,
|
||||
"scene.render.simplify_subdivision_render": 6,
|
||||
"scene.render.simplify_volumes": 1.0,
|
||||
"scene.render.stamp_font_size": 12,
|
||||
"scene.render.stamp_note_text": "",
|
||||
"scene.render.threads": 20,
|
||||
"scene.render.threads_mode": "AUTO",
|
||||
"scene.render.use_bake_clear": true,
|
||||
"scene.render.use_bake_lores_mesh": false,
|
||||
"scene.render.use_bake_multires": false,
|
||||
"scene.render.use_bake_selected_to_active": false,
|
||||
"scene.render.use_bake_user_scale": false,
|
||||
"scene.render.use_border": false,
|
||||
"scene.render.use_compositing": true,
|
||||
"scene.render.use_crop_to_border": false,
|
||||
"scene.render.use_file_extension": true,
|
||||
"scene.render.use_freestyle": false,
|
||||
"scene.render.use_high_quality_normals": false,
|
||||
"scene.render.use_lock_interface": false,
|
||||
"scene.render.use_motion_blur": false,
|
||||
"scene.render.use_multiview": false,
|
||||
"scene.render.use_overwrite": true,
|
||||
"scene.render.use_persistent_data": false,
|
||||
"scene.render.use_placeholder": false,
|
||||
"scene.render.use_render_cache": false,
|
||||
"scene.render.use_sequencer": true,
|
||||
"scene.render.use_sequencer_override_scene_strip": false,
|
||||
"scene.render.use_simplify": false,
|
||||
"scene.render.use_single_layer": false,
|
||||
"scene.render.use_stamp": false,
|
||||
"scene.render.use_stamp_camera": true,
|
||||
"scene.render.use_stamp_date": true,
|
||||
"scene.render.use_stamp_filename": true,
|
||||
"scene.render.use_stamp_frame": true,
|
||||
"scene.render.use_stamp_frame_range": false,
|
||||
"scene.render.use_stamp_hostname": false,
|
||||
"scene.render.use_stamp_labels": true,
|
||||
"scene.render.use_stamp_lens": false,
|
||||
"scene.render.use_stamp_marker": false,
|
||||
"scene.render.use_stamp_memory": false,
|
||||
"scene.render.use_stamp_note": false,
|
||||
"scene.render.use_stamp_render_time": true,
|
||||
"scene.render.use_stamp_scene": true,
|
||||
"scene.render.use_stamp_sequencer_strip": false,
|
||||
"scene.render.use_stamp_time": true,
|
||||
"scene.render.views_format": "STEREO_3D",
|
||||
"scene.view_settings.exposure": 0.0,
|
||||
"scene.view_settings.gamma": 1.0,
|
||||
"scene.view_settings.look": "None",
|
||||
"scene.view_settings.use_curve_mapping": false,
|
||||
"scene.view_settings.view_transform": "Standard",
|
||||
"scene.display.shading.aov_name": "",
|
||||
"scene.display.shading.background_color": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"scene.display.shading.background_type": "THEME",
|
||||
"scene.display.shading.cavity_ridge_factor": 1.0,
|
||||
"scene.display.shading.cavity_type": "BOTH",
|
||||
"scene.display.shading.cavity_valley_factor": 1.0,
|
||||
"scene.display.shading.color_type": "MATERIAL",
|
||||
"scene.display.shading.curvature_ridge_factor": 1.0,
|
||||
"scene.display.shading.curvature_valley_factor": 1.0,
|
||||
"scene.display.shading.light": "STUDIO",
|
||||
"scene.display.shading.object_outline_color": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"scene.display.shading.render_pass": "COMBINED",
|
||||
"scene.display.shading.shadow_intensity": 0.5,
|
||||
"scene.display.shading.show_backface_culling": false,
|
||||
"scene.display.shading.show_cavity": true,
|
||||
"scene.display.shading.show_object_outline": true,
|
||||
"scene.display.shading.show_shadows": true,
|
||||
"scene.display.shading.show_specular_highlight": true,
|
||||
"scene.display.shading.show_xray": false,
|
||||
"scene.display.shading.show_xray_wireframe": false,
|
||||
"scene.display.shading.single_color": [
|
||||
1.0,
|
||||
1.0,
|
||||
1.0
|
||||
],
|
||||
"scene.display.shading.studio_light": "Default",
|
||||
"scene.display.shading.studiolight_background_alpha": 0.0,
|
||||
"scene.display.shading.studiolight_background_blur": 0.0,
|
||||
"scene.display.shading.studiolight_intensity": 0.0,
|
||||
"scene.display.shading.studiolight_rotate_z": 0.0,
|
||||
"scene.display.shading.type": "SOLID",
|
||||
"scene.display.shading.use_compositor": "DISABLED",
|
||||
"scene.display.shading.use_dof": false,
|
||||
"scene.display.shading.use_scene_lights": false,
|
||||
"scene.display.shading.use_scene_lights_render": false,
|
||||
"scene.display.shading.use_scene_world": false,
|
||||
"scene.display.shading.use_scene_world_render": false,
|
||||
"scene.display.shading.use_studiolight_view_rotation": true,
|
||||
"scene.display.shading.use_world_space_lighting": false,
|
||||
"scene.display.shading.wireframe_color_type": "MATERIAL",
|
||||
"scene.display.shading.xray_alpha": 0.5,
|
||||
"scene.display.shading.xray_alpha_wireframe": 0.0,
|
||||
"scene.display.light_direction": [
|
||||
0.5,
|
||||
0.5,
|
||||
0.5
|
||||
],
|
||||
"scene.display.matcap_ssao_attenuation": 1.0,
|
||||
"scene.display.matcap_ssao_distance": 0.20000000298023224,
|
||||
"scene.display.matcap_ssao_samples": 16,
|
||||
"scene.display.render_aa": "8",
|
||||
"scene.display.shadow_focus": 0.0,
|
||||
"scene.display.shadow_shift": 0.10000000149011612,
|
||||
"scene.display.viewport_aa": "FXAA",
|
||||
"space.overlay.backwire_opacity": 0.5,
|
||||
"space.overlay.bone_wire_alpha": 1.0,
|
||||
"space.overlay.display_handle": "SELECTED",
|
||||
"space.overlay.fade_inactive_alpha": 0.4000000059604645,
|
||||
"space.overlay.gpencil_fade_layer": 0.5,
|
||||
"space.overlay.gpencil_fade_objects": 0.5,
|
||||
"space.overlay.gpencil_grid_opacity": 0.5,
|
||||
"space.overlay.gpencil_vertex_paint_opacity": 1.0,
|
||||
"space.overlay.grid_lines": 16,
|
||||
"space.overlay.grid_scale": 1.0,
|
||||
"space.overlay.grid_subdivisions": 10,
|
||||
"space.overlay.normals_constant_screen_size": 7.0,
|
||||
"space.overlay.normals_length": 0.10000000149011612,
|
||||
"space.overlay.sculpt_curves_cage_opacity": 0.5,
|
||||
"space.overlay.sculpt_mode_face_sets_opacity": 1.0,
|
||||
"space.overlay.sculpt_mode_mask_opacity": 0.75,
|
||||
"space.overlay.show_annotation": true,
|
||||
"space.overlay.show_axis_x": false,
|
||||
"space.overlay.show_axis_y": false,
|
||||
"space.overlay.show_axis_z": false,
|
||||
"space.overlay.show_bones": true,
|
||||
"space.overlay.show_cursor": true,
|
||||
"space.overlay.show_curve_normals": false,
|
||||
"space.overlay.show_edge_bevel_weight": true,
|
||||
"space.overlay.show_edge_crease": true,
|
||||
"space.overlay.show_edge_seams": true,
|
||||
"space.overlay.show_edge_sharp": true,
|
||||
"space.overlay.show_edges": false,
|
||||
"space.overlay.show_extra_edge_angle": false,
|
||||
"space.overlay.show_extra_edge_length": false,
|
||||
"space.overlay.show_extra_face_angle": false,
|
||||
"space.overlay.show_extra_face_area": false,
|
||||
"space.overlay.show_extra_indices": false,
|
||||
"space.overlay.show_extras": true,
|
||||
"space.overlay.show_face_center": false,
|
||||
"space.overlay.show_face_normals": false,
|
||||
"space.overlay.show_face_orientation": false,
|
||||
"space.overlay.show_faces": true,
|
||||
"space.overlay.show_fade_inactive": false,
|
||||
"space.overlay.show_floor": false,
|
||||
"space.overlay.show_freestyle_edge_marks": true,
|
||||
"space.overlay.show_freestyle_face_marks": true,
|
||||
"space.overlay.show_look_dev": false,
|
||||
"space.overlay.show_motion_paths": true,
|
||||
"space.overlay.show_object_origins": false,
|
||||
"space.overlay.show_object_origins_all": false,
|
||||
"space.overlay.show_occlude_wire": false,
|
||||
"space.overlay.show_onion_skins": false,
|
||||
"space.overlay.show_ortho_grid": true,
|
||||
"space.overlay.show_outline_selected": true,
|
||||
"space.overlay.show_overlays": true,
|
||||
"space.overlay.show_paint_wire": false,
|
||||
"space.overlay.show_relationship_lines": false,
|
||||
"space.overlay.show_sculpt_curves_cage": true,
|
||||
"space.overlay.show_sculpt_face_sets": true,
|
||||
"space.overlay.show_sculpt_mask": true,
|
||||
"space.overlay.show_split_normals": false,
|
||||
"space.overlay.show_stats": false,
|
||||
"space.overlay.show_statvis": false,
|
||||
"space.overlay.show_text": true,
|
||||
"space.overlay.show_vertex_normals": false,
|
||||
"space.overlay.show_viewer_attribute": true,
|
||||
"space.overlay.show_weight": false,
|
||||
"space.overlay.show_wireframes": false,
|
||||
"space.overlay.show_wpaint_contours": false,
|
||||
"space.overlay.show_xray_bone": false,
|
||||
"space.overlay.texture_paint_mode_opacity": 1.0,
|
||||
"space.overlay.use_debug_freeze_view_culling": false,
|
||||
"space.overlay.use_gpencil_canvas_xray": false,
|
||||
"space.overlay.use_gpencil_edit_lines": true,
|
||||
"space.overlay.use_gpencil_fade_gp_objects": false,
|
||||
"space.overlay.use_gpencil_fade_layers": false,
|
||||
"space.overlay.use_gpencil_fade_objects": false,
|
||||
"space.overlay.use_gpencil_grid": false,
|
||||
"space.overlay.use_gpencil_multiedit_line_only": false,
|
||||
"space.overlay.use_gpencil_onion_skin": false,
|
||||
"space.overlay.use_gpencil_show_directions": false,
|
||||
"space.overlay.use_gpencil_show_material_name": false,
|
||||
"space.overlay.use_normals_constant_screen_size": false,
|
||||
"space.overlay.vertex_opacity": 1.0,
|
||||
"space.overlay.vertex_paint_mode_opacity": 1.0,
|
||||
"space.overlay.viewer_attribute_opacity": 1.0,
|
||||
"space.overlay.weight_paint_mode_opacity": 1.0,
|
||||
"space.overlay.wireframe_opacity": 1.0,
|
||||
"space.overlay.wireframe_threshold": 0.0,
|
||||
"space.overlay.xray_alpha_bone": 0.0
|
||||
}
|
||||
},
|
||||
"Blender Default": {
|
||||
"render_type": "VIEWPORT",
|
||||
"raster_style": {
|
||||
"bpy.data.worlds[0].color": [
|
||||
0.05087608844041824,
|
||||
0.05087608844041824,
|
||||
0.05087608844041824
|
||||
],
|
||||
"scene.render.bake_bias": 0.0010000000474974513,
|
||||
"scene.render.bake_margin": 16,
|
||||
"scene.render.bake_margin_type": "ADJACENT_FACES",
|
||||
"scene.render.bake_samples": 256,
|
||||
"scene.render.bake_type": "NORMALS",
|
||||
"scene.render.bake_user_scale": 0.0,
|
||||
"scene.render.border_max_x": 1.0,
|
||||
"scene.render.border_max_y": 1.0,
|
||||
"scene.render.border_min_x": 0.0,
|
||||
"scene.render.border_min_y": 0.0,
|
||||
"scene.render.dither_intensity": 1.0,
|
||||
"scene.render.engine": "BLENDER_EEVEE",
|
||||
"scene.render.film_transparent": false,
|
||||
"scene.render.filter_size": 1.5,
|
||||
"scene.render.fps": 24,
|
||||
"scene.render.fps_base": 1.0,
|
||||
"scene.render.frame_map_new": 100,
|
||||
"scene.render.frame_map_old": 100,
|
||||
"scene.render.hair_subdiv": 0,
|
||||
"scene.render.hair_type": "STRAND",
|
||||
"scene.render.line_thickness": 1.0,
|
||||
"scene.render.line_thickness_mode": "ABSOLUTE",
|
||||
"scene.render.metadata_input": "SCENE",
|
||||
"scene.render.motion_blur_shutter": 0.5,
|
||||
"scene.render.pixel_aspect_x": 1.0,
|
||||
"scene.render.pixel_aspect_y": 1.0,
|
||||
"scene.render.preview_pixel_size": "AUTO",
|
||||
"scene.render.resolution_percentage": 100,
|
||||
"scene.render.resolution_x": 1000,
|
||||
"scene.render.resolution_y": 1000,
|
||||
"scene.render.sequencer_gl_preview": "SOLID",
|
||||
"scene.render.simplify_child_particles": 1.0,
|
||||
"scene.render.simplify_child_particles_render": 1.0,
|
||||
"scene.render.simplify_gpencil": false,
|
||||
"scene.render.simplify_gpencil_antialiasing": true,
|
||||
"scene.render.simplify_gpencil_modifier": true,
|
||||
"scene.render.simplify_gpencil_onplay": false,
|
||||
"scene.render.simplify_gpencil_shader_fx": true,
|
||||
"scene.render.simplify_gpencil_tint": true,
|
||||
"scene.render.simplify_gpencil_view_fill": true,
|
||||
"scene.render.simplify_shadows": 1.0,
|
||||
"scene.render.simplify_shadows_render": 1.0,
|
||||
"scene.render.simplify_subdivision": 6,
|
||||
"scene.render.simplify_subdivision_render": 6,
|
||||
"scene.render.simplify_volumes": 1.0,
|
||||
"scene.render.stamp_font_size": 12,
|
||||
"scene.render.stamp_note_text": "",
|
||||
"scene.render.threads": 20,
|
||||
"scene.render.threads_mode": "AUTO",
|
||||
"scene.render.use_bake_clear": true,
|
||||
"scene.render.use_bake_lores_mesh": false,
|
||||
"scene.render.use_bake_multires": false,
|
||||
"scene.render.use_bake_selected_to_active": false,
|
||||
"scene.render.use_bake_user_scale": false,
|
||||
"scene.render.use_border": false,
|
||||
"scene.render.use_compositing": true,
|
||||
"scene.render.use_crop_to_border": false,
|
||||
"scene.render.use_file_extension": true,
|
||||
"scene.render.use_freestyle": false,
|
||||
"scene.render.use_high_quality_normals": false,
|
||||
"scene.render.use_lock_interface": false,
|
||||
"scene.render.use_motion_blur": false,
|
||||
"scene.render.use_multiview": false,
|
||||
"scene.render.use_overwrite": true,
|
||||
"scene.render.use_persistent_data": false,
|
||||
"scene.render.use_placeholder": false,
|
||||
"scene.render.use_render_cache": false,
|
||||
"scene.render.use_sequencer": true,
|
||||
"scene.render.use_sequencer_override_scene_strip": false,
|
||||
"scene.render.use_simplify": false,
|
||||
"scene.render.use_single_layer": false,
|
||||
"scene.render.use_stamp": false,
|
||||
"scene.render.use_stamp_camera": true,
|
||||
"scene.render.use_stamp_date": true,
|
||||
"scene.render.use_stamp_filename": true,
|
||||
"scene.render.use_stamp_frame": true,
|
||||
"scene.render.use_stamp_frame_range": false,
|
||||
"scene.render.use_stamp_hostname": false,
|
||||
"scene.render.use_stamp_labels": true,
|
||||
"scene.render.use_stamp_lens": false,
|
||||
"scene.render.use_stamp_marker": false,
|
||||
"scene.render.use_stamp_memory": false,
|
||||
"scene.render.use_stamp_note": false,
|
||||
"scene.render.use_stamp_render_time": true,
|
||||
"scene.render.use_stamp_scene": true,
|
||||
"scene.render.use_stamp_sequencer_strip": false,
|
||||
"scene.render.use_stamp_time": true,
|
||||
"scene.render.views_format": "STEREO_3D",
|
||||
"scene.view_settings.exposure": 0.0,
|
||||
"scene.view_settings.gamma": 1.0,
|
||||
"scene.view_settings.look": "None",
|
||||
"scene.view_settings.use_curve_mapping": false,
|
||||
"scene.view_settings.view_transform": "Filmic",
|
||||
"scene.display.shading.aov_name": "",
|
||||
"scene.display.shading.background_color": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"scene.display.shading.background_type": "THEME",
|
||||
"scene.display.shading.cavity_ridge_factor": 1.0,
|
||||
"scene.display.shading.cavity_type": "WORLD",
|
||||
"scene.display.shading.cavity_valley_factor": 1.0,
|
||||
"scene.display.shading.color_type": "MATERIAL",
|
||||
"scene.display.shading.curvature_ridge_factor": 0.0,
|
||||
"scene.display.shading.curvature_valley_factor": 0.0,
|
||||
"scene.display.shading.light": "STUDIO",
|
||||
"scene.display.shading.object_outline_color": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"scene.display.shading.render_pass": "COMBINED",
|
||||
"scene.display.shading.shadow_intensity": 0.5,
|
||||
"scene.display.shading.show_backface_culling": false,
|
||||
"scene.display.shading.show_cavity": false,
|
||||
"scene.display.shading.show_object_outline": false,
|
||||
"scene.display.shading.show_shadows": false,
|
||||
"scene.display.shading.show_specular_highlight": true,
|
||||
"scene.display.shading.show_xray": false,
|
||||
"scene.display.shading.show_xray_wireframe": false,
|
||||
"scene.display.shading.single_color": [
|
||||
0.800000011920929,
|
||||
0.800000011920929,
|
||||
0.800000011920929
|
||||
],
|
||||
"scene.display.shading.studio_light": "Default",
|
||||
"scene.display.shading.studiolight_background_alpha": 0.0,
|
||||
"scene.display.shading.studiolight_background_blur": 0.0,
|
||||
"scene.display.shading.studiolight_intensity": 0.0,
|
||||
"scene.display.shading.studiolight_rotate_z": 0.0,
|
||||
"scene.display.shading.type": "SOLID",
|
||||
"scene.display.shading.use_compositor": "DISABLED",
|
||||
"scene.display.shading.use_dof": false,
|
||||
"scene.display.shading.use_scene_lights": false,
|
||||
"scene.display.shading.use_scene_lights_render": false,
|
||||
"scene.display.shading.use_scene_world": false,
|
||||
"scene.display.shading.use_scene_world_render": false,
|
||||
"scene.display.shading.use_studiolight_view_rotation": true,
|
||||
"scene.display.shading.use_world_space_lighting": false,
|
||||
"scene.display.shading.wireframe_color_type": "MATERIAL",
|
||||
"scene.display.shading.xray_alpha": 0.5,
|
||||
"scene.display.shading.xray_alpha_wireframe": 0.0,
|
||||
"scene.display.light_direction": [
|
||||
0.5773502588272095,
|
||||
0.5773502588272095,
|
||||
0.5773502588272095
|
||||
],
|
||||
"scene.display.matcap_ssao_attenuation": 1.0,
|
||||
"scene.display.matcap_ssao_distance": 0.20000000298023224,
|
||||
"scene.display.matcap_ssao_samples": 16,
|
||||
"scene.display.render_aa": "8",
|
||||
"scene.display.shadow_focus": 0.0,
|
||||
"scene.display.shadow_shift": 0.10000000149011612,
|
||||
"scene.display.viewport_aa": "FXAA",
|
||||
"space.overlay.backwire_opacity": 0.5,
|
||||
"space.overlay.bone_wire_alpha": 1.0,
|
||||
"space.overlay.display_handle": "SELECTED",
|
||||
"space.overlay.fade_inactive_alpha": 0.4000000059604645,
|
||||
"space.overlay.gpencil_fade_layer": 0.5,
|
||||
"space.overlay.gpencil_fade_objects": 0.5,
|
||||
"space.overlay.gpencil_grid_opacity": 0.5,
|
||||
"space.overlay.gpencil_vertex_paint_opacity": 1.0,
|
||||
"space.overlay.grid_lines": 16,
|
||||
"space.overlay.grid_scale": 1.0,
|
||||
"space.overlay.grid_subdivisions": 10,
|
||||
"space.overlay.normals_constant_screen_size": 7.0,
|
||||
"space.overlay.normals_length": 0.10000000149011612,
|
||||
"space.overlay.sculpt_curves_cage_opacity": 0.5,
|
||||
"space.overlay.sculpt_mode_face_sets_opacity": 1.0,
|
||||
"space.overlay.sculpt_mode_mask_opacity": 0.75,
|
||||
"space.overlay.show_annotation": true,
|
||||
"space.overlay.show_axis_x": true,
|
||||
"space.overlay.show_axis_y": true,
|
||||
"space.overlay.show_axis_z": false,
|
||||
"space.overlay.show_bones": true,
|
||||
"space.overlay.show_cursor": true,
|
||||
"space.overlay.show_curve_normals": false,
|
||||
"space.overlay.show_edge_bevel_weight": true,
|
||||
"space.overlay.show_edge_crease": true,
|
||||
"space.overlay.show_edge_seams": true,
|
||||
"space.overlay.show_edge_sharp": true,
|
||||
"space.overlay.show_edges": false,
|
||||
"space.overlay.show_extra_edge_angle": false,
|
||||
"space.overlay.show_extra_edge_length": false,
|
||||
"space.overlay.show_extra_face_angle": false,
|
||||
"space.overlay.show_extra_face_area": false,
|
||||
"space.overlay.show_extra_indices": false,
|
||||
"space.overlay.show_extras": true,
|
||||
"space.overlay.show_face_center": false,
|
||||
"space.overlay.show_face_normals": false,
|
||||
"space.overlay.show_face_orientation": false,
|
||||
"space.overlay.show_faces": true,
|
||||
"space.overlay.show_fade_inactive": false,
|
||||
"space.overlay.show_floor": true,
|
||||
"space.overlay.show_freestyle_edge_marks": true,
|
||||
"space.overlay.show_freestyle_face_marks": true,
|
||||
"space.overlay.show_look_dev": false,
|
||||
"space.overlay.show_motion_paths": true,
|
||||
"space.overlay.show_object_origins": true,
|
||||
"space.overlay.show_object_origins_all": false,
|
||||
"space.overlay.show_occlude_wire": false,
|
||||
"space.overlay.show_onion_skins": false,
|
||||
"space.overlay.show_ortho_grid": true,
|
||||
"space.overlay.show_outline_selected": true,
|
||||
"space.overlay.show_overlays": true,
|
||||
"space.overlay.show_paint_wire": false,
|
||||
"space.overlay.show_relationship_lines": true,
|
||||
"space.overlay.show_sculpt_curves_cage": true,
|
||||
"space.overlay.show_sculpt_face_sets": true,
|
||||
"space.overlay.show_sculpt_mask": true,
|
||||
"space.overlay.show_split_normals": false,
|
||||
"space.overlay.show_stats": false,
|
||||
"space.overlay.show_statvis": false,
|
||||
"space.overlay.show_text": true,
|
||||
"space.overlay.show_vertex_normals": false,
|
||||
"space.overlay.show_viewer_attribute": true,
|
||||
"space.overlay.show_weight": false,
|
||||
"space.overlay.show_wireframes": false,
|
||||
"space.overlay.show_wpaint_contours": false,
|
||||
"space.overlay.show_xray_bone": false,
|
||||
"space.overlay.texture_paint_mode_opacity": 1.0,
|
||||
"space.overlay.use_debug_freeze_view_culling": false,
|
||||
"space.overlay.use_gpencil_canvas_xray": false,
|
||||
"space.overlay.use_gpencil_edit_lines": true,
|
||||
"space.overlay.use_gpencil_fade_gp_objects": false,
|
||||
"space.overlay.use_gpencil_fade_layers": false,
|
||||
"space.overlay.use_gpencil_fade_objects": false,
|
||||
"space.overlay.use_gpencil_grid": false,
|
||||
"space.overlay.use_gpencil_multiedit_line_only": false,
|
||||
"space.overlay.use_gpencil_onion_skin": false,
|
||||
"space.overlay.use_gpencil_show_directions": false,
|
||||
"space.overlay.use_gpencil_show_material_name": false,
|
||||
"space.overlay.use_normals_constant_screen_size": false,
|
||||
"space.overlay.vertex_opacity": 1.0,
|
||||
"space.overlay.vertex_paint_mode_opacity": 1.0,
|
||||
"space.overlay.viewer_attribute_opacity": 1.0,
|
||||
"space.overlay.weight_paint_mode_opacity": 1.0,
|
||||
"space.overlay.wireframe_opacity": 1.0,
|
||||
"space.overlay.wireframe_threshold": 1.0,
|
||||
"space.overlay.xray_alpha_bone": 0.0
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@@ -5,10 +5,11 @@ 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,#16,#17,#18,#19));
|
||||
#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,#20,#21));
|
||||
#2=IFCSIMPLEPROPERTYTEMPLATE('23JavTMk98ZxXhrUEnjAcf',$,'TargetView','',.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.);
|
||||
#4=IFCSIMPLEPROPERTYTEMPLATE('2T$a4OFsv2LeD5JeBKEV4f',$,'IsNTS','Whether or not the scale is intended to be significant',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.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.);
|
||||
@@ -16,13 +17,15 @@ DATA;
|
||||
#9=IFCSIMPLEPROPERTYTEMPLATE('10hT_1zrzEbRRKMXYAWvtD',$,'Metadata','',.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.);
|
||||
#12=IFCSIMPLEPROPERTYTEMPLATE('0c1$8NpYDEaBiJrj16jHIo',$,'Stylesheet','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
|
||||
#13=IFCSIMPLEPROPERTYTEMPLATE('3mRF52q81FQB$h4oTh7M45',$,'Markers','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
|
||||
#14=IFCSIMPLEPROPERTYTEMPLATE('1rhr_0N3LDtuORcEJP0KXM',$,'Symbols','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
|
||||
#15=IFCSIMPLEPROPERTYTEMPLATE('2sHDBuW7P4TROy$hL2w7ct',$,'Patterns','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.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.);
|
||||
#20=IFCSIMPLEPROPERTYTEMPLATE('0nYMT3OSj5gArVniCWZRtv',$,'ShadingStyles','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
|
||||
#21=IFCSIMPLEPROPERTYTEMPLATE('3VWG22eZXBdQwdKlzMeVQH',$,'CurrentShadingStyle','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
ENDSEC;
|
||||
END-ISO-10303-21;
|
||||
|
||||
@@ -18,7 +18,7 @@ DATA;
|
||||
#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));
|
||||
#14=IFCPROPERTYSETTEMPLATE('3V8oZ8YRD3_O7uR5vcUleS',$,'BBIM_Documentation','',.PSET_OCCURRENCEDRIVEN.,'IfcProject',(#15,#16,#17,#18,#19,#20,#21,#22,#23));
|
||||
#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.);
|
||||
@@ -27,8 +27,12 @@ DATA;
|
||||
#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.);
|
||||
#23=IFCSIMPLEPROPERTYTEMPLATE('26qFNMv7nCHgU6Jd7Anga5',$,'ShadingStylesPath','Default shading styles',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#24=IFCPROPERTYSETTEMPLATE('0I9merLinF5Ap$aZwaclgm',$,'BBIM_Dimension','',.PSET_OCCURRENCEDRIVEN.,'IfcAnnotation,IfcTypeProduct',(#25,#26,#27,#28));
|
||||
#25=IFCSIMPLEPROPERTYTEMPLATE('1rL2AbQsXD8RbpoWH5pYOV',$,'ShowDescriptionOnly','Hide the measurement values and show only annotation description',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
|
||||
#26=IFCSIMPLEPROPERTYTEMPLATE('0SVyOfB0rC2xNfdRYf3XvY',$,'SuppressZeroInches','Suppress 0 inch values in dimension annotation text (for example: 12'' - 0" -> 12'')',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
|
||||
#27=IFCSIMPLEPROPERTYTEMPLATE('2bUmj458PBqPAtUoI3MXsb',$,'TextPrefix','Text to add before annotation measurement value',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#28=IFCSIMPLEPROPERTYTEMPLATE('0bnzttUb9BPuN597uNTXOE',$,'TextSuffix','Text to add after annotation measurement value',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
|
||||
ENDSEC;
|
||||
END-ISO-10303-21;
|
||||
|
||||
@@ -44,9 +44,8 @@ class IfcExporter:
|
||||
self.file = IfcStore.get_file()
|
||||
self.set_header()
|
||||
IfcStore.update_cache()
|
||||
if bpy.context.scene.BIMProjectProperties.is_authoring:
|
||||
self.sync_all_objects()
|
||||
self.sync_edited_objects()
|
||||
self.sync_all_objects()
|
||||
self.sync_edited_objects()
|
||||
extension = self.ifc_export_settings.output_file.split(".")[-1].lower()
|
||||
if extension == "ifczip":
|
||||
with tempfile.TemporaryDirectory() as unzipped_path:
|
||||
|
||||
@@ -22,7 +22,6 @@ import addon_utils
|
||||
import ifcopenshell.api.owner.settings
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.core.owner as core_owner
|
||||
from blenderbim.bim.module.drawing.prop import RasterStyleProperty
|
||||
from bpy.app.handlers import persistent
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.module.owner.prop import get_user_person, get_user_organisation
|
||||
@@ -35,8 +34,6 @@ global_subscription_owner = object()
|
||||
|
||||
|
||||
def mode_callback(obj, data):
|
||||
if not bpy.context.scene.BIMProjectProperties.is_authoring:
|
||||
return
|
||||
objects = bpy.context.selected_objects
|
||||
if bpy.context.active_object:
|
||||
objects += [bpy.context.active_object]
|
||||
@@ -75,21 +72,13 @@ def name_callback(obj, data):
|
||||
if not obj.BIMObjectProperties.ifc_definition_id or "/" not in obj.name:
|
||||
return
|
||||
element = IfcStore.get_file().by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
if element.is_a("IfcGridAxis"):
|
||||
element.AxisTag = obj.name.split("/")[1]
|
||||
refresh_ui_data()
|
||||
if not element.is_a("IfcRoot"):
|
||||
return
|
||||
if element.is_a("IfcSpatialStructureElement") or (hasattr(element, "IsDecomposedBy") and element.IsDecomposedBy):
|
||||
collection = obj.users_collection[0]
|
||||
collection.name = obj.name
|
||||
if element.is_a("IfcGrid"):
|
||||
axis_obj = IfcStore.get_element(element.UAxes[0].id())
|
||||
axis_collection = axis_obj.users_collection[0]
|
||||
grid_collection = None
|
||||
for collection in bpy.data.collections:
|
||||
if axis_collection.name in collection.children.keys():
|
||||
grid_collection = collection
|
||||
break
|
||||
if grid_collection:
|
||||
grid_collection.name = obj.name
|
||||
if obj.BIMObjectProperties.collection:
|
||||
obj.BIMObjectProperties.collection.name = obj.name
|
||||
element.Name = "/".join(obj.name.split("/")[1:])
|
||||
refresh_ui_data()
|
||||
|
||||
@@ -112,8 +101,8 @@ def update_bim_tool_props():
|
||||
if not obj:
|
||||
return
|
||||
mode = bpy.context.mode
|
||||
current_tool = bpy.context.workspace.tools.from_space_view3d_mode(mode).idname
|
||||
if current_tool != "bim.bim_tool":
|
||||
current_tool = bpy.context.workspace.tools.from_space_view3d_mode(mode)
|
||||
if not current_tool or current_tool.idname != "bim.bim_tool":
|
||||
return
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element:
|
||||
@@ -182,6 +171,9 @@ def refresh_ui_data():
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
if isinstance(tool.Ifc.get(), ifcopenshell.sqlite):
|
||||
tool.Ifc.get().clear_cache()
|
||||
|
||||
|
||||
def purge_module_data():
|
||||
from blenderbim.bim import modules
|
||||
@@ -273,6 +265,12 @@ def get_application_version():
|
||||
)
|
||||
|
||||
|
||||
def viewport_shading_changed_callback(area):
|
||||
shading = area.spaces.active.shading.type
|
||||
if shading == "RENDERED":
|
||||
bpy.context.scene.BIMStylesProperties.active_style_type = "External"
|
||||
|
||||
|
||||
@persistent
|
||||
def setDefaultProperties(scene):
|
||||
global global_subscription_owner
|
||||
@@ -280,75 +278,21 @@ def setDefaultProperties(scene):
|
||||
bpy.msgbus.subscribe_rna(
|
||||
key=active_object_key, owner=global_subscription_owner, args=(), notify=active_object_callback
|
||||
)
|
||||
|
||||
# subscribe to changes in viewport shading mode
|
||||
# NOTE: couldn't find a way to make it work for new areas too
|
||||
# it starts working for them after blender restart though
|
||||
for screen in bpy.data.screens:
|
||||
for area in screen.areas:
|
||||
if area.type != "VIEW_3D":
|
||||
continue
|
||||
shading = area.spaces.active.shading
|
||||
key = shading.path_resolve("type", False)
|
||||
|
||||
bpy.msgbus.subscribe_rna(
|
||||
key=key, owner=global_subscription_owner, args=(area,), notify=viewport_shading_changed_callback
|
||||
)
|
||||
|
||||
ifcopenshell.api.owner.settings.get_user = lambda ifc: core_owner.get_user(tool.Owner)
|
||||
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"
|
||||
drawing_style.raster_style = json.dumps(
|
||||
{
|
||||
RasterStyleProperty.WORLD_COLOR.value: (1, 1, 1),
|
||||
RasterStyleProperty.RENDER_ENGINE.value: "BLENDER_WORKBENCH",
|
||||
RasterStyleProperty.RENDER_TRANSPARENT.value: False,
|
||||
RasterStyleProperty.SHADING_SHOW_OBJECT_OUTLINE.value: True,
|
||||
RasterStyleProperty.SHADING_SHOW_CAVITY.value: False,
|
||||
RasterStyleProperty.SHADING_CAVITY_TYPE.value: "BOTH",
|
||||
RasterStyleProperty.SHADING_CURVATURE_RIDGE_FACTOR.value: 1,
|
||||
RasterStyleProperty.SHADING_CURVATURE_VALLEY_FACTOR.value: 1,
|
||||
RasterStyleProperty.VIEW_TRANSFORM.value: "Standard",
|
||||
RasterStyleProperty.SHADING_LIGHT.value: "FLAT",
|
||||
RasterStyleProperty.SHADING_COLOR_TYPE.value: "SINGLE",
|
||||
RasterStyleProperty.SHADING_SINGLE_COLOR.value: (1, 1, 1),
|
||||
RasterStyleProperty.SHADING_SHOW_SHADOWS.value: False,
|
||||
RasterStyleProperty.SHADING_SHADOW_INTENSITY.value: 0.5,
|
||||
RasterStyleProperty.DISPLAY_LIGHT_DIRECTION.value: (0.5, 0.5, 0.5),
|
||||
RasterStyleProperty.VIEW_USE_CURVE_MAPPING.value: False,
|
||||
RasterStyleProperty.OVERLAY_SHOW_WIREFRAMES.value: True,
|
||||
RasterStyleProperty.OVERLAY_WIREFRAME_THRESHOLD.value: 0,
|
||||
RasterStyleProperty.OVERLAY_SHOW_FLOOR.value: False,
|
||||
RasterStyleProperty.OVERLAY_SHOW_AXIS_X.value: False,
|
||||
RasterStyleProperty.OVERLAY_SHOW_AXIS_Y.value: False,
|
||||
RasterStyleProperty.OVERLAY_SHOW_AXIS_Z.value: False,
|
||||
RasterStyleProperty.OVERLAY_SHOW_OBJECT_ORIGINS.value: False,
|
||||
RasterStyleProperty.OVERLAY_SHOW_RELATIONSHIP_LINES.value: False,
|
||||
}
|
||||
)
|
||||
drawing_style = bpy.context.scene.DocProperties.drawing_styles.add()
|
||||
drawing_style.name = "Shaded"
|
||||
drawing_style.render_type = "VIEWPORT"
|
||||
drawing_style.raster_style = json.dumps(
|
||||
{
|
||||
RasterStyleProperty.WORLD_COLOR.value: (1, 1, 1),
|
||||
RasterStyleProperty.RENDER_ENGINE.value: "BLENDER_WORKBENCH",
|
||||
RasterStyleProperty.RENDER_TRANSPARENT.value: False,
|
||||
RasterStyleProperty.SHADING_SHOW_OBJECT_OUTLINE.value: True,
|
||||
RasterStyleProperty.SHADING_SHOW_CAVITY.value: True,
|
||||
RasterStyleProperty.SHADING_CAVITY_TYPE.value: "BOTH",
|
||||
RasterStyleProperty.SHADING_CURVATURE_RIDGE_FACTOR.value: 1,
|
||||
RasterStyleProperty.SHADING_CURVATURE_VALLEY_FACTOR.value: 1,
|
||||
RasterStyleProperty.VIEW_TRANSFORM.value: "Standard",
|
||||
RasterStyleProperty.SHADING_LIGHT.value: "STUDIO",
|
||||
RasterStyleProperty.SHADING_COLOR_TYPE.value: "MATERIAL",
|
||||
RasterStyleProperty.SHADING_SINGLE_COLOR.value: (1, 1, 1),
|
||||
RasterStyleProperty.SHADING_SHOW_SHADOWS.value: True,
|
||||
RasterStyleProperty.SHADING_SHADOW_INTENSITY.value: 0.5,
|
||||
RasterStyleProperty.DISPLAY_LIGHT_DIRECTION.value: (0.5, 0.5, 0.5),
|
||||
RasterStyleProperty.VIEW_USE_CURVE_MAPPING.value: False,
|
||||
RasterStyleProperty.OVERLAY_SHOW_WIREFRAMES.value: False,
|
||||
RasterStyleProperty.OVERLAY_WIREFRAME_THRESHOLD.value: 0,
|
||||
RasterStyleProperty.OVERLAY_SHOW_FLOOR.value: False,
|
||||
RasterStyleProperty.OVERLAY_SHOW_AXIS_X.value: False,
|
||||
RasterStyleProperty.OVERLAY_SHOW_AXIS_Y.value: False,
|
||||
RasterStyleProperty.OVERLAY_SHOW_AXIS_Z.value: False,
|
||||
RasterStyleProperty.OVERLAY_SHOW_OBJECT_ORIGINS.value: False,
|
||||
RasterStyleProperty.OVERLAY_SHOW_RELATIONSHIP_LINES.value: False,
|
||||
}
|
||||
)
|
||||
AuthoringData.type_thumbnails = {}
|
||||
AuthoringData.type_thumbnails = {}
|
||||
|
||||
@@ -273,6 +273,9 @@ def convert_property_group_from_si(property_group, skip_props=()):
|
||||
setattr(property_group, prop_name, prop_value)
|
||||
|
||||
|
||||
# TODO this should move into ifcopenshell.util
|
||||
|
||||
|
||||
class IfcHeaderExtractor:
|
||||
def __init__(self, filepath: str):
|
||||
self.filepath = filepath
|
||||
@@ -284,6 +287,8 @@ class IfcHeaderExtractor:
|
||||
return self.extract_ifc_spf(ifc_file)
|
||||
elif extension.lower() == "ifczip":
|
||||
return self.extract_ifc_zip()
|
||||
elif extension.lower() == "ifcsqlite":
|
||||
return {} # TODO
|
||||
|
||||
def extract_ifc_spf(self, ifc_file):
|
||||
# https://www.steptools.com/stds/step/IS_final_p21e3.html#clause-8
|
||||
|
||||
@@ -120,6 +120,8 @@ class IfcStore:
|
||||
|
||||
@staticmethod
|
||||
def load_file(path):
|
||||
if not os.path.isfile(path):
|
||||
return
|
||||
extension = path.split(".")[-1]
|
||||
if extension.lower() == "ifczip":
|
||||
with tempfile.TemporaryDirectory() as unzipped_path:
|
||||
@@ -130,7 +132,9 @@ class IfcStore:
|
||||
return
|
||||
elif extension.lower() == "ifcxml":
|
||||
IfcStore.file = ifcopenshell.file(ifcopenshell.ifcopenshell_wrapper.parse_ifcxml(path))
|
||||
elif extension.lower() == "ifc":
|
||||
elif bpy.context.scene.BIMProjectProperties.should_stream:
|
||||
IfcStore.file = ifcopenshell.open(path, should_stream=True)
|
||||
else:
|
||||
IfcStore.file = ifcopenshell.open(path)
|
||||
|
||||
@staticmethod
|
||||
|
||||
@@ -36,7 +36,7 @@ import ifcopenshell.util.geolocation
|
||||
import blenderbim.tool as tool
|
||||
from itertools import chain, accumulate
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.module.drawing.prop import get_diagram_scales
|
||||
from blenderbim.bim.module.drawing.prop import ANNOTATION_TYPES_DATA
|
||||
|
||||
|
||||
class FileCopy(threading.Thread):
|
||||
@@ -187,7 +187,7 @@ class IfcImporter:
|
||||
self.settings_native.set(self.settings_native.INCLUDE_CURVES, True)
|
||||
self.settings_2d = ifcopenshell.geom.settings()
|
||||
self.settings_2d.set(self.settings_2d.INCLUDE_CURVES, True)
|
||||
self.settings_2d.set(self.settings.STRICT_TOLERANCE, True)
|
||||
self.settings_2d.set(self.settings_2d.STRICT_TOLERANCE, True)
|
||||
self.project = None
|
||||
self.has_existing_project = False
|
||||
self.collections = {}
|
||||
@@ -347,7 +347,8 @@ class IfcImporter:
|
||||
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)
|
||||
filtered_elements = self.elements | set(self.file.by_type("IfcGrid"))
|
||||
self.spatial_elements = self.get_spatial_elements_filtered_by_elements(filtered_elements)
|
||||
else:
|
||||
if self.file.schema == "IFC2X3":
|
||||
self.spatial_elements = set(self.file.by_type("IfcSpatialStructureElement"))
|
||||
@@ -361,11 +362,13 @@ class IfcImporter:
|
||||
while True:
|
||||
results.add(spatial_element)
|
||||
spatial_element = ifcopenshell.util.element.get_aggregate(spatial_element)
|
||||
if not spatial_element or spatial_element.is_a("IfcContext"):
|
||||
if not spatial_element or spatial_element.is_a() in ("IfcProject", "IfcProjectLibrary"):
|
||||
break
|
||||
return results
|
||||
|
||||
def parse_native_elements(self):
|
||||
if not self.ifc_import_settings.should_load_geometry:
|
||||
return
|
||||
for element in self.elements:
|
||||
if self.is_native(element):
|
||||
self.native_elements.add(element)
|
||||
@@ -381,7 +384,7 @@ class IfcImporter:
|
||||
representations = self.get_transformed_body_representations(element.Representation.Representations)
|
||||
|
||||
# Single swept disk solids (e.g. rebar) are better natively represented as beveled curves
|
||||
if self.is_native_swept_disk_solid(representations):
|
||||
if self.is_native_swept_disk_solid(element, representations):
|
||||
self.native_data[element.GlobalId] = {
|
||||
"representations": representations,
|
||||
"representation": self.get_body_representation(element.Representation.Representations),
|
||||
@@ -409,16 +412,20 @@ class IfcImporter:
|
||||
}
|
||||
return True
|
||||
|
||||
def is_native_swept_disk_solid(self, representations):
|
||||
def is_native_swept_disk_solid(self, element, representations):
|
||||
for representation in representations:
|
||||
items = representation["raw"].Items or [] # Be forgiving of invalid IFCs because Revit :(
|
||||
if len(items) == 1 and items[0].is_a("IfcSweptDiskSolid"):
|
||||
if tool.Pset.get_element_pset(element, "BBIM_Railing"):
|
||||
return False
|
||||
return True
|
||||
elif len(items) and ( # See #2508 why we accommodate for invalid IFCs here
|
||||
items[0].is_a("IfcSweptDiskSolid")
|
||||
and len({i.is_a() for i in items}) == 1
|
||||
and len({i.Radius for i in items}) == 1
|
||||
):
|
||||
if tool.Pset.get_element_pset(element, "BBIM_Railing"):
|
||||
return False
|
||||
return True
|
||||
return False
|
||||
|
||||
@@ -573,6 +580,8 @@ class IfcImporter:
|
||||
return result
|
||||
|
||||
def create_grids(self):
|
||||
if not self.ifc_import_settings.should_load_geometry:
|
||||
return
|
||||
grids = self.file.by_type("IfcGrid")
|
||||
for grid in grids:
|
||||
shape = None
|
||||
@@ -618,34 +627,37 @@ class IfcImporter:
|
||||
|
||||
def create_element_type(self, element):
|
||||
self.ifc_import_settings.logger.info("Creating object %s", element)
|
||||
representation = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
|
||||
if not representation:
|
||||
representation = ifcopenshell.util.representation.get_representation(element, "Plan", "Annotation")
|
||||
if not representation:
|
||||
representation = ifcopenshell.util.representation.get_representation(element, "Model", "Annotation")
|
||||
mesh = None
|
||||
if representation:
|
||||
mesh_name = "{}/{}".format(representation.ContextOfItems.id(), representation.id())
|
||||
mesh = self.meshes.get(mesh_name)
|
||||
if mesh is None:
|
||||
shape = None
|
||||
try:
|
||||
shape = ifcopenshell.geom.create_shape(self.settings, representation)
|
||||
except:
|
||||
if self.ifc_import_settings.should_load_geometry:
|
||||
representation = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
|
||||
if not representation:
|
||||
representation = ifcopenshell.util.representation.get_representation(element, "Plan", "Annotation")
|
||||
if not representation:
|
||||
representation = ifcopenshell.util.representation.get_representation(element, "Model", "Annotation")
|
||||
if representation:
|
||||
mesh_name = "{}/{}".format(representation.ContextOfItems.id(), representation.id())
|
||||
mesh = self.meshes.get(mesh_name)
|
||||
if mesh is None:
|
||||
shape = None
|
||||
try:
|
||||
shape = ifcopenshell.geom.create_shape(self.settings_2d, representation)
|
||||
shape = ifcopenshell.geom.create_shape(self.settings, representation)
|
||||
except:
|
||||
self.ifc_import_settings.logger.error("Failed to generate shape for %s", element)
|
||||
if shape:
|
||||
mesh = self.create_mesh(element, shape)
|
||||
tool.Loader.link_mesh(shape, mesh)
|
||||
self.meshes[mesh_name] = mesh
|
||||
try:
|
||||
shape = ifcopenshell.geom.create_shape(self.settings_2d, representation)
|
||||
except:
|
||||
self.ifc_import_settings.logger.error("Failed to generate shape for %s", element)
|
||||
if shape:
|
||||
mesh = self.create_mesh(element, shape)
|
||||
tool.Loader.link_mesh(shape, mesh)
|
||||
self.meshes[mesh_name] = mesh
|
||||
obj = bpy.data.objects.new(tool.Loader.get_name(element), mesh)
|
||||
self.link_element(element, obj)
|
||||
self.material_creator.create(element, obj, mesh)
|
||||
self.type_products[element.GlobalId] = obj
|
||||
|
||||
def create_native_elements(self):
|
||||
if not self.ifc_import_settings.should_load_geometry:
|
||||
return
|
||||
progress = 0
|
||||
checkpoint = time.time()
|
||||
total = len(self.native_elements)
|
||||
@@ -687,21 +699,76 @@ class IfcImporter:
|
||||
self.create_generic_elements(self.elements)
|
||||
|
||||
def create_generic_elements(self, elements):
|
||||
if isinstance(self.file, ifcopenshell.sqlite):
|
||||
return self.create_generic_sqlite_elements(elements)
|
||||
|
||||
# Based on my experience in viewing BIM models, representations are prioritised as follows:
|
||||
# 1. 3D Body, 2. 2D Body, 3. 2D Plans / annotations, 4. Point clouds, 5. No representation
|
||||
# If an element has a representation that doesn't follow 1, 2, 3, or 4, it will not show by default.
|
||||
# The user can load them later if they want to view them.
|
||||
products = self.create_products(elements)
|
||||
elements -= products
|
||||
products = self.create_products(elements, settings=self.settings_curve)
|
||||
elements -= products
|
||||
products = self.create_products(elements, settings=self.settings_2d)
|
||||
elements -= products
|
||||
products = self.create_pointclouds(elements)
|
||||
elements -= products
|
||||
for element in elements:
|
||||
if self.ifc_import_settings.should_load_geometry:
|
||||
products = self.create_products(elements)
|
||||
elements -= products
|
||||
products = self.create_products(elements, settings=self.settings_curve)
|
||||
elements -= products
|
||||
products = self.create_products(elements, settings=self.settings_2d)
|
||||
elements -= products
|
||||
products = self.create_pointclouds(elements)
|
||||
elements -= products
|
||||
|
||||
total = len(elements)
|
||||
for i, element in enumerate(elements):
|
||||
if i % 250 == 0:
|
||||
print("{} / {} elements processed ...".format(i, total))
|
||||
self.create_product(element)
|
||||
|
||||
def create_generic_sqlite_elements(self, elements):
|
||||
self.geometry_cache = self.file.get_geometry([e.id() for e in elements])
|
||||
for geometry_id, geometry in self.geometry_cache["geometry"].items():
|
||||
mesh_name = tool.Loader.get_mesh_name(type("Geometry", (), {"id": geometry_id}))
|
||||
mesh = bpy.data.meshes.new(mesh_name)
|
||||
|
||||
verts = geometry["verts"]
|
||||
mesh["has_cartesian_point_offset"] = False
|
||||
|
||||
if geometry["faces"]:
|
||||
num_vertices = len(verts) // 3
|
||||
total_faces = len(geometry["faces"])
|
||||
loop_start = range(0, total_faces, 3)
|
||||
num_loops = total_faces // 3
|
||||
loop_total = [3] * num_loops
|
||||
num_vertex_indices = len(geometry["faces"])
|
||||
|
||||
mesh.vertices.add(num_vertices)
|
||||
mesh.vertices.foreach_set("co", verts)
|
||||
mesh.loops.add(num_vertex_indices)
|
||||
mesh.loops.foreach_set("vertex_index", geometry["faces"])
|
||||
mesh.polygons.add(num_loops)
|
||||
mesh.polygons.foreach_set("loop_start", loop_start)
|
||||
mesh.polygons.foreach_set("loop_total", loop_total)
|
||||
mesh.update()
|
||||
else:
|
||||
e = geometry["edges"]
|
||||
v = verts
|
||||
vertices = [[v[i], v[i + 1], v[i + 2]] for i in range(0, len(v), 3)]
|
||||
edges = [[e[i], e[i + 1]] for i in range(0, len(e), 2)]
|
||||
mesh.from_pydata(vertices, edges, [])
|
||||
|
||||
mesh["ios_materials"] = geometry["materials"]
|
||||
mesh["ios_material_ids"] = geometry["material_ids"]
|
||||
self.meshes[mesh_name] = mesh
|
||||
|
||||
total = len(elements)
|
||||
for i, element in enumerate(elements):
|
||||
if i % 250 == 0:
|
||||
print("{} / {} elements processed ...".format(i, total))
|
||||
mesh = None
|
||||
geometry_id = self.geometry_cache["shapes"][element.id()]["geometry"]
|
||||
if geometry_id:
|
||||
mesh_name = tool.Loader.get_mesh_name(type("Geometry", (), {"id": geometry_id}))
|
||||
mesh = self.meshes.get(mesh_name)
|
||||
self.create_product(element, mesh=mesh)
|
||||
|
||||
def create_products(self, products, settings=None):
|
||||
if settings is None:
|
||||
settings = self.settings
|
||||
@@ -1272,7 +1339,6 @@ class IfcImporter:
|
||||
bpy.context.view_layer.objects.active = last_obj
|
||||
context_override = {}
|
||||
bpy.ops.object.editmode_toggle(context_override)
|
||||
bpy.ops.mesh.remove_doubles(context_override)
|
||||
bpy.ops.mesh.tris_convert_to_quads(context_override)
|
||||
bpy.ops.mesh.normals_make_consistent(context_override)
|
||||
bpy.ops.object.editmode_toggle(context_override)
|
||||
@@ -1327,25 +1393,23 @@ class IfcImporter:
|
||||
self.project = {"ifc": project}
|
||||
obj = tool.Ifc.get_object(project)
|
||||
if obj:
|
||||
self.project["blender"] = obj.users_collection[0]
|
||||
self.project["blender"] = obj.BIMObjectProperties.collection
|
||||
self.has_existing_project = True
|
||||
return
|
||||
self.project["blender"] = bpy.data.collections.new(
|
||||
"{}/{}".format(self.project["ifc"].is_a(), self.project["ifc"].Name)
|
||||
)
|
||||
obj = self.create_product(self.project["ifc"])
|
||||
if obj:
|
||||
self.project["blender"].objects.link(obj)
|
||||
self.project["blender"].objects.link(obj)
|
||||
self.project["blender"].BIMCollectionProperties.obj = obj
|
||||
obj.BIMObjectProperties.collection = self.project["blender"]
|
||||
|
||||
def create_collections(self):
|
||||
if self.ifc_import_settings.collection_mode == "DECOMPOSITION":
|
||||
self.create_decomposition_collections()
|
||||
elif self.ifc_import_settings.collection_mode == "SPATIAL_DECOMPOSITION":
|
||||
self.create_spatial_decomposition_collections()
|
||||
|
||||
def create_decomposition_collections(self):
|
||||
self.create_spatial_decomposition_collections()
|
||||
self.create_aggregate_collections()
|
||||
if self.ifc_import_settings.collection_mode == "DECOMPOSITION":
|
||||
self.create_aggregate_collections()
|
||||
elif self.ifc_import_settings.collection_mode == "SPATIAL_DECOMPOSITION":
|
||||
pass
|
||||
|
||||
def create_spatial_decomposition_collections(self):
|
||||
for rel_aggregate in self.project["ifc"].IsDecomposedBy or []:
|
||||
@@ -1388,7 +1452,7 @@ class IfcImporter:
|
||||
obj = tool.Ifc.get_object(element)
|
||||
if obj:
|
||||
is_existing = True
|
||||
collection = obj.users_collection[0]
|
||||
collection = obj.BIMObjectProperties.collection
|
||||
self.collections[element.GlobalId] = collection
|
||||
if not is_existing:
|
||||
collection = bpy.data.collections.new(tool.Loader.get_name(element))
|
||||
@@ -1474,183 +1538,18 @@ class IfcImporter:
|
||||
if not blender_material:
|
||||
name = style.Name or str(style.id())
|
||||
blender_material = bpy.data.materials.new(name)
|
||||
blender_material.use_fake_user = True
|
||||
|
||||
self.link_element(style, blender_material)
|
||||
|
||||
blender_material.BIMMaterialProperties.ifc_style_id = style.id()
|
||||
self.material_creator.styles[style.id()] = blender_material
|
||||
|
||||
rendering_style = None
|
||||
texture_style = None
|
||||
|
||||
for surface_style in style.Styles:
|
||||
if surface_style.is_a() == "IfcSurfaceStyleShading":
|
||||
self.create_surface_style_shading(blender_material, surface_style)
|
||||
elif surface_style.is_a("IfcSurfaceStyleRendering"):
|
||||
rendering_style = surface_style
|
||||
self.create_surface_style_rendering(blender_material, surface_style)
|
||||
elif surface_style.is_a("IfcSurfaceStyleWithTextures"):
|
||||
texture_style = surface_style
|
||||
|
||||
if rendering_style and texture_style:
|
||||
self.create_surface_style_with_textures(blender_material, rendering_style, texture_style)
|
||||
|
||||
tool.Style.record_shading(blender_material)
|
||||
|
||||
def create_surface_style_shading(self, blender_material, surface_style):
|
||||
alpha = 1.0
|
||||
# Transparency was added in IFC4
|
||||
if hasattr(surface_style, "Transparency") and surface_style.Transparency:
|
||||
alpha = 1 - surface_style.Transparency
|
||||
blender_material.diffuse_color = (
|
||||
surface_style.SurfaceColour.Red,
|
||||
surface_style.SurfaceColour.Green,
|
||||
surface_style.SurfaceColour.Blue,
|
||||
alpha,
|
||||
)
|
||||
|
||||
def create_surface_style_rendering(self, blender_material, surface_style):
|
||||
self.create_surface_style_shading(blender_material, surface_style)
|
||||
if surface_style.ReflectanceMethod in ["PHYSICAL", "NOTDEFINED"]:
|
||||
blender_material.use_nodes = True
|
||||
bsdf = blender_material.node_tree.nodes["Principled BSDF"]
|
||||
if surface_style.DiffuseColour:
|
||||
if surface_style.DiffuseColour.is_a("IfcColourRgb"):
|
||||
bsdf.inputs["Base Color"].default_value = (
|
||||
surface_style.DiffuseColour.Red,
|
||||
surface_style.DiffuseColour.Green,
|
||||
surface_style.DiffuseColour.Blue,
|
||||
1,
|
||||
)
|
||||
elif surface_style.DiffuseColour.is_a("IfcNormalisedRatioMeasure"):
|
||||
bsdf.inputs["Base Color"].default_value = (
|
||||
surface_style.SurfaceColour.Red * surface_style.DiffuseColour.wrappedValue,
|
||||
surface_style.SurfaceColour.Green * surface_style.DiffuseColour.wrappedValue,
|
||||
surface_style.SurfaceColour.Blue * surface_style.DiffuseColour.wrappedValue,
|
||||
1,
|
||||
)
|
||||
if surface_style.SpecularColour and surface_style.SpecularColour.is_a("IfcNormalisedRatioMeasure"):
|
||||
bsdf.inputs["Metallic"].default_value = surface_style.SpecularColour.wrappedValue
|
||||
if surface_style.SpecularHighlight and surface_style.SpecularHighlight.is_a("IfcSpecularRoughness"):
|
||||
bsdf.inputs["Roughness"].default_value = surface_style.SpecularHighlight.wrappedValue
|
||||
if hasattr(surface_style, "Transparency") and surface_style.Transparency:
|
||||
bsdf.inputs["Alpha"].default_value = 1 - surface_style.Transparency
|
||||
blender_material.blend_method = "BLEND"
|
||||
elif surface_style.ReflectanceMethod == "FLAT":
|
||||
blender_material.use_nodes = True
|
||||
|
||||
output = {n.type: n for n in blender_material.node_tree.nodes}.get("OUTPUT_MATERIAL", None)
|
||||
bsdf = blender_material.node_tree.nodes["Principled BSDF"]
|
||||
|
||||
mix = blender_material.node_tree.nodes.new(type="ShaderNodeMixShader")
|
||||
mix.location = bsdf.location
|
||||
blender_material.node_tree.links.new(mix.outputs[0], output.inputs["Surface"])
|
||||
|
||||
blender_material.node_tree.nodes.remove(bsdf)
|
||||
|
||||
lightpath = blender_material.node_tree.nodes.new(type="ShaderNodeLightPath")
|
||||
lightpath.location = mix.location - mathutils.Vector((200, -200))
|
||||
blender_material.node_tree.links.new(lightpath.outputs[0], mix.inputs[0])
|
||||
|
||||
bsdf = blender_material.node_tree.nodes.new(type="ShaderNodeBsdfTransparent")
|
||||
bsdf.location = mix.location - mathutils.Vector((200, 0))
|
||||
blender_material.node_tree.links.new(bsdf.outputs[0], mix.inputs[1])
|
||||
|
||||
rgb = blender_material.node_tree.nodes.new(type="ShaderNodeRGB")
|
||||
rgb.location = mix.location - mathutils.Vector((200, 200))
|
||||
blender_material.node_tree.links.new(rgb.outputs[0], mix.inputs[2])
|
||||
|
||||
if surface_style.DiffuseColour and surface_style.DiffuseColour.is_a("IfcColourRgb"):
|
||||
rgb.outputs[0].default_value = (
|
||||
surface_style.DiffuseColour.Red,
|
||||
surface_style.DiffuseColour.Green,
|
||||
surface_style.DiffuseColour.Blue,
|
||||
1,
|
||||
)
|
||||
|
||||
def create_surface_style_with_textures(self, blender_material, rendering_style, texture_style):
|
||||
for texture in texture_style.Textures:
|
||||
mode = getattr(texture, "Mode", None)
|
||||
node = None
|
||||
|
||||
if texture.is_a("IfcImageTexture"):
|
||||
image_url = texture.URLReference
|
||||
if not os.path.abspath(texture.URLReference) and tool.Ifc.get_path():
|
||||
image_url = os.path.join(os.path.dirname(tool.Ifc.get_path()), texture.URLReference)
|
||||
|
||||
if rendering_style.ReflectanceMethod in ["PHYSICAL", "NOTDEFINED"]:
|
||||
bsdf = blender_material.node_tree.nodes["Principled BSDF"]
|
||||
if mode == "NORMAL":
|
||||
normalmap = blender_material.node_tree.nodes.new(type="ShaderNodeNormalMap")
|
||||
normalmap.location = bsdf.location - mathutils.Vector((200, 0))
|
||||
blender_material.node_tree.links.new(normalmap.outputs[0], bsdf.inputs["Normal"])
|
||||
|
||||
node = blender_material.node_tree.nodes.new(type="ShaderNodeTexImage")
|
||||
node.location = normalmap.location - mathutils.Vector((200, 0))
|
||||
image = bpy.data.images.load(image_url)
|
||||
image.colorspace_settings.name = "Non-Color"
|
||||
node.image = image
|
||||
blender_material.node_tree.links.new(node.outputs[0], normalmap.inputs["Color"])
|
||||
elif mode == "EMISSIVE":
|
||||
output = {n.type: n for n in blender_material.node_tree.nodes}.get("OUTPUT_MATERIAL", None)
|
||||
|
||||
add = blender_material.node_tree.nodes.new(type="ShaderNodeAddShader")
|
||||
add.location = bsdf.location + mathutils.Vector((200, 0))
|
||||
blender_material.node_tree.links.new(bsdf.outputs[0], add.inputs[1])
|
||||
blender_material.node_tree.links.new(add.outputs[0], output.inputs[0])
|
||||
|
||||
emission = blender_material.node_tree.nodes.new(type="ShaderNodeEmission")
|
||||
emission.location = add.location - mathutils.Vector((200, 0))
|
||||
blender_material.node_tree.links.new(emission.outputs[0], add.inputs[0])
|
||||
|
||||
node = blender_material.node_tree.nodes.new(type="ShaderNodeTexImage")
|
||||
node.location = emission.location - mathutils.Vector((200, 0))
|
||||
image = bpy.data.images.load(image_url)
|
||||
node.image = image
|
||||
blender_material.node_tree.links.new(node.outputs[0], emission.inputs[0])
|
||||
elif mode == "METALLICROUGHNESS":
|
||||
separate = blender_material.node_tree.nodes.new(type="ShaderNodeSeparateRGB")
|
||||
separate.location = bsdf.location - mathutils.Vector((200, 0))
|
||||
blender_material.node_tree.links.new(separate.outputs[1], bsdf.inputs["Roughness"])
|
||||
blender_material.node_tree.links.new(separate.outputs[2], bsdf.inputs["Metallic"])
|
||||
|
||||
node = blender_material.node_tree.nodes.new(type="ShaderNodeTexImage")
|
||||
node.location = separate.location - mathutils.Vector((200, 0))
|
||||
image = bpy.data.images.load(image_url)
|
||||
image.colorspace_settings.name = "Non-Color"
|
||||
node.image = image
|
||||
blender_material.node_tree.links.new(node.outputs[0], separate.inputs[0])
|
||||
elif mode == "OCCLUSION":
|
||||
# TODO work out how to implement glTF settings here
|
||||
# https://docs.blender.org/manual/en/dev/addons/import_export/scene_gltf2.html
|
||||
pass
|
||||
elif mode == "DIFFUSE":
|
||||
node = blender_material.node_tree.nodes.new(type="ShaderNodeTexImage")
|
||||
node.location = bsdf.location - mathutils.Vector((400, 0))
|
||||
image = bpy.data.images.load(image_url)
|
||||
node.image = image
|
||||
blender_material.node_tree.links.new(node.outputs[0], bsdf.inputs["Base Color"])
|
||||
blender_material.node_tree.links.new(node.outputs[1], bsdf.inputs["Alpha"])
|
||||
blender_material.blend_method = "BLEND"
|
||||
elif rendering_style.ReflectanceMethod == "FLAT":
|
||||
bsdf = blender_material.node_tree.nodes["Mix Shader"]
|
||||
if mode == "EMISSIVE":
|
||||
node = blender_material.node_tree.nodes.new(type="ShaderNodeTexImage")
|
||||
node.location = bsdf.location - mathutils.Vector((200, 0))
|
||||
image = bpy.data.images.load(image_url)
|
||||
node.image = image
|
||||
blender_material.node_tree.links.new(node.outputs[0], bsdf.inputs[2])
|
||||
|
||||
if node and getattr(texture, "IsMappedBy", None):
|
||||
coordinates = texture.IsMappedBy[0]
|
||||
coord = blender_material.node_tree.nodes.new(type="ShaderNodeTexCoord")
|
||||
coord.location = node.location - mathutils.Vector((200, 0))
|
||||
if coordinates.is_a("IfcTextureCoordinateGenerator") and coordinates.Mode == "COORD":
|
||||
blender_material.node_tree.links.new(coord.outputs["Generated"], node.inputs["Vector"])
|
||||
elif coordinates.is_a("IfcTextureCoordinateGenerator") and coordinates.Mode == "COORD-EYE":
|
||||
blender_material.node_tree.links.new(coord.outputs["Camera"], node.inputs["Vector"])
|
||||
else:
|
||||
blender_material.node_tree.links.new(coord.outputs["UV"], node.inputs["Vector"])
|
||||
style_elements = tool.Style.get_style_elements(blender_material)
|
||||
if tool.Style.has_blender_external_style(style_elements):
|
||||
blender_material.BIMStyleProperties.active_style_type = "External"
|
||||
else:
|
||||
blender_material.BIMStyleProperties.active_style_type = "Shading"
|
||||
|
||||
def place_objects_in_collections(self):
|
||||
for ifc_definition_id, obj in self.added_data.items():
|
||||
@@ -1670,6 +1569,8 @@ class IfcImporter:
|
||||
return
|
||||
elif element.GlobalId in self.collections:
|
||||
collection = self.collections[element.GlobalId]
|
||||
collection.BIMCollectionProperties.obj = obj
|
||||
obj.BIMObjectProperties.collection = collection
|
||||
collection.name = obj.name
|
||||
return collection.objects.link(obj)
|
||||
elif getattr(element, "Decomposes", None):
|
||||
@@ -1684,7 +1585,10 @@ class IfcImporter:
|
||||
elif element.is_a("IfcGridAxis"):
|
||||
return
|
||||
elif element.GlobalId in self.collections:
|
||||
return self.collections[element.GlobalId].objects.link(obj)
|
||||
collection = self.collections[element.GlobalId]
|
||||
collection.BIMCollectionProperties.obj = obj
|
||||
obj.BIMObjectProperties.collection = collection
|
||||
return collection.objects.link(obj)
|
||||
elif element.is_a("IfcTypeObject"):
|
||||
return self.type_collection.objects.link(obj)
|
||||
elif element.is_a("IfcStructuralMember"):
|
||||
@@ -1711,14 +1615,8 @@ class IfcImporter:
|
||||
bpy.context.scene.collection.objects.link(obj)
|
||||
|
||||
def is_curve_annotation(self, element):
|
||||
return element.ObjectType in [
|
||||
"DIMENSION",
|
||||
"EQUAL_DIMENSION",
|
||||
"PLAN_LEVEL",
|
||||
"SECTION_LEVEL",
|
||||
"STAIR_ARROW",
|
||||
"TEXT_LEADER",
|
||||
]
|
||||
object_type = element.ObjectType
|
||||
return object_type in ANNOTATION_TYPES_DATA and ANNOTATION_TYPES_DATA[object_type][3] == "curve"
|
||||
|
||||
def get_drawing_group(self, element):
|
||||
for rel in element.HasAssignments or []:
|
||||
@@ -1726,7 +1624,10 @@ class IfcImporter:
|
||||
return rel.RelatingGroup
|
||||
|
||||
def get_element_matrix(self, element):
|
||||
result = ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement)
|
||||
if isinstance(element, ifcopenshell.sqlite_entity):
|
||||
result = self.geometry_cache["shapes"][element.id()]["matrix"]
|
||||
else:
|
||||
result = ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement)
|
||||
result[0][3] *= self.unit_scale
|
||||
result[1][3] *= self.unit_scale
|
||||
result[2][3] *= self.unit_scale
|
||||
@@ -1883,6 +1784,7 @@ class IfcImporter:
|
||||
mesh.polygons.add(num_loops)
|
||||
mesh.polygons.foreach_set("loop_start", loop_start)
|
||||
mesh.polygons.foreach_set("loop_total", loop_total)
|
||||
mesh.polygons.foreach_set("use_smooth", [0] * total_faces)
|
||||
mesh.update()
|
||||
else:
|
||||
e = geometry.edges
|
||||
@@ -1959,6 +1861,7 @@ class IfcImportSettings:
|
||||
self.should_use_cpu_multiprocessing = True
|
||||
self.merge_mode = None
|
||||
self.should_merge_materials_by_colour = False
|
||||
self.should_load_geometry = True
|
||||
self.should_use_native_meshes = False
|
||||
self.should_clean_mesh = True
|
||||
self.should_cache = True
|
||||
@@ -1988,6 +1891,7 @@ class IfcImportSettings:
|
||||
settings.should_use_cpu_multiprocessing = props.should_use_cpu_multiprocessing
|
||||
settings.merge_mode = props.merge_mode
|
||||
settings.should_merge_materials_by_colour = props.should_merge_materials_by_colour
|
||||
settings.should_load_geometry = props.should_load_geometry
|
||||
settings.should_use_native_meshes = props.should_use_native_meshes
|
||||
settings.should_clean_mesh = props.should_clean_mesh
|
||||
settings.should_cache = props.should_cache
|
||||
|
||||
@@ -40,16 +40,31 @@ class BIM_OT_assign_object(bpy.types.Operator, Operator):
|
||||
bl_label = "Assign Object"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
relating_object: bpy.props.IntProperty()
|
||||
related_object: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
core.assign_object(
|
||||
tool.Ifc,
|
||||
tool.Aggregate,
|
||||
tool.Collector,
|
||||
relating_obj=tool.Ifc.get_object(tool.Ifc.get().by_id(self.relating_object)),
|
||||
related_obj=tool.Ifc.get_object(tool.Ifc.get().by_id(self.related_object)),
|
||||
)
|
||||
relating_obj = None
|
||||
if self.relating_object:
|
||||
relating_obj = tool.Ifc.get_object(tool.Ifc.get().by_id(self.relating_object))
|
||||
elif context.active_object:
|
||||
relating_obj = context.active_object
|
||||
if not relating_obj:
|
||||
return
|
||||
|
||||
for obj in bpy.context.selected_objects:
|
||||
if obj == relating_obj:
|
||||
continue
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element:
|
||||
continue
|
||||
result = core.assign_object(
|
||||
tool.Ifc,
|
||||
tool.Aggregate,
|
||||
tool.Collector,
|
||||
relating_obj=relating_obj,
|
||||
related_obj=obj,
|
||||
)
|
||||
if not result:
|
||||
self.report({"ERROR"}, f" Cannot aggregate {obj.name} to {relating_obj.name}")
|
||||
|
||||
|
||||
class BIM_OT_unassign_object(bpy.types.Operator, Operator):
|
||||
@@ -148,10 +163,7 @@ class BIM_OT_add_aggregate(bpy.types.Operator, tool.Ifc.Operator):
|
||||
core.assign_object(tool.Ifc, tool.Aggregate, tool.Collector, relating_obj=aggregate, related_obj=obj)
|
||||
|
||||
def create_aggregate(self, context, ifc_class):
|
||||
aggregate_collection = bpy.data.collections.new(f"{ifc_class}/Assembly")
|
||||
context.scene.collection.children.link(aggregate_collection)
|
||||
aggregate = bpy.data.objects.new("Assembly", None)
|
||||
aggregate_collection.objects.link(aggregate)
|
||||
bpy.ops.bim.assign_class(obj=aggregate.name, ifc_class=ifc_class)
|
||||
return aggregate
|
||||
|
||||
|
||||
@@ -56,7 +56,6 @@ class BIM_PT_aggregate(Panel):
|
||||
if props.relating_object:
|
||||
op = row.operator("bim.assign_object", icon="CHECKMARK", text="")
|
||||
op.relating_object = props.relating_object.BIMObjectProperties.ifc_definition_id
|
||||
op.related_object = context.active_object.BIMObjectProperties.ifc_definition_id
|
||||
row.operator("bim.disable_editing_aggregate", icon="CANCEL", text="")
|
||||
else:
|
||||
row = layout.row(align=True)
|
||||
|
||||
@@ -60,6 +60,8 @@ class EnableEditingAttributes(bpy.types.Operator):
|
||||
new.data_type = "string"
|
||||
new.ifc_class = data["type"]
|
||||
new.string_value = "" if new.is_null else json.dumps(data[name])
|
||||
blenderbim.bim.helper.add_attribute_description(new)
|
||||
new.description += " The degrees, minutes and seconds should follow this format : [12,34,56]"
|
||||
|
||||
blenderbim.bim.helper.import_attributes2(
|
||||
tool.Ifc.get().by_id(oprops.ifc_definition_id), props.attributes, callback=callback
|
||||
|
||||
@@ -57,6 +57,7 @@ classes = (
|
||||
operator.SelectBcfBimSnippetReference,
|
||||
operator.SelectBcfDocumentReference,
|
||||
operator.SelectBcfHeaderFile,
|
||||
operator.UnloadBcfProject,
|
||||
operator.ViewBcfTopic,
|
||||
prop.BcfReferenceLink,
|
||||
prop.BcfLabel,
|
||||
|
||||
@@ -58,7 +58,6 @@ class LoadBcfProject(bpy.types.Operator):
|
||||
filter_glob: bpy.props.StringProperty(default="*.bcf;*.bcfzip", options={"HIDDEN"})
|
||||
|
||||
def execute(self, context):
|
||||
context.scene.BCFProperties.is_loaded = False
|
||||
if self.filepath:
|
||||
bcfstore.BcfStore.bcfxml = bcf.bcfxml.load(self.filepath)
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
@@ -72,6 +71,17 @@ class LoadBcfProject(bpy.types.Operator):
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
|
||||
class UnloadBcfProject(bpy.types.Operator):
|
||||
bl_idname = "bim.unload_bcf_project"
|
||||
bl_label = "Unload BCF Project"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
bcfstore.BcfStore.set(None)
|
||||
context.scene.BCFProperties.is_loaded = False
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class LoadBcfTopics(bpy.types.Operator):
|
||||
bl_idname = "bim.load_bcf_topics"
|
||||
bl_label = "Load BCF Topics"
|
||||
|
||||
@@ -39,14 +39,16 @@ class BIM_PT_bcf(Panel):
|
||||
scene = context.scene
|
||||
props = scene.BCFProperties
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.operator("bim.new_bcf_project", text="New Project")
|
||||
row.operator("bim.load_bcf_project", text="Load Project")
|
||||
|
||||
if not props.is_loaded:
|
||||
row = layout.row(align=True)
|
||||
row.operator("bim.new_bcf_project", text="New Project")
|
||||
row.operator("bim.load_bcf_project", text="Load Project")
|
||||
return
|
||||
|
||||
row.operator("bim.save_bcf_project", text="Save Project")
|
||||
row = layout.row(align=True)
|
||||
row.operator("bim.save_bcf_project", icon="EXPORT", text="Save Project")
|
||||
row.operator("bim.unload_bcf_project", text="", icon="CANCEL")
|
||||
|
||||
row = layout.row()
|
||||
row.prop(props, "name")
|
||||
|
||||
@@ -35,10 +35,11 @@ from mathutils import Vector, Matrix
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.module.model.decorator import ProfileDecorator
|
||||
from blenderbim.bim.module.boundary.decorator import BoundaryDecorator
|
||||
import blenderbim.core
|
||||
|
||||
|
||||
def get_boundaries_collection(blender_space):
|
||||
space_collection = bpy.data.collections.get(blender_space.name, blender_space.users_collection[0])
|
||||
space_collection = blender_space.BIMObjectProperties.collection
|
||||
collection_name = f"Boundaries/{blender_space.BIMObjectProperties.ifc_definition_id}"
|
||||
boundaries_collection = space_collection.children.get(collection_name)
|
||||
if not boundaries_collection:
|
||||
|
||||
@@ -33,6 +33,10 @@ classes = (
|
||||
operator.RewindBrickClass,
|
||||
operator.ViewBrickClass,
|
||||
operator.ViewBrickItem,
|
||||
operator.UndoBrick,
|
||||
operator.RedoBrick,
|
||||
operator.SerializeBrick,
|
||||
operator.AddBrickNamespace,
|
||||
prop.Brick,
|
||||
prop.BIMBrickProperties,
|
||||
ui.BIM_PT_brickschema,
|
||||
|
||||
@@ -109,8 +109,10 @@ class BrickschemaData:
|
||||
if BrickStore.graph is None:
|
||||
return []
|
||||
results = []
|
||||
filter = ["brick", "owl", "w3", "xml"]
|
||||
for alias, uri in BrickStore.graph.namespaces():
|
||||
results.append((uri, f"{alias}: {uri}", ""))
|
||||
if alias not in filter:
|
||||
results.append((uri, f"{alias}: {uri}", ""))
|
||||
return results
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -22,7 +22,7 @@ import blenderbim.tool as tool
|
||||
import blenderbim.core.brick as core
|
||||
import blenderbim.bim.handler
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
|
||||
from blenderbim.tool.brick import BrickStore
|
||||
|
||||
class Operator:
|
||||
def execute(self, context):
|
||||
@@ -126,6 +126,7 @@ class AddBrick(bpy.types.Operator, Operator):
|
||||
namespace=props.namespace,
|
||||
brick_class=props.brick_equipment_class,
|
||||
library=library,
|
||||
label=props.new_brick_label
|
||||
)
|
||||
|
||||
|
||||
@@ -189,3 +190,54 @@ class RemoveBrick(bpy.types.Operator, Operator):
|
||||
library=tool.Ifc.get().by_id(int(props.libraries)) if props.libraries else None,
|
||||
brick_uri=props.bricks[props.active_brick_index].uri,
|
||||
)
|
||||
|
||||
class UndoBrick(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.undo_brick"
|
||||
bl_label = "Undo Brick"
|
||||
|
||||
def _execute(self, context):
|
||||
core.undo_brick(tool.Brick)
|
||||
|
||||
class RedoBrick(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.redo_brick"
|
||||
bl_label = "Redo Brick"
|
||||
|
||||
def _execute(self, context):
|
||||
core.redo_brick(tool.Brick)
|
||||
|
||||
class SerializeBrick(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.serialize_brick"
|
||||
bl_label = "Serialize Brick"
|
||||
filter_glob: bpy.props.StringProperty(default="*.ttl", options={"HIDDEN"})
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
should_save_as: bpy.props.BoolProperty(name="Should Save As", default=False, options={"HIDDEN"})
|
||||
|
||||
def invoke(self, context, event):
|
||||
if self.should_save_as or not BrickStore.path:
|
||||
WindowManager = context.window_manager
|
||||
WindowManager.fileselect_add(self)
|
||||
return {"RUNNING_MODAL"}
|
||||
else:
|
||||
return self.execute(context)
|
||||
|
||||
def _execute(self, context):
|
||||
if self.should_save_as or not BrickStore.path:
|
||||
BrickStore.path = self.filepath
|
||||
core.serialize_brick(tool.Brick)
|
||||
return {"FINISHED"}
|
||||
|
||||
@classmethod
|
||||
def description(cls, context, properties):
|
||||
if properties.should_save_as:
|
||||
return "Save Brick project to a selected file"
|
||||
return "Save the Brick project"
|
||||
|
||||
class AddBrickNamespace(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.add_brick_namespace"
|
||||
bl_label = "Add Brick Namespace"
|
||||
|
||||
def _execute(self, context):
|
||||
props = context.scene.BIMBrickProperties
|
||||
alias = props.new_brick_namespace_alias
|
||||
uri = props.new_brick_namespace_uri
|
||||
core.add_namespace(tool.Brick, alias=alias, uri=uri)
|
||||
@@ -69,3 +69,7 @@ class BIMBrickProperties(PropertyGroup):
|
||||
libraries: EnumProperty(name="Libraries", items=get_libraries)
|
||||
namespace: EnumProperty(name="Namespace", items=get_namespaces)
|
||||
brick_equipment_class: EnumProperty(name="Brick Equipment Class", items=get_brick_equipment_classes)
|
||||
brick_settings_toggled: BoolProperty(name="Brick Settings Toggled", default=False)
|
||||
new_brick_label: StringProperty(name="New Brick Label")
|
||||
new_brick_namespace_alias: StringProperty(name="New Brick Namespace Alias")
|
||||
new_brick_namespace_uri: StringProperty(name="New Brick Namespace URI")
|
||||
@@ -20,7 +20,7 @@ import blenderbim.tool as tool
|
||||
from bpy.types import Panel, UIList
|
||||
from blenderbim.bim.helper import prop_with_search
|
||||
from blenderbim.bim.module.brick.data import BrickschemaData, BrickschemaReferencesData
|
||||
|
||||
from blenderbim.tool.brick import BrickStore
|
||||
|
||||
class BIM_PT_brickschema(Panel):
|
||||
bl_label = "Brickschema Project"
|
||||
@@ -41,15 +41,39 @@ class BIM_PT_brickschema(Panel):
|
||||
row.operator("bim.load_brick_project", text="Load Project")
|
||||
return
|
||||
|
||||
if BrickStore.path:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=BrickStore.path, icon='FILEBROWSER')
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
if len(self.props.brick_breadcrumbs):
|
||||
row.operator("bim.rewind_brick_class", text="", icon="FRAME_PREV")
|
||||
row.label(text=self.props.active_brick_class)
|
||||
row.prop(data=self.props, property="brick_settings_toggled", text="", icon="PREFERENCES")
|
||||
row.operator("bim.refresh_brick_viewer", text="", icon="FILE_REFRESH")
|
||||
row.operator("bim.close_brick_project", text="", icon="CANCEL")
|
||||
|
||||
if self.props.brick_settings_toggled:
|
||||
box = self.layout.box()
|
||||
row = box.row(align=True)
|
||||
row.label(text="Active Namespace:")
|
||||
row = box.row(align=True)
|
||||
prop_with_search(row, self.props, "namespace", text="")
|
||||
row = box.row(align=True)
|
||||
row.label(text="Bind New Namespace:")
|
||||
row = box.row(align=True)
|
||||
row.prop(data=self.props, property="new_brick_namespace_alias", text="")
|
||||
col = row.column()
|
||||
col.alignment = "CENTER"
|
||||
col.scale_x = 1.1
|
||||
col.label(text=":")
|
||||
row.prop(data=self.props, property="new_brick_namespace_uri", text="")
|
||||
row.operator("bim.add_brick_namespace", text="", icon="ADD")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
prop_with_search(row, self.props, "namespace", text="")
|
||||
row.label(text="Create Entity:")
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(data=self.props, property="new_brick_label", text="")
|
||||
prop_with_search(row, self.props, "brick_equipment_class", text="")
|
||||
row.operator("bim.add_brick", text="", icon="ADD")
|
||||
|
||||
@@ -58,6 +82,16 @@ class BIM_PT_brickschema(Panel):
|
||||
row.operator("bim.add_brick_feed", text="", icon="PLUGIN")
|
||||
row.operator("bim.remove_brick", text="", icon="X")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.operator("bim.undo_brick", icon="LOOP_BACK")
|
||||
row.operator("bim.redo_brick", icon="LOOP_FORWARDS")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
op = row.operator("bim.serialize_brick", icon="EXPORT", text="Save")
|
||||
op.should_save_as = False
|
||||
op = row.operator("bim.serialize_brick", icon="FILE_TICK", text="Save As")
|
||||
op.should_save_as = True
|
||||
|
||||
self.layout.template_list("BIM_UL_bricks", "", self.props, "bricks", self.props, "active_brick_index")
|
||||
|
||||
for attribute in BrickschemaData.data["attributes"]:
|
||||
|
||||
@@ -33,7 +33,6 @@ class CadTool(WorkSpaceTool):
|
||||
bl_description = "Gives you CAD authoring related superpowers"
|
||||
bl_icon = os.path.join(os.path.dirname(__file__), "ops.authoring.cad")
|
||||
bl_widget = None
|
||||
# https://docs.blender.org/api/current/bpy.types.KeyMapItems.html
|
||||
bl_keymap = tool.Blender.get_default_selection_keypmap() + (
|
||||
("bim.cad_hotkey", {"type": "C", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_C")]}),
|
||||
("bim.cad_hotkey", {"type": "E", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_E")]}),
|
||||
@@ -142,9 +141,8 @@ class CadTool(WorkSpaceTool):
|
||||
and context.active_object.BIMRailingProperties.is_editing_path
|
||||
):
|
||||
row = layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_Q")
|
||||
row.operator("bim.cad_hotkey", text="Apply Railing Path").hotkey = "S_Q"
|
||||
row.label(text="", icon=f"EVENT_TAB")
|
||||
row.operator("bim.finish_editing_railing_path")
|
||||
row.operator("bim.cancel_editing_railing_path", icon="CANCEL", text="")
|
||||
|
||||
elif (
|
||||
@@ -153,9 +151,8 @@ class CadTool(WorkSpaceTool):
|
||||
and context.active_object.BIMRoofProperties.is_editing_path
|
||||
):
|
||||
row = layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_Q")
|
||||
row.operator("bim.cad_hotkey", text="Apply Roof Path").hotkey = "S_Q"
|
||||
row.label(text="", icon=f"EVENT_TAB")
|
||||
row.operator("bim.finish_editing_roof_path")
|
||||
row.operator("bim.cancel_editing_roof_path", icon="CANCEL", text="")
|
||||
|
||||
row = layout.row(align=True)
|
||||
@@ -271,20 +268,6 @@ class CadHotkey(bpy.types.Operator):
|
||||
elif bpy.context.active_object.data.BIMMeshProperties.subshape_type == "AXIS":
|
||||
bpy.ops.bim.edit_extrusion_axis()
|
||||
|
||||
elif (
|
||||
(RailingData.is_loaded or not RailingData.load())
|
||||
and RailingData.data["parameters"]
|
||||
and bpy.context.active_object.BIMRailingProperties.is_editing_path
|
||||
):
|
||||
bpy.ops.bim.finish_editing_railing_path()
|
||||
|
||||
elif (
|
||||
(RoofData.is_loaded or not RoofData.load())
|
||||
and RoofData.data["parameters"]
|
||||
and bpy.context.active_object.BIMRoofProperties.is_editing_path
|
||||
):
|
||||
bpy.ops.bim.finish_editing_roof_path()
|
||||
|
||||
def hotkey_S_R(self):
|
||||
if self.is_profile():
|
||||
bpy.ops.bim.add_rectangle(x=self.props.x, y=self.props.y)
|
||||
|
||||
@@ -23,7 +23,7 @@ import bmesh
|
||||
import logging
|
||||
import numpy as np
|
||||
import ifcopenshell
|
||||
from mathutils import Matrix
|
||||
from mathutils import Matrix, Vector
|
||||
from math import radians
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
|
||||
@@ -222,6 +222,7 @@ class ExecuteIfcClash(bpy.types.Operator):
|
||||
_, extension = os.path.splitext(self.filepath)
|
||||
if extension != ".json":
|
||||
self.filepath = bpy.path.ensure_ext(self.filepath, ".bcf")
|
||||
|
||||
settings = ifcclash.ClashSettings()
|
||||
settings.output = self.filepath
|
||||
settings.logger = logging.getLogger("Clash")
|
||||
@@ -230,12 +231,28 @@ class ExecuteIfcClash(bpy.types.Operator):
|
||||
|
||||
if context.scene.BIMClashProperties.should_create_clash_snapshots:
|
||||
|
||||
def get_viewpoint_snapshot(viewpoint, mat):
|
||||
def get_viewpoint_snapshot(viewpoint):
|
||||
camera = bpy.data.objects.get("IFC Clash Camera")
|
||||
if not camera:
|
||||
camera = bpy.data.objects.new("IFC Clash Camera", bpy.data.cameras.new("IFC Clash Camera"))
|
||||
context.scene.collection.objects.link(camera)
|
||||
camera.matrix_world = Matrix(mat)
|
||||
|
||||
bcf_camera = viewpoint.visualization_info.perspective_camera
|
||||
p = bcf_camera.camera_view_point
|
||||
z = bcf_camera.camera_direction
|
||||
z = Vector([z.x, z.y, z.z]) * -1
|
||||
y = bcf_camera.camera_up_vector
|
||||
y = Vector([y.x, y.y, y.z])
|
||||
x = y.cross(z)
|
||||
|
||||
mat = Matrix([
|
||||
[x[0], y[0], z[0], p.x],
|
||||
[x[1], y[1], z[1], p.y],
|
||||
[x[2], y[2], z[2], p.z],
|
||||
[0, 0, 0, 0],
|
||||
])
|
||||
|
||||
camera.matrix_world = mat
|
||||
context.scene.camera = camera
|
||||
camera.data.angle = radians(60)
|
||||
area = next(area for area in context.screen.areas if area.type == "VIEW_3D")
|
||||
@@ -246,7 +263,8 @@ class ExecuteIfcClash(bpy.types.Operator):
|
||||
context.scene.render.image_settings.file_format = "PNG"
|
||||
context.scene.render.filepath = os.path.join(context.scene.BIMProperties.data_dir, "snapshot.png")
|
||||
bpy.ops.render.opengl(write_still=True)
|
||||
return context.scene.render.filepath
|
||||
with open(context.scene.render.filepath, "rb") as f:
|
||||
return ("snapshot.png", f.read())
|
||||
|
||||
clasher.get_viewpoint_snapshot = get_viewpoint_snapshot
|
||||
|
||||
|
||||
@@ -76,6 +76,8 @@ classes = (
|
||||
operator.LoadCostItemTaskQuantities,
|
||||
operator.LoadCostItemResourceQuantities,
|
||||
operator.ChangeParentCostItem,
|
||||
operator.CopyCostItem,
|
||||
operator.AddCurrency,
|
||||
prop.CostItem,
|
||||
prop.CostItemQuantity,
|
||||
prop.CostItemType,
|
||||
|
||||
@@ -46,9 +46,16 @@ class CostSchedulesData:
|
||||
"cost_quantities": cls.cost_quantities(),
|
||||
"cost_values": cls.cost_values(),
|
||||
"quantity_types": cls.quantity_types(),
|
||||
"currency": cls.currency(),
|
||||
}
|
||||
cls.is_loaded = True
|
||||
|
||||
@classmethod
|
||||
def currency(cls):
|
||||
unit = tool.Unit.get_project_currency_unit()
|
||||
if unit:
|
||||
return {"id": unit.id(), "name": unit.Currency}
|
||||
|
||||
@classmethod
|
||||
def total_cost_schedules(cls):
|
||||
return len(tool.Ifc.get().by_type("IfcCostSchedule"))
|
||||
@@ -62,6 +69,7 @@ class CostSchedulesData:
|
||||
{
|
||||
"id": schedule.id(),
|
||||
"name": schedule.Name or "Unnamed",
|
||||
"predefined_type": ifcopenshell.util.element.get_predefined_type(schedule),
|
||||
}
|
||||
)
|
||||
else:
|
||||
@@ -70,6 +78,7 @@ class CostSchedulesData:
|
||||
{
|
||||
"id": schedule.id(),
|
||||
"name": schedule.Name or "Unnamed",
|
||||
"predefined_type": ifcopenshell.util.element.get_predefined_type(schedule),
|
||||
}
|
||||
)
|
||||
return results
|
||||
@@ -107,6 +116,7 @@ class CostSchedulesData:
|
||||
data["UnitBasisUnitSymbol"] = None
|
||||
data["TotalAppliedValue"] = 0.0
|
||||
data["TotalCost"] = 0.0
|
||||
has_unit_basis = False
|
||||
if root_element.is_a("IfcCostItem"):
|
||||
values = root_element.CostValues
|
||||
elif root_element.is_a("IfcConstructionResource"):
|
||||
@@ -119,10 +129,15 @@ class CostSchedulesData:
|
||||
cost_value_data = cls._cost_values[cost_value.id()]
|
||||
data["UnitBasisValueComponent"] = cost_value_data["UnitBasis"]["ValueComponent"]
|
||||
data["UnitBasisUnitSymbol"] = cost_value_data["UnitBasis"]["UnitSymbol"]
|
||||
if data["UnitBasisValueComponent"]:
|
||||
data["TotalCost"] = data["TotalCostQuantity"] / data["UnitBasisValueComponent"] * data["TotalAppliedValue"]
|
||||
has_unit_basis = True
|
||||
if has_unit_basis:
|
||||
data["TotalCost"] = data["TotalAppliedValue"] / data["UnitBasisValueComponent"]
|
||||
else:
|
||||
data["TotalCost"] = data["TotalCostQuantity"] * data["TotalAppliedValue"]
|
||||
if data["TotalCostQuantity"] is not None:
|
||||
data["TotalCost"] = data["TotalAppliedValue"] * data["TotalCostQuantity"]
|
||||
else:
|
||||
data["TotalCost"] = data["TotalAppliedValue"]
|
||||
data["TotalAppliedValue"] = None
|
||||
|
||||
@classmethod
|
||||
def _load_cost_item_quantities(cls, cost_item, data):
|
||||
|
||||
@@ -97,10 +97,9 @@ class AddSummaryCostItem(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_label = "Add Cost Item"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Add a summary cost item"
|
||||
cost_schedule: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
core.add_summary_cost_item(tool.Ifc, tool.Cost, cost_schedule=tool.Ifc.get().by_id(self.cost_schedule))
|
||||
core.add_summary_cost_item(tool.Ifc, tool.Cost, cost_schedule=tool.Cost.get_active_cost_schedule())
|
||||
|
||||
|
||||
class AddCostItem(bpy.types.Operator, tool.Ifc.Operator):
|
||||
@@ -114,6 +113,16 @@ class AddCostItem(bpy.types.Operator, tool.Ifc.Operator):
|
||||
core.add_cost_item(tool.Ifc, tool.Cost, cost_item=tool.Ifc.get().by_id(self.cost_item))
|
||||
|
||||
|
||||
class CopyCostItem(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.copy_cost_item"
|
||||
bl_label = "Copy Cost Item"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Copy a cost item"
|
||||
|
||||
def _execute(self, context):
|
||||
core.copy_cost_item(tool.Ifc, tool.Cost)
|
||||
|
||||
|
||||
class ExpandCostItem(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.expand_cost_item"
|
||||
bl_label = "Expand Cost Item"
|
||||
@@ -154,7 +163,7 @@ class RemoveCostItem(bpy.types.Operator, tool.Ifc.Operator):
|
||||
cost_item: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
core.remove_cost_item(tool.Ifc, tool.Cost, cost_item=tool.Ifc.get().by_id(self.cost_item))
|
||||
core.remove_cost_item(tool.Ifc, tool.Cost, cost_item_id=self.cost_item)
|
||||
|
||||
|
||||
class EnableEditingCostItem(bpy.types.Operator, tool.Ifc.Operator):
|
||||
@@ -214,8 +223,9 @@ class UnassignCostItemType(bpy.types.Operator, tool.Ifc.Operator):
|
||||
core.unassign_cost_item_type(
|
||||
tool.Ifc,
|
||||
tool.Cost,
|
||||
self.cost_item,
|
||||
products=[tool.Ifc.get().by_id(self.related_object)] if self.related_object else [],
|
||||
tool.Spatial,
|
||||
cost_item=tool.Ifc.get().by_id(self.cost_item),
|
||||
product_types=[tool.Ifc.get().by_id(self.related_object)] if self.related_object else [],
|
||||
)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -491,7 +501,7 @@ class AddCostColumn(bpy.types.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
name: bpy.props.StringProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
def execute(self, context):
|
||||
core.add_cost_column(tool.Cost, self.name)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -619,15 +629,21 @@ class ExportCostSchedules(bpy.types.Operator):
|
||||
bl_label = "Export Cost Schedule"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Export a cost schedule to a CSV, XSLX OR ODS file"
|
||||
cost_schedule: bpy.props.IntProperty()
|
||||
format: bpy.props.EnumProperty("Format", items=(("CSV", "CSV", ""), ("XLSX", "XLSX", ""), ("ODS", "ODS", "")))
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
|
||||
def execute(self, context):
|
||||
core.export_cost_schedules(tool.Cost, format=self.format)
|
||||
cost_schedule = tool.Ifc.get().by_id(self.cost_schedule) if self.cost_schedule else None
|
||||
r = core.export_cost_schedules(tool.Cost, filepath=self.filepath, format=self.format, cost_schedule=cost_schedule)
|
||||
if isinstance(r, str):
|
||||
self.report({"ERROR"}, r)
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
wm = context.window_manager
|
||||
return wm.invoke_props_dialog(self)
|
||||
wm.fileselect_add(self)
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
def draw(self, context):
|
||||
self.layout.label(text="Choose a format")
|
||||
@@ -667,7 +683,9 @@ class LoadProductCostItems(bpy.types.Operator):
|
||||
return True
|
||||
|
||||
def execute(self, context):
|
||||
core.load_product_cost_items(tool.Cost, product=tool.Ifc.get().by_id(context.active_object.BIMObjectProperties.ifc_definition_id))
|
||||
core.load_product_cost_items(
|
||||
tool.Cost, product=tool.Ifc.get().by_id(context.active_object.BIMObjectProperties.ifc_definition_id)
|
||||
)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -724,3 +742,12 @@ class ChangeParentCostItem(bpy.types.Operator, tool.Ifc.Operator):
|
||||
if isinstance(r, str):
|
||||
self.report({"WARNING"}, r)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AddCurrency(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.add_currency"
|
||||
bl_label = "Add Currency"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
core.add_currency(tool.Ifc, tool.Cost)
|
||||
|
||||
@@ -115,6 +115,27 @@ def get_schedule_predefined_types(self, context):
|
||||
return CostSchedulesData.data["predefined_types"]
|
||||
|
||||
|
||||
def get_currencies(self, context):
|
||||
return [
|
||||
("USD", "USD", "Dollar"),
|
||||
("EUR", "EUR", "Euro"),
|
||||
("GBP", "GBP", "Pound"),
|
||||
("AUD", "AUD", "Australian Dollar"),
|
||||
("CAD", "CAD", "Canadian Dollar"),
|
||||
("CHF", "CHF", "Swiss Franc"),
|
||||
("CNY", "CNY", "Chinese Yuan"),
|
||||
("HKD", "HKD", "Hong Kong Dollar"),
|
||||
("JPY", "JPY", "Japanese Yen"),
|
||||
("NZD", "NZD", "New Zealand Dollar"),
|
||||
("SEK", "SEK", "Swedish Krona"),
|
||||
("KRW", "KRW", "South Korean Won"),
|
||||
("SGD", "SGD", "Singapore Dollar"),
|
||||
("NOK", "NOK", "Norwegian Krone"),
|
||||
("MAD", "MAD", "Moroccan Dirham"),
|
||||
("CUSTOM", "Custom currency", "Custom"),
|
||||
]
|
||||
|
||||
|
||||
class CostItem(PropertyGroup):
|
||||
name: StringProperty(name="Name", update=update_cost_item_name)
|
||||
identification: StringProperty(name="Identification", update=update_cost_item_identification)
|
||||
@@ -128,6 +149,8 @@ class CostItemQuantity(PropertyGroup):
|
||||
name: StringProperty(name="Name")
|
||||
ifc_definition_id: IntProperty(name="IFC Definition ID")
|
||||
total_quantity: FloatProperty(name="Total Quantity")
|
||||
unit_symbol: StringProperty(name="Unit Symbol")
|
||||
total_cost_quantity: FloatProperty(name="Total Quantity")
|
||||
|
||||
|
||||
class CostItemType(PropertyGroup):
|
||||
@@ -137,7 +160,7 @@ class CostItemType(PropertyGroup):
|
||||
|
||||
def update_cost_item_parent(self, context):
|
||||
cost_item = tool.Cost.get_highlighted_cost_item()
|
||||
tool.Cost.toggle_cost_item_parent(cost_item=cost_item)
|
||||
tool.Cost.toggle_cost_item_parent_change(cost_item=cost_item)
|
||||
|
||||
|
||||
def update_active_cost_item_elements(self, context):
|
||||
@@ -188,6 +211,7 @@ class BIMCostProperties(PropertyGroup):
|
||||
cost_value_formula: StringProperty(name="Cost Value Formula")
|
||||
cost_column: StringProperty(name="Cost Column")
|
||||
should_show_column_ui: BoolProperty(name="Should Show Column UI", default=False)
|
||||
should_show_currency_ui: BoolProperty(name="Should Show Currency UI", default=False)
|
||||
columns: CollectionProperty(name="Columns", type=StrProperty)
|
||||
active_column_index: IntProperty(name="Active Column Index")
|
||||
cost_item_products: CollectionProperty(name="Cost Item Products", type=CostItemQuantity)
|
||||
@@ -204,7 +228,7 @@ class BIMCostProperties(PropertyGroup):
|
||||
cost_item_rates: CollectionProperty(name="Cost Item Rates", type=CostItem)
|
||||
active_cost_item_rate_index: IntProperty(name="Active Cost Rate Index")
|
||||
contracted_cost_item_rates: StringProperty(name="Contracted Cost Item Rates", default="[]")
|
||||
product_cost_items: CollectionProperty(name="Product Cost Items", type=CostItem)
|
||||
product_cost_items: CollectionProperty(name="Product Cost Items", type=CostItemQuantity)
|
||||
active_product_cost_item_index: IntProperty(name="Active Product Cost Item Index")
|
||||
enable_reorder: BoolProperty(name="Enable Reorder", default=False)
|
||||
show_nested_elements: BoolProperty(name="Show Nested Tasks", default=False, update=update_active_cost_item_elements)
|
||||
@@ -213,3 +237,8 @@ class BIMCostProperties(PropertyGroup):
|
||||
name="Show Nested Tasks", default=False, update=update_active_cost_item_resources
|
||||
)
|
||||
change_cost_item_parent: BoolProperty(name="Change Cost Item Parent", default=False, update=update_cost_item_parent)
|
||||
show_cost_item_operators: BoolProperty(name="Show Cost Item Operators", default=False)
|
||||
currency: EnumProperty(items=get_currencies, name="Currencies")
|
||||
custom_currency: StringProperty(
|
||||
name="Custom Currency", default="USD", description="Custom Currency in ISO 4217 format"
|
||||
)
|
||||
|
||||
@@ -43,49 +43,68 @@ class BIM_PT_cost_schedules(Panel):
|
||||
|
||||
self.props = context.scene.BIMCostProperties
|
||||
row = self.layout.row()
|
||||
if CostSchedulesData.data["total_cost_schedules"]:
|
||||
row.label(text=f"{CostSchedulesData.data['total_cost_schedules']} Cost Schedules Found", icon="TEXT")
|
||||
row.operator("bim.export_cost_schedules", text="Export as spreadsheet", icon="EXPORT")
|
||||
else:
|
||||
row.label(text="No Cost Schedules Found found.", icon="COMMUNITY")
|
||||
|
||||
row = self.layout.row()
|
||||
row.prop(self.props, "cost_schedule_predefined_types")
|
||||
row.operator("bim.add_cost_schedule", icon="ADD", text="Add new")
|
||||
if not self.props.active_cost_schedule_id:
|
||||
if CostSchedulesData.data["total_cost_schedules"]:
|
||||
row.label(text=f"{CostSchedulesData.data['total_cost_schedules']} Cost Schedules Found", icon="TEXT")
|
||||
row.operator("bim.export_cost_schedules", text="Export as spreadsheet", icon="EXPORT")
|
||||
else:
|
||||
row.label(text="No Cost Schedules Found found.", icon="COMMUNITY")
|
||||
row = self.layout.row()
|
||||
row.prop(self.props, "cost_schedule_predefined_types")
|
||||
row.operator("bim.add_cost_schedule", icon="ADD", text="Add")
|
||||
|
||||
for schedule in CostSchedulesData.data["schedules"]:
|
||||
self.draw_cost_schedule_ui(schedule)
|
||||
|
||||
def draw_cost_schedule_ui(self, cost_schedule):
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=cost_schedule["name"], icon="LINENUMBERS_ON")
|
||||
|
||||
if self.props.active_cost_schedule_id and self.props.active_cost_schedule_id == cost_schedule["id"]:
|
||||
op = row.operator("bim.select_cost_schedule_products", icon="RESTRICT_SELECT_OFF", text="Assigned")
|
||||
row.label(
|
||||
text="Currently editing: {}[{}]".format(cost_schedule["name"], cost_schedule["predefined_type"]),
|
||||
icon="LINENUMBERS_ON",
|
||||
)
|
||||
grid = self.layout.grid_flow(columns=2, even_columns=True)
|
||||
col = grid.column()
|
||||
row1 = col.row(align=True)
|
||||
row1.alignment = "LEFT"
|
||||
row1.label(text="Schedule tools")
|
||||
row1 = col.row(align=True)
|
||||
row1.alignment = "RIGHT"
|
||||
row1.operator("bim.export_cost_schedules", text="Export", icon="EXPORT").cost_schedule = cost_schedule["id"]
|
||||
row2 = col.row(align=True)
|
||||
row2.alignment = "RIGHT"
|
||||
op = row2.operator("bim.select_cost_schedule_products", icon="RESTRICT_SELECT_OFF", text="Assigned")
|
||||
op.cost_schedule = cost_schedule["id"]
|
||||
row.operator("bim.select_unassigned_products", icon="RESTRICT_SELECT_OFF", text="Unassigned")
|
||||
row2.operator("bim.select_unassigned_products", icon="RESTRICT_SELECT_OFF", text="Unassigned")
|
||||
|
||||
row.prop(self.props, "should_show_column_ui", text="", icon="SHORTDISPLAY")
|
||||
col = grid.column()
|
||||
row1 = col.row(align=True)
|
||||
row1.alignment = "LEFT"
|
||||
row1.label(text="Settings")
|
||||
row1 = col.row(align=True)
|
||||
row1.alignment = "RIGHT"
|
||||
row1.prop(self.props, "should_show_currency_ui", text="Project Currency", icon="COPY_ID")
|
||||
row1.prop(self.props, "should_show_column_ui", text="Schedule Columns", icon="SHORTDISPLAY")
|
||||
if self.props.is_editing == "COST_SCHEDULE_ATTRIBUTES":
|
||||
row.operator("bim.edit_cost_schedule", text="", icon="CHECKMARK")
|
||||
elif self.props.is_editing == "COST_ITEMS":
|
||||
row.operator("bim.add_summary_cost_item", text="", icon="ADD").cost_schedule = cost_schedule["id"]
|
||||
row.operator("bim.disable_editing_cost_schedule", text="", icon="CANCEL")
|
||||
elif self.props.active_cost_schedule_id:
|
||||
row.operator("bim.remove_cost_schedule", text="", icon="X").cost_schedule = cost_schedule["id"]
|
||||
row.operator("bim.disable_editing_cost_schedule", text="Disable Editing", icon="CANCEL")
|
||||
else:
|
||||
row.label(
|
||||
text="{}[{}]".format(cost_schedule["name"], cost_schedule["predefined_type"]), icon="LINENUMBERS_ON"
|
||||
)
|
||||
row.operator("bim.enable_editing_cost_items", text="", icon="OUTLINER").cost_schedule = cost_schedule["id"]
|
||||
row.operator(
|
||||
"bim.enable_editing_cost_schedule_attributes", text="", icon="GREASEPENCIL"
|
||||
).cost_schedule = cost_schedule["id"]
|
||||
row.operator("bim.remove_cost_schedule", text="", icon="X").cost_schedule = cost_schedule["id"]
|
||||
|
||||
if self.props.active_cost_schedule_id == cost_schedule["id"]:
|
||||
if self.props.is_editing == "COST_SCHEDULE_ATTRIBUTES":
|
||||
self.draw_editable_cost_schedule_ui()
|
||||
elif self.props.is_editing == "COST_ITEMS":
|
||||
if self.props.should_show_column_ui:
|
||||
self.draw_column_ui()
|
||||
if self.props.should_show_currency_ui:
|
||||
self.draw_currency_ui()
|
||||
self.draw_editable_cost_item_ui()
|
||||
|
||||
def draw_column_ui(self):
|
||||
@@ -94,6 +113,20 @@ class BIM_PT_cost_schedules(Panel):
|
||||
row.operator("bim.add_cost_column", text="", icon="ADD").name = self.props.cost_column
|
||||
self.layout.template_list("BIM_UL_cost_columns", "", self.props, "columns", self.props, "active_column_index")
|
||||
|
||||
def draw_currency_ui(self):
|
||||
row = self.layout.row(align=True)
|
||||
if CostSchedulesData.data["currency"]:
|
||||
text = "Currency used: {}".format(CostSchedulesData.data["currency"]["name"])
|
||||
row.label(text=text)
|
||||
row.operator("bim.remove_unit", text="", icon="X").unit = CostSchedulesData.data["currency"]["id"]
|
||||
else:
|
||||
row.label(text="No currency set")
|
||||
row.prop(self.props, "currency", text="")
|
||||
if self.props.currency == "CUSTOM":
|
||||
row.alignment = "RIGHT"
|
||||
row.prop(self.props, "custom_currency", text="")
|
||||
row.operator("bim.add_currency", text="", icon="ADD")
|
||||
|
||||
def draw_editable_cost_schedule_ui(self):
|
||||
blenderbim.bim.helper.draw_attributes(self.props.cost_schedule_attributes, self.layout)
|
||||
|
||||
@@ -101,30 +134,39 @@ class BIM_PT_cost_schedules(Panel):
|
||||
row = self.layout.row(align=True)
|
||||
row.alignment = "RIGHT"
|
||||
ifc_definition_id = None
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Cost Item Tools")
|
||||
row = self.layout.row(align=True)
|
||||
row.alignment = "RIGHT"
|
||||
row.operator("bim.add_summary_cost_item", text="Add Summary Cost", icon="ADD")
|
||||
row.operator("bim.expand_all_tasks", text="Expand All")
|
||||
row.operator("bim.contract_all_tasks", text="Contract All")
|
||||
row = self.layout.row(align=True)
|
||||
row.alignment = "RIGHT"
|
||||
if self.props.cost_items and self.props.active_cost_item_index < len(self.props.cost_items):
|
||||
ifc_definition_id = self.props.cost_items[self.props.active_cost_item_index].ifc_definition_id
|
||||
if ifc_definition_id:
|
||||
row.prop(self.props, "change_cost_item_parent", text="", icon="LINKED")
|
||||
row.prop(self.props, "enable_reorder", text="", icon="SORTALPHA")
|
||||
if not CostSchedulesData.data["is_editing_rates"]:
|
||||
op = row.operator("bim.enable_editing_cost_item_quantities", text="", icon="PROPERTIES")
|
||||
row.prop(self.props, "show_cost_item_operators", text="Edit", icon="DOWNARROW_HLT")
|
||||
row.operator("bim.add_cost_item", text="Add", icon="ADD").cost_item = ifc_definition_id
|
||||
row.operator("bim.copy_cost_item", text="Copy", icon="ADD")
|
||||
row.operator("bim.remove_cost_item", text="Delete", icon="X").cost_item = ifc_definition_id
|
||||
if self.props.show_cost_item_operators:
|
||||
row = self.layout.row(align=True)
|
||||
row.alignment = "RIGHT"
|
||||
row.prop(self.props, "change_cost_item_parent", text="", icon="LINKED")
|
||||
row.prop(self.props, "enable_reorder", text="", icon="SORTALPHA")
|
||||
if not CostSchedulesData.data["is_editing_rates"]:
|
||||
op = row.operator("bim.enable_editing_cost_item_quantities", text="", icon="PROPERTIES")
|
||||
op.cost_item = ifc_definition_id
|
||||
op = row.operator("bim.enable_editing_cost_item_values", text="", icon="DISC")
|
||||
op.cost_item = ifc_definition_id
|
||||
|
||||
op = row.operator("bim.enable_editing_cost_item_values", text="", icon="DISC")
|
||||
op.cost_item = ifc_definition_id
|
||||
|
||||
row.operator("bim.add_cost_item", text="", icon="ADD").cost_item = ifc_definition_id
|
||||
|
||||
if self.props.active_cost_item_id == ifc_definition_id:
|
||||
if self.props.cost_item_editing_type == "ATTRIBUTES":
|
||||
row.operator("bim.edit_cost_item", text="", icon="CHECKMARK")
|
||||
row.operator("bim.disable_editing_cost_item", text="", icon="CANCEL")
|
||||
else:
|
||||
op = row.operator("bim.enable_editing_cost_item_attributes", text="", icon="GREASEPENCIL")
|
||||
op.cost_item = ifc_definition_id
|
||||
|
||||
row.operator("bim.remove_cost_item", text="", icon="X").cost_item = ifc_definition_id
|
||||
|
||||
if self.props.active_cost_item_id == ifc_definition_id:
|
||||
if self.props.cost_item_editing_type == "ATTRIBUTES":
|
||||
row.operator("bim.edit_cost_item", text="", icon="CHECKMARK")
|
||||
row.operator("bim.disable_editing_cost_item", text="", icon="CANCEL")
|
||||
else:
|
||||
op = row.operator("bim.enable_editing_cost_item_attributes", text="", icon="GREASEPENCIL")
|
||||
op.cost_item = ifc_definition_id
|
||||
self.layout.template_list(
|
||||
"BIM_UL_cost_items",
|
||||
"",
|
||||
@@ -579,7 +621,10 @@ class BIM_UL_cost_items_trait:
|
||||
row.label(text="", icon="DOT")
|
||||
|
||||
def draw_total_cost_column(self, layout, cost_item):
|
||||
layout.label(text="{0:.2f}".format(cost_item["TotalCost"]))
|
||||
format_numbers = "{0:,.2f}".format(cost_item["TotalCost"]).replace(",", " ")
|
||||
currency = CostSchedulesData.data["currency"]
|
||||
text = "{} {}".format(format_numbers, currency["name"]) if currency else format_numbers
|
||||
layout.label(text=text)
|
||||
|
||||
def draw_quantity_column(self, layout, cost_item):
|
||||
if CostSchedulesData.data["is_editing_rates"]:
|
||||
@@ -588,13 +633,18 @@ class BIM_UL_cost_items_trait:
|
||||
self.draw_total_quantity_column(layout, cost_item)
|
||||
|
||||
def draw_value_column(self, layout, cost_item):
|
||||
text = "{0:.2f}".format(cost_item["TotalAppliedValue"])
|
||||
if cost_item["UnitBasisValueComponent"] not in [None, 1]:
|
||||
text += " / {}".format(round(cost_item["UnitBasisValueComponent"], 2))
|
||||
layout.label(text=text)
|
||||
if cost_item["TotalAppliedValue"]:
|
||||
text = "{0:,.2f}".format(cost_item["TotalAppliedValue"]).replace(",", " ")
|
||||
if cost_item["UnitBasisValueComponent"] not in [None, 1]:
|
||||
text = "{} / {}".format(text, round(cost_item["UnitBasisValueComponent"], 2))
|
||||
currency = CostSchedulesData.data["currency"]
|
||||
text = "{} {}".format(text, currency["name"]) if currency else text
|
||||
layout.label(text=text)
|
||||
else:
|
||||
layout.label(text="-")
|
||||
|
||||
def draw_uom_column(self, layout, cost_item):
|
||||
layout.label(text=cost_item["UnitBasisUnitSymbol"] or "?" if cost_item["UnitBasisValueComponent"] else "-")
|
||||
layout.label(text=cost_item["UnitBasisUnitSymbol"] or "-" if cost_item["UnitBasisValueComponent"] else "-")
|
||||
|
||||
def draw_order_operator(self, row, ifc_definition_id, cost_item):
|
||||
if cost_item["NestingIndex"] is not None:
|
||||
@@ -608,7 +658,18 @@ class BIM_UL_cost_items_trait:
|
||||
op.new_index = cost_item["NestingIndex"] - 1
|
||||
|
||||
def draw_total_quantity_column(self, layout, cost_item):
|
||||
layout.label(text="{0:.2f}".format(cost_item["TotalCostQuantity"]) + f" {cost_item['UnitSymbol'] or '?'}")
|
||||
if cost_item["TotalCostQuantity"]:
|
||||
label = "{0:.2f}".format(cost_item["TotalCostQuantity"]) + f" {cost_item['UnitSymbol'] or '-'}"
|
||||
layout.label(text=label)
|
||||
else:
|
||||
layout.label(text="-")
|
||||
# if cost_item["DerivedTotalCostQuantity"] not in [None, 0]:
|
||||
# layout.label(text="{0:.2f}".format(cost_item["DerivedTotalCostQuantity"]) + f" {cost_item['DerivedUnitSymbol'] or '-'}")
|
||||
# else:
|
||||
# if cost_item["TotalCostQuantity"] == 0:
|
||||
# layout.label(text="-")
|
||||
# else:
|
||||
# layout.label(text="{0:.2f}".format(cost_item["TotalCostQuantity"]) + f" {cost_item['UnitSymbol'] or '-'}")
|
||||
|
||||
|
||||
class BIM_UL_cost_items(BIM_UL_cost_items_trait, UIList):
|
||||
@@ -658,14 +719,14 @@ class BIM_UL_cost_item_quantities(UIList):
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
props = context.scene.BIMCostProperties
|
||||
cost_item = props.cost_items[props.active_cost_item_index]
|
||||
|
||||
if item:
|
||||
row = layout.row(align=True)
|
||||
op = row.operator("bim.select_product", text="", icon="RESTRICT_SELECT_OFF")
|
||||
op.product = item.ifc_definition_id
|
||||
row.split(factor=0.8)
|
||||
row.label(text=item.name)
|
||||
row.label(text="{0:.2f}".format(item.total_quantity))
|
||||
formatted_quantity = "{0:.2f}".format(item.total_quantity)
|
||||
row.label(text="{}{}".format(formatted_quantity, item.unit_symbol))
|
||||
op = row.operator("bim.unassign_cost_item_quantity", text="", icon="X")
|
||||
op.cost_item = cost_item.ifc_definition_id
|
||||
op.related_object = item.ifc_definition_id
|
||||
@@ -677,8 +738,11 @@ class BIM_UL_product_cost_items(UIList):
|
||||
row = layout.row(align=True)
|
||||
op = row.operator("bim.highlight_product_cost_item", text="", icon="STYLUS_PRESSURE")
|
||||
op.cost_item = item.ifc_definition_id
|
||||
row.split(factor=0.8)
|
||||
row.split(factor=0.5)
|
||||
row.label(text=item.name)
|
||||
qty = "{0:.2f}".format(item.total_quantity)
|
||||
formatted_total_quantity = "{0:.2f}".format(item.total_cost_quantity)
|
||||
row.label(text="{}/{} {}".format(qty, formatted_total_quantity, item.unit_symbol))
|
||||
|
||||
|
||||
class BIM_PT_Costing_Tools(Panel):
|
||||
@@ -691,9 +755,7 @@ class BIM_PT_Costing_Tools(Panel):
|
||||
def draw(self, context):
|
||||
self.props = context.scene.BIMCostProperties
|
||||
row = self.layout.row()
|
||||
row.operator(
|
||||
"bim.load_product_cost_items", icon="FILE_REFRESH"
|
||||
)
|
||||
row.operator("bim.load_product_cost_items", icon="FILE_REFRESH")
|
||||
row = self.layout.row()
|
||||
row.template_list(
|
||||
"BIM_UL_product_cost_items",
|
||||
|
||||
@@ -31,6 +31,7 @@ from blenderbim.bim.handler import purge_module_data
|
||||
class AddCsvAttribute(bpy.types.Operator):
|
||||
bl_idname = "bim.add_csv_attribute"
|
||||
bl_label = "Add CSV Attribute"
|
||||
bl_description = "Add a new IFC Attribute to the CSV export"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
@@ -52,6 +53,7 @@ class RemoveCsvAttribute(bpy.types.Operator):
|
||||
class RemoveAllCsvAttributes(bpy.types.Operator):
|
||||
bl_idname = "bim.remove_all_csv_attributes"
|
||||
bl_label = "Remove all CSV Attributes"
|
||||
bl_description = "Remove all IFC Attributes from the CSV export"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
@@ -61,20 +63,25 @@ class RemoveAllCsvAttributes(bpy.types.Operator):
|
||||
|
||||
class ImportCsvAttributes(bpy.types.Operator):
|
||||
bl_idname = "bim.import_csv_attributes"
|
||||
bl_label = "Import CSV Attributes"
|
||||
bl_label = "Import CSV Template"
|
||||
bl_description = "Import a json template for CSV export"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
filter_glob: bpy.props.StringProperty(default="*.json", options={"HIDDEN"})
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
|
||||
def execute(self, context):
|
||||
csv_attributes = context.scene.CsvProperties.csv_attributes
|
||||
csv_attributes.clear()
|
||||
csv_props = context.scene.CsvProperties
|
||||
csv_json = json.load(open(self.filepath))
|
||||
i = 0
|
||||
for attribute in csv_json:
|
||||
csv_attributes.add()
|
||||
csv_attributes[i].name = attribute
|
||||
i += 1
|
||||
|
||||
expression = csv_json.get("expression", "")
|
||||
if expression:
|
||||
csv_props.ifc_selector = expression
|
||||
|
||||
attributes = csv_json.get("attributes", [])
|
||||
if attributes:
|
||||
csv_props.csv_attributes.clear()
|
||||
for attribute in attributes:
|
||||
csv_props.csv_attributes.add().name = attribute
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -85,19 +92,30 @@ class ImportCsvAttributes(bpy.types.Operator):
|
||||
|
||||
class ExportCsvAttributes(bpy.types.Operator):
|
||||
bl_idname = "bim.export_csv_attributes"
|
||||
bl_label = "Export CSV Attributes"
|
||||
bl_label = "Export CSV Template"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Save a json template for CSV export"
|
||||
filename_ext = ".json"
|
||||
filter_glob: bpy.props.StringProperty(default="*.json", options={"HIDDEN"})
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
|
||||
def execute(self, context):
|
||||
csv_attributes = []
|
||||
with open(self.filepath, "w") as outfile:
|
||||
for attribute in context.scene.CsvProperties.csv_attributes:
|
||||
csv_attributes.append(attribute.name)
|
||||
csv_props = context.scene.CsvProperties
|
||||
|
||||
json.dump(csv_attributes, outfile)
|
||||
csv_template = {}
|
||||
expression = csv_props.ifc_selector
|
||||
if expression:
|
||||
csv_template["expression"] = expression
|
||||
|
||||
csv_attributes = []
|
||||
for attribute in csv_props.csv_attributes:
|
||||
attribute_name = attribute.name
|
||||
csv_attributes.append(attribute_name)
|
||||
if csv_attributes:
|
||||
csv_template["attributes"] = csv_attributes
|
||||
|
||||
with open(self.filepath, "w") as outfile:
|
||||
json.dump(csv_template, outfile)
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -110,12 +128,14 @@ class ExportCsvAttributes(bpy.types.Operator):
|
||||
|
||||
class ExportIfcCsv(bpy.types.Operator):
|
||||
bl_idname = "bim.export_ifccsv"
|
||||
bl_label = "Export IFC to CSV"
|
||||
filename_ext = ".csv"
|
||||
bl_label = "Export IFC"
|
||||
#filename_ext = ".csv"
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
|
||||
|
||||
def invoke(self, context, event):
|
||||
self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ".csv")
|
||||
props = context.scene.CsvProperties
|
||||
self.filepath = bpy.path.ensure_ext(bpy.data.filepath, f".{props.format}")
|
||||
WindowManager = context.window_manager
|
||||
WindowManager.fileselect_add(self)
|
||||
return {"RUNNING_MODAL"}
|
||||
@@ -124,7 +144,7 @@ class ExportIfcCsv(bpy.types.Operator):
|
||||
import ifccsv
|
||||
|
||||
props = context.scene.CsvProperties
|
||||
self.filepath = bpy.path.ensure_ext(self.filepath, ".csv")
|
||||
self.filepath = bpy.path.ensure_ext(self.filepath, f".{props.format}")
|
||||
if props.should_load_from_memory:
|
||||
ifc_file = IfcStore.get_file()
|
||||
else:
|
||||
@@ -132,14 +152,9 @@ class ExportIfcCsv(bpy.types.Operator):
|
||||
selector = ifcopenshell.util.selector.Selector()
|
||||
results = selector.parse(ifc_file, props.ifc_selector)
|
||||
ifc_csv = ifccsv.IfcCsv()
|
||||
ifc_csv.output = self.filepath
|
||||
ifc_csv.attributes = [a.name for a in props.csv_attributes]
|
||||
ifc_csv.selector = selector
|
||||
if props.csv_delimiter == "CUSTOM":
|
||||
ifc_csv.delimiter = props.csv_custom_delimiter
|
||||
else:
|
||||
ifc_csv.delimiter = props.csv_delimiter
|
||||
ifc_csv.export(ifc_file, results)
|
||||
attributes = [a.name for a in props.csv_attributes]
|
||||
sep = props.csv_custom_delimiter if props.csv_delimiter == "CUSTOM" else props.csv_delimiter
|
||||
ifc_csv.export(ifc_file, results, attributes, output=self.filepath, format=props.format, delimiter=sep)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -164,12 +179,8 @@ class ImportIfcCsv(bpy.types.Operator):
|
||||
else:
|
||||
ifc_file = ifcopenshell.open(props.csv_ifc_file)
|
||||
ifc_csv = ifccsv.IfcCsv()
|
||||
ifc_csv.output = self.filepath
|
||||
if props.csv_delimiter == "CUSTOM":
|
||||
ifc_csv.delimiter = props.csv_custom_delimiter
|
||||
else:
|
||||
ifc_csv.delimiter = props.csv_delimiter
|
||||
ifc_csv.Import(ifc_file)
|
||||
sep = props.csv_custom_delimiter if props.csv_delimiter == "CUSTOM" else props.csv_delimiter
|
||||
ifc_csv.Import(ifc_file, self.filepath, delimiter=sep)
|
||||
if not props.should_load_from_memory:
|
||||
ifc_file.write(props.csv_ifc_file)
|
||||
purge_module_data()
|
||||
|
||||
@@ -45,5 +45,14 @@ class CsvProperties(PropertyGroup):
|
||||
name="IFC CSV Delimiter",
|
||||
default=",",
|
||||
)
|
||||
format: EnumProperty(
|
||||
items=[
|
||||
("csv", "csv", ""),
|
||||
("xlsx", "xlsx", ""),
|
||||
("ods", "ods", ""),
|
||||
],
|
||||
name="Output format",
|
||||
default="csv",
|
||||
)
|
||||
csv_custom_delimiter: StringProperty(default="", name="Custom Delimiter")
|
||||
should_load_from_memory: BoolProperty(default=False, name="Load from Memory")
|
||||
|
||||
@@ -49,37 +49,32 @@ class BIM_PT_ifccsv(Panel):
|
||||
row.operator("bim.eyedrop_ifccsv", icon="EYEDROPPER", text="")
|
||||
layout.separator()
|
||||
|
||||
row = layout.row()
|
||||
split = row.split(factor=0.7)
|
||||
c = split.column()
|
||||
c.operator("bim.add_csv_attribute")
|
||||
c = split.column()
|
||||
c.operator("bim.import_csv_attributes", icon="IMPORT", text="Load Template")
|
||||
row = layout.row(align=True)
|
||||
row.operator("bim.add_csv_attribute", icon="ADD")
|
||||
row.operator("bim.remove_all_csv_attributes", icon="CANCEL")
|
||||
row = layout.row(align=True)
|
||||
row.operator("bim.import_csv_attributes", icon="IMPORT")
|
||||
row.operator("bim.export_csv_attributes", icon="EXPORT")
|
||||
|
||||
for index, attribute in enumerate(props.csv_attributes):
|
||||
row = layout.row(align=True)
|
||||
row.prop(attribute, "name", text="")
|
||||
row.operator("bim.remove_csv_attribute", icon="X", text="").index = index
|
||||
|
||||
if props.csv_attributes:
|
||||
row = layout.row()
|
||||
row.label()
|
||||
row.operator("bim.remove_all_csv_attributes", icon="CANCEL", text="")
|
||||
row = layout.row()
|
||||
row.operator("bim.export_csv_attributes", icon="EXPORT", text="Create Template")
|
||||
|
||||
layout.separator()
|
||||
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.prop(props, "csv_delimiter")
|
||||
row.prop(props, "format")
|
||||
|
||||
if props.csv_delimiter == "CUSTOM":
|
||||
if props.format == 'csv':
|
||||
row = layout.row(align=True)
|
||||
row.prop(props, "csv_custom_delimiter")
|
||||
row.prop(props, "csv_delimiter")
|
||||
|
||||
if props.csv_delimiter == "CUSTOM":
|
||||
row = layout.row(align=True)
|
||||
row.prop(props, "csv_custom_delimiter")
|
||||
|
||||
row = layout.row()
|
||||
split = row.split(factor=0.7)
|
||||
split = row.split(factor=0.5)
|
||||
c = split.column()
|
||||
c.operator("bim.export_ifccsv", icon="EXPORT")
|
||||
c.operator("bim.export_ifccsv", icon="EXPORT", text="Export IFC to " + props.format.upper())
|
||||
c = split.column()
|
||||
c.operator("bim.import_ifccsv", icon="IMPORT")
|
||||
|
||||
@@ -20,6 +20,7 @@ import bpy
|
||||
from . import ui, prop, operator
|
||||
|
||||
classes = (
|
||||
operator.CopyDebugInformation,
|
||||
operator.CreateAllShapes,
|
||||
operator.CreateShapeFromStepId,
|
||||
operator.InspectFromObject,
|
||||
|
||||
@@ -20,6 +20,9 @@ import os
|
||||
import bpy
|
||||
import time
|
||||
import logging
|
||||
import platform
|
||||
import subprocess
|
||||
import addon_utils
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.placement
|
||||
import ifcopenshell.util.representation
|
||||
@@ -31,6 +34,58 @@ import blenderbim.tool as tool
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
|
||||
|
||||
class CopyDebugInformation(bpy.types.Operator):
|
||||
bl_idname = "bim.copy_debug_information"
|
||||
bl_label = "Copy Debug Information"
|
||||
bl_description = "Copies debugging information to your clipboard for use in bugreports"
|
||||
|
||||
def execute(self, context):
|
||||
version = ".".join(
|
||||
[
|
||||
str(x)
|
||||
for x in [
|
||||
addon.bl_info.get("version", (-1, -1, -1))
|
||||
for addon in addon_utils.modules()
|
||||
if addon.bl_info["name"] == "BlenderBIM"
|
||||
][0]
|
||||
]
|
||||
)
|
||||
info = {
|
||||
"os": platform.system(),
|
||||
"os_version": platform.version(),
|
||||
"python_version": platform.python_version(),
|
||||
"architecture": platform.architecture(),
|
||||
"machine": platform.machine(),
|
||||
"processor": platform.processor(),
|
||||
"blender_version": bpy.app.version_string,
|
||||
"blenderbim_version": version,
|
||||
"ifc": False,
|
||||
}
|
||||
|
||||
if tool.Ifc.get():
|
||||
info.update(
|
||||
{
|
||||
"ifc": os.path.basename(tool.Ifc.get_path()) if os.path.isfile(tool.Ifc.get_path()) else "Unsaved",
|
||||
"schema": tool.Ifc.get().schema,
|
||||
"preprocessor_version": tool.Ifc.get().wrapped_data.header.file_name.preprocessor_version,
|
||||
"originating_system": tool.Ifc.get().wrapped_data.header.file_name.originating_system,
|
||||
}
|
||||
)
|
||||
|
||||
# Format it in a readable way
|
||||
text = "\n".join(f"{k}: {v}" for k, v in info.items())
|
||||
print(text)
|
||||
|
||||
if platform.system() == "Windows":
|
||||
command = "echo | set /p nul=" + text.strip()
|
||||
elif platform.system() == "Darwin": # for MacOS
|
||||
command = 'printf "' + text.strip().replace("\n", "\\n").replace('"', "") + '" | pbcopy'
|
||||
else: # Linux
|
||||
command = 'printf "' + text.strip().replace("\n", "\\n").replace('"', "") + '" | xclip -selection clipboard'
|
||||
subprocess.run(command, shell=True, check=True)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class PrintIfcFile(bpy.types.Operator):
|
||||
bl_idname = "bim.print_ifc_file"
|
||||
bl_label = "Print IFC File"
|
||||
@@ -73,7 +128,7 @@ class ConvertToBlender(bpy.types.Operator):
|
||||
|
||||
def execute(self, context):
|
||||
for o in bpy.data.objects:
|
||||
if o.type in {'MESH', 'EMPTY'}:
|
||||
if o.type in {"MESH", "EMPTY"}:
|
||||
o.BIMObjectProperties.ifc_definition_id = 0
|
||||
if o.data:
|
||||
o.data.BIMMeshProperties.ifc_definition_id = 0
|
||||
@@ -135,6 +190,8 @@ class CreateAllShapes(bpy.types.Operator):
|
||||
|
||||
total = len(elements)
|
||||
settings = ifcopenshell.geom.settings()
|
||||
settings_2d = ifcopenshell.geom.settings()
|
||||
settings_2d.set(settings_2d.INCLUDE_CURVES, True)
|
||||
failures = []
|
||||
excludes = () # For the developer to debug with
|
||||
for i, element in enumerate(elements):
|
||||
@@ -142,8 +199,16 @@ class CreateAllShapes(bpy.types.Operator):
|
||||
continue
|
||||
print(f"{i}/{total}:", element)
|
||||
start = time.time()
|
||||
shape = None
|
||||
try:
|
||||
shape = ifcopenshell.geom.create_shape(settings, element)
|
||||
except:
|
||||
try:
|
||||
shape = ifcopenshell.geom.create_shape(settings_2d, element)
|
||||
except:
|
||||
failures.append(element)
|
||||
print("***** FAILURE *****")
|
||||
if shape:
|
||||
print(
|
||||
"Success",
|
||||
time.time() - start,
|
||||
@@ -151,9 +216,6 @@ class CreateAllShapes(bpy.types.Operator):
|
||||
len(shape.geometry.edges),
|
||||
len(shape.geometry.faces),
|
||||
)
|
||||
except:
|
||||
failures.append(element)
|
||||
print("***** FAILURE *****")
|
||||
print(f"Failures: {len(failures)}")
|
||||
for failure in failures:
|
||||
print(failure)
|
||||
|
||||
@@ -48,6 +48,9 @@ class BIM_PT_debug(Panel):
|
||||
row = layout.row()
|
||||
row.operator("bim.print_ifc_file")
|
||||
|
||||
row = layout.row()
|
||||
row.operator("bim.copy_debug_information")
|
||||
|
||||
row = layout.row()
|
||||
row.operator("bim.purge_hdf5_cache")
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ classes = (
|
||||
operator.AddDrawingToSheet,
|
||||
operator.AddSchedule,
|
||||
operator.AddScheduleToSheet,
|
||||
operator.AddReferenceToSheet,
|
||||
operator.AddSheet,
|
||||
operator.AddTextLiteral,
|
||||
operator.BuildSchedule,
|
||||
@@ -61,16 +62,23 @@ classes = (
|
||||
operator.RemoveDrawingStyle,
|
||||
operator.RemoveDrawingStyleAttribute,
|
||||
operator.RemoveSchedule,
|
||||
operator.ReloadDrawingStyles,
|
||||
operator.RemoveSheet,
|
||||
operator.RemoveTextLiteral,
|
||||
operator.SelectAllDrawings,
|
||||
operator.ResizeText,
|
||||
operator.SaveDrawingStyle,
|
||||
operator.SaveDrawingStylesData,
|
||||
operator.SelectAssignedProduct,
|
||||
operator.SelectDocIfcFile,
|
||||
operator.LoadReferences,
|
||||
operator.DisableEditingReferences,
|
||||
operator.AddReference,
|
||||
operator.RemoveReference,
|
||||
operator.OpenReference,
|
||||
prop.Variable,
|
||||
prop.Drawing,
|
||||
prop.Schedule,
|
||||
prop.Document,
|
||||
prop.DrawingStyle,
|
||||
prop.Sheet,
|
||||
prop.DocProperties,
|
||||
@@ -85,6 +93,7 @@ classes = (
|
||||
ui.BIM_PT_sheets,
|
||||
ui.BIM_PT_drawings,
|
||||
ui.BIM_PT_schedules,
|
||||
ui.BIM_PT_references,
|
||||
ui.BIM_PT_product_assignments,
|
||||
ui.BIM_PT_text,
|
||||
ui.BIM_UL_drawinglist,
|
||||
@@ -100,7 +109,7 @@ classes = (
|
||||
|
||||
def register():
|
||||
if not bpy.app.background:
|
||||
bpy.utils.register_tool(workspace.AnnotationTool, after={"bim.bim_tool"}, separator=True, group=True)
|
||||
bpy.utils.register_tool(workspace.AnnotationTool, after={"bim.bim_tool"}, separator=False, 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)
|
||||
|
||||
@@ -20,6 +20,7 @@ import bpy
|
||||
import os
|
||||
import blenderbim.tool as tool
|
||||
from mathutils import Vector
|
||||
import bmesh
|
||||
|
||||
|
||||
class Annotator:
|
||||
@@ -44,7 +45,7 @@ class Annotator:
|
||||
font.name = "OpenGost Type B TT"
|
||||
obj.data.font = font
|
||||
obj.data.BIMTextProperties.font_size = "2.5"
|
||||
collection = bpy.context.scene.camera.users_collection[0]
|
||||
collection = bpy.context.scene.camera.BIMObjectProperties.collection
|
||||
collection.objects.link(obj)
|
||||
Annotator.resize_text(obj)
|
||||
return obj
|
||||
@@ -52,9 +53,10 @@ class Annotator:
|
||||
@staticmethod
|
||||
def resize_text(text_obj):
|
||||
camera = None
|
||||
for obj in text_obj.users_collection[0].objects:
|
||||
if isinstance(obj.data, bpy.types.Camera):
|
||||
camera = obj
|
||||
group = tool.Drawing.get_drawing_group(tool.Ifc.get_entity(text_obj))
|
||||
for element in tool.Drawing.get_drawing_elements(group):
|
||||
if element.is_a("IfcAnnotation") and element.ObjectType == "DRAWING":
|
||||
camera = tool.Ifc.get_object(element)
|
||||
break
|
||||
if not camera:
|
||||
return
|
||||
@@ -62,12 +64,7 @@ class Annotator:
|
||||
font_size = 1.6 / 1000
|
||||
font_size *= float(text_obj.data.BIMTextProperties.font_size)
|
||||
|
||||
if camera.data.BIMCameraProperties.diagram_scale == "CUSTOM":
|
||||
human_scale, fraction = camera.data.BIMCameraProperties.custom_diagram_scale.split("|")
|
||||
else:
|
||||
human_scale, fraction = camera.data.BIMCameraProperties.diagram_scale.split("|")
|
||||
numerator, denominator = fraction.split("/")
|
||||
font_size /= float(numerator) / float(denominator)
|
||||
font_size /= tool.Drawing.get_scale_ratio(tool.Drawing.get_diagram_scale(camera)["Scale"])
|
||||
|
||||
text_obj.data.size = font_size
|
||||
|
||||
@@ -94,7 +91,7 @@ class Annotator:
|
||||
return obj
|
||||
|
||||
@staticmethod
|
||||
def add_plane_to_annotation(obj):
|
||||
def add_plane_to_annotation(obj, remove_face=False):
|
||||
# default order = bot left, top left, bot right, top right
|
||||
# therefore we redefine the order
|
||||
face_verts = [0, 2, 3, 1]
|
||||
@@ -104,7 +101,9 @@ class Annotator:
|
||||
bm = tool.Blender.get_bmesh_for_mesh(obj.data, clean=True)
|
||||
new_verts = [bm.verts.new(v) for v in verts_local]
|
||||
|
||||
bm.faces.new([new_verts[i] for i in face_verts])
|
||||
face = bm.faces.new([new_verts[i] for i in face_verts])
|
||||
if remove_face:
|
||||
bmesh.ops.delete(bm, geom=[face], context="FACES_ONLY")
|
||||
tool.Blender.apply_bmesh(obj.data, bm, obj)
|
||||
return obj
|
||||
|
||||
@@ -114,7 +113,7 @@ class Annotator:
|
||||
co1, _, _, _ = Annotator.get_placeholder_coords(camera)
|
||||
matrix_world = camera.matrix_world.copy()
|
||||
matrix_world.translation = co1
|
||||
collection = camera.users_collection[0]
|
||||
collection = camera.BIMObjectProperties.collection
|
||||
|
||||
if object_type == "TEXT":
|
||||
obj = bpy.data.objects.new(object_type, None)
|
||||
|
||||
@@ -27,7 +27,7 @@ from pathlib import Path
|
||||
def refresh():
|
||||
ProductAssignmentsData.is_loaded = False
|
||||
SheetsData.is_loaded = False
|
||||
SchedulesData.is_loaded = False
|
||||
DocumentsData.is_loaded = False
|
||||
DrawingsData.is_loaded = False
|
||||
AnnotationData.is_loaded = False
|
||||
DecoratorData.data = {}
|
||||
@@ -103,6 +103,7 @@ class DrawingsData:
|
||||
"has_saved_ifc": cls.has_saved_ifc(),
|
||||
"total_drawings": cls.total_drawings(),
|
||||
"location_hint": cls.location_hint(),
|
||||
"active_drawing_pset_data": cls.active_drawing_pset_data(),
|
||||
}
|
||||
cls.is_loaded = True
|
||||
|
||||
@@ -124,14 +125,28 @@ class DrawingsData:
|
||||
return results
|
||||
return [(h.upper(), h, "") for h in ["North", "South", "East", "West"]]
|
||||
|
||||
@classmethod
|
||||
def active_drawing_pset_data(cls):
|
||||
ifc_file = tool.Ifc.get()
|
||||
drawing_id = bpy.context.scene.DocProperties.active_drawing_id
|
||||
if drawing_id == 0:
|
||||
return {}
|
||||
drawing = ifc_file.by_id(bpy.context.scene.DocProperties.active_drawing_id)
|
||||
return ifcopenshell.util.element.get_pset(drawing, "EPset_Drawing")
|
||||
|
||||
class SchedulesData:
|
||||
|
||||
class DocumentsData:
|
||||
data = {}
|
||||
is_loaded = False
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.data = {"has_saved_ifc": cls.has_saved_ifc(), "total_schedules": cls.total_schedules()}
|
||||
documents = cls.count_documents()
|
||||
cls.data = {
|
||||
"has_saved_ifc": cls.has_saved_ifc(),
|
||||
"total_schedules": documents["SCHEDULE"],
|
||||
"total_references": documents["REFERENCE"],
|
||||
}
|
||||
cls.is_loaded = True
|
||||
|
||||
@classmethod
|
||||
@@ -139,8 +154,16 @@ class SchedulesData:
|
||||
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"])
|
||||
def count_documents(cls):
|
||||
documents = {
|
||||
"SCHEDULE": 0,
|
||||
"REFERENCE": 0,
|
||||
}
|
||||
for d in tool.Ifc.get().by_type("IfcDocumentInformation"):
|
||||
scope = d.Scope
|
||||
documents[scope] = documents.get(scope, 0) + 1
|
||||
|
||||
return documents
|
||||
|
||||
|
||||
FONT_SIZES = {
|
||||
@@ -232,14 +255,12 @@ class DecoratorData:
|
||||
# use `regular` as default
|
||||
|
||||
# get font size
|
||||
if classes := pset_data.get("Classes", None):
|
||||
classes_split = classes.split()
|
||||
# prioritize smaller font sizes just like in svg
|
||||
font_size_type = next(
|
||||
(font_size_type for font_size_type in FONT_SIZES if font_size_type in classes_split), "regular"
|
||||
)
|
||||
else:
|
||||
font_size_type = "regular"
|
||||
classes = pset_data.get("Classes", None) or "regular"
|
||||
classes_split = classes.split()
|
||||
# prioritize smaller font sizes just like in svg
|
||||
font_size_type = next(
|
||||
(font_size_type for font_size_type in FONT_SIZES if font_size_type in classes_split), "regular"
|
||||
)
|
||||
font_size = FONT_SIZES[font_size_type]
|
||||
|
||||
# get symbol
|
||||
@@ -279,18 +300,28 @@ class DecoratorData:
|
||||
return None
|
||||
|
||||
dimension_style = "arrow"
|
||||
fill_bg = False
|
||||
classes = ifcopenshell.util.element.get_pset(element, "EPset_Annotation", "Classes")
|
||||
if classes and "oblique" in classes.lower().split():
|
||||
dimension_style = "oblique"
|
||||
if classes:
|
||||
classes_split = classes.lower().split()
|
||||
if "oblique" in classes_split:
|
||||
dimension_style = "oblique"
|
||||
elif "fill-bg" in classes_split:
|
||||
fill_bg = True
|
||||
|
||||
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)
|
||||
text_prefix = pset_data.get("TextPrefix", "")
|
||||
text_suffix = pset_data.get("TextSuffix", "")
|
||||
|
||||
dimension_data = {
|
||||
"dimension_style": dimension_style,
|
||||
"show_description_only": show_description_only,
|
||||
"suppress_zero_inches": suppress_zero_inches,
|
||||
"text_prefix": text_prefix,
|
||||
"text_suffix": text_suffix,
|
||||
"fill_bg": fill_bg,
|
||||
}
|
||||
cls.data[obj.name] = dimension_data
|
||||
return dimension_data
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -23,7 +23,7 @@ from bpy import types
|
||||
from mathutils import Vector
|
||||
from mathutils import geometry
|
||||
from bpy_extras import view3d_utils
|
||||
from blenderbim.bim.module.drawing.shaders import DotsGizmoShader, ExtrusionGuidesShader, BaseLinesShader
|
||||
from blenderbim.bim.module.drawing.shaders import DotsGizmoShader, ExtrusionGuidesShader
|
||||
from ifcopenshell.util.unit import si_conversions
|
||||
|
||||
|
||||
@@ -251,11 +251,12 @@ X3DISC = (
|
||||
class CustomGizmo:
|
||||
# FIXME: highliting/selection doesnt work
|
||||
def draw_very_custom_shape(self, ctx, custom_shape, select_id=None):
|
||||
# similar to draw_custom_shape
|
||||
shape, batch, shader = custom_shape
|
||||
# create shader and batch
|
||||
shader_wrapper, batch = custom_shape
|
||||
shader = shader_wrapper.get_shader()
|
||||
|
||||
# setup params
|
||||
shader.bind()
|
||||
|
||||
if select_id is not None:
|
||||
gpu.select.load_id(select_id)
|
||||
else:
|
||||
@@ -264,13 +265,17 @@ class CustomGizmo:
|
||||
else:
|
||||
color = (*self.color, self.alpha)
|
||||
shader.uniform_float("color", color)
|
||||
shape.glenable()
|
||||
shader_wrapper.glenable()
|
||||
shader_wrapper.uniform_region(ctx)
|
||||
|
||||
shape.uniform_region(ctx)
|
||||
# shader.uniform_float('modelMatrix', self.matrix_world)
|
||||
# using `with` block to make sure matrix multiplication
|
||||
# won't affect other shaders
|
||||
with gpu.matrix.push_pop():
|
||||
gpu.matrix.multiply_matrix(self.matrix_world)
|
||||
batch.draw()
|
||||
# using matrix_world seems to be unaffected by matrix_offset
|
||||
# therefore we use basis @ offset
|
||||
matrix = self.matrix_basis @ self.matrix_offset
|
||||
gpu.matrix.multiply_matrix(matrix)
|
||||
batch.draw(shader)
|
||||
|
||||
gpu.state.blend_set("NONE")
|
||||
|
||||
@@ -360,6 +365,7 @@ class UglyDotGizmo(OffsetHandle, types.Gizmo):
|
||||
self.draw_custom_shape(self.custom_shape, select_id=select_id)
|
||||
|
||||
|
||||
# TODO: dead code?
|
||||
class DotGizmo(CustomGizmo, OffsetHandle, types.Gizmo):
|
||||
"""Single dot viewport-aligned"""
|
||||
|
||||
@@ -389,10 +395,6 @@ class DotGizmo(CustomGizmo, OffsetHandle, types.Gizmo):
|
||||
self.refresh()
|
||||
self.draw_very_custom_shape(ctx, self.custom_shape, select_id=select_id)
|
||||
|
||||
# doesn't get called
|
||||
# def test_select(self, ctx, location):
|
||||
# pass
|
||||
|
||||
|
||||
class ExtrusionGuidesGizmo(CustomGizmo, types.Gizmo):
|
||||
"""Extrusion guides
|
||||
@@ -407,19 +409,25 @@ class ExtrusionGuidesGizmo(CustomGizmo, types.Gizmo):
|
||||
__slots__ = ("scale_value", "custom_shape")
|
||||
|
||||
def setup(self):
|
||||
shader = ExtrusionGuidesShader()
|
||||
self.custom_shape = shader, shader.batch(pos=((0, 0, 0), (0, 0, 1))), shader.prog
|
||||
self.use_draw_scale = False
|
||||
|
||||
def refresh(self):
|
||||
depth = self.target_get_value("depth") / self.scale_value
|
||||
self.matrix_offset.col[2][2] = depth # z-scaled
|
||||
"""setup `custom_shape`"""
|
||||
shader_wrapper = ExtrusionGuidesShader()
|
||||
verts = [Vector((0, 0, 0)), Vector((0, 0, 1))]
|
||||
verts, edges = shader_wrapper.process_geometry(verts)
|
||||
self.custom_shape = shader_wrapper, shader_wrapper.batch(
|
||||
pos=verts,
|
||||
indices=edges,
|
||||
)
|
||||
|
||||
def draw(self, ctx):
|
||||
self.refresh()
|
||||
self.draw_very_custom_shape(ctx, self.custom_shape)
|
||||
|
||||
def refresh(self):
|
||||
depth = self.target_get_value("depth") / self.scale_value
|
||||
self.matrix_offset.col[2][2] = depth # z-scaled
|
||||
|
||||
|
||||
# TODO: dead code?
|
||||
class DimensionLabelGizmo(types.Gizmo):
|
||||
"""Text label for a dimension"""
|
||||
|
||||
@@ -486,6 +494,7 @@ class ExtrusionWidget(types.GizmoGroup):
|
||||
theme = ctx.preferences.themes[0].user_interface
|
||||
scale_value = self.get_scale_value(ctx.scene.unit_settings.system, ctx.scene.unit_settings.length_unit)
|
||||
|
||||
# setup handle
|
||||
gz = self.handle = self.gizmos.new("BIM_GT_uglydot_3d")
|
||||
gz.matrix_basis = basis
|
||||
gz.scale_basis = 0.1
|
||||
@@ -496,6 +505,7 @@ class ExtrusionWidget(types.GizmoGroup):
|
||||
gz.target_set_prop("offset", prop, "value")
|
||||
gz.scale_value = scale_value
|
||||
|
||||
# setup guides
|
||||
gz = self.guides = self.gizmos.new("BIM_GT_extrusion_guides")
|
||||
gz.matrix_basis = basis
|
||||
gz.color = gz.color_highlight = tuple(theme.gizmo_secondary)
|
||||
|
||||
@@ -154,13 +154,14 @@ def format_distance(
|
||||
# Separate ft and inches
|
||||
# Unless Inches are the specified Length Unit
|
||||
if unit_length != "INCHES":
|
||||
feet = math.floor(decInches / inPerFoot)
|
||||
feet = int(decInches / inPerFoot) # remove decimal
|
||||
decInches -= feet * inPerFoot
|
||||
else:
|
||||
feet = 0
|
||||
|
||||
# Separate Fractional Inches
|
||||
inches = math.floor(decInches)
|
||||
decInches = abs(decInches) # ignore the sign for inches
|
||||
inches = math.floor(decInches) # remove decimal
|
||||
if inches != 0:
|
||||
frac = round(base * (decInches - inches))
|
||||
else:
|
||||
@@ -268,7 +269,7 @@ def get_active_drawing(scene):
|
||||
props = scene.DocProperties
|
||||
try:
|
||||
camera = tool.Ifc.get_object(tool.Ifc.get().by_id(props.active_drawing_id))
|
||||
return camera.users_collection[0], camera
|
||||
return camera.BIMObjectProperties.collection, camera
|
||||
except:
|
||||
return None, None
|
||||
|
||||
|
||||
@@ -41,12 +41,12 @@ import blenderbim.bim.module.drawing.sheeter as sheeter
|
||||
import blenderbim.bim.module.drawing.scheduler as scheduler
|
||||
import blenderbim.bim.module.drawing.helper as helper
|
||||
from blenderbim.bim.module.drawing.decoration import CutDecorator
|
||||
from blenderbim.bim.module.drawing.data import DecoratorData
|
||||
from blenderbim.bim.module.drawing.data import DecoratorData, DrawingsData
|
||||
import blenderbim.bim.export_ifc
|
||||
from lxml import etree
|
||||
from mathutils import Vector
|
||||
from mathutils import Vector, Color
|
||||
from timeit import default_timer as timer
|
||||
from blenderbim.bim.module.drawing.prop import RasterStyleProperty, Literal
|
||||
from blenderbim.bim.module.drawing.prop import RasterStyleProperty, Literal, RASTER_STYLE_PROPERTIES_EXCLUDE
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from pathlib import Path
|
||||
|
||||
@@ -150,7 +150,7 @@ class CreateDrawing(bpy.types.Operator):
|
||||
"Creates/refreshes a .svg drawing based on currently active camera.\n\n"
|
||||
+ "SHIFT+CLICK to print all selected drawings"
|
||||
)
|
||||
print_all: bpy.props.BoolProperty(name="Print All", default=False)
|
||||
print_all: bpy.props.BoolProperty(name="Print All", default=False, options={"SKIP_SAVE"})
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
@@ -158,12 +158,9 @@ class CreateDrawing(bpy.types.Operator):
|
||||
|
||||
def invoke(self, context, event):
|
||||
# printing all drawings on shift+click
|
||||
# make sure to use SKIP_SAVE on property, otherwise it might get stuck
|
||||
if event.type == "LEFTMOUSE" and event.shift:
|
||||
self.print_all = True
|
||||
else:
|
||||
# can't rely on default value since the line above
|
||||
# will set the value to `True` for all future operator calls
|
||||
self.print_all = False
|
||||
return self.execute(context)
|
||||
|
||||
def execute(self, context):
|
||||
@@ -177,7 +174,7 @@ class CreateDrawing(bpy.types.Operator):
|
||||
|
||||
for drawing_id in drawings_to_print:
|
||||
if self.print_all:
|
||||
bpy.ops.bim.activate_drawing(drawing=drawing_id)
|
||||
bpy.ops.bim.activate_drawing(drawing=drawing_id, camera_view_point=False)
|
||||
|
||||
self.camera = context.scene.camera
|
||||
self.camera_element = tool.Ifc.get_entity(self.camera)
|
||||
@@ -189,7 +186,7 @@ class CreateDrawing(bpy.types.Operator):
|
||||
self.cprops = self.camera.data.BIMCameraProperties
|
||||
self.drawing_name = self.file.by_id(drawing_id).Name
|
||||
self.metadata = tool.Drawing.get_drawing_metadata(self.camera_element)
|
||||
self.get_scale()
|
||||
self.get_scale(context)
|
||||
if self.cprops.update_representation(self.camera):
|
||||
bpy.ops.bim.update_representation(obj=self.camera.name, ifc_representation_class="")
|
||||
|
||||
@@ -220,7 +217,7 @@ class CreateDrawing(bpy.types.Operator):
|
||||
open_with_user_command(context.preferences.addons["blenderbim"].preferences.svg_command, svg_path)
|
||||
|
||||
if self.print_all:
|
||||
bpy.ops.bim.activate_drawing(drawing=original_drawing_id)
|
||||
bpy.ops.bim.activate_drawing(drawing=original_drawing_id, camera_view_point=False)
|
||||
return {"FINISHED"}
|
||||
|
||||
def get_camera_dimensions(self):
|
||||
@@ -288,7 +285,7 @@ class CreateDrawing(bpy.types.Operator):
|
||||
bpy.ops.render.render(write_still=True)
|
||||
else:
|
||||
previous_visibility = {}
|
||||
for obj in self.camera.users_collection[0].objects:
|
||||
for obj in self.camera.BIMObjectProperties.collection.objects:
|
||||
if bpy.context.view_layer.objects.get(obj.name):
|
||||
previous_visibility[obj.name] = obj.hide_get()
|
||||
obj.hide_set(True)
|
||||
@@ -481,6 +478,14 @@ class CreateDrawing(bpy.types.Operator):
|
||||
tree.add_element(elem)
|
||||
elements -= processed
|
||||
|
||||
if self.camera_element not in drawing_elements:
|
||||
with profile("Camera element"):
|
||||
# The camera must always be included, regardless of any include/exclude filters.
|
||||
geom_settings = ifcopenshell.geom.settings(DISABLE_TRIANGULATION=True, STRICT_TOLERANCE=True)
|
||||
it = ifcopenshell.geom.iterator(geom_settings, ifc, include=[self.camera_element])
|
||||
for elem in self.yield_from_iterator(it):
|
||||
self.serialiser.write(elem)
|
||||
|
||||
with profile("Finalizing"):
|
||||
self.serialiser.finalize()
|
||||
results = self.svg_buffer.get_value()
|
||||
@@ -628,7 +633,6 @@ class CreateDrawing(bpy.types.Operator):
|
||||
|
||||
# Loop over the cell paths
|
||||
for pi, p in enumerate(g2.getElementsByTagName("path")):
|
||||
|
||||
d = p.getAttribute("d")
|
||||
coords = [co[1:].split(",") for co in d.split() if co[1:]]
|
||||
polygon = shapely.Polygon(coords)
|
||||
@@ -789,6 +793,7 @@ class CreateDrawing(bpy.types.Operator):
|
||||
self.serialiser.setElevationRefGuid(self.camera_element.GlobalId)
|
||||
self.serialiser.setScale(self.scale)
|
||||
self.serialiser.setSubtractionSettings(ifcopenshell.ifcopenshell_wrapper.ALWAYS)
|
||||
self.serialiser.setUsePrefiltering(True) # See #3359
|
||||
# tree = ifcopenshell.geom.tree()
|
||||
# This instructs the tree to explode BReps into faces and return
|
||||
# the style of the face when running tree.select_ray()
|
||||
@@ -837,15 +842,15 @@ class CreateDrawing(bpy.types.Operator):
|
||||
ifc = tool.Ifc.get()
|
||||
for el in root.findall(".//{http://www.w3.org/2000/svg}g[@{http://www.ifcopenshell.org/ns}guid]"):
|
||||
element = ifc.by_guid(el.get("{http://www.ifcopenshell.org/ns}guid"))
|
||||
# Architectural convention only merges these objects. E.g. pipe segments and fittings shouldn't merge.
|
||||
if not element.is_a("IfcWall") and not element.is_a("IfcSlab"):
|
||||
continue
|
||||
|
||||
classes = self.get_svg_classes(element)
|
||||
classes.append("cut")
|
||||
|
||||
el.set("class", " ".join(classes))
|
||||
|
||||
# Architectural convention only merges these objects. E.g. pipe segments and fittings shouldn't merge.
|
||||
if not element.is_a("IfcWall") and not element.is_a("IfcSlab"):
|
||||
continue
|
||||
|
||||
keys = []
|
||||
for query in join_criteria:
|
||||
key = ifcopenshell.util.selector.get_element_value(element, query)
|
||||
@@ -939,18 +944,14 @@ class CreateDrawing(bpy.types.Operator):
|
||||
|
||||
return svg_path
|
||||
|
||||
def get_scale(self):
|
||||
if self.camera.data.BIMCameraProperties.diagram_scale == "CUSTOM":
|
||||
self.human_scale, fraction = self.camera.data.BIMCameraProperties.custom_diagram_scale.split("|")
|
||||
else:
|
||||
self.human_scale, fraction = self.camera.data.BIMCameraProperties.diagram_scale.split("|")
|
||||
def get_scale(self, context):
|
||||
diagram_scale = tool.Drawing.get_diagram_scale(self.camera)
|
||||
self.human_scale = diagram_scale["HumanScale"]
|
||||
self.scale = tool.Drawing.get_scale_ratio(diagram_scale["Scale"])
|
||||
|
||||
if self.camera.data.BIMCameraProperties.is_nts:
|
||||
if ifcopenshell.util.element.get_pset(self.camera_element, "EPset_Drawing", "IsNTS"):
|
||||
self.human_scale = "NTS"
|
||||
|
||||
numerator, denominator = fraction.split("/")
|
||||
self.scale = float(numerator) / float(denominator)
|
||||
|
||||
def is_landscape(self, render):
|
||||
return render.resolution_x > render.resolution_y
|
||||
|
||||
@@ -1020,7 +1021,7 @@ class AddSheet(bpy.types.Operator, Operator):
|
||||
|
||||
class OpenSheet(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.open_sheet"
|
||||
bl_label = "Open Sheet"
|
||||
bl_label = "Open Sheet Layout"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
@@ -1103,6 +1104,7 @@ class RemoveDrawingFromSheet(bpy.types.Operator, Operator):
|
||||
class CreateSheets(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.create_sheets"
|
||||
bl_label = "Create Sheets"
|
||||
bl_description = "Build a sheet from the sheet layout"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
@classmethod
|
||||
@@ -1139,7 +1141,7 @@ class CreateSheets(bpy.types.Operator, Operator):
|
||||
has_sheet_reference = True
|
||||
elif reference.Description == "RASTER":
|
||||
if reference.Location in raster_references:
|
||||
del raster_references[reference.Location]
|
||||
raster_references.remove(reference.Location)
|
||||
else:
|
||||
tool.Ifc.run("document.remove_reference", reference=reference)
|
||||
|
||||
@@ -1187,16 +1189,13 @@ class SelectAllDrawings(bpy.types.Operator):
|
||||
bl_label = "Select All Drawings"
|
||||
view: bpy.props.StringProperty()
|
||||
bl_description = "Select all drawings in the drawing list.\n\n" + "SHIFT+CLICK to deselect all drawings"
|
||||
select_all: bpy.props.BoolProperty(name="Open All", default=False)
|
||||
select_all: bpy.props.BoolProperty(name="Open All", default=False, options={"SKIP_SAVE"})
|
||||
|
||||
def invoke(self, context, event):
|
||||
# deselect all drawings on shift+click
|
||||
# make sure to use SKIP_SAVE on property, otherwise it might get stuck
|
||||
if event.type == "LEFTMOUSE" and event.shift:
|
||||
self.select_all = False
|
||||
else:
|
||||
# can't rely on default value since the line above
|
||||
# will set the value to `True` for all future operator calls
|
||||
self.select_all = True
|
||||
return self.execute(context)
|
||||
|
||||
def execute(self, context):
|
||||
@@ -1215,16 +1214,13 @@ class OpenDrawing(bpy.types.Operator):
|
||||
+ 'or using "svg_command" from the BlenderBIM preferences (if provided).\n\n'
|
||||
+ "SHIFT+CLICK to open all selected drawings"
|
||||
)
|
||||
open_all: bpy.props.BoolProperty(name="Open All", default=False)
|
||||
open_all: bpy.props.BoolProperty(name="Open All", default=False, options={"SKIP_SAVE"})
|
||||
|
||||
def invoke(self, context, event):
|
||||
# opening all drawings on shift+click
|
||||
# make sure to use SKIP_SAVE on property, otherwise it might get stuck
|
||||
if event.type == "LEFTMOUSE" and event.shift:
|
||||
self.open_all = True
|
||||
else:
|
||||
# can't rely on default value since the line above
|
||||
# will set the value to `True` for all future operator calls
|
||||
self.open_all = False
|
||||
return self.execute(context)
|
||||
|
||||
def execute(self, context):
|
||||
@@ -1263,9 +1259,13 @@ class ActivateModel(bpy.types.Operator):
|
||||
bl_description = "Activates the model view"
|
||||
|
||||
def execute(self, context):
|
||||
dprops = bpy.context.scene.DocProperties
|
||||
dprops.active_drawing_id = 0
|
||||
|
||||
CutDecorator.uninstall()
|
||||
|
||||
bpy.ops.object.hide_view_clear()
|
||||
if not bpy.app.background:
|
||||
bpy.ops.object.hide_view_clear()
|
||||
|
||||
subcontext = ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Model", "Body", "MODEL_VIEW")
|
||||
|
||||
@@ -1298,17 +1298,40 @@ class ActivateDrawing(bpy.types.Operator):
|
||||
bl_idname = "bim.activate_drawing"
|
||||
bl_label = "Activate Drawing"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Activates the selected drawing view"
|
||||
bl_description = "Activates the selected drawing view.\n\n" + "ALT+CLICK to keep the viewport position"
|
||||
|
||||
drawing: bpy.props.IntProperty()
|
||||
camera_view_point: bpy.props.BoolProperty(name="Camera View Point", default=True, options={"SKIP_SAVE"})
|
||||
|
||||
def invoke(self, context, event):
|
||||
# keep the viewport position on alt+click
|
||||
# make sure to use SKIP_SAVE on property, otherwise it might get stuck
|
||||
if event.type == "LEFTMOUSE" and event.alt:
|
||||
self.camera_view_point = False
|
||||
return self.execute(context)
|
||||
|
||||
def execute(self, context):
|
||||
drawing = tool.Ifc.get().by_id(self.drawing)
|
||||
dprops = bpy.context.scene.DocProperties
|
||||
|
||||
if not self.camera_view_point:
|
||||
viewport_position = tool.Blender.get_viewport_position()
|
||||
|
||||
core.activate_drawing_view(tool.Ifc, tool.Drawing, drawing=drawing)
|
||||
bpy.context.scene.DocProperties.active_drawing_id = self.drawing
|
||||
|
||||
if not self.camera_view_point:
|
||||
tool.Blender.set_viewport_position(viewport_position)
|
||||
|
||||
dprops.active_drawing_id = self.drawing
|
||||
# reset DrawingsData to reload_drawing_styles work correctly
|
||||
DrawingsData.is_loaded = False
|
||||
dprops.drawing_styles.clear()
|
||||
if ifcopenshell.util.element.get_pset(drawing, "EPset_Drawing", "HasUnderlay"):
|
||||
bpy.ops.bim.reload_drawing_styles()
|
||||
bpy.ops.bim.activate_drawing_style()
|
||||
core.sync_references(tool.Ifc, tool.Collector, tool.Drawing, drawing=tool.Ifc.get().by_id(self.drawing))
|
||||
CutDecorator.install(context)
|
||||
tool.Drawing.show_decorations()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -1336,7 +1359,7 @@ class ResizeText(bpy.types.Operator):
|
||||
# TODO: check undo redo
|
||||
|
||||
def execute(self, context):
|
||||
for obj in context.scene.camera.users_collection[0].objects:
|
||||
for obj in context.scene.camera.BIMObjectProperties.collection.objects:
|
||||
if isinstance(obj.data, bpy.types.TextCurve):
|
||||
annotation.Annotator.resize_text(obj)
|
||||
return {"FINISHED"}
|
||||
@@ -1349,16 +1372,13 @@ class RemoveDrawing(bpy.types.Operator, Operator):
|
||||
bl_description = "Remove currently selected drawing.\n\n" + "SHIFT+CLICK to remove all selected drawings"
|
||||
|
||||
drawing: bpy.props.IntProperty()
|
||||
remove_all: bpy.props.BoolProperty(name="Remove All", default=False)
|
||||
remove_all: bpy.props.BoolProperty(name="Remove All", default=False, options={"SKIP_SAVE"})
|
||||
|
||||
def invoke(self, context, event):
|
||||
# removing all selected drawings on shift+click
|
||||
# make sure to use SKIP_SAVE on property, otherwise it might get stuck
|
||||
if event.type == "LEFTMOUSE" and event.shift:
|
||||
self.remove_all = True
|
||||
else:
|
||||
# can't rely on default value since the line above
|
||||
# will set the value to `True` for all future operator calls
|
||||
self.remove_all = False
|
||||
return self.execute(context)
|
||||
|
||||
def _execute(self, context):
|
||||
@@ -1369,24 +1389,88 @@ class RemoveDrawing(bpy.types.Operator, Operator):
|
||||
else:
|
||||
drawings = [tool.Ifc.get().by_id(self.drawing)]
|
||||
|
||||
print("Removing drawings: {}".format([d for d in drawings]))
|
||||
removed_drawings = [drawing.id() for drawing in drawings]
|
||||
|
||||
for drawing in drawings:
|
||||
core.remove_drawing(tool.Ifc, tool.Drawing, drawing=drawing)
|
||||
|
||||
|
||||
class AddDrawingStyle(bpy.types.Operator):
|
||||
class ReloadDrawingStyles(bpy.types.Operator):
|
||||
bl_idname = "bim.reload_drawing_styles"
|
||||
bl_label = "Reload Drawing Styles"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Reload drawing styles for the active camera"
|
||||
|
||||
def execute(self, context):
|
||||
if not DrawingsData.is_loaded:
|
||||
DrawingsData.load()
|
||||
drawing_pset_data = DrawingsData.data["active_drawing_pset_data"]
|
||||
camera_props = context.active_object.data.BIMCameraProperties
|
||||
|
||||
# added this part as a temporary fallback
|
||||
# TODO: should remove it a bit later when projects get more accommodated
|
||||
# with saving shadingstyles to ifc and separate json file
|
||||
if "ShadingStyles" not in drawing_pset_data:
|
||||
ifc_file = tool.Ifc.get()
|
||||
pset = ifc_file.by_id(drawing_pset_data["id"])
|
||||
edit_properties = {
|
||||
"ShadingStyles": (
|
||||
shading_styles_path := tool.Drawing.get_default_drawing_resource_path("ShadingStyles")
|
||||
),
|
||||
"CurrentShadingStyle": tool.Drawing.get_default_shading_style(),
|
||||
}
|
||||
ifcopenshell.api.run("pset.edit_pset", ifc_file, pset=pset, properties=edit_properties)
|
||||
tool.Drawing.setup_shading_styles_path(shading_styles_path)
|
||||
|
||||
DrawingsData.load()
|
||||
drawing_pset_data = DrawingsData.data["active_drawing_pset_data"]
|
||||
|
||||
if "ShadingStyles" not in drawing_pset_data:
|
||||
self.report({"ERROR"}, "Could not find shading styles path in EPset_Drawing.ShadingStyles.")
|
||||
return {"CANCELLED"}
|
||||
|
||||
rel_path = drawing_pset_data["ShadingStyles"]
|
||||
current_style = drawing_pset_data.get("CurrentShadingStyle", None)
|
||||
|
||||
json_path = Path(tool.Ifc.resolve_uri(rel_path))
|
||||
if not json_path.exists():
|
||||
self.report({"ERROR"}, "Shading styles file not found: {}".format(json_path))
|
||||
return {"CANCELLED"}
|
||||
|
||||
with open(json_path, "r") as fi:
|
||||
shading_styles_json = json.load(fi)
|
||||
|
||||
drawing_styles = context.scene.DocProperties.drawing_styles
|
||||
drawing_styles.clear()
|
||||
styles = [style for style in shading_styles_json]
|
||||
for style_name in styles:
|
||||
style_data = shading_styles_json[style_name]
|
||||
drawing_style = drawing_styles.add()
|
||||
drawing_style["name"] = style_name # setting as attribute to avoid triggering setter
|
||||
drawing_style.render_type = style_data["render_type"]
|
||||
drawing_style.raster_style = json.dumps(style_data["raster_style"])
|
||||
|
||||
if current_style is not None:
|
||||
camera_props.active_drawing_style_index = styles.index(current_style)
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AddDrawingStyle(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.add_drawing_style"
|
||||
bl_label = "Add Drawing Style"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
new = context.scene.DocProperties.drawing_styles.add()
|
||||
new.name = "New Drawing Style"
|
||||
drawing_styles = context.scene.DocProperties.drawing_styles
|
||||
new = drawing_styles.add()
|
||||
# drawing style is saved to ifc on rename
|
||||
new.name = tool.Blender.ensure_unique_name("New Drawing Style", drawing_styles)
|
||||
context.scene.camera.data.BIMCameraProperties.active_drawing_style_index = len(drawing_styles) - 1
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class RemoveDrawingStyle(bpy.types.Operator):
|
||||
class RemoveDrawingStyle(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.remove_drawing_style"
|
||||
bl_label = "Remove Drawing Style"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
@@ -1394,13 +1478,17 @@ class RemoveDrawingStyle(bpy.types.Operator):
|
||||
|
||||
def execute(self, context):
|
||||
context.scene.DocProperties.drawing_styles.remove(self.index)
|
||||
context.scene.camera.data.BIMCameraProperties.active_drawing_style_index = max(self.index - 1, 0)
|
||||
bpy.ops.bim.save_drawing_styles_data()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class SaveDrawingStyle(bpy.types.Operator):
|
||||
class SaveDrawingStyle(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.save_drawing_style"
|
||||
bl_label = "Save Drawing Style"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Save current render setting to currently selected drawing style. Also resaves styles data to IFC"
|
||||
|
||||
index: bpy.props.StringProperty()
|
||||
# TODO: check undo redo
|
||||
|
||||
@@ -1408,19 +1496,46 @@ class SaveDrawingStyle(bpy.types.Operator):
|
||||
space = self.get_view_3d(context) # Do not remove. It is used later in eval
|
||||
scene = context.scene
|
||||
style = {}
|
||||
for prop in RasterStyleProperty:
|
||||
value = eval(prop.value)
|
||||
eval_namespace = {"context": context, "scene": scene, "space": space}
|
||||
|
||||
def add_prop_to_style(prop_path, context, scene, space):
|
||||
value = eval(prop_path)
|
||||
if not isinstance(value, str):
|
||||
try:
|
||||
value = tuple(value)
|
||||
except TypeError:
|
||||
pass
|
||||
style[prop.value] = value
|
||||
style[prop_path] = value
|
||||
|
||||
for prop in RasterStyleProperty:
|
||||
if prop.name.startswith("EVAL_PROP"):
|
||||
prop_path = prop.value
|
||||
add_prop_to_style(prop_path, **eval_namespace)
|
||||
else:
|
||||
props_source_path = prop.value
|
||||
props_source = eval(props_source_path)
|
||||
for prop_name in dir(props_source):
|
||||
if prop_name.startswith("__"):
|
||||
continue
|
||||
|
||||
prop_path = f"{props_source_path}.{prop_name}"
|
||||
prop_value = eval(prop_path)
|
||||
if (
|
||||
not isinstance(prop_value, (int, float, bool, str, Color, Vector))
|
||||
or props_source.is_property_readonly(prop_name)
|
||||
or prop_path in RASTER_STYLE_PROPERTIES_EXCLUDE
|
||||
):
|
||||
continue
|
||||
|
||||
add_prop_to_style(prop_path, **eval_namespace)
|
||||
|
||||
if self.index:
|
||||
index = int(self.index)
|
||||
else:
|
||||
index = context.active_object.data.BIMCameraProperties.active_drawing_style_index
|
||||
scene.DocProperties.drawing_styles[index].raster_style = json.dumps(style)
|
||||
|
||||
bpy.ops.bim.save_drawing_styles_data()
|
||||
return {"FINISHED"}
|
||||
|
||||
def get_view_3d(self, context):
|
||||
@@ -1433,19 +1548,84 @@ class SaveDrawingStyle(bpy.types.Operator):
|
||||
return space
|
||||
|
||||
|
||||
class ActivateDrawingStyle(bpy.types.Operator):
|
||||
class SaveDrawingStylesData(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.save_drawing_styles_data"
|
||||
bl_label = "Save Drawing Styles Data"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
skip_updating_current_style: bpy.props.BoolProperty(default=False)
|
||||
rename_style: bpy.props.BoolProperty(default=False)
|
||||
rename_style_from: bpy.props.StringProperty(default="")
|
||||
rename_style_to: bpy.props.StringProperty(default="")
|
||||
|
||||
def execute(self, context):
|
||||
if not DrawingsData.is_loaded:
|
||||
DrawingsData.load()
|
||||
drawing_pset_data = DrawingsData.data["active_drawing_pset_data"]
|
||||
drawing_styles = context.scene.DocProperties.drawing_styles
|
||||
|
||||
rel_path = drawing_pset_data["ShadingStyles"]
|
||||
current_style = drawing_pset_data.get("CurrentShadingStyle", None)
|
||||
json_path = Path(tool.Ifc.resolve_uri(rel_path))
|
||||
if not json_path.exists():
|
||||
self.report({"ERROR"}, "Shading styles file not found: {}".format(json_path))
|
||||
return {"CANCELLED"}
|
||||
|
||||
styles_data = {}
|
||||
for style in drawing_styles:
|
||||
style_data = {"render_type": style.render_type, "raster_style": json.loads(style.raster_style)}
|
||||
styles_data[style.name] = style_data
|
||||
|
||||
with open(json_path, "w") as fo:
|
||||
json.dump(styles_data, fo, indent=4)
|
||||
|
||||
# TODO: currently it doesn't update current style for the other drawings
|
||||
# handling case when current style is not present in styles saved in ifc
|
||||
if not self.skip_updating_current_style and current_style not in styles_data and current_style is not None:
|
||||
# style was renamed
|
||||
if self.rename_style and current_style == self.rename_style_from:
|
||||
new_style_name = self.rename_style_to
|
||||
|
||||
# style was removed
|
||||
else:
|
||||
new_style_name = None
|
||||
|
||||
ifc_file = tool.Ifc.get()
|
||||
drawing = ifc_file.by_id(context.scene.DocProperties.active_drawing_id)
|
||||
pset = tool.Pset.get_element_pset(drawing, "EPset_Drawing")
|
||||
ifcopenshell.api.run(
|
||||
"pset.edit_pset", ifc_file, pset=pset, properties={"CurrentShadingStyle": new_style_name}
|
||||
)
|
||||
blenderbim.bim.handler.refresh_ui_data()
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class ActivateDrawingStyle(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.activate_drawing_style"
|
||||
bl_label = "Activate Drawing Style"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
scene = context.scene
|
||||
if scene.camera.data.BIMCameraProperties.active_drawing_style_index < len(scene.DocProperties.drawing_styles):
|
||||
self.drawing_style = scene.DocProperties.drawing_styles[
|
||||
scene.camera.data.BIMCameraProperties.active_drawing_style_index
|
||||
]
|
||||
self.set_raster_style(context)
|
||||
self.set_query(context)
|
||||
ifc_file = tool.Ifc.get()
|
||||
active_drawing_style_index = scene.camera.data.BIMCameraProperties.active_drawing_style_index
|
||||
|
||||
if active_drawing_style_index >= len(scene.DocProperties.drawing_styles):
|
||||
self.report({"ERROR"}, "Could not find active drawing style")
|
||||
return {"CANCELLED"}
|
||||
|
||||
self.drawing_style = scene.DocProperties.drawing_styles[active_drawing_style_index]
|
||||
|
||||
self.set_raster_style(context)
|
||||
self.set_query(context)
|
||||
|
||||
drawing = ifc_file.by_id(scene.DocProperties.active_drawing_id)
|
||||
pset = tool.Pset.get_element_pset(drawing, "EPset_Drawing")
|
||||
ifcopenshell.api.run(
|
||||
"pset.edit_pset", ifc_file, pset=pset, properties={"CurrentShadingStyle": self.drawing_style.name}
|
||||
)
|
||||
blenderbim.bim.handler.refresh_ui_data()
|
||||
return {"FINISHED"}
|
||||
|
||||
def set_raster_style(self, context):
|
||||
@@ -1534,9 +1714,10 @@ class AddSchedule(bpy.types.Operator, Operator):
|
||||
# taking into account different drives on windows
|
||||
if Path(filepath).drive == Path(ifc_path).drive:
|
||||
filepath = os.path.relpath(filepath, ifc_path)
|
||||
core.add_schedule(
|
||||
core.add_document(
|
||||
tool.Ifc,
|
||||
tool.Drawing,
|
||||
"SCHEDULE",
|
||||
uri=filepath,
|
||||
)
|
||||
|
||||
@@ -1552,7 +1733,7 @@ class RemoveSchedule(bpy.types.Operator, Operator):
|
||||
schedule: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
core.remove_schedule(tool.Ifc, tool.Drawing, schedule=tool.Ifc.get().by_id(self.schedule))
|
||||
core.remove_document(tool.Ifc, tool.Drawing, "SCHEDULE", schedule=tool.Ifc.get().by_id(self.schedule))
|
||||
|
||||
|
||||
class OpenSchedule(bpy.types.Operator, Operator):
|
||||
@@ -1623,11 +1804,116 @@ class AddScheduleToSheet(bpy.types.Operator, Operator):
|
||||
|
||||
sheet_builder = sheeter.SheetBuilder()
|
||||
sheet_builder.data_dir = context.scene.BIMProperties.data_dir
|
||||
sheet_builder.add_schedule(reference, schedule, sheet)
|
||||
sheet_builder.add_document(reference, schedule, sheet)
|
||||
|
||||
tool.Drawing.import_sheets()
|
||||
|
||||
|
||||
class AddReferenceToSheet(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.add_reference_to_sheet"
|
||||
bl_label = "Add Reference To Sheet"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
props = context.scene.DocProperties
|
||||
return props.references and props.sheets and context.scene.BIMProperties.data_dir
|
||||
|
||||
def _execute(self, context):
|
||||
props = context.scene.DocProperties
|
||||
active_reference = props.references[props.active_reference_index]
|
||||
active_sheet = tool.Drawing.get_active_sheet(context)
|
||||
reference = tool.Ifc.get().by_id(active_reference.ifc_definition_id)
|
||||
if tool.Ifc.get_schema() == "IFC2X3":
|
||||
reference_location = tool.Drawing.get_path_with_ext(reference.DocumentReferences[0].Location, "svg")
|
||||
else:
|
||||
reference_location = tool.Drawing.get_path_with_ext(reference.HasDocumentReferences[0].Location, "svg")
|
||||
|
||||
sheet = tool.Ifc.get().by_id(active_sheet.ifc_definition_id)
|
||||
if not sheet.is_a("IfcDocumentInformation"):
|
||||
return
|
||||
|
||||
references = tool.Drawing.get_document_references(sheet)
|
||||
|
||||
has_reference = False
|
||||
for reference in references:
|
||||
if reference.Location == reference_location:
|
||||
has_reference = True
|
||||
break
|
||||
if has_reference:
|
||||
return
|
||||
|
||||
if not tool.Drawing.does_file_exist(tool.Ifc.resolve_uri(reference_location)):
|
||||
self.report({"ERROR"}, f"Cannot find reference svg by path {reference_location}.")
|
||||
return
|
||||
|
||||
reference = tool.Ifc.run("document.add_reference", information=sheet)
|
||||
id_attr = "ItemReference" if tool.Ifc.get_schema() == "IFC2X3" else "Identification"
|
||||
attributes = {
|
||||
id_attr: str(len([r for r in references if r.Description in ("DRAWING", "REFERENCE")]) + 1),
|
||||
"Location": reference_location,
|
||||
"Description": "REFERENCE",
|
||||
}
|
||||
tool.Ifc.run("document.edit_reference", reference=reference, attributes=attributes)
|
||||
|
||||
sheet_builder = sheeter.SheetBuilder()
|
||||
sheet_builder.data_dir = context.scene.BIMProperties.data_dir
|
||||
sheet_builder.add_document(reference, reference, sheet)
|
||||
|
||||
tool.Drawing.import_sheets()
|
||||
|
||||
|
||||
class AddReference(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.add_reference"
|
||||
bl_label = "Add Reference"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
filter_glob: bpy.props.StringProperty(default="*.svg", options={"HIDDEN"})
|
||||
use_relative_path: bpy.props.BoolProperty(name="Use Relative Path", default=True)
|
||||
|
||||
def _execute(self, context):
|
||||
filepath = self.filepath
|
||||
if self.use_relative_path:
|
||||
ifc_path = tool.Ifc.get_path()
|
||||
if os.path.isfile(ifc_path):
|
||||
ifc_path = os.path.dirname(ifc_path)
|
||||
|
||||
# taking into account different drives on windows
|
||||
if Path(filepath).drive == Path(ifc_path).drive:
|
||||
filepath = os.path.relpath(filepath, ifc_path)
|
||||
core.add_document(
|
||||
tool.Ifc,
|
||||
tool.Drawing,
|
||||
"REFERENCE",
|
||||
uri=filepath,
|
||||
)
|
||||
|
||||
def invoke(self, context, event):
|
||||
context.window_manager.fileselect_add(self)
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
|
||||
class RemoveReference(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.remove_reference"
|
||||
bl_label = "Remove Reference"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
reference: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
core.remove_document(tool.Ifc, tool.Drawing, "REFERENCE", document=tool.Ifc.get().by_id(self.reference))
|
||||
|
||||
|
||||
class OpenReference(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.open_reference"
|
||||
bl_label = "Open Reference"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
reference: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
core.open_reference(tool.Drawing, reference=tool.Ifc.get().by_id(self.reference))
|
||||
|
||||
|
||||
# TODO: dead code - drawing style attributes are never used?
|
||||
class AddDrawingStyleAttribute(bpy.types.Operator):
|
||||
bl_idname = "bim.add_drawing_style_attribute"
|
||||
bl_label = "Add Drawing Style Attribute"
|
||||
@@ -1639,6 +1925,7 @@ class AddDrawingStyleAttribute(bpy.types.Operator):
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
# TODO: dead code - drawing style attributes are never used?
|
||||
class RemoveDrawingStyleAttribute(bpy.types.Operator):
|
||||
bl_idname = "bim.remove_drawing_style_attribute"
|
||||
bl_label = "Remove Drawing Style Attribute"
|
||||
@@ -1951,6 +2238,24 @@ class DisableEditingSchedules(bpy.types.Operator, Operator):
|
||||
core.disable_editing_schedules(tool.Drawing)
|
||||
|
||||
|
||||
class LoadReferences(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.load_references"
|
||||
bl_label = "Load References"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
core.load_references(tool.Drawing)
|
||||
|
||||
|
||||
class DisableEditingReferences(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.disable_editing_references"
|
||||
bl_label = "Disable Editing References"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
core.disable_editing_references(tool.Drawing)
|
||||
|
||||
|
||||
class LoadDrawings(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.load_drawings"
|
||||
bl_label = "Load Drawings"
|
||||
|
||||
@@ -65,12 +65,27 @@ def get_location_hint(self, context):
|
||||
|
||||
|
||||
def update_diagram_scale(self, context):
|
||||
scale = self.diagram_scale
|
||||
if scale == "CUSTOM":
|
||||
scale = self.custom_diagram_scale
|
||||
if "|" not in scale:
|
||||
try:
|
||||
element = (
|
||||
tool.Ifc.get()
|
||||
.by_id(self.id_data.BIMMeshProperties.ifc_definition_id)
|
||||
.OfProductRepresentation[0]
|
||||
.ShapeOfProduct[0]
|
||||
)
|
||||
except:
|
||||
return
|
||||
human_scale, scale = scale.split("|")
|
||||
diagram_scale = tool.Drawing.get_diagram_scale(tool.Ifc.get_object(element))
|
||||
if not diagram_scale:
|
||||
return
|
||||
pset = ifcopenshell.util.element.get_pset(element, "EPset_Drawing")
|
||||
if pset:
|
||||
pset = tool.Ifc.get().by_id(pset["id"])
|
||||
else:
|
||||
pset = ifcopenshell.api.run("pset.add_pset", tool.Ifc.get(), product=element, name="EPset_Drawing")
|
||||
ifcopenshell.api.run("pset.edit_pset", tool.Ifc.get(), pset=pset, properties=diagram_scale)
|
||||
|
||||
|
||||
def update_is_nts(self, context):
|
||||
try:
|
||||
element = (
|
||||
tool.Ifc.get()
|
||||
@@ -85,9 +100,7 @@ def update_diagram_scale(self, context):
|
||||
pset = tool.Ifc.get().by_id(pset["id"])
|
||||
else:
|
||||
pset = ifcopenshell.api.run("pset.add_pset", tool.Ifc.get(), product=element, name="EPset_Drawing")
|
||||
ifcopenshell.api.run(
|
||||
"pset.edit_pset", tool.Ifc.get(), pset=pset, properties={"Scale": scale, "HumanScale": human_scale}
|
||||
)
|
||||
ifcopenshell.api.run("pset.edit_pset", tool.Ifc.get(), pset=pset, properties={"IsNTS": self.is_nts})
|
||||
|
||||
|
||||
def get_diagram_scales(self, context):
|
||||
@@ -156,13 +169,32 @@ def update_drawing_name(self, context):
|
||||
core.update_drawing_name(tool.Ifc, tool.Drawing, drawing=drawing, name=self.name)
|
||||
|
||||
|
||||
def update_schedule_name(self, context):
|
||||
schedule = tool.Ifc.get().by_id(self.ifc_definition_id)
|
||||
core.update_schedule_name(tool.Ifc, tool.Drawing, schedule=schedule, name=self.name)
|
||||
def get_drawing_style_name(self):
|
||||
"""needed to make `set_drawing_style_name` work"""
|
||||
return self.get("name", "")
|
||||
|
||||
|
||||
def set_drawing_style_name(self, new_value):
|
||||
"""ensure the name is unique"""
|
||||
scene = bpy.context.scene
|
||||
drawing_styles = [s.name for s in scene.DocProperties.drawing_styles if s.name != self.name]
|
||||
new_value = tool.Blender.ensure_unique_name(new_value, drawing_styles)
|
||||
old_value = self.name
|
||||
self["name"] = new_value
|
||||
bpy.ops.bim.save_drawing_styles_data(rename_style=True, rename_style_from=old_value, rename_style_to=new_value)
|
||||
|
||||
|
||||
def update_document_name(self, context):
|
||||
document = tool.Ifc.get().by_id(self.ifc_definition_id)
|
||||
core.update_document_name(tool.Ifc, tool.Drawing, document=document, name=self.name)
|
||||
|
||||
|
||||
def update_has_underlay(self, context):
|
||||
update_layer(self, context, "HasUnderlay", self.has_underlay)
|
||||
# making sure that camera is active
|
||||
if self.has_underlay and (context.active_object and context.active_object.data == self.id_data):
|
||||
bpy.ops.bim.reload_drawing_styles()
|
||||
bpy.ops.bim.activate_drawing_style()
|
||||
|
||||
|
||||
def update_has_linework(self, context):
|
||||
@@ -199,8 +231,11 @@ def toggleDecorations(self, context):
|
||||
toggle = self.should_draw_decorations
|
||||
if toggle:
|
||||
# TODO: design a proper text variable templating renderer
|
||||
collection = context.scene.camera.users_collection[0]
|
||||
collection = context.scene.camera.BIMObjectProperties.collection
|
||||
for obj in collection.objects:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element or not tool.Drawing.is_annotation_object_type(element, ["TEXT", "TEXT_LEADER"]):
|
||||
continue
|
||||
tool.Drawing.update_text_value(obj)
|
||||
refresh_drawing_data()
|
||||
decoration.DecorationsHandler.install(context)
|
||||
@@ -220,9 +255,9 @@ class Drawing(PropertyGroup):
|
||||
is_selected: BoolProperty(name="Is Selected", default=True)
|
||||
|
||||
|
||||
class Schedule(PropertyGroup):
|
||||
class Document(PropertyGroup):
|
||||
ifc_definition_id: IntProperty(name="IFC Definition ID")
|
||||
name: StringProperty(name="Name", update=update_schedule_name)
|
||||
name: StringProperty(name="Name", update=update_document_name)
|
||||
identification: StringProperty(name="Identification")
|
||||
|
||||
|
||||
@@ -236,8 +271,8 @@ class Sheet(PropertyGroup):
|
||||
|
||||
|
||||
class DrawingStyle(PropertyGroup):
|
||||
name: StringProperty(name="Name")
|
||||
raster_style: StringProperty(name="Raster Style")
|
||||
name: StringProperty(name="Name", get=get_drawing_style_name, set=set_drawing_style_name)
|
||||
raster_style: StringProperty(name="Raster Style", default="{}")
|
||||
render_type: EnumProperty(
|
||||
items=[
|
||||
("NONE", "None", ""),
|
||||
@@ -253,30 +288,20 @@ class DrawingStyle(PropertyGroup):
|
||||
|
||||
|
||||
class RasterStyleProperty(enum.Enum):
|
||||
WORLD_COLOR = "bpy.data.worlds[0].color"
|
||||
RENDER_ENGINE = "scene.render.engine"
|
||||
RENDER_TRANSPARENT = "scene.render.film_transparent"
|
||||
VIEW_TRANSFORM = "scene.view_settings.view_transform"
|
||||
SHADING_SHOW_OBJECT_OUTLINE = "scene.display.shading.show_object_outline"
|
||||
SHADING_SHOW_CAVITY = "scene.display.shading.show_cavity"
|
||||
SHADING_CAVITY_TYPE = "scene.display.shading.cavity_type"
|
||||
SHADING_CURVATURE_RIDGE_FACTOR = "scene.display.shading.curvature_ridge_factor"
|
||||
SHADING_CURVATURE_VALLEY_FACTOR = "scene.display.shading.curvature_valley_factor"
|
||||
SHADING_LIGHT = "scene.display.shading.light"
|
||||
SHADING_COLOR_TYPE = "scene.display.shading.color_type"
|
||||
SHADING_SINGLE_COLOR = "scene.display.shading.single_color"
|
||||
SHADING_SHOW_SHADOWS = "scene.display.shading.show_shadows"
|
||||
SHADING_SHADOW_INTENSITY = "scene.display.shading.shadow_intensity"
|
||||
DISPLAY_LIGHT_DIRECTION = "scene.display.light_direction"
|
||||
VIEW_USE_CURVE_MAPPING = "scene.view_settings.use_curve_mapping"
|
||||
OVERLAY_SHOW_WIREFRAMES = "space.overlay.show_wireframes"
|
||||
OVERLAY_WIREFRAME_THRESHOLD = "space.overlay.wireframe_threshold"
|
||||
OVERLAY_SHOW_FLOOR = "space.overlay.show_floor"
|
||||
OVERLAY_SHOW_AXIS_X = "space.overlay.show_axis_x"
|
||||
OVERLAY_SHOW_AXIS_Y = "space.overlay.show_axis_y"
|
||||
OVERLAY_SHOW_AXIS_Z = "space.overlay.show_axis_z"
|
||||
OVERLAY_SHOW_OBJECT_ORIGINS = "space.overlay.show_object_origins"
|
||||
OVERLAY_SHOW_RELATIONSHIP_LINES = "space.overlay.show_relationship_lines"
|
||||
# EVAL_PROP_ props will be evaluated explicitly
|
||||
EVAL_PROP_WORLD_COLOR = "bpy.data.worlds[0].color"
|
||||
|
||||
# those props attributes used as a source for shading style properties
|
||||
RENDER = "scene.render"
|
||||
VIEW_SETTINGS = "scene.view_settings"
|
||||
SHADING = "scene.display.shading"
|
||||
DISPLAY = "scene.display"
|
||||
OVERLAY = "space.overlay"
|
||||
|
||||
|
||||
RASTER_STYLE_PROPERTIES_EXCLUDE = (
|
||||
"scene.render.filepath",
|
||||
)
|
||||
|
||||
|
||||
class DocProperties(PropertyGroup):
|
||||
@@ -286,6 +311,7 @@ class DocProperties(PropertyGroup):
|
||||
should_extract: BoolProperty(name="Should Extract", default=True)
|
||||
is_editing_drawings: BoolProperty(name="Is Editing Drawings", default=False)
|
||||
is_editing_schedules: BoolProperty(name="Is Editing Schedules", default=False)
|
||||
is_editing_references: BoolProperty(name="Is Editing References", default=False)
|
||||
target_view: EnumProperty(
|
||||
items=[
|
||||
("PLAN_VIEW", "Plan", ""),
|
||||
@@ -303,8 +329,10 @@ class DocProperties(PropertyGroup):
|
||||
active_drawing_id: IntProperty(name="Active Drawing Id")
|
||||
active_drawing_index: IntProperty(name="Active Drawing Index")
|
||||
current_drawing_index: IntProperty(name="Current Drawing Index")
|
||||
schedules: CollectionProperty(name="Schedules", type=Schedule)
|
||||
schedules: CollectionProperty(name="Schedules", type=Document)
|
||||
active_schedule_index: IntProperty(name="Active Schedule Index")
|
||||
references: CollectionProperty(name="References", type=Document)
|
||||
active_reference_index: IntProperty(name="Active Reference Index")
|
||||
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)
|
||||
@@ -324,6 +352,10 @@ class DocProperties(PropertyGroup):
|
||||
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")
|
||||
shadingstyles_path: StringProperty(
|
||||
default=os.path.join("drawings", "assets", "shading_styles.json"), name="Default Shading Styles"
|
||||
)
|
||||
shadingstyle_default: StringProperty(default="Blender Default", name="Default Shading Style")
|
||||
|
||||
|
||||
class BIMCameraProperties(PropertyGroup):
|
||||
@@ -335,10 +367,11 @@ class BIMCameraProperties(PropertyGroup):
|
||||
representation: StringProperty(name="Representation")
|
||||
view_name: StringProperty(name="View Name")
|
||||
diagram_scale: EnumProperty(items=get_diagram_scales, name="Drawing Scale", update=update_diagram_scale)
|
||||
custom_diagram_scale: StringProperty(name="Custom Scale", update=update_diagram_scale)
|
||||
custom_scale_numerator: bpy.props.StringProperty(default="1", update=update_diagram_scale)
|
||||
custom_scale_denominator: bpy.props.StringProperty(default="100", update=update_diagram_scale)
|
||||
raster_x: IntProperty(name="Raster X", default=1000)
|
||||
raster_y: IntProperty(name="Raster Y", default=1000)
|
||||
is_nts: BoolProperty(name="Is NTS")
|
||||
is_nts: BoolProperty(name="Is NTS", update=update_is_nts)
|
||||
active_drawing_style_index: IntProperty(name="Active Drawing Style Index")
|
||||
|
||||
# For now, this JSON dump are all the parameters that determine a camera's "Block representation"
|
||||
@@ -468,6 +501,7 @@ ANNOTATION_TYPES_DATA = {
|
||||
"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"),
|
||||
"REVISION_CLOUD":("Revision Cloud", "Add revision cloud", "VOLUME_DATA", "mesh"),
|
||||
"FILL_AREA": ("Fill Area", "", "NODE_TEXTURE", "mesh"),
|
||||
"FALL": ("Fall", "", "SORT_ASC", "curve"),
|
||||
}
|
||||
@@ -491,6 +525,10 @@ def update_annotation_object_type(self, context):
|
||||
AnnotationData.is_loaded = False
|
||||
|
||||
|
||||
def update_sheet_data(self, context):
|
||||
SheetsData.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
|
||||
|
||||
@@ -19,14 +19,38 @@
|
||||
from blenderbim.bim.module.drawing.svgwriter import SvgWriter
|
||||
import svgwrite
|
||||
|
||||
from odf.opendocument import load
|
||||
from odf.opendocument import load as load_ods
|
||||
from odf.table import Table, TableRow, TableColumn, TableCell
|
||||
from odf.text import P
|
||||
from odf.style import Style
|
||||
from textwrap import wrap
|
||||
from pathlib import Path
|
||||
import string
|
||||
|
||||
FONT_SIZE = 4.13
|
||||
FONT_WIDTH = FONT_SIZE * 0.45
|
||||
FONT_WIDTH = lambda size: size * 0.45
|
||||
FONT_SIZE_PT = 12
|
||||
FONT_FAMILY = "OpenGost Type B TT"
|
||||
DEBUG = False
|
||||
|
||||
|
||||
def col2num(col):
|
||||
"""convert letter column index to number:
|
||||
`"A" -> 1`, `"AA" -> 27``
|
||||
"""
|
||||
num = 0
|
||||
for c in col:
|
||||
if c in string.ascii_letters:
|
||||
num = num * 26 + (ord(c.upper()) - ord("A")) + 1
|
||||
return num
|
||||
|
||||
|
||||
def a1_to_rc(cell):
|
||||
"""convert cell index from A1 format to RC: `"A1" -> (0,0)`"""
|
||||
column_letter = cell.strip(string.digits)
|
||||
col_number = col2num(column_letter) - 1
|
||||
row_number = int(cell[len(column_letter) :]) - 1
|
||||
return row_number, col_number
|
||||
|
||||
|
||||
class Scheduler:
|
||||
@@ -38,21 +62,33 @@ class Scheduler:
|
||||
)
|
||||
self.padding = 1
|
||||
self.margin = 1
|
||||
doc = load(infile)
|
||||
doc = load_ods(infile)
|
||||
|
||||
# useful for debugging ods
|
||||
if DEBUG:
|
||||
import xml.dom.minidom
|
||||
|
||||
path = Path(infile)
|
||||
dom = xml.dom.minidom.parseString(doc.xml())
|
||||
pretty_xml = dom.toprettyxml()
|
||||
|
||||
with open(path.with_suffix(".xml"), "w") as fo:
|
||||
fo.write(pretty_xml)
|
||||
|
||||
styles = {}
|
||||
for style in doc.getElementsByType(Style):
|
||||
name = style.getAttribute("name")
|
||||
for cell_style in doc.getElementsByType(Style):
|
||||
name = cell_style.getAttribute("name")
|
||||
styles[name] = {}
|
||||
|
||||
# NOTE: there are also styles that inherit from parent styles that we do not process atm
|
||||
if not style.firstChild:
|
||||
if not cell_style.firstChild:
|
||||
continue
|
||||
|
||||
if style.firstChild.tagName in ["style:table-column-properties", "style:table-row-properties"]:
|
||||
style_children = [style.firstChild]
|
||||
if cell_style.firstChild.tagName in ["style:table-column-properties", "style:table-row-properties"]:
|
||||
style_children = [cell_style.firstChild]
|
||||
else:
|
||||
# for style:table-cell-properties we need to collect also text and paragraph properties
|
||||
style_children = style.childNodes
|
||||
style_children = cell_style.childNodes
|
||||
|
||||
for child in style_children:
|
||||
child_params = {key[1]: value for key, value in child.attributes.items()}
|
||||
@@ -60,117 +96,252 @@ class Scheduler:
|
||||
|
||||
table = doc.getElementsByType(Table)[0]
|
||||
|
||||
# related styles stored as a list of tuples:
|
||||
# [(child, parent), ...]
|
||||
related_styles = []
|
||||
|
||||
# collect columns width
|
||||
column_widths = []
|
||||
column_styles = []
|
||||
for col in table.getElementsByType(TableColumn):
|
||||
style_name = col.getAttribute("stylename")
|
||||
repeat = col.getAttribute("numbercolumnsrepeated")
|
||||
repeat = int(repeat) if repeat else 1
|
||||
for i in range(repeat):
|
||||
col_repeat = col.getAttribute("numbercolumnsrepeated")
|
||||
col_repeat = int(col_repeat) if col_repeat else 1
|
||||
for i in range(col_repeat):
|
||||
if not style_name or "column-width" not in styles[style_name]:
|
||||
column_width = 50
|
||||
else:
|
||||
column_width = self.convert_to_mm(styles[style_name]["column-width"])
|
||||
column_styles.append(style_name)
|
||||
column_widths.append(column_width)
|
||||
cell_style = col.getAttribute("defaultcellstylename")
|
||||
if cell_style:
|
||||
related_styles.append((style_name, cell_style))
|
||||
|
||||
# collect rows height
|
||||
row_heights = []
|
||||
# TODO: never used yet because unsure about priority for row styles
|
||||
# over column styles or vice versa
|
||||
row_styles = []
|
||||
|
||||
for col in table.getElementsByType(TableRow):
|
||||
style_name = col.getAttribute("stylename")
|
||||
repeat = col.getAttribute("numberrowsrepeated")
|
||||
repeat = int(repeat) if repeat else 1
|
||||
for i in range(repeat):
|
||||
row_repeat = col.getAttribute("numberrowsrepeated")
|
||||
row_repeat = int(row_repeat) if row_repeat else 1
|
||||
for i in range(row_repeat):
|
||||
if not style_name or "row-height" not in styles[style_name]:
|
||||
row_height = 6
|
||||
else:
|
||||
row_height = self.convert_to_mm(styles[style_name]["row-height"])
|
||||
row_styles.append(style_name)
|
||||
row_heights.append(row_height)
|
||||
cell_style = col.getAttribute("defaultcellstylename")
|
||||
if cell_style:
|
||||
related_styles.append((style_name, cell_style))
|
||||
|
||||
while len(related_styles) > 0:
|
||||
# unzip related styles to children and parents
|
||||
children, parents = zip(*related_styles)
|
||||
independent_styles = set(parents) - set(children)
|
||||
for relation in related_styles[:]:
|
||||
child, parent = relation
|
||||
if parent in independent_styles:
|
||||
child_style = styles[child]
|
||||
styles[child] = styles[parent] | child_style
|
||||
related_styles.remove(relation)
|
||||
|
||||
# TODO: multiple print ranges? 😔
|
||||
print_range = table.getAttribute("printranges")
|
||||
if print_range:
|
||||
min_rc, max_rc = [a1_to_rc(cell.rsplit(".", 1)[1]) for cell in print_range.split(":")]
|
||||
else:
|
||||
# fallback if print range is not defined
|
||||
n_rows = len(row_heights)
|
||||
n_cols = len(column_widths)
|
||||
n_cells = len(row_heights) * len(column_widths)
|
||||
cells_limit = 10000
|
||||
if n_cells >= cells_limit:
|
||||
raise Exception(
|
||||
f"You were about to build a very big table with number of cells more than {cells_limit}.\n"
|
||||
f"In fact it is {n_rows} rows x {n_cols} cols = {n_cells} cells \n"
|
||||
"and the operation was stopped to prevent system freeze.\n"
|
||||
"Please define print range in .ods file to proceede\n"
|
||||
"(needed to make sure printed table will have reasonable size)."
|
||||
)
|
||||
|
||||
min_rc, max_rc = (0, 0), (1048576, 16384)
|
||||
min_row, min_col = min_rc
|
||||
max_row, max_col = max_rc
|
||||
|
||||
# draw table
|
||||
y = self.margin
|
||||
for tri, tr in enumerate(table.getElementsByType(TableRow)):
|
||||
x = self.margin
|
||||
height = row_heights[tri]
|
||||
tdi = 0
|
||||
tri = 0
|
||||
stop_iterating_over_rows = False
|
||||
# TODO: row spans support?
|
||||
for tr in table.getElementsByType(TableRow):
|
||||
if stop_iterating_over_rows:
|
||||
break
|
||||
|
||||
for td in tr.getElementsByType(TableCell):
|
||||
column_span = td.getAttribute("numbercolumnsspanned")
|
||||
column_span = int(column_span) if column_span else 1
|
||||
row_repeat = tr.getAttribute("numberrowsrepeated")
|
||||
row_repeat = int(row_repeat) if row_repeat else 1
|
||||
|
||||
repeat = td.getAttribute("numbercolumnsrepeated")
|
||||
repeat = int(repeat) if repeat else 1
|
||||
for i_row_repeat in range(row_repeat):
|
||||
if tri < min_row:
|
||||
tri += 1
|
||||
continue
|
||||
elif tri > max_row:
|
||||
stop_iterating_over_rows = True
|
||||
break
|
||||
|
||||
# figuring text alignment
|
||||
style_name = td.getAttribute("stylename")
|
||||
style = styles[style_name] if style_name else {}
|
||||
if style_name and "vertical-align" in style and style["vertical-align"] != "automatic":
|
||||
vertical_align = style["vertical-align"]
|
||||
else:
|
||||
vertical_align = "bottom"
|
||||
x = self.margin
|
||||
height = row_heights[tri]
|
||||
tdi = 0
|
||||
stop_iterating_over_columns = False
|
||||
|
||||
alignment_translation = {
|
||||
"center": "middle",
|
||||
"end": "right",
|
||||
"start": "left",
|
||||
}
|
||||
for td in tr.getElementsByType(TableCell):
|
||||
if stop_iterating_over_columns:
|
||||
break
|
||||
|
||||
if style_name and "text-align" in style and style["text-align"] != "automatic":
|
||||
horizontal_align = style["text-align"]
|
||||
horizontal_align = alignment_translation.get(horizontal_align, horizontal_align)
|
||||
else:
|
||||
horizontal_align = "left"
|
||||
column_span = td.getAttribute("numbercolumnsspanned")
|
||||
column_span = int(column_span) if column_span else 1
|
||||
|
||||
if vertical_align == "middle" and horizontal_align == "middle":
|
||||
box_alignment = "center"
|
||||
else:
|
||||
box_alignment = f"{vertical_align}-{horizontal_align}"
|
||||
col_repeat = td.getAttribute("numbercolumnsrepeated")
|
||||
col_repeat = int(col_repeat) if col_repeat else 1
|
||||
|
||||
# for future use
|
||||
wrap_text = style.get("wrap-option", None) == "wrap"
|
||||
# figuring text alignment
|
||||
cell_style = self.get_style(td.getAttribute("stylename"), styles)
|
||||
|
||||
# drawing cells and text
|
||||
for i in range(0, repeat):
|
||||
width = sum(column_widths[tdi : tdi + int(column_span)])
|
||||
self.svg.add(
|
||||
self.svg.rect(
|
||||
insert=(x, y),
|
||||
size=(width, height),
|
||||
style="fill: #ffffff; stroke-width:.125; stroke: #000000;",
|
||||
# drawing cells and text
|
||||
for i_col_repeat in range(col_repeat):
|
||||
start_tdi = tdi
|
||||
end_tdi = tdi + int(column_span) - 1
|
||||
# if the entire span is beyond print range => continue
|
||||
# if only part then keeping that part
|
||||
if start_tdi < min_col:
|
||||
if end_tdi < min_col:
|
||||
tdi += column_span
|
||||
continue
|
||||
else:
|
||||
start_tdi = min_col
|
||||
|
||||
# stop if start column is beyond print range
|
||||
if start_tdi > max_col:
|
||||
stop_iterating_over_columns = True
|
||||
break
|
||||
|
||||
# making sure last column won't go beyond the print range
|
||||
if end_tdi > max_col:
|
||||
end_tdi = max_col
|
||||
|
||||
width = sum(column_widths[start_tdi : end_tdi + 1])
|
||||
col_style = self.get_style(column_styles[tdi], styles)
|
||||
final_cell_style = cell_style or col_style
|
||||
background_color = final_cell_style.get("background-color", "#ffffff")
|
||||
|
||||
self.svg.add(
|
||||
self.svg.rect(
|
||||
insert=(x, y),
|
||||
size=(width, height),
|
||||
style=f"fill: {background_color}; stroke-width:.125; stroke: #000000;",
|
||||
)
|
||||
)
|
||||
)
|
||||
p_tags = td.getElementsByType(P)
|
||||
|
||||
if p_tags:
|
||||
# figuring text position based on alignment
|
||||
text_position = [0.0, 0.0]
|
||||
if box_alignment.endswith("left"):
|
||||
text_position[0] = x + self.padding
|
||||
elif box_alignment.endswith("middle") or box_alignment == "center":
|
||||
text_position[0] = x + width / 2
|
||||
elif box_alignment.endswith("right"):
|
||||
text_position[0] = x + width - self.padding
|
||||
|
||||
if box_alignment.startswith("top"):
|
||||
text_position[1] = y + self.padding
|
||||
elif box_alignment.startswith("middle") or box_alignment == "center":
|
||||
text_position[1] = y + height / 2
|
||||
elif box_alignment.startswith("bottom"):
|
||||
text_position[1] = y + height - self.padding
|
||||
|
||||
self.add_text(
|
||||
p_tags, *text_position, box_alignment=box_alignment, wrap_text=wrap_text, cell_width=width
|
||||
p_tags = td.getElementsByType(P)
|
||||
text_color = final_cell_style.get("color", None)
|
||||
box_alignment = self.get_box_alignment(final_cell_style)
|
||||
wrap_text = final_cell_style.get("wrap-option", None) == "wrap"
|
||||
bold_text = final_cell_style.get("font-weight", None) == "bold"
|
||||
italic_text = final_cell_style.get("font-style", None) == "italic"
|
||||
# NOTE: very naive since we're scaling text proportionally
|
||||
font_size = (
|
||||
float(final_cell_style.get("font-size", f"{FONT_SIZE_PT}pt")[:-2])
|
||||
/ FONT_SIZE_PT
|
||||
* FONT_SIZE
|
||||
)
|
||||
x += width
|
||||
tdi += column_span
|
||||
|
||||
y += height
|
||||
total_width = sum(column_widths) + (self.margin * 2)
|
||||
if p_tags:
|
||||
# figuring text position based on alignment
|
||||
text_position = [0.0, 0.0]
|
||||
if box_alignment.endswith("left"):
|
||||
text_position[0] = x + self.padding
|
||||
elif box_alignment.endswith("middle") or box_alignment == "center":
|
||||
text_position[0] = x + width / 2
|
||||
elif box_alignment.endswith("right"):
|
||||
text_position[0] = x + width - self.padding
|
||||
|
||||
if box_alignment.startswith("top"):
|
||||
text_position[1] = y + self.padding
|
||||
elif box_alignment.startswith("middle") or box_alignment == "center":
|
||||
text_position[1] = y + height / 2
|
||||
elif box_alignment.startswith("bottom"):
|
||||
text_position[1] = y + height - self.padding
|
||||
|
||||
self.add_text(
|
||||
p_tags,
|
||||
*text_position,
|
||||
font_size=font_size,
|
||||
box_alignment=box_alignment,
|
||||
wrap_text=wrap_text,
|
||||
cell_width=width,
|
||||
bold=bold_text,
|
||||
italic=italic_text,
|
||||
text_color=text_color,
|
||||
)
|
||||
x += width
|
||||
tdi += column_span
|
||||
|
||||
tri += 1
|
||||
y += height
|
||||
|
||||
total_width = x + self.margin
|
||||
total_height = y + self.margin
|
||||
self.svg["width"] = "{}mm".format(total_width)
|
||||
self.svg["height"] = "{}mm".format(y)
|
||||
self.svg["viewBox"] = "0 0 {} {}".format(total_width, y)
|
||||
self.svg["height"] = "{}mm".format(total_height)
|
||||
self.svg["viewBox"] = "0 0 {} {}".format(total_width, total_height)
|
||||
self.svg.save(pretty=True)
|
||||
|
||||
def add_text(self, p_tags, x, y, box_alignment="bottom-left", wrap_text=False, cell_width=100):
|
||||
def get_style(self, style_name, styles):
|
||||
style = styles[style_name] if style_name else {}
|
||||
return style
|
||||
|
||||
def get_box_alignment(self, style):
|
||||
if style and "vertical-align" in style and style["vertical-align"] != "automatic":
|
||||
vertical_align = style["vertical-align"]
|
||||
else:
|
||||
vertical_align = "bottom"
|
||||
|
||||
alignment_translation = {
|
||||
"center": "middle",
|
||||
"end": "right",
|
||||
"start": "left",
|
||||
}
|
||||
|
||||
if style and "text-align" in style and style["text-align"] != "automatic":
|
||||
horizontal_align = style["text-align"]
|
||||
horizontal_align = alignment_translation.get(horizontal_align, horizontal_align)
|
||||
else:
|
||||
horizontal_align = "left"
|
||||
|
||||
if vertical_align == "middle" and horizontal_align == "middle":
|
||||
box_alignment = "center"
|
||||
else:
|
||||
box_alignment = f"{vertical_align}-{horizontal_align}"
|
||||
|
||||
return box_alignment
|
||||
|
||||
def add_text(
|
||||
self,
|
||||
p_tags,
|
||||
x,
|
||||
y,
|
||||
font_size,
|
||||
box_alignment="bottom-left",
|
||||
wrap_text=False,
|
||||
cell_width=100,
|
||||
bold=False,
|
||||
italic=False,
|
||||
text_color=None,
|
||||
):
|
||||
"""
|
||||
Adds text to svg.
|
||||
|
||||
@@ -183,16 +354,23 @@ class Scheduler:
|
||||
text_lines = [str(p).upper() for p in p_tags]
|
||||
box_alignment_params = SvgWriter.get_box_alignment_parameters(box_alignment)
|
||||
text_params = {
|
||||
"font-size": FONT_SIZE,
|
||||
"font-family": "OpenGost Type B TT",
|
||||
"font-size": font_size,
|
||||
"font-family": FONT_FAMILY,
|
||||
}
|
||||
if bold:
|
||||
text_params["font-weight"] = "bold"
|
||||
if italic:
|
||||
text_params["font-style"] = "italic"
|
||||
if text_color:
|
||||
text_params["fill"] = text_color
|
||||
|
||||
if len(text_lines) == 1 and not wrap_text:
|
||||
text_params.update(box_alignment_params)
|
||||
text_tag = self.svg.text(text_lines[0], insert=(x, y), **text_params)
|
||||
text_tag = self.svg.text(text_lines[0], insert=(x, y), **(text_params))
|
||||
self.svg.add(text_tag)
|
||||
return
|
||||
|
||||
text_tag = self.svg.text("", **({"style": "font-size: 0;"} | box_alignment_params))
|
||||
text_tag = self.svg.text("", **(text_params | {"font-size": "0"} | box_alignment_params))
|
||||
|
||||
# TODO: should be done in less naive way
|
||||
# without using magic number for FONT_WIDTH
|
||||
@@ -200,7 +378,7 @@ class Scheduler:
|
||||
if wrap_text:
|
||||
wrapped_lines = []
|
||||
for line in text_lines:
|
||||
wrapped_line = wrap(line, width=int(cell_width // FONT_WIDTH), break_long_words=False)
|
||||
wrapped_line = wrap(line, width=int(cell_width // FONT_WIDTH(font_size)), break_long_words=False)
|
||||
wrapped_lines.extend(wrapped_line)
|
||||
else:
|
||||
wrapped_lines = text_lines
|
||||
|
||||
@@ -16,9 +16,23 @@
|
||||
# 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/>.
|
||||
|
||||
from gpu.types import GPUShader
|
||||
from gpu_extras.batch import batch_for_shader
|
||||
import gpu
|
||||
from mathutils import Vector
|
||||
|
||||
|
||||
# NOTES:
|
||||
# Since Metal doesn't support geometry shaders we stick to builtin shaders
|
||||
# and generate all geometry data in python before passing it to the shader.
|
||||
# This way was considered to be the most reliable atm.
|
||||
# More: https://blender.stackexchange.com/questions/291674/migrating-geometry-shaders-to-metal
|
||||
#
|
||||
# BGL deprecation:
|
||||
# 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_DEF_GLSL = """
|
||||
@@ -29,12 +43,6 @@ BASE_DEF_GLSL = """
|
||||
#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;
|
||||
@@ -160,12 +168,11 @@ void do_vertex_util(vec4 pos, vec2 ofs)
|
||||
|
||||
// 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;
|
||||
// TODO: radius is unnecessary?
|
||||
head[0] = side * -radius;
|
||||
head[1] = nose * -.5;
|
||||
head[2] = vec4(0) + ear;
|
||||
head[3] = nose * .5;
|
||||
head[1] = side * length * -.5;
|
||||
head[2] = dir * width;
|
||||
head[3] = side * length * .5;
|
||||
head[4] = side * radius;
|
||||
}
|
||||
|
||||
@@ -189,6 +196,22 @@ void do_circle_head(vec4 pos_w, vec4 head[CIRCLE_SEGS]) {
|
||||
"""
|
||||
|
||||
|
||||
def add_verts_sequence(verts, start_i, output_verts, output_edges, closed=False):
|
||||
"""Add sequence of verts to output lists, returns next vertex index"""
|
||||
for i, v in enumerate(verts[:-1], start_i):
|
||||
output_verts.append(v)
|
||||
output_edges.append((i, i + 1))
|
||||
output_verts.append(verts[-1])
|
||||
if closed:
|
||||
output_edges.append((i + 1, start_i))
|
||||
return i + 2
|
||||
|
||||
|
||||
def add_offsets(v, offsets):
|
||||
"""returns list of verts with offsets added"""
|
||||
return [v + offset for offset in offsets]
|
||||
|
||||
|
||||
class BaseShader:
|
||||
"""Wrapper for GPUShader
|
||||
To use for viewport decorations with geometry generated on GPU side.
|
||||
@@ -265,48 +288,49 @@ class BaseShader:
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
# NB: libcode arg doesn't work
|
||||
# TODO: rename to .shader
|
||||
self.prog = GPUShader(
|
||||
vertexcode=self.VERT_GLSL,
|
||||
fragcode=self.FRAG_GLSL,
|
||||
geocode=self.LIB_GLSL + self.GEOM_GLSL,
|
||||
defines=self.DEF_GLSL,
|
||||
)
|
||||
# 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.base_shader = gpu.shader.from_builtin("3D_UNIFORM_COLOR")
|
||||
|
||||
def get_shader(self):
|
||||
"""Returns shader for this type"""
|
||||
return self.line_shader if self.TYPE == "LINES" else self.base_shader
|
||||
|
||||
def batch(self, indices=None, **data):
|
||||
"""Returns automatic GPUBatch filled with provided parameters"""
|
||||
batch = batch_for_shader(self.prog, self.TYPE, data, indices=indices)
|
||||
batch.program_set(self.prog)
|
||||
shader = self.get_shader()
|
||||
batch = batch_for_shader(shader, self.TYPE, data, indices=indices)
|
||||
return batch
|
||||
|
||||
def bind(self):
|
||||
self.prog.bind()
|
||||
"""need to bind shader before changing it's uniforms"""
|
||||
shader = self.get_shader()
|
||||
shader.bind()
|
||||
return shader
|
||||
|
||||
def glenable(self):
|
||||
gpu.state.blend_set("ALPHA")
|
||||
gpu.state.depth_test_set("LESS_EQUAL")
|
||||
|
||||
def uniform_region(self, ctx):
|
||||
shader = self.bind()
|
||||
|
||||
region = ctx.region
|
||||
region3d = ctx.region_data
|
||||
|
||||
uniform_floats = {
|
||||
"viewMatrix": region3d.perspective_matrix,
|
||||
"winsize": (region.width, region.height),
|
||||
"ModelViewProjectionMatrix": region3d.perspective_matrix,
|
||||
# POLYLINE_UNIFORM_COLOR specific uniforms
|
||||
"viewportSize": (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
|
||||
shader.uniform_float(name, value)
|
||||
|
||||
|
||||
# TODO: add smoothing if this shades is going to be used
|
||||
# TODO: add smoothing if this shader is going to be used
|
||||
# TODO: dead code?
|
||||
class BaseLinesShader(BaseShader):
|
||||
"""Draws line segments with gaps around vertices at endpoints"""
|
||||
|
||||
@@ -429,37 +453,24 @@ class ExtrusionGuidesShader(GizmoShader):
|
||||
|
||||
TYPE = "LINES"
|
||||
|
||||
DEF_GLSL = (
|
||||
BaseShader.DEF_GLSL
|
||||
+ """
|
||||
#define CROSS_SIZE .5
|
||||
"""
|
||||
)
|
||||
def process_geometry(self, verts):
|
||||
CROSS_SIZE = 0.5
|
||||
|
||||
GEOM_GLSL = """
|
||||
uniform mat4 ModelViewProjectionMatrix;
|
||||
p0, p1 = verts
|
||||
bx = Vector((1, 0, 0)) * CROSS_SIZE
|
||||
by = Vector((0, 1, 0)) * CROSS_SIZE
|
||||
|
||||
layout(lines) in;
|
||||
layout(triangle_strip, max_vertices=MAX_POINTS) out;
|
||||
output_verts = []
|
||||
output_edges = []
|
||||
out_kwargs = {
|
||||
"output_verts": output_verts,
|
||||
"output_edges": output_edges,
|
||||
}
|
||||
|
||||
void main() {
|
||||
// default setup for macro to work
|
||||
vec2 EDGE_DIR;
|
||||
start_i = 0
|
||||
start_i = add_verts_sequence(add_offsets(p0, [-bx, bx]), start_i, **out_kwargs)
|
||||
start_i = add_verts_sequence(add_offsets(p0, [-by, by]), start_i, **out_kwargs)
|
||||
start_i = add_verts_sequence(add_offsets(p1, [-bx, bx]), start_i, **out_kwargs)
|
||||
start_i = add_verts_sequence(add_offsets(p1, [-by, by]), start_i, **out_kwargs)
|
||||
|
||||
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;
|
||||
|
||||
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();
|
||||
}
|
||||
"""
|
||||
return output_verts, output_edges
|
||||
|
||||
@@ -27,6 +27,13 @@ import xml.etree.ElementTree as ET
|
||||
import blenderbim.tool as tool
|
||||
import ifcopenshell.util.geolocation
|
||||
from xml.dom import minidom
|
||||
from mathutils import Vector
|
||||
import re
|
||||
|
||||
VIEW_TITLE_OFFSET_Y = 5
|
||||
DEFAULT_POSITION = Vector((30, 30))
|
||||
SVG = "{http://www.w3.org/2000/svg}"
|
||||
XLINK = "{http://www.w3.org/1999/xlink}"
|
||||
|
||||
|
||||
class SheetBuilder:
|
||||
@@ -104,8 +111,8 @@ class SheetBuilder:
|
||||
background = ET.SubElement(view, "image")
|
||||
background.attrib["data-type"] = "background"
|
||||
background.attrib["xlink:href"] = os.path.relpath(underlay_path, layout_dir)
|
||||
background.attrib["x"] = "30"
|
||||
background.attrib["y"] = "30"
|
||||
background.attrib["x"] = str(DEFAULT_POSITION.x)
|
||||
background.attrib["y"] = str(DEFAULT_POSITION.y)
|
||||
background.attrib["width"] = str(view_width)
|
||||
background.attrib["height"] = str(view_height)
|
||||
|
||||
@@ -114,17 +121,18 @@ class SheetBuilder:
|
||||
foreground = ET.SubElement(view, "image")
|
||||
foreground.attrib["data-type"] = "foreground"
|
||||
foreground.attrib["xlink:href"] = os.path.relpath(drawing_path, layout_dir)
|
||||
foreground.attrib["x"] = "30"
|
||||
foreground.attrib["y"] = "30"
|
||||
foreground.attrib["x"] = str(DEFAULT_POSITION.x)
|
||||
foreground.attrib["y"] = str(DEFAULT_POSITION.y)
|
||||
foreground.attrib["width"] = str(view_width)
|
||||
foreground.attrib["height"] = str(view_height)
|
||||
|
||||
self.add_view_title(30, view_height + 35, view, layout_dir)
|
||||
self.add_view_title(
|
||||
DEFAULT_POSITION.x, view_height + DEFAULT_POSITION.y + VIEW_TITLE_OFFSET_Y, view, layout_dir
|
||||
)
|
||||
layout_tree.write(layout_path)
|
||||
|
||||
def update_sheet_drawing_sizes(self, sheet):
|
||||
ET.register_namespace("", "http://www.w3.org/2000/svg")
|
||||
SVG = "{http://www.w3.org/2000/svg}"
|
||||
|
||||
layout_path = tool.Drawing.get_document_uri(sheet, "LAYOUT")
|
||||
layout_tree = ET.parse(layout_path)
|
||||
@@ -143,10 +151,20 @@ class SheetBuilder:
|
||||
view_width = self.convert_to_mm(drawing_root.attrib.get("width"))
|
||||
view_height = self.convert_to_mm(drawing_root.attrib.get("height"))
|
||||
|
||||
foreground = drawing_view.find(f'.//{SVG}image[@data-type="foreground"]')
|
||||
height = float(foreground.attrib["height"])
|
||||
width = float(foreground.attrib["width"])
|
||||
readjust = Vector((width - view_width, height - view_height)) / 2
|
||||
|
||||
for image in drawing_view.findall(f"{SVG}image"):
|
||||
x = float(image.attrib["x"])
|
||||
y = float(image.attrib["y"])
|
||||
if image.attrib["data-type"] == "view-title":
|
||||
image.attrib["y"] = str(view_height + 35)
|
||||
image.attrib["x"] = str(x - readjust.x)
|
||||
image.attrib["y"] = str(y - readjust.y)
|
||||
else:
|
||||
image.attrib["x"] = str(x + readjust.x)
|
||||
image.attrib["y"] = str(y + readjust.y)
|
||||
image.attrib["width"] = str(view_width)
|
||||
image.attrib["height"] = str(view_height)
|
||||
|
||||
@@ -166,7 +184,7 @@ class SheetBuilder:
|
||||
|
||||
layout_tree.write(layout_path)
|
||||
|
||||
def add_schedule(self, reference, schedule, sheet):
|
||||
def add_document(self, reference, schedule, sheet):
|
||||
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)
|
||||
@@ -193,12 +211,14 @@ class SheetBuilder:
|
||||
foreground = ET.SubElement(view, "image")
|
||||
foreground.attrib["data-type"] = "table"
|
||||
foreground.attrib["xlink:href"] = os.path.relpath(view_path, layout_dir)
|
||||
foreground.attrib["x"] = "30"
|
||||
foreground.attrib["y"] = "30"
|
||||
foreground.attrib["x"] = str(DEFAULT_POSITION.x)
|
||||
foreground.attrib["y"] = str(DEFAULT_POSITION.y)
|
||||
foreground.attrib["width"] = str(view_width)
|
||||
foreground.attrib["height"] = str(view_height)
|
||||
|
||||
self.add_view_title(30, view_height + 35, view, layout_dir)
|
||||
self.add_view_title(
|
||||
DEFAULT_POSITION.x, view_height + DEFAULT_POSITION.y + VIEW_TITLE_OFFSET_Y, view, layout_dir
|
||||
)
|
||||
layout_tree.write(layout_path)
|
||||
|
||||
def add_view_title(self, x, y, parent, layout_dir):
|
||||
@@ -262,8 +282,61 @@ class SheetBuilder:
|
||||
titleblock.append(g)
|
||||
titleblock.remove(image)
|
||||
|
||||
def ensure_drawing_unique_styles(self, svg, drawing_id):
|
||||
"""ensures all drawing's classes and ids will be unique for the whole sheet
|
||||
by adding `drawing_id` based prefix
|
||||
"""
|
||||
prefix = f"d{drawing_id}" # just number doesn't work
|
||||
|
||||
# add .prefix class to all css selectors
|
||||
style = svg.find(f"{SVG}defs/{SVG}style")
|
||||
style_data = style.text
|
||||
text = ""
|
||||
brackets_level = 0
|
||||
for l in style_data:
|
||||
if l == "{":
|
||||
if brackets_level == 0:
|
||||
cur_line = text.splitlines()[-1]
|
||||
text = text[: -len(cur_line)]
|
||||
css_selectors = []
|
||||
# making sure cases like "text, tspan" will be
|
||||
# converted to "text.prefix, tspan.prefix"
|
||||
for css_selector in cur_line.split(","):
|
||||
css_selector = f"{css_selector.strip()}.{prefix}"
|
||||
css_selectors.append(css_selector)
|
||||
text += ", ".join(css_selectors) + " "
|
||||
brackets_level += 1
|
||||
elif l == "}":
|
||||
brackets_level -= 1
|
||||
text += l
|
||||
|
||||
# replace urls url(#marker) with url(#prefix-marker)
|
||||
# since url(#marker.prefix) doesn't seem to work
|
||||
text = re.sub(r"url\(#([^\)]+)\)", rf"url(#{prefix}-\1)", text)
|
||||
style.text = text
|
||||
|
||||
for svg_element in svg.findall(f".//*"):
|
||||
if svg_element.tag in (f"{SVG}style", f"{SVG}svg"):
|
||||
continue
|
||||
attrib = svg_element.attrib
|
||||
# add "prefix-" to all ids
|
||||
if "id" in attrib:
|
||||
attrib["id"] = f"{prefix}-{attrib['id']}"
|
||||
# add class "prefix" to all classes
|
||||
if "class" in attrib:
|
||||
attrib["class"] += f" {prefix}"
|
||||
if svg_element.tag == f"{SVG}use":
|
||||
href_attrib = f"{XLINK}href"
|
||||
if href_attrib in attrib:
|
||||
href = attrib[href_attrib]
|
||||
if href.startswith("#"):
|
||||
attrib[href_attrib] = f"#{prefix}-{href[1:]}"
|
||||
|
||||
return svg
|
||||
|
||||
def build_drawings(self, root, sheet):
|
||||
for view in root.findall('{http://www.w3.org/2000/svg}g[@data-type="drawing"]'):
|
||||
drawing_id = int(view.attrib["data-id"])
|
||||
reference = tool.Ifc.get().by_id(int(view.attrib["data-id"]))
|
||||
drawing = tool.Ifc.get().by_id(view.attrib["data-drawing"])
|
||||
|
||||
@@ -282,7 +355,9 @@ class SheetBuilder:
|
||||
view_title = image
|
||||
|
||||
if foreground is not None:
|
||||
view.append(self.parse_embedded_svg(foreground, {}))
|
||||
svg = self.parse_embedded_svg(foreground, {})
|
||||
svg = self.ensure_drawing_unique_styles(svg, drawing_id)
|
||||
view.append(svg)
|
||||
|
||||
if background is not None:
|
||||
background_path = os.path.join(self.layout_dir, self.get_href(background))
|
||||
|
||||
@@ -100,6 +100,9 @@ class SvgWriter:
|
||||
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)
|
||||
print(
|
||||
f"WARNING. Couldn't find {resource} for the drawing by the path: {resource_path}. Default BBIM resource will be copied from {ootb_resource}"
|
||||
)
|
||||
if os.path.exists(ootb_resource):
|
||||
shutil.copy(ootb_resource, resource_path)
|
||||
self.resource_paths[resource] = resource_path
|
||||
@@ -121,23 +124,35 @@ class SvgWriter:
|
||||
self.height = self.raw_height * self.svg_scale
|
||||
|
||||
def add_stylesheet(self):
|
||||
if not self.resource_paths["Stylesheet"] or not os.path.exists(self.resource_paths["Stylesheet"]):
|
||||
path = self.resource_paths["Stylesheet"]
|
||||
if not path:
|
||||
return
|
||||
with open(self.resource_paths["Stylesheet"], "r") as stylesheet:
|
||||
if not os.path.exists(path):
|
||||
print(f"WARNING. Couldn't find stylesheet for the drawing by the path: {path}")
|
||||
return
|
||||
with open(path, "r") as stylesheet:
|
||||
self.svg.defs.add(self.svg.style(stylesheet.read()))
|
||||
|
||||
def add_markers(self):
|
||||
if not self.resource_paths["Markers"] or not os.path.exists(self.resource_paths["Markers"]):
|
||||
path = self.resource_paths["Markers"]
|
||||
if not path:
|
||||
return
|
||||
tree = ET.parse(self.resource_paths["Markers"])
|
||||
if not os.path.exists(path):
|
||||
print(f"WARNING. Couldn't find markers for the drawing by the path: {path}")
|
||||
return
|
||||
tree = ET.parse(path)
|
||||
root = tree.getroot()
|
||||
for child in root:
|
||||
self.svg.defs.add(External(child))
|
||||
|
||||
def add_symbols(self):
|
||||
if not self.resource_paths["Symbols"] or not os.path.exists(self.resource_paths["Symbols"]):
|
||||
path = self.resource_paths["Symbols"]
|
||||
if not path:
|
||||
return
|
||||
tree = ET.parse(self.resource_paths["Symbols"])
|
||||
if not os.path.exists(path):
|
||||
print(f"WARNING. Couldn't find symbols for the drawing by the path: {path}")
|
||||
return
|
||||
tree = ET.parse(path)
|
||||
root = tree.getroot()
|
||||
for child in root:
|
||||
self.svg.defs.add(External(child))
|
||||
@@ -148,9 +163,15 @@ class SvgWriter:
|
||||
return External(xml_symbol) if xml_symbol else None
|
||||
|
||||
def add_patterns(self):
|
||||
if not self.resource_paths["Patterns"] or not os.path.exists(self.resource_paths["Patterns"]):
|
||||
path = self.resource_paths["Patterns"]
|
||||
if not path:
|
||||
return
|
||||
tree = ET.parse(self.resource_paths["Patterns"])
|
||||
if not os.path.exists(path):
|
||||
print(f"WARNING. Couldn't find patterns for the drawing by the path: {path}")
|
||||
return
|
||||
if not path or not os.path.exists(path):
|
||||
return
|
||||
tree = ET.parse(path)
|
||||
root = tree.getroot()
|
||||
for child in root:
|
||||
self.svg.defs.add(External(child))
|
||||
@@ -225,17 +246,24 @@ class SvgWriter:
|
||||
)
|
||||
)
|
||||
# TODO: allow metric to be configurable
|
||||
rl = (matrix_world @ points[0].co.xyz).z
|
||||
rl_value = (matrix_world @ points[0].co.xyz).z
|
||||
if bpy.context.scene.unit_settings.system == "IMPERIAL":
|
||||
rl = helper.format_distance(rl, precision=self.precision, decimal_places=self.decimal_places)
|
||||
rl = helper.format_distance(rl_value, precision=self.precision, decimal_places=self.decimal_places)
|
||||
else:
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
rl /= unit_scale
|
||||
rl = rl_value / unit_scale
|
||||
rl = ifcopenshell.util.geolocation.auto_z2e(tool.Ifc.get(), rl)
|
||||
rl *= unit_scale
|
||||
rl = "{:.3f}m".format(rl)
|
||||
text_style = SvgWriter.get_box_alignment_parameters("bottom-left")
|
||||
self.svg.add(self.svg.text(f"RL +{rl}", insert=tuple(text_position), class_="SECTIONLEVEL", **text_style))
|
||||
self.svg.add(
|
||||
self.svg.text(
|
||||
"RL {}{}".format("" if rl_value < 0 else "+", 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))
|
||||
|
||||
@@ -396,8 +424,7 @@ class SvgWriter:
|
||||
self.svg.line(start=start * self.svg_scale, end=end * self.svg_scale, class_=" ".join(classes))
|
||||
)
|
||||
|
||||
# BATTING ANNOTATIONS
|
||||
if predefined_type == "BATTING":
|
||||
def draw_batting_annotation():
|
||||
v0_global = matrix_world @ obj.data.vertices[0].co.xyz
|
||||
v1_global = matrix_world @ obj.data.vertices[1].co.xyz
|
||||
v0 = self.project_point_onto_camera(v0_global)
|
||||
@@ -463,7 +490,94 @@ class SvgWriter:
|
||||
)
|
||||
self.svg.add(self.svg.polyline(points=points, class_=" ".join(classes), style=polyline_style))
|
||||
|
||||
elif predefined_type == "SECTION":
|
||||
def draw_revision_cloud_annotation():
|
||||
segment_width = 15.0
|
||||
base_height = 1
|
||||
width = 5
|
||||
|
||||
def get_svg_half_circle(height, width):
|
||||
cp0 = f"0,-{height}"
|
||||
cp1 = f"{width},-{height}"
|
||||
end_point = f"{width},0"
|
||||
circle = f"c{cp0} {cp1} {end_point}"
|
||||
return circle
|
||||
|
||||
def get_revision_pattern(base_offset):
|
||||
pattern = f"m{base_offset.x},{base_offset.y}"
|
||||
pattern += " " + get_svg_half_circle(2 * base_height, width)
|
||||
pattern += " " + get_svg_half_circle(2.5 * base_height, width)
|
||||
pattern += " " + get_svg_half_circle(1.5 * base_height, width)
|
||||
return pattern
|
||||
|
||||
def get_scale(size, direction):
|
||||
vector = direction * size
|
||||
shrinked_vector = size // segment_width * segment_width * direction
|
||||
scale = [1 if vector[i] == 0 else vector[i] / shrinked_vector[i] for i in range(2)]
|
||||
return "scale(%f, %f)" % (scale[0], scale[1])
|
||||
|
||||
def poly_to_edges(poly):
|
||||
edges = []
|
||||
n_verts = len(poly)
|
||||
lats_index = n_verts - 1
|
||||
for i in range(len(poly)):
|
||||
edge = [poly[i], (poly[i + 1]) if i != lats_index else poly[0]]
|
||||
edges.append(edge)
|
||||
return edges
|
||||
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
safe_offset_x = 2.0
|
||||
marker_width = segment_width + safe_offset_x * 2
|
||||
market_height = 15.0
|
||||
ref_y = 5.0
|
||||
revision_pattern = get_revision_pattern(Vector([safe_offset_x, ref_y]))
|
||||
|
||||
bm = tool.Blender.get_bmesh_for_mesh(obj.data).copy()
|
||||
bmesh.ops.contextual_create(bm, geom=bm.edges[:])
|
||||
faces = bm.faces[:]
|
||||
assert len(faces) == 1, "Revision cloud edges must form just 1 polygon"
|
||||
|
||||
# ensure clockwise order of polygon verts
|
||||
# given default blender counter-clockwise order
|
||||
polygon = faces[0]
|
||||
if polygon.normal.z > 0:
|
||||
polygon.normal_flip()
|
||||
|
||||
marker_id = f"revision-cloud-{element.GlobalId}"
|
||||
svg_path = self.svg.path(style="fill: none; stroke:red; stroke-width:0.20", d=revision_pattern)
|
||||
marker = self.svg.marker(
|
||||
markerUnits="userSpaceOnUse",
|
||||
insert=(safe_offset_x, ref_y),
|
||||
size=(marker_width, market_height),
|
||||
orient="auto",
|
||||
id=marker_id,
|
||||
)
|
||||
marker.add(svg_path)
|
||||
self.svg.add(marker)
|
||||
|
||||
for v0, v1 in poly_to_edges(polygon.verts):
|
||||
v0_global = matrix_world @ v0.co.xyz
|
||||
v1_global = matrix_world @ v1.co.xyz
|
||||
v0 = self.project_point_onto_camera(v0_global)
|
||||
v1 = self.project_point_onto_camera(v1_global)
|
||||
start_svg = Vector(((x_offset + v0.x), (y_offset - v0.y))) * self.svg_scale
|
||||
end_svg = Vector(((x_offset + v1.x), (y_offset - v1.y))) * self.svg_scale
|
||||
|
||||
pattern_edge = end_svg - start_svg
|
||||
pattern_dir = pattern_edge.normalized()
|
||||
pattern_length = pattern_edge.length
|
||||
|
||||
segments = int(pattern_length // segment_width)
|
||||
pattern_dir_step = pattern_dir * segment_width
|
||||
points = [pattern_dir_step * i for i in range(segments)]
|
||||
|
||||
polyline_style = f"marker: url(#{marker_id}); stroke: none;"
|
||||
polyline_transform = f"translate({start_svg.x}, {start_svg.y}) {get_scale(pattern_length, pattern_dir)}"
|
||||
polyline = self.svg.polyline(
|
||||
points=points, class_=" ".join(classes), style=polyline_style, transform=polyline_transform
|
||||
)
|
||||
self.svg.add(polyline)
|
||||
|
||||
def draw_section_annotation():
|
||||
display_data = DecoratorData.get_section_markers_display_data(obj)
|
||||
connect_markers = display_data["connect_markers"]
|
||||
|
||||
@@ -504,6 +618,12 @@ class SvgWriter:
|
||||
)
|
||||
)
|
||||
|
||||
if predefined_type == "BATTING":
|
||||
draw_batting_annotation()
|
||||
elif predefined_type == "REVISIONCLOUD":
|
||||
draw_revision_cloud_annotation()
|
||||
elif predefined_type == "SECTION":
|
||||
draw_section_annotation()
|
||||
else:
|
||||
for edge in obj.data.edges:
|
||||
draw_simple_edge_annotation(*edge.vertices[:])
|
||||
@@ -706,40 +826,22 @@ class SvgWriter:
|
||||
if not symbol_svg or not template_text_fields:
|
||||
self.svg.add(self.svg.use(f"#{symbol}", transform=symbol_transform))
|
||||
|
||||
line_number = 0
|
||||
for text_literal in text_literals:
|
||||
# after pretty indentation some redundant spaces can occur in svg tags
|
||||
# this is why we apply "font-size: 0;" to the text tag to remove those spaces
|
||||
# and add clases to the tspan tags
|
||||
# ref: https://github.com/IfcOpenShell/IfcOpenShell/issues/2833#issuecomment-1471584960
|
||||
|
||||
text = tool.Drawing.replace_text_literal_variables(text_literal.Literal, product)
|
||||
attribs = {
|
||||
"transform": text_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,
|
||||
**SvgWriter.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
|
||||
# note that tspan doesn't support using `transform` attribute
|
||||
# so we use (0,0) position because tspan is already offseted by text transform
|
||||
tspan = self.svg.tspan(text_line, class_=classes_str, insert=(0, 0))
|
||||
# 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)
|
||||
text_tags = self.create_text_tag(
|
||||
text,
|
||||
text_position_svg,
|
||||
angle,
|
||||
text_literal.BoxAlignment,
|
||||
classes_str,
|
||||
multiline=True,
|
||||
fill_bg="fill-bg" in classes,
|
||||
line_number_start=line_number,
|
||||
)
|
||||
for tag in text_tags:
|
||||
self.svg.add(tag)
|
||||
line_number += len(tag.elements)
|
||||
|
||||
def draw_break_annotations(self, obj):
|
||||
x_offset = self.raw_width / 2
|
||||
@@ -783,16 +885,16 @@ class SvgWriter:
|
||||
text_position = Vector(
|
||||
(
|
||||
(x_offset + projected_points[0].x) * self.svg_scale,
|
||||
((y_offset - projected_points[0].y) * self.svg_scale) - 2.5,
|
||||
((y_offset - projected_points[0].y) * self.svg_scale) - 1.0,
|
||||
)
|
||||
)
|
||||
# TODO: allow metric to be configurable
|
||||
rl = (matrix_world @ points[0].co).z
|
||||
rl_value = (matrix_world @ points[0].co).z
|
||||
if bpy.context.scene.unit_settings.system == "IMPERIAL":
|
||||
rl = helper.format_distance(rl, precision=self.precision, decimal_places=self.decimal_places)
|
||||
rl = helper.format_distance(rl_value, precision=self.precision, decimal_places=self.decimal_places)
|
||||
else:
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
rl /= unit_scale
|
||||
rl = rl_value / unit_scale
|
||||
rl = ifcopenshell.util.geolocation.auto_z2e(tool.Ifc.get(), rl)
|
||||
rl *= unit_scale
|
||||
rl = "{:.3f}m".format(rl)
|
||||
@@ -801,7 +903,7 @@ class SvgWriter:
|
||||
text_style = SvgWriter.get_box_alignment_parameters(box_alignment)
|
||||
self.svg.add(
|
||||
self.svg.text(
|
||||
"RL +{}".format(rl),
|
||||
"{}{}".format("" if rl_value < 0 else "+", rl),
|
||||
insert=tuple(text_position),
|
||||
class_="PLANLEVEL",
|
||||
**text_style,
|
||||
@@ -1079,6 +1181,8 @@ class SvgWriter:
|
||||
text_format=lambda x: "D" + x,
|
||||
show_description_only=dimension_data["show_description_only"],
|
||||
suppress_zero_inches=dimension_data["suppress_zero_inches"],
|
||||
text_prefix=dimension_data["text_prefix"],
|
||||
text_suffix=dimension_data["text_suffix"],
|
||||
)
|
||||
|
||||
def draw_dimension_annotations(self, obj):
|
||||
@@ -1100,6 +1204,9 @@ class SvgWriter:
|
||||
dimension_text=dimension_text,
|
||||
show_description_only=dimension_data["show_description_only"],
|
||||
suppress_zero_inches=dimension_data["suppress_zero_inches"],
|
||||
text_prefix=dimension_data["text_prefix"],
|
||||
text_suffix=dimension_data["text_suffix"],
|
||||
fill_bg=dimension_data["fill_bg"],
|
||||
)
|
||||
|
||||
def draw_measureit_arch_dimension_annotations(self):
|
||||
@@ -1123,6 +1230,9 @@ class SvgWriter:
|
||||
text_format=lambda x: x,
|
||||
show_description_only=False,
|
||||
suppress_zero_inches=False,
|
||||
text_prefix="",
|
||||
text_suffix="",
|
||||
fill_bg=False,
|
||||
):
|
||||
offset = Vector([self.raw_width, self.raw_height]) / 2
|
||||
v0 = self.project_point_onto_camera(v0_global)
|
||||
@@ -1143,27 +1253,111 @@ class SvgWriter:
|
||||
|
||||
# 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))))
|
||||
angle = math.degrees(vector.angle_signed(Vector((1, 0))))
|
||||
|
||||
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",
|
||||
**(text_kwargs | SvgWriter.get_box_alignment_parameters(box_alignment)),
|
||||
)
|
||||
text_tags = []
|
||||
text_tag_kwargs = {
|
||||
"angle": angle,
|
||||
"class_str": "DIMENSION",
|
||||
"text_format": text_format,
|
||||
"multiline": True,
|
||||
"fill_bg": fill_bg,
|
||||
}
|
||||
|
||||
if not show_description_only:
|
||||
self.svg.add(create_text_tag(str(dimension), text_position + perpendicular, "bottom-middle"))
|
||||
text = f"{text_prefix}{str(dimension)}{text_suffix}"
|
||||
text_tags += self.create_text_tag(
|
||||
text,
|
||||
text_position + perpendicular,
|
||||
box_alignment="bottom-middle",
|
||||
multiline_to_bottom=False,
|
||||
**text_tag_kwargs,
|
||||
)
|
||||
if dimension_text:
|
||||
self.svg.add(create_text_tag(dimension_text, text_position - perpendicular, "top-middle"))
|
||||
text_tags += self.create_text_tag(
|
||||
dimension_text,
|
||||
text_position - perpendicular,
|
||||
box_alignment="top-middle",
|
||||
multiline_to_bottom=True,
|
||||
**text_tag_kwargs,
|
||||
)
|
||||
|
||||
elif show_description_only and dimension_text:
|
||||
self.svg.add(create_text_tag(dimension_text, text_position + perpendicular, "bottom-middle"))
|
||||
text_tags.extend += self.create_text_tag(
|
||||
dimension_text,
|
||||
text_position + perpendicular,
|
||||
box_alignment="bottom-middle",
|
||||
multiline_to_bottom=False,
|
||||
**text_tag_kwargs,
|
||||
)
|
||||
|
||||
for tag in text_tags:
|
||||
self.svg.add(tag)
|
||||
|
||||
def create_text_tag(
|
||||
self,
|
||||
text,
|
||||
text_position,
|
||||
angle,
|
||||
box_alignment,
|
||||
class_str,
|
||||
text_format=lambda x: x,
|
||||
multiline=False,
|
||||
multiline_to_bottom=True,
|
||||
fill_bg=False,
|
||||
line_number_start=0,
|
||||
_draw_fill_bg=False,
|
||||
):
|
||||
"""returns list of created text tags"""
|
||||
text_tags = []
|
||||
if fill_bg:
|
||||
method_kwargs = locals() | {"_draw_fill_bg": True, "fill_bg": False}
|
||||
del method_kwargs["self"]
|
||||
del method_kwargs["text_tags"]
|
||||
text_tags += self.create_text_tag(**method_kwargs)
|
||||
|
||||
base_text_attrs = SvgWriter.get_box_alignment_parameters(box_alignment)
|
||||
base_text_attrs = base_text_attrs | ({"filter": "url(#fill-background)"} if _draw_fill_bg else {})
|
||||
|
||||
if not multiline:
|
||||
transform_kwargs = {"transform": "rotate({} {} {})".format(angle, text_position.x, text_position.y)}
|
||||
text_tag = self.svg.text(
|
||||
text_format(text),
|
||||
insert=text_position,
|
||||
class_=class_str,
|
||||
**(transform_kwargs | base_text_attrs),
|
||||
)
|
||||
text_tags.append(text_tag)
|
||||
return text_tags
|
||||
|
||||
text_position_svg_str = ", ".join(map(str, text_position))
|
||||
text_transform = f"translate({text_position_svg_str}) rotate({angle})"
|
||||
# after pretty indentation some redundant spaces can occur in svg tags
|
||||
# this is why we apply "font-size: 0;" to the text tag to remove those spaces
|
||||
# and add clases to the tspan tags
|
||||
# ref: https://github.com/IfcOpenShell/IfcOpenShell/issues/2833#issuecomment-1471584960
|
||||
text_kwargs = {
|
||||
"transform": text_transform,
|
||||
"style": "font-size: 0;",
|
||||
}
|
||||
|
||||
text_tag = self.svg.text("", **text_kwargs, **base_text_attrs)
|
||||
text_tags.append(text_tag)
|
||||
text_lines = text.replace("\\n", "\n").split("\n")
|
||||
text_lines = text_lines if multiline_to_bottom else text_lines[::-1]
|
||||
|
||||
for line_number, text_line in enumerate(text_lines, line_number_start):
|
||||
# position has to be inserted at tspan to avoid x offset between tspans
|
||||
# note that tspan doesn't support using `transform` attribute
|
||||
# so we use (0,0) position because tspan is already offseted by text transform
|
||||
tspan = self.svg.tspan(text_format(text_line), class_=class_str, insert=(0, 0))
|
||||
# doing it here and not in tspan constructor because constructor adds unnecessary spaces
|
||||
tspan.update({"dy": f"{line_number if multiline_to_bottom else -line_number}em"})
|
||||
text_tag.add(tspan)
|
||||
return text_tags
|
||||
|
||||
def project_point_onto_camera(self, point):
|
||||
# TODO is this needlessly complex?
|
||||
|
||||
@@ -23,10 +23,11 @@ from bpy.types import Panel
|
||||
from blenderbim.bim.module.drawing.data import (
|
||||
ProductAssignmentsData,
|
||||
SheetsData,
|
||||
SchedulesData,
|
||||
DocumentsData,
|
||||
DrawingsData,
|
||||
DecoratorData,
|
||||
)
|
||||
from blenderbim.bim.module.drawing.prop import ANNOTATION_TYPES_DATA
|
||||
|
||||
|
||||
class BIM_PT_camera(Panel):
|
||||
@@ -70,22 +71,19 @@ class BIM_PT_camera(Panel):
|
||||
row = layout.row()
|
||||
row.prop(dprops, "should_extract")
|
||||
|
||||
row = layout.row()
|
||||
row.prop(props, "is_nts")
|
||||
|
||||
row = layout.row()
|
||||
row.operator("bim.resize_text")
|
||||
|
||||
row = layout.row()
|
||||
row.prop(props, "raster_x")
|
||||
row = layout.row()
|
||||
row.prop(props, "raster_y")
|
||||
|
||||
row = layout.row()
|
||||
row = layout.row(align=True)
|
||||
row.prop(props, "diagram_scale")
|
||||
row.prop(props, "is_nts", text="", icon="MOD_EDGESPLIT")
|
||||
|
||||
if props.diagram_scale == "CUSTOM":
|
||||
row = layout.row()
|
||||
row.prop(props, "custom_diagram_scale")
|
||||
row = layout.row(align=True)
|
||||
row.prop(props, "custom_scale_numerator", text="Custom Scale")
|
||||
row.prop(props, "custom_scale_denominator", text="")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.operator("bim.create_drawing", text="Create Drawing", icon="OUTPUT")
|
||||
@@ -111,38 +109,53 @@ class BIM_PT_drawing_underlay(Panel):
|
||||
layout.use_property_split = True
|
||||
dprops = context.scene.DocProperties
|
||||
props = context.active_object.data.BIMCameraProperties
|
||||
drawing_index_is_valid = props.active_drawing_style_index < len(dprops.drawing_styles)
|
||||
|
||||
if not DrawingsData.is_loaded:
|
||||
DrawingsData.load()
|
||||
drawing_pset_data = DrawingsData.data["active_drawing_pset_data"]
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.operator("bim.add_drawing_style")
|
||||
current_shading_style = drawing_pset_data.get("CurrentShadingStyle", None)
|
||||
if current_shading_style is None:
|
||||
row.label(text="Current style is not set.")
|
||||
else:
|
||||
row.label(text="Current Shading Style:")
|
||||
row.label(text=current_shading_style)
|
||||
row.operator("bim.add_drawing_style", icon="ADD", text="")
|
||||
if drawing_index_is_valid:
|
||||
row.operator("bim.remove_drawing_style", icon="X", text="").index = props.active_drawing_style_index
|
||||
row.operator("bim.reload_drawing_styles", icon="FILE_REFRESH", text="")
|
||||
|
||||
if dprops.drawing_styles:
|
||||
layout.template_list("BIM_UL_generic", "", dprops, "drawing_styles", props, "active_drawing_style_index")
|
||||
if not dprops.drawing_styles:
|
||||
return
|
||||
layout.template_list("BIM_UL_generic", "", dprops, "drawing_styles", props, "active_drawing_style_index")
|
||||
|
||||
if props.active_drawing_style_index < len(dprops.drawing_styles):
|
||||
drawing_style = dprops.drawing_styles[props.active_drawing_style_index]
|
||||
if not drawing_index_is_valid:
|
||||
return
|
||||
drawing_style = dprops.drawing_styles[props.active_drawing_style_index]
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.prop(drawing_style, "name")
|
||||
row.operator("bim.remove_drawing_style", icon="X", text="").index = props.active_drawing_style_index
|
||||
row = layout.row(align=True)
|
||||
row.prop(drawing_style, "name")
|
||||
|
||||
row = layout.row()
|
||||
row.prop(drawing_style, "render_type")
|
||||
row = layout.row(align=True)
|
||||
row.prop(drawing_style, "include_query")
|
||||
row = layout.row(align=True)
|
||||
row.prop(drawing_style, "exclude_query")
|
||||
row = layout.row()
|
||||
row.prop(drawing_style, "render_type")
|
||||
row = layout.row(align=True)
|
||||
row.prop(drawing_style, "include_query")
|
||||
row = layout.row(align=True)
|
||||
row.prop(drawing_style, "exclude_query")
|
||||
|
||||
row = layout.row()
|
||||
row.operator("bim.add_drawing_style_attribute")
|
||||
row = layout.row()
|
||||
row.operator("bim.add_drawing_style_attribute")
|
||||
|
||||
for index, attribute in enumerate(drawing_style.attributes):
|
||||
row = layout.row(align=True)
|
||||
row.prop(attribute, "name", text="")
|
||||
row.operator("bim.remove_drawing_style_attribute", icon="X", text="").index = index
|
||||
for index, attribute in enumerate(drawing_style.attributes):
|
||||
row = layout.row(align=True)
|
||||
row.prop(attribute, "name", text="")
|
||||
row.operator("bim.remove_drawing_style_attribute", icon="X", text="").index = index
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.operator("bim.save_drawing_style")
|
||||
row.operator("bim.activate_drawing_style")
|
||||
row = layout.row(align=True)
|
||||
row.operator("bim.save_drawing_style")
|
||||
row.operator("bim.activate_drawing_style")
|
||||
|
||||
|
||||
class BIM_PT_drawings(Panel):
|
||||
@@ -161,11 +174,7 @@ class BIM_PT_drawings(Panel):
|
||||
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
|
||||
draw_project_not_saved_ui(self)
|
||||
return
|
||||
|
||||
self.props = context.scene.DocProperties
|
||||
@@ -229,22 +238,18 @@ class BIM_PT_schedules(Panel):
|
||||
return tool.Ifc.get()
|
||||
|
||||
def draw(self, context):
|
||||
if not SchedulesData.is_loaded:
|
||||
SchedulesData.load()
|
||||
if not DocumentsData.is_loaded:
|
||||
DocumentsData.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
|
||||
if not DocumentsData.data["has_saved_ifc"]:
|
||||
draw_project_not_saved_ui(self)
|
||||
return
|
||||
|
||||
self.props = context.scene.DocProperties
|
||||
|
||||
if not self.props.is_editing_schedules:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=f"{SchedulesData.data['total_schedules']} Schedules Found", icon="LONGDISPLAY")
|
||||
row.label(text=f"{DocumentsData.data['total_schedules']} Schedules Found", icon="LONGDISPLAY")
|
||||
row.operator("bim.load_schedules", text="", icon="IMPORT")
|
||||
return
|
||||
|
||||
@@ -268,6 +273,58 @@ class BIM_PT_schedules(Panel):
|
||||
)
|
||||
|
||||
|
||||
def draw_project_not_saved_ui(self):
|
||||
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
|
||||
|
||||
|
||||
class BIM_PT_references(Panel):
|
||||
bl_label = "References"
|
||||
bl_idname = "BIM_PT_references"
|
||||
bl_space_type = "VIEW_3D"
|
||||
bl_region_type = "UI"
|
||||
bl_category = "BIM Documentation"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return tool.Ifc.get()
|
||||
|
||||
def draw(self, context):
|
||||
if not DocumentsData.is_loaded:
|
||||
DocumentsData.load()
|
||||
|
||||
if not DocumentsData.data["has_saved_ifc"]:
|
||||
draw_project_not_saved_ui(self)
|
||||
return
|
||||
|
||||
self.props = context.scene.DocProperties
|
||||
|
||||
if not self.props.is_editing_references:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=f"{DocumentsData.data['total_references']} References Found", icon="LONGDISPLAY")
|
||||
row.operator("bim.load_references", text="", icon="IMPORT")
|
||||
return
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.operator("bim.add_reference", icon="ADD")
|
||||
row.operator("bim.disable_editing_references", text="", icon="CANCEL")
|
||||
|
||||
if self.props.references:
|
||||
if self.props.active_reference_index < len(self.props.references):
|
||||
active_reference = self.props.references[self.props.active_reference_index]
|
||||
row = self.layout.row(align=True)
|
||||
row.alignment = "RIGHT"
|
||||
row.operator("bim.open_reference", icon="URL", text="").reference = active_reference.ifc_definition_id
|
||||
row.operator("bim.remove_reference", icon="X", text="").reference = active_reference.ifc_definition_id
|
||||
|
||||
self.layout.template_list(
|
||||
"BIM_UL_generic", "", self.props, "references", self.props, "active_reference_index"
|
||||
)
|
||||
|
||||
|
||||
class BIM_PT_sheets(Panel):
|
||||
bl_label = "Sheets"
|
||||
bl_idname = "BIM_PT_sheets"
|
||||
@@ -284,11 +341,7 @@ class BIM_PT_sheets(Panel):
|
||||
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
|
||||
draw_project_not_saved_ui(self)
|
||||
return
|
||||
|
||||
self.props = context.scene.DocProperties
|
||||
@@ -312,6 +365,7 @@ class BIM_PT_sheets(Panel):
|
||||
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="")
|
||||
row.operator("bim.add_reference_to_sheet", icon="IMAGE_REFERENCE", text="")
|
||||
row.operator("bim.create_sheets", icon="FILE_REFRESH", text="")
|
||||
if active_sheet.is_sheet:
|
||||
row.operator("bim.remove_sheet", icon="X", text="").sheet = active_sheet.ifc_definition_id
|
||||
@@ -450,55 +504,55 @@ class BIM_PT_annotation_utilities(Panel):
|
||||
row = layout.row(align=True)
|
||||
op = row.operator("bim.add_annotation", text="Dimension", icon="FIXED_SIZE")
|
||||
op.object_type = "DIMENSION"
|
||||
op.data_type = "curve"
|
||||
op.data_type = tool.Drawing.get_annotation_data_type(op.object_type)
|
||||
op = row.operator("bim.add_annotation", text="Angle", icon="DRIVER_ROTATIONAL_DIFFERENCE")
|
||||
op.object_type = "ANGLE"
|
||||
op.data_type = "curve"
|
||||
op.data_type = tool.Drawing.get_annotation_data_type(op.object_type)
|
||||
|
||||
row = layout.row(align=True)
|
||||
op = row.operator("bim.add_annotation", text="Radius", icon="FORWARD")
|
||||
op.object_type = "RADIUS"
|
||||
op.data_type = "curve"
|
||||
op.data_type = tool.Drawing.get_annotation_data_type(op.object_type)
|
||||
op = row.operator("bim.add_annotation", text="Diameter", icon="ARROW_LEFTRIGHT")
|
||||
op.object_type = "DIAMETER"
|
||||
op.data_type = "curve"
|
||||
op.data_type = tool.Drawing.get_annotation_data_type(op.object_type)
|
||||
|
||||
row = layout.row(align=True)
|
||||
op = row.operator("bim.add_annotation", text="Text", icon="SMALL_CAPS")
|
||||
op.object_type = "TEXT"
|
||||
op.data_type = "empty"
|
||||
op.data_type = tool.Drawing.get_annotation_data_type(op.object_type)
|
||||
op = row.operator("bim.add_annotation", text="Leader", icon="TRACKING_BACKWARDS")
|
||||
op.object_type = "TEXT_LEADER"
|
||||
op.data_type = "curve"
|
||||
op.data_type = tool.Drawing.get_annotation_data_type(op.object_type)
|
||||
|
||||
row = layout.row(align=True)
|
||||
op = row.operator("bim.add_annotation", text="Stair Arrow", icon="SCREEN_BACK")
|
||||
op.object_type = "STAIR_ARROW"
|
||||
op.data_type = "curve"
|
||||
op.data_type = tool.Drawing.get_annotation_data_type(op.object_type)
|
||||
op = row.operator("bim.add_annotation", text="Hidden", icon="CON_TRACKTO")
|
||||
op.object_type = "HIDDEN_LINE"
|
||||
op.data_type = "mesh"
|
||||
op.data_type = tool.Drawing.get_annotation_data_type(op.object_type)
|
||||
|
||||
row = layout.row(align=True)
|
||||
op = row.operator("bim.add_annotation", text="Level (Plan)", icon="SORTBYEXT")
|
||||
op.object_type = "PLAN_LEVEL"
|
||||
op.data_type = "curve"
|
||||
op.data_type = tool.Drawing.get_annotation_data_type(op.object_type)
|
||||
op = row.operator("bim.add_annotation", text="Level (Section)", icon="TRIA_DOWN")
|
||||
op.object_type = "SECTION_LEVEL"
|
||||
op.data_type = "curve"
|
||||
op.data_type = tool.Drawing.get_annotation_data_type(op.object_type)
|
||||
|
||||
row = layout.row(align=True)
|
||||
op = row.operator("bim.add_annotation", text="Breakline", icon="FCURVE")
|
||||
op.object_type = "BREAKLINE"
|
||||
op.data_type = "mesh"
|
||||
op.data_type = tool.Drawing.get_annotation_data_type(op.object_type)
|
||||
op = row.operator("bim.add_annotation", text="Line", icon="MESH_MONKEY")
|
||||
op.object_type = "LINEWORK"
|
||||
op.data_type = "mesh"
|
||||
op.data_type = tool.Drawing.get_annotation_data_type(op.object_type)
|
||||
|
||||
row = layout.row(align=True)
|
||||
op = row.operator("bim.add_annotation", text="Batting", icon="FORCE_FORCE")
|
||||
op.object_type = "BATTING"
|
||||
op.data_type = "mesh"
|
||||
op.data_type = tool.Drawing.get_annotation_data_type(op.object_type)
|
||||
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"
|
||||
@@ -506,7 +560,10 @@ class BIM_PT_annotation_utilities(Panel):
|
||||
row = layout.row(align=True)
|
||||
op = row.operator("bim.add_annotation", text="Fall", icon="SORT_ASC")
|
||||
op.object_type = "FALL"
|
||||
op.data_type = "curve"
|
||||
op.data_type = tool.Drawing.get_annotation_data_type(op.object_type)
|
||||
op = row.operator("bim.add_annotation", text="Revision", icon="VOLUME_DATA")
|
||||
op.object_type = "REVISION_CLOUD"
|
||||
op.data_type = tool.Drawing.get_annotation_data_type(op.object_type)
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.prop(self.props, "should_draw_decorations", text="Viewport Annotations")
|
||||
@@ -561,6 +618,8 @@ class BIM_UL_sheets(bpy.types.UIList):
|
||||
row.label(text="", icon="MENU_PANEL")
|
||||
elif item.reference_type == "REVISION":
|
||||
row.label(text="", icon="RECOVER_LAST")
|
||||
elif item.reference_type == "REFERENCE":
|
||||
row.label(text="", icon="IMAGE_REFERENCE")
|
||||
|
||||
if item.identification:
|
||||
name = f"{item.identification} - {item.name or 'Unnamed'}"
|
||||
|
||||
@@ -47,7 +47,6 @@ class AnnotationTool(WorkSpaceTool):
|
||||
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")]}),
|
||||
@@ -108,7 +107,7 @@ def create_annotation_type(context):
|
||||
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))
|
||||
ifc_context = tool.Drawing.get_annotation_context(tool.Drawing.get_drawing_target_view(drawing), object_type)
|
||||
|
||||
element = tool.Drawing.run_root_assign_class(
|
||||
obj=obj,
|
||||
@@ -133,7 +132,7 @@ def create_annotation_occurence(context):
|
||||
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))
|
||||
ifc_context = tool.Drawing.get_annotation_context(tool.Drawing.get_drawing_target_view(drawing), object_type)
|
||||
relating_type_repr = tool.Drawing.get_annotation_representation(relating_type)
|
||||
element = tool.Drawing.run_root_assign_class(
|
||||
obj=obj,
|
||||
@@ -193,14 +192,6 @@ class AnnotationToolUI:
|
||||
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
|
||||
|
||||
@@ -63,6 +63,8 @@ def register():
|
||||
bpy.types.Scene.BIMGeometryProperties = bpy.props.PointerProperty(type=prop.BIMGeometryProperties)
|
||||
bpy.types.OBJECT_PT_transform.append(ui.BIM_PT_transform)
|
||||
bpy.types.VIEW3D_MT_object.append(ui.object_menu)
|
||||
bpy.types.OUTLINER_MT_object.append(ui.outliner_menu)
|
||||
bpy.types.VIEW3D_MT_object_context_menu.append(ui.object_menu)
|
||||
wm = bpy.context.window_manager
|
||||
if wm.keyconfigs.addon:
|
||||
km = wm.keyconfigs.addon.keymaps.new(name="Object Mode", space_type="EMPTY")
|
||||
@@ -90,6 +92,8 @@ def register():
|
||||
def unregister():
|
||||
bpy.types.VIEW3D_MT_object.remove(ui.object_menu)
|
||||
bpy.types.OBJECT_PT_transform.remove(ui.BIM_PT_transform)
|
||||
bpy.types.OUTLINER_MT_object.remove(ui.outliner_menu)
|
||||
bpy.types.VIEW3D_MT_object_context_menu.remove(ui.outliner_menu)
|
||||
del bpy.types.Scene.BIMGeometryProperties
|
||||
del bpy.types.Object.BIMGeometryProperties
|
||||
wm = bpy.context.window_manager
|
||||
|
||||
@@ -104,24 +104,50 @@ class ConnectionsData:
|
||||
def connections(cls):
|
||||
results = []
|
||||
element = tool.Ifc.get_entity(bpy.context.active_object)
|
||||
for rel in getattr(element, "ConnectedTo", []):
|
||||
|
||||
connected_to = getattr(element, "ConnectedTo", [])
|
||||
connected_from = getattr(element, "ConnectedFrom", [])
|
||||
|
||||
for rel in connected_to:
|
||||
if element.is_a("IfcDistributionPort"):
|
||||
related_element = rel.RelatedPort
|
||||
else:
|
||||
related_element = rel.RelatedElement
|
||||
|
||||
if element.is_a("IfcRelConnectsPathElements"):
|
||||
related_element_connection_type = rel.RelatedConnectionType
|
||||
else:
|
||||
related_element_connection_type = ""
|
||||
|
||||
results.append(
|
||||
{
|
||||
"id": rel.id(),
|
||||
"is_relating": True,
|
||||
"Name": rel.RelatedElement.Name or "Unnamed",
|
||||
"ConnectionType": rel.RelatingConnectionType,
|
||||
"Name": related_element.Name or "Unnamed",
|
||||
"ConnectionType": related_element_connection_type,
|
||||
}
|
||||
)
|
||||
for rel in getattr(element, "ConnectedFrom", []):
|
||||
|
||||
for rel in connected_from:
|
||||
if element.is_a("IfcDistributionPort"):
|
||||
relating_element = rel.RelatingPort
|
||||
else:
|
||||
relating_element = rel.RelatingElement
|
||||
|
||||
if element.is_a("IfcRelConnectsPathElements"):
|
||||
relating_element_connection_type = rel.RelatingConnectionType
|
||||
else:
|
||||
relating_element_connection_type = ""
|
||||
|
||||
results.append(
|
||||
{
|
||||
"id": rel.id(),
|
||||
"is_relating": False,
|
||||
"Name": rel.RelatingElement.Name or "Unnamed",
|
||||
"ConnectionType": rel.RelatedConnectionType,
|
||||
"Name": relating_element.Name or "Unnamed",
|
||||
"ConnectionType": relating_element_connection_type,
|
||||
}
|
||||
)
|
||||
|
||||
return results
|
||||
|
||||
|
||||
@@ -152,10 +178,18 @@ class DerivedPlacementsData:
|
||||
|
||||
@classmethod
|
||||
def load_collection(cls):
|
||||
cls.collection = bpy.data.objects.get(bpy.context.active_object.users_collection[0].name)
|
||||
cls.collection = None
|
||||
cls.collection_z = 0
|
||||
if cls.collection:
|
||||
cls.collection_z = cls.collection.matrix_world.translation.z
|
||||
element = tool.Ifc.get_entity(bpy.context.active_object)
|
||||
if not element:
|
||||
return
|
||||
parent = ifcopenshell.util.element.get_aggregate(element)
|
||||
if not parent:
|
||||
parent = ifcopenshell.util.element.get_container(element)
|
||||
if parent:
|
||||
cls.collection = tool.Ifc.get_object(parent)
|
||||
if cls.collection:
|
||||
cls.collection_z = cls.collection.matrix_world.translation.z
|
||||
|
||||
@classmethod
|
||||
def has_collection(cls):
|
||||
|
||||
@@ -199,6 +199,13 @@ class UpdateRepresentation(bpy.types.Operator, Operator):
|
||||
old_representation = self.file.by_id(obj.data.BIMMeshProperties.ifc_definition_id)
|
||||
context_of_items = old_representation.ContextOfItems
|
||||
|
||||
# TODO: remove this code a bit later
|
||||
# added this as a fallback for easier transition some annotation types to 3d
|
||||
# if they were create before as 2d
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if tool.Drawing.is_annotation_object_type(element, ("FALL", "SECTION_LEVEL", "PLAN_LEVEL")):
|
||||
context_of_items = tool.Drawing.get_annotation_context("MODEL_VIEW")
|
||||
|
||||
gprop = context.scene.BIMGeoreferenceProperties
|
||||
coordinate_offset = None
|
||||
if gprop.has_blender_offset and obj.BIMObjectProperties.blender_offset_type == "CARTESIAN_POINT":
|
||||
@@ -366,6 +373,7 @@ class OverrideDelete(bpy.types.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
use_global: bpy.props.BoolProperty(default=False)
|
||||
confirm: bpy.props.BoolProperty(default=True)
|
||||
is_batch: bpy.props.BoolProperty(name="Is Batch", default=False)
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
@@ -373,7 +381,7 @@ class OverrideDelete(bpy.types.Operator):
|
||||
|
||||
def execute(self, context):
|
||||
# Deep magick from the dawn of time
|
||||
if IfcStore.get_file():
|
||||
if tool.Ifc.get():
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
for obj in context.selected_objects:
|
||||
bpy.data.objects.remove(obj)
|
||||
@@ -382,21 +390,59 @@ class OverrideDelete(bpy.types.Operator):
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
if self.confirm:
|
||||
if tool.Ifc.get():
|
||||
total_elements = len(tool.Ifc.get().wrapped_data.entity_names())
|
||||
total_polygons = sum([len(o.data.polygons) for o in context.selected_objects if o.type == "MESH"])
|
||||
# These numbers are a bit arbitrary, but basically batching is only
|
||||
# really necessary on large models and large geometry removals.
|
||||
self.is_batch = total_elements > 500000 and total_polygons > 2000
|
||||
if self.is_batch:
|
||||
return context.window_manager.invoke_props_dialog(self)
|
||||
elif self.confirm:
|
||||
return context.window_manager.invoke_confirm(self, event)
|
||||
elif self.confirm:
|
||||
return context.window_manager.invoke_confirm(self, event)
|
||||
self.confirm = True
|
||||
return self.execute(context)
|
||||
|
||||
def draw(self, context):
|
||||
row = self.layout.row()
|
||||
row.label(text="Warning: Faster deletion will use more memory.", icon="ERROR")
|
||||
row = self.layout.row()
|
||||
row.prop(self, "is_batch", text="Enable Faster Deletion")
|
||||
|
||||
def _execute(self, context):
|
||||
if self.is_batch:
|
||||
ifcopenshell.util.element.batch_remove_deep2(tool.Ifc.get())
|
||||
for obj in context.selected_objects:
|
||||
if tool.Ifc.get_entity(obj):
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if element:
|
||||
if ifcopenshell.util.element.get_pset(element, "BBIM_Array"):
|
||||
self.report({"INFO"}, "Elements that are part of an array cannot be deleted.")
|
||||
return {"FINISHED"}
|
||||
tool.Geometry.delete_ifc_object(obj)
|
||||
else:
|
||||
bpy.data.objects.remove(obj)
|
||||
if self.is_batch:
|
||||
old_file = tool.Ifc.get()
|
||||
old_file.end_transaction()
|
||||
new_file = ifcopenshell.util.element.unbatch_remove_deep2(tool.Ifc.get())
|
||||
new_file.begin_transaction()
|
||||
tool.Ifc.set(new_file)
|
||||
self.transaction_data = {"old_file": old_file, "new_file": new_file}
|
||||
IfcStore.add_transaction_operation(self)
|
||||
# Required otherwise gizmos are still visible
|
||||
context.view_layer.objects.active = None
|
||||
return {"FINISHED"}
|
||||
|
||||
def rollback(self, data):
|
||||
tool.Ifc.set(data["old_file"])
|
||||
data["old_file"].undo()
|
||||
|
||||
def commit(self, data):
|
||||
data["old_file"].redo()
|
||||
tool.Ifc.set(data["new_file"])
|
||||
|
||||
|
||||
class OverrideOutlinerDelete(bpy.types.Operator):
|
||||
bl_idname = "bim.override_outliner_delete"
|
||||
@@ -519,6 +565,8 @@ class OverrideDuplicateMove(bpy.types.Operator):
|
||||
collection.objects.link(new_obj)
|
||||
obj.select_set(False)
|
||||
new_obj.select_set(True)
|
||||
# clear object's collection so it will be able to have it's own
|
||||
new_obj.BIMObjectProperties.collection = None
|
||||
# Copy the actual class
|
||||
new = blenderbim.core.root.copy_class(tool.Ifc, tool.Collector, tool.Geometry, tool.Root, obj=new_obj)
|
||||
if new:
|
||||
@@ -731,13 +779,17 @@ class OverrideModeSetEdit(bpy.types.Operator):
|
||||
if not obj:
|
||||
continue
|
||||
|
||||
if not obj.data:
|
||||
obj.select_set(False)
|
||||
continue
|
||||
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element:
|
||||
continue
|
||||
|
||||
# We are switching from OBJECT to EDIT mode.
|
||||
usage_type = tool.Model.get_usage_type(element)
|
||||
if usage_type:
|
||||
if usage_type is not None and usage_type not in ("PROFILE", "LAYER3"):
|
||||
# Parametric objects shall not be edited as meshes as they
|
||||
# can be modified to be incompatible with the parametric
|
||||
# constraints.
|
||||
@@ -748,6 +800,55 @@ class OverrideModeSetEdit(bpy.types.Operator):
|
||||
if not representation:
|
||||
continue
|
||||
|
||||
if (
|
||||
tool.Pset.get_element_pset(element, "BBIM_Door")
|
||||
or tool.Pset.get_element_pset(element, "BBIM_Window")
|
||||
or tool.Pset.get_element_pset(element, "BBIM_Stair")
|
||||
):
|
||||
obj.select_set(False)
|
||||
continue
|
||||
|
||||
if usage_type == "PROFILE":
|
||||
if len(context.selected_objects) == 1:
|
||||
bpy.ops.bim.hotkey(hotkey="A_E", description="")
|
||||
return {"FINISHED"}
|
||||
else:
|
||||
self.report({"INFO"}, "Only a single profile-based representation can be edited at a time.")
|
||||
obj.select_set(False)
|
||||
continue
|
||||
|
||||
# TODO: refactor repetitive code
|
||||
if tool.Pset.get_element_pset(element, "BBIM_Roof"):
|
||||
if len(context.selected_objects) == 1:
|
||||
bpy.ops.bim.enable_editing_roof_path()
|
||||
return {"FINISHED"}
|
||||
else:
|
||||
self.report({"INFO"}, "Only a single profile-based representation can be edited at a time.")
|
||||
obj.select_set(False)
|
||||
continue
|
||||
|
||||
if tool.Pset.get_element_pset(element, "BBIM_Railing"):
|
||||
if len(context.selected_objects) == 1:
|
||||
bpy.ops.bim.enable_editing_railing_path()
|
||||
return {"FINISHED"}
|
||||
else:
|
||||
self.report({"INFO"}, "Only a single profile-based representation can be edited at a time.")
|
||||
obj.select_set(False)
|
||||
continue
|
||||
|
||||
if (
|
||||
tool.Geometry.is_profile_based(obj.data)
|
||||
or usage_type == "LAYER3"
|
||||
or tool.Geometry.is_swept_profile(representation)
|
||||
):
|
||||
if len(context.selected_objects) == 1:
|
||||
bpy.ops.bim.hotkey(hotkey="S_E", description="")
|
||||
return {"FINISHED"}
|
||||
else:
|
||||
self.report({"INFO"}, "Only a single profile-based representation can be edited at a time.")
|
||||
obj.select_set(False)
|
||||
continue
|
||||
|
||||
if tool.Geometry.is_meshlike(representation):
|
||||
if getattr(element, "HasOpenings", None):
|
||||
# Mesh elements with openings must disable openings
|
||||
@@ -859,7 +960,22 @@ class OverrideModeSetObject(bpy.types.Operator):
|
||||
if not element:
|
||||
continue
|
||||
|
||||
if obj.data.BIMMeshProperties.ifc_definition_id:
|
||||
if tool.Profile.is_editing_profile():
|
||||
profile_id = context.scene.BIMProfileProperties.active_profile_id
|
||||
if profile_id:
|
||||
profile = tool.Ifc.get().by_id(profile_id)
|
||||
if tool.Ifc.get_object(profile): # We are editing an arbitrary profile
|
||||
bpy.ops.bim.edit_arbitrary_profile()
|
||||
elif tool.Pset.get_element_pset(element, "BBIM_Railing"):
|
||||
bpy.ops.bim.finish_editing_railing_path()
|
||||
elif tool.Pset.get_element_pset(element, "BBIM_Roof"):
|
||||
bpy.ops.bim.finish_editing_roof_path()
|
||||
elif tool.Model.get_usage_type(element) == "PROFILE":
|
||||
bpy.ops.bim.edit_extrusion_axis()
|
||||
else:
|
||||
bpy.ops.bim.edit_extrusion_profile()
|
||||
return self.execute(context)
|
||||
elif obj.data.BIMMeshProperties.ifc_definition_id:
|
||||
if not tool.Geometry.has_geometric_data(obj):
|
||||
self.is_valid = False
|
||||
self.should_save = False
|
||||
|
||||
@@ -24,10 +24,14 @@ from blenderbim.bim.module.geometry.data import RepresentationsData, Connections
|
||||
|
||||
|
||||
def object_menu(self, context):
|
||||
self.layout.separator()
|
||||
self.layout.operator("bim.override_object_duplicate_move", icon="PLUGIN")
|
||||
self.layout.operator("bim.override_object_delete", icon="PLUGIN")
|
||||
self.layout.operator("bim.override_paste_buffer", icon="PLUGIN")
|
||||
|
||||
def outliner_menu(self, context):
|
||||
self.layout.separator()
|
||||
self.layout.operator("bim.override_outliner_delete", icon='X')
|
||||
|
||||
class BIM_PT_representations(Panel):
|
||||
bl_label = "IFC Representations"
|
||||
|
||||
@@ -33,6 +33,7 @@ classes = (
|
||||
operator.ConvertGlobalToLocal,
|
||||
operator.GetCursorLocation,
|
||||
operator.SetCursorLocation,
|
||||
operator.ConvertAngleToCoordinates,
|
||||
prop.BIMGeoreferenceProperties,
|
||||
ui.BIM_PT_gis,
|
||||
ui.BIM_PT_gis_utilities,
|
||||
|
||||
@@ -171,3 +171,19 @@ class ConvertGlobalToLocal(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
def _execute(self, context):
|
||||
core.convert_global_to_local(tool.Georeference)
|
||||
|
||||
class ConvertAngleToCoordinates(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.convert_angle_to_coord"
|
||||
bl_label = "Convert Angle To Y Axis"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Convert angle to Y axis"
|
||||
type: bpy.props.StringProperty()
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
file = tool.Ifc.get()
|
||||
props = context.scene.BIMGeoreferenceProperties
|
||||
return file and (props.angle_degree_input_x or props.angle_degree_input_y)
|
||||
|
||||
def _execute(self, context):
|
||||
core.convert_angle_to_coord(tool.Georeference, type=self.type)
|
||||
|
||||
@@ -37,6 +37,12 @@ class BIMGeoreferenceProperties(PropertyGroup):
|
||||
projected_crs: CollectionProperty(name="Projected CRS", type=Attribute)
|
||||
coordinate_input: StringProperty(name="Coordinate Input", description='Formatted "x,y,z" (without quotes)')
|
||||
coordinate_output: StringProperty(name="Coordinate Output", description='Formatted "x,y,z" (without quotes)')
|
||||
angle_degree_input_x: FloatProperty(name="Angle Degree Input", description="Angle (in degrees) rel to Easting")
|
||||
angle_degree_input_y: FloatProperty(name="Angle Degree Input", description="Angle (in degrees) rel to +Y")
|
||||
x_axis_abscissa_output: StringProperty(name="X Axis Abscissa Ordinate Output", description="X axis abscissa and ordinate", )
|
||||
x_axis_ordinate_output: StringProperty(name="X Axis Abscissa Ordinate Output", description="X axis abscissa and ordinate", )
|
||||
y_axis_abscissa_output: StringProperty(name="Y Axis Abscissa Ordinate Output", description="Y axis abscissa and ordinate", )
|
||||
y_axis_ordinate_output: StringProperty(name="Y Axis Abscissa Ordinate Output", description="Y axis abscissa and ordinate", )
|
||||
has_blender_offset: BoolProperty(name="Has Blender Offset")
|
||||
blender_eastings: StringProperty(name="Blender Eastings", default="0")
|
||||
blender_northings: StringProperty(name="Blender Northings", default="0")
|
||||
|
||||
@@ -167,3 +167,21 @@ class BIM_PT_gis_utilities(Panel):
|
||||
row = self.layout.row(align=True)
|
||||
row.operator("bim.convert_local_to_global", text="Local to Global")
|
||||
row.operator("bim.convert_global_to_local", text="Global to Local")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Orientation Calculator", icon="TRACKING_REFINE_FORWARDS")
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(props, "angle_degree_input_x", text="Angle (°) rel to Easting")
|
||||
row.operator("bim.convert_angle_to_coord", text="", icon="FILE_REFRESH").type = "rel_x"
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(props, "x_axis_abscissa_output", text="XAxis Abscissa")
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(props, "x_axis_ordinate_output", text="XAxis Ordinate")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(props, "angle_degree_input_y", text="Angle (°) rel to to +Y")
|
||||
row.operator("bim.convert_angle_to_coord", text="", icon="FILE_REFRESH").type = "rel_y"
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(props, "y_axis_abscissa_output", text="North Abscissa")
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(props, "y_axis_ordinate_output", text="North Ordinate")
|
||||
@@ -33,6 +33,7 @@ classes = (
|
||||
operator.ToggleGroup,
|
||||
operator.UnassignGroup,
|
||||
operator.UpdateGroup,
|
||||
operator.SelectGroupElements,
|
||||
prop.ExpandedGroups,
|
||||
prop.Group,
|
||||
prop.BIMGroupProperties,
|
||||
|
||||
@@ -275,3 +275,19 @@ class UpdateGroup(bpy.types.Operator, tool.Ifc.Operator):
|
||||
)
|
||||
bpy.ops.bim.load_groups()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class SelectGroupElements(bpy.types.Operator):
|
||||
bl_idname = "bim.select_group_elements"
|
||||
bl_label = "Select Group elements"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
group: bpy.props.IntProperty()
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return bool(tool.Ifc.get() and context.active_object)
|
||||
|
||||
def execute(self, context):
|
||||
elements = tool.Drawing.get_group_elements(tool.Ifc.get().by_id(self.group))
|
||||
tool.Spatial.select_products(elements)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -107,6 +107,7 @@ class BIM_PT_object_groups(Panel):
|
||||
for group in ObjectGroupsData.data["groups"]:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=group["name"])
|
||||
row.operator("bim.select_group_elements", text="", icon="RESTRICT_SELECT_OFF").group = group["id"]
|
||||
op = row.operator("bim.unassign_group", text="", icon="X")
|
||||
op.group = group["id"]
|
||||
|
||||
|
||||
@@ -21,18 +21,28 @@ from . import ui, prop, operator
|
||||
|
||||
classes = (
|
||||
operator.AddFileToRepo,
|
||||
operator.AddRemote,
|
||||
operator.AddTag,
|
||||
operator.CloneRepo,
|
||||
operator.CommitChanges,
|
||||
operator.CreateRepo,
|
||||
operator.DeleteRemote,
|
||||
operator.DeleteTag,
|
||||
operator.DiscardUncommitted,
|
||||
operator.DisplayRevision,
|
||||
operator.DisplayUncommitted,
|
||||
operator.Fetch,
|
||||
operator.Merge,
|
||||
operator.ObjectLog,
|
||||
operator.Push,
|
||||
operator.RefreshGit,
|
||||
operator.SwitchRevision,
|
||||
prop.IfcGitTag,
|
||||
prop.IfcGitListItem,
|
||||
prop.IfcGitProperties,
|
||||
ui.IFCGIT_PT_panel,
|
||||
ui.COMMIT_UL_List,
|
||||
ui.IFCGIT_PT_revision_inspector,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -15,17 +15,29 @@ class IfcGitData:
|
||||
data = {}
|
||||
is_loaded = False
|
||||
|
||||
@classmethod
|
||||
def make_sure_is_loaded(cls):
|
||||
if not cls.is_loaded:
|
||||
cls.load()
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.data = {
|
||||
"repo": cls.repo(),
|
||||
"remotes": cls.remotes(),
|
||||
"branch_names": cls.branch_names(),
|
||||
"remote_names": cls.remote_names(),
|
||||
"remote_urls": cls.remote_urls(),
|
||||
"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(),
|
||||
"working_dir": cls.working_dir(),
|
||||
"untracked_files": cls.untracked_files(),
|
||||
"is_detached": cls.is_detached(),
|
||||
"active_branch_name": cls.active_branch_name(),
|
||||
"is_dirty": cls.is_dirty(),
|
||||
"commit": cls.commit(),
|
||||
"current_revision": cls.current_revision(),
|
||||
@@ -42,10 +54,28 @@ class IfcGitData:
|
||||
return tool.IfcGit.repo_from_path(path_ifc)
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def remotes(cls):
|
||||
if cls.repo():
|
||||
return cls.repo().remotes
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def branch_names(cls):
|
||||
return []
|
||||
|
||||
@classmethod
|
||||
def remote_names(cls):
|
||||
return []
|
||||
|
||||
@classmethod
|
||||
def remote_urls(cls):
|
||||
result = {}
|
||||
if cls.repo():
|
||||
for remote in cls.repo().remotes:
|
||||
result[remote.name] = remote.url
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
def path_ifc(cls):
|
||||
path_ifc = tool.Ifc.get_path()
|
||||
@@ -92,6 +122,27 @@ class IfcGitData:
|
||||
return os.path.basename(path_ifc)
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def working_dir(cls):
|
||||
if cls.repo():
|
||||
return cls.repo().working_dir
|
||||
|
||||
@classmethod
|
||||
def untracked_files(cls):
|
||||
if cls.repo():
|
||||
return cls.repo().untracked_files
|
||||
return []
|
||||
|
||||
@classmethod
|
||||
def is_detached(cls):
|
||||
if cls.repo():
|
||||
return cls.repo().head.is_detached
|
||||
|
||||
@classmethod
|
||||
def active_branch_name(cls):
|
||||
if cls.repo() and not cls.is_detached():
|
||||
return cls.repo().active_branch.name
|
||||
|
||||
@classmethod
|
||||
def is_dirty(cls):
|
||||
if cls.repo() and cls.git_exe():
|
||||
|
||||
@@ -15,8 +15,9 @@ class CreateRepo(bpy.types.Operator):
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
IfcGitData.make_sure_is_loaded()
|
||||
path_ifc = IfcGitData.data["path_ifc"]
|
||||
if not os.path.isfile(path_ifc):
|
||||
if not path_ifc or not os.path.isfile(path_ifc):
|
||||
return False
|
||||
if IfcGitData.data["repo"]:
|
||||
# repo already exists
|
||||
@@ -42,8 +43,9 @@ class AddFileToRepo(bpy.types.Operator):
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
IfcGitData.make_sure_is_loaded()
|
||||
path_ifc = IfcGitData.data["path_ifc"]
|
||||
if not os.path.isfile(path_ifc):
|
||||
if not path_ifc or not os.path.isfile(path_ifc):
|
||||
return False
|
||||
if not IfcGitData.data["repo"]:
|
||||
# repo doesn't exist
|
||||
@@ -57,6 +59,34 @@ class AddFileToRepo(bpy.types.Operator):
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class CloneRepo(bpy.types.Operator):
|
||||
"""Clone a remote Git repository"""
|
||||
|
||||
bl_label = "Clone repository"
|
||||
bl_idname = "ifcgit.clone_repo"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
props = context.scene.IfcGitProperties
|
||||
if (
|
||||
props.remote_url
|
||||
and props.local_folder
|
||||
and os.path.isdir(props.local_folder)
|
||||
and not os.listdir(props.local_folder)
|
||||
):
|
||||
return True
|
||||
return False
|
||||
|
||||
def execute(self, context):
|
||||
|
||||
props = context.scene.IfcGitProperties
|
||||
core.clone_repo(tool.IfcGit, props.remote_url, props.local_folder, self)
|
||||
props.remote_url = ""
|
||||
refresh()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class DiscardUncommitted(bpy.types.Operator):
|
||||
"""Discard saved changes and update to HEAD"""
|
||||
|
||||
@@ -80,6 +110,7 @@ class CommitChanges(bpy.types.Operator):
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
IfcGitData.make_sure_is_loaded()
|
||||
props = context.scene.IfcGitProperties
|
||||
repo = IfcGitData.data["repo"]
|
||||
if props.commit_message == "":
|
||||
@@ -101,7 +132,54 @@ class CommitChanges(bpy.types.Operator):
|
||||
def execute(self, context):
|
||||
|
||||
repo = IfcGitData.data["repo"]
|
||||
core.commit_changes(tool.IfcGit, tool.Ifc, repo, context)
|
||||
core.commit_changes(tool.IfcGit, tool.Ifc, repo)
|
||||
bpy.ops.ifcgit.refresh()
|
||||
refresh()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AddTag(bpy.types.Operator):
|
||||
"""Tag selected revision"""
|
||||
|
||||
bl_label = "Tag selected revision"
|
||||
bl_idname = "ifcgit.add_tag"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
IfcGitData.make_sure_is_loaded()
|
||||
props = context.scene.IfcGitProperties
|
||||
if props.new_tag_name == "":
|
||||
return False
|
||||
repo = IfcGitData.data["repo"]
|
||||
if repo and (
|
||||
not tool.IfcGit.is_valid_ref_format(props.new_tag_name)
|
||||
or props.new_tag_name in [tag.name for tag in repo.tags]
|
||||
):
|
||||
return False
|
||||
return True
|
||||
|
||||
def execute(self, context):
|
||||
|
||||
repo = IfcGitData.data["repo"]
|
||||
core.add_tag(tool.IfcGit, repo)
|
||||
bpy.ops.ifcgit.refresh()
|
||||
refresh()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class DeleteTag(bpy.types.Operator):
|
||||
"""Delete a tag"""
|
||||
|
||||
bl_label = "Delete tag"
|
||||
bl_idname = "ifcgit.delete_tag"
|
||||
bl_options = {"REGISTER"}
|
||||
tag_name: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
|
||||
repo = IfcGitData.data["repo"]
|
||||
core.delete_tag(tool.IfcGit, repo, self.tag_name)
|
||||
bpy.ops.ifcgit.refresh()
|
||||
refresh()
|
||||
return {"FINISHED"}
|
||||
@@ -116,8 +194,9 @@ class RefreshGit(bpy.types.Operator):
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
IfcGitData.make_sure_is_loaded()
|
||||
repo = IfcGitData.data["repo"]
|
||||
if repo != None and repo.heads:
|
||||
if repo:
|
||||
return True
|
||||
return False
|
||||
|
||||
@@ -136,9 +215,15 @@ class DisplayRevision(bpy.types.Operator):
|
||||
bl_idname = "ifcgit.display_revision"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
props = context.scene.IfcGitProperties
|
||||
if props.ifcgit_commits:
|
||||
return True
|
||||
|
||||
def execute(self, context):
|
||||
|
||||
core.colourise_revision(tool.IfcGit, context)
|
||||
core.colourise_revision(tool.IfcGit)
|
||||
refresh()
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -165,6 +250,12 @@ class SwitchRevision(bpy.types.Operator):
|
||||
bl_idname = "ifcgit.switch_revision"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
props = context.scene.IfcGitProperties
|
||||
if props.ifcgit_commits:
|
||||
return True
|
||||
|
||||
def execute(self, context):
|
||||
|
||||
core.switch_revision(tool.IfcGit, tool.Ifc)
|
||||
@@ -181,7 +272,9 @@ class Merge(bpy.types.Operator):
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
if IfcGitData.data["ifcmerge_exe"]:
|
||||
IfcGitData.make_sure_is_loaded()
|
||||
props = context.scene.IfcGitProperties
|
||||
if IfcGitData.data["ifcmerge_exe"] and props.ifcgit_commits and not IfcGitData.data["is_detached"]:
|
||||
return True
|
||||
return False
|
||||
|
||||
@@ -192,3 +285,103 @@ class Merge(bpy.types.Operator):
|
||||
return {"FINISHED"}
|
||||
else:
|
||||
return {"CANCELLED"}
|
||||
|
||||
|
||||
class Push(bpy.types.Operator):
|
||||
"""Pushes the working branch to selected remote"""
|
||||
|
||||
bl_label = "Push working branch"
|
||||
bl_idname = "ifcgit.push"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
def execute(self, context):
|
||||
|
||||
props = context.scene.IfcGitProperties
|
||||
repo = IfcGitData.data["repo"]
|
||||
core.push(tool.IfcGit, repo, props.select_remote, self)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class Fetch(bpy.types.Operator):
|
||||
"""Fetches from the selected remote"""
|
||||
|
||||
bl_label = "Fetch from remote"
|
||||
bl_idname = "ifcgit.fetch"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
def execute(self, context):
|
||||
|
||||
props = context.scene.IfcGitProperties
|
||||
repo = IfcGitData.data["repo"]
|
||||
remote = repo.remotes[props.select_remote]
|
||||
remote.fetch()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AddRemote(bpy.types.Operator):
|
||||
"""Add a remote repository"""
|
||||
|
||||
bl_label = "Add Remote"
|
||||
bl_idname = "ifcgit.add_remote"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
IfcGitData.make_sure_is_loaded()
|
||||
props = context.scene.IfcGitProperties
|
||||
repo = IfcGitData.data["repo"]
|
||||
if (
|
||||
not repo
|
||||
or not tool.IfcGit.is_valid_ref_format(props.remote_name)
|
||||
or not props.remote_url
|
||||
or props.remote_name in [remote.name for remote in repo.remotes]
|
||||
):
|
||||
return False
|
||||
return True
|
||||
|
||||
def execute(self, context):
|
||||
|
||||
repo = IfcGitData.data["repo"]
|
||||
core.add_remote(tool.IfcGit, repo)
|
||||
bpy.ops.ifcgit.refresh()
|
||||
refresh()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class DeleteRemote(bpy.types.Operator):
|
||||
"""Delete the selected remote"""
|
||||
|
||||
bl_label = "Delete Remote"
|
||||
bl_idname = "ifcgit.delete_remote"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
def execute(self, context):
|
||||
|
||||
repo = IfcGitData.data["repo"]
|
||||
core.delete_remote(tool.IfcGit, repo)
|
||||
bpy.ops.ifcgit.refresh()
|
||||
refresh()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class ObjectLog(bpy.types.Operator):
|
||||
"""Displays Git log of selected object"""
|
||||
|
||||
bl_label = "Selected Object History"
|
||||
bl_idname = "ifcgit.object_log"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
if not context.active_object:
|
||||
cls.poll_message_set("No Active Object")
|
||||
elif not context.active_object.BIMObjectProperties.ifc_definition_id:
|
||||
cls.poll_message_set("Active Object doesn't have an IFC definition")
|
||||
else:
|
||||
return True
|
||||
|
||||
def execute(self, context):
|
||||
|
||||
step_id = context.active_object.BIMObjectProperties.ifc_definition_id
|
||||
core.entity_log(tool.IfcGit, tool.Ifc, step_id, self)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -7,7 +7,7 @@ from bpy.props import (
|
||||
IntProperty,
|
||||
EnumProperty,
|
||||
)
|
||||
from blenderbim.bim.module.ifcgit.data import IfcGitData, refresh
|
||||
from blenderbim.bim.module.ifcgit.data import IfcGitData
|
||||
|
||||
|
||||
def git_branches(self, context):
|
||||
@@ -21,9 +21,26 @@ def git_branches(self, context):
|
||||
IfcGitData.data["branch_names"].remove("main")
|
||||
IfcGitData.data["branch_names"] = ["main"] + IfcGitData.data["branch_names"]
|
||||
|
||||
if IfcGitData.data["remotes"]:
|
||||
for remote in IfcGitData.data["remotes"]:
|
||||
for remote_branch in remote.refs:
|
||||
IfcGitData.data["branch_names"].append(remote_branch.name)
|
||||
|
||||
return [(myname, myname, myname) for myname in IfcGitData.data["branch_names"]]
|
||||
|
||||
|
||||
def git_remotes(self, context):
|
||||
"""remotes enum"""
|
||||
|
||||
IfcGitData.data["remote_names"] = sorted([remote.name for remote in IfcGitData.data["remotes"]])
|
||||
|
||||
if "origin" in IfcGitData.data["remote_names"]:
|
||||
IfcGitData.data["remote_names"].remove("origin")
|
||||
IfcGitData.data["remote_names"] = ["origin"] + IfcGitData.data["remote_names"]
|
||||
|
||||
return [(myname, myname, myname) for myname in IfcGitData.data["remote_names"]]
|
||||
|
||||
|
||||
def update_revlist(self, context):
|
||||
"""wrapper to trigger update of the revision list"""
|
||||
|
||||
@@ -32,6 +49,19 @@ def update_revlist(self, context):
|
||||
props.commit_index = 0
|
||||
|
||||
|
||||
class IfcGitTag(PropertyGroup):
|
||||
"""Properties of a Git tag"""
|
||||
|
||||
name: StringProperty(
|
||||
name="Tag name",
|
||||
default="",
|
||||
)
|
||||
message: StringProperty(
|
||||
name="Tag message",
|
||||
default="",
|
||||
)
|
||||
|
||||
|
||||
class IfcGitListItem(PropertyGroup):
|
||||
"""Group of properties representing an item in the list."""
|
||||
|
||||
@@ -45,6 +75,19 @@ class IfcGitListItem(PropertyGroup):
|
||||
description="does this commit reference our ifc file",
|
||||
default=False,
|
||||
)
|
||||
author_name: StringProperty(
|
||||
name="Author Name",
|
||||
default="",
|
||||
)
|
||||
author_email: StringProperty(
|
||||
name="Author Email",
|
||||
default="",
|
||||
)
|
||||
message: StringProperty(
|
||||
name="Commit Message",
|
||||
default="",
|
||||
)
|
||||
tags: CollectionProperty(type=IfcGitTag, name="List of revision tags")
|
||||
|
||||
|
||||
class IfcGitProperties(PropertyGroup):
|
||||
@@ -61,7 +104,34 @@ class IfcGitProperties(PropertyGroup):
|
||||
description="A short name used to refer to this branch",
|
||||
default="",
|
||||
)
|
||||
new_tag_name: StringProperty(
|
||||
name="New tag name",
|
||||
description="A short name used to refer to this tag",
|
||||
default="",
|
||||
)
|
||||
new_tag_message: StringProperty(
|
||||
name="Tag message (optional)",
|
||||
description="An optional human readable description of this tag",
|
||||
default="",
|
||||
)
|
||||
remote_name: StringProperty(
|
||||
name="New remote name",
|
||||
description="A local name for a remote Git repository",
|
||||
default="",
|
||||
)
|
||||
remote_url: StringProperty(
|
||||
name="Git URL",
|
||||
description="A URL pointing to a Git repository",
|
||||
default="",
|
||||
)
|
||||
local_folder: StringProperty(
|
||||
name="Local folder",
|
||||
description="A local Git repository path",
|
||||
default="",
|
||||
subtype="DIR_PATH",
|
||||
)
|
||||
display_branch: EnumProperty(items=git_branches, update=update_revlist)
|
||||
select_remote: EnumProperty(items=git_remotes)
|
||||
ifcgit_filter: EnumProperty(
|
||||
items=[
|
||||
("all", "All", "All revisions"),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import bpy
|
||||
import time
|
||||
from blenderbim.bim.module.ifcgit.data import IfcGitData, refresh
|
||||
from blenderbim.bim.module.ifcgit.data import IfcGitData
|
||||
|
||||
|
||||
class IFCGIT_PT_panel(bpy.types.Panel):
|
||||
@@ -34,8 +34,8 @@ class IFCGIT_PT_panel(bpy.types.Panel):
|
||||
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.label(text=IfcGitData.data["working_dir"], icon="SYSTEM")
|
||||
if name_ifc in IfcGitData.data["untracked_files"]:
|
||||
row.operator(
|
||||
"ifcgit.addfile",
|
||||
text="Add '" + name_ifc + "' to repository",
|
||||
@@ -54,6 +54,16 @@ class IFCGIT_PT_panel(bpy.types.Panel):
|
||||
else:
|
||||
row.label(text="No Git repository found", icon="SYSTEM")
|
||||
row.label(text="No IFC project saved", icon="FILE")
|
||||
|
||||
box = layout.box()
|
||||
row = box.row()
|
||||
row.label(text="Clone a remote Git repository")
|
||||
row = box.row()
|
||||
row.prop(props, "remote_url")
|
||||
row = box.row()
|
||||
row.prop(props, "local_folder")
|
||||
row = box.row()
|
||||
row.operator("ifcgit.clone_repo", icon="IMPORT")
|
||||
return
|
||||
|
||||
is_dirty = IfcGitData.data["is_dirty"]
|
||||
@@ -69,7 +79,7 @@ class IFCGIT_PT_panel(bpy.types.Panel):
|
||||
row = layout.row()
|
||||
row.prop(props, "commit_message")
|
||||
|
||||
if IfcGitData.data["repo"].head.is_detached:
|
||||
if IfcGitData.data["is_detached"]:
|
||||
row = layout.row()
|
||||
row.label(text="HEAD is detached, commit will create a branch", icon="ERROR")
|
||||
row.prop(props, "new_branch_name")
|
||||
@@ -78,10 +88,10 @@ class IFCGIT_PT_panel(bpy.types.Panel):
|
||||
row.operator("ifcgit.commit_changes", icon="GREASEPENCIL")
|
||||
|
||||
row = layout.row()
|
||||
if IfcGitData.data["repo"].head.is_detached:
|
||||
if IfcGitData.data["is_detached"]:
|
||||
row.label(text="Working branch: Detached HEAD")
|
||||
else:
|
||||
row.label(text="Working branch: " + IfcGitData.data["repo"].active_branch.name)
|
||||
row.label(text="Working branch: " + IfcGitData.data["active_branch_name"])
|
||||
|
||||
grouped = layout.row()
|
||||
column = grouped.column()
|
||||
@@ -110,8 +120,6 @@ class IFCGIT_PT_panel(bpy.types.Panel):
|
||||
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="")
|
||||
|
||||
@@ -119,7 +127,6 @@ class IFCGIT_PT_panel(bpy.types.Panel):
|
||||
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")
|
||||
@@ -127,11 +134,50 @@ class IFCGIT_PT_panel(bpy.types.Panel):
|
||||
box = layout.box()
|
||||
column = box.column(align=True)
|
||||
row = column.row()
|
||||
row.label(text=commit.hexsha)
|
||||
row.label(text=item.hexsha)
|
||||
row = column.row()
|
||||
row.label(text=commit.author.name + " <" + commit.author.email + ">")
|
||||
row = column.row()
|
||||
row.label(text=commit.message)
|
||||
row.label(text=item.author_name + " <" + item.author_email + ">")
|
||||
for message_line in item.message.split("\n"):
|
||||
row = column.row()
|
||||
row.label(text=message_line)
|
||||
|
||||
for tag in item.tags:
|
||||
box = layout.box()
|
||||
item = box.row()
|
||||
column = item.column(align=True)
|
||||
row = column.row()
|
||||
row.label(text=tag.name)
|
||||
row.operator("ifcgit.delete_tag", icon="PANEL_CLOSE").tag_name = tag.name
|
||||
if tag.message:
|
||||
for message_line in tag.message.split("\n"):
|
||||
row = column.row()
|
||||
row.label(text=message_line)
|
||||
|
||||
box = layout.box()
|
||||
row = box.row()
|
||||
row.prop(props, "new_tag_name")
|
||||
row = box.row()
|
||||
row.prop(props, "new_tag_message")
|
||||
row = box.row()
|
||||
row.operator("ifcgit.add_tag", icon="GREASEPENCIL")
|
||||
|
||||
if IfcGitData.data["remotes"]:
|
||||
row = layout.row()
|
||||
row.prop(props, "select_remote", text="Select remote")
|
||||
urls = IfcGitData.data["remote_urls"]
|
||||
row.label(text=urls[props.select_remote])
|
||||
row.operator("ifcgit.delete_remote", text="", icon="PANEL_CLOSE")
|
||||
row = layout.row()
|
||||
row.operator("ifcgit.push", icon="EXPORT")
|
||||
row.operator("ifcgit.fetch", icon="IMPORT")
|
||||
|
||||
box = layout.box()
|
||||
row = box.row()
|
||||
row.prop(props, "remote_name")
|
||||
row = box.row()
|
||||
row.prop(props, "remote_url")
|
||||
row = box.row()
|
||||
row.operator("ifcgit.add_remote", icon="ADD")
|
||||
|
||||
|
||||
class COMMIT_UL_List(bpy.types.UIList):
|
||||
@@ -160,7 +206,36 @@ class COMMIT_UL_List(bpy.types.UIList):
|
||||
refs += "{" + tag.name + "} "
|
||||
|
||||
if commit == current_revision:
|
||||
layout.label(text="[HEAD] " + refs + commit.message, icon="DECORATE_KEYFRAME")
|
||||
layout.label(text="[HEAD] " + refs + commit.message.split("\n")[0], icon="DECORATE_KEYFRAME")
|
||||
else:
|
||||
layout.label(text=refs + commit.message, icon="DECORATE_ANIMATE")
|
||||
layout.label(text=refs + commit.message.split("\n")[0], icon="DECORATE_ANIMATE")
|
||||
layout.label(text=time.strftime("%c", time.localtime(commit.committed_date)))
|
||||
|
||||
|
||||
class IFCGIT_PT_revision_inspector(bpy.types.Panel):
|
||||
"""Tool panel to interact with revision history"""
|
||||
|
||||
bl_idname = "IFCGIT_PT_revision_inspector"
|
||||
bl_label = "Git History"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "VIEW_3D"
|
||||
bl_region_type = "UI"
|
||||
bl_category = "BlenderBIM"
|
||||
|
||||
def draw(self, context):
|
||||
|
||||
if not IfcGitData.is_loaded:
|
||||
IfcGitData.load()
|
||||
|
||||
layout = self.layout
|
||||
|
||||
if not IfcGitData.data["git_exe"]:
|
||||
row = layout.row()
|
||||
row.label(text="Git is not installed", icon="ERROR")
|
||||
return
|
||||
|
||||
row = layout.row()
|
||||
row.operator(
|
||||
"ifcgit.object_log",
|
||||
icon="TEXT",
|
||||
)
|
||||
|
||||
@@ -31,13 +31,16 @@ classes = (
|
||||
operator.ContractMaterialCategory,
|
||||
operator.CopyMaterial,
|
||||
operator.DisableEditingAssignedMaterial,
|
||||
operator.DisableEditingMaterial,
|
||||
operator.DisableEditingMaterials,
|
||||
operator.DisableEditingMaterialSetItem,
|
||||
operator.DisableEditingMaterialSetItemProfile,
|
||||
operator.DisableEditingMaterials,
|
||||
operator.EditAssignedMaterial,
|
||||
operator.EditMaterial,
|
||||
operator.EditMaterialSetItem,
|
||||
operator.EditMaterialSetItemProfile,
|
||||
operator.EnableEditingAssignedMaterial,
|
||||
operator.EnableEditingMaterial,
|
||||
operator.EnableEditingMaterialSetItem,
|
||||
operator.EnableEditingMaterialSetItemProfile,
|
||||
operator.ExpandMaterialCategory,
|
||||
|
||||
@@ -96,6 +96,7 @@ class ObjectMaterialData:
|
||||
cls.data["total_thickness"] = cls.total_thickness()
|
||||
cls.data["materials"] = cls.materials()
|
||||
cls.data["type_material"] = cls.type_material()
|
||||
cls.data["material_type"] = cls.material_type()
|
||||
cls.is_loaded = True
|
||||
|
||||
@classmethod
|
||||
@@ -244,3 +245,17 @@ class ObjectMaterialData:
|
||||
else:
|
||||
name_attr = "Name"
|
||||
return getattr(material, name_attr, "Unnamed") or "Unnamed"
|
||||
|
||||
@classmethod
|
||||
def material_type(cls):
|
||||
material_types = [
|
||||
"IfcMaterial",
|
||||
"IfcMaterialConstituentSet",
|
||||
"IfcMaterialLayerSet",
|
||||
"IfcMaterialProfileSet",
|
||||
"IfcMaterialList",
|
||||
]
|
||||
version = tool.Ifc.get_schema()
|
||||
if version == "IFC2X3":
|
||||
material_types = ["IfcMaterial", "IfcMaterialLayerSet", "IfcMaterialList"]
|
||||
return [(m, m, ifcopenshell.util.doc.get_entity_doc(version, m).get("description", "")) for m in material_types]
|
||||
|
||||
@@ -56,7 +56,37 @@ class SelectByMaterial(bpy.types.Operator, tool.Ifc.Operator):
|
||||
material: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
core.select_by_material(tool.Material, material=tool.Ifc.get().by_id(self.material))
|
||||
core.select_by_material(tool.Material, tool.Spatial, material=tool.Ifc.get().by_id(self.material))
|
||||
|
||||
|
||||
class EnableEditingMaterial(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.enable_editing_material"
|
||||
bl_label = "Enable Editing Material"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
material: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
core.enable_editing_material(tool.Material, material=tool.Ifc.get().by_id(self.material))
|
||||
|
||||
|
||||
class EditMaterial(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.edit_material"
|
||||
bl_label = "Edit Material"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
material: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
core.edit_material(tool.Ifc, tool.Material, material=tool.Ifc.get().by_id(self.material))
|
||||
|
||||
|
||||
class DisableEditingMaterial(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.disable_editing_material"
|
||||
bl_label = "Disable Editing Material"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
material: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
core.disable_editing_material(tool.Material)
|
||||
|
||||
|
||||
class AssignParameterizedProfile(bpy.types.Operator, tool.Ifc.Operator):
|
||||
@@ -144,59 +174,7 @@ class AssignMaterial(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
def _execute(self, context):
|
||||
objects = [bpy.data.objects.get(self.obj)] if self.obj else tool.Blender.get_selected_objects()
|
||||
active_obj = context.active_object
|
||||
active_object_material_type = self.material_type or active_obj.BIMObjectMaterialProperties.material_type
|
||||
material = tool.Ifc.get().by_id(int(active_obj.BIMObjectMaterialProperties.material))
|
||||
for obj in objects:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element:
|
||||
continue
|
||||
ifcopenshell.api.run(
|
||||
"material.assign_material",
|
||||
tool.Ifc.get(),
|
||||
product=element,
|
||||
type=active_object_material_type,
|
||||
material=material,
|
||||
)
|
||||
assigned_material = ifcopenshell.util.element.get_material(element)
|
||||
if assigned_material.is_a("IfcMaterialLayerSet"):
|
||||
if not assigned_material.MaterialLayers:
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
layer = ifcopenshell.api.run(
|
||||
"material.add_layer",
|
||||
tool.Ifc.get(),
|
||||
layer_set=assigned_material,
|
||||
material=tool.Ifc.get().by_type("IfcMaterial")[0],
|
||||
)
|
||||
thickness = 0.1 # Arbitrary metric thickness for now
|
||||
layer.LayerThickness = thickness / unit_scale
|
||||
elif assigned_material.is_a("IfcMaterialProfileSet"):
|
||||
if not assigned_material.MaterialProfiles:
|
||||
named_profiles = [p for p in tool.Ifc.get().by_type("IfcProfileDef") if p.ProfileName]
|
||||
if named_profiles:
|
||||
profile = named_profiles[0]
|
||||
else:
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
size = 0.5 / unit_scale
|
||||
profile = tool.Ifc.get().create_entity(
|
||||
"IfcRectangleProfileDef",
|
||||
ProfileName="New Profile",
|
||||
ProfileType="AREA",
|
||||
XDim=size,
|
||||
YDim=size,
|
||||
)
|
||||
material_profile = ifcopenshell.api.run(
|
||||
"material.add_profile",
|
||||
tool.Ifc.get(),
|
||||
profile_set=assigned_material,
|
||||
material=tool.Ifc.get().by_type("IfcMaterial")[0],
|
||||
)
|
||||
ifcopenshell.api.run(
|
||||
"material.assign_profile",
|
||||
tool.Ifc.get(),
|
||||
material_profile=material_profile,
|
||||
profile=profile,
|
||||
)
|
||||
core.assign_material(tool.Ifc, tool.Material, material_type= self.material_type , objects=objects )
|
||||
|
||||
|
||||
class UnassignMaterial(bpy.types.Operator, tool.Ifc.Operator):
|
||||
@@ -207,12 +185,7 @@ class UnassignMaterial(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
def _execute(self, context):
|
||||
objects = [bpy.data.objects.get(self.obj)] if self.obj else tool.Blender.get_selected_objects()
|
||||
for obj in objects:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if element:
|
||||
material = ifcopenshell.util.element.get_material(element, should_inherit=False)
|
||||
if "Usage" not in material.is_a():
|
||||
ifcopenshell.api.run("material.unassign_material", tool.Ifc.get(), product=element)
|
||||
core.unassign_material(tool.Ifc, tool.Material, objects=objects )
|
||||
|
||||
|
||||
class AddConstituent(bpy.types.Operator, tool.Ifc.Operator):
|
||||
@@ -243,12 +216,12 @@ class RemoveConstituent(bpy.types.Operator, tool.Ifc.Operator):
|
||||
constituent: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
for inverse in tool.Ifc.get().get_inverse(tool.Ifc.get().by_id(self.constituent)):
|
||||
if inverse.is_a("IfcMaterialConstituentSet") and len(inverse.MaterialConstituents) == 1:
|
||||
return
|
||||
ifcopenshell.api.run(
|
||||
"material.remove_constituent", tool.Ifc.get(), constituent=tool.Ifc.get().by_id(self.constituent)
|
||||
)
|
||||
constituent = tool.Ifc.get().by_id(self.constituent)
|
||||
for material_set in layer.ToMaterialConstituentSet:
|
||||
if len(material_set.MaterialConstituents) == 1:
|
||||
self.report({"ERROR"}, "At least one constituent must exist")
|
||||
return {"ERROR"}
|
||||
ifcopenshell.api.run("material.remove_constituent", tool.Ifc.get(), constituent=constituent)
|
||||
|
||||
|
||||
class AddProfile(bpy.types.Operator, tool.Ifc.Operator):
|
||||
@@ -278,10 +251,12 @@ class RemoveProfile(bpy.types.Operator, tool.Ifc.Operator):
|
||||
profile: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
for inverse in tool.Ifc.get().get_inverse(tool.Ifc.get().by_id(self.profile)):
|
||||
if inverse.is_a("IfcMaterialProfileSet") and len(inverse.MaterialProfiles) == 1:
|
||||
return
|
||||
ifcopenshell.api.run("material.remove_profile", tool.Ifc.get(), profile=tool.Ifc.get().by_id(self.profile))
|
||||
profile = tool.Ifc.get().by_id(self.profile)
|
||||
for material_set in profile.ToMaterialProfileSet:
|
||||
if len(material_set.MaterialProfiles) == 1:
|
||||
self.report({"ERROR"}, "At least one profile must exist")
|
||||
return {"ERROR"}
|
||||
ifcopenshell.api.run("material.remove_profile", tool.Ifc.get(), profile=profile)
|
||||
|
||||
|
||||
class AddLayer(bpy.types.Operator, tool.Ifc.Operator):
|
||||
@@ -294,7 +269,7 @@ class AddLayer(bpy.types.Operator, tool.Ifc.Operator):
|
||||
def _execute(self, context):
|
||||
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
|
||||
self.file = IfcStore.get_file()
|
||||
ifcopenshell.api.run(
|
||||
layer = ifcopenshell.api.run(
|
||||
"material.add_layer",
|
||||
self.file,
|
||||
**{
|
||||
@@ -303,6 +278,10 @@ class AddLayer(bpy.types.Operator, tool.Ifc.Operator):
|
||||
},
|
||||
)
|
||||
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
thickness = 0.1 # Arbitrary metric thickness for now
|
||||
layer.LayerThickness = thickness / unit_scale
|
||||
|
||||
|
||||
class ReorderMaterialSetItem(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.reorder_material_set_item"
|
||||
@@ -336,10 +315,12 @@ class RemoveLayer(bpy.types.Operator, tool.Ifc.Operator):
|
||||
layer: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
for inverse in tool.Ifc.get().get_inverse(tool.Ifc.get().by_id(self.layer)):
|
||||
if inverse.is_a("IfcMaterialLayerSet") and len(inverse.MaterialLayers) == 1:
|
||||
return
|
||||
ifcopenshell.api.run("material.remove_layer", tool.Ifc.get(), layer=tool.Ifc.get().by_id(self.layer))
|
||||
layer = tool.Ifc.get().by_id(self.layer)
|
||||
for material_set in layer.ToMaterialLayerSet:
|
||||
if len(material_set.MaterialLayers) == 1:
|
||||
self.report({"ERROR"}, "At least one layer must exist")
|
||||
return {"ERROR"}
|
||||
ifcopenshell.api.run("material.remove_layer", tool.Ifc.get(), layer=layer)
|
||||
|
||||
|
||||
class AddListItem(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
@@ -85,24 +85,10 @@ def get_materials(self, context):
|
||||
return ObjectMaterialData.data["materials"]
|
||||
|
||||
|
||||
def get_object_material_types(self, context):
|
||||
global materialtypes_enum
|
||||
if len(materialtypes_enum) == 0 and IfcStore.get_file():
|
||||
material_types = [
|
||||
"IfcMaterial",
|
||||
"IfcMaterialConstituentSet",
|
||||
"IfcMaterialLayerSet",
|
||||
"IfcMaterialLayerSetUsage",
|
||||
"IfcMaterialProfileSet",
|
||||
"IfcMaterialProfileSetUsage",
|
||||
"IfcMaterialList",
|
||||
]
|
||||
version = tool.Ifc.get_schema()
|
||||
if version == "IFC2X3":
|
||||
material_types = ["IfcMaterial", "IfcMaterialLayerSet", "IfcMaterialLayerSetUsage", "IfcMaterialList"]
|
||||
materialtypes_enum.clear()
|
||||
materialtypes_enum = [(m, m, get_entity_doc(version, m).get("description", "")) for m in material_types]
|
||||
return materialtypes_enum
|
||||
def get_object_material_type(self, context):
|
||||
if not ObjectMaterialData.is_loaded:
|
||||
ObjectMaterialData.load()
|
||||
return ObjectMaterialData.data["material_type"]
|
||||
|
||||
|
||||
def get_material_types(self, context):
|
||||
@@ -131,10 +117,13 @@ class BIMMaterialProperties(PropertyGroup):
|
||||
materials: CollectionProperty(name="Materials", type=Material)
|
||||
active_material_index: IntProperty(name="Active Material Index")
|
||||
profiles: EnumProperty(items=get_profiles, name="Profiles")
|
||||
active_material_id: IntProperty(name="Active Material ID")
|
||||
material_attributes: CollectionProperty(name="Material Attributes", type=Attribute)
|
||||
editing_material_type = StringProperty(name="Editing Material Type")
|
||||
|
||||
|
||||
class BIMObjectMaterialProperties(PropertyGroup):
|
||||
material_type: EnumProperty(items=get_object_material_types, name="Material Type")
|
||||
material_type: EnumProperty(items=get_object_material_type, name="Material Type")
|
||||
material: EnumProperty(items=get_materials, name="Material")
|
||||
is_editing: BoolProperty(name="Is Editing", default=False)
|
||||
material_set_usage_attributes: CollectionProperty(name="Material Set Usage Attributes", type=Attribute)
|
||||
|
||||
@@ -57,13 +57,21 @@ class BIM_PT_materials(Panel):
|
||||
row.alignment = "RIGHT"
|
||||
|
||||
if self.props.material_type == "IfcMaterial":
|
||||
row.operator("bim.add_material", text="", icon="ADD")
|
||||
if self.props.materials and self.props.active_material_index < len(self.props.materials):
|
||||
material = self.props.materials[self.props.active_material_index]
|
||||
if material.ifc_definition_id:
|
||||
op = row.operator("bim.select_by_material", text="", icon="RESTRICT_SELECT_OFF")
|
||||
op.material = material.ifc_definition_id
|
||||
row.operator("bim.remove_material", text="", icon="X").material = material.ifc_definition_id
|
||||
if self.props.active_material_id:
|
||||
row.operator("bim.edit_material", text="", icon="CHECKMARK").material = material.ifc_definition_id
|
||||
row.operator("bim.disable_editing_material", text="", icon="CANCEL").material = material.ifc_definition_id
|
||||
self.draw_editable_material_attributes_ui()
|
||||
else:
|
||||
row.operator("bim.add_material", text="", icon="ADD")
|
||||
op = row.operator("bim.select_by_material", text="", icon="RESTRICT_SELECT_OFF")
|
||||
op.material = material.ifc_definition_id
|
||||
row.operator("bim.enable_editing_material", text="", icon="GREASEPENCIL").material = material.ifc_definition_id
|
||||
row.operator("bim.remove_material", text="", icon="X").material = material.ifc_definition_id
|
||||
else:
|
||||
row.operator("bim.add_material", text="", icon="ADD")
|
||||
else:
|
||||
row.operator("bim.add_material_set", text="", icon="ADD").set_type = self.props.material_type
|
||||
if self.props.materials and self.props.active_material_index < len(self.props.materials):
|
||||
@@ -76,6 +84,9 @@ class BIM_PT_materials(Panel):
|
||||
self.layout.template_list("BIM_UL_materials", "", self.props, "materials", self.props, "active_material_index")
|
||||
|
||||
|
||||
def draw_editable_material_attributes_ui(self):
|
||||
blenderbim.bim.helper.draw_attributes(self.props.material_attributes, self.layout)
|
||||
|
||||
class BIM_PT_material(Panel):
|
||||
bl_label = "IFC Material"
|
||||
bl_idname = "BIM_PT_material"
|
||||
@@ -144,8 +155,7 @@ class BIM_PT_object_material(Panel):
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
prop_with_search(row, self.props, "material_type", text="")
|
||||
if self.props.material_type == "IfcMaterial" or self.props.material_type == "IfcMaterialList":
|
||||
prop_with_search(row, self.props, "material", text="")
|
||||
prop_with_search(row, self.props, "material", text="")
|
||||
row.operator("bim.assign_material", icon="ADD", text="")
|
||||
|
||||
def draw_material_ui(self):
|
||||
|
||||
@@ -30,7 +30,6 @@ class BIM_PT_misc_utilities(bpy.types.Panel):
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
props = context.scene.BIMMiscProperties
|
||||
|
||||
row = layout.split(factor=0.2, align=True)
|
||||
row.prop(props, "override_colour", text="")
|
||||
row.operator("bim.set_override_colour")
|
||||
@@ -49,6 +48,8 @@ class BIM_PT_misc_utilities(bpy.types.Panel):
|
||||
row.operator("bim.draw_system_arrows")
|
||||
row = layout.row()
|
||||
row.operator("bim.clean_wireframes")
|
||||
row = layout.row()
|
||||
row.operator("bim.patch_non_parametric_mep_segment")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.operator("bim.enable_editing_sketch_extrusion_profile", text="Start Sketching")
|
||||
|
||||
@@ -86,6 +86,7 @@ classes = (
|
||||
profile.ExtendProfile,
|
||||
profile.RecalculateProfile,
|
||||
profile.Rotate90,
|
||||
profile.PatchNonParametricMepSegment,
|
||||
roof.GenerateHippedRoof,
|
||||
slab.DisableEditingExtrusionProfile,
|
||||
slab.DisableEditingSketchExtrusionProfile,
|
||||
@@ -97,6 +98,7 @@ classes = (
|
||||
slab.SetArcIndex,
|
||||
space.GenerateSpace,
|
||||
space.GenerateSpacesFromWalls,
|
||||
space.ToggleSpaceVisibility,
|
||||
prop.BIMModelProperties,
|
||||
prop.BIMArrayProperties,
|
||||
prop.BIMStairProperties,
|
||||
@@ -105,7 +107,6 @@ classes = (
|
||||
prop.BIMDoorProperties,
|
||||
prop.BIMRailingProperties,
|
||||
prop.BIMRoofProperties,
|
||||
ui.BIM_PT_authoring,
|
||||
ui.BIM_PT_array,
|
||||
ui.BIM_PT_stair,
|
||||
ui.BIM_PT_sverchok,
|
||||
@@ -115,6 +116,7 @@ classes = (
|
||||
ui.BIM_PT_roof,
|
||||
ui.LaunchTypeManager,
|
||||
ui.BIM_MT_model,
|
||||
ui.BIM_PT_Grids,
|
||||
grid.BIM_OT_add_object,
|
||||
stair.BIM_OT_add_object,
|
||||
stair.BIM_OT_add_clever_stair,
|
||||
|
||||
@@ -86,9 +86,10 @@ class EnableEditingArray(bpy.types.Operator, tool.Ifc.Operator):
|
||||
data = json.loads(ifcopenshell.util.element.get_pset(element, "BBIM_Array", "Data"))[self.item]
|
||||
props = obj.BIMArrayProperties
|
||||
props.count = data["count"]
|
||||
props.x = data["x"]
|
||||
props.y = data["y"]
|
||||
props.z = data["z"]
|
||||
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
props.x = data["x"] * si_conversion
|
||||
props.y = data["y"] * si_conversion
|
||||
props.z = data["z"] * si_conversion
|
||||
props.use_local_space = data.get("use_local_space", False)
|
||||
props.sync_children = data.get("sync_children", False)
|
||||
props.method = data.get("method", "OFFSET")
|
||||
@@ -106,15 +107,16 @@ class EditArray(bpy.types.Operator, tool.Ifc.Operator):
|
||||
obj = context.active_object
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
props = obj.BIMArrayProperties
|
||||
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
|
||||
pset = ifcopenshell.util.element.get_pset(element, "BBIM_Array")
|
||||
data = json.loads(pset["Data"])
|
||||
data[self.item] = {
|
||||
"children": data[self.item]["children"],
|
||||
"count": props.count,
|
||||
"x": props.x,
|
||||
"y": props.y,
|
||||
"z": props.z,
|
||||
"x": props.x / si_conversion,
|
||||
"y": props.y / si_conversion,
|
||||
"z": props.z / si_conversion,
|
||||
"use_local_space": props.use_local_space,
|
||||
"sync_children": props.sync_children,
|
||||
"method": props.method,
|
||||
@@ -187,6 +189,7 @@ class SelectArrayParent(bpy.types.Operator):
|
||||
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"
|
||||
@@ -202,6 +205,7 @@ class Input3DCursorXArray(bpy.types.Operator):
|
||||
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"
|
||||
@@ -217,6 +221,7 @@ class Input3DCursorYArray(bpy.types.Operator):
|
||||
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"
|
||||
|
||||
@@ -48,15 +48,18 @@ class AuthoringData:
|
||||
cls.is_loaded = True
|
||||
cls.props = bpy.context.scene.BIMModelProperties
|
||||
cls.data["ifc_classes"] = cls.ifc_classes()
|
||||
cls.data["relating_type_id"] = cls.relating_type_id()
|
||||
cls.data["relating_type_id"] = cls.relating_type_id() # only after .ifc_classes()
|
||||
cls.data["type_class"] = cls.type_class()
|
||||
|
||||
# only after .type_class()
|
||||
cls.data["type_predefined_type"] = cls.type_predefined_type()
|
||||
cls.data["total_types"] = cls.total_types()
|
||||
cls.data["total_pages"] = cls.total_pages()
|
||||
cls.data["next_page"] = cls.next_page()
|
||||
cls.data["prev_page"] = cls.prev_page()
|
||||
cls.data["paginated_relating_types"] = cls.paginated_relating_types()
|
||||
cls.data["type_thumbnail"] = cls.type_thumbnail()
|
||||
|
||||
cls.data["type_thumbnail"] = cls.type_thumbnail() # only after .relating_type_id()
|
||||
cls.data["is_voidable_element"] = cls.is_voidable_element()
|
||||
cls.data["has_visible_openings"] = cls.has_visible_openings()
|
||||
cls.data["has_visible_boundaries"] = cls.has_visible_boundaries()
|
||||
@@ -112,6 +115,8 @@ class AuthoringData:
|
||||
def type_thumbnail(cls):
|
||||
if not cls.data["relating_type_id"]:
|
||||
return 0
|
||||
if not tool.Blender.enum_property_has_valid_index(cls.props, "relating_type_id", 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
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
|
||||
|
||||
import bpy
|
||||
from bpy.types import Operator
|
||||
import bmesh
|
||||
from bmesh.types import BMVert, BMFace
|
||||
|
||||
@@ -27,8 +26,6 @@ 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.window import create_bm_window, create_bm_box, update_simple_openings
|
||||
|
||||
from mathutils import Vector, Matrix
|
||||
@@ -44,32 +41,31 @@ def update_door_modifier_representation(context):
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
ifc_file = tool.Ifc.get()
|
||||
sliding_door = "SLIDING" in props.door_type
|
||||
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
|
||||
representation_data = {
|
||||
"operation_type": props.door_type,
|
||||
"overall_height": props.overall_height,
|
||||
"overall_width": props.overall_width,
|
||||
"overall_height": props.overall_height / si_conversion,
|
||||
"overall_width": props.overall_width / si_conversion,
|
||||
"lining_properties": {
|
||||
"LiningDepth": props.lining_depth,
|
||||
"LiningThickness": props.lining_thickness,
|
||||
"LiningOffset": props.lining_offset,
|
||||
"LiningToPanelOffsetX": props.lining_to_panel_offset_x,
|
||||
"LiningToPanelOffsetY": props.lining_to_panel_offset_y,
|
||||
"TransomThickness": props.transom_thickness,
|
||||
"TransomOffset": props.transom_offset,
|
||||
"transomThickness": props.transom_thickness,
|
||||
"TransomOffset": props.transom_offset,
|
||||
"CasingThickness": props.casing_thickness,
|
||||
"CasingDepth": props.casing_depth,
|
||||
"ThresholdThickness": props.threshold_thickness,
|
||||
"ThresholdDepth": props.threshold_depth,
|
||||
"ThresholdOffset": props.threshold_offset,
|
||||
"LiningDepth": props.lining_depth / si_conversion,
|
||||
"LiningThickness": props.lining_thickness / si_conversion,
|
||||
"LiningOffset": props.lining_offset / si_conversion,
|
||||
"LiningToPanelOffsetX": props.lining_to_panel_offset_x / si_conversion,
|
||||
"LiningToPanelOffsetY": props.lining_to_panel_offset_y / si_conversion,
|
||||
"TransomThickness": props.transom_thickness / si_conversion,
|
||||
"TransomOffset": props.transom_offset / si_conversion,
|
||||
"CasingThickness": props.casing_thickness / si_conversion,
|
||||
"CasingDepth": props.casing_depth / si_conversion,
|
||||
"ThresholdThickness": props.threshold_thickness / si_conversion,
|
||||
"ThresholdDepth": props.threshold_depth / si_conversion,
|
||||
"ThresholdOffset": props.threshold_offset / si_conversion,
|
||||
},
|
||||
"panel_properties": {
|
||||
"PanelDepth": props.panel_depth,
|
||||
"PanelDepth": props.panel_depth / si_conversion,
|
||||
"PanelWidth": props.panel_width_ratio,
|
||||
"FrameDepth": props.frame_depth,
|
||||
"FrameThickness": props.frame_thickness,
|
||||
"FrameDepth": props.frame_depth / si_conversion,
|
||||
"FrameThickness": props.frame_thickness / si_conversion,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -132,15 +128,16 @@ def update_door_modifier_representation(context):
|
||||
)
|
||||
|
||||
# type attributes
|
||||
element.OperationType = props.door_type
|
||||
if tool.Ifc.get_schema() != "IFC2X3":
|
||||
element.OperationType = props.door_type
|
||||
|
||||
# occurences attributes
|
||||
occurences = tool.Ifc.get_all_element_occurences(element)
|
||||
for occurence in occurences:
|
||||
occurence.OverallWidth = props.overall_width
|
||||
occurence.OverallHeight = props.overall_height
|
||||
occurence.OverallWidth = props.overall_width / si_conversion
|
||||
occurence.OverallHeight = props.overall_height / si_conversion
|
||||
|
||||
update_simple_openings(element, props.overall_width, props.overall_height)
|
||||
update_simple_openings(element, props.overall_width / si_conversion, props.overall_height / si_conversion)
|
||||
|
||||
|
||||
# TODO: move it out to tools
|
||||
@@ -281,27 +278,26 @@ def create_bm_door_lining(bm, size: Vector, thickness: list, position: Vector =
|
||||
def update_door_modifier_bmesh(context):
|
||||
obj = context.object
|
||||
props = obj.BIMDoorProperties
|
||||
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
|
||||
overall_width = props.overall_width * si_conversion
|
||||
overall_height = props.overall_height * si_conversion
|
||||
overall_width = props.overall_width
|
||||
overall_height = props.overall_height
|
||||
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_depth = props.lining_depth
|
||||
lining_thickness_default = props.lining_thickness
|
||||
lining_offset = props.lining_offset
|
||||
lining_to_panel_offset_x = (
|
||||
props.lining_to_panel_offset_x * si_conversion if not sliding_door else lining_thickness_default
|
||||
props.lining_to_panel_offset_x 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
|
||||
panel_depth = props.panel_depth
|
||||
lining_to_panel_offset_y = props.lining_to_panel_offset_y if not sliding_door else -panel_depth
|
||||
|
||||
transom_thickness = props.transom_thickness * si_conversion / 2
|
||||
transfom_offset = props.transom_offset * si_conversion
|
||||
transom_thickness = props.transom_thickness / 2
|
||||
transfom_offset = props.transom_offset
|
||||
if transom_thickness == 0:
|
||||
transfom_offset = 0
|
||||
window_lining_height = overall_height - transfom_offset - transom_thickness
|
||||
@@ -316,24 +312,24 @@ def update_door_modifier_bmesh(context):
|
||||
side_lining_thickness = side_lining_thickness - panel_lining_overlap_x
|
||||
top_lining_thickness = top_lining_thickness - panel_top_lining_overlap_x
|
||||
|
||||
threshold_thickness = props.threshold_thickness * si_conversion
|
||||
threshold_depth = props.threshold_depth * si_conversion
|
||||
threshold_offset = props.threshold_offset * si_conversion
|
||||
threshold_thickness = props.threshold_thickness
|
||||
threshold_depth = props.threshold_depth
|
||||
threshold_offset = props.threshold_offset
|
||||
threshold_width = overall_width - side_lining_thickness * 2
|
||||
|
||||
casing_thickness = props.casing_thickness * si_conversion
|
||||
casing_depth = props.casing_depth * si_conversion
|
||||
casing_thickness = props.casing_thickness
|
||||
casing_depth = props.casing_depth
|
||||
|
||||
# panel params
|
||||
panel_width = door_opening_width * props.panel_width_ratio
|
||||
frame_depth = props.frame_depth * si_conversion
|
||||
frame_thickness = props.frame_thickness * si_conversion
|
||||
frame_depth = props.frame_depth
|
||||
frame_thickness = props.frame_thickness
|
||||
frame_height = window_lining_height - lining_to_panel_offset_x * 2
|
||||
glass_thickness = 0.01 * si_conversion
|
||||
glass_thickness = 0.01
|
||||
|
||||
# handle dimensions (hardcoded)
|
||||
handle_size = V(120, 40, 20) * 0.001 * si_conversion
|
||||
handle_offset = V(60, 0, 1000) * 0.001 * si_conversion # to the handle center
|
||||
handle_size = V(120, 40, 20) * 0.001
|
||||
handle_offset = V(60, 0, 1000) * 0.001 # to the handle center
|
||||
handle_center_offset = V(handle_size.y / 2, 0, handle_size.z) / 2
|
||||
|
||||
if transfom_offset:
|
||||
@@ -418,7 +414,7 @@ def update_door_modifier_bmesh(context):
|
||||
|
||||
if double_door:
|
||||
# keeping a little space between doors for readibility
|
||||
double_door_offset = 0.001 * si_conversion
|
||||
double_door_offset = 0.001
|
||||
panel_size.x = panel_size.x / 2 - double_door_offset
|
||||
door_verts.extend(create_bm_door_panel(panel_size, panel_position, "LEFT"))
|
||||
|
||||
@@ -488,10 +484,15 @@ class BIM_OT_add_door(bpy.types.Operator, tool.Ifc.Operator):
|
||||
mesh = bpy.data.meshes.new("IfcDoor")
|
||||
obj = bpy.data.objects.new("IfcDoor", mesh)
|
||||
obj.location = spawn_location
|
||||
collection = context.view_layer.active_layer_collection.collection
|
||||
collection.objects.link(obj)
|
||||
|
||||
element = blenderbim.core.root.assign_class(
|
||||
tool.Ifc, tool.Collector, tool.Root, obj=obj, ifc_class="IfcDoor", should_add_representation=False
|
||||
)
|
||||
element.PredefinedType = "DOOR"
|
||||
blenderbim.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj)
|
||||
if tool.Ifc.get_schema() != "IFC2X3":
|
||||
element.PredefinedType = "DOOR"
|
||||
|
||||
bpy.ops.object.select_all(action="DESELECT")
|
||||
bpy.context.view_layer.objects.active = None
|
||||
@@ -512,18 +513,13 @@ class AddDoor(bpy.types.Operator, tool.Ifc.Operator):
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
props = obj.BIMDoorProperties
|
||||
|
||||
if element.is_a() not in ("IfcDoor", "IfcDoorType"):
|
||||
self.report({"ERROR"}, "Object has to be IfcDoor/IfcDoorType type to add a door.")
|
||||
if element.is_a() not in ("IfcDoor", "IfcDoorType", "IfcDoorStyle"):
|
||||
self.report({"ERROR"}, "Object has to be IfcDoor/IfcDoorType/IfcDoorStyle type to add a door.")
|
||||
return {"CANCELLED"}
|
||||
|
||||
# need to make sure all default props will have correct units
|
||||
if not props.door_added_previously:
|
||||
skip_props = ("is_editing", "door_type", "door_added_previously", "panel_width_ratio")
|
||||
convert_property_group_from_si(props, skip_props=skip_props)
|
||||
|
||||
door_data = props.get_general_kwargs()
|
||||
lining_props = props.get_lining_kwargs()
|
||||
panel_props = props.get_panel_kwargs()
|
||||
door_data = props.get_general_kwargs(convert_to_project_units=True)
|
||||
lining_props = props.get_lining_kwargs(convert_to_project_units=True)
|
||||
panel_props = props.get_panel_kwargs(convert_to_project_units=True)
|
||||
|
||||
door_data["lining_properties"] = lining_props
|
||||
door_data["panel_properties"] = panel_props
|
||||
@@ -550,11 +546,13 @@ class CancelEditingDoor(bpy.types.Operator, tool.Ifc.Operator):
|
||||
def _execute(self, context):
|
||||
obj = context.active_object
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
data = json.loads(ifcopenshell.util.element.get_pset(element, "BBIM_Door", "Data"))
|
||||
props = obj.BIMDoorProperties
|
||||
data = json.loads(ifcopenshell.util.element.get_pset(element, "BBIM_Door", "Data"))
|
||||
data.update(data.pop("lining_properties"))
|
||||
data.update(data.pop("panel_properties"))
|
||||
|
||||
# restore previous settings since editing was canceled
|
||||
for prop_name in data:
|
||||
setattr(props, prop_name, data[prop_name])
|
||||
props.set_props_kwargs_from_ifc_data(data)
|
||||
|
||||
body = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
|
||||
blenderbim.core.geometry.switch_representation(
|
||||
@@ -581,9 +579,9 @@ class FinishEditingDoor(bpy.types.Operator, tool.Ifc.Operator):
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
props = obj.BIMDoorProperties
|
||||
|
||||
door_data = props.get_general_kwargs()
|
||||
lining_props = props.get_lining_kwargs()
|
||||
panel_props = props.get_panel_kwargs()
|
||||
door_data = props.get_general_kwargs(convert_to_project_units=True)
|
||||
lining_props = props.get_lining_kwargs(convert_to_project_units=True)
|
||||
panel_props = props.get_panel_kwargs(convert_to_project_units=True)
|
||||
|
||||
door_data["lining_properties"] = lining_props
|
||||
door_data["panel_properties"] = panel_props
|
||||
@@ -612,15 +610,7 @@ class EnableEditingDoor(bpy.types.Operator, tool.Ifc.Operator):
|
||||
data.update(data.pop("panel_properties"))
|
||||
|
||||
# required since we could load pset from .ifc and BIMDoorProperties won't be set
|
||||
for prop_name in data:
|
||||
setattr(props, prop_name, data[prop_name])
|
||||
|
||||
# need to make sure all props that weren't used before
|
||||
# will have correct units
|
||||
skip_props = ("is_editing", "door_type", "door_added_previously", "panel_width_ratio")
|
||||
skip_props += tuple(data.keys())
|
||||
convert_property_group_from_si(props, skip_props=skip_props)
|
||||
|
||||
props.set_props_kwargs_from_ifc_data(data)
|
||||
props.is_editing = 1
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -638,7 +628,6 @@ class RemoveDoor(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
pset = tool.Pset.get_element_pset(element, "BBIM_Door")
|
||||
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), pset=pset)
|
||||
props.door_added_previously = True
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -30,32 +30,20 @@ def add_object(self, context):
|
||||
obj = bpy.data.objects.new("Grid", None)
|
||||
obj.name = "Grid"
|
||||
|
||||
collection = bpy.data.collections.new(obj.name)
|
||||
has_site_collection = False
|
||||
for child in context.view_layer.layer_collection.children:
|
||||
if "IfcProject/" not in child.name:
|
||||
continue
|
||||
for grandchild in child.children:
|
||||
if "IfcSite/" not in grandchild.name:
|
||||
continue
|
||||
has_site_collection = True
|
||||
grandchild.collection.children.link(collection)
|
||||
break
|
||||
if not has_site_collection:
|
||||
context.view_layer.active_layer_collection.collection.children.link(collection)
|
||||
collection = context.view_layer.active_layer_collection.collection
|
||||
site = tool.Ifc.get().by_type("IfcSite")
|
||||
if site:
|
||||
site = site[0]
|
||||
site_obj = tool.Ifc.get_object(site)
|
||||
if site_obj:
|
||||
collection = site_obj.BIMObjectProperties.collection or collection
|
||||
|
||||
collection.objects.link(obj)
|
||||
|
||||
self.file = IfcStore.get_file()
|
||||
if self.file:
|
||||
bpy.ops.bim.assign_class(obj=obj.name, ifc_class="IfcGrid")
|
||||
collection.name = obj.name
|
||||
grid = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
if has_site_collection:
|
||||
site_obj = bpy.data.objects.get(grandchild.name)
|
||||
if site_obj and site_obj.BIMObjectProperties.ifc_definition_id:
|
||||
blenderbim.core.spatial.assign_container(
|
||||
tool.Ifc, tool.Collector, tool.Spatial, structure_obj=site_obj, element_obj=obj
|
||||
)
|
||||
bpy.ops.bim.assign_class(obj=obj.name, ifc_class="IfcGrid")
|
||||
grid = tool.Ifc.get_entity(obj)
|
||||
|
||||
collection = obj.BIMObjectProperties.collection or collection
|
||||
|
||||
axes_collection = bpy.data.collections.new("UAxes")
|
||||
collection.children.link(axes_collection)
|
||||
@@ -73,15 +61,14 @@ def add_object(self, context):
|
||||
|
||||
axes_collection.objects.link(obj)
|
||||
|
||||
if self.file:
|
||||
result = ifcopenshell.api.run(
|
||||
"grid.create_grid_axis",
|
||||
self.file,
|
||||
**{"axis_tag": tag, "uvw_axes": "UAxes", "grid": grid},
|
||||
)
|
||||
IfcStore.link_element(result, obj)
|
||||
ifcopenshell.api.run("grid.create_axis_curve", self.file, **{"axis_curve": obj, "grid_axis": result})
|
||||
obj.BIMObjectProperties.ifc_definition_id = result.id()
|
||||
result = ifcopenshell.api.run(
|
||||
"grid.create_grid_axis",
|
||||
tool.Ifc.get(),
|
||||
**{"axis_tag": tag, "uvw_axes": "UAxes", "grid": grid},
|
||||
)
|
||||
IfcStore.link_element(result, obj)
|
||||
ifcopenshell.api.run("grid.create_axis_curve", tool.Ifc.get(), **{"axis_curve": obj, "grid_axis": result})
|
||||
obj.BIMObjectProperties.ifc_definition_id = result.id()
|
||||
|
||||
axes_collection = bpy.data.collections.new("VAxes")
|
||||
collection.children.link(axes_collection)
|
||||
@@ -99,18 +86,17 @@ def add_object(self, context):
|
||||
|
||||
axes_collection.objects.link(obj)
|
||||
|
||||
if self.file:
|
||||
result = ifcopenshell.api.run(
|
||||
"grid.create_grid_axis",
|
||||
self.file,
|
||||
**{"axis_tag": tag, "uvw_axes": "VAxes", "grid": grid},
|
||||
)
|
||||
IfcStore.link_element(result, obj)
|
||||
ifcopenshell.api.run("grid.create_axis_curve", self.file, **{"axis_curve": obj, "grid_axis": result})
|
||||
obj.BIMObjectProperties.ifc_definition_id = result.id()
|
||||
result = ifcopenshell.api.run(
|
||||
"grid.create_grid_axis",
|
||||
tool.Ifc.get(),
|
||||
**{"axis_tag": tag, "uvw_axes": "VAxes", "grid": grid},
|
||||
)
|
||||
IfcStore.link_element(result, obj)
|
||||
ifcopenshell.api.run("grid.create_axis_curve", tool.Ifc.get(), **{"axis_curve": obj, "grid_axis": result})
|
||||
obj.BIMObjectProperties.ifc_definition_id = result.id()
|
||||
|
||||
|
||||
class BIM_OT_add_object(Operator):
|
||||
class BIM_OT_add_object(Operator, tool.Ifc.Operator):
|
||||
bl_idname = "mesh.add_grid"
|
||||
bl_label = "IFC Grid"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
@@ -122,10 +108,7 @@ class BIM_OT_add_object(Operator):
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return IfcStore.get_file()
|
||||
|
||||
def execute(self, context):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
return tool.Ifc.get()
|
||||
|
||||
def _execute(self, context):
|
||||
add_object(self, context)
|
||||
|
||||
@@ -19,9 +19,12 @@
|
||||
import bpy
|
||||
import gpu
|
||||
import bmesh
|
||||
import shapely
|
||||
import logging
|
||||
import numpy as np
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.shape
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.shape_builder
|
||||
import ifcopenshell.util.representation
|
||||
import blenderbim.tool as tool
|
||||
@@ -215,9 +218,8 @@ class FilledOpeningGenerator:
|
||||
|
||||
def generate_opening_from_filling(self, filling, filling_obj, voided_obj):
|
||||
thickness = voided_obj.dimensions[1] + 0.1 + 0.1
|
||||
ifc_file = tool.Ifc.get()
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(ifc_file)
|
||||
shape_builder = ifcopenshell.util.shape_builder.ShapeBuilder(ifc_file)
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
shape_builder = ifcopenshell.util.shape_builder.ShapeBuilder(tool.Ifc.get())
|
||||
|
||||
profile = None
|
||||
filling_type = ifcopenshell.util.element.get_type(filling)
|
||||
@@ -226,19 +228,40 @@ class FilledOpeningGenerator:
|
||||
filling_type, "Model", "Profile", "ELEVATION_VIEW"
|
||||
)
|
||||
filling_obj = tool.Ifc.get_object(filling_type)
|
||||
context = ifcopenshell.util.representation.get_context(ifc_file, "Model", "Body", "MODEL_VIEW")
|
||||
context = ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Model", "Body", "MODEL_VIEW")
|
||||
|
||||
if profile:
|
||||
curve_3d = ifcopenshell.util.representation.resolve_representation(profile).Items[0]
|
||||
profile = ifcopenshell.util.representation.resolve_representation(profile)
|
||||
|
||||
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
|
||||
def get_curve_2d_from_3d(profile):
|
||||
if len(profile.Items) == 1:
|
||||
curve_3d = profile.Items[0]
|
||||
if tool.Ifc.get_schema() == "IFC2X3":
|
||||
coords = [Vector(p).xz for p in shape_builder.get_polyline_coords(curve_3d)]
|
||||
return shape_builder.polyline(coords, closed=True)
|
||||
# using different algorithm to keep arc segments possible in the future
|
||||
ifc_segments = [shape_builder.deep_copy(s) for s in curve_3d.Segments]
|
||||
ifc_points = tool.Ifc.get().createIfcCartesianPointList2D(
|
||||
[Vector(p).xz for p in curve_3d.Points.CoordList]
|
||||
)
|
||||
return tool.Ifc.get().createIfcIndexedPolyCurve(Points=ifc_points, Segments=ifc_segments)
|
||||
|
||||
settings = ifcopenshell.geom.settings()
|
||||
settings.set(settings.INCLUDE_CURVES, True)
|
||||
geometry = ifcopenshell.geom.create_shape(settings, profile)
|
||||
verts = ifcopenshell.util.shape.get_vertices(geometry)
|
||||
# [0, 2] represents X and Z ordinates
|
||||
verts = [(np.around(v[[0, 2]], decimals=3) / unit_scale).tolist() for v in verts]
|
||||
edges = ifcopenshell.util.shape.get_edges(geometry)
|
||||
|
||||
boundary_lines = [shapely.LineString([verts[v] for v in e]) for e in edges]
|
||||
unioned_boundaries = shapely.union_all(shapely.GeometryCollection(boundary_lines))
|
||||
closed_polygons = shapely.polygonize(boundary_lines)
|
||||
polygon = max(closed_polygons.geoms, key=lambda polygon: polygon.area)
|
||||
return shape_builder.polyline(list(polygon.exterior.coords))
|
||||
|
||||
extrusion = shape_builder.extrude(
|
||||
get_curve_2d_from_3d(curve_3d),
|
||||
get_curve_2d_from_3d(profile),
|
||||
magnitude=thickness / unit_scale,
|
||||
position=Vector([0.0, -0.1 / unit_scale, 0.0]),
|
||||
position_x_axis=Vector((1, 0, 0)),
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
import bpy
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.core.spatial
|
||||
import blenderbim.core.aggregate
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
|
||||
|
||||
@@ -67,7 +68,7 @@ class PieUpdateContainer(bpy.types.Operator):
|
||||
if not obj.BIMObjectProperties.ifc_definition_id:
|
||||
continue
|
||||
for collection in obj.users_collection:
|
||||
spatial_obj = bpy.data.objects.get(collection.name)
|
||||
spatial_obj = collection.BIMCollectionProperties.obj
|
||||
if spatial_obj and spatial_obj.BIMObjectProperties.ifc_definition_id:
|
||||
blenderbim.core.spatial.assign_container(
|
||||
tool.Ifc, tool.Collector, tool.Spatial, structure_obj=spatial_obj, element_obj=obj
|
||||
@@ -86,6 +87,8 @@ class VIEW3D_MT_PIE_bim(bpy.types.Menu):
|
||||
pie.operator("bim.pie_add_opening")
|
||||
pie.operator("bim.pie_update_container")
|
||||
pie.operator("bim.open_pie_class", text="Assign IFC Class")
|
||||
pie.operator("bim.assign_object", text ="Assign Aggregation")
|
||||
pie.operator("bim.unassign_object", text ="Unassign Aggregation")
|
||||
|
||||
|
||||
class VIEW3D_MT_PIE_bim_class(bpy.types.Menu):
|
||||
|
||||
@@ -37,12 +37,6 @@ from . import prop
|
||||
import json
|
||||
|
||||
|
||||
def select_and_activate_single_object(context, obj):
|
||||
bpy.ops.object.select_all(action="DESELECT")
|
||||
context.view_layer.objects.active = obj
|
||||
obj.select_set(True)
|
||||
|
||||
|
||||
class AddEmptyType(bpy.types.Operator, AddObjectHelper):
|
||||
bl_idname = "bim.add_empty_type"
|
||||
bl_label = "Add Empty Type"
|
||||
@@ -52,7 +46,7 @@ class AddEmptyType(bpy.types.Operator, AddObjectHelper):
|
||||
obj = bpy.data.objects.new("TYPEX", None)
|
||||
context.scene.collection.objects.link(obj)
|
||||
context.scene.BIMRootProperties.ifc_product = "IfcElementType"
|
||||
select_and_activate_single_object(context, obj)
|
||||
tool.Blender.select_and_activate_single_object(context, obj)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -97,7 +91,7 @@ class AddConstrTypeInstance(bpy.types.Operator):
|
||||
return {"FINISHED"}
|
||||
elif material and material.is_a("IfcMaterialLayerSet"):
|
||||
if self.generate_layered_element(ifc_class, relating_type):
|
||||
select_and_activate_single_object(context, context.selected_objects[-1])
|
||||
tool.Blender.select_and_activate_single_object(context, context.selected_objects[-1])
|
||||
return {"FINISHED"}
|
||||
if relating_type.is_a("IfcFlowSegmentType") and not relating_type.RepresentationMaps:
|
||||
if mep.MepGenerator(relating_type).generate():
|
||||
@@ -133,9 +127,10 @@ class AddConstrTypeInstance(bpy.types.Operator):
|
||||
obj = bpy.data.objects.new(tool.Model.generate_occurrence_name(relating_type, instance_class), mesh)
|
||||
|
||||
obj.location = context.scene.cursor.location
|
||||
|
||||
collection = context.view_layer.active_layer_collection.collection
|
||||
collection.objects.link(obj)
|
||||
collection_obj = bpy.data.objects.get(collection.name)
|
||||
collection_obj = collection.BIMCollectionProperties.obj
|
||||
|
||||
bpy.ops.bim.assign_class(obj=obj.name, ifc_class=instance_class)
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
@@ -144,6 +139,27 @@ class AddConstrTypeInstance(bpy.types.Operator):
|
||||
# Update required as core.type.assign_type may change obj.data
|
||||
context.view_layer.update()
|
||||
|
||||
if (
|
||||
building_obj
|
||||
and building_element
|
||||
and building_element.is_a() in ["IfcWall", "IfcWallStandardCase", "IfcCovering"]
|
||||
and instance_class in ["IfcWindow", "IfcDoor"]
|
||||
):
|
||||
# Fills should be a sibling to the building element
|
||||
parent = ifcopenshell.util.element.get_aggregate(building_element)
|
||||
if parent:
|
||||
parent_obj = tool.Ifc.get_object(parent)
|
||||
blenderbim.core.aggregate.assign_object(
|
||||
tool.Ifc, tool.Aggregate, tool.Collector, relating_obj=parent_obj, related_obj=obj
|
||||
)
|
||||
else:
|
||||
parent = ifcopenshell.util.element.get_container(building_element)
|
||||
if parent:
|
||||
parent_obj = tool.Ifc.get_object(parent)
|
||||
blenderbim.core.spatial.assign_container(
|
||||
tool.Ifc, tool.Collector, tool.Spatial, structure_obj=parent_obj, element_obj=obj
|
||||
)
|
||||
|
||||
# set occurences properties for the types defined with modifiers
|
||||
if instance_class in ["IfcWindow", "IfcDoor"]:
|
||||
pset_name = f"BBIM_{instance_class[3:]}"
|
||||
@@ -179,7 +195,7 @@ class AddConstrTypeInstance(bpy.types.Operator):
|
||||
if ifc_class == "IfcDoorType" and len(context.selected_objects) >= 1:
|
||||
pass
|
||||
else:
|
||||
select_and_activate_single_object(context, obj)
|
||||
tool.Blender.select_and_activate_single_object(context, obj)
|
||||
return {"FINISHED"}
|
||||
|
||||
@staticmethod
|
||||
@@ -277,8 +293,6 @@ class LoadTypeThumbnails(bpy.types.Operator, tool.Ifc.Operator):
|
||||
offset: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
from PIL import Image, ImageDraw
|
||||
|
||||
if bpy.app.background:
|
||||
return
|
||||
|
||||
@@ -295,106 +309,11 @@ class LoadTypeThumbnails(bpy.types.Operator, tool.Ifc.Operator):
|
||||
offset = 0
|
||||
queue = queue[offset : offset + 9]
|
||||
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
|
||||
while queue:
|
||||
# if bpy.app.is_job_running("RENDER_PREVIEW") does not seem to reflect asset preview generation
|
||||
element = queue.pop()
|
||||
obj = tool.Ifc.get_object(element)
|
||||
|
||||
if not obj:
|
||||
continue # Nothing to process
|
||||
elif AuthoringData.type_thumbnails.get(element.id(), None):
|
||||
continue # Already processed
|
||||
elif obj.preview and obj.preview.icon_id:
|
||||
AuthoringData.type_thumbnails[element.id()] = obj.preview.icon_id
|
||||
continue
|
||||
|
||||
if obj.data:
|
||||
obj.asset_generate_preview()
|
||||
while not obj.preview:
|
||||
pass
|
||||
else:
|
||||
size = 128
|
||||
img = Image.new("RGBA", (size, size))
|
||||
draw = ImageDraw.Draw(img)
|
||||
|
||||
material = ifcopenshell.util.element.get_material(element)
|
||||
if material and material.is_a("IfcMaterialProfileSet"):
|
||||
profile = material.MaterialProfiles[0].Profile
|
||||
tool.Profile.draw_image_for_ifc_profile(draw, profile, size)
|
||||
|
||||
elif material and material.is_a("IfcMaterialLayerSet"):
|
||||
thicknesses = [l.LayerThickness for l in material.MaterialLayers]
|
||||
total_thickness = sum(thicknesses)
|
||||
si_total_thickness = total_thickness * unit_scale
|
||||
if si_total_thickness <= 0.051:
|
||||
width = 10
|
||||
elif si_total_thickness <= 0.11:
|
||||
width = 20
|
||||
elif si_total_thickness <= 0.21:
|
||||
width = 30
|
||||
elif si_total_thickness <= 0.31:
|
||||
width = 40
|
||||
else:
|
||||
width = 50
|
||||
|
||||
height = 100
|
||||
|
||||
is_horizontal = False
|
||||
if element.is_a("IfcSlabType"):
|
||||
is_horizontal = True
|
||||
|
||||
parametric = ifcopenshell.util.element.get_psets(element).get("EPset_Parametric")
|
||||
if parametric:
|
||||
layer_set_direction = parametric.get("LayerSetDirection", None)
|
||||
if layer_set_direction == "AXIS2":
|
||||
is_horizontal = False
|
||||
elif layer_set_direction == "AXIS3":
|
||||
is_horizontal = True
|
||||
|
||||
if is_horizontal:
|
||||
width, height = height, width
|
||||
|
||||
x_offset = (size / 2) - (width / 2)
|
||||
y_offset = (size / 2) - (height / 2)
|
||||
draw.rectangle([x_offset, y_offset, width + x_offset, height + y_offset], outline="white", width=5)
|
||||
current_thickness = 0
|
||||
del thicknesses[-1]
|
||||
for thickness in thicknesses:
|
||||
current_thickness += thickness
|
||||
if element.is_a("IfcSlabType"):
|
||||
y = (current_thickness / total_thickness) * height
|
||||
line = [x_offset, y_offset + y, x_offset + width, y_offset + y]
|
||||
else:
|
||||
x = (current_thickness / total_thickness) * width
|
||||
line = [x_offset + x, y_offset, x_offset + x, y_offset + height]
|
||||
draw.line(line, fill="white", width=2)
|
||||
elif False:
|
||||
# TODO: things like parametric duct segments
|
||||
pass
|
||||
elif not element.RepresentationMaps:
|
||||
# Empties are represented by a generic thumbnail
|
||||
width = height = 100
|
||||
x_offset = (size / 2) - (width / 2)
|
||||
y_offset = (size / 2) - (height / 2)
|
||||
draw.line([x_offset, y_offset, width + x_offset, height + y_offset], fill="white", width=2)
|
||||
draw.line([x_offset, y_offset + height, width + x_offset, y_offset], fill="white", width=2)
|
||||
draw.rectangle([x_offset, y_offset, width + x_offset, height + y_offset], outline="white", width=5)
|
||||
else:
|
||||
draw.line([0, 0, size, size], fill="red", width=2)
|
||||
draw.line([0, size, size, 0], fill="red", width=2)
|
||||
|
||||
pixels = [item for sublist in img.getdata() for item in sublist]
|
||||
|
||||
obj.asset_generate_preview()
|
||||
while not obj.preview:
|
||||
pass
|
||||
|
||||
obj.preview.image_size = size, size
|
||||
obj.preview.image_pixels_float = pixels
|
||||
|
||||
queue.append(element)
|
||||
if tool.Model.update_thumbnail_for_element(element):
|
||||
queue.append(element)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -456,12 +375,6 @@ class MirrorElements(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
obj.matrix_world = newmat
|
||||
|
||||
def copy_obj(self, obj):
|
||||
new = obj.copy()
|
||||
new.data = wall2.data.copy()
|
||||
wall1.users_collection[0].objects.link(wall2)
|
||||
blenderbim.core.root.copy_class(tool.Ifc, tool.Collector, tool.Geometry, tool.Root, obj=wall2)
|
||||
|
||||
|
||||
def generate_box(usecase_path, ifc_file, settings):
|
||||
box_context = ifcopenshell.util.representation.get_context(ifc_file, "Model", "Box", "MODEL_VIEW")
|
||||
|
||||
@@ -28,6 +28,7 @@ import blenderbim.bim.handler
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.core.type
|
||||
import blenderbim.core.geometry
|
||||
import blenderbim.core.material
|
||||
from math import pi, degrees, inf
|
||||
from mathutils import Vector, Matrix, Quaternion
|
||||
from blenderbim.bim.module.geometry.helper import Helper
|
||||
@@ -53,7 +54,7 @@ class DumbProfileGenerator:
|
||||
self.axis_context = ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Model", "Axis", "GRAPH_VIEW")
|
||||
props = bpy.context.scene.BIMModelProperties
|
||||
self.collection = bpy.context.view_layer.active_layer_collection.collection
|
||||
self.collection_obj = bpy.data.objects.get(self.collection.name)
|
||||
self.collection_obj = self.collection.BIMCollectionProperties.obj
|
||||
self.depth = props.extrusion_depth
|
||||
self.rotation = 0
|
||||
self.location = Vector((0, 0, 0))
|
||||
@@ -144,12 +145,21 @@ class DumbProfileRegenerator:
|
||||
objs = []
|
||||
if not profile:
|
||||
return
|
||||
|
||||
element_types = set()
|
||||
for element in self.get_elements_using_profile(profile):
|
||||
obj = tool.Ifc.get_object(element)
|
||||
if obj:
|
||||
objs.append(obj)
|
||||
if element.is_a("IfcElementType"):
|
||||
element_types.add(element)
|
||||
|
||||
DumbProfileRecalculator().recalculate(objs)
|
||||
|
||||
# update related thumbnails
|
||||
for element in self.get_element_types_using_profile(profile):
|
||||
tool.Model.update_thumbnail_for_element(element, refresh=True)
|
||||
|
||||
def regenerate_from_profile(self, usecase_path, ifc_file, settings):
|
||||
self.file = ifc_file
|
||||
objs = []
|
||||
@@ -164,9 +174,10 @@ class DumbProfileRegenerator:
|
||||
|
||||
def get_elements_using_profile(self, profile):
|
||||
results = []
|
||||
for profile_set in [
|
||||
profile_sets = [
|
||||
mp.ToMaterialProfileSet[0] for mp in self.file.get_inverse(profile) if mp.is_a("IfcMaterialProfile")
|
||||
]:
|
||||
]
|
||||
for profile_set in profile_sets:
|
||||
for inverse in self.file.get_inverse(profile_set):
|
||||
if not inverse.is_a("IfcMaterialProfileSetUsage"):
|
||||
continue
|
||||
@@ -180,6 +191,18 @@ class DumbProfileRegenerator:
|
||||
results.extend(rel.RelatedObjects)
|
||||
return results
|
||||
|
||||
def get_element_types_using_profile(self, profile):
|
||||
results = []
|
||||
profile_sets = [
|
||||
mp.ToMaterialProfileSet[0] for mp in self.file.get_inverse(profile) if mp.is_a("IfcMaterialProfile")
|
||||
]
|
||||
for profile_set in profile_sets:
|
||||
for inverse in self.file.get_inverse(profile_set):
|
||||
if not inverse.is_a("IfcRelAssociatesMaterial"):
|
||||
continue
|
||||
results.extend(inverse.RelatedObjects)
|
||||
return results
|
||||
|
||||
def regenerate_from_type(self, usecase_path, ifc_file, settings):
|
||||
obj = tool.Ifc.get_object(settings["related_object"])
|
||||
if not obj or not obj.data or not obj.data.BIMMeshProperties.ifc_definition_id:
|
||||
@@ -857,6 +880,25 @@ class Rotate90(bpy.types.Operator, tool.Ifc.Operator):
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class PatchNonParametricMepSegment(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.patch_non_parametric_mep_segment"
|
||||
bl_label = "Set MEP segment Material Profile"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.active_object
|
||||
|
||||
def _execute(self, context):
|
||||
styles = tool.Geometry.get_styles(context.active_object)
|
||||
blenderbim.core.material.patch_non_parametric_mep_segment(
|
||||
tool.Ifc, tool.Material, tool.Profile, obj=context.active_object
|
||||
)
|
||||
bpy.ops.bim.enable_editing_extrusion_axis()
|
||||
bpy.ops.bim.edit_extrusion_axis()
|
||||
styles = tool.Geometry.get_styles(context.active_object)
|
||||
|
||||
|
||||
class EnableEditingExtrusionAxis(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.enable_editing_extrusion_axis"
|
||||
bl_label = "Enable Editing Extrusion Axis"
|
||||
@@ -927,7 +969,7 @@ class DisableEditingExtrusionAxis(bpy.types.Operator, tool.Ifc.Operator):
|
||||
return context.selected_objects
|
||||
|
||||
def _execute(self, context):
|
||||
return disable_editing_extrusion_axis()
|
||||
return disable_editing_extrusion_axis(context)
|
||||
|
||||
|
||||
class EditExtrusionAxis(bpy.types.Operator, tool.Ifc.Operator):
|
||||
@@ -948,6 +990,10 @@ class EditExtrusionAxis(bpy.types.Operator, tool.Ifc.Operator):
|
||||
depth = (end - start).length
|
||||
z_axis = (end - start).normalized()
|
||||
y_axis = Vector((0, 0, 1))
|
||||
# making sure z_axis != y_axis
|
||||
if z_axis == y_axis:
|
||||
y_axis = Vector((0,1,0))
|
||||
|
||||
x_axis = y_axis.cross(z_axis).normalized()
|
||||
y_axis = z_axis.cross(x_axis).normalized()
|
||||
|
||||
|
||||
@@ -57,8 +57,10 @@ def get_type_predefined_type(self, context):
|
||||
|
||||
def update_ifc_class(self, context):
|
||||
bpy.ops.bim.load_type_thumbnails(ifc_class=self.ifc_class)
|
||||
AuthoringData.data["relating_type_id"] = AuthoringData.relating_types()
|
||||
AuthoringData.data["relating_type_id"] = AuthoringData.relating_type_id()
|
||||
AuthoringData.data["type_thumbnail"] = AuthoringData.type_thumbnail()
|
||||
if not tool.Blender.enum_property_has_valid_index(self, "relating_type_id", AuthoringData.data["relating_type_id"]):
|
||||
self["relating_type_id"] = 0
|
||||
|
||||
|
||||
def update_type_class(self, context):
|
||||
@@ -69,6 +71,10 @@ def update_type_class(self, context):
|
||||
AuthoringData.data["paginated_relating_types"] = AuthoringData.paginated_relating_types()
|
||||
AuthoringData.data["type_predefined_type"] = AuthoringData.type_predefined_type()
|
||||
|
||||
type_class = self.type_class
|
||||
if (type_class, type_class, "") in get_ifc_class(self, context):
|
||||
self.ifc_class = type_class
|
||||
|
||||
|
||||
def update_relating_type_id(self, context):
|
||||
AuthoringData.data["relating_type_id"] = AuthoringData.relating_type_id()
|
||||
@@ -82,7 +88,7 @@ def update_type_page(self, context):
|
||||
class BIMModelProperties(PropertyGroup):
|
||||
ifc_class: bpy.props.EnumProperty(items=get_ifc_class, name="Construction Class", update=update_ifc_class)
|
||||
relating_type_id: bpy.props.EnumProperty(
|
||||
items=get_relating_type_id, name="Construction Type", update=update_relating_type_id
|
||||
items=get_relating_type_id, name="Relating Type", update=update_relating_type_id
|
||||
)
|
||||
icon_id: bpy.props.IntProperty()
|
||||
updating: bpy.props.BoolProperty(default=False)
|
||||
@@ -126,7 +132,7 @@ class BIMModelProperties(PropertyGroup):
|
||||
z: bpy.props.FloatProperty(name="Z", default=0.5)
|
||||
rl1: bpy.props.FloatProperty(name="RL", default=1) # Used for things like walls, doors, flooring, skirting, etc
|
||||
rl2: bpy.props.FloatProperty(name="RL", default=1) # Used for things like windows, other hosted furniture
|
||||
x_angle: bpy.props.FloatProperty(name="X Angle", default=0, subtype="ANGLE")
|
||||
x_angle: bpy.props.FloatProperty(name="X Angle", default=0, subtype="ANGLE", min=0, max=pi / 180 * 89)
|
||||
type_page: bpy.props.IntProperty(name="Type Page", default=1, update=update_type_page)
|
||||
type_template: bpy.props.EnumProperty(
|
||||
items=(
|
||||
@@ -155,9 +161,9 @@ class BIMArrayProperties(PropertyGroup):
|
||||
default=-1, description="Currently edited array index. -1 if not in array editing mode."
|
||||
)
|
||||
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)
|
||||
x: bpy.props.FloatProperty(name="X", default=0, subtype="DISTANCE")
|
||||
y: bpy.props.FloatProperty(name="Y", default=0, subtype="DISTANCE")
|
||||
z: bpy.props.FloatProperty(name="Z", default=0, subtype="DISTANCE")
|
||||
use_local_space: bpy.props.BoolProperty(
|
||||
name="Use Local Space",
|
||||
description="Use local space for array items offset instead of world space",
|
||||
@@ -176,25 +182,25 @@ class BIMArrayProperties(PropertyGroup):
|
||||
|
||||
|
||||
class BIMStairProperties(PropertyGroup):
|
||||
non_si_units_props = ("is_editing", "number_of_treads", "has_top_nib", "stair_type")
|
||||
stair_types = (
|
||||
("CONCRETE", "Concrete", ""),
|
||||
("WOOD/STEEL", "Wood / Steel", ""),
|
||||
("GENERIC", "Generic", ""),
|
||||
)
|
||||
|
||||
stair_added_previously: bpy.props.BoolProperty(default=False)
|
||||
is_editing: bpy.props.IntProperty(default=-1)
|
||||
width: bpy.props.FloatProperty(name="Width", default=1.2, soft_min=0.01)
|
||||
height: bpy.props.FloatProperty(name="Height", default=1.0, soft_min=0.01)
|
||||
width: bpy.props.FloatProperty(name="Width", default=1.2, soft_min=0.01, subtype="DISTANCE")
|
||||
height: bpy.props.FloatProperty(name="Height", default=1.0, soft_min=0.01, subtype="DISTANCE")
|
||||
number_of_treads: bpy.props.IntProperty(name="Number of treads", default=6, soft_min=1)
|
||||
tread_depth: bpy.props.FloatProperty(name="Tread Depth", default=0.25, soft_min=0.01)
|
||||
tread_run: bpy.props.FloatProperty(name="Tread Run", default=0.3, soft_min=0.01)
|
||||
base_slab_depth: bpy.props.FloatProperty(name="Base slab depth", default=0.25, soft_min=0)
|
||||
top_slab_depth: bpy.props.FloatProperty(name="Top slab depth", default=0.25, soft_min=0)
|
||||
tread_depth: bpy.props.FloatProperty(name="Tread Depth", default=0.25, soft_min=0.01, subtype="DISTANCE")
|
||||
tread_run: bpy.props.FloatProperty(name="Tread Run", default=0.3, soft_min=0.01, subtype="DISTANCE")
|
||||
base_slab_depth: bpy.props.FloatProperty(name="Base slab depth", default=0.25, soft_min=0, subtype="DISTANCE")
|
||||
top_slab_depth: bpy.props.FloatProperty(name="Top slab depth", default=0.25, soft_min=0, subtype="DISTANCE")
|
||||
has_top_nib: bpy.props.BoolProperty(name="Has top nib", default=True)
|
||||
stair_type: bpy.props.EnumProperty(name="Stair type", items=stair_types, default="CONCRETE")
|
||||
|
||||
def get_props_kwargs(self):
|
||||
def get_props_kwargs(self, convert_to_project_units=False):
|
||||
stair_kwargs = {
|
||||
"stair_type": self.stair_type,
|
||||
"width": self.width,
|
||||
@@ -204,27 +210,34 @@ class BIMStairProperties(PropertyGroup):
|
||||
}
|
||||
|
||||
if self.stair_type == "CONCRETE":
|
||||
stair_kwargs.update(
|
||||
{
|
||||
"base_slab_depth": self.base_slab_depth,
|
||||
"top_slab_depth": self.top_slab_depth,
|
||||
"has_top_nib": self.has_top_nib,
|
||||
"tread_depth": self.tread_depth,
|
||||
}
|
||||
)
|
||||
return stair_kwargs
|
||||
concrete_props = {
|
||||
"base_slab_depth": self.base_slab_depth,
|
||||
"top_slab_depth": self.top_slab_depth,
|
||||
"has_top_nib": self.has_top_nib,
|
||||
"tread_depth": self.tread_depth,
|
||||
}
|
||||
stair_kwargs.update(concrete_props)
|
||||
|
||||
elif self.stair_type == "WOOD/STEEL":
|
||||
stair_kwargs.update(
|
||||
{
|
||||
"tread_depth": self.tread_depth,
|
||||
}
|
||||
)
|
||||
return stair_kwargs
|
||||
wood_steel_props = {
|
||||
"tread_depth": self.tread_depth,
|
||||
}
|
||||
stair_kwargs.update(wood_steel_props)
|
||||
|
||||
elif self.stair_type == "GENERIC":
|
||||
pass
|
||||
|
||||
if not convert_to_project_units:
|
||||
return stair_kwargs
|
||||
|
||||
stair_kwargs = tool.Model.convert_data_to_project_units(stair_kwargs, self.non_si_units_props)
|
||||
return stair_kwargs
|
||||
|
||||
def set_props_kwargs_from_ifc_data(self, kwargs):
|
||||
kwargs = tool.Model.convert_data_to_si_units(kwargs, self.non_si_units_props)
|
||||
for prop_name in kwargs:
|
||||
setattr(self, prop_name, kwargs[prop_name])
|
||||
|
||||
|
||||
class BIMSverchokProperties(PropertyGroup):
|
||||
node_group: bpy.props.PointerProperty(name="Node Group", type=NodeTree)
|
||||
@@ -232,15 +245,13 @@ class BIMSverchokProperties(PropertyGroup):
|
||||
|
||||
def window_type_prop_update(self, context):
|
||||
number_of_panels, panels_data = self.window_types_panels[self.window_type]
|
||||
|
||||
si_coversion = 1.0 / ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
panels_data = [[v * si_coversion for v in data] for data in panels_data]
|
||||
self.first_mullion_offset, self.second_mullion_offset = panels_data[0]
|
||||
self.first_transom_offset, self.second_transom_offset = panels_data[1]
|
||||
|
||||
|
||||
# default prop values are in mm and converted later
|
||||
class BIMWindowProperties(PropertyGroup):
|
||||
non_si_units_props = ("is_editing", "window_type")
|
||||
window_types = (
|
||||
("SINGLE_PANEL", "SINGLE_PANEL", ""),
|
||||
("DOUBLE_PANEL_HORIZONTAL", "DOUBLE_PANEL_HORIZONTAL", ""),
|
||||
@@ -268,55 +279,67 @@ class BIMWindowProperties(PropertyGroup):
|
||||
}
|
||||
# fmt: on
|
||||
|
||||
window_added_previously: bpy.props.BoolProperty(default=False)
|
||||
is_editing: bpy.props.IntProperty(default=-1)
|
||||
window_type: bpy.props.EnumProperty(
|
||||
name="Window Type", items=window_types, default="SINGLE_PANEL", update=window_type_prop_update
|
||||
)
|
||||
overall_height: bpy.props.FloatProperty(name="Overall Height", default=0.9)
|
||||
overall_width: bpy.props.FloatProperty(name="Overall Width", default=0.6)
|
||||
overall_height: bpy.props.FloatProperty(name="Overall Height", default=0.9, subtype="DISTANCE")
|
||||
overall_width: bpy.props.FloatProperty(name="Overall Width", default=0.6, subtype="DISTANCE")
|
||||
|
||||
# lining properties
|
||||
lining_depth: bpy.props.FloatProperty(name="Lining Depth", default=0.050)
|
||||
lining_thickness: bpy.props.FloatProperty(name="Lining Thickness", default=0.050)
|
||||
lining_offset: bpy.props.FloatProperty(name="Lining Offset", default=0.050)
|
||||
lining_to_panel_offset_x: bpy.props.FloatProperty(name="Lining to Panel Offset X", default=0.025)
|
||||
lining_to_panel_offset_y: bpy.props.FloatProperty(name="Lining to Panel Offset Y", default=0.025)
|
||||
mullion_thickness: bpy.props.FloatProperty(name="Mullion Thickness", default=0.050)
|
||||
lining_depth: bpy.props.FloatProperty(name="Lining Depth", default=0.050, subtype="DISTANCE")
|
||||
lining_thickness: bpy.props.FloatProperty(name="Lining Thickness", default=0.050, subtype="DISTANCE")
|
||||
lining_offset: bpy.props.FloatProperty(name="Lining Offset", default=0.050, subtype="DISTANCE")
|
||||
lining_to_panel_offset_x: bpy.props.FloatProperty(
|
||||
name="Lining to Panel Offset X", default=0.025, subtype="DISTANCE"
|
||||
)
|
||||
lining_to_panel_offset_y: bpy.props.FloatProperty(
|
||||
name="Lining to Panel Offset Y", default=0.025, subtype="DISTANCE"
|
||||
)
|
||||
mullion_thickness: bpy.props.FloatProperty(name="Mullion Thickness", default=0.050, subtype="DISTANCE")
|
||||
first_mullion_offset: bpy.props.FloatProperty(
|
||||
name="First Mullion Offset",
|
||||
description="Distance from the first lining to the first mullion center",
|
||||
default=0.3,
|
||||
subtype="DISTANCE",
|
||||
)
|
||||
second_mullion_offset: bpy.props.FloatProperty(
|
||||
name="Second Mullion Offset",
|
||||
description="Distance from the first lining to the second mullion center",
|
||||
default=0.45,
|
||||
subtype="DISTANCE",
|
||||
)
|
||||
transom_thickness: bpy.props.FloatProperty(name="Transom Thickness", default=0.050)
|
||||
transom_thickness: bpy.props.FloatProperty(name="Transom Thickness", default=0.050, subtype="DISTANCE")
|
||||
first_transom_offset: bpy.props.FloatProperty(
|
||||
name="First Transom Offset",
|
||||
description="Distance from the first lining to the first transom center",
|
||||
default=0.3,
|
||||
subtype="DISTANCE",
|
||||
)
|
||||
second_transom_offset: bpy.props.FloatProperty(
|
||||
name="Second Transom Offset",
|
||||
description="Distance from the first lining to the second transom center",
|
||||
default=0.6,
|
||||
subtype="DISTANCE",
|
||||
)
|
||||
|
||||
# panel properties
|
||||
frame_depth: bpy.props.FloatVectorProperty(name="Frame Depth", size=3, default=[0.035] * 3)
|
||||
frame_thickness: bpy.props.FloatVectorProperty(name="Frame Thickness", size=3, default=[0.035] * 3)
|
||||
frame_depth: bpy.props.FloatVectorProperty(name="Frame Depth", size=3, default=[0.035] * 3, subtype="TRANSLATION")
|
||||
frame_thickness: bpy.props.FloatVectorProperty(
|
||||
name="Frame Thickness", size=3, default=[0.035] * 3, subtype="TRANSLATION"
|
||||
)
|
||||
|
||||
def get_general_kwargs(self):
|
||||
return {
|
||||
def get_general_kwargs(self, convert_to_project_units=False):
|
||||
kwargs = {
|
||||
"window_type": self.window_type,
|
||||
"overall_height": self.overall_height,
|
||||
"overall_width": self.overall_width,
|
||||
}
|
||||
if not convert_to_project_units:
|
||||
return kwargs
|
||||
return tool.Model.convert_data_to_project_units(kwargs, ["window_type"])
|
||||
|
||||
def get_lining_kwargs(self):
|
||||
def get_lining_kwargs(self, convert_to_project_units=False):
|
||||
kwargs = {
|
||||
"lining_depth": self.lining_depth,
|
||||
"lining_thickness": self.lining_thickness,
|
||||
@@ -353,16 +376,27 @@ class BIMWindowProperties(PropertyGroup):
|
||||
if self.window_type in ("TRIPLE_PANEL_HORIZONTAL",):
|
||||
kwargs["second_transom_offset"] = self.second_transom_offset
|
||||
|
||||
return kwargs
|
||||
if not convert_to_project_units:
|
||||
return kwargs
|
||||
return tool.Model.convert_data_to_project_units(kwargs)
|
||||
|
||||
def get_panel_kwargs(self):
|
||||
return {
|
||||
def get_panel_kwargs(self, convert_to_project_units=False):
|
||||
kwargs = {
|
||||
"frame_depth": self.frame_depth,
|
||||
"frame_thickness": self.frame_thickness,
|
||||
}
|
||||
if not convert_to_project_units:
|
||||
return kwargs
|
||||
return tool.Model.convert_data_to_project_units(kwargs)
|
||||
|
||||
def set_props_kwargs_from_ifc_data(self, kwargs):
|
||||
kwargs = tool.Model.convert_data_to_si_units(kwargs, self.non_si_units_props)
|
||||
for prop_name in kwargs:
|
||||
setattr(self, prop_name, kwargs[prop_name])
|
||||
|
||||
|
||||
class BIMDoorProperties(PropertyGroup):
|
||||
non_si_units_props = ("is_editing", "door_type", "panel_width_ratio")
|
||||
door_types = (
|
||||
("SINGLE_SWING_LEFT", "SINGLE_SWING_LEFT", ""),
|
||||
("SINGLE_SWING_RIGHT", "SINGLE_SWING_RIGHT", ""),
|
||||
@@ -374,51 +408,56 @@ class BIMDoorProperties(PropertyGroup):
|
||||
("DOUBLE_DOOR_SLIDING", "DOUBLE_DOOR_SLIDING", ""),
|
||||
)
|
||||
|
||||
door_added_previously: bpy.props.BoolProperty(default=False)
|
||||
is_editing: bpy.props.IntProperty(default=-1)
|
||||
door_type: bpy.props.EnumProperty(name="Door Operation Type", items=door_types, default="SINGLE_SWING_LEFT")
|
||||
overall_height: bpy.props.FloatProperty(name="Overall Height", default=2.0)
|
||||
overall_width: bpy.props.FloatProperty(name="Overall Width", default=0.9)
|
||||
overall_height: bpy.props.FloatProperty(name="Overall Height", default=2.0, subtype="DISTANCE")
|
||||
overall_width: bpy.props.FloatProperty(name="Overall Width", default=0.9, subtype="DISTANCE")
|
||||
|
||||
# lining properties
|
||||
lining_depth: bpy.props.FloatProperty(name="Lining Depth", default=0.050)
|
||||
lining_thickness: bpy.props.FloatProperty(name="Lining Thickness", default=0.050)
|
||||
lining_depth: bpy.props.FloatProperty(name="Lining Depth", default=0.050, subtype="DISTANCE")
|
||||
lining_thickness: bpy.props.FloatProperty(name="Lining Thickness", default=0.050, subtype="DISTANCE")
|
||||
lining_offset: bpy.props.FloatProperty(
|
||||
name="Lining Offset",
|
||||
description="Offset from the outer side of the wall (by Y-axis). "
|
||||
"If present then adding casing is not possible.\n"
|
||||
"`0.025 mm` is good as default value",
|
||||
default=0.0,
|
||||
default=0.0,
|
||||
subtype="DISTANCE"
|
||||
)
|
||||
lining_to_panel_offset_x: bpy.props.FloatProperty(name="Lining to Panel Offset X", default=0.025)
|
||||
lining_to_panel_offset_y: bpy.props.FloatProperty(name="Lining to Panel Offset Y", default=0.025)
|
||||
lining_to_panel_offset_x: bpy.props.FloatProperty(name="Lining to Panel Offset X", default=0.025, subtype="DISTANCE")
|
||||
lining_to_panel_offset_y: bpy.props.FloatProperty(name="Lining to Panel Offset Y", default=0.025, subtype="DISTANCE")
|
||||
|
||||
transom_thickness: bpy.props.FloatProperty(
|
||||
name="Transom Thickness",
|
||||
description="Set values > 0 to add a transom.\n" "`0.050 mm` is good as default value",
|
||||
default=0.000,
|
||||
subtype="DISTANCE"
|
||||
)
|
||||
transom_offset: bpy.props.FloatProperty(
|
||||
name="Transom Offset",
|
||||
description="Distance from the bottom door opening to the beginning of the transom (unlike windows)",
|
||||
default=1.525,
|
||||
subtype="DISTANCE"
|
||||
)
|
||||
|
||||
casing_thickness: bpy.props.FloatProperty(
|
||||
name="Casing Thickness", description="Set values > 0 and LiningOffset = 0 to add a casing.", default=0.075
|
||||
name="Casing Thickness", description="Set values > 0 and LiningOffset = 0 to add a casing.", default=0.075,
|
||||
subtype="DISTANCE"
|
||||
)
|
||||
casing_depth: bpy.props.FloatProperty(name="Casing Depth", default=0.005)
|
||||
casing_depth: bpy.props.FloatProperty(name="Casing Depth", default=0.005, subtype="DISTANCE")
|
||||
|
||||
threshold_thickness: bpy.props.FloatProperty(
|
||||
name="Threshold Thickness", description="Set values > 0 to add a threshold.", default=0.025
|
||||
name="Threshold Thickness", description="Set values > 0 to add a threshold.", default=0.025,
|
||||
subtype="DISTANCE"
|
||||
)
|
||||
threshold_depth: bpy.props.FloatProperty(name="Threshold Depth", default=0.1)
|
||||
threshold_depth: bpy.props.FloatProperty(name="Threshold Depth", default=0.1, subtype="DISTANCE")
|
||||
threshold_offset: bpy.props.FloatProperty(
|
||||
name="Threshold Offset", description="`0.025 mm` is good as default value", default=0.000
|
||||
name="Threshold Offset", description="`0.025 mm` is good as default value", default=0.000,
|
||||
subtype="DISTANCE"
|
||||
)
|
||||
|
||||
# panel properties
|
||||
panel_depth: bpy.props.FloatProperty(name="Panel Depth", default=0.035)
|
||||
panel_depth: bpy.props.FloatProperty(name="Panel Depth", default=0.035, subtype="DISTANCE")
|
||||
panel_width_ratio: bpy.props.FloatProperty(
|
||||
name="Panel Width Ratio",
|
||||
description="Width of this panel, given as ratio " "relative to the total clear opening width of the door",
|
||||
@@ -426,17 +465,20 @@ class BIMDoorProperties(PropertyGroup):
|
||||
soft_min=0,
|
||||
soft_max=1,
|
||||
)
|
||||
frame_thickness: bpy.props.FloatProperty(name="Window Frame Thickness", default=0.035)
|
||||
frame_depth: bpy.props.FloatProperty(name="Window Frame Depth", default=0.035)
|
||||
frame_thickness: bpy.props.FloatProperty(name="Window Frame Thickness", default=0.035, subtype="DISTANCE")
|
||||
frame_depth: bpy.props.FloatProperty(name="Window Frame Depth", default=0.035, subtype="DISTANCE")
|
||||
|
||||
def get_general_kwargs(self):
|
||||
return {
|
||||
def get_general_kwargs(self, convert_to_project_units=False):
|
||||
kwargs = {
|
||||
"door_type": self.door_type,
|
||||
"overall_height": self.overall_height,
|
||||
"overall_width": self.overall_width,
|
||||
}
|
||||
if not convert_to_project_units:
|
||||
return kwargs
|
||||
return tool.Model.convert_data_to_project_units(kwargs, ["door_type"])
|
||||
|
||||
def get_lining_kwargs(self):
|
||||
def get_lining_kwargs(self, convert_to_project_units=False):
|
||||
kwargs = {
|
||||
"lining_depth": self.lining_depth,
|
||||
"lining_thickness": self.lining_thickness,
|
||||
@@ -461,19 +503,36 @@ class BIMDoorProperties(PropertyGroup):
|
||||
kwargs["threshold_depth"] = self.threshold_depth
|
||||
kwargs["threshold_offset"] = self.threshold_offset
|
||||
|
||||
return kwargs
|
||||
if not convert_to_project_units:
|
||||
return kwargs
|
||||
return tool.Model.convert_data_to_project_units(kwargs)
|
||||
|
||||
def get_panel_kwargs(self):
|
||||
def get_panel_kwargs(self, convert_to_project_units=False):
|
||||
kwargs = {"panel_depth": self.panel_depth, "panel_width_ratio": self.panel_width_ratio}
|
||||
|
||||
if self.transom_thickness:
|
||||
kwargs["frame_thickness"] = self.frame_thickness
|
||||
kwargs["frame_depth"] = self.frame_depth
|
||||
|
||||
return kwargs
|
||||
if not convert_to_project_units:
|
||||
return kwargs
|
||||
return tool.Model.convert_data_to_project_units(kwargs, ("panel_width_ratio", ))
|
||||
|
||||
def set_props_kwargs_from_ifc_data(self, kwargs):
|
||||
kwargs = tool.Model.convert_data_to_si_units(kwargs, self.non_si_units_props)
|
||||
for prop_name in kwargs:
|
||||
setattr(self, prop_name, kwargs[prop_name])
|
||||
|
||||
|
||||
class BIMRailingProperties(PropertyGroup):
|
||||
non_si_units_props = (
|
||||
"is_editing",
|
||||
"railing_type",
|
||||
"use_manual_supports",
|
||||
"terminal_type",
|
||||
"path_data",
|
||||
)
|
||||
|
||||
railing_types = (
|
||||
("FRAMELESS_PANEL", "FRAMELESS_PANEL", ""),
|
||||
("WALL_MOUNTED_HANDRAIL", "WALL_MOUNTED_HANDRAIL", ""),
|
||||
@@ -487,14 +546,13 @@ class BIMRailingProperties(PropertyGroup):
|
||||
("NONE", "NONE", ""),
|
||||
)
|
||||
|
||||
railing_added_previously: bpy.props.BoolProperty(default=False)
|
||||
is_editing: bpy.props.IntProperty(default=-1)
|
||||
is_editing_path: bpy.props.BoolProperty(default=False)
|
||||
|
||||
railing_type: bpy.props.EnumProperty(name="Railing Type", items=railing_types, default="FRAMELESS_PANEL")
|
||||
height: bpy.props.FloatProperty(name="Height", default=1.0)
|
||||
thickness: bpy.props.FloatProperty(name="Thickness", default=0.050)
|
||||
spacing: bpy.props.FloatProperty(name="Spacing", default=0.050)
|
||||
height: bpy.props.FloatProperty(name="Height", default=1.0, subtype="DISTANCE")
|
||||
thickness: bpy.props.FloatProperty(name="Thickness", default=0.050, subtype="DISTANCE")
|
||||
spacing: bpy.props.FloatProperty(name="Spacing", default=0.050, subtype="DISTANCE")
|
||||
|
||||
# wall mounted handrail specific properties
|
||||
use_manual_supports: bpy.props.BoolProperty(
|
||||
@@ -512,7 +570,7 @@ class BIMRailingProperties(PropertyGroup):
|
||||
)
|
||||
terminal_type: bpy.props.EnumProperty(name="Terminal Type", items=cap_types, default="180")
|
||||
|
||||
def get_general_kwargs(self):
|
||||
def get_general_kwargs(self, convert_to_project_units=False):
|
||||
base_kwargs = {
|
||||
"railing_type": self.railing_type,
|
||||
"height": self.height,
|
||||
@@ -531,17 +589,34 @@ class BIMRailingProperties(PropertyGroup):
|
||||
"support_spacing": self.support_spacing,
|
||||
"terminal_type": self.terminal_type,
|
||||
}
|
||||
return base_kwargs | additional_kwargs
|
||||
kwargs = base_kwargs | additional_kwargs
|
||||
|
||||
if not convert_to_project_units:
|
||||
return kwargs
|
||||
|
||||
kwargs = tool.Model.convert_data_to_project_units(kwargs, self.non_si_units_props)
|
||||
return kwargs
|
||||
|
||||
def set_props_kwargs_from_ifc_data(self, kwargs):
|
||||
kwargs = tool.Model.convert_data_to_si_units(kwargs, self.non_si_units_props)
|
||||
for prop_name in kwargs:
|
||||
setattr(self, prop_name, kwargs[prop_name])
|
||||
|
||||
class BIMRoofProperties(PropertyGroup):
|
||||
non_si_units_props = (
|
||||
"is_editing",
|
||||
"path_data",
|
||||
"roof_type",
|
||||
"generation_method",
|
||||
"angle",
|
||||
"rafter_edge_angle",
|
||||
)
|
||||
roof_types = (("HIP/GABLE ROOF", "HIP/GABLE ROOF", ""),)
|
||||
roof_generation_methods = (
|
||||
("HEIGHT", "HEIGHT", ""),
|
||||
("ANGLE", "ANGLE", ""),
|
||||
)
|
||||
|
||||
roof_added_previously: bpy.props.BoolProperty(default=False)
|
||||
is_editing: bpy.props.IntProperty(default=-1)
|
||||
is_editing_path: bpy.props.BoolProperty(default=False)
|
||||
|
||||
@@ -553,14 +628,26 @@ class BIMRoofProperties(PropertyGroup):
|
||||
name="Height", default=1.0, description="Maximum height of the roof to be generated.", subtype="DISTANCE"
|
||||
)
|
||||
angle: bpy.props.FloatProperty(name="Slope Angle", default=pi / 18, subtype="ANGLE")
|
||||
roof_thickness: bpy.props.FloatProperty(name="Roof Thickness", default=0.1, subtype="DISTANCE")
|
||||
rafter_edge_angle: bpy.props.FloatProperty(name="Rafter Edge Angle", min=0, max=pi, default=pi / 2, subtype="ANGLE")
|
||||
|
||||
def get_general_kwargs(self):
|
||||
def get_general_kwargs(self, convert_to_project_units=False):
|
||||
kwargs = {
|
||||
"roof_type": self.roof_type,
|
||||
"generation_method": self.generation_method,
|
||||
"roof_thickness": self.roof_thickness,
|
||||
"rafter_edge_angle": self.rafter_edge_angle,
|
||||
}
|
||||
if self.generation_method == "HEIGHT":
|
||||
kwargs["height"] = self.height
|
||||
else:
|
||||
kwargs["angle"] = self.angle
|
||||
return kwargs
|
||||
|
||||
if not convert_to_project_units:
|
||||
return kwargs
|
||||
return tool.Model.convert_data_to_project_units(kwargs, self.non_si_units_props)
|
||||
|
||||
def set_props_kwargs_from_ifc_data(self, kwargs):
|
||||
kwargs = tool.Model.convert_data_to_si_units(kwargs, self.non_si_units_props)
|
||||
for prop_name in kwargs:
|
||||
setattr(self, prop_name, kwargs[prop_name])
|
||||
|
||||
@@ -38,14 +38,6 @@ 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
|
||||
@@ -92,16 +84,18 @@ def update_railing_modifier_ifc_data(context):
|
||||
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"]]
|
||||
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
|
||||
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,
|
||||
"support_spacing": props.support_spacing / si_conversion,
|
||||
"railing_diameter": props.railing_diameter / si_conversion,
|
||||
"clear_width": props.clear_width / si_conversion,
|
||||
"terminal_type": props.terminal_type,
|
||||
"height": props.height,
|
||||
"height": props.height / si_conversion,
|
||||
}
|
||||
model_representation = ifcopenshell.api.run(
|
||||
"geometry.add_railing_representation", ifc_file, **representation_data
|
||||
@@ -153,9 +147,9 @@ def update_railing_modifier_bmesh(context):
|
||||
|
||||
def generate_frameless_panel_railing():
|
||||
# generating FRAMELESS_PANEL railing
|
||||
height = props.height * si_conversion
|
||||
thickness = props.thickness * si_conversion
|
||||
spacing = props.spacing * si_conversion
|
||||
height = props.height
|
||||
thickness = props.thickness
|
||||
spacing = props.spacing
|
||||
|
||||
# spacing
|
||||
# split each edge in 3 segments by 0.5 * spacing by x-y plane
|
||||
@@ -202,9 +196,10 @@ def update_railing_modifier_bmesh(context):
|
||||
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)
|
||||
|
||||
bmesh.ops.recalc_face_normals(bm, faces=bm.faces[:])
|
||||
|
||||
tool.Blender.apply_bmesh(obj.data, bm)
|
||||
|
||||
@@ -247,9 +242,15 @@ def get_path_data(obj):
|
||||
other_edge = lambda edges, edge: next(e for e in edges if e != edge)
|
||||
|
||||
while len(link_edges := v.link_edges) != 1:
|
||||
prev_v = v
|
||||
link_edges = v.link_edges
|
||||
edge = other_edge(link_edges, edge)
|
||||
v = edge.other_vert(v)
|
||||
v = edge.other_vert(prev_v)
|
||||
|
||||
# skip path verts if they just go vertical to avoid errors
|
||||
if (v.co.xy - prev_v.co.xy).length <= 0.0001:
|
||||
continue
|
||||
|
||||
points.append(v.co)
|
||||
segments.append((i - 1, i))
|
||||
i += 1
|
||||
@@ -279,6 +280,9 @@ class BIM_OT_add_railing(bpy.types.Operator, tool.Ifc.Operator):
|
||||
mesh = bpy.data.meshes.new("IfcRailing")
|
||||
obj = bpy.data.objects.new("IfcRailing", mesh)
|
||||
obj.location = spawn_location
|
||||
collection = context.view_layer.active_layer_collection.collection
|
||||
collection.objects.link(obj)
|
||||
|
||||
body_context = ifcopenshell.util.representation.get_context(ifc_file, "Model", "Body", "MODEL_VIEW")
|
||||
blenderbim.core.root.assign_class(
|
||||
tool.Ifc,
|
||||
@@ -313,11 +317,8 @@ class AddRailing(bpy.types.Operator, tool.Ifc.Operator):
|
||||
self.report({"ERROR"}, "Object has to be IfcRailing/IfcRailingType type to add a railing.")
|
||||
return {"CANCELLED"}
|
||||
|
||||
# need to make sure all default props will have correct units
|
||||
if not props.railing_added_previously:
|
||||
convert_property_group_from_si(props, skip_props=NON_SI_RAILING_PROPS)
|
||||
|
||||
railing_data = props.get_general_kwargs()
|
||||
railing_data = props.get_general_kwargs(convert_to_project_units=True)
|
||||
path_data = get_path_data(obj)
|
||||
if not path_data:
|
||||
path_data = {
|
||||
@@ -350,13 +351,7 @@ class EnableEditingRailing(bpy.types.Operator, tool.Ifc.Operator):
|
||||
data["path_data"] = json.dumps(data["path_data"])
|
||||
|
||||
# required since we could load pset from .ifc and BIMRailingProperties won't be set
|
||||
for prop_name in data:
|
||||
setattr(props, prop_name, data[prop_name])
|
||||
|
||||
# need to make sure all props that weren't used before
|
||||
# will have correct units
|
||||
skip_props = NON_SI_RAILING_PROPS + tuple(data.keys())
|
||||
convert_property_group_from_si(props, skip_props=skip_props)
|
||||
props.set_props_kwargs_from_ifc_data(data)
|
||||
|
||||
props.is_editing = 1
|
||||
return {"FINISHED"}
|
||||
@@ -373,8 +368,7 @@ class CancelEditingRailing(bpy.types.Operator, tool.Ifc.Operator):
|
||||
data = json.loads(ifcopenshell.util.element.get_pset(element, "BBIM_Railing", "Data"))
|
||||
props = obj.BIMRailingProperties
|
||||
# restore previous settings since editing was canceled
|
||||
for prop_name in data:
|
||||
setattr(props, prop_name, data[prop_name])
|
||||
props.set_props_kwargs_from_ifc_data(data)
|
||||
|
||||
body = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
|
||||
blenderbim.core.geometry.switch_representation(
|
||||
@@ -405,7 +399,7 @@ class FinishEditingRailing(bpy.types.Operator, tool.Ifc.Operator):
|
||||
RailingData.load()
|
||||
path_data = RailingData.data["parameters"]["data_dict"]["path_data"]
|
||||
|
||||
railing_data = props.get_general_kwargs()
|
||||
railing_data = props.get_general_kwargs(convert_to_project_units=True)
|
||||
railing_data["path_data"] = path_data
|
||||
props.is_editing = -1
|
||||
|
||||
@@ -437,7 +431,7 @@ class FlipRailingPathOrder(bpy.types.Operator, tool.Ifc.Operator):
|
||||
edge = [abs(vi - last_vert_i) for vi in edge[::-1]]
|
||||
edges.append(edge)
|
||||
|
||||
railing_data = props.get_general_kwargs()
|
||||
railing_data = props.get_general_kwargs(convert_to_project_units=True)
|
||||
railing_data["path_data"] = path_data
|
||||
|
||||
update_bbim_railing_pset(element, railing_data)
|
||||
@@ -496,7 +490,7 @@ class FinishEditingRailingPath(bpy.types.Operator, tool.Ifc.Operator):
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
props = obj.BIMRailingProperties
|
||||
|
||||
railing_data = props.get_general_kwargs()
|
||||
railing_data = props.get_general_kwargs(convert_to_project_units=True)
|
||||
path_data = get_path_data(obj)
|
||||
railing_data["path_data"] = path_data
|
||||
ProfileDecorator.uninstall()
|
||||
@@ -524,7 +518,6 @@ class RemoveRailing(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
pset = tool.Pset.get_element_pset(element, "BBIM_Railing")
|
||||
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), pset=pset)
|
||||
props.railing_added_previously = True
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
|
||||
@@ -28,8 +28,9 @@ from blenderbim.bim.module.model.data import RoofData, refresh
|
||||
from blenderbim.bim.module.model.decorator import ProfileDecorator
|
||||
|
||||
import json
|
||||
from math import tan, pi
|
||||
from math import tan, pi, radians
|
||||
from mathutils import Vector, Matrix
|
||||
import mathutils.geometry
|
||||
from bpypolyskel import bpypolyskel
|
||||
import shapely
|
||||
from pprint import pprint
|
||||
@@ -39,9 +40,6 @@ from pprint import pprint
|
||||
# https://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/lexical/IfcRoofType.htm
|
||||
|
||||
|
||||
NON_SI_ROOF_PROPS = ("is_editing", "roof_type", "roof_added_previously", "generation_method", "angle")
|
||||
|
||||
|
||||
def float_is_zero(f):
|
||||
return 0.0001 >= f >= -0.0001
|
||||
|
||||
@@ -87,12 +85,32 @@ class GenerateHippedRoof(bpy.types.Operator, tool.Ifc.Operator):
|
||||
return {"CANCELLED"}
|
||||
|
||||
bm = tool.Blender.get_bmesh_for_mesh(obj.data)
|
||||
op_status, error_message = is_valid_roof_footprint(bm)
|
||||
if error_message:
|
||||
self.report(op_status, error_message)
|
||||
return {"CANCELLED"}
|
||||
|
||||
generate_hiped_roof_bmesh(bm, self.mode, self.height, self.angle)
|
||||
tool.Blender.apply_bmesh(obj.data, bm)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
def generate_hiped_roof_bmesh(bm, mode="ANGLE", height=1.0, angle=pi / 18, mutate_current_bmesh=True):
|
||||
def is_valid_roof_footprint(bm):
|
||||
# should be bmesh to support edit mode
|
||||
bm.verts.ensure_lookup_table()
|
||||
base_z = bm.verts[0].co.z
|
||||
all_verts_same_level = all([float_is_zero(v.co.z - base_z) for v in bm.verts[1:]])
|
||||
if not all_verts_same_level:
|
||||
return (
|
||||
{"ERROR"},
|
||||
"\nAll roof footprint vertices should have same Z-level.\nCurrently Z-level doesn't completely match",
|
||||
)
|
||||
return ({"FINISHED"}, "")
|
||||
|
||||
|
||||
def generate_hiped_roof_bmesh(
|
||||
bm, mode="ANGLE", height=1.0, roof_thickness=0.1, angle=pi / 18, rafter_edge_angle=pi / 2, mutate_current_bmesh=True
|
||||
):
|
||||
"""return bmesh with gable roof geometry
|
||||
|
||||
`mutate_current_bmesh` is a flag to indicate whether the input bmesh
|
||||
@@ -215,9 +233,15 @@ def generate_hiped_roof_bmesh(bm, mode="ANGLE", height=1.0, angle=pi / 18, mutat
|
||||
verts_to_rip = []
|
||||
bottom_chords_to_remove = []
|
||||
|
||||
def is_footprint_vert(v):
|
||||
return float_is_zero(v.co.z - footprint_z)
|
||||
|
||||
def is_footprint_edge(edge):
|
||||
return all(is_footprint_vert(v) for v in edge.verts)
|
||||
|
||||
# find footprint edges
|
||||
for edge in bm.edges:
|
||||
if all(float_is_zero(v.co.z - footprint_z) for v in edge.verts):
|
||||
if is_footprint_edge(edge):
|
||||
footprint_edges.append(edge)
|
||||
footprint_verts.update(edge.verts)
|
||||
|
||||
@@ -229,27 +253,29 @@ 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
|
||||
|
||||
process_later = []
|
||||
for old_edge_verts, defined_angle, separate_verts in original_geometry_data["edges"]:
|
||||
if not defined_angle:
|
||||
continue
|
||||
|
||||
edge_verts_remaped = set(old_verts_remap[old_vert] for old_vert in old_edge_verts)
|
||||
|
||||
identical_edge = None
|
||||
for edge in footprint_edges:
|
||||
if set(edge.verts) == edge_verts_remaped:
|
||||
identical_edge = edge
|
||||
break
|
||||
assert identical_edge
|
||||
|
||||
verts_to_move = find_other_polygon_verts(identical_edge)
|
||||
for v in verts_to_move:
|
||||
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:
|
||||
if separate_verts:
|
||||
verts_to_move = find_other_polygon_verts(identical_edge)
|
||||
for v in verts_to_move:
|
||||
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])
|
||||
else:
|
||||
process_later.append([identical_edge, edge_verts_remaped, defined_angle])
|
||||
|
||||
if defined_angle >= pi / 2:
|
||||
bottom_chords_to_remove.append(identical_edge)
|
||||
@@ -264,20 +290,91 @@ def generate_hiped_roof_bmesh(bm, mode="ANGLE", height=1.0, angle=pi / 18, mutat
|
||||
if cur_edge == edge:
|
||||
continue
|
||||
bmesh.ops.contextual_create(bm, geom=[new_edge, cur_edge])
|
||||
return new_v
|
||||
|
||||
# correct angle for verts that require verts separation
|
||||
# store separated verts for angle correction later
|
||||
related_verts = {}
|
||||
for v, new_co, edge in verts_to_rip:
|
||||
new_v = separate_vert(bm, v, edge, new_co)
|
||||
related_verts.setdefault(v, []).append(new_v)
|
||||
|
||||
# verts angle correction.
|
||||
# we're taking into account new verts created after verts separation
|
||||
# required for asymmetrical gable roof
|
||||
for identical_edge, edge_verts_remaped, defined_angle in process_later:
|
||||
verts_to_move = find_other_polygon_verts(identical_edge)
|
||||
for v in verts_to_move[:]:
|
||||
verts_to_move.extend(related_verts.get(v, []))
|
||||
|
||||
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
|
||||
|
||||
# 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)
|
||||
# add roof thickness
|
||||
extrusion_geom = bm_sort_out_geom(bmesh.ops.extrude_face_region(bm, geom=bm.faces)["geom"])
|
||||
extruded_edges = extrusion_geom["edges"]
|
||||
extruded_verts = extrusion_geom["verts"]
|
||||
rafter_edge_angle = pi / 2 - rafter_edge_angle
|
||||
default_offset_dir = Vector([0, 0, 1]) * roof_thickness
|
||||
footprint_verts = set()
|
||||
|
||||
if not float_is_zero(rafter_edge_angle):
|
||||
footprint_edges = []
|
||||
for edge in extruded_edges:
|
||||
if is_footprint_edge(edge):
|
||||
footprint_edges.append(edge)
|
||||
footprint_verts.update(edge.verts)
|
||||
|
||||
def get_top_vert(v):
|
||||
non_footprint_verts = []
|
||||
for edge in v.link_edges:
|
||||
v1 = edge.other_vert(v)
|
||||
if not is_footprint_vert(v1):
|
||||
non_footprint_verts.append(v1)
|
||||
|
||||
if len(non_footprint_verts) == 1:
|
||||
return non_footprint_verts[0]
|
||||
|
||||
return min(non_footprint_verts, key=lambda v1: (v1.co - v.co).length)
|
||||
|
||||
top_verts = dict()
|
||||
for v in footprint_verts:
|
||||
top_verts[v] = get_top_vert(v)
|
||||
|
||||
# TODO: rafter_edge_angle might differ
|
||||
# if footprint edges are not connected by 90 degrees
|
||||
verts_offsets = dict()
|
||||
for edge in footprint_edges:
|
||||
v0, v1 = edge.verts
|
||||
edge_dir = (v0.co - v1.co).normalized()
|
||||
offset_dir = Matrix.Rotation(rafter_edge_angle, 4, edge_dir) @ default_offset_dir
|
||||
for v in edge.verts:
|
||||
previous_offset = verts_offsets.get(v, None)
|
||||
if previous_offset:
|
||||
previous_offset.xy += offset_dir.xy
|
||||
else:
|
||||
verts_offsets[v] = offset_dir
|
||||
|
||||
for v in verts_offsets:
|
||||
vert_offset = verts_offsets[v]
|
||||
top = top_verts[v].co
|
||||
bot = v.co + default_offset_dir
|
||||
base = v.co
|
||||
offsetted = v.co + vert_offset
|
||||
# all this math is needed to maintain the same roof thickness
|
||||
# for different rafter edge angles
|
||||
v.co = mathutils.geometry.intersect_line_line(bot, top, base, offsetted)[0]
|
||||
|
||||
verts = [v for v in extruded_verts if v not in footprint_verts]
|
||||
bmesh.ops.translate(bm, vec=default_offset_dir, verts=verts)
|
||||
bmesh.ops.recalc_face_normals(bm, faces=bm.faces[:])
|
||||
return bm
|
||||
|
||||
@@ -357,8 +454,15 @@ def update_roof_modifier_bmesh(context):
|
||||
tool.Blender.apply_bmesh(obj.data, bm)
|
||||
return
|
||||
|
||||
height = props.height * si_conversion
|
||||
generate_hiped_roof_bmesh(bm, props.generation_method, height, props.angle, mutate_current_bmesh=True)
|
||||
generate_hiped_roof_bmesh(
|
||||
bm,
|
||||
props.generation_method,
|
||||
props.height,
|
||||
props.roof_thickness,
|
||||
props.angle,
|
||||
props.rafter_edge_angle,
|
||||
mutate_current_bmesh=True,
|
||||
)
|
||||
tool.Blender.apply_bmesh(obj.data, bm)
|
||||
|
||||
|
||||
@@ -410,6 +514,9 @@ class BIM_OT_add_roof(bpy.types.Operator, tool.Ifc.Operator):
|
||||
mesh = bpy.data.meshes.new("IfcRoof")
|
||||
obj = bpy.data.objects.new("IfcRoof", mesh)
|
||||
obj.location = spawn_location
|
||||
collection = context.view_layer.active_layer_collection.collection
|
||||
collection.objects.link(obj)
|
||||
|
||||
body_context = ifcopenshell.util.representation.get_context(ifc_file, "Model", "Body", "MODEL_VIEW")
|
||||
blenderbim.core.root.assign_class(
|
||||
tool.Ifc,
|
||||
@@ -444,10 +551,6 @@ class AddRoof(bpy.types.Operator, tool.Ifc.Operator):
|
||||
self.report({"ERROR"}, "Object has to be IfcRoof/IfcRoofType type to add a roof.")
|
||||
return {"CANCELLED"}
|
||||
|
||||
# need to make sure all default props will have correct units
|
||||
if not props.roof_added_previously:
|
||||
convert_property_group_from_si(props, skip_props=NON_SI_ROOF_PROPS)
|
||||
|
||||
# rejecting original roof shape to be safe
|
||||
# taking into account only it's bounding box dimensions
|
||||
if obj.dimensions.x == 0 or obj.dimensions.y == 0:
|
||||
@@ -462,7 +565,7 @@ class AddRoof(bpy.types.Operator, tool.Ifc.Operator):
|
||||
max_y = bbox["max_y"]
|
||||
min_z = bbox["min_z"]
|
||||
|
||||
roof_data = props.get_general_kwargs()
|
||||
roof_data = props.get_general_kwargs(convert_to_project_units=True)
|
||||
path_data = {
|
||||
"edges": [[0, 1], [1, 2], [2, 3], [3, 0]],
|
||||
"verts": [
|
||||
@@ -491,17 +594,8 @@ class EnableEditingRoof(bpy.types.Operator, tool.Ifc.Operator):
|
||||
props = obj.BIMRoofProperties
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
data = json.loads(ifcopenshell.util.element.get_pset(element, "BBIM_Roof", "Data"))
|
||||
data["path_data"] = json.dumps(data["path_data"])
|
||||
|
||||
# required since we could load pset from .ifc and BIMRoofProperties won't be set
|
||||
for prop_name in data:
|
||||
setattr(props, prop_name, data[prop_name])
|
||||
|
||||
# need to make sure all props that weren't used before
|
||||
# will have correct units
|
||||
skip_props = NON_SI_ROOF_PROPS + tuple(data.keys())
|
||||
convert_property_group_from_si(props, skip_props=skip_props)
|
||||
|
||||
props.set_props_kwargs_from_ifc_data(data)
|
||||
props.is_editing = 1
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -517,8 +611,7 @@ class CancelEditingRoof(bpy.types.Operator, tool.Ifc.Operator):
|
||||
data = json.loads(ifcopenshell.util.element.get_pset(element, "BBIM_Roof", "Data"))
|
||||
props = obj.BIMRoofProperties
|
||||
# restore previous settings since editing was canceled
|
||||
for prop_name in data:
|
||||
setattr(props, prop_name, data[prop_name])
|
||||
props.set_props_kwargs_from_ifc_data(data)
|
||||
|
||||
body = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
|
||||
blenderbim.core.geometry.switch_representation(
|
||||
@@ -549,7 +642,7 @@ class FinishEditingRoof(bpy.types.Operator, tool.Ifc.Operator):
|
||||
RoofData.load()
|
||||
path_data = RoofData.data["parameters"]["data_dict"]["path_data"]
|
||||
|
||||
roof_data = props.get_general_kwargs()
|
||||
roof_data = props.get_general_kwargs(convert_to_project_units=True)
|
||||
roof_data["path_data"] = path_data
|
||||
props.is_editing = -1
|
||||
|
||||
@@ -584,12 +677,21 @@ class EnableEditingRoofPath(bpy.types.Operator, tool.Ifc.Operator):
|
||||
# copying to make sure not to mutate the edit mode bmesh
|
||||
bm = tool.Blender.get_bmesh_for_mesh(obj.data)
|
||||
main_bm = bm.copy()
|
||||
op_status, error_message = is_valid_roof_footprint(main_bm)
|
||||
if error_message:
|
||||
print("Error: %s" % error_message)
|
||||
return main_bm
|
||||
|
||||
main_bm.edges.layers.int.new("BBIM_preview")
|
||||
|
||||
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
height = props.height * si_conversion
|
||||
second_bm = generate_hiped_roof_bmesh(
|
||||
bm, props.generation_method, height, props.angle, mutate_current_bmesh=False
|
||||
bm,
|
||||
props.generation_method,
|
||||
props.height,
|
||||
props.roof_thickness,
|
||||
props.angle,
|
||||
props.rafter_edge_angle,
|
||||
mutate_current_bmesh=False,
|
||||
)
|
||||
bmesh.ops.translate(second_bm, verts=second_bm.verts, vec=Vector((0, 0, 1)))
|
||||
|
||||
@@ -637,7 +739,13 @@ class FinishEditingRoofPath(bpy.types.Operator, tool.Ifc.Operator):
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
props = obj.BIMRoofProperties
|
||||
|
||||
roof_data = props.get_general_kwargs()
|
||||
bm = tool.Blender.get_bmesh_for_mesh(obj.data)
|
||||
op_status, error_message = is_valid_roof_footprint(bm)
|
||||
if error_message:
|
||||
self.report(op_status, error_message)
|
||||
return {"CANCELLED"}
|
||||
|
||||
roof_data = props.get_general_kwargs(convert_to_project_units=True)
|
||||
path_data = get_path_data(obj)
|
||||
roof_data["path_data"] = path_data
|
||||
ProfileDecorator.uninstall()
|
||||
@@ -646,6 +754,7 @@ class FinishEditingRoofPath(bpy.types.Operator, tool.Ifc.Operator):
|
||||
update_bbim_roof_pset(element, roof_data)
|
||||
refresh() # RoofData has to be updated before run update_roof_modifier_bmesh
|
||||
update_roof_modifier_bmesh(context)
|
||||
|
||||
update_roof_modifier_ifc_data(context)
|
||||
if bpy.context.object.mode == "EDIT":
|
||||
bpy.ops.object.mode_set(mode="OBJECT")
|
||||
@@ -666,7 +775,6 @@ class RemoveRoof(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
pset = tool.Pset.get_element_pset(element, "BBIM_Roof")
|
||||
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), pset=pset)
|
||||
props.roof_added_previously = True
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ def sync_name(usecase_path, ifc_file, settings):
|
||||
style = tool.Style.get_style(obj)
|
||||
if style and style != element:
|
||||
style.Name = new_name
|
||||
collection = bpy.data.collections.get(obj.name)
|
||||
collection = obj.BIMObjectProperties.collection
|
||||
if collection:
|
||||
collection.name = new_name
|
||||
obj.name = new_name
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user