Compare commits

...

96 Commits

Author SHA1 Message Date
Chun 2137b34063 Add descriptions to IFC Search function (#1618)
* Added descriptions to IFC Search function

* change color to colour
2021-08-01 19:33:55 +10:00
Dion Moult 86a44c21ac WIP experimental hpp-fcl and aabbtree-based IfcClash 2021-08-01 10:51:11 +10:00
Long a427462646 GlobalId is string (#1615) 2021-07-31 12:39:05 +10:00
Gorgious56 b79d23b3e2 Hide panels with features unsupported in V2x3 (#1614) 2021-07-30 22:13:41 +10:00
Gorgious56 2af1ebc614 Use passed context instead of bpy when available (#1607) 2021-07-30 07:08:17 +10:00
Dion Moult d2e421382b Minor fix 2021-07-29 20:08:51 +10:00
Bruno Postle 7b22f9c85b BlenderBIM some data_dir / schema_dir workarounds (#1606)
* write process.log to a tempdir if data_dir is not writable

* Don't include filename returned by data_dir and schema_dir chooser
2021-07-29 20:07:04 +10:00
Gorgious56 e06591bbbd Prevent linking non-existant or non-ifc file (#1604) 2021-07-29 17:03:53 +10:00
Dion Moult 110daf4ca7 Fix #1592. Material name changes are now auto synced with style names. 2021-07-29 17:03:06 +10:00
Dion Moult 6bf692b44d Fix #1401. Modified custom psets are now immediately available without needing to restart Blender. 2021-07-29 16:16:01 +10:00
bosonprojets c257afbd1c Fix BlenderBIM bug Class assignement for predefined_type attribue 2021-07-29 00:28:04 +01:00
bosonprojets cdf18e9f20 Revert "Fix BlenderBIM bug class assignement when PredefinedType is set as USERDEFINED"
This reverts commit d1e51fb05b.
2021-07-29 00:24:38 +01:00
bosonprojets c715f41972 Fix BlenderBIM bug missing module reference for Pie Menu shortcuts 2021-07-28 23:37:06 +01:00
bosonprojets d1e51fb05b Fix BlenderBIM bug class assignement when PredefinedType is set as USERDEFINED 2021-07-28 15:27:51 +01:00
Gorgious56 99f72a8451 Fix Assign Container operator loses active object (#1601)
* Fix Assign Container operator loses active object

* Fix bad indentation for restoring active object
2021-07-28 21:40:23 +10:00
Dion Moult 2537349ec2 Object style colours are now auto synchronised upon export. See #1585. 2021-07-28 21:01:09 +10:00
Dion Moult 10b6253a36 Fix #1585. Materials are added as a potential if you assign it as an object material too, for convenience. 2021-07-28 19:55:18 +10:00
Dion Moult d88c4fd377 Fix #1597. Prevent mixing objects on project creation. 2021-07-28 18:43:20 +10:00
Dion Moult 0596e2000c New debug utility to purge all Blender IFC links. See #1599. 2021-07-28 18:21:05 +10:00
Dion Moult b685610900 Fix #1599. Bug where removing the last object in a container would have an error. 2021-07-28 18:21:05 +10:00
dedicos 5cab72deaa removed get_property in attributes_psets step file (not implemented yet) (#1596)
* removed get_property in attributes_psets step file (not implemented yet)

problem with stepmatcher remains

* Fix pattern search for properties
2021-07-28 17:51:16 +10:00
Dion Moult 52f15d27b1 Fix bug where import filters didn't properly exclude types and empty objects 2021-07-28 17:48:36 +10:00
Dion Moult cfa0575263 Minor fix 2021-07-27 20:47:49 +10:00
Dion Moult f5e089e0fd Material based surface styles are now supported on import as a style fallback. See #1585. 2021-07-27 20:47:49 +10:00
Dion Moult 5d246a8716 Fix bug where module data was not purged correctly when a new scene was started 2021-07-27 20:47:49 +10:00
Dion Moult 1f241912f2 Make recipe name generic 2021-07-27 20:47:49 +10:00
Carmen Fan f7765c531b GeomIterator is not exposed for use if the library is built dynamically on windows (#1583) 2021-07-27 09:36:44 +02:00
Carmen Fan 3b401ec28f fix hash define logical operation 2021-07-27 09:36:16 +02:00
Dion Moult 77206cb761 Fix #1588. Profiles now auto recalculate cardinal points after editing. 2021-07-27 09:42:42 +10:00
Dion Moult f8e9f81b9b Parametric profiles now auto sync prior to depth changes. See #1588. 2021-07-26 21:13:15 +10:00
Dion Moult 186c4dd118 New experimental serialiser-based drawing generation code. See #1153. See #1564. 2021-07-26 20:24:39 +10:00
Dion Moult aa9b6b61fa Fix bug where material profile data was not purged since a profile module didn't exist. See #1588. 2021-07-26 09:45:46 +10:00
Dion Moult 6337d04efc IfcSverchok API node now actually executes API calls and has descriptive tooltips 2021-07-25 18:42:28 +10:00
Dion Moult c00bcc78e9 Editing voids or dumb walls now auto enable dynamic voids, so openings work well with them. 2021-07-25 14:06:49 +10:00
Dion Moult 07f19ad80a New patch to fix duplicate Revit types. See commit message for details.
Types in Revit are secretly duplicated (or more) if they are mirrored or hosted in another object unbeknownst to the user. So what the user thinks is a single construction type in Revit ends up being multiple IFC types. This leads to incorrect results when someone interrogates the IFC file. This is fundamentally an issue in the way Revit handles element mirroring and hosting, so there is no fix on the Revit side, so this workaround mitigates the issue by doing a shallow merge of the type objects.
2021-07-25 12:00:05 +10:00
Dion Moult 8ca6183313 Fix #169. New recipe to fix duplicate and invalid GlobalIds. 2021-07-25 11:39:11 +10:00
Ryan Schultz 492df6845b cleaned up list of hotkeys a bit (#1586) 2021-07-25 10:29:33 +10:00
Alexander Nitsch 43f8d8547d Bimtester: Fixed attributes_psets step files (#1580)
Co-authored-by: Nitsch Alexander <alexander.nitsch@scs.ch>
2021-07-25 09:57:59 +10:00
Thomas Krijnen 6e3c72707d submodule 2021-07-24 22:25:01 +02:00
Dion Moult 08d6c0a2d8 Openings are now auto dissolved when authoring for easier editing. 2021-07-24 22:08:50 +10:00
Dion Moult 8c1eff5c26 New workspace hotkey to toggle visibility of openings when authoring 2021-07-24 21:50:14 +10:00
Dion Moult fe3ffb7771 Openings are now placed in their own collection in case they accidentally get placed in the spatial tree 2021-07-24 21:35:27 +10:00
Dion Moult 6660714308 New feature to add slab openings 2021-07-23 21:18:43 +10:00
Dion Moult 2420ad2fa7 New feature to add wall openings where your cursor is 2021-07-23 21:10:36 +10:00
Dion Moult 16fd06a4e9 Initial commit for half implemented IfcFM library to support new FM exchange requirements and related FM tasks. Initially just an IfcCOBie replacement but will grow. 2021-07-23 18:03:07 +10:00
Dion Moult beb5dda6dd New FM utility module for selecting maintainable assets according to different FM definitions 2021-07-23 16:10:38 +10:00
Dion Moult c04afe1a4b Minor fix. 2021-07-23 10:41:08 +10:00
Dion Moult d2a1e0193b Fix #1574. Fix #1578. Bug where deprecated functions were used in Python 3.9 for drawing generation. 2021-07-22 20:42:03 +10:00
Thomas Krijnen f2f4057c98 #1579 Fix attribute assignment to enum types 2021-07-22 11:48:27 +02:00
Thomas Krijnen 347aae9696 #1579 regenerate code 2021-07-22 11:48:02 +02:00
Thomas Krijnen 1a2d0d702b #1579 Update templates, thanks @RickBrice 2021-07-22 11:47:47 +02:00
Dion Moult e791449d3d Bump IfcOpenShell. See #1567. 2021-07-22 19:36:31 +10:00
Dion Moult 8071e079b3 New system module with UI to browse systems in an IFC. 2021-07-22 17:40:50 +10:00
Dion Moult 1c71af058f Fix bug where date conversions fail if IFC2X3 seconds have fractions 2021-07-22 13:22:23 +10:00
Dion Moult c4f3633ab8 Property and quantity sets are now shown in alphabetical order to be much less confusing. 2021-07-21 17:05:02 +10:00
Dion Moult c40fd91730 New feature where you can select related objects from an actively selected type. 2021-07-21 10:41:59 +10:00
Dion Moult a7252273b4 Fix bug where searching a GlobalId wouldn't work if you previously used the debug panel. 2021-07-21 10:41:29 +10:00
Dion Moult 6e0a443cca Fix bug where the covetool module didn't detect skylights properly. See #967. 2021-07-21 08:58:57 +10:00
Dion Moult 4574887709 Fix bug where editing multiple objects didn't synchronise changes when exporting. 2021-07-20 23:01:16 +10:00
Dion Moult f14d349be0 Fix bug where psets didn't show up for non element types like 2X3 door styles. 2021-07-20 09:50:46 +10:00
Dion Moult 023eb719f5 Added profiles of more specialised profile subtypes are now supported. 2021-07-19 21:35:20 +10:00
Dion Moult 8ba1b1c9b4 New script to generate a demo project asset library 2021-07-19 21:22:54 +10:00
Dion Moult 7cb163efef New print object placement debug feature 2021-07-19 20:16:47 +10:00
Dion Moult e68c57e9ef Fix bug where reused placements (e.g. from copied objects) were all updated which led to incorrect locations 2021-07-19 20:16:07 +10:00
Dion Moult 7d17b3170c New feature to select all of the active IFC element's class 2021-07-19 19:19:12 +10:00
Dion Moult a968f2b04d The BIM Tool can now create profiled members. 2021-07-18 17:20:16 +10:00
Dion Moult 9a14406f92 BIM tool can now add parametric profiles for beams, not just columns. 2021-07-18 14:48:30 +10:00
Dion Moult 32062e4085 Experimental WIP broken Sverchok API node. 2021-07-18 12:58:27 +10:00
Dion Moult 8da17cf4a0 New extract docs system to dynamically parse usecases. 2021-07-18 12:57:38 +10:00
Dion Moult 2d6e941c41 New documentation for project create_file usecase 2021-07-18 12:56:49 +10:00
Dion Moult 0fffbac519 Fix #1569. 2021-07-18 09:26:37 +10:00
Dion Moult db62faa5ae Fix #1570. 2021-07-18 09:21:38 +10:00
Dion Moult 02dc7b5274 The align tool now works on any generic product, not just walls. 2021-07-17 21:36:03 +10:00
Dion Moult 1bf136e93c Copying a spatial element now also automatically creates a new collection. See #1565. 2021-07-17 18:58:25 +10:00
Dion Moult cccab9e751 Minor fix. See #1567. 2021-07-17 18:38:04 +10:00
Thomas Krijnen 99e74533c3 #1567 Clear inverse cache map after entity insertion 2021-07-17 10:20:50 +02:00
Thomas Krijnen 0f39b09396 #1567 add instance after the values are set 2021-07-17 09:38:31 +02:00
Dion Moult ac2324a65f Minor fix 2021-07-17 17:28:27 +10:00
Thomas Krijnen ca85927a51 #1572 wrong serializer output temp name 2021-07-17 09:06:03 +02:00
Prabhat Singh e3786423c2 Update README.md (#1571) 2021-07-17 16:42:19 +10:00
Dion Moult 8d352ea07a Fix bug where editing a single material didn't quite work. 2021-07-17 09:13:37 +10:00
Dion Moult fd04066940 Minor fix 2021-07-15 18:21:18 +10:00
Dion Moult d34956c2c7 Add readme info on how to use new BCFAPI. Thanks TestPrab! 2021-07-15 18:02:16 +10:00
Prabhat Singh ad2585c571 Merge branch 'v0.6.0' of https://github.com/TestPrab/IfcOpenShell into prabhat_gsoc (#1566) 2021-07-15 17:41:24 +10:00
Dion Moult a08cf23d63 Fix #1562. Fix bug where all instances change meshes when changing type. 2021-07-14 09:37:29 +10:00
Dion Moult 37fe04ffd2 New recipe to downgrade indexed poly curves, often used to allow IFC4 grids to show up in viewers like Revizto / XBim Xplorer / etc. 2021-07-13 19:32:12 +10:00
Dion Moult 248b21eab3 Importing 2D elements of any arbitrary element is now supported 2021-07-13 18:58:30 +10:00
Dion Moult 63f578315d Add support for 3D curve / wireframe representations. 2021-07-13 18:45:07 +10:00
Dion Moult 19b6765994 Minor fix 2021-07-13 16:39:56 +10:00
Dion Moult 7456c270ff You can now extend a wall to your cursor location instead of another wall 2021-07-12 22:10:10 +10:00
Dion Moult b480b43435 BIM workspace tool is now no longer wall specific, and adapts to different element types 2021-07-12 17:34:35 +10:00
Dion Moult 3614587556 Fix #1558. New loading bar when importing an IFC. 2021-07-11 10:29:19 +10:00
Dion Moult 86ce367ba8 Fix #1561. 2021-07-11 09:28:22 +10:00
Thomas Krijnen d320c1dac1 Some quick and dirty #1560 2021-07-10 21:43:27 +02:00
Thomas Krijnen cf48661a35 limit schema versions on Travis 2021-07-10 21:10:03 +02:00
Thomas Krijnen 5e03846783 Update .travis.yml 2021-07-10 17:17:47 +02:00
150 changed files with 9875 additions and 3063 deletions
+6
View File
@@ -22,10 +22,15 @@ addons:
- nlohmann-json3-dev
- opencollada-dev
- python3-all-dev
- python3-pip
- swig
before_script:
- if [ $TRAVIS_OS_NAME == "linux" ]; then ccache -z; fi
install:
# for IDS
- python3 -m pip install xmlschema
script:
- pwd
@@ -39,6 +44,7 @@ script:
-DPYTHON_EXECUTABLE:FILEPATH=/usr/bin/python3 \
-DPYTHON_INCLUDE_DIR:PATH=/usr/include/python3.8 \
-DPYTHON_LIBRARY:FILEPATH=/usr/lib/x86_64-linux-gnu/libpython3.8.so \
"-DSCHEMA_VERSIONS=2x3;4" \
-DGLTF_SUPPORT=On \
-DJSON_INCLUDE_DIR=/usr/include \
../cmake
+3 -1
View File
@@ -494,7 +494,9 @@ function(files_for_ifc_version IFC_VERSION RESULT_NAME)
)
endfunction()
set(SCHEMA_VERSIONS "2x3" "4" "4x1" "4x2" "4x3_rc1" "4x3_rc2" "4x3_rc3" "4x3_rc4")
if(NOT SCHEMA_VERSIONS)
set(SCHEMA_VERSIONS "2x3" "4" "4x1" "4x2" "4x3_rc1" "4x3_rc2" "4x3_rc3" "4x3_rc4")
endif()
foreach(s ${SCHEMA_VERSIONS})
add_definitions(-DHAS_SCHEMA_${s})
+41 -2
View File
@@ -7,13 +7,13 @@ is available via `bcfapi.py`.
- BCF-XML version 2.1: Fully supported
- BCF-API version 2.1: Not supported, will probably tackle this after BCF-API v3.0
- BCF-XML version 3.0: Almost fully supported, except for the documents module
- BCF-API version 3.0: Not supported, but work underway to support it
- BCF-API version 3.0: Almost fully supported, except for two requests.
## bcfxml
The `bcfxml` module lets you interact with the BCF-XML standard.
```
```python
from bcf import bcfxml
@@ -60,3 +60,42 @@ topic = bcfxml.get_topic(guid)
topic.title = "New title"
bcfxml.edit_topic(topic)
```
## bcfapi
The `bcfapi` module lets you interact with the BCF-API standard.
```python
from bcf.v3.bcfapi import Client
client_id = "YOUR_CLIENT_ID"
client_secret = "YOUR_CLIENT_SECRET"
client = Client(client_id, client_secret)
client.set_urls(base_url="OPENCDE_BASEURL")
auth_methods = client.get_auth_methods()
# Our library currently only implements the authorization_code flow
if "authorization_code" in auth_methods:
client.login()
versions = client.get_versions()
if "3.0" in versions:
client.set_version(version="3.0")
data = client.get_projects()
print(data)
project_id = data[0]["project_id"]
print(project_id)
data = client.get_project(project_id)
print(data)
data = client.get_extensions(project_id)
print(data)
```
## Todo List
The remaining work that needs to be completed in `bcfxml.py` and `bcfapi.py`.
* For `bcfxml.py` two xsds support is remaining namely 'documents.xsd` and `extensions.xsd`.
* For `bcfapi.py` two requests that are `get_topics` and `get_comments` are remaining.
+540
View File
@@ -0,0 +1,540 @@
import uuid
import time
import json
import urllib
import requests
import webbrowser
import http.server
import base64
client_id, client_secret = "", ""
class OAuthReceiver(http.server.BaseHTTPRequestHandler):
def do_GET(self):
query = urllib.parse.parse_qs(urllib.parse.urlparse(self.path).query)
self.server.auth_code = query.get("code", [""])[0]
self.server.auth_state = query.get("state", [""])[0]
self.send_response(200)
self.send_header("Content-type", "text/plain")
self.end_headers()
self.wfile.write("You have now authenticated :) You may now close this browser window.".encode("utf-8"))
class Client:
def __init__(self, client_id, client_secret):
self.baseurl = None
self.access_token = ""
self.refresh_token = ""
self.access_token_expires_on = time.time()
self.refresh_token_expires_on = float("inf")
self.auth_endpoint = None
self.token_endpoint = None
self.client_id = client_id
self.client_secret = client_secret
self.version_ids = {}
self.version = None
self.auth_method = None
self.redirect_uri = None
self.api_baseurl = None
def get(self, endpoint, params=None, is_auth_required=False):
headers = {"Authorization": "Bearer " + self.get_access_token()}
return requests.get(f"{self.api_baseurl}{endpoint}", headers=headers, params=params or None).json()
def post(self, endpoint, data=None, params=None):
headers = {
"Authorization": "Bearer " + self.get_access_token(),
"Content-type": "application/json",
}
resp = requests.post(
f"{self.api_baseurl}{endpoint}",
headers=headers,
params=params or None,
data=data or None,
)
return resp.status_code, resp.text
def put(self, endpoint, data=None, params=None):
headers = {
"Authorization": "Bearer " + self.get_access_token(),
"Content-type": "application/json",
}
resp = requests.put(
f"{self.baseurl}{endpoint}",
headers=headers,
params=params or None,
data=data or None,
)
return resp.status_code, resp.text
def set_urls(self, base_url=None, redirect_uri=None):
self.baseurl = base_url
self.redirect_uri = redirect_uri
def delete(self, endpoint, params=None):
headers = {"Authorization": "Bearer " + self.get_access_token()}
resp = requests.put(
f"{self.baseurl}{endpoint}",
headers=headers,
params=params or None,
)
return resp.status_code
def get_access_token(self):
if self.access_token and self.access_token_expires_on > time.time():
return self.access_token
elif self.refresh_token and self.refresh_token_expires_on > time.time():
self.get_refresh_token()
else:
self.login()
return self.access_token
def get_auth_methods(self):
resp = requests.get(f"{self.baseurl}opencde/1.0/auth")
return resp.json()["supported_oauth2_flows"]
def get_versions(self):
resp = requests.get(f"{self.baseurl}opencde/versions")
resp_values = resp.json()["versions"]
for version in resp_values:
if "api_base_url" in version:
self.version_ids.update({version["version_id"]: version["api_base_url"]})
return self.version_ids
def set_version(self, version=None):
self.version = version
self.api_baseurl = self.version_ids[self.version]
def login(self):
resp = requests.get(f"{self.baseurl}opencde/1.0/auth")
values = resp.json()
self.auth_endpoint = values["oauth2_auth_url"]
self.token_endpoint = values["oauth2_token_url"]
with http.server.HTTPServer(("", 8080), OAuthReceiver) as server:
state = str(uuid.uuid4())
query = urllib.parse.urlencode(
{
"client_id": self.client_id,
"response_type": "code",
"state": state,
"redirect_uri": f"http://localhost:{server.server_address[1]}/{self.redirect_uri}",
}
)
if "?" in self.auth_endpoint:
webbrowser.open(f"{self.auth_endpoint}&{query}")
else:
webbrowser.open(f"{self.auth_endpoint}?{query}")
server.timeout = 100
server.state = state
server.handle_request()
if server.auth_code and server.auth_state == state:
data = {
"grant_type": "authorization_code",
"code": server.auth_code,
"redirect_uri": f"http://localhost:{server.server_address[1]}/{self.redirect_uri}",
}
auth_string = f"{self.client_id}:{self.client_secret}"
header_string = base64.b64encode(auth_string.encode("utf-8")).decode("utf-8")
headers = {"Authorization": f"Basic {header_string}"}
self.set_tokens_from_response(requests.post(self.token_endpoint, data=data, headers=headers))
def get_refresh_token(self):
self.set_tokens_from_response(
requests.post(
self.token_endpoint,
params={
"grant_type": "refresh_token",
"refresh_token": self.refresh_token,
},
).json()
)
def get_new_access_token(self):
auth_string = f"{self.client_id}:{self.client_secret}"
header_string = base64.b64encode(auth_string.encode("utf-8")).decode("utf-8")
headers = {"Authorization": f"Basic {header_string}"}
self.set_tokens_from_response(
requests.post(
self.token_endpoint,
data={
"grant_type": "refresh_token",
"refresh_token": self.refresh_token,
},
headers=headers,
).json()
)
def set_auth_method(self, method="authorization_code_grant"):
if method != "authorization_code_grant":
raise NotImplementedError(f"{method} not supported")
else:
self.auth_method = method
def set_tokens_from_response(self, response):
response = response.json()
self.access_token = response["access_token"]
self.refresh_token = response["refresh_token"]
self.access_token_expires_on = time.time() + response["expires_in"]
if "refresh_token_expires_in" in response:
self.refresh_token_expires_on = time.time() + response["refresh_token_expires_in"]
def get_projects(self) -> list:
return self.get(
f"/projects",
)
def get_project(
self,
project_id="",
) -> dict:
return self.get(
f"/projects/{project_id}",
{
"project_id": project_id,
},
)
def update_project(self, project_id="", data=None) -> dict:
url = f"{self.baseurl}/projects/{project_id}"
headers = {"Authorization": "Bearer " + self.get_access_token()}
resp = requests.put(url, headers=headers, data=data)
return resp.status_code, resp.text
def get_extensions(
self,
project_id="",
) -> dict:
return self.get(
f"/projects/{project_id}/extensions",
{
"project_id": project_id,
},
)
def get_topics(
self,
project_id="",
topics="",
query_string=None,
) -> list:
# return self.get(
# f"/projects/{project_id}/topics",
# {
# "project_id": project_id,
# "topics": topics,
# "query_string": query_string,
# },
# )
pass
def get_topic(self, project_id="", topic_id="") -> dict:
return self.get(
f"/projects/{project_id}/topics/{topic_id}",
{
"project_id": project_id,
"topic_id": topic_id,
},
)
def create_topic(self, project_id="", data=None):
return self.post(f"/projects/{project_id}/topics", data=data)
def update_topic(self, project_id="", topic_id="", data=None) -> dict:
return self.put(f"/projects/{project_id}/topics/{topic_id}", data=data)
def delete_topic(self, project_id="", topic_id=""):
return self.delete(f"/projects/{project_id}/topics/{topic_id}")
def get_snippet(self, project_id="", topic_id="") -> str:
return self.get(
f"/projects/{project_id}/topics/{topic_id}/snippet",
{
"project_id": project_id,
"topic_id": topic_id,
},
)
def update_snippet(self, project_id="", topic_id="", data=None):
return self.put(f"/projects/{project_id}/topics", data=data)
def get_files_information(self, project_id="") -> list:
return self.get(
f"/projects/{project_id}/files_information",
{
"project_id": project_id,
},
)
def get_files(self, project_id="", topic_id="") -> list:
return self.get(
f"/projects/{project_id}/topics/{topic_id}/files",
{
"project_id": project_id,
"topic_id": topic_id,
},
)
def update_files(
self,
project_id="",
topic_id="",
data=None,
params=None,
):
return self.put(
f"/projects/{project_id}/topics/{topic_id}/files",
data=data,
)
def get_comments(self, project_id="", topic_id="") -> list:
pass
def create_comments(
self,
project_id="",
topic_id="",
data=None,
params=None,
):
return self.post(
f"/projects/{project_id}/topics/{topic_id}/comments",
data=data,
)
def get_comment(self, project_id="", topic_id="", comment_id="") -> dict:
return self.get(
f"/projects/{project_id}/topics/{topic_id}/comments/{comment_id}",
{
"project_id": project_id,
"topic_id": topic_id,
"comment_id": comment_id,
},
)
def delete_comment(self, project_id="", topic_id="", comment_id=""):
return self.delete(f"/projects/{project_id}/topics/{topic_id}/comments/{comment_id}")
def update_comment(
self,
project_id="",
topic_id="",
comment_id="",
data=None,
):
return self.put(
f"/projects/{project_id}/topics/{topic_id}/comments/{comment_id}",
data=data,
)
def get_viewpoints(self, project_id="", topic_id="") -> list:
return self.get(
f"/projects/{project_id}/topics/{topic_id}/viewpoints",
{
"project_id": project_id,
"topic_id": topic_id,
},
)
def create_viewpoints(self, project_id="", topic_id="", data=None):
return self.post(
f"/projects/{project_id}/topics/{topic_id}/viewpoints",
data=data,
)
def get_viewpoint(self, project_id="", topic_id="", viewpoint_id="") -> dict:
return self.get(
f"/projects/{project_id}/topics/{topic_id}/viewpoints/{viewpoint_id}",
{
"project_id": project_id,
"topic_id": topic_id,
"viewpoint_id": viewpoint_id,
},
)
def delete_viewpoint(
self,
project_id="",
topic_id="",
viewpoint_id="",
):
return self.delete(
f"/projects/{project_id}/topics/{topic_id}/viewpoints/{viewpoint_id}",
)
def get_snapshot(self, project_id="", topic_id="", viewpoint_id="") -> str:
return self.get(
f"/projects/{project_id}/topics/{topic_id}/viewpoints/{viewpoint_id}/snapshot",
{
"project_id": project_id,
"topic_id": topic_id,
"viewpoint_id": viewpoint_id,
},
)
def get_bitmap(self, project_id="", topic_id="", viewpoint_id="", bitmap_id="") -> str:
return self.get(
f"/projects/{project_id}/topics/{topic_id}/viewpoints/{viewpoint_id}/bitmaps/{bitmap_id}",
{
"project_id": project_id,
"topic_id": topic_id,
"viewpoint_id": viewpoint_id,
"bitmap_id": bitmap_id,
},
)
def get_selection(self, project_id="", topic_id="", viewpoint_id="") -> dict:
return self.get(
f"/projects/{project_id}/topics/{topic_id}/viewpoints/{viewpoint_id}/selection",
{
"project_id": project_id,
"topic_id": topic_id,
"viewpoint_id": viewpoint_id,
},
)
def get_coloring(self, project_id="", topic_id="", viewpoint_id="") -> dict:
return self.get(
f"/projects/{project_id}/topics/{topic_id}/viewpoints/{viewpoint_id}/coloring",
{
"project_id": project_id,
"topic_id": topic_id,
"viewpoint_id": viewpoint_id,
},
)
def get_visibility(self, project_id="", topic_id="", viewpoint_id="") -> dict:
return self.get(
f"/projects/{project_id}/topics/{topic_id}/viewpoints/{viewpoint_id}/visibility",
{
"project_id": project_id,
"topic_id": topic_id,
"viewpoint_id": viewpoint_id,
},
)
def get_related_topics(self, project_id="", topic_id="") -> list:
return self.get(
f"/projects/{project_id}/topics/{topic_id}/related_topics",
{
"project_id": project_id,
"topic_id": topic_id,
},
)
def update_related_topics(
self,
project_id="",
topic_id="",
data=None,
):
return self.put(
f"/projects/{project_id}/topics/{topic_id}/related_topics",
data=data,
)
def get_document_references(self, project_id="", topic_id="") -> list:
return self.get(
f"/projects/{project_id}/topics/{topic_id}/document_references",
{
"project_id": project_id,
"topic_id": topic_id,
},
)
def create_document_reference(
self,
project_id="",
topic_id="",
data=None,
):
return self.post(
f"/projects/{project_id}/topics/{topic_id}/document_references",
data=data,
)
def update_document_references(
self,
project_id="",
topic_id="",
document_reference_id="",
data=None,
):
return self.put(
f"/projects/{project_id}/topics/{topic_id}/document_references/{document_reference_id}",
data=data,
)
def get_documents(self, project_id="", topic_id="") -> list:
return self.get(
f"/projects/{project_id}/topics/{topic_id}/documents",
{
"project_id": project_id,
"topic_id": topic_id,
},
)
def create_document(
self,
project_id="",
topic_id="",
guid=None,
data=None,
):
headers = {
"Authorization": "Bearer " + self.get_access_token(),
"Content-type": "application/octet-stream",
}
response = requests.post(
f"/projects/{project_id}/topics/{topic_id}/documents",
data=data,
params={guid},
headers=headers,
)
def get_document(self, project_id="", topic_id="", document_id="") -> str:
return self.get(
f"/projects/{project_id}/topics/{topic_id}/documents/{document_id}",
{
"project_id": project_id,
"topic_id": topic_id,
"document_id": document_id,
},
)
def get_topics_events(self, project_id="") -> list:
return self.get(
f"/projects/{project_id}/topics/events",
{
"project_id": project_id,
},
)
def get_topic_events(self, project_id="", topic_id="") -> list:
return self.get(
f"/projects/{project_id}/topics/{topic_id}/events",
{
"project_id": project_id,
"topic_id": topic_id,
},
)
def get_comments_events(self, project_id="") -> list:
return self.get(
f"/projects/{project_id}/topics/comments/events",
{
"project_id": project_id,
},
)
def get_comment_events(self, project_id="", topic_id="", comment_id="") -> list:
return self.get(
f"/projects/{project_id}/topics/{topic_id}/comments/{comment_id}/events",
{
"project_id": project_id,
"topic_id": topic_id,
"comment_id": comment_id,
},
)
+4 -2
View File
@@ -13,10 +13,10 @@ endif
# Provides IfcOpenShell Python functionality
ifeq ($(PYVERSION), py37)
cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcblender-python-37-v0.6.0-2fd2b49-$(PLATFORM)64.zip
cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcblender-python-37-v0.6.0-f14d349-$(PLATFORM)64.zip
endif
ifeq ($(PYVERSION), py39)
cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcblender-python-39-v0.6.0-2fd2b49-$(PLATFORM)64.zip
cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcblender-python-39-v0.6.0-f14d349-$(PLATFORM)64.zip
endif
cd dist/working && unzip ifcblender*
cp -r dist/working/io_import_scene_ifc/ifcopenshell dist/blenderbim/libs/site/packages/
@@ -44,6 +44,8 @@ endif
# IfcOpenBot sometimes lags behind, so we hotfix the Python utilities
cp -r dist/working/IfcOpenShell-0.6.0/src/ifcopenshell-python/ifcopenshell/util/* dist/blenderbim/libs/site/packages/ifcopenshell/util/
cp -r dist/working/IfcOpenShell-0.6.0/src/ifcopenshell-python/ifcopenshell/api/* dist/blenderbim/libs/site/packages/ifcopenshell/api/
cp dist/working/IfcOpenShell-0.6.0/src/ifcopenshell-python/ifcopenshell/entity_instance.py dist/blenderbim/libs/site/packages/ifcopenshell/
cp dist/working/IfcOpenShell-0.6.0/src/ifcopenshell-python/ifcopenshell/file.py dist/blenderbim/libs/site/packages/ifcopenshell/
# Provides bcf functionality
cp -r dist/working/IfcOpenShell-0.6.0/src/bcf/bcf dist/blenderbim/libs/site/packages/
# Provides IFCClash functionality
@@ -29,8 +29,10 @@ if bpy is not None:
"cost": None,
"sequence": None,
"group": None,
"system": None,
"structural": None,
"boundary": None,
"profile": None,
"material": None,
"style": None,
"layer": None,
+6 -1
View File
@@ -68,6 +68,8 @@ class IfcExporter:
for ifc_definition_id, obj in IfcStore.id_map.items():
try:
if isinstance(obj, bpy.types.Material):
continue
self.sync_object_placement(obj)
self.sync_object_container(ifc_definition_id, obj)
except ReferenceError:
@@ -87,7 +89,10 @@ class IfcExporter:
if not obj:
continue
try:
bpy.ops.bim.update_representation(obj=obj.name)
if isinstance(obj, bpy.types.Material):
bpy.ops.bim.update_style_colours(material=obj.name)
else:
bpy.ops.bim.update_representation(obj=obj.name)
except ReferenceError:
pass # The object is likely deleted
IfcStore.edited_objs.clear()
+12 -3
View File
@@ -6,6 +6,7 @@ from bpy.app.handlers import persistent
from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.attribute.data import Data as AttributeData
from ifcopenshell.api.material.data import Data as MaterialData
from ifcopenshell.api.style.data import Data as StyleData
from ifcopenshell.api.type.data import Data as TypeData
@@ -13,6 +14,8 @@ 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]
@@ -22,9 +25,8 @@ def mode_callback(obj, data):
or not obj.data
or not isinstance(obj.data, (bpy.types.Mesh, bpy.types.Curve, bpy.types.TextCurve))
or not obj.BIMObjectProperties.ifc_definition_id
or not bpy.context.scene.BIMProjectProperties.is_authoring
):
return
continue
if obj.data.BIMMeshProperties.ifc_definition_id:
representation = IfcStore.get_file().by_id(obj.data.BIMMeshProperties.ifc_definition_id)
if representation.RepresentationType in ["Tessellation", "Brep", "Annotation2D"]:
@@ -34,6 +36,7 @@ def mode_callback(obj, data):
def name_callback(obj, data):
# TODO Do we still need this, now that we are monitoring the undo redo objects?
try:
obj.name
except:
@@ -49,6 +52,7 @@ def name_callback(obj, data):
MaterialData.load_materials()
if obj.BIMMaterialProperties.ifc_style_id:
IfcStore.get_file().by_id(obj.BIMMaterialProperties.ifc_style_id).Name = obj.name
StyleData.load(IfcStore.get_file(), obj.BIMMaterialProperties.ifc_style_id)
return
if not obj.BIMObjectProperties.ifc_definition_id or "/" not in obj.name:
@@ -75,6 +79,11 @@ def name_callback(obj, data):
AttributeData.load(IfcStore.get_file(), obj.BIMObjectProperties.ifc_definition_id)
def color_callback(obj, data):
if obj.BIMMaterialProperties.ifc_style_id:
IfcStore.edited_objs.add(obj)
def active_object_callback():
obj = bpy.context.active_object
for obj in bpy.context.selected_objects:
@@ -122,12 +131,12 @@ def purge_module_data():
@persistent
def loadIfcStore(scene):
IfcStore.purge()
purge_module_data()
ifc_file = IfcStore.get_file()
if not ifc_file:
return
IfcStore.get_schema()
IfcStore.reload_linked_elements()
purge_module_data()
@persistent
+16 -2
View File
@@ -114,9 +114,19 @@ class IfcStore:
IfcStore.id_map[element.id()] = obj
if hasattr(element, "GlobalId"):
IfcStore.guid_map[element.GlobalId] = obj
obj.BIMObjectProperties.ifc_definition_id = element.id()
blenderbim.bim.handler.subscribe_to(obj, "mode", blenderbim.bim.handler.mode_callback)
if element.is_a("IfcSurfaceStyle"):
obj.BIMMaterialProperties.ifc_style_id = element.id()
else:
obj.BIMObjectProperties.ifc_definition_id = element.id()
blenderbim.bim.handler.subscribe_to(obj, "name", blenderbim.bim.handler.name_callback)
if isinstance(obj, bpy.types.Material):
blenderbim.bim.handler.subscribe_to(obj, "diffuse_color", blenderbim.bim.handler.color_callback)
elif isinstance(obj, bpy.types.Object):
blenderbim.bim.handler.subscribe_to(obj, "mode", blenderbim.bim.handler.mode_callback)
for listener in IfcStore.element_listeners:
listener(element, obj)
@@ -140,6 +150,10 @@ class IfcStore:
IfcStore.guid_map[data["guid"]] = obj
blenderbim.bim.handler.subscribe_to(obj, "mode", blenderbim.bim.handler.mode_callback)
blenderbim.bim.handler.subscribe_to(obj, "name", blenderbim.bim.handler.name_callback)
if isinstance(obj, bpy.types.Material):
blenderbim.bim.handler.subscribe_to(obj, "diffuse_color", blenderbim.bim.handler.color_callback)
# TODO Listeners are not re-registered. Does this cause nasty problems to debug later on?
# TODO We're handling id_map and guid_map, but what about edited_objs? This might cause big problems.
@staticmethod
def unlink_element(element=None, obj=None):
+69 -12
View File
@@ -54,9 +54,28 @@ class MaterialCreator:
if not self.mesh or self.mesh.name in self.parsed_meshes:
return
self.parsed_meshes.add(self.mesh.name)
self.add_default_material(element)
if self.parse_representations(element):
self.assign_material_slots_to_faces()
def add_default_material(self, element):
element_material = ifcopenshell.util.element.get_material(element)
if not element_material:
return
for material in [m for m in self.ifc_importer.file.traverse(element_material) if m.is_a("IfcMaterial")]:
if not material.HasRepresentation:
continue
surface_style = [
s for s in self.ifc_importer.file.traverse(material.HasRepresentation[0]) if s.is_a("IfcSurfaceStyle")
]
if surface_style:
self.mesh.materials.append(self.styles[surface_style[0].id()])
return
# For authoring convenience, we choose to assign a material, even if it has no surface style. See #1585.
for material in [m for m in self.ifc_importer.file.traverse(element_material) if m.is_a("IfcMaterial")]:
self.mesh.materials.append(self.materials[material.id()])
return
def load_existing_materials(self):
for material in bpy.data.materials:
if material.BIMObjectProperties.ifc_definition_id:
@@ -139,7 +158,7 @@ class IfcImporter:
self.exclude_elements = set()
self.project = None
self.spatial_structure_elements = {}
self.elements = {}
self.elements = []
self.type_collection = None
self.type_products = {}
self.openings = {}
@@ -159,8 +178,16 @@ class IfcImporter:
self.time = time.time()
print("{} :: {:.2f}".format(message, time.time() - self.time))
self.time = time.time()
self.update_progress(self.progress + 1)
def update_progress(self, progress):
if progress <= 100:
self.progress = progress
bpy.context.window_manager.progress_update(self.progress)
def execute(self):
bpy.context.window_manager.progress_begin(0, 100)
self.progress = 0
self.profile_code("Starting import process")
self.load_diff()
self.profile_code("Load diff")
@@ -200,7 +227,7 @@ class IfcImporter:
self.profile_code("Create native products")
self.create_products()
self.profile_code("Create products")
self.create_empty_products()
self.create_empty_and_2d_elements()
self.profile_code("Create empty products")
self.create_type_products()
self.profile_code("Create type products")
@@ -228,6 +255,8 @@ class IfcImporter:
self.profile_code("Mesh cleaning")
self.set_default_context()
self.profile_code("Setting default context")
self.update_progress(100)
bpy.context.window_manager.progress_end()
def is_element_far_away(self, element, is_meters=True):
try:
@@ -322,10 +351,10 @@ class IfcImporter:
return
project = self.file.by_type("IfcProject")[0]
site = self.find_decomposed_ifc_class(project, "IfcSite")
if site and self.is_element_far_away(site[0]):
if site and self.is_element_far_away(site[0], is_meters=False):
return self.guess_georeferencing(site[0])
building = self.find_decomposed_ifc_class(project, "IfcBuilding")
if building and self.is_element_far_away(building[0]):
if building and self.is_element_far_away(building[0], is_meters=False):
return self.guess_georeferencing(building[0])
return self.guess_absolute_coordinate()
@@ -450,7 +479,6 @@ class IfcImporter:
grid_collection.objects.link(obj)
def create_type_products(self):
type_products = self.file.by_type("IfcTypeProduct")
for collection in self.project["blender"].children:
if collection.name == "Types":
self.type_collection = collection
@@ -458,7 +486,15 @@ class IfcImporter:
if not self.type_collection:
self.type_collection = bpy.data.collections.new("Types")
self.project["blender"].children.link(self.type_collection)
if self.filter_mode in ["WHITELIST", "BLACKLIST"]:
type_products = set([ifcopenshell.util.element.get_type(e) for e in self.elements])
else:
type_products = self.file.by_type("IfcTypeProduct")
for type_product in type_products:
if not type_product:
continue
self.create_type_product(type_product)
def create_type_product(self, element):
@@ -546,12 +582,20 @@ class IfcImporter:
if not valid_file:
return False
checkpoint = time.time()
total = 0
total_created = 0
approx_total_products = len(self.include_elements) or len(self.file.by_type("IfcElement"))
start_progress = self.progress
progress_range = 85 - start_progress
while True:
total += 1
if total % 250 == 0:
print("{} elements processed in {:.2f}s ...".format(total, time.time() - checkpoint))
if total_created % 250 == 0:
print(
"{} / ~{} elements processed in {:.2f}s ...".format(
total_created, approx_total_products, time.time() - checkpoint
)
)
checkpoint = time.time()
if approx_total_products:
self.update_progress(((total_created / approx_total_products) * progress_range) + start_progress)
shape = iterator.get()
if shape:
product = self.file.by_id(shape.guid)
@@ -565,18 +609,32 @@ class IfcImporter:
pass
else:
self.create_product(product, shape)
total_created += 1
if not iterator.next():
break
print("Done creating geometry")
def create_empty_products(self):
for element in self.file.by_type("IfcProduct"):
def create_empty_and_2d_elements(self):
curve_products = []
if self.filter_mode == "WHITELIST":
self.elements = self.include_elements
elif self.filter_mode == "BLACKLIST":
self.elements = [e for e in self.file.by_type("IfcElement") if e not in self.exclude_elements]
else:
self.elements = self.file.by_type("IfcElement")
for element in self.elements:
if element.id() in self.added_data:
continue
if element.is_a("IfcPort"):
continue
if not element.Representation:
self.create_product(element)
else:
curve_products.append(element)
if curve_products:
self.create_curve_products(curve_products)
def create_annotation(self):
self.create_curve_products(self.file.by_type("IfcAnnotation"))
@@ -911,7 +969,6 @@ class IfcImporter:
def set_ifc_file(self):
bpy.context.scene.BIMProperties.ifc_file = self.ifc_import_settings.input_file
IfcStore.file = self.file
IfcStore.path = self.ifc_import_settings.input_file
def calculate_unit_scale(self):
@@ -17,7 +17,7 @@ class AssignObject(bpy.types.Operator):
def _execute(self, context):
self.file = IfcStore.get_file()
related_objects = (
[bpy.data.objects.get(self.related_object)] if self.related_object else bpy.context.selected_objects
[bpy.data.objects.get(self.related_object)] if self.related_object else context.selected_objects
)
relating_object = bpy.data.objects.get(self.relating_object)
if not relating_object or not relating_object.BIMObjectProperties.ifc_definition_id:
@@ -40,7 +40,7 @@ class AssignObject(bpy.types.Operator):
spatial_collection = bpy.data.collections.get(related_object.name)
relating_collection = bpy.data.collections.get(relating_object.name)
if spatial_collection:
self.remove_collection(bpy.context.scene.collection, spatial_collection)
self.remove_collection(context.scene.collection, spatial_collection)
for collection in bpy.data.collections:
if collection == relating_collection:
if not collection.children.get(spatial_collection.name):
@@ -67,8 +67,8 @@ class EnableEditingAggregate(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
bpy.context.active_object.BIMObjectProperties.relating_object = None
bpy.context.active_object.BIMObjectProperties.is_editing_aggregate = True
context.active_object.BIMObjectProperties.relating_object = None
context.active_object.BIMObjectProperties.is_editing_aggregate = True
return {"FINISHED"}
@@ -79,7 +79,7 @@ class DisableEditingAggregate(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
obj.BIMObjectProperties.is_editing_aggregate = False
return {"FINISHED"}
@@ -94,9 +94,9 @@ class AddAggregate(bpy.types.Operator):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
aggregate_collection = bpy.data.collections.new("IfcElementAssembly/Assembly")
bpy.context.scene.collection.children.link(aggregate_collection)
context.scene.collection.children.link(aggregate_collection)
aggregate = bpy.data.objects.new("Assembly", None)
aggregate_collection.objects.link(aggregate)
bpy.ops.bim.assign_class(obj=aggregate.name, ifc_class="IfcElementAssembly")
@@ -104,12 +104,6 @@ class EditAttributes(bpy.types.Operator):
ifcopenshell.api.run(
"attribute.edit_attributes", self.file, **{"product": product, "attributes": attributes}
)
if "Name" in attributes:
new_name = "{}/{}".format(product.is_a(), product.Name or "Unnamed")
collection = bpy.data.collections.get(obj.name)
if collection:
collection.name = new_name
obj.name = new_name
Data.load(IfcStore.get_file(), oprops.ifc_definition_id)
bpy.ops.bim.disable_editing_attributes(obj=obj.name, obj_type=self.obj_type)
return {"FINISHED"}
@@ -121,11 +115,11 @@ class GenerateGlobalId(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
index = bpy.context.active_object.BIMAttributeProperties.attributes.find("GlobalId")
index = context.active_object.BIMAttributeProperties.attributes.find("GlobalId")
if index >= 0:
global_id = bpy.context.active_object.BIMAttributeProperties.attributes[index]
global_id = context.active_object.BIMAttributeProperties.attributes[index]
else:
global_id = bpy.context.active_object.BIMAttributeProperties.attributes.add()
global_id = context.active_object.BIMAttributeProperties.attributes.add()
global_id.name = "GlobalId"
global_id.data_type = "string"
global_id.string_value = ifcopenshell.guid.new()
@@ -16,7 +16,7 @@ class AuginLogin(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
props = bpy.context.scene.AuginProperties
props = context.scene.AuginProperties
url = "https://server.auge.pro.br/API/v3/augin_rest.php/user_login"
payload = {"email": props.username, "password": props.password}
@@ -36,7 +36,7 @@ class AuginReset(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
props = bpy.context.scene.AuginProperties
props = context.scene.AuginProperties
props.is_success = False
return {"FINISHED"}
@@ -49,7 +49,7 @@ class AuginCreateNewModel(bpy.types.Operator):
def execute(self, context):
import boto3
from botocore.config import Config
props = bpy.context.scene.AuginProperties
props = context.scene.AuginProperties
# Create project
url = "https://server.auge.pro.br/API/v3/augin_rest.php/new_model"
@@ -113,7 +113,7 @@ class AuginCreateNewModel(bpy.types.Operator):
context.scene.render.image_settings.file_format = old_file_format
context.scene.render.filepath = old_filepath
client.upload_file(bpy.context.scene.BIMProperties.ifc_file, result["s3_bucket"], result["model_path"])
client.upload_file(context.scene.BIMProperties.ifc_file, result["s3_bucket"], result["model_path"])
client.upload_file(thumb_path, result["s3_bucket"], result["thumb_path"])
@@ -121,8 +121,8 @@ class AuginCreateNewModel(bpy.types.Operator):
url = "https://server.auge.pro.br/API/v3/augin_rest.php/files_uploaded"
payload = {
"user_token": props.token,
"ifc_filesize": os.path.getsize(bpy.context.scene.BIMProperties.ifc_file),
"model_filesize": os.path.getsize(bpy.context.scene.BIMProperties.ifc_file),
"ifc_filesize": os.path.getsize(context.scene.BIMProperties.ifc_file),
"model_filesize": os.path.getsize(context.scene.BIMProperties.ifc_file),
"thumb_filesize": os.path.getsize(thumb_path),
"model_upload_path": result["model_path"],
"thumb_upload_path": result["thumb_path"],
@@ -28,7 +28,7 @@ class BIM_PT_augin(bpy.types.Panel):
row = layout.row()
row.label(text="Logged in as " + props.username)
if not bpy.context.scene.BIMProperties.ifc_file:
if not context.scene.BIMProperties.ifc_file:
row = layout.row()
row.label(text="No IFC Found")
return
@@ -14,11 +14,11 @@ class NewBcfProject(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
bpy.context.scene.BCFProperties.is_loaded = False
context.scene.BCFProperties.is_loaded = False
bcfxml = bcfstore.BcfStore.get_bcfxml()
bcfxml.new_project()
bpy.ops.bim.load_bcf_project()
bpy.context.scene.BCFProperties.is_loaded = True
context.scene.BCFProperties.is_loaded = True
return {"FINISHED"}
@@ -30,14 +30,14 @@ class LoadBcfProject(bpy.types.Operator):
filter_glob: bpy.props.StringProperty(default="*.bcf;*.bcfzip", options={"HIDDEN"})
def execute(self, context):
bpy.context.scene.BCFProperties.is_loaded = False
context.scene.BCFProperties.is_loaded = False
if self.filepath:
bcfstore.BcfStore.bcfxml = bcf.bcfxml.load(self.filepath)
bcfxml = bcfstore.BcfStore.get_bcfxml()
bcfxml.get_project()
bpy.context.scene.BCFProperties.name = bcfxml.project.name
context.scene.BCFProperties.name = bcfxml.project.name
bpy.ops.bim.load_bcf_topics()
bpy.context.scene.BCFProperties.is_loaded = True
context.scene.BCFProperties.is_loaded = True
return {"FINISHED"}
def invoke(self, context, event):
@@ -53,11 +53,11 @@ class LoadBcfTopics(bpy.types.Operator):
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
bcfxml.get_topics()
while len(bpy.context.scene.BCFProperties.topics) > 0:
bpy.context.scene.BCFProperties.topics.remove(0)
while len(context.scene.BCFProperties.topics) > 0:
context.scene.BCFProperties.topics.remove(0)
index = 0
for topic_guid in bcfxml.topics.keys():
new = bpy.context.scene.BCFProperties.topics.add()
new = context.scene.BCFProperties.topics.add()
bpy.ops.bim.load_bcf_topic(topic_guid = topic_guid, topic_index = index)
index += 1
return {"FINISHED"}
@@ -73,7 +73,7 @@ class LoadBcfTopic(bpy.types.Operator):
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
topic = bcfxml.get_topic(self.topic_guid)
new = bpy.context.scene.BCFProperties.topics[self.topic_index]
new = context.scene.BCFProperties.topics[self.topic_index]
data_map = {
"name": topic.guid,
"title": topic.title,
@@ -143,7 +143,7 @@ class LoadBcfComments(bpy.types.Operator):
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
bcfxml.get_comments(self.topic_guid)
blender_topic = bpy.context.scene.BCFProperties.topics.get(self.topic_guid)
blender_topic = context.scene.BCFProperties.topics.get(self.topic_guid)
while len(blender_topic.comments) > 0:
blender_topic.comments.remove(0)
for comment in bcfxml.topics[self.topic_guid].comments.values():
@@ -170,7 +170,7 @@ class EditBcfProjectName(bpy.types.Operator):
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
bcfxml.project.name = bpy.context.scene.BCFProperties.name
bcfxml.project.name = context.scene.BCFProperties.name
bcfxml.edit_project()
return {"FINISHED"}
@@ -182,7 +182,7 @@ class EditBcfAuthor(bpy.types.Operator):
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
bcfxml.author = bpy.context.scene.BCFProperties.author
bcfxml.author = context.scene.BCFProperties.author
return {"FINISHED"}
@@ -192,7 +192,7 @@ class EditBcfTopicName(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
props = bpy.context.scene.BCFProperties
props = context.scene.BCFProperties
blender_topic = props.topics[props.active_topic_index]
bcfxml = bcfstore.BcfStore.get_bcfxml()
topic = bcfxml.topics[blender_topic.name]
@@ -207,7 +207,7 @@ class EditBcfTopic(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
props = bpy.context.scene.BCFProperties
props = context.scene.BCFProperties
blender_topic = props.topics[props.active_topic_index]
bcfxml = bcfstore.BcfStore.get_bcfxml()
@@ -250,7 +250,7 @@ class AddBcfTopic(bpy.types.Operator):
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
bcfxml.add_topic()
new = bpy.context.scene.BCFProperties.topics.add()
new = context.scene.BCFProperties.topics.add()
new.name = "New Topic"
bpy.ops.bim.load_bcf_topics()
return {"FINISHED"}
@@ -263,7 +263,7 @@ class AddBcfBimSnippet(bpy.types.Operator):
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
props = bpy.context.scene.BCFProperties
props = context.scene.BCFProperties
blender_topic = props.topics[props.active_topic_index]
topic = bcfxml.topics[blender_topic.name]
bim_snippet = bcf.v2.data.BimSnippet()
@@ -282,7 +282,7 @@ class AddBcfRelatedTopic(bpy.types.Operator):
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
props = bpy.context.scene.BCFProperties
props = context.scene.BCFProperties
blender_topic = props.topics[props.active_topic_index]
related_topic = None
for topic in bcfxml.topics.values():
@@ -306,7 +306,7 @@ class AddBcfHeaderFile(bpy.types.Operator):
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
props = bpy.context.scene.BCFProperties
props = context.scene.BCFProperties
blender_topic = props.topics[props.active_topic_index]
topic = bcfxml.topics[blender_topic.name]
header_file = bcf.v2.data.HeaderFile()
@@ -329,9 +329,9 @@ class ViewBcfTopic(bpy.types.Operator):
topic_guid: bpy.props.StringProperty()
def execute(self, context):
for index, topic in enumerate(bpy.context.scene.BCFProperties.topics):
for index, topic in enumerate(context.scene.BCFProperties.topics):
if topic.guid.lower() == self.topic_guid.lower():
bpy.context.scene.BCFProperties.active_topic_index = index
context.scene.BCFProperties.active_topic_index = index
return {"FINISHED"}
@@ -341,44 +341,44 @@ class AddBcfViewpoint(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
if not bpy.context.scene.camera:
if not context.scene.camera:
return {"FINISHED"}
bcfxml = bcfstore.BcfStore.get_bcfxml()
props = bpy.context.scene.BCFProperties
props = context.scene.BCFProperties
blender_topic = props.topics[props.active_topic_index]
topic = bcfxml.topics[blender_topic.name]
viewpoint = bcf.v2.data.Viewpoint()
if bpy.context.scene.camera.data.type == "ORTHO":
if context.scene.camera.data.type == "ORTHO":
camera = bcf.v2.data.OrthogonalCamera()
camera.view_to_world_scale = bpy.context.scene.camera.data.ortho_scale
camera.view_to_world_scale = context.scene.camera.data.ortho_scale
viewpoint.orthogonal_camera = camera
elif bpy.context.scene.camera.data.type == "PERSP":
elif context.scene.camera.data.type == "PERSP":
camera = bcf.v2.data.PerspectiveCamera()
camera.field_of_view = degrees(bpy.context.scene.camera.data.angle)
camera.field_of_view = degrees(context.scene.camera.data.angle)
viewpoint.perspective_camera = camera
camera.camera_view_point.x = bpy.context.scene.camera.location.x
camera.camera_view_point.y = bpy.context.scene.camera.location.y
camera.camera_view_point.z = bpy.context.scene.camera.location.z
direction = bpy.context.scene.camera.matrix_world.to_quaternion() @ Vector((0.0, 0.0, -1.0))
camera.camera_view_point.x = context.scene.camera.location.x
camera.camera_view_point.y = context.scene.camera.location.y
camera.camera_view_point.z = context.scene.camera.location.z
direction = context.scene.camera.matrix_world.to_quaternion() @ Vector((0.0, 0.0, -1.0))
camera.camera_direction.x = direction.x
camera.camera_direction.y = direction.y
camera.camera_direction.z = direction.z
up = bpy.context.scene.camera.matrix_world.to_quaternion() @ Vector((0.0, 1.0, 0.0))
up = context.scene.camera.matrix_world.to_quaternion() @ Vector((0.0, 1.0, 0.0))
camera.camera_up_vector.x = up.x
camera.camera_up_vector.y = up.y
camera.camera_up_vector.z = up.z
old_file_format = bpy.context.scene.render.image_settings.file_format
bpy.context.scene.render.image_settings.file_format = "PNG"
old_filepath = bpy.context.scene.render.filepath
bpy.context.scene.render.filepath = os.path.join(bpy.context.scene.BIMProperties.data_dir, "snapshot.png")
old_file_format = context.scene.render.image_settings.file_format
context.scene.render.image_settings.file_format = "PNG"
old_filepath = context.scene.render.filepath
context.scene.render.filepath = os.path.join(context.scene.BIMProperties.data_dir, "snapshot.png")
bpy.ops.render.opengl(write_still=True)
viewpoint.snapshot = bpy.context.scene.render.filepath
viewpoint.snapshot = context.scene.render.filepath
bcfxml.add_viewpoint(topic, viewpoint)
bpy.context.scene.render.filepath = old_filepath
bpy.context.scene.render.image_settings.file_format = old_file_format
context.scene.render.filepath = old_filepath
context.scene.render.image_settings.file_format = old_file_format
props.active_topic_index = props.active_topic_index # refreshes the BCF Topic
return {"FINISHED"}
@@ -390,7 +390,7 @@ class RemoveBcfViewpoint(bpy.types.Operator):
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
props = bpy.context.scene.BCFProperties
props = context.scene.BCFProperties
blender_topic = props.topics[props.active_topic_index]
viewpoint_guid = blender_topic.viewpoints
topic = bcfxml.topics[blender_topic.name]
@@ -407,7 +407,7 @@ class RemoveBcfFile(bpy.types.Operator):
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
props = bpy.context.scene.BCFProperties
props = context.scene.BCFProperties
blender_topic = props.topics[props.active_topic_index]
topic = bcfxml.topics[blender_topic.name]
bcfxml.delete_file(topic, self.index)
@@ -422,7 +422,7 @@ class AddBcfReferenceLink(bpy.types.Operator):
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
props = bpy.context.scene.BCFProperties
props = context.scene.BCFProperties
blender_topic = props.topics[props.active_topic_index]
topic = bcfxml.topics[blender_topic.name]
if not blender_topic.reference_link:
@@ -441,7 +441,7 @@ class AddBcfDocumentReference(bpy.types.Operator):
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
props = bpy.context.scene.BCFProperties
props = context.scene.BCFProperties
blender_topic = props.topics[props.active_topic_index]
topic = bcfxml.topics[blender_topic.name]
if not blender_topic.document_reference:
@@ -463,7 +463,7 @@ class AddBcfLabel(bpy.types.Operator):
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
props = bpy.context.scene.BCFProperties
props = context.scene.BCFProperties
blender_topic = props.topics[props.active_topic_index]
topic = bcfxml.topics[blender_topic.name]
if not blender_topic.label:
@@ -483,7 +483,7 @@ class EditBcfReferenceLinks(bpy.types.Operator):
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
props = bpy.context.scene.BCFProperties
props = context.scene.BCFProperties
blender_topic = props.topics[props.active_topic_index]
topic = bcfxml.topics[blender_topic.name]
for index, reference_link in enumerate(topic.reference_links):
@@ -501,7 +501,7 @@ class EditBcfLabels(bpy.types.Operator):
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
props = bpy.context.scene.BCFProperties
props = context.scene.BCFProperties
blender_topic = props.topics[props.active_topic_index]
topic = bcfxml.topics[blender_topic.name]
for index, label in enumerate(blender_topic.labels):
@@ -520,7 +520,7 @@ class RemoveBcfReferenceLink(bpy.types.Operator):
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
props = bpy.context.scene.BCFProperties
props = context.scene.BCFProperties
blender_topic = props.topics[props.active_topic_index]
topic = bcfxml.topics[blender_topic.name]
del topic.reference_links[self.index]
@@ -537,7 +537,7 @@ class RemoveBcfLabel(bpy.types.Operator):
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
props = bpy.context.scene.BCFProperties
props = context.scene.BCFProperties
blender_topic = props.topics[props.active_topic_index]
topic = bcfxml.topics[blender_topic.name]
del topic.labels[self.index]
@@ -553,7 +553,7 @@ class RemoveBcfBimSnippet(bpy.types.Operator):
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
props = bpy.context.scene.BCFProperties
props = context.scene.BCFProperties
blender_topic = props.topics[props.active_topic_index]
topic = bcfxml.topics[blender_topic.name]
bcfxml.delete_bim_snippet(topic)
@@ -571,7 +571,7 @@ class RemoveBcfDocumentReference(bpy.types.Operator):
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
props = bpy.context.scene.BCFProperties
props = context.scene.BCFProperties
blender_topic = props.topics[props.active_topic_index]
topic = bcfxml.topics[blender_topic.name]
bcfxml.delete_document_reference(topic, self.index)
@@ -587,7 +587,7 @@ class RemoveBcfRelatedTopic(bpy.types.Operator):
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
props = bpy.context.scene.BCFProperties
props = context.scene.BCFProperties
blender_topic = props.topics[props.active_topic_index]
topic = bcfxml.topics[blender_topic.name]
del topic.related_topics[self.index]
@@ -604,7 +604,7 @@ class RemoveBcfComment(bpy.types.Operator):
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
props = bpy.context.scene.BCFProperties
props = context.scene.BCFProperties
blender_topic = props.topics[props.active_topic_index]
topic = bcfxml.topics[blender_topic.name]
bcfxml.delete_comment(self.comment_guid, topic)
@@ -620,7 +620,7 @@ class EditBcfComment(bpy.types.Operator):
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
props = bpy.context.scene.BCFProperties
props = context.scene.BCFProperties
blender_topic = props.topics[props.active_topic_index]
blender_comment = blender_topic.comments.get(self.comment_guid)
topic = bcfxml.topics[blender_topic.name]
@@ -639,7 +639,7 @@ class AddBcfComment(bpy.types.Operator):
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
props = bpy.context.scene.BCFProperties
props = context.scene.BCFProperties
blender_topic = props.topics[props.active_topic_index]
topic = bcfxml.topics[blender_topic.name]
if not blender_topic.comment:
@@ -662,7 +662,7 @@ class ActivateBcfViewpoint(bpy.types.Operator):
def execute(self, context):
self.file = IfcStore.get_file()
bcfxml = bcfstore.BcfStore.get_bcfxml()
props = bpy.context.scene.BCFProperties
props = context.scene.BCFProperties
blender_topic = props.topics[props.active_topic_index]
topic = bcfxml.topics[blender_topic.name]
if not topic.viewpoints:
@@ -673,11 +673,11 @@ class ActivateBcfViewpoint(bpy.types.Operator):
obj = bpy.data.objects.get("Viewpoint")
if not obj:
obj = bpy.data.objects.new("Viewpoint", bpy.data.cameras.new("Viewpoint"))
bpy.context.scene.collection.objects.link(obj)
bpy.context.scene.camera = obj
context.scene.collection.objects.link(obj)
context.scene.camera = obj
cam_width = bpy.context.scene.render.resolution_x
cam_height = bpy.context.scene.render.resolution_y
cam_width = context.scene.render.resolution_x
cam_height = context.scene.render.resolution_y
cam_aspect = cam_width / cam_height
if viewpoint.snapshot:
@@ -697,7 +697,7 @@ class ActivateBcfViewpoint(bpy.types.Operator):
else:
background.frame_method = "CROP"
background.display_depth = "FRONT"
area = next(area for area in bpy.context.screen.areas if area.type == "VIEW_3D")
area = next(area for area in context.screen.areas if area.type == "VIEW_3D")
area.spaces[0].region_3d.view_perspective = "CAMERA"
if viewpoint.orthogonal_camera:
@@ -719,13 +719,13 @@ class ActivateBcfViewpoint(bpy.types.Operator):
if gp:
bpy.data.grease_pencils.remove(gp)
if viewpoint.lines:
self.draw_lines(viewpoint)
self.draw_lines(viewpoint, context)
self.delete_clipping_planes()
self.delete_clipping_planes(context)
if viewpoint.clipping_planes:
self.create_clipping_planes(viewpoint)
self.delete_bitmaps()
self.delete_bitmaps(context)
if viewpoint.bitmaps:
self.create_bitmaps(bcfxml, viewpoint, topic)
@@ -776,9 +776,9 @@ class ActivateBcfViewpoint(bpy.types.Operator):
if global_id in global_id_colours:
obj.color = self.hex_to_rgb(global_id_colours[global_id])
def draw_lines(self, viewpoint):
def draw_lines(self, viewpoint, context):
gp = bpy.data.grease_pencils.new("BCF")
scene = bpy.context.scene
scene = context.scene
scene.grease_pencil = gp
scene.frame_set(1)
layer = gp.layers.new("BCF Annotation", set_active=True)
@@ -808,19 +808,19 @@ class ActivateBcfViewpoint(bpy.types.Operator):
)
n += 1
def delete_clipping_planes(self):
def delete_clipping_planes(self, context):
collection = bpy.data.collections.get("Sections")
if not collection:
return
for section in collection.objects:
bpy.context.view_layer.objects.active = section
context.view_layer.objects.active = section
bpy.ops.bim.remove_section_plane()
def delete_bitmaps(self):
def delete_bitmaps(self, context):
collection = bpy.data.collections.get("Bitmaps")
if not collection:
collection = bpy.data.collections.new("Bitmaps")
bpy.context.scene.collection.children.link(collection)
context.scene.collection.children.link(collection)
for bitmap in collection.objects:
bpy.data.objects.remove(bitmap)
@@ -860,7 +860,7 @@ class OpenBcfReferenceLink(bpy.types.Operator):
index: bpy.props.IntProperty()
def execute(self, context):
webbrowser.open(bpy.context.scene.BCFProperties.topic_links[self.index].name)
webbrowser.open(context.scene.BCFProperties.topic_links[self.index].name)
return {"FINISHED"}
@@ -872,7 +872,7 @@ class SelectBcfHeaderFile(bpy.types.Operator):
def execute(self, context):
if self.filepath:
props = bpy.context.scene.BCFProperties
props = context.scene.BCFProperties
topic = props.topics[props.active_topic_index]
topic.file_reference = self.filepath
return {"FINISHED"}
@@ -890,7 +890,7 @@ class SelectBcfBimSnippetReference(bpy.types.Operator):
def execute(self, context):
if self.filepath:
props = bpy.context.scene.BCFProperties
props = context.scene.BCFProperties
topic = props.topics[props.active_topic_index]
topic.bim_snippet_reference = self.filepath
return {"FINISHED"}
@@ -908,7 +908,7 @@ class SelectBcfDocumentReference(bpy.types.Operator):
def execute(self, context):
if self.filepath:
props = bpy.context.scene.BCFProperties
props = context.scene.BCFProperties
topic = props.topics[props.active_topic_index]
topic.document_reference = self.filepath
return {"FINISHED"}
@@ -23,37 +23,37 @@ def purge():
def updateBcfReferenceLink(self, context):
if bpy.context.scene.BCFProperties.is_loaded:
if context.scene.BCFProperties.is_loaded:
bpy.ops.bim.edit_bcf_reference_links()
def updateBcfLabel(self, context):
if bpy.context.scene.BCFProperties.is_loaded:
if context.scene.BCFProperties.is_loaded:
bpy.ops.bim.edit_bcf_labels()
def updateBcfProjectName(self, context):
if bpy.context.scene.BCFProperties.is_loaded:
if context.scene.BCFProperties.is_loaded:
bpy.ops.bim.edit_bcf_project_name()
def updateBcfAuthor(self, context):
if bpy.context.scene.BCFProperties.is_loaded:
if context.scene.BCFProperties.is_loaded:
bpy.ops.bim.edit_bcf_author()
def updateBcfTopicName(self, context):
if bpy.context.scene.BCFProperties.is_loaded:
if context.scene.BCFProperties.is_loaded:
bpy.ops.bim.edit_bcf_topic_name()
def updateBcfTopicIsEditable(self, context):
if bpy.context.scene.BCFProperties.is_loaded and not self.is_editable:
if context.scene.BCFProperties.is_loaded and not self.is_editable:
bpy.ops.bim.edit_bcf_topic()
def updateBcfCommentIsEditable(self, context):
if bpy.context.scene.BCFProperties.is_loaded and not self.is_editable:
if context.scene.BCFProperties.is_loaded and not self.is_editable:
bpy.ops.bim.edit_bcf_comment(comment_guid = self.name)
@@ -61,7 +61,7 @@ def refreshBcfTopic(self, context):
global bcfviewpoints_enum
bcfviewpoints_enum = None
props = bpy.context.scene.BCFProperties
props = context.scene.BCFProperties
bcfxml = bcfstore.BcfStore.get_bcfxml()
topic = props.topics[props.active_topic_index]
header = bcfxml.get_header(topic.name)
@@ -80,7 +80,7 @@ def getBcfViewpoints(self, context):
global bcfviewpoints_enum
if bcfviewpoints_enum is None:
bcfviewpoints_enum = []
props = bpy.context.scene.BCFProperties
props = context.scene.BCFProperties
bcfxml = bcfstore.BcfStore.get_bcfxml()
topic = props.topics[props.active_topic_index]
viewpoints = bcfxml.get_viewpoints(topic.name)
@@ -17,7 +17,7 @@ class BIM_PT_bcf(Panel):
layout.use_property_decorate = False
scene = context.scene
props = bpy.context.scene.BCFProperties
props = scene.BCFProperties
row = layout.row(align=True)
row.operator("bim.new_bcf_project", text="New Project")
@@ -34,7 +34,7 @@ class BIM_PT_bcf(Panel):
row = layout.row()
row.prop(props, "author")
props = bpy.context.scene.BCFProperties
props = context.scene.BCFProperties
row = layout.row()
row.template_list("BIM_UL_topics", "", props, "topics", props, "active_topic_index")
col = row.column(align=True)
@@ -93,7 +93,7 @@ class BIM_PT_bcf_metadata(Panel):
layout.use_property_decorate = False
scene = context.scene
props = bpy.context.scene.BCFProperties
props = scene.BCFProperties
if props.active_topic_index >= len(props.topics):
layout.label(text="No BCF project is loaded")
@@ -236,7 +236,7 @@ class BIM_PT_bcf_comments(Panel):
layout.use_property_decorate = False
scene = context.scene
props = bpy.context.scene.BCFProperties
props = scene.BCFProperties
if props.active_topic_index >= len(props.topics):
layout.label(text="No BCF project is loaded")
@@ -52,11 +52,11 @@ class BIMTesterPurge(bpy.types.Operator):
def execute(self, context):
filename = os.path.join(
bpy.context.scene.BimTesterProperties.features_dir,
bpy.context.scene.BimTesterProperties.features_file + ".feature",
context.scene.BimTesterProperties.features_dir,
context.scene.BimTesterProperties.features_file + ".feature",
)
cwd = os.getcwd()
os.chdir(bpy.context.scene.BimTesterProperties.features_dir)
os.chdir(context.scene.BimTesterProperties.features_dir)
bimtester.clean.TestPurger().purge()
os.chdir(cwd)
return {"FINISHED"}
@@ -71,7 +71,7 @@ class SelectFeature(bpy.types.Operator):
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def execute(self, context):
bpy.context.scene.BimTesterProperties.feature = self.filepath
context.scene.BimTesterProperties.feature = self.filepath
return {"FINISHED"}
def invoke(self, context, event):
@@ -88,7 +88,7 @@ class SelectSteps(bpy.types.Operator):
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def execute(self, context):
bpy.context.scene.BimTesterProperties.steps = self.filepath
context.scene.BimTesterProperties.steps = self.filepath
return {"FINISHED"}
def invoke(self, context, event):
@@ -120,14 +120,14 @@ class RejectElement(bpy.types.Operator):
def execute(self, context):
lines = []
self.file = IfcStore.get_file()
for obj in bpy.context.selected_objects:
for obj in context.selected_objects:
lines.append(
" * The element {} should not exist because {}".format(
self.file.by_id(obj.BIMObjectProperties.ifc_definition_id).GlobalId,
bpy.context.scene.BimTesterProperties.qa_reject_element_reason,
context.scene.BimTesterProperties.qa_reject_element_reason,
)
)
QAHelper.append_to_scenario(lines)
QAHelper.append_to_scenario(lines, context)
return {"FINISHED"}
@@ -138,12 +138,12 @@ class ApproveClass(bpy.types.Operator):
def execute(self, context):
lines = []
self.file = IfcStore.get_file()
for obj in bpy.context.selected_objects:
for obj in context.selected_objects:
if not obj.BIMObjectProperties.ifc_definition_id:
continue
element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
lines.append(" * The element {} is an {}".format(element.GlobalId, element.is_a()))
QAHelper.append_to_scenario(lines)
QAHelper.append_to_scenario(lines, context)
return {"FINISHED"}
@@ -154,16 +154,16 @@ class RejectClass(bpy.types.Operator):
def execute(self, context):
lines = []
self.file = IfcStore.get_file()
for obj in bpy.context.selected_objects:
for obj in context.selected_objects:
if not obj.BIMObjectProperties.ifc_definition_id:
continue
lines.append(
" * The element {} is an {}".format(
self.file.by_id(obj.BIMObjectProperties.ifc_definition_id).GlobalId,
bpy.context.scene.BimTesterProperties.audit_ifc_class,
context.scene.BimTesterProperties.audit_ifc_class,
)
)
QAHelper.append_to_scenario(lines)
QAHelper.append_to_scenario(lines, context)
return {"FINISHED"}
@@ -175,7 +175,7 @@ class SelectAudited(bpy.types.Operator):
def execute(self, context):
audited_global_ids = []
self.file = IfcStore.get_file()
for filename in Path(bpy.context.scene.BimTesterProperties.features_dir).glob("*.feature"):
for filename in Path(context.scene.BimTesterProperties.features_dir).glob("*.feature"):
with open(filename, "r") as feature_file:
lines = feature_file.readlines()
for line in lines:
@@ -183,7 +183,7 @@ class SelectAudited(bpy.types.Operator):
for word in words:
if self.is_a_global_id(word):
audited_global_ids.append(word)
for obj in bpy.context.visible_objects:
for obj in context.visible_objects:
if not obj.BIMObjectProperties.ifc_definition_id:
continue
if self.file.by_id(obj.BIMObjectProperties.ifc_definition_id).GlobalId in audited_global_ids:
@@ -196,10 +196,10 @@ class SelectAudited(bpy.types.Operator):
class QAHelper:
@classmethod
def append_to_scenario(cls, lines):
def append_to_scenario(cls, lines, context):
filename = os.path.join(
bpy.context.scene.BimTesterProperties.features_dir,
bpy.context.scene.BimTesterProperties.features_file + ".feature",
context.scene.BimTesterProperties.features_dir,
context.scene.BimTesterProperties.features_file + ".feature",
)
if os.path.exists(filename + "~"):
os.remove(filename + "~")
@@ -210,7 +210,7 @@ class QAHelper:
for source_line in source:
if (
"Scenario: " in source_line
and bpy.context.scene.BimTesterProperties.scenario == source_line.strip()[len("Scenario: ") :]
and context.scene.BimTesterProperties.scenario == source_line.strip()[len("Scenario: ") :]
):
is_in_scenario = True
elif is_in_scenario:
@@ -23,7 +23,7 @@ class ExportClashSets(bpy.types.Operator):
def execute(self, context):
self.filepath = bpy.path.ensure_ext(self.filepath, ".json")
clash_sets = []
for clash_set in bpy.context.scene.BIMClashProperties.clash_sets:
for clash_set in context.scene.BIMClashProperties.clash_sets:
self.a = []
self.b = []
for ab in ["a", "b"]:
@@ -56,7 +56,7 @@ class ImportClashSets(bpy.types.Operator):
with open(self.filepath) as f:
clash_sets = json.load(f)
for clash_set in clash_sets:
new = bpy.context.scene.BIMClashProperties.clash_sets.add()
new = context.scene.BIMClashProperties.clash_sets.add()
new.name = clash_set["name"]
new.tolerance = clash_set["tolerance"]
for clash_source in clash_set["a"]:
@@ -81,7 +81,7 @@ class AddClashSet(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
new = bpy.context.scene.BIMClashProperties.clash_sets.add()
new = context.scene.BIMClashProperties.clash_sets.add()
new.name = "New Clash Set"
new.tolerance = 0.01
return {"FINISHED"}
@@ -94,7 +94,7 @@ class RemoveClashSet(bpy.types.Operator):
index: bpy.props.IntProperty()
def execute(self, context):
bpy.context.scene.BIMClashProperties.clash_sets.remove(self.index)
context.scene.BIMClashProperties.clash_sets.remove(self.index)
return {"FINISHED"}
@@ -105,7 +105,7 @@ class AddClashSource(bpy.types.Operator):
group: bpy.props.StringProperty()
def execute(self, context):
clash_set = bpy.context.scene.BIMClashProperties.clash_sets[bpy.context.scene.BIMClashProperties.active_clash_set_index]
clash_set = context.scene.BIMClashProperties.clash_sets[context.scene.BIMClashProperties.active_clash_set_index]
source = getattr(clash_set, self.group).add()
return {"FINISHED"}
@@ -118,7 +118,7 @@ class RemoveClashSource(bpy.types.Operator):
group: bpy.props.StringProperty()
def execute(self, context):
clash_set = bpy.context.scene.BIMClashProperties.clash_sets[bpy.context.scene.BIMClashProperties.active_clash_set_index]
clash_set = context.scene.BIMClashProperties.clash_sets[context.scene.BIMClashProperties.active_clash_set_index]
getattr(clash_set, self.group).remove(self.index)
return {"FINISHED"}
@@ -133,7 +133,7 @@ class SelectClashSource(bpy.types.Operator):
group: bpy.props.StringProperty()
def execute(self, context):
clash_set = bpy.context.scene.BIMClashProperties.clash_sets[bpy.context.scene.BIMClashProperties.active_clash_set_index]
clash_set = context.scene.BIMClashProperties.clash_sets[context.scene.BIMClashProperties.active_clash_set_index]
getattr(clash_set, self.group)[self.index].name = self.filepath
return {"FINISHED"}
@@ -149,7 +149,7 @@ class SelectClashResults(bpy.types.Operator):
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def execute(self, context):
bpy.context.scene.BIMClashProperties.clash_results_path = self.filepath
context.scene.BIMClashProperties.clash_results_path = self.filepath
return {"FINISHED"}
def invoke(self, context, event):
@@ -164,7 +164,7 @@ class SelectSmartGroupedClashesPath(bpy.types.Operator):
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def execute(self, context):
bpy.context.scene.BIMClashProperties.smart_grouped_clashes_path = self.filepath
context.scene.BIMClashProperties.smart_grouped_clashes_path = self.filepath
return {"FINISHED"}
def invoke(self, context, event):
@@ -196,32 +196,32 @@ class ExecuteIfcClash(bpy.types.Operator):
settings.logger.setLevel(logging.DEBUG)
ifc_clasher = ifcclash.IfcClasher(settings)
if bpy.context.scene.BIMClashProperties.should_create_clash_snapshots:
if context.scene.BIMClashProperties.should_create_clash_snapshots:
def get_viewpoint_snapshot(self, viewpoint, mat):
camera = bpy.data.objects.get("IFC Clash Camera")
if not camera:
camera = bpy.data.objects.new("IFC Clash Camera", bpy.data.cameras.new("IFC Clash Camera"))
bpy.context.scene.collection.objects.link(camera)
context.scene.collection.objects.link(camera)
camera.matrix_world = Matrix(mat)
bpy.context.scene.camera = camera
context.scene.camera = camera
camera.data.angle = radians(60)
area = next(area for area in bpy.context.screen.areas if area.type == "VIEW_3D")
area = next(area for area in context.screen.areas if area.type == "VIEW_3D")
area.spaces[0].region_3d.view_perspective = "CAMERA"
area.spaces[0].shading.show_xray = True
bpy.context.scene.render.resolution_x = 480
bpy.context.scene.render.resolution_y = 270
bpy.context.scene.render.image_settings.file_format = "PNG"
bpy.context.scene.render.filepath = os.path.join(
bpy.context.scene.BIMProperties.data_dir, "snapshot.png"
context.scene.render.resolution_x = 480
context.scene.render.resolution_y = 270
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 bpy.context.scene.render.filepath
return context.scene.render.filepath
ifcclash.IfcClasher.get_viewpoint_snapshot = get_viewpoint_snapshot
ifc_clasher.clash_sets = []
for clash_set in bpy.context.scene.BIMClashProperties.clash_sets:
for clash_set in context.scene.BIMClashProperties.clash_sets:
self.a = []
self.b = []
for ab in ["a", "b"]:
@@ -256,8 +256,8 @@ class SelectIfcClashResults(bpy.types.Operator):
self.filepath = bpy.path.ensure_ext(self.filepath, ".json")
with open(self.filepath) as f:
clash_sets = json.load(f)
clash_set_name = bpy.context.scene.BIMClashProperties.clash_sets[
bpy.context.scene.BIMClashProperties.active_clash_set_index
clash_set_name = context.scene.BIMClashProperties.clash_sets[
context.scene.BIMClashProperties.active_clash_set_index
].name
global_ids = []
for clash_set in clash_sets:
@@ -268,7 +268,7 @@ class SelectIfcClashResults(bpy.types.Operator):
return {"CANCELLED"}
for clash in clash_set["clashes"].values():
global_ids.extend([clash["a_global_id"], clash["b_global_id"]])
for obj in bpy.context.visible_objects:
for obj in context.visible_objects:
if not obj.BIMObjectProperties.ifc_definition_id:
continue
element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
@@ -287,7 +287,7 @@ class SmartClashGroup(bpy.types.Operator):
import ifcclash
settings = ifcclash.IfcClashSettings()
self.filepath = bpy.path.ensure_ext(bpy.context.scene.BIMClashProperties.clash_results_path, ".json")
self.filepath = bpy.path.ensure_ext(context.scene.BIMClashProperties.clash_results_path, ".json")
settings.output = self.filepath
settings.logger = logging.getLogger("Clash")
settings.logger.setLevel(logging.DEBUG)
@@ -297,21 +297,21 @@ class SmartClashGroup(bpy.types.Operator):
clash_sets = json.load(f)
# execute the smart grouping
save_path = bpy.path.ensure_ext(bpy.context.scene.BIMClashProperties.smart_grouped_clashes_path, ".json")
save_path = bpy.path.ensure_ext(context.scene.BIMClashProperties.smart_grouped_clashes_path, ".json")
smart_grouped_clashes = ifc_clasher.smart_group_clashes(
clash_sets, bpy.context.scene.BIMClashProperties.smart_clash_grouping_max_distance
clash_sets, context.scene.BIMClashProperties.smart_clash_grouping_max_distance
)
# save smart_groups to json
with open(save_path, "w") as f:
f.write(json.dumps(smart_grouped_clashes))
clash_set_name = bpy.context.scene.BIMClashProperties.clash_sets[
bpy.context.scene.BIMClashProperties.active_clash_set_index
clash_set_name = context.scene.BIMClashProperties.clash_sets[
context.scene.BIMClashProperties.active_clash_set_index
].name
# Reset the list of smart_clash_groups for the UI
bpy.context.scene.BIMClashProperties.smart_clash_groups.clear()
context.scene.BIMClashProperties.smart_clash_groups.clear()
for clash_set, smart_groups in smart_grouped_clashes.items():
# Only select the clashes that correspond to the actively selected IFC Clash Set
@@ -319,7 +319,7 @@ class SmartClashGroup(bpy.types.Operator):
continue
else:
for smart_group, global_id_pairs in smart_groups[0].items():
new_group = bpy.context.scene.BIMClashProperties.smart_clash_groups.add()
new_group = context.scene.BIMClashProperties.smart_clash_groups.add()
new_group.number = f"{smart_group}"
for pair in global_id_pairs:
@@ -336,17 +336,17 @@ class LoadSmartGroupsForActiveClashSet(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
smart_groups_path = bpy.path.ensure_ext(bpy.context.scene.BIMClashProperties.smart_grouped_clashes_path, ".json")
smart_groups_path = bpy.path.ensure_ext(context.scene.BIMClashProperties.smart_grouped_clashes_path, ".json")
clash_set_name = bpy.context.scene.BIMClashProperties.clash_sets[
bpy.context.scene.BIMClashProperties.active_clash_set_index
clash_set_name = context.scene.BIMClashProperties.clash_sets[
context.scene.BIMClashProperties.active_clash_set_index
].name
with open(smart_groups_path) as f:
smart_grouped_clashes = json.load(f)
# Reset the list of smart_clash_groups for the UI
bpy.context.scene.BIMClashProperties.smart_clash_groups.clear()
context.scene.BIMClashProperties.smart_clash_groups.clear()
for clash_set, smart_groups in smart_grouped_clashes.items():
# Only select the clashes that correspond to the actively selected IFC Clash Set
@@ -354,7 +354,7 @@ class LoadSmartGroupsForActiveClashSet(bpy.types.Operator):
continue
else:
for smart_group, global_id_pairs in smart_groups[0].items():
new_group = bpy.context.scene.BIMClashProperties.smart_clash_groups.add()
new_group = context.scene.BIMClashProperties.smart_clash_groups.add()
new_group.number = f"{smart_group}"
for pair in global_id_pairs:
for id in pair:
@@ -371,12 +371,12 @@ class SelectSmartGroup(bpy.types.Operator):
def execute(self, context):
# Select smart group in view
selected_smart_group = bpy.context.scene.BIMClashProperties.smart_clash_groups[
bpy.context.scene.BIMCLashProperties.active_smart_group_index
selected_smart_group = context.scene.BIMClashProperties.smart_clash_groups[
context.scene.BIMCLashProperties.active_smart_group_index
]
# print(selected_smart_group.number)
for obj in bpy.context.visible_objects:
for obj in context.visible_objects:
if not obj.BIMObjectProperties.ifc_definition_id:
continue
element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
@@ -391,13 +391,13 @@ class SelectSmartGroup(bpy.types.Operator):
class BlenderClasher:
def process_clash_set(self):
def process_clash_set(self, context):
import collision
a_cm = collision.CollisionManager()
b_cm = collision.CollisionManager()
self.add_to_cm(a_cm, bpy.context.scene.BIMClashProperties.blender_clash_set_a)
self.add_to_cm(b_cm, bpy.context.scene.BIMClashProperties.blender_clash_set_b)
self.add_to_cm(a_cm, context.scene.BIMClashProperties.blender_clash_set_a, context)
self.add_to_cm(b_cm, context.scene.BIMClashProperties.blender_clash_set_b, context)
results = a_cm.in_collision_other(b_cm, return_data=True)
if not results[0]:
print("No clashes")
@@ -410,20 +410,20 @@ class BlenderClasher:
print(contact.raw.normal)
print(contact.raw.pos)
def add_to_cm(self, cm, object_names):
def add_to_cm(self, cm, object_names, context):
import ifcclash
for object_name in object_names:
name = object_name.name
obj = bpy.data.objects[name]
triangulated_mesh = self.triangulate_mesh(obj)
triangulated_mesh = self.triangulate_mesh(obj, context)
mesh = ifcclash.Mesh()
mesh.vertices = np.array([tuple(obj.matrix_world @ v.co) for v in triangulated_mesh.vertices])
mesh.faces = np.array([tuple(p.vertices) for p in triangulated_mesh.polygons])
cm.add_object(name, mesh)
def triangulate_mesh(self, obj):
mesh = obj.evaluated_get(bpy.context.evaluated_depsgraph_get()).to_mesh()
def triangulate_mesh(self, obj, context):
mesh = obj.evaluated_get(context.evaluated_depsgraph_get()).to_mesh()
bm = bmesh.new()
bm.from_mesh(mesh)
bmesh.ops.triangulate(bm, faces=bm.faces)
@@ -439,10 +439,10 @@ class SetBlenderClashSetA(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
while len(bpy.context.scene.BIMClashProperties.blender_clash_set_a) > 0:
bpy.context.scene.BIMClashProperties.blender_clash_set_a.remove(0)
for obj in bpy.context.selected_objects:
new = bpy.context.scene.BIMClashProperties.blender_clash_set_a.add()
while len(context.scene.BIMClashProperties.blender_clash_set_a) > 0:
context.scene.BIMClashProperties.blender_clash_set_a.remove(0)
for obj in context.selected_objects:
new = context.scene.BIMClashProperties.blender_clash_set_a.add()
new.name = obj.name
return {"FINISHED"}
@@ -453,10 +453,10 @@ class SetBlenderClashSetB(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
while len(bpy.context.scene.BIMClashProperties.blender_clash_set_b) > 0:
bpy.context.scene.BIMClashProperties.blender_clash_set_b.remove(0)
for obj in bpy.context.selected_objects:
new = bpy.context.scene.BIMClashProperties.blender_clash_set_b.add()
while len(context.scene.BIMClashProperties.blender_clash_set_b) > 0:
context.scene.BIMClashProperties.blender_clash_set_b.remove(0)
for obj in context.selected_objects:
new = context.scene.BIMClashProperties.blender_clash_set_b.add()
new.name = obj.name
return {"FINISHED"}
@@ -467,5 +467,5 @@ class ExecuteBlenderClash(bpy.types.Operator):
def execute(self, context):
blender_clasher = BlenderClasher()
blender_clasher.process_clash_set()
blender_clasher.process_clash_set(context)
return {"FINISHED"}
@@ -133,7 +133,7 @@ class EnableEditingClassificationReference(bpy.types.Operator):
obj: bpy.props.StringProperty()
def execute(self, context):
obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
props = obj.BIMClassificationReferenceProperties
while len(props.reference_attributes) > 0:
props.reference_attributes.remove(0)
@@ -157,7 +157,7 @@ class DisableEditingClassificationReference(bpy.types.Operator):
obj: bpy.props.StringProperty()
def execute(self, context):
obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
obj.BIMClassificationReferenceProperties.active_reference_id = 0
return {"FINISHED"}
@@ -173,7 +173,7 @@ class RemoveClassificationReference(bpy.types.Operator):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"classification.remove_reference",
@@ -198,7 +198,7 @@ class EditClassificationReference(bpy.types.Operator):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
props = obj.BIMClassificationReferenceProperties
attributes = {}
for attribute in props.reference_attributes:
@@ -228,7 +228,7 @@ class AddClassificationReference(bpy.types.Operator):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
self.file = IfcStore.get_file()
classification = None
@@ -15,7 +15,7 @@ class SelectCobieIfcFile(bpy.types.Operator):
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def execute(self, context):
bpy.context.scene.COBieProperties.cobie_ifc_file = self.filepath
context.scene.COBieProperties.cobie_ifc_file = self.filepath
return {"FINISHED"}
def invoke(self, context, event):
@@ -30,7 +30,7 @@ class SelectCobieJsonFile(bpy.types.Operator):
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def execute(self, context):
bpy.context.scene.COBieProperties.cobie_json_file = self.filepath
context.scene.COBieProperties.cobie_json_file = self.filepath
return {"FINISHED"}
def invoke(self, context, event):
@@ -45,7 +45,7 @@ class ExecuteIfcCobie(bpy.types.Operator):
def execute(self, context):
from cobie import IfcCobieParser
props = bpy.context.scene.COBieProperties
props = context.scene.COBieProperties
output_dir = os.path.dirname(props.cobie_ifc_file)
@@ -21,9 +21,9 @@ class AddSubcontext(bpy.types.Operator):
"context.add_context",
self.file,
**{
"context": self.context or bpy.context.scene.BIMProperties.available_contexts,
"subcontext": self.subcontext or bpy.context.scene.BIMProperties.available_subcontexts,
"target_view": self.target_view or bpy.context.scene.BIMProperties.available_target_views,
"context": self.context or context.scene.BIMProperties.available_contexts,
"subcontext": self.subcontext or context.scene.BIMProperties.available_subcontexts,
"target_view": self.target_view or context.scene.BIMProperties.available_target_views,
},
)
Data.load(IfcStore.get_file())
@@ -307,7 +307,7 @@ class AssignCostItemProduct(bpy.types.Operator):
def _execute(self, context):
related_objects = (
[bpy.data.objects.get(self.related_object)] if self.related_object else bpy.context.selected_objects
[bpy.data.objects.get(self.related_object)] if self.related_object else context.selected_objects
)
self.file = IfcStore.get_file()
ifcopenshell.api.run(
@@ -336,7 +336,7 @@ class UnassignCostItemProduct(bpy.types.Operator):
def _execute(self, context):
related_objects = (
[bpy.data.objects.get(self.related_object)] if self.related_object else bpy.context.selected_objects
[bpy.data.objects.get(self.related_object)] if self.related_object else context.selected_objects
)
self.file = IfcStore.get_file()
ifcopenshell.api.run(
@@ -621,7 +621,7 @@ class SelectCostItemProducts(bpy.types.Operator):
def execute(self, context):
self.file = IfcStore.get_file()
related_products = Data.cost_items[self.cost_item]["Controls"]
for obj in bpy.context.visible_objects:
for obj in context.visible_objects:
obj.select_set(False)
if obj.BIMObjectProperties.ifc_definition_id in related_products:
obj.select_set(True)
@@ -640,7 +640,7 @@ class SelectCostScheduleProducts(bpy.types.Operator):
for cost_item_id in Data.cost_schedules[self.cost_schedule]["Controls"]:
self.get_related_products(Data.cost_items[cost_item_id])
self.related_products = set(self.related_products)
for obj in bpy.context.visible_objects:
for obj in context.visible_objects:
obj.select_set(False)
if obj.BIMObjectProperties.ifc_definition_id in self.related_products:
obj.select_set(True)
@@ -13,7 +13,8 @@ class BIM_PT_cost_schedules(Panel):
@classmethod
def poll(cls, context):
return IfcStore.get_file()
file = IfcStore.get_file()
return file and hasattr(file, "schema") and file.schema != "IFC2X3"
def draw(self, context):
self.props = context.scene.BIMCostProperties
@@ -16,13 +16,13 @@ class Login(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
token = api.login(bpy.context.scene.CoveToolProperties.username, bpy.context.scene.CoveToolProperties.password)
token = api.login(context.scene.CoveToolProperties.username, context.scene.CoveToolProperties.password)
if token:
bpy.context.scene.CoveToolProperties.token = token
context.scene.CoveToolProperties.token = token
projects = api.get_request("projects")
for project in projects:
new_project = bpy.context.scene.CoveToolProperties.projects.add()
new_project = context.scene.CoveToolProperties.projects.add()
new_project.name = project["name"]
new_project.run_set = project["run_set"][0]
new_project.url = project["url"]
@@ -36,10 +36,10 @@ class RunSimpleAnalysis(bpy.types.Operator):
bl_label = "Run Simple Analysis"
def execute(self, context):
simple_analysis = bpy.context.scene.CoveToolProperties.simple_analysis
simple_analysis = context.scene.CoveToolProperties.simple_analysis
data = {
"run": bpy.context.scene.CoveToolProperties.projects[
bpy.context.scene.CoveToolProperties.active_project_index
"run": context.scene.CoveToolProperties.projects[
context.scene.CoveToolProperties.active_project_index
].run_set,
"si_units": simple_analysis.si_units,
"building_height": simple_analysis.building_height,
@@ -84,10 +84,10 @@ class RunAnalysis(bpy.types.Operator):
"roofs": [],
"shading_devices": [],
}
self.parse_objects()
self.parse_objects(context)
data = {
"run": bpy.context.scene.CoveToolProperties.projects[
bpy.context.scene.CoveToolProperties.active_project_index
"run": context.scene.CoveToolProperties.projects[
context.scene.CoveToolProperties.active_project_index
].run_set,
"source": "BlenderBIM",
"rotation_angle": self.get_rotation_angle(),
@@ -112,14 +112,14 @@ class RunAnalysis(bpy.types.Operator):
rotation = 360 - rotation
return rotation
def parse_objects(self):
for obj in bpy.context.visible_objects:
def parse_objects(self, context):
for obj in context.visible_objects:
covetool_category = self.get_covetool_category(obj)
if not covetool_category:
continue
if not self.has_triangulate_modifier(obj):
obj.modifiers.new(name="Triangulate", type="TRIANGULATE")
mesh = obj.evaluated_get(bpy.context.evaluated_depsgraph_get()).to_mesh()
mesh = obj.evaluated_get(context.evaluated_depsgraph_get()).to_mesh()
meshes = {}
for polygon in mesh.polygons:
normal = "{}|{}|{}".format(
@@ -197,5 +197,4 @@ class RunAnalysis(bpy.types.Operator):
return True
def is_window_skylight(self, element):
predefined_type = element.get_info().get("PredefinedType")
return predefined_type and predefined_type.string_value == "SKYLIGHT"
return element.get_info().get("PredefinedType") == "SKYLIGHT"
@@ -3,6 +3,8 @@ from . import ui, prop, operator
classes = (
operator.PrintIfcFile,
operator.PurgeIfcLinks,
operator.PrintObjectPlacement,
operator.ValidateIfcFile,
operator.ProfileImportIFC,
operator.CreateAllShapes,
@@ -1,6 +1,9 @@
import bpy
import logging
import ifcopenshell
import ifcopenshell.util.placement
import ifcopenshell.util.representation
import blenderbim.bim.handler
import blenderbim.bim.import_ifc as import_ifc
from blenderbim.bim.ifc import IfcStore
@@ -14,6 +17,24 @@ class PrintIfcFile(bpy.types.Operator):
return {"FINISHED"}
class PurgeIfcLinks(bpy.types.Operator):
bl_idname = "bim.purge_ifc_links"
bl_label = "Purge IFC Links"
def execute(self, context):
for obj in bpy.data.objects:
if obj.type in {"MESH", "EMPTY"}:
obj.BIMObjectProperties.ifc_definition_id = 0
if obj.data:
obj.data.BIMMeshProperties.ifc_definition_id = 0
for material in bpy.data.materials:
material.BIMMaterialProperties.ifc_style_id = False
context.scene.BIMProperties.ifc_file = ""
IfcStore.purge()
blenderbim.bim.handler.purge_module_data()
return {"FINISHED"}
class ValidateIfcFile(bpy.types.Operator):
bl_idname = "bim.validate_ifc_file"
bl_label = "Validate IFC File"
@@ -36,11 +57,9 @@ class ProfileImportIFC(bpy.types.Operator):
import pstats
# For Windows
filepath = bpy.context.scene.BIMProperties.ifc_file.replace('\\', '\\\\')
filepath = context.scene.BIMProperties.ifc_file.replace("\\", "\\\\")
cProfile.run(
f"import bpy; bpy.ops.import_ifc.bim(filepath='{filepath}')", "blender.prof"
)
cProfile.run(f"import bpy; bpy.ops.import_ifc.bim(filepath='{filepath}')", "blender.prof")
p = pstats.Stats("blender.prof")
p.sort_stats("cumulative").print_stats(50)
return {"FINISHED"}
@@ -56,18 +75,18 @@ class CreateAllShapes(bpy.types.Operator):
total = len(elements)
settings = ifcopenshell.geom.settings()
failures = []
excludes = () # For the developer to debug with
excludes = () # For the developer to debug with
for i, element in enumerate(elements):
if element.GlobalId in excludes:
continue
print(f'{i}/{total}:', element)
print(f"{i}/{total}:", element)
try:
shape = ifcopenshell.geom.create_shape(settings, element)
print("Success", len(shape.geometry.verts), len(shape.geometry.edges), len(shape.geometry.faces))
except:
failures.append(element)
print('***** FAILURE *****')
print('Failures:')
print("***** FAILURE *****")
print("Failures:")
for failure in failures:
print(failure)
return {"FINISHED"}
@@ -83,9 +102,9 @@ class CreateShapeFromStepId(bpy.types.Operator):
def _execute(self, context):
logger = logging.getLogger("ImportIFC")
self.ifc_import_settings = import_ifc.IfcImportSettings.factory(bpy.context, IfcStore.path, logger)
self.ifc_import_settings = import_ifc.IfcImportSettings.factory(context, IfcStore.path, logger)
self.file = IfcStore.get_file()
element = self.file.by_id(int(bpy.context.scene.BIMDebugProperties.step_id))
element = self.file.by_id(int(context.scene.BIMDebugProperties.step_id))
settings = ifcopenshell.geom.settings()
# settings.set(settings.INCLUDE_CURVES, True)
shape = ifcopenshell.geom.create_shape(settings, element)
@@ -93,7 +112,7 @@ class CreateShapeFromStepId(bpy.types.Operator):
ifc_importer.file = self.file
mesh = ifc_importer.create_mesh(element, shape)
obj = bpy.data.objects.new("Debug", mesh)
bpy.context.scene.collection.objects.link(obj)
context.scene.collection.objects.link(obj)
return {"FINISHED"}
@@ -106,7 +125,7 @@ class SelectHighPolygonMeshes(bpy.types.Operator):
results = {}
for obj in bpy.data.objects:
if not isinstance(obj.data, bpy.types.Mesh) or len(obj.data.polygons) < int(
bpy.context.scene.BIMDebugProperties.number_of_polygons
context.scene.BIMDebugProperties.number_of_polygons
):
continue
try:
@@ -122,7 +141,7 @@ class RewindInspector(bpy.types.Operator):
bl_label = "Rewind Inspector"
def execute(self, context):
props = bpy.context.scene.BIMDebugProperties
props = context.scene.BIMDebugProperties
total_breadcrumbs = len(props.step_id_breadcrumb)
if total_breadcrumbs < 2:
return {"FINISHED"}
@@ -140,18 +159,18 @@ class InspectFromStepId(bpy.types.Operator):
def execute(self, context):
self.file = IfcStore.get_file()
bpy.context.scene.BIMDebugProperties.active_step_id = self.step_id
crumb = bpy.context.scene.BIMDebugProperties.step_id_breadcrumb.add()
context.scene.BIMDebugProperties.active_step_id = self.step_id
crumb = context.scene.BIMDebugProperties.step_id_breadcrumb.add()
crumb.name = str(self.step_id)
element = self.file.by_id(self.step_id)
while len(bpy.context.scene.BIMDebugProperties.attributes) > 0:
bpy.context.scene.BIMDebugProperties.attributes.remove(0)
while len(bpy.context.scene.BIMDebugProperties.inverse_attributes) > 0:
bpy.context.scene.BIMDebugProperties.inverse_attributes.remove(0)
while len(bpy.context.scene.BIMDebugProperties.inverse_references) > 0:
bpy.context.scene.BIMDebugProperties.inverse_references.remove(0)
while len(context.scene.BIMDebugProperties.attributes) > 0:
context.scene.BIMDebugProperties.attributes.remove(0)
while len(context.scene.BIMDebugProperties.inverse_attributes) > 0:
context.scene.BIMDebugProperties.inverse_attributes.remove(0)
while len(context.scene.BIMDebugProperties.inverse_references) > 0:
context.scene.BIMDebugProperties.inverse_references.remove(0)
for key, value in element.get_info().items():
self.add_attribute(bpy.context.scene.BIMDebugProperties.attributes, key, value)
self.add_attribute(context.scene.BIMDebugProperties.attributes, key, value)
for key in dir(element):
if (
not key[0].isalpha()
@@ -160,9 +179,9 @@ class InspectFromStepId(bpy.types.Operator):
or not getattr(element, key)
):
continue
self.add_attribute(bpy.context.scene.BIMDebugProperties.inverse_attributes, key, getattr(element, key))
self.add_attribute(context.scene.BIMDebugProperties.inverse_attributes, key, getattr(element, key))
for inverse in self.file.get_inverse(element):
new = bpy.context.scene.BIMDebugProperties.inverse_references.add()
new = context.scene.BIMDebugProperties.inverse_references.add()
new.string_value = str(inverse)
new.int_value = inverse.id()
return {"FINISHED"}
@@ -186,8 +205,18 @@ class InspectFromObject(bpy.types.Operator):
bl_label = "Inspect From Object"
def execute(self, context):
ifc_definition_id = bpy.context.active_object.BIMObjectProperties.ifc_definition_id
ifc_definition_id = context.active_object.BIMObjectProperties.ifc_definition_id
if not ifc_definition_id:
return {"FINISHED"}
bpy.ops.bim.inspect_from_step_id(step_id=ifc_definition_id)
return {"FINISHED"}
class PrintObjectPlacement(bpy.types.Operator):
bl_idname = "bim.print_object_placement"
bl_label = "Print Object Placement"
step_id: bpy.props.IntProperty()
def execute(self, context):
print(ifcopenshell.util.placement.get_local_placement(IfcStore.get_file().by_id(self.step_id)))
return {"FINISHED"}
@@ -23,6 +23,9 @@ class BIM_PT_debug(Panel):
row = layout.row()
row.operator("bim.print_ifc_file")
row = layout.row()
row.operator("bim.purge_ifc_links")
row = layout.row()
row.operator("bim.create_all_shapes")
@@ -46,7 +49,7 @@ class BIM_PT_debug(Panel):
row.operator("bim.rewind_inspector", icon="FRAME_PREV", text="")
row.prop(props, "active_step_id", text="")
row = layout.row(align=True)
row.operator("bim.inspect_from_step_id").step_id = bpy.context.scene.BIMDebugProperties.active_step_id
row.operator("bim.inspect_from_step_id").step_id = context.scene.BIMDebugProperties.active_step_id
row.operator("bim.inspect_from_object")
if props.attributes:
@@ -59,6 +62,9 @@ class BIM_PT_debug(Panel):
if attribute.name == "GlobalId":
op = row.operator("bim.select_global_id", icon="RESTRICT_SELECT_OFF", text="")
op.global_id = attribute.string_value
if attribute.name == "ObjectPlacement":
op = row.operator("bim.print_object_placement", icon="TRACKER", text="")
op.step_id = attribute.int_value
if attribute.int_value:
row.operator(
"bim.inspect_from_step_id", icon="DISCLOSURE_TRI_RIGHT", text=""
@@ -13,7 +13,7 @@ class SelectDiffJsonFile(bpy.types.Operator):
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def execute(self, context):
bpy.context.scene.DiffProperties.diff_json_file = self.filepath
context.scene.DiffProperties.diff_json_file = self.filepath
return {"FINISHED"}
def invoke(self, context, event):
@@ -29,20 +29,20 @@ class VisualiseDiff(bpy.types.Operator):
def execute(self, context):
#ifc_file = IfcStore.get_file() # In case we get from Store
ifc_file = ifcopenshell.open(context.scene.DiffProperties.diff_new_file) # for Now refer to the new file
with open(bpy.context.scene.DiffProperties.diff_json_file, "r") as file:
with open(context.scene.DiffProperties.diff_json_file, "r") as file:
diff = json.load(file)
for obj in bpy.context.visible_objects:
for obj in context.visible_objects:
obj.color = (1.0, 1.0, 1.0, 0.2)
global_id = ifc_file.by_id(obj.BIMObjectProperties.ifc_definition_id).GlobalId
if not global_id:
continue
if global_id.string_value in diff["deleted"]:
if global_id in diff["deleted"]:
obj.color = (1.0, 0.0, 0.0, 0.2)
elif global_id.string_value in diff["added"]:
elif global_id in diff["added"]:
obj.color = (0.0, 1.0, 0.0, 0.2)
elif global_id.string_value in diff["changed"]:
elif global_id in diff["changed"]:
obj.color = (0.0, 0.0, 1.0, 0.2)
area = next(area for area in bpy.context.screen.areas if area.type == "VIEW_3D")
area = next(area for area in context.screen.areas if area.type == "VIEW_3D")
area.spaces[0].shading.color_type = "OBJECT"
return {"FINISHED"}
@@ -54,7 +54,7 @@ class SelectDiffOldFile(bpy.types.Operator):
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def execute(self, context):
bpy.context.scene.DiffProperties.diff_old_file = self.filepath
context.scene.DiffProperties.diff_old_file = self.filepath
return {"FINISHED"}
def invoke(self, context, event):
@@ -69,7 +69,7 @@ class SelectDiffNewFile(bpy.types.Operator):
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def execute(self, context):
bpy.context.scene.DiffProperties.diff_new_file = self.filepath
context.scene.DiffProperties.diff_new_file = self.filepath
return {"FINISHED"}
def invoke(self, context, event):
@@ -93,12 +93,12 @@ class ExecuteIfcDiff(bpy.types.Operator):
import ifcdiff
ifc_diff = ifcdiff.IfcDiff(
bpy.context.scene.DiffProperties.diff_old_file,
bpy.context.scene.DiffProperties.diff_new_file,
context.scene.DiffProperties.diff_old_file,
context.scene.DiffProperties.diff_new_file,
self.filepath,
bpy.context.scene.DiffProperties.diff_relationships.split(),
context.scene.DiffProperties.diff_relationships.split(),
)
ifc_diff.diff()
ifc_diff.export()
bpy.context.scene.DiffProperties.diff_json_file = self.filepath
context.scene.DiffProperties.diff_json_file = self.filepath
return {"FINISHED"}
@@ -16,13 +16,13 @@ class Annotator:
return float(sizes[str(size)])
@staticmethod
def add_text(related_element=None):
def add_text(context, related_element=None):
curve = bpy.data.curves.new(type="FONT", name="Text")
curve.body = "TEXT"
obj = bpy.data.objects.new("Text", curve)
obj.matrix_world = bpy.context.scene.camera.matrix_world
obj.matrix_world = context.scene.camera.matrix_world
if related_element is None:
location, _, _, _ = Annotator.get_placeholder_coords()
location, _, _, _ = Annotator.get_placeholder_coords(context)
else:
obj.data.BIMTextProperties.related_element = related_element
location = related_element.location
@@ -31,12 +31,12 @@ class Annotator:
font = bpy.data.fonts.get("OpenGost TypeB TT")
if not font:
font = bpy.data.fonts.load(
os.path.join(bpy.context.scene.BIMProperties.data_dir, "fonts", "OpenGost Type B TT.ttf")
os.path.join(context.scene.BIMProperties.data_dir, "fonts", "OpenGost Type B TT.ttf")
)
font.name = "OpenGost Type B TT"
obj.data.font = font
obj.data.BIMTextProperties.font_size = "2.5"
collection = bpy.context.scene.camera.users_collection[0]
collection = context.scene.camera.users_collection[0]
collection.objects.link(obj)
Annotator.resize_text(obj)
return obj
@@ -64,9 +64,9 @@ class Annotator:
text_obj.data.size = font_size
@staticmethod
def add_line_to_annotation(obj, co1=None, co2=None):
def add_line_to_annotation(obj, context, co1=None, co2=None):
if co1 is None:
co1, co2, _, _ = Annotator.get_placeholder_coords()
co1, co2, _, _ = Annotator.get_placeholder_coords(context)
co1 = obj.matrix_world.inverted() @ co1
co2 = obj.matrix_world.inverted() @ co2
if isinstance(obj.data, bpy.types.Mesh):
@@ -83,8 +83,8 @@ class Annotator:
return obj
@staticmethod
def add_plane_to_annotation(obj):
co1, co2, co3, co4 = Annotator.get_placeholder_coords()
def add_plane_to_annotation(obj, context):
co1, co2, co3, co4 = Annotator.get_placeholder_coords(context)
co1 = obj.matrix_world.inverted() @ co1 # bot left
co2 = obj.matrix_world.inverted() @ co2 # top left
co3 = obj.matrix_world.inverted() @ co3 # bot right
@@ -132,8 +132,8 @@ class Annotator:
return obj
@staticmethod
def get_annotation_obj(name, data_type):
collection = bpy.context.scene.camera.users_collection[0]
def get_annotation_obj(name, data_type, context):
collection = context.scene.camera.users_collection[0]
for obj in collection.objects:
if name in obj.name:
return obj
@@ -148,13 +148,13 @@ class Annotator:
return obj
@staticmethod
def get_placeholder_coords():
camera = bpy.context.scene.camera
def get_placeholder_coords(context):
camera = context.scene.camera
z_offset = camera.matrix_world.to_quaternion() @ Vector((0, 0, -1))
if bpy.context.scene.render.resolution_x > bpy.context.scene.render.resolution_y:
if context.scene.render.resolution_x > context.scene.render.resolution_y:
y = (
camera.data.ortho_scale
* (bpy.context.scene.render.resolution_y / bpy.context.scene.render.resolution_x)
* (context.scene.render.resolution_y / context.scene.render.resolution_x)
/ 4
)
else:
@@ -7,6 +7,9 @@ import bmesh
import shutil
import subprocess
import webbrowser
import multiprocessing
import ifcopenshell
import ifcopenshell.geom
import ifcopenshell.util.selector
import ifcopenshell.util.representation
import blenderbim.bim.module.drawing.svgwriter as svgwriter
@@ -231,6 +234,39 @@ class CreateDrawing(bpy.types.Operator):
svg_path = os.path.join(context.scene.BIMProperties.data_dir, "cache", self.drawing_name + "-linework.svg")
if os.path.isfile(svg_path) and self.props.should_use_linework_cache:
return svg_path
# This is a work in progress. See #1153 and #1564.
# Switch from old to new if you are testing v0.7.0
self.generate_linework_old(svg_path)
# self.generate_linework_new(svg_path)
return svg_path
def generate_linework_new(self, svg_path):
settings = ifcopenshell.geom.settings(
APPLY_DEFAULT_MATERIALS=True,
DISABLE_TRIANGULATION=True,
INCLUDE_CURVES=True,
EXCLUDE_SOLIDS_AND_SURFACES=False,
)
buffer = ifcopenshell.geom.serializers.buffer()
serialiser = ifcopenshell.geom.serializers.svg(buffer, settings)
serialiser.setFile(self.file)
serialiser.setElevationRef("DRAWING")
serialiser.setUseNamespace(True)
serialiser.setAlwaysProject(True)
serialiser.setUseHlrPoly(True)
serialiser.setWithoutStoreys(True)
excluded_elements = []
for ifc_class in ["IfcSpace", "IfcOpeningElement", "IfcDoor", "IfcWindow"]:
excluded_elements += self.file.by_type(ifc_class)
for element in ifcopenshell.geom.iterate(
settings, self.file, multiprocessing.cpu_count(), exclude=excluded_elements
):
serialiser.write(element)
serialiser.finalize()
with open(svg_path, "w") as svg:
svg.write(buffer.get_value())
def generate_linework_old(self, svg_path):
ifcconvert_path = os.path.join(cwd, "..", "..", "..", "libs", "IfcConvert")
subprocess.run(
[
@@ -431,7 +467,7 @@ class AddAnnotation(bpy.types.Operator):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
if not bpy.context.scene.camera:
if not context.scene.camera:
return {"FINISHED"}
subcontext = ifcopenshell.util.representation.get_context(
IfcStore.get_file(), "Plan", "Annotation", context.scene.camera.data.BIMCameraProperties.target_view
@@ -439,17 +475,17 @@ class AddAnnotation(bpy.types.Operator):
if not subcontext:
return {"FINISHED"}
if self.data_type == "text":
if bpy.context.selected_objects:
for selected_object in bpy.context.selected_objects:
obj = annotation.Annotator.add_text(related_element=selected_object)
if context.selected_objects:
for selected_object in context.selected_objects:
obj = annotation.Annotator.add_text(context, related_element=selected_object)
else:
obj = annotation.Annotator.add_text()
obj = annotation.Annotator.add_text(context)
else:
obj = annotation.Annotator.get_annotation_obj(self.obj_name, self.data_type)
obj = annotation.Annotator.get_annotation_obj(self.obj_name, self.data_type, context)
if self.obj_name == "Break":
obj = annotation.Annotator.add_plane_to_annotation(obj)
obj = annotation.Annotator.add_plane_to_annotation(obj, context)
else:
obj = annotation.Annotator.add_line_to_annotation(obj)
obj = annotation.Annotator.add_line_to_annotation(obj, context)
if not obj.BIMObjectProperties.ifc_definition_id:
bpy.ops.bim.assign_class(obj=obj.name, ifc_class="IfcAnnotation", context_id=subcontext.id())
@@ -659,10 +695,10 @@ class GenerateReferences(bpy.types.Operator):
self.generate_grids()
if self.camera.data.BIMCameraProperties.target_view == "ELEVATION_VIEW":
self.generate_grids()
self.generate_levels()
self.generate_levels(context)
if self.camera.data.BIMCameraProperties.target_view == "SECTION_VIEW":
self.generate_grids()
self.generate_levels()
self.generate_levels(context)
return {"FINISHED"}
def filter_potential_references(self):
@@ -678,7 +714,7 @@ class GenerateReferences(bpy.types.Operator):
# TODO
pass
def generate_levels(self):
def generate_levels(self, context):
if self.camera.data.BIMCameraProperties.raster_x > self.camera.data.BIMCameraProperties.raster_y:
width = self.camera.data.ortho_scale
height = (
@@ -689,7 +725,7 @@ class GenerateReferences(bpy.types.Operator):
width = (
height / self.camera.data.BIMCameraProperties.raster_y * self.camera.data.BIMCameraProperties.raster_x
)
level_obj = annotation.Annotator.get_annotation_obj("Section Level", "curve")
level_obj = annotation.Annotator.get_annotation_obj("Section Level", "curve", context)
width_in_mm = width * 1000
if self.camera.data.BIMCameraProperties.diagram_scale == "CUSTOM":
@@ -706,7 +742,7 @@ class GenerateReferences(bpy.types.Operator):
projection = self.project_point_onto_camera(obj.location)
co1 = self.camera.matrix_world @ Vector((width / 2 - (offset_percentage * width), projection[1], -1))
co2 = self.camera.matrix_world @ Vector((-(width / 2), projection[1], -1))
annotation.Annotator.add_line_to_annotation(level_obj, co1, co2)
annotation.Annotator.add_line_to_annotation(level_obj, context, co1, co2)
def project_point_onto_camera(self, point):
projection = self.camera.matrix_world.to_quaternion() @ Vector((0, 0, -1))
@@ -28,7 +28,7 @@ class External(svgwrite.container.Group):
# Remove namespace
ns = u"{http://www.w3.org/2000/svg}"
nsl = len(ns)
for elem in self.xml.getiterator():
for elem in self.xml.iter():
if elem.tag.startswith(ns):
elem.tag = elem.tag[nsl:]
@@ -85,19 +85,19 @@ class SvgWriter:
def add_markers(self):
tree = ET.parse(os.path.join(self.data_dir, "templates", "markers.svg"))
root = tree.getroot()
for child in root.getchildren():
for child in root:
self.svg.defs.add(External(child))
def add_symbols(self):
tree = ET.parse(os.path.join(self.data_dir, "templates", "symbols.svg"))
root = tree.getroot()
for child in root.getchildren():
for child in root:
self.svg.defs.add(External(child))
def add_patterns(self):
tree = ET.parse(os.path.join(self.data_dir, "templates", "patterns.svg"))
root = tree.getroot()
for child in root.getchildren():
for child in root:
self.svg.defs.add(External(child))
def draw_background_image(self):
@@ -24,7 +24,7 @@ class BIM_PT_camera(Panel):
return
layout.use_property_split = True
dprops = bpy.context.scene.DocProperties
dprops = context.scene.DocProperties
props = context.active_object.data.BIMCameraProperties
col = layout.column(align=True)
@@ -92,7 +92,7 @@ class BIM_PT_drawing_underlay(Panel):
def draw(self, context):
layout = self.layout
layout.use_property_split = True
dprops = bpy.context.scene.DocProperties
dprops = context.scene.DocProperties
props = context.active_object.data.BIMCameraProperties
row = layout.row(align=True)
@@ -142,7 +142,7 @@ class BIM_PT_drawings(Panel):
def draw(self, context):
layout = self.layout
layout.use_property_split = True
props = bpy.context.scene.DocProperties
props = context.scene.DocProperties
row = layout.row(align=True)
row.operator("bim.add_drawing")
@@ -175,7 +175,7 @@ class BIM_PT_schedules(Panel):
def draw(self, context):
layout = self.layout
layout.use_property_split = True
props = bpy.context.scene.DocProperties
props = context.scene.DocProperties
row = layout.row(align=True)
row.operator("bim.add_schedule")
@@ -200,7 +200,7 @@ class BIM_PT_sheets(Panel):
def draw(self, context):
layout = self.layout
props = bpy.context.scene.DocProperties
props = context.scene.DocProperties
row = layout.row(align=True)
row.prop(props, "titleblock", text="")
@@ -315,7 +315,7 @@ class BIM_PT_annotation_utilities(Panel):
op.obj_name = "Misc"
op.data_type = "mesh"
props = bpy.context.scene.DocProperties
props = context.scene.DocProperties
row = layout.row(align=True)
row.operator("bim.add_drawing")
@@ -24,10 +24,10 @@ class EditObjectPlacement(bpy.types.Operator):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
objs = [bpy.data.objects.get(self.obj)] if self.obj else bpy.context.selected_objects
objs = [bpy.data.objects.get(self.obj)] if self.obj else context.selected_objects
self.file = IfcStore.get_file()
# TODO: determine how to deal with this module dependency
props = bpy.context.scene.BIMGeoreferenceProperties
props = context.scene.BIMGeoreferenceProperties
for obj in objs:
if not obj.BIMObjectProperties.ifc_definition_id:
continue
@@ -69,7 +69,7 @@ class AddRepresentation(bpy.types.Operator):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
self.file = IfcStore.get_file()
bpy.ops.bim.edit_object_placement(obj=obj.name)
@@ -79,7 +79,7 @@ class AddRepresentation(bpy.types.Operator):
product = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
context_id = self.context_id or int(bpy.context.scene.BIMProperties.contexts)
context_id = self.context_id or int(context.scene.BIMProperties.contexts)
context_of_items = self.file.by_id(context_id)
gprop = context.scene.BIMGeoreferenceProperties
@@ -117,19 +117,20 @@ class AddRepresentation(bpy.types.Operator):
if s.material and not s.material.BIMMaterialProperties.ifc_style_id
]
ifcopenshell.api.run(
"style.assign_representation_styles",
self.file,
**{
"shape_representation": result,
"styles": [
self.file.by_id(s.material.BIMMaterialProperties.ifc_style_id)
for s in obj.material_slots
if s.material
],
"should_use_presentation_style_assignment": context.scene.BIMGeometryProperties.should_use_presentation_style_assignment,
},
)
if isinstance(obj.data, bpy.types.Mesh) and len(obj.data.polygons):
ifcopenshell.api.run(
"style.assign_representation_styles",
self.file,
**{
"shape_representation": result,
"styles": [
self.file.by_id(s.material.BIMMaterialProperties.ifc_style_id)
for s in obj.material_slots
if s.material
],
"should_use_presentation_style_assignment": context.scene.BIMGeometryProperties.should_use_presentation_style_assignment,
},
)
ifcopenshell.api.run(
"geometry.assign_representation", self.file, **{"product": product, "representation": result}
)
@@ -160,9 +161,10 @@ class SwitchRepresentation(bpy.types.Operator):
ifc_definition_id: bpy.props.IntProperty()
should_reload: bpy.props.BoolProperty()
disable_opening_subtractions: bpy.props.BoolProperty()
should_switch_all_meshes: bpy.props.BoolProperty()
def execute(self, context):
self.element_obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
self.element_obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
self.oprops = self.element_obj.BIMObjectProperties
self.file = IfcStore.get_file()
@@ -171,11 +173,17 @@ class SwitchRepresentation(bpy.types.Operator):
mesh = bpy.data.meshes.get(self.mesh_name)
if mesh:
self.element_obj.data.user_remap(mesh)
self.switch_mesh(mesh)
if not mesh or self.should_reload:
self.pull_mesh_from_ifc()
self.pull_mesh_from_ifc(context)
return {"FINISHED"}
def switch_mesh(self, mesh):
if self.should_switch_all_meshes or self.file.by_id(self.oprops.ifc_definition_id).is_a("IfcTypeProduct"):
self.element_obj.data.user_remap(mesh)
else:
self.element_obj.data = mesh
def get_mesh_name(self):
representation = self.resolve_mapped_representation(self.file.by_id(self.ifc_definition_id))
return "{}/{}".format(self.context_of_items.id(), representation.id())
@@ -185,9 +193,9 @@ class SwitchRepresentation(bpy.types.Operator):
return self.resolve_mapped_representation(representation.Items[0].MappingSource.MappedRepresentation)
return representation
def pull_mesh_from_ifc(self):
def pull_mesh_from_ifc(self, context):
logger = logging.getLogger("ImportIFC")
ifc_import_settings = import_ifc.IfcImportSettings.factory(bpy.context, IfcStore.path, logger)
ifc_import_settings = import_ifc.IfcImportSettings.factory(context, IfcStore.path, logger)
element = self.file.by_id(self.oprops.ifc_definition_id)
settings = ifcopenshell.geom.settings()
@@ -205,7 +213,7 @@ class SwitchRepresentation(bpy.types.Operator):
mesh = ifc_importer.create_mesh(element, shape)
mesh.name = self.mesh_name
mesh.BIMMeshProperties.ifc_definition_id = self.ifc_definition_id
self.element_obj.data.user_remap(mesh)
self.switch_mesh(mesh)
material_creator = import_ifc.MaterialCreator(ifc_import_settings, ifc_importer)
material_creator.load_existing_materials()
material_creator.create(element, self.element_obj, mesh)
@@ -239,7 +247,7 @@ class RemoveRepresentation(bpy.types.Operator):
def _execute(self, context):
self.file = IfcStore.get_file()
representation = self.file.by_id(self.representation_id)
obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
is_mapped_representation = representation.RepresentationType == "MappedRepresentation"
if is_mapped_representation:
mesh_name = "{}/{}".format(
@@ -280,7 +288,7 @@ class UpdateRepresentation(bpy.types.Operator):
if not ContextData.is_loaded:
ContextData.load(IfcStore.get_file())
objs = [bpy.data.objects.get(self.obj)] if self.obj else bpy.context.selected_objects
objs = [bpy.data.objects.get(self.obj)] if self.obj else context.selected_objects
self.file = IfcStore.get_file()
for obj in objs:
@@ -330,19 +338,20 @@ class UpdateRepresentation(bpy.types.Operator):
if s.material and not s.material.BIMMaterialProperties.ifc_style_id
]
ifcopenshell.api.run(
"style.assign_representation_styles",
self.file,
**{
"shape_representation": new_representation,
"styles": [
self.file.by_id(s.material.BIMMaterialProperties.ifc_style_id)
for s in obj.material_slots
if s.material
],
"should_use_presentation_style_assignment": context.scene.BIMGeometryProperties.should_use_presentation_style_assignment,
},
)
if isinstance(obj.data, bpy.types.Mesh) and len(obj.data.polygons):
ifcopenshell.api.run(
"style.assign_representation_styles",
self.file,
**{
"shape_representation": new_representation,
"styles": [
self.file.by_id(s.material.BIMMaterialProperties.ifc_style_id)
for s in obj.material_slots
if s.material
],
"should_use_presentation_style_assignment": context.scene.BIMGeometryProperties.should_use_presentation_style_assignment,
},
)
# TODO: move this into a replace_representation usecase or something
for inverse in self.file.get_inverse(old_representation):
@@ -362,11 +371,13 @@ class UpdateParametricRepresentation(bpy.types.Operator):
def execute(self, context):
self.file = IfcStore.get_file()
obj = bpy.context.active_object
obj = context.active_object
props = obj.data.BIMMeshProperties
parameter = props.ifc_parameters[self.index]
element = IfcStore.get_file().by_id(parameter.step_id)[parameter.index] = parameter.value
bpy.ops.bim.switch_representation(ifc_definition_id=props.ifc_definition_id, should_reload=True)
bpy.ops.bim.switch_representation(
ifc_definition_id=props.ifc_definition_id, should_reload=True, should_switch_all_meshes=True
)
return {"FINISHED"}
@@ -377,7 +388,7 @@ class GetRepresentationIfcParameters(bpy.types.Operator):
def execute(self, context):
self.file = IfcStore.get_file()
obj = bpy.context.active_object
obj = context.active_object
props = obj.data.BIMMeshProperties
elements = IfcStore.get_file().traverse(IfcStore.get_file().by_id(props.ifc_definition_id))
for element in elements:
@@ -30,7 +30,7 @@ class BIM_PT_representations(Panel):
layout.label(text="No representations found")
row = layout.row(align=True)
row.prop(bpy.context.scene.BIMProperties, "contexts", text="")
row.prop(context.scene.BIMProperties, "contexts", text="")
row.operator("bim.add_representation", icon="ADD", text="")
for ifc_definition_id in representations:
@@ -41,6 +41,7 @@ class BIM_PT_representations(Panel):
row.label(text=representation["ContextOfItems"]["TargetView"])
row.label(text=representation["RepresentationType"])
op = row.operator("bim.switch_representation", icon="OUTLINER_DATA_MESH", text="")
op.should_switch_all_meshes = True
op.should_reload = True
op.ifc_definition_id = ifc_definition_id
op.disable_opening_subtractions = False
@@ -71,10 +72,12 @@ class BIM_PT_mesh(Panel):
row = layout.row(align=True)
op = row.operator("bim.switch_representation", text="Bake Voids", icon="SELECT_SUBTRACT")
op.should_switch_all_meshes=True
op.should_reload = True
op.ifc_definition_id = props.ifc_definition_id
op.disable_opening_subtractions = False
op = row.operator("bim.switch_representation", text="Dynamic Voids", icon="SELECT_INTERSECT")
op.should_switch_all_meshes=True
op.should_reload = True
op.ifc_definition_id = props.ifc_definition_id
op.disable_opening_subtractions = True
@@ -276,7 +276,7 @@ class ConvertLocalToGlobal(bpy.types.Operator):
results = (x, y, z)
props.coordinate_output = ",".join([str(r) for r in results])
bpy.context.scene.cursor.location = results
context.scene.cursor.location = results
return {"FINISHED"}
@@ -322,7 +322,7 @@ class ConvertGlobalToLocal(bpy.types.Operator):
props.coordinate_output = ",".join([str(r) for r in results])
scale = ifcopenshell.util.unit.calculate_unit_scale(IfcStore.get_file())
bpy.context.scene.cursor.location = [o * scale for o in results]
context.scene.cursor.location = [o * scale for o in results]
return {"FINISHED"}
@@ -334,7 +334,7 @@ class GetCursorLocation(bpy.types.Operator):
def execute(self, context):
props = context.scene.BIMGeoreferenceProperties
scale = ifcopenshell.util.unit.calculate_unit_scale(IfcStore.get_file())
project_coordinates = [o / scale for o in bpy.context.scene.cursor.location]
project_coordinates = [o / scale for o in context.scene.cursor.location]
props.coordinate_input = ",".join([str(o) for o in project_coordinates])
return {"FINISHED"}
@@ -347,5 +347,5 @@ class SetCursorLocation(bpy.types.Operator):
def execute(self, context):
props = context.scene.BIMGeoreferenceProperties
scale = ifcopenshell.util.unit.calculate_unit_scale(IfcStore.get_file())
bpy.context.scene.cursor.location = [float(co) * scale for co in props.coordinate_output.split(",")]
context.scene.cursor.location = [float(co) * scale for co in props.coordinate_output.split(",")]
return {"FINISHED"}
@@ -186,7 +186,7 @@ class SelectGroupProducts(bpy.types.Operator):
def execute(self, context):
self.file = IfcStore.get_file()
for obj in bpy.context.visible_objects:
for obj in context.visible_objects:
obj.select_set(False)
if not obj.BIMObjectProperties.ifc_definition_id:
continue
@@ -1,6 +1,7 @@
import bpy
import json
import ifcopenshell.api
import ifcopenshell.util.element
import ifcopenshell.util.attribute
import ifcopenshell.util.representation
import blenderbim.bim.helper
@@ -22,7 +23,7 @@ class AssignParameterizedProfile(bpy.types.Operator):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
self.file = IfcStore.get_file()
profile = ifcopenshell.api.run(
"profile.add_parameterized_profile",
@@ -50,18 +51,22 @@ class AddMaterial(bpy.types.Operator):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
obj = bpy.data.materials.get(self.obj) if self.obj else bpy.context.active_object.active_material
obj = bpy.data.materials.get(self.obj) if self.obj else context.active_object.active_material
self.file = IfcStore.get_file()
result = ifcopenshell.api.run("material.add_material", self.file, **{"name": obj.name})
obj.BIMObjectProperties.ifc_definition_id = result.id()
IfcStore.link_element(result, obj)
if obj.BIMMaterialProperties.ifc_style_id:
context = ifcopenshell.util.representation.get_context(self.file, "Model", "Body", "MODEL_VIEW")
if context:
ifcopenshell.api.run("style.assign_material_style", self.file, **{
"material": result,
"style": self.file.by_id(obj.BIMMaterialProperties.ifc_style_id),
"context": context,
})
ifcopenshell.api.run(
"style.assign_material_style",
self.file,
**{
"material": result,
"style": self.file.by_id(obj.BIMMaterialProperties.ifc_style_id),
"context": context,
},
)
Data.load(IfcStore.get_file())
material_prop_purge()
return {"FINISHED"}
@@ -77,7 +82,7 @@ class RemoveMaterial(bpy.types.Operator):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
obj = bpy.data.materials.get(self.obj) if self.obj else bpy.context.active_object.active_material
obj = bpy.data.materials.get(self.obj) if self.obj else context.active_object.active_material
self.file = IfcStore.get_file()
result = ifcopenshell.api.run(
"material.remove_material",
@@ -100,22 +105,40 @@ class AssignMaterial(bpy.types.Operator):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
material_type = self.material_type or obj.BIMObjectMaterialProperties.material_type
self.file = IfcStore.get_file()
element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
ifcopenshell.api.run(
"material.assign_material",
self.file,
**{
"product": self.file.by_id(obj.BIMObjectProperties.ifc_definition_id),
"product": element,
"type": material_type,
"material": self.file.by_id(int(obj.BIMObjectMaterialProperties.material)),
},
)
Data.load(IfcStore.get_file())
Data.load(IfcStore.get_file(), obj.BIMObjectProperties.ifc_definition_id)
self.set_default_material(obj, element)
return {"FINISHED"}
def set_default_material(self, obj, element):
element_material = ifcopenshell.util.element.get_material(element)
material = [m for m in self.file.traverse(element_material) if m.is_a("IfcMaterial")]
if not material:
return
object_material_ids = [
om.BIMObjectProperties.ifc_definition_id
for om in obj.data.materials
if om is not None and om.BIMObjectProperties.ifc_definition_id
]
if material[0].id() in object_material_ids:
return
obj.data.materials.append(IfcStore.get_element(material[0].id()))
class UnassignMaterial(bpy.types.Operator):
bl_idname = "bim.unassign_material"
@@ -127,7 +150,7 @@ class UnassignMaterial(bpy.types.Operator):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"material.unassign_material",
@@ -149,7 +172,7 @@ class AddConstituent(bpy.types.Operator):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"material.add_constituent",
@@ -174,7 +197,7 @@ class RemoveConstituent(bpy.types.Operator):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"material.remove_constituent", self.file, **{"constituent": self.file.by_id(self.constituent)}
@@ -194,7 +217,7 @@ class AddProfile(bpy.types.Operator):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"material.add_profile",
@@ -205,6 +228,7 @@ class AddProfile(bpy.types.Operator):
},
)
Data.load_profiles()
ProfileData.load(self.file)
return {"FINISHED"}
@@ -219,10 +243,11 @@ class RemoveProfile(bpy.types.Operator):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
self.file = IfcStore.get_file()
ifcopenshell.api.run("material.remove_profile", self.file, **{"profile": self.file.by_id(self.profile)})
Data.load_profiles()
ProfileData.load(self.file)
return {"FINISHED"}
@@ -237,7 +262,7 @@ class AddLayer(bpy.types.Operator):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"material.add_layer",
@@ -264,7 +289,7 @@ class ReorderMaterialSetItem(bpy.types.Operator):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
self.file = IfcStore.get_file()
material_set = self.file.by_id(self.material_set)
ifcopenshell.api.run(
@@ -282,6 +307,7 @@ class ReorderMaterialSetItem(bpy.types.Operator):
Data.load_layers()
elif material_set.is_a("IfcMaterialProfileSet"):
Data.load_profiles()
ProfileData.load(self.file)
elif material_set.is_a("IfcMaterialList"):
Data.load_lists()
return {"FINISHED"}
@@ -298,7 +324,7 @@ class RemoveLayer(bpy.types.Operator):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
self.file = IfcStore.get_file()
ifcopenshell.api.run("material.remove_layer", self.file, **{"layer": self.file.by_id(self.layer)})
Data.load_layers()
@@ -316,7 +342,7 @@ class AddListItem(bpy.types.Operator):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"material.add_list_item",
@@ -342,7 +368,7 @@ class RemoveListItem(bpy.types.Operator):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"material.remove_list_item",
@@ -363,7 +389,7 @@ class EnableEditingAssignedMaterial(bpy.types.Operator):
obj: bpy.props.StringProperty()
def execute(self, context):
obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
props = obj.BIMObjectMaterialProperties
props.is_editing = True
product_data = Data.products[obj.BIMObjectProperties.ifc_definition_id]
@@ -451,7 +477,7 @@ class DisableEditingAssignedMaterial(bpy.types.Operator):
obj: bpy.props.StringProperty()
def execute(self, context):
obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
props = obj.BIMObjectMaterialProperties
props.is_editing = False
return {"FINISHED"}
@@ -470,10 +496,9 @@ class EditAssignedMaterial(bpy.types.Operator):
def _execute(self, context):
self.file = IfcStore.get_file()
obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
props = obj.BIMObjectMaterialProperties
product_data = Data.products[obj.BIMObjectProperties.ifc_definition_id]
material_set = self.file.by_id(self.material_set)
if product_data["type"] == "IfcMaterial":
bpy.ops.bim.unassign_material(obj=obj.name)
@@ -482,6 +507,8 @@ class EditAssignedMaterial(bpy.types.Operator):
bpy.ops.bim.disable_editing_assigned_material(obj=obj.name)
return {"FINISHED"}
material_set = self.file.by_id(self.material_set)
attributes = {}
for attribute in props.material_set_attributes:
attributes[attribute.name] = None if attribute.is_null else attribute.string_value
@@ -495,16 +522,21 @@ class EditAssignedMaterial(bpy.types.Operator):
if self.material_set_usage:
material_set_usage = self.file.by_id(self.material_set_usage)
attributes = blenderbim.bim.helper.export_attributes(props.material_set_usage_attributes)
if attributes.get("CardinalPoint", None):
attributes["CardinalPoint"] = int(attributes["CardinalPoint"])
ifcopenshell.api.run(
"material.edit_profile_usage",
self.file,
**{"usage": material_set_usage, "attributes": attributes},
)
if material_set_usage.is_a("IfcMaterialLayerSetUsage"):
ifcopenshell.api.run(
"material.edit_layer_usage",
self.file,
**{"usage": material_set_usage, "attributes": attributes},
)
Data.load_layer_usages()
elif material_set_usage.is_a("IfcMaterialProfileSetUsage"):
if attributes.get("CardinalPoint", None):
attributes["CardinalPoint"] = int(attributes["CardinalPoint"])
ifcopenshell.api.run(
"material.edit_profile_usage",
self.file,
**{"usage": material_set_usage, "attributes": attributes},
)
Data.load_profile_usages()
if material_set.is_a("IfcMaterialConstituentSet"):
@@ -514,8 +546,25 @@ class EditAssignedMaterial(bpy.types.Operator):
elif material_set.is_a("IfcMaterialProfileSet"):
Data.load_profiles()
bpy.ops.bim.disable_editing_assigned_material(obj=obj.name)
self.set_default_material(obj, self.file.by_id(obj.BIMObjectProperties.ifc_definition_id))
return {"FINISHED"}
def set_default_material(self, obj, element):
element_material = ifcopenshell.util.element.get_material(element)
material = [m for m in self.file.traverse(element_material) if m.is_a("IfcMaterial")]
if not material:
return
object_material_ids = [
om.BIMObjectProperties.ifc_definition_id
for om in obj.data.materials
if om is not None and om.BIMObjectProperties.ifc_definition_id
]
if material[0].id() in object_material_ids:
return
obj.data.materials.append(IfcStore.get_element(material[0].id()))
class EnableEditingMaterialSetItem(bpy.types.Operator):
bl_idname = "bim.enable_editing_material_set_item"
@@ -526,7 +575,7 @@ class EnableEditingMaterialSetItem(bpy.types.Operator):
def execute(self, context):
self.file = IfcStore.get_file()
obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
self.props = obj.BIMObjectMaterialProperties
self.props.active_material_set_item_id = self.material_set_item
product_data = Data.products[obj.BIMObjectProperties.ifc_definition_id]
@@ -618,7 +667,7 @@ class DisableEditingMaterialSetItem(bpy.types.Operator):
obj: bpy.props.StringProperty()
def execute(self, context):
obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
props = obj.BIMObjectMaterialProperties
props.active_material_set_item_id = 0
return {"FINISHED"}
@@ -635,7 +684,7 @@ class EditMaterialSetItem(bpy.types.Operator):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
self.file = IfcStore.get_file()
props = obj.BIMObjectMaterialProperties
product_data = Data.products[obj.BIMObjectProperties.ifc_definition_id]
@@ -49,6 +49,11 @@ def getParameterizedProfileClasses(self, context):
(t.name(), t.name(), "")
for t in IfcStore.get_schema().declaration_by_name("IfcParameterizedProfileDef").subtypes()
]
for ifc_class in parameterizedprofileclasses_enum:
parameterizedprofileclasses_enum.extend([
(t.name(), t.name(), "")
for t in IfcStore.get_schema().declaration_by_name(ifc_class[0]).subtypes() or []
])
return parameterizedprofileclasses_enum
@@ -97,6 +97,8 @@ class BIM_PT_object_material(Panel):
self.material_set_data = Data.lists[self.material_set_id]
self.set_items = self.material_set_data["Materials"] or []
self.set_item_name = "list_item"
else:
self.material_set_id = 0
return self.draw_material_ui()
row = self.layout.row(align=True)
@@ -316,11 +318,11 @@ class BIM_PT_object_material(Panel):
item_name = item.get("Name", "Unnamed") or "Unnamed"
thickness = item.get("LayerThickness")
if thickness:
item_name += f" ({thickness})"
item_name += f" ({thickness:.3f})"
total_thickness += thickness
row.label(text=item_name, icon="ALIGN_CENTER")
row.label(text=Data.materials[item["Material"]]["Name"], icon="MATERIAL")
if total_thickness:
row = self.layout.row(align=True)
row.label(text=f"Total Thickness: {total_thickness}")
row.label(text=f"Total Thickness: {total_thickness:.3f}")
@@ -3,11 +3,14 @@ from . import handler, prop, ui, grid, product, wall, slab, stair, door, window,
classes = (
product.AddTypeInstance,
wall.AddWall,
product.AlignProduct,
workspace.Hotkey,
wall.JoinWall,
wall.AlignWall,
wall.FlipWall,
wall.SplitWall,
wall.AddWallOpening,
slab.AddSlabOpening,
prop.BIMModelProperties,
ui.BIM_PT_authoring,
ui.BIM_PT_authoring_architectural,
@@ -82,7 +82,7 @@ def add_object(self, context):
obj = bpy.data.objects.new("Door Profile", mesh)
context.view_layer.active_layer_collection.collection.objects.link(obj)
bpy.context.view_layer.objects.active = obj
context.view_layer.objects.active = obj
obj.select_set(True)
bpy.ops.object.convert(target="CURVE")
@@ -100,7 +100,7 @@ def add_object(self, context):
obj2 = bpy.data.objects.new("Door", mesh)
context.view_layer.active_layer_collection.collection.objects.link(obj2)
bpy.context.view_layer.objects.active = obj2
context.view_layer.objects.active = obj2
obj2.select_set(True)
bpy.ops.object.convert(target="CURVE")
@@ -130,11 +130,11 @@ def add_object(self, context):
modifier.thickness = self.overall_height - 0.045
context.view_layer.active_layer_collection.collection.objects.link(obj3)
bpy.context.view_layer.objects.active = obj3
context.view_layer.objects.active = obj3
obj3.select_set(True)
bpy.ops.object.convert(target="MESH")
ctx = bpy.context.copy()
ctx = context.copy()
ctx["active_object"] = obj2
ctx["selected_editable_objects"] = [obj2, obj3]
bpy.ops.object.join(ctx)
@@ -156,7 +156,7 @@ def add_object(self, context):
modifier.thickness = self.overall_height + 0.1
context.view_layer.active_layer_collection.collection.objects.link(obj4)
bpy.context.view_layer.objects.active = obj4
context.view_layer.objects.active = obj4
obj4.select_set(True)
bpy.ops.object.convert(target="MESH")
@@ -12,7 +12,7 @@ def add_object(self, context):
collection = bpy.data.collections.new(obj.name)
has_site_collection = False
for child in bpy.context.view_layer.layer_collection.children:
for child in context.view_layer.layer_collection.children:
if "IfcProject/" not in child.name:
continue
for grandchild in child.children:
@@ -22,7 +22,7 @@ def add_object(self, context):
grandchild.collection.children.link(collection)
break
if not has_site_collection:
bpy.context.view_layer.active_layer_collection.collection.children.link(collection)
context.view_layer.active_layer_collection.collection.children.link(collection)
collection.objects.link(obj)
self.file = IfcStore.get_file()
@@ -1,13 +1,17 @@
import bpy
import ifcopenshell
import ifcopenshell.api
from blenderbim.bim.module.model import product, wall, slab, column
from blenderbim.bim.module.model import root, product, wall, slab, profile, opening
from blenderbim.bim.ifc import IfcStore
from bpy.app.handlers import persistent
@persistent
def load_post(*args):
ifcopenshell.api.add_pre_listener(
"attribute.edit_attributes", "BlenderBIM.Root.SyncName", root.sync_name
)
ifcopenshell.api.add_post_listener(
"geometry.add_representation", "BlenderBIM.Product.GenerateBox", product.generate_box
)
@@ -17,6 +21,8 @@ def load_post(*args):
product.regenerate_profile_usage,
)
IfcStore.add_element_listener(opening.element_listener)
IfcStore.add_element_listener(wall.element_listener)
ifcopenshell.api.add_pre_listener(
"geometry.add_representation", "BlenderBIM.DumbWall.EnsureSolid", wall.ensure_solid
@@ -51,17 +57,22 @@ def load_post(*args):
"type.assign_type", "BlenderBIM.DumbSlab.RegenerateFromType", slab.DumbSlabPlaner().regenerate_from_type
)
IfcStore.add_element_listener(column.element_listener)
IfcStore.add_element_listener(profile.element_listener)
ifcopenshell.api.add_pre_listener(
"geometry.add_representation", "BlenderBIM.DumbColumn.EnsureSolid", column.ensure_solid
"geometry.add_representation", "BlenderBIM.DumbProfile.EnsureSolid", profile.ensure_solid
)
ifcopenshell.api.add_pre_listener(
"material.edit_profile",
"BlenderBIM.DumbProfile.SyncObjectFromProfile",
profile.DumbProfileRegenerator().sync_object_from_profile,
)
ifcopenshell.api.add_post_listener(
"material.edit_profile",
"BlenderBIM.DumbColumn.RegenerateFromProfile",
column.DumbColumnRegenerator().regenerate_from_profile,
"BlenderBIM.DumbProfile.RegenerateFromProfile",
profile.DumbProfileRegenerator().regenerate_from_profile,
)
ifcopenshell.api.add_post_listener(
"type.assign_type",
"BlenderBIM.DumbColumn.RegenerateFromType",
column.DumbColumnRegenerator().regenerate_from_type,
"BlenderBIM.DumbProfile.RegenerateFromType",
profile.DumbProfileRegenerator().regenerate_from_type,
)
@@ -1,10 +1,57 @@
import bpy
import bmesh
import blenderbim.bim.handler
import ifcopenshell
import ifcopenshell.util.representation
from blenderbim.bim.ifc import IfcStore
from math import pi
from bpy.types import Operator
from bpy.props import FloatProperty
from bpy_extras.object_utils import AddObjectHelper, object_data_add
def element_listener(element, obj):
blenderbim.bim.handler.subscribe_to(obj, "mode", mode_callback)
def mode_callback(obj, data):
for obj in set(bpy.context.selected_objects + [bpy.context.active_object]):
if (
obj.mode != "EDIT"
or not obj.data
or not isinstance(obj.data, (bpy.types.Mesh, bpy.types.Curve, bpy.types.TextCurve))
or not obj.BIMObjectProperties.ifc_definition_id
or not bpy.context.scene.BIMProjectProperties.is_authoring
):
return
product = IfcStore.get_file().by_id(obj.BIMObjectProperties.ifc_definition_id)
if not product.is_a("IfcOpeningElement"):
return
for rel in product.VoidsElements:
building_element_obj = IfcStore.get_element(rel.RelatingBuildingElement.id())
if not building_element_obj:
continue
if [m for m in building_element_obj.modifiers if m.type == "BOOLEAN"]:
continue
representation = ifcopenshell.util.representation.get_representation(
rel.RelatingBuildingElement, "Model", "Body", "MODEL_VIEW"
)
if not representation:
continue
bpy.ops.bim.switch_representation(
obj=building_element_obj.name,
should_switch_all_meshes=True,
should_reload=True,
ifc_definition_id=representation.id(),
disable_opening_subtractions=True,
)
IfcStore.edited_objs.add(obj)
bm = bmesh.from_edit_mesh(obj.data)
bmesh.ops.dissolve_limit(bm, angle_limit=pi / 180 * 1, verts=bm.verts, edges=bm.edges)
bmesh.update_edit_mesh(obj.data)
bm.free()
def add_object(self, context):
bm = bmesh.new()
bmesh.ops.create_cube(bm, size=self.size)
@@ -1,5 +1,5 @@
import bpy
from blenderbim.bim.ifc import IfcStore
class OpenPieClass(bpy.types.Operator):
bl_idname = "bim.open_pie_class"
@@ -1,12 +1,13 @@
import bpy
import mathutils
import ifcopenshell
import ifcopenshell.api
import ifcopenshell.util.element
import ifcopenshell.util.representation
from . import wall, slab, column
from . import wall, slab, profile
from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.pset.data import Data as PsetData
from mathutils import Vector
from mathutils import Vector, Matrix
class AddTypeInstance(bpy.types.Operator):
@@ -35,8 +36,8 @@ class AddTypeInstance(bpy.types.Operator):
obj = slab.DumbSlabGenerator(self.file.by_id(int(relating_type))).generate()
if obj:
return {"FINISHED"}
elif ifc_class == "IfcColumnType":
obj = column.DumbColumnGenerator(self.file.by_id(int(relating_type))).generate()
elif ifc_class in ["IfcColumnType", "IfcBeamType", "IfcMemberType"]:
obj = profile.DumbProfileGenerator(self.file.by_id(int(relating_type))).generate()
if obj:
return {"FINISHED"}
# A cube
@@ -63,7 +64,7 @@ class AddTypeInstance(bpy.types.Operator):
mesh.from_pydata(verts, edges, faces)
obj = bpy.data.objects.new("Instance", mesh)
obj.location = context.scene.cursor.location
collection = bpy.context.view_layer.active_layer_collection.collection
collection = context.view_layer.active_layer_collection.collection
collection.objects.link(obj)
collection_obj = bpy.data.objects.get(collection.name)
bpy.ops.bim.assign_class(obj=obj.name, ifc_class=instance_class)
@@ -73,6 +74,52 @@ class AddTypeInstance(bpy.types.Operator):
return {"FINISHED"}
class AlignProduct(bpy.types.Operator):
bl_idname = "bim.align_product"
bl_label = "Align Product"
bl_options = {"REGISTER", "UNDO"}
align_type: bpy.props.StringProperty()
def execute(self, context):
selected_objs = context.selected_objects
if len(selected_objs) < 2 or not context.active_object:
return {"FINISHED"}
if self.align_type == "CENTERLINE":
point = context.active_object.matrix_world @ (
Vector(context.active_object.bound_box[0]) + (context.active_object.dimensions / 2)
)
elif self.align_type == "POSITIVE":
point = context.active_object.matrix_world @ Vector(context.active_object.bound_box[6])
elif self.align_type == "NEGATIVE":
point = context.active_object.matrix_world @ Vector(context.active_object.bound_box[0])
active_x_axis = context.active_object.matrix_world.to_quaternion() @ Vector((1, 0, 0))
active_y_axis = context.active_object.matrix_world.to_quaternion() @ Vector((0, 1, 0))
active_z_axis = context.active_object.matrix_world.to_quaternion() @ Vector((0, 0, 1))
x_distances = self.get_axis_distances(point, active_x_axis, context)
y_distances = self.get_axis_distances(point, active_y_axis, context)
if abs(sum(x_distances)) < abs(sum(y_distances)):
for i, obj in enumerate(selected_objs):
obj.matrix_world = Matrix.Translation(active_x_axis * -x_distances[i]) @ obj.matrix_world
else:
for i, obj in enumerate(selected_objs):
obj.matrix_world = Matrix.Translation(active_y_axis * -y_distances[i]) @ obj.matrix_world
return {"FINISHED"}
def get_axis_distances(self, point, axis, context):
results = []
for obj in context.selected_objects:
if self.align_type == "CENTERLINE":
obj_point = obj.matrix_world @ (Vector(obj.bound_box[0]) + (obj.dimensions / 2))
elif self.align_type == "POSITIVE":
obj_point = obj.matrix_world @ Vector(obj.bound_box[6])
elif self.align_type == "NEGATIVE":
obj_point = obj.matrix_world @ Vector(obj.bound_box[0])
results.append(mathutils.geometry.distance_point_to_plane(obj_point, point, axis))
return results
def generate_box(usecase_path, ifc_file, settings):
box_context = ifcopenshell.util.representation.get_context(ifc_file, "Model", "Box", "MODEL_VIEW")
if not box_context:
@@ -98,6 +145,7 @@ def generate_box(usecase_path, ifc_file, settings):
**{"product": product, "representation": new_box}
)
def regenerate_profile_usage(usecase_path, ifc_file, settings):
elements = []
if ifc_file.schema == "IFC2X3":
@@ -117,4 +165,6 @@ def regenerate_profile_usage(usecase_path, ifc_file, settings):
continue
representation = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
if representation:
bpy.ops.bim.switch_representation(obj=obj.name, ifc_definition_id=representation.id(), should_reload=True)
bpy.ops.bim.switch_representation(
obj=obj.name, ifc_definition_id=representation.id(), should_reload=True, should_switch_all_meshes=True
)
@@ -20,10 +20,9 @@ def element_listener(element, obj):
def mode_callback(obj, data):
for obj in bpy.context.selected_objects + [bpy.context.active_object]:
for obj in set(bpy.context.selected_objects + [bpy.context.active_object]):
if (
obj.mode != "EDIT"
or not obj.data
not obj.data
or not isinstance(obj.data, (bpy.types.Mesh, bpy.types.Curve, bpy.types.TextCurve))
or not obj.BIMObjectProperties.ifc_definition_id
or not bpy.context.scene.BIMProjectProperties.is_authoring
@@ -31,19 +30,51 @@ def mode_callback(obj, data):
return
product = IfcStore.get_file().by_id(obj.BIMObjectProperties.ifc_definition_id)
parametric = ifcopenshell.util.element.get_psets(product).get("EPset_Parametric")
if not parametric or parametric["Engine"] != "BlenderBIM.DumbColumn":
if not parametric or parametric["Engine"] != "BlenderBIM.DumbProfile":
return
IfcStore.edited_objs.add(obj)
bm = bmesh.from_edit_mesh(obj.data)
bmesh.ops.dissolve_limit(bm, angle_limit=pi / 180 * 1, verts=bm.verts, edges=bm.edges)
bmesh.update_edit_mesh(obj.data)
bm.free()
if obj.mode == "EDIT":
IfcStore.edited_objs.add(obj)
bm = bmesh.from_edit_mesh(obj.data)
bmesh.ops.dissolve_limit(bm, angle_limit=pi / 180 * 1, verts=bm.verts, edges=bm.edges)
bmesh.update_edit_mesh(obj.data)
bm.free()
else:
material_usage = ifcopenshell.util.element.get_material(product)
x, y = obj.dimensions[0:2]
if not material_usage.CardinalPoint:
new_origin = obj.matrix_world @ (Vector(obj.bound_box[0]) + (Vector((x, y, 0)) / 2))
elif material_usage.CardinalPoint == 1:
new_origin = obj.matrix_world @ Vector(obj.bound_box[4])
elif material_usage.CardinalPoint == 2:
new_origin = obj.matrix_world @ (Vector(obj.bound_box[0]) + (Vector((x, 0, 0)) / 2))
elif material_usage.CardinalPoint == 3:
new_origin = obj.matrix_world @ Vector(obj.bound_box[0])
elif material_usage.CardinalPoint == 4:
new_origin = obj.matrix_world @ (Vector(obj.bound_box[4]) + (Vector((0, y, 0)) / 2))
elif material_usage.CardinalPoint == 5:
new_origin = obj.matrix_world @ (Vector(obj.bound_box[0]) + (Vector((x, y, 0)) / 2))
elif material_usage.CardinalPoint == 6:
new_origin = obj.matrix_world @ (Vector(obj.bound_box[0]) + (Vector((0, y, 0)) / 2))
elif material_usage.CardinalPoint == 7:
new_origin = obj.matrix_world @ Vector(obj.bound_box[7])
elif material_usage.CardinalPoint == 8:
new_origin = obj.matrix_world @ (Vector(obj.bound_box[3]) + (Vector((x, 0, 0)) / 2))
elif material_usage.CardinalPoint == 9:
new_origin = obj.matrix_world @ Vector(obj.bound_box[3])
if (obj.matrix_world.translation - new_origin).length < 0.001:
return
obj.data.transform(
Matrix.Translation(
(obj.matrix_world.inverted().to_quaternion() @ (obj.matrix_world.translation - new_origin))
)
)
obj.matrix_world.translation = new_origin
def ensure_solid(usecase_path, ifc_file, settings):
product = ifc_file.by_id(settings["blender_object"].BIMObjectProperties.ifc_definition_id)
parametric = ifcopenshell.util.element.get_psets(product).get("EPset_Parametric")
if not parametric or parametric["Engine"] != "BlenderBIM.DumbColumn":
if not parametric or parametric["Engine"] != "BlenderBIM.DumbProfile":
return
material = ifcopenshell.util.element.get_material(product)
if material and material.is_a("IfcMaterialProfileSetUsage"):
@@ -53,7 +84,7 @@ def ensure_solid(usecase_path, ifc_file, settings):
settings["ifc_representation_class"] = "IfcExtrudedAreaSolid/IfcMaterialProfileSetUsage"
class DumbColumnGenerator:
class DumbProfileGenerator:
def __init__(self, relating_type):
self.relating_type = relating_type
@@ -75,9 +106,9 @@ class DumbColumnGenerator:
def derive_from_cursor(self):
self.location = bpy.context.scene.cursor.location
return self.create_column()
return self.create_profile()
def create_column(self):
def create_profile(self):
# A cube
verts = [
Vector((-1, -1, -1)),
@@ -99,17 +130,32 @@ class DumbColumnGenerator:
[0, 2, 6, 4],
]
mesh = bpy.data.meshes.new(name="Dumb Column")
mesh = bpy.data.meshes.new(name="Dumb Profile")
mesh.from_pydata(verts, edges, faces)
obj = bpy.data.objects.new("Column", mesh)
obj.name = "Column"
obj = bpy.data.objects.new("Profile", mesh)
obj.location = self.location
if self.collection_obj and self.collection_obj.BIMObjectProperties.ifc_definition_id:
obj.location[2] = self.collection_obj.location[2]
self.collection.objects.link(obj)
bpy.ops.bim.assign_class(
obj=obj.name, ifc_class="IfcColumn", predefined_type="COLUMN", should_add_representation=False
)
if self.relating_type.is_a("IfcColumnType"):
obj.name = "Column"
bpy.ops.bim.assign_class(
obj=obj.name, ifc_class="IfcColumn", predefined_type="COLUMN", should_add_representation=False
)
elif self.relating_type.is_a("IfcBeamType"):
obj.name = "Beam"
obj.rotation_euler[0] = math.pi / 2
obj.rotation_euler[2] = math.pi / 2
bpy.ops.bim.assign_class(
obj=obj.name, ifc_class="IfcBeam", predefined_type="BEAM", should_add_representation=False
)
elif self.relating_type.is_a("IfcMemberType"):
obj.name = "Member"
obj.rotation_euler[0] = math.pi / 2
obj.rotation_euler[2] = math.pi / 2
bpy.ops.bim.assign_class(
obj=obj.name, ifc_class="IfcMember", predefined_type="MEMBER", should_add_representation=False
)
element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
bpy.ops.bim.assign_type(relating_type=self.relating_type.id(), related_object=obj.name)
profile_set_usage = ifcopenshell.util.element.get_material(element)
@@ -120,40 +166,52 @@ class DumbColumnGenerator:
profile_set_usage=profile_set_usage.id(),
)
representation = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
bpy.ops.bim.switch_representation(obj=obj.name, ifc_definition_id=representation.id(), should_reload=True)
bpy.ops.bim.switch_representation(
obj=obj.name, ifc_definition_id=representation.id(), should_reload=True, should_switch_all_meshes=True
)
pset = ifcopenshell.api.run("pset.add_pset", self.file, product=element, name="EPset_Parametric")
ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"Engine": "BlenderBIM.DumbColumn"})
ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"Engine": "BlenderBIM.DumbProfile"})
MaterialData.load(self.file)
obj.select_set(True)
return obj
class DumbColumnRegenerator:
class DumbProfileRegenerator:
def regenerate_from_profile(self, usecase_path, ifc_file, settings):
self.file = IfcStore.get_file()
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(ifc_file)
self.file = ifc_file
profile = settings["profile"].Profile
if not profile:
return
for element in self.get_elements_using_profile(profile):
self.change_profile(element)
def sync_object_from_profile(self, usecase_path, ifc_file, settings):
self.file = ifc_file
profile = settings["profile"].Profile
if not profile:
return
for element in self.get_elements_using_profile(profile):
self.sync_object(element)
def get_elements_using_profile(self, profile):
results = []
for profile_set in [
mp.ToMaterialProfileSet[0] for mp in self.file.get_inverse(profile) if mp.is_a("IfcMaterialProfile")
]:
for inverse in ifc_file.get_inverse(profile_set):
for inverse in self.file.get_inverse(profile_set):
if not inverse.is_a("IfcMaterialProfileSetUsage"):
continue
if ifc_file.schema == "IFC2X3":
for rel in ifc_file.get_inverse(inverse):
if self.file.schema == "IFC2X3":
for rel in self.file.get_inverse(inverse):
if not rel.is_a("IfcRelAssociatesMaterial"):
continue
for element in rel.RelatedObjects:
self.change_profile(element)
results.extend(rel.RelatedObjects)
else:
for rel in inverse.AssociatedTo:
for element in rel.RelatedObjects:
self.change_profile(element)
results.extend(rel.RelatedObjects)
return results
def regenerate_from_type(self, usecase_path, ifc_file, settings):
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(ifc_file)
new_material = ifcopenshell.util.element.get_material(settings["relating_type"])
if not new_material or not new_material.is_a("IfcMaterialProfileSet"):
return
@@ -165,4 +223,12 @@ class DumbColumnRegenerator:
return
representation = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
if representation:
bpy.ops.bim.switch_representation(obj=obj.name, ifc_definition_id=representation.id(), should_reload=True)
bpy.ops.bim.switch_representation(
obj=obj.name, ifc_definition_id=representation.id(), should_reload=True, should_switch_all_meshes=True
)
def sync_object(self, element):
obj = IfcStore.get_element(element.id())
if not obj or obj not in IfcStore.edited_objs:
return
bpy.ops.bim.update_representation(obj=obj.name)
@@ -0,0 +1,22 @@
import bpy
from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.style.data import Data as StyleData
def sync_name(usecase_path, ifc_file, settings):
if "Name" not in settings["attributes"]:
return
obj = IfcStore.get_element(settings["product"].id())
if not obj:
return
if isinstance(obj, bpy.types.Object):
new_name = "{}/{}".format(settings["product"].is_a(), settings["attributes"]["Name"] or "Unnamed")
elif isinstance(obj, bpy.types.Material):
new_name = settings["attributes"]["Name"] or "Unnamed"
if obj.BIMMaterialProperties.ifc_style_id:
IfcStore.get_file().by_id(obj.BIMMaterialProperties.ifc_style_id).Name = new_name
StyleData.load(IfcStore.get_file(), obj.BIMMaterialProperties.ifc_style_id)
collection = bpy.data.collections.get(obj.name)
if collection:
collection.name = new_name
obj.name = new_name
@@ -195,6 +195,44 @@ def calculate_quantities(usecase_path, ifc_file, settings):
PsetData.load(ifc_file, obj.BIMObjectProperties.ifc_definition_id)
class AddSlabOpening(bpy.types.Operator):
bl_idname = "bim.add_slab_opening"
bl_label = "Add Slab Opening"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
selected_objs = context.selected_objects
if len(selected_objs) == 0 or not context.active_object:
return {"FINISHED"}
slab_obj = context.active_object
if not slab_obj.BIMObjectProperties.ifc_definition_id:
return {"FINISHED"}
slab = IfcStore.get_file().by_id(slab_obj.BIMObjectProperties.ifc_definition_id)
if not slab.is_a("IfcSlab"):
return {"FINISHED"}
local_location = slab_obj.matrix_world.inverted() @ context.scene.cursor.location
raycast = slab_obj.closest_point_on_mesh(local_location, distance=0.01)
if not raycast[0]:
return {"FINISHED"}
bpy.ops.mesh.primitive_cube_add(size=slab_obj.dimensions[2] * 2)
opening = context.selected_objects[0]
# Place the opening in the middle of the slab
global_location = slab_obj.matrix_world @ raycast[1]
normal = raycast[2]
normal.negate()
global_normal = slab_obj.matrix_world.to_quaternion() @ normal
opening.location = global_location + (global_normal * (slab_obj.dimensions[2] / 2))
opening.rotation_euler = slab_obj.rotation_euler
opening.name = "Opening"
bpy.ops.bim.add_opening(opening=opening.name, obj=slab_obj.name)
return {"FINISHED"}
class DumbSlabGenerator:
def __init__(self, relating_type):
self.relating_type = relating_type
@@ -20,7 +20,7 @@ def element_listener(element, obj):
def mode_callback(obj, data):
for obj in bpy.context.selected_objects + [bpy.context.active_object]:
for obj in set(bpy.context.selected_objects + [bpy.context.active_object]):
if (
obj.mode != "EDIT"
or not obj.data
@@ -33,21 +33,65 @@ def mode_callback(obj, data):
parametric = ifcopenshell.util.element.get_psets(product).get("EPset_Parametric")
if not parametric or parametric["Engine"] != "BlenderBIM.DumbWall":
return
if product.HasOpenings:
if [m for m in obj.modifiers if m.type == "BOOLEAN"]:
continue
representation = ifcopenshell.util.representation.get_representation(
product, "Model", "Body", "MODEL_VIEW"
)
if not representation:
continue
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.bim.switch_representation(
obj=obj.name,
should_switch_all_meshes=True,
should_reload=True,
ifc_definition_id=representation.id(),
disable_opening_subtractions=True,
)
bpy.ops.object.mode_set(mode='EDIT')
IfcStore.edited_objs.add(obj)
bm = bmesh.from_edit_mesh(obj.data)
bmesh.ops.dissolve_limit(bm, angle_limit=pi / 180 * 1, verts=bm.verts, edges=bm.edges)
bmesh.update_edit_mesh(obj.data)
bm.free()
class AddWall(bpy.types.Operator):
bl_idname = "bim.add_wall"
bl_label = "Add Wall"
class AddWallOpening(bpy.types.Operator):
bl_idname = "bim.add_wall_opening"
bl_label = "Add Wall Opening"
bl_options = {"REGISTER", "UNDO"}
join_type: bpy.props.StringProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
props = context.scene.BIMModelProperties
bpy.ops.bim.add_type_instance(ifc_class="IfcWallType", relating_type=int(props.relating_type))
selected_objs = context.selected_objects
if len(selected_objs) == 0 or not context.active_object:
return {"FINISHED"}
wall_obj = context.active_object
if not wall_obj.BIMObjectProperties.ifc_definition_id:
return {"FINISHED"}
wall = IfcStore.get_file().by_id(wall_obj.BIMObjectProperties.ifc_definition_id)
if not wall.is_a("IfcWall"):
return {"FINISHED"}
local_location = wall_obj.matrix_world.inverted() @ context.scene.cursor.location
raycast = wall_obj.closest_point_on_mesh(local_location, distance=0.01)
if not raycast[0]:
return {"FINISHED"}
bpy.ops.mesh.primitive_cube_add(size=wall_obj.dimensions[1] * 2)
opening = bpy.context.selected_objects[0]
# Place the opening in the middle of the wall
global_location = wall_obj.matrix_world @ raycast[1]
normal = raycast[2]
normal.negate()
global_normal = wall_obj.matrix_world.to_quaternion() @ normal
opening.location = global_location + (global_normal * (wall_obj.dimensions[1] / 2))
opening.rotation_euler = wall_obj.rotation_euler
opening.name = "Opening"
bpy.ops.bim.add_opening(opening=opening.name, obj=wall_obj.name)
return {"FINISHED"}
@@ -65,7 +109,13 @@ class JoinWall(bpy.types.Operator):
for obj in selected_objs:
DumbWallJoiner(obj, obj).unjoin()
return {"FINISHED"}
if len(selected_objs) < 2 or not context.active_object:
if not context.active_object:
return {"FINISHED"}
if len(selected_objs) == 1:
DumbWallJoiner(context.active_object, target_coordinate=context.scene.cursor.location).extend()
IfcStore.edited_objs.add(context.active_object)
return {"FINISHED"}
if len(selected_objs) < 2:
return {"FINISHED"}
for obj in selected_objs:
if obj == context.active_object:
@@ -327,16 +377,19 @@ class DumbWallJoiner:
# 2. Given an "end face", identify a side "target face" of the other wall
# to project towards.
# 3. Project the vertices of an "end face" to the "target face".
def __init__(self, wall1, wall2):
# Alternatively, a target coordinate may be provided as an imaginary point for the wall to join to
def __init__(self, wall1, wall2=None, target_coordinate=None):
self.wall1 = wall1
self.wall2 = wall2
self.target_coordinate = target_coordinate
self.should_project_to_frontface = True
self.should_attempt_v_junction_projection = False
self.initialise_convenience_variables()
def initialise_convenience_variables(self):
self.wall1_matrix = self.wall1.matrix_world
self.wall2_matrix = self.wall2.matrix_world
if self.wall2:
self.wall2_matrix = self.wall2.matrix_world
self.pos_x = self.wall1_matrix.to_quaternion() @ Vector((1, 0, 0))
self.neg_x = self.wall1_matrix.to_quaternion() @ Vector((-1, 0, 0))
@@ -354,6 +407,26 @@ class DumbWallJoiner:
self.wall1.data.vertices[v].co[0] = max_x
self.recalculate_origins()
# An extension is where a single end of wall1 is projected to an imaginary
# plane denoted by the target coordinate.
def extend(self):
wall1_min_faces, wall1_max_faces = self.get_wall_end_faces(self.wall1)
ef1_distance = abs(mathutils.geometry.distance_point_to_plane(
self.wall1_matrix @ self.wall1.data.vertices[wall1_min_faces[0].vertices[0]].co,
self.target_coordinate,
self.pos_x,
))
ef2_distance = abs(mathutils.geometry.distance_point_to_plane(
self.wall1_matrix @ self.wall1.data.vertices[wall1_max_faces[0].vertices[0]].co,
self.target_coordinate,
self.neg_x,
))
if ef1_distance < ef2_distance:
self.project_end_faces_to_target(wall1_min_faces)
else:
self.project_end_faces_to_target(wall1_max_faces)
self.recalculate_origins()
# A T-junction is an ordered operation where a single end of wall1 is joined
# to wall2 if possible (i.e. walls aren't parallel). Wall2 is not modified.
# First, wall1 end faces are identified. We attempt to project an end face
@@ -439,7 +512,8 @@ class DumbWallJoiner:
def recalculate_origins(self):
bpy.context.view_layer.update()
recalculate_dumb_wall_origin(self.wall1)
recalculate_dumb_wall_origin(self.wall2)
if self.wall2:
recalculate_dumb_wall_origin(self.wall2)
def swap_walls(self):
self.wall1, self.wall2 = self.wall2, self.wall1
@@ -467,6 +541,14 @@ class DumbWallJoiner:
local_point = wall_matrix.inverted() @ point
wall.data.vertices[v].co = local_point
def project_end_faces_to_target(self, end_faces):
for end_face in end_faces:
for v in end_face.vertices:
vertex = self.wall1_matrix @ self.wall1.data.vertices[v].co
self.wall1.data.vertices[v].co = self.wall1_matrix.inverted() @ mathutils.geometry.intersect_line_plane(
vertex, vertex + self.pos_x, self.target_coordinate, self.pos_x
)
# A projection target face is a side face on the target wall that has a
# significant local Y component to its normal (i.e. is not pointing up or
# down or something). In addition, its plane must intersect with the
@@ -67,7 +67,7 @@ def add_object(self, context):
obj = bpy.data.objects.new("Window Profile", mesh)
context.view_layer.active_layer_collection.collection.objects.link(obj)
bpy.context.view_layer.objects.active = obj
context.view_layer.objects.active = obj
obj.select_set(True)
bpy.ops.object.convert(target="CURVE")
@@ -85,7 +85,7 @@ def add_object(self, context):
obj2 = bpy.data.objects.new("Window", mesh)
context.view_layer.active_layer_collection.collection.objects.link(obj2)
bpy.context.view_layer.objects.active = obj2
context.view_layer.objects.active = obj2
obj2.select_set(True)
bpy.ops.object.convert(target="CURVE")
obj2.data.splines[0].use_cyclic_u = True
@@ -116,11 +116,11 @@ def add_object(self, context):
modifier.thickness = self.overall_height - 0.08
context.view_layer.active_layer_collection.collection.objects.link(obj3)
bpy.context.view_layer.objects.active = obj3
context.view_layer.objects.active = obj3
obj3.select_set(True)
bpy.ops.object.convert(target="MESH")
ctx = bpy.context.copy()
ctx = context.copy()
ctx["active_object"] = obj2
ctx["selected_editable_objects"] = [obj2, obj3]
bpy.ops.object.join(ctx)
@@ -142,7 +142,7 @@ def add_object(self, context):
modifier.thickness = self.overall_height
context.view_layer.active_layer_collection.collection.objects.link(obj4)
bpy.context.view_layer.objects.active = obj4
context.view_layer.objects.active = obj4
obj4.select_set(True)
bpy.ops.object.convert(target="MESH")
obj4.display_type = "WIRE"
@@ -1,6 +1,7 @@
import os
import bpy
from bpy.types import WorkSpaceTool
from blenderbim.bim.ifc import IfcStore
class BimTool(WorkSpaceTool):
@@ -16,44 +17,120 @@ class BimTool(WorkSpaceTool):
bl_keymap = (
# ("bim.wall_tool_op", {"type": 'MOUSEMOVE', "value": 'ANY'}, {"properties": []}),
# ("mesh.add_wall", {"type": 'LEFTMOUSE', "value": 'PRESS'}, {"properties": []}),
("bim.add_wall", {"type": "A", "value": "PRESS", "shift": True}, {"properties": []}),
("bim.join_wall", {"type": "E", "value": "PRESS", "shift": True}, {"properties": [("join_type", "T")]}),
("bim.add_type_instance", {"type": "A", "value": "PRESS", "shift": True}, {"properties": []}),
("bim.hotkey", {"type": "E", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_E")]}),
("bim.join_wall", {"type": "T", "value": "PRESS", "shift": True}, {"properties": [("join_type", "L")]}),
("bim.join_wall", {"type": "Y", "value": "PRESS", "shift": True}, {"properties": [("join_type", "V")]}),
("bim.flip_wall", {"type": "F", "value": "PRESS", "shift": True}, {"properties": []}),
("bim.split_wall", {"type": "S", "value": "PRESS", "shift": True}, {"properties": []}),
(
"bim.align_wall",
{"type": "X", "value": "PRESS", "shift": True},
{"properties": [("align_type", "EXTERIOR")]},
),
(
"bim.align_wall",
{"type": "C", "value": "PRESS", "shift": True},
{"properties": [("align_type", "CENTERLINE")]},
),
(
"bim.align_wall",
{"type": "V", "value": "PRESS", "shift": True},
{"properties": [("align_type", "INTERIOR")]},
),
("bim.hotkey", {"type": "X", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_X")]}),
("bim.hotkey", {"type": "C", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_C")]}),
("bim.hotkey", {"type": "V", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_V")]}),
("bim.hotkey", {"type": "O", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_O")]}),
("bim.hotkey", {"type": "O", "value": "PRESS", "alt": True}, {"properties": [("hotkey", "A_O")]}),
)
def draw_settings(context, layout, tool):
props = context.scene.BIMModelProperties
row = layout.row(align=True)
props = context.scene.BIMTypeProperties
row.prop(props, "ifc_class", text="")
row.prop(props, "relating_type", text="")
row.label(text="", icon="BLANK1")
row = layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="Add", icon="EVENT_A")
row.label(text="Extend", icon="EVENT_E")
row.label(text="Butt", icon="EVENT_T")
row.label(text="Mitre", icon="EVENT_Y")
row.label(text="Flip", icon="EVENT_F")
row.label(text="Split", icon="EVENT_S")
row.label(text="", icon="EVENT_X")
row.label(text="", icon="EVENT_C")
row.label(text="", icon="EVENT_V")
row.label(text="Add Type Instance", icon="EVENT_A")
if props.ifc_class == "IfcWallType":
row = layout.row(align=True)
row.label(text="Join")
row = layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="Extend", icon="EVENT_E")
row = layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="Butt", icon="EVENT_T")
row = layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="Mitre", icon="EVENT_Y")
row = layout.row(align=True)
row.label(text="Wall Tools")
row = layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="Flip", icon="EVENT_F")
row = layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="Split", icon="EVENT_S")
row = layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="Opening", icon="EVENT_O")
if props.ifc_class == "IfcSlabType":
row = layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="Opening", icon="EVENT_O")
row = layout.row(align=True)
row.label(text="Align")
row = layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="Align Exterior", icon="EVENT_X")
row = layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="Align Centerline", icon="EVENT_C")
row = layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="Align Interior", icon="EVENT_V")
row = layout.row(align=True)
row = layout.row(align=True)
row.label(text="", icon="EVENT_ALT")
row.label(text="Opening", icon="EVENT_O")
class Hotkey(bpy.types.Operator):
bl_idname = "bim.hotkey"
bl_label = "Hotkey"
bl_options = {"REGISTER", "UNDO"}
hotkey: bpy.props.StringProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
self.props = context.scene.BIMTypeProperties
getattr(self, f"hotkey_{self.hotkey}")()
return {"FINISHED"}
def hotkey_S_C(self):
if self.props.ifc_class == "IfcWallType":
bpy.ops.bim.align_wall(align_type="CENTERLINE")
else:
bpy.ops.bim.align_product(align_type="CENTERLINE")
def hotkey_S_E(self):
if self.props.ifc_class == "IfcWallType":
bpy.ops.bim.join_wall(join_type="T")
def hotkey_S_V(self):
if self.props.ifc_class == "IfcWallType":
bpy.ops.bim.align_wall(align_type="INTERIOR")
else:
bpy.ops.bim.align_product(align_type="POSITIVE")
def hotkey_S_X(self):
if self.props.ifc_class == "IfcWallType":
bpy.ops.bim.align_wall(align_type="EXTERIOR")
else:
bpy.ops.bim.align_product(align_type="NEGATIVE")
def hotkey_S_O(self):
if self.props.ifc_class == "IfcWallType":
bpy.ops.bim.add_wall_opening()
elif self.props.ifc_class == "IfcSlabType":
bpy.ops.bim.add_slab_opening()
def hotkey_A_O(self):
bpy.ops.bim.toggle_opening_visibility()
@@ -4,8 +4,8 @@ from ifcopenshell.api.owner.data import Data
from blenderbim.bim.ifc import IfcStore
def draw_roles_ui(box, assigned_object_id, roles):
props = bpy.context.scene.BIMOwnerProperties
def draw_roles_ui(box, assigned_object_id, roles, context):
props = context.scene.BIMOwnerProperties
row = box.row(align=True)
row.label(text="Roles")
row.operator("bim.add_role", icon="ADD", text="").assigned_object_id = assigned_object_id
@@ -30,8 +30,8 @@ def draw_roles_ui(box, assigned_object_id, roles):
row.operator("bim.remove_role", icon="X", text="").role_id = role_id
def draw_addresses_ui(box, assigned_object_id, addresses, file):
props = bpy.context.scene.BIMOwnerProperties
def draw_addresses_ui(box, assigned_object_id, addresses, file, context):
props = context.scene.BIMOwnerProperties
row = box.row(align=True)
row.label(text="Addresses")
op = row.operator("bim.add_address", icon="LINK_BLEND", text="")
@@ -135,8 +135,8 @@ class BIM_PT_people(Panel):
row = box.row()
row.prop(blender_person, "suffix_titles")
draw_roles_ui(box, person_id, person["Roles"])
draw_addresses_ui(box, person_id, person["Addresses"], self.file)
draw_roles_ui(box, person_id, person["Roles"], context)
draw_addresses_ui(box, person_id, person["Addresses"], self.file, context)
else:
row = self.layout.row(align=True)
name = person["Id"] if self.file.schema == "IFC2X3" else person["Identification"]
@@ -189,8 +189,8 @@ class BIM_PT_organisations(Panel):
row = box.row()
row.prop(blender_organisation, "description")
draw_roles_ui(box, organisation_id, organisation["Roles"])
draw_addresses_ui(box, organisation_id, organisation["Addresses"], self.file)
draw_roles_ui(box, organisation_id, organisation["Roles"], context)
draw_addresses_ui(box, organisation_id, organisation["Addresses"], self.file, context)
else:
row = self.layout.row(align=True)
row.label(text=organisation["Name"])
@@ -0,0 +1,9 @@
classes = ()
def register():
pass
def unregister():
pass
@@ -29,17 +29,17 @@ class CreateProject(bpy.types.Operator):
return {"FINISHED"}
IfcStore.file = ifcopenshell.api.run(
"project.create_file", **{"version": bpy.context.scene.BIMProperties.export_schema}
"project.create_file", **{"version": context.scene.BIMProperties.export_schema}
)
self.file = IfcStore.get_file()
bpy.ops.bim.add_person()
bpy.ops.bim.add_organisation()
project = bpy.data.objects.new("My Project", None)
site = bpy.data.objects.new("My Site", None)
building = bpy.data.objects.new("My Building", None)
building_storey = bpy.data.objects.new("Ground Floor", None)
project = bpy.data.objects.new(self.get_name("IfcProject", "My Project"), None)
site = bpy.data.objects.new(self.get_name("IfcSite", "My Site"), None)
building = bpy.data.objects.new(self.get_name("IfcBuilding", "My Building"), None)
building_storey = bpy.data.objects.new(self.get_name("IfcBuildingStorey", "My Storey"), None)
bpy.ops.bim.assign_class(obj=project.name, ifc_class="IfcProject")
bpy.ops.bim.assign_unit()
@@ -49,7 +49,7 @@ class CreateProject(bpy.types.Operator):
bpy.ops.bim.add_subcontext(context="Plan")
bpy.ops.bim.add_subcontext(context="Plan", subcontext="Annotation", target_view="PLAN_VIEW")
bpy.context.scene.BIMProperties.contexts = str(
context.scene.BIMProperties.contexts = str(
ifcopenshell.util.representation.get_context(self.file, "Model", "Body", "MODEL_VIEW").id()
)
@@ -62,6 +62,14 @@ class CreateProject(bpy.types.Operator):
return {"FINISHED"}
def get_name(self, ifc_class, name):
if not bpy.data.objects.get(f"{ifc_class}/{name}"):
return name
i = 2
while bpy.data.objects.get(f"{ifc_class}/{name} {i}"):
i += 1
return f"{name} {i}"
def rollback(self, data):
IfcStore.file = None
@@ -89,7 +97,7 @@ class CreateProjectLibrary(bpy.types.Operator):
return {"FINISHED"}
IfcStore.file = ifcopenshell.api.run(
"project.create_file", **{"version": bpy.context.scene.BIMProperties.export_schema}
"project.create_file", **{"version": context.scene.BIMProperties.export_schema}
)
self.file = IfcStore.get_file()
@@ -318,14 +326,14 @@ class AppendLibraryElement(bpy.types.Operator):
library=IfcStore.library_file,
element=IfcStore.library_file.by_id(self.definition),
)
self.import_type_from_ifc(element)
self.import_type_from_ifc(element, context)
blenderbim.bim.handler.purge_module_data()
return {"FINISHED"}
def import_type_from_ifc(self, element):
def import_type_from_ifc(self, element, context):
self.file = IfcStore.get_file()
logger = logging.getLogger("ImportIFC")
ifc_import_settings = import_ifc.IfcImportSettings.factory(bpy.context, IfcStore.path, logger)
ifc_import_settings = import_ifc.IfcImportSettings.factory(context, IfcStore.path, logger)
type_collection = bpy.data.collections.get("Types")
if not type_collection:
@@ -16,7 +16,7 @@ class TogglePsetExpansion(bpy.types.Operator):
pset_id: bpy.props.IntProperty()
def execute(self, context):
obj = bpy.context.active_object
obj = context.active_object
data = Data.psets if self.pset_id in Data.psets else Data.qtos
data[self.pset_id]["is_expanded"] = not data[self.pset_id]["is_expanded"]
return {"FINISHED"}
@@ -195,6 +195,7 @@ class EditPset(bpy.types.Operator):
"pset": self.file.by_id(pset_id),
"name": props.active_pset_name,
"properties": properties,
"pset_template": blenderbim.bim.schema.ifc.psetqto.get_by_name(props.active_pset_name),
},
)
else:
@@ -294,7 +295,7 @@ class AddQto(bpy.types.Operator):
def _execute(self, context):
self.file = IfcStore.get_file()
obj = bpy.context.active_object
obj = context.active_object
oprops = obj.BIMObjectProperties
props = obj.PsetProperties
ifcopenshell.api.run(
@@ -317,23 +318,23 @@ class GuessQuantity(bpy.types.Operator):
def execute(self, context):
self.qto_calculator = QtoCalculator()
obj = bpy.context.active_object
obj = context.active_object
prop = obj.PsetProperties.properties.get(self.prop)
prop.float_value = self.guess_quantity(obj)
prop.float_value = self.guess_quantity(obj, context)
return {"FINISHED"}
def guess_quantity(self, obj):
def guess_quantity(self, obj, context):
quantity = self.qto_calculator.guess_quantity(self.prop, [p.name for p in obj.PsetProperties.properties], obj)
if "area" in self.prop.lower():
if bpy.context.scene.BIMProperties.area_unit:
prefix, name = self.get_prefix_name(bpy.context.scene.BIMProperties.area_unit)
if context.scene.BIMProperties.area_unit:
prefix, name = self.get_prefix_name(context.scene.BIMProperties.area_unit)
quantity = ifcopenshell.util.unit.convert(quantity, None, "SQUARE_METRE", prefix, name)
elif "volume" in self.prop.lower():
if bpy.context.scene.BIMProperties.volume_unit:
prefix, name = self.get_prefix_name(bpy.context.scene.BIMProperties.volume_unit)
if context.scene.BIMProperties.volume_unit:
prefix, name = self.get_prefix_name(context.scene.BIMProperties.volume_unit)
quantity = ifcopenshell.util.unit.convert(quantity, None, "CUBIC_METRE", prefix, name)
else:
prefix, name = self.get_blender_prefix_name()
prefix, name = self.get_blender_prefix_name(context)
quantity = ifcopenshell.util.unit.convert(quantity, None, "METRE", prefix, name)
return round(quantity, 3)
@@ -342,13 +343,14 @@ class GuessQuantity(bpy.types.Operator):
return value.split("/")
return None, value
def get_blender_prefix_name(self):
if bpy.context.scene.unit_settings.system == "IMPERIAL":
if bpy.context.scene.unit_settings.length_unit == "INCHES":
def get_blender_prefix_name(self, context):
unit_settings = context.scene.unit_settings
if unit_settings.system == "IMPERIAL":
if unit_settings.length_unit == "INCHES":
return None, "inch"
elif bpy.context.scene.unit_settings.length_unit == "FEET":
elif unit_settings.length_unit == "FEET":
return None, "foot"
elif bpy.context.scene.unit_settings.system == "METRIC":
if bpy.context.scene.unit_settings.length_unit == "METERS":
elif unit_settings.system == "METRIC":
if unit_settings.length_unit == "METERS":
return None, "METRE"
return bpy.context.scene.unit_settings.length_unit[0 : -len("METERS")], "METRE"
return unit_settings.length_unit[0 : -len("METERS")], "METRE"
@@ -1,7 +1,8 @@
import bpy
import blenderbim.bim.schema
from blenderbim.bim.prop import Attribute
from ifcopenshell.api.pset.data import Data
import blenderbim.bim.schema
from blenderbim.bim.ifc import IfcStore
from bpy.types import PropertyGroup
from bpy.props import (
PointerProperty,
@@ -29,16 +30,17 @@ def purge():
def getPsetNames(self, context):
global psetnames
obj = context.active_object
if "/" in obj.name:
ifc_class = obj.name.split("/")[0]
if ifc_class not in psetnames:
psets = blenderbim.bim.schema.ifc.psetqto.get_applicable(ifc_class, pset_only=True)
psetnames[ifc_class] = [(p.Name, p.Name, "") for p in psets]
assigned_names = [
Data.psets[p]["Name"] for p in Data.products[obj.BIMObjectProperties.ifc_definition_id]["psets"]
]
return [p for p in psetnames[ifc_class] if p[0] not in assigned_names]
return []
if not obj.BIMObjectProperties.ifc_definition_id:
return []
element = IfcStore.get_file().by_id(obj.BIMObjectProperties.ifc_definition_id)
ifc_class = element.is_a()
if ifc_class not in psetnames:
psets = blenderbim.bim.schema.ifc.psetqto.get_applicable(ifc_class, pset_only=True)
psetnames[ifc_class] = [(p.Name, p.Name, "") for p in psets]
assigned_names = [
Data.psets[p]["Name"] for p in Data.products[obj.BIMObjectProperties.ifc_definition_id]["psets"]
]
return [p for p in psetnames[ifc_class] if p[0] not in assigned_names]
def getMaterialPsetNames(self, context):
@@ -122,8 +122,8 @@ class BIM_PT_object_psets(Panel):
op.obj = context.active_object.name
op.obj_type = "Object"
for pset_id in Data.products[oprops.ifc_definition_id]["psets"]:
pset = Data.psets[pset_id]
psets = [(pset_id, Data.psets[pset_id]) for pset_id in Data.products[oprops.ifc_definition_id]["psets"]]
for pset_id, pset in sorted(psets, key = lambda v: v[1]["Name"]):
draw_psetqto_ui(context, pset_id, pset, props, self.layout, "Object")
# TODO reimplement. See #1222.
@@ -165,8 +165,8 @@ class BIM_PT_object_qtos(Panel):
row.prop(props, "qto_name", text="")
row.operator("bim.add_qto", icon="ADD", text="")
for qto_id in Data.products[oprops.ifc_definition_id]["qtos"]:
qto = Data.qtos[qto_id]
qtos = [(qto_id, Data.qtos[qto_id]) for qto_id in Data.products[oprops.ifc_definition_id]["qtos"]]
for qto_id, qto in sorted(qtos, key = lambda v: v[1]["Name"]):
draw_psetqto_ui(context, qto_id, qto, props, self.layout, "Object")
@@ -207,6 +207,6 @@ class BIM_PT_material_psets(Panel):
op.obj = context.active_object.active_material.name
op.obj_type = "Material"
for pset_id in Data.products[oprops.ifc_definition_id]["psets"]:
pset = Data.psets[pset_id]
psets = [(pset_id, Data.psets[pset_id]) for pset_id in Data.products[oprops.ifc_definition_id]["psets"]]
for pset_id, pset in sorted(psets, key = lambda v: v[1]["Name"]):
draw_psetqto_ui(context, pset_id, pset, props, self.layout, "Material")
@@ -1,6 +1,8 @@
import bpy
import ifcopenshell
import ifcopenshell.api
import blenderbim.bim.schema
import blenderbim.bim.handler
from blenderbim.bim.module.pset_template.prop import updatePsetTemplateFiles, updatePsetTemplates
from ifcopenshell.api.pset_template.data import Data
from blenderbim.bim.ifc import IfcStore
@@ -165,6 +167,8 @@ class SavePsetTemplateFile(bpy.types.Operator):
def execute(self, context):
IfcStore.pset_template_file.write(IfcStore.pset_template_path)
blenderbim.bim.handler.purge_module_data()
blenderbim.bim.schema.reload()
return {"FINISHED"}
@@ -14,7 +14,7 @@ def calculate_volume(obj):
return result
def calculate_formwork_area(objs):
def calculate_formwork_area(objs, context):
"""
Formwork is defined as the surface area required to cover all exposed
surfaces of one or more objects, excluding top surfaces (i.e. that have a
@@ -27,7 +27,7 @@ def calculate_formwork_area(objs):
new_obj = obj.copy()
new_obj.data = obj.data.copy()
new_obj.animation_data_clear()
bpy.context.collection.objects.link(new_obj)
context.collection.objects.link(new_obj)
copied_objs.append(new_obj)
context_override = {}
@@ -53,7 +53,7 @@ def calculate_formwork_area(objs):
else:
modifier.octree_depth = 5
mesh = copied_objs[0].evaluated_get(bpy.context.evaluated_depsgraph_get()).to_mesh()
mesh = copied_objs[0].evaluated_get(context.evaluated_depsgraph_get()).to_mesh()
for polygon in mesh.polygons:
if polygon.normal.z > 0.5:
continue
@@ -14,13 +14,13 @@ class CalculateEdgeLengths(bpy.types.Operator):
def execute(self, context):
result = 0
for obj in bpy.context.selected_objects:
for obj in context.selected_objects:
if not obj.data or not obj.data.edges:
continue
for edge in obj.data.edges:
if edge.select:
result += (obj.data.vertices[edge.vertices[1]].co - obj.data.vertices[edge.vertices[0]].co).length
bpy.context.scene.BIMQtoProperties.qto_result = str(round(result, 3))
context.scene.BIMQtoProperties.qto_result = str(round(result, 3))
return {"FINISHED"}
@@ -31,13 +31,13 @@ class CalculateFaceAreas(bpy.types.Operator):
def execute(self, context):
result = 0
for obj in bpy.context.selected_objects:
for obj in context.selected_objects:
if not obj.data or not obj.data.polygons:
continue
for polygon in obj.data.polygons:
if polygon.select:
result += polygon.area
bpy.context.scene.BIMQtoProperties.qto_result = str(round(result, 3))
context.scene.BIMQtoProperties.qto_result = str(round(result, 3))
return {"FINISHED"}
@@ -48,14 +48,14 @@ class CalculateObjectVolumes(bpy.types.Operator):
def execute(self, context):
result = 0
for obj in bpy.context.selected_objects:
for obj in context.selected_objects:
if not obj.data or not isinstance(obj.data, bpy.types.Mesh):
continue
bm = bmesh.new()
bm.from_mesh(obj.data)
result += bm.calc_volume()
bm.free()
bpy.context.scene.BIMQtoProperties.qto_result = str(round(result, 3))
context.scene.BIMQtoProperties.qto_result = str(round(result, 3))
return {"FINISHED"}
@@ -65,16 +65,16 @@ class ExecuteQtoMethod(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
props = bpy.context.scene.BIMQtoProperties
props = context.scene.BIMQtoProperties
result = 0
if props.qto_methods == "HEIGHT":
for obj in bpy.context.selected_objects:
for obj in context.selected_objects:
result += helper.calculate_height(obj)
elif props.qto_methods == "VOLUME":
for obj in bpy.context.selected_objects:
for obj in context.selected_objects:
result += helper.calculate_volume(obj)
elif props.qto_methods == "FORMWORK":
result = helper.calculate_formwork_area(bpy.context.selected_objects)
result = helper.calculate_formwork_area(context.selected_objects, context)
props.qto_result = str(round(result, 3))
return {"FINISHED"}
@@ -88,9 +88,9 @@ class QuantifyObjects(bpy.types.Operator):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
props = bpy.context.scene.BIMQtoProperties
props = context.scene.BIMQtoProperties
self.file = IfcStore.get_file()
for obj in bpy.context.selected_objects:
for obj in context.selected_objects:
if not obj.BIMObjectProperties.ifc_definition_id:
continue
result = 0
@@ -99,7 +99,7 @@ class QuantifyObjects(bpy.types.Operator):
elif props.qto_methods == "VOLUME":
result = helper.calculate_volume(obj)
elif props.qto_methods == "FORMWORK":
result = helper.calculate_formwork_area([obj])
result = helper.calculate_formwork_area([obj], context)
if not result:
continue
result = round(result, 3)
@@ -230,7 +230,7 @@ class AssignResource(bpy.types.Operator):
def _execute(self, context):
related_objects = (
[bpy.data.objects.get(self.related_object)] if self.related_object else bpy.context.selected_objects
[bpy.data.objects.get(self.related_object)] if self.related_object else context.selected_objects
)
for related_object in related_objects:
self.file = IfcStore.get_file()
@@ -253,7 +253,7 @@ class UnassignResource(bpy.types.Operator):
def execute(self, context):
related_objects = (
[bpy.data.objects.get(self.related_object)] if self.related_object else bpy.context.selected_objects
[bpy.data.objects.get(self.related_object)] if self.related_object else context.selected_objects
)
for related_object in related_objects:
self.file = IfcStore.get_file()
@@ -13,7 +13,8 @@ class BIM_PT_resources(Panel):
@classmethod
def poll(cls, context):
return IfcStore.get_file()
file = IfcStore.get_file()
return file and hasattr(file, "schema") and file.schema != "IFC2X3"
def draw(self, context):
self.props = context.scene.BIMResourceProperties
@@ -15,10 +15,10 @@ class EnableReassignClass(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
obj = bpy.context.active_object
obj = context.active_object
self.file = IfcStore.get_file()
ifc_class = obj.name.split("/")[0]
bpy.context.active_object.BIMObjectProperties.is_reassigning_class = True
context.active_object.BIMObjectProperties.is_reassigning_class = True
ifc_products = [
"IfcElement",
"IfcElementType",
@@ -31,11 +31,11 @@ class EnableReassignClass(bpy.types.Operator):
]
for ifc_product in ifc_products:
if ifcopenshell.util.schema.is_a(IfcStore.get_schema().declaration_by_name(ifc_class), ifc_product):
bpy.context.scene.BIMRootProperties.ifc_product = ifc_product
context.scene.BIMRootProperties.ifc_product = ifc_product
element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
bpy.context.scene.BIMRootProperties.ifc_class = element.is_a()
context.scene.BIMRootProperties.ifc_class = element.is_a()
if hasattr(element, "PredefinedType") and element.PredefinedType:
bpy.context.scene.BIMRootProperties.ifc_predefined_type = element.PredefinedType
context.scene.BIMRootProperties.ifc_predefined_type = element.PredefinedType
return {"FINISHED"}
@@ -45,7 +45,7 @@ class DisableReassignClass(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
bpy.context.active_object.BIMObjectProperties.is_reassigning_class = False
context.active_object.BIMObjectProperties.is_reassigning_class = False
return {"FINISHED"}
@@ -59,18 +59,18 @@ class ReassignClass(bpy.types.Operator):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
objects = [bpy.data.objects.get(self.obj)] if self.obj else bpy.context.selected_objects
objects = [bpy.data.objects.get(self.obj)] if self.obj else context.selected_objects
self.file = IfcStore.get_file()
predefined_type = bpy.context.scene.BIMRootProperties.ifc_predefined_type
predefined_type = context.scene.BIMRootProperties.ifc_predefined_type
if predefined_type == "USERDEFINED":
predefined_type = bpy.context.scene.BIMRootProperties.ifc_userdefined_type
predefined_type = context.scene.BIMRootProperties.ifc_userdefined_type
for obj in objects:
product = ifcopenshell.api.run(
"root.reassign_class",
self.file,
**{
"product": self.file.by_id(obj.BIMObjectProperties.ifc_definition_id),
"ifc_class": bpy.context.scene.BIMRootProperties.ifc_class,
"ifc_class": context.scene.BIMRootProperties.ifc_class,
"predefined_type": predefined_type,
},
)
@@ -96,11 +96,11 @@ class AssignClass(bpy.types.Operator):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
objects = [bpy.data.objects.get(self.obj)] if self.obj else bpy.context.selected_objects
objects = [bpy.data.objects.get(self.obj)] if self.obj else context.selected_objects
self.file = IfcStore.get_file()
self.declaration = IfcStore.get_schema().declaration_by_name(self.ifc_class)
if self.predefined_type == "USERDEFINED":
self.predefined_type = self.ifc_userdefined_type
self.predefined_type = self.userdefined_type
elif self.predefined_type == "":
predefined_type = None
for obj in objects:
@@ -128,20 +128,22 @@ class AssignClass(bpy.types.Operator):
)
if product.is_a("IfcElementType"):
self.place_in_types_collection(obj)
self.place_in_types_collection(obj, context)
elif product.is_a("IfcOpeningElement"):
self.place_in_openings_collection(obj, context)
elif (
product.is_a("IfcSpatialElement")
or product.is_a("IfcSpatialStructureElement")
or product.is_a("IfcProject")
or product.is_a("IfcContext")
):
self.place_in_spatial_collection(obj)
self.place_in_spatial_collection(obj, context)
else:
self.assign_potential_spatial_container(obj)
context.view_layer.objects.active = obj
def place_in_types_collection(self, obj):
for project in [c for c in bpy.context.view_layer.layer_collection.children if "IfcProject" in c.name]:
def place_in_types_collection(self, obj, context):
for project in [c for c in context.view_layer.layer_collection.children if "IfcProject" in c.name]:
if not [c for c in project.children if "Types" in c.name]:
types = bpy.data.collections.new("Types")
project.collection.children.link(types)
@@ -152,7 +154,19 @@ class AssignClass(bpy.types.Operator):
break
break
def place_in_spatial_collection(self, obj):
def place_in_openings_collection(self, obj, context):
for project in [c for c in context.view_layer.layer_collection.children if "IfcProject" in c.name]:
if not [c for c in project.children if "IfcOpeningElements" in c.name]:
opening_elements = bpy.data.collections.new("IfcOpeningElements")
project.collection.children.link(opening_elements)
for collection in [c for c in project.children if "IfcOpeningElements" in c.name]:
for user_collection in obj.users_collection:
user_collection.objects.unlink(obj)
collection.collection.objects.link(obj)
break
break
def place_in_spatial_collection(self, obj, context):
for collection in obj.users_collection:
if collection.name == obj.name:
return
@@ -167,7 +181,7 @@ class AssignClass(bpy.types.Operator):
parent_collection.children.link(collection)
bpy.ops.bim.assign_object(related_object=obj.name, relating_object=parent_collection.name)
else:
bpy.context.scene.collection.children.link(collection)
context.scene.collection.children.link(collection)
def assign_potential_spatial_container(self, obj):
for collection in obj.users_collection:
@@ -195,7 +209,7 @@ class UnassignClass(bpy.types.Operator):
if self.obj:
objects = [bpy.data.objects.get(self.obj)]
else:
objects = bpy.context.selected_objects
objects = context.selected_objects
for obj in objects:
if not obj.BIMObjectProperties.ifc_definition_id:
continue
@@ -238,7 +252,7 @@ class UnlinkObject(bpy.types.Operator):
if self.obj:
objects = [bpy.data.objects.get(self.obj)]
else:
objects = bpy.context.selected_objects
objects = context.selected_objects
for obj in objects:
if obj.BIMObjectProperties.ifc_definition_id:
IfcStore.unlink_element(obj=obj)
@@ -261,17 +275,33 @@ class CopyClass(bpy.types.Operator):
if self.obj:
objects = [bpy.data.objects.get(self.obj)]
else:
objects = bpy.context.selected_objects
objects = context.selected_objects
for obj in objects:
if not obj.BIMObjectProperties.ifc_definition_id:
continue
result = ifcopenshell.api.run(
"root.copy_class", self.file, **{"product": self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)}
)
old_element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
result = ifcopenshell.api.run("root.copy_class", self.file, **{"product": old_element})
IfcStore.link_element(result, obj)
relating_type = ifcopenshell.util.element.get_type(result)
if relating_type and relating_type.RepresentationMaps:
bpy.ops.bim.assign_type(relating_type=relating_type.id(), related_object=obj.name)
else:
bpy.ops.bim.add_representation(obj=obj.name)
if result.is_a("IfcSpatialElement") or element.is_a("IfcSpatialStructureElement"):
self.place_in_spatial_collection(old_element, obj)
return {"FINISHED"}
def place_in_spatial_collection(self, old_element, obj):
aggregate = ifcopenshell.util.element.get_aggregate(old_element)
if not aggregate:
return
container_obj = IfcStore.get_element(aggregate.id())
for collection in obj.users_collection:
collection.objects.unlink(obj)
if "Ifc" in collection.name:
parent_collection = collection
for collection in container_obj.users_collection:
if collection.name == container_obj.name:
new = bpy.data.collections.new(obj.name)
new.objects.link(obj)
collection.children.link(new)
@@ -30,7 +30,7 @@ class BIM_PT_class(Panel):
row = self.layout.row(align=True)
row.operator("bim.reassign_class", icon="CHECKMARK")
row.operator("bim.disable_reassign_class", icon="X", text="")
self.draw_class_dropdowns()
self.draw_class_dropdowns(context)
else:
data = Data.products[props.ifc_definition_id]
name = data["type"]
@@ -40,6 +40,7 @@ class BIM_PT_class(Panel):
name += "[{}]".format(data["PredefinedType"])
row = self.layout.row(align=True)
row.label(text=name)
row.operator("bim.select_ifc_class", text="", icon="RESTRICT_SELECT_OFF").ifc_class = data["type"]
row.operator("bim.copy_class", icon="DUPLICATE", text="")
row.operator("bim.unlink_object", icon="UNLINKED", text="")
if IfcStore.get_file().by_id(props.ifc_definition_id).is_a("IfcRoot"):
@@ -49,15 +50,15 @@ class BIM_PT_class(Panel):
else:
row.operator("bim.unassign_class", icon="X", text="").obj = context.active_object.name
else:
self.draw_class_dropdowns()
self.draw_class_dropdowns(context)
row = self.layout.row(align=True)
op = row.operator("bim.assign_class")
op.ifc_class = bpy.context.scene.BIMRootProperties.ifc_class
op.predefined_type = bpy.context.scene.BIMRootProperties.ifc_predefined_type
op.userdefined_type = bpy.context.scene.BIMRootProperties.ifc_userdefined_type
op.ifc_class = context.scene.BIMRootProperties.ifc_class
op.predefined_type = context.scene.BIMRootProperties.ifc_predefined_type
op.userdefined_type = context.scene.BIMRootProperties.ifc_userdefined_type
def draw_class_dropdowns(self):
props = bpy.context.scene.BIMRootProperties
def draw_class_dropdowns(self, context):
props = context.scene.BIMRootProperties
row = self.layout.row()
row.prop(props, "ifc_product")
row = self.layout.row()
@@ -69,4 +70,4 @@ class BIM_PT_class(Panel):
row = self.layout.row()
row.prop(props, "ifc_userdefined_type")
row = self.layout.row()
row.prop(bpy.context.scene.BIMProperties, "contexts")
row.prop(context.scene.BIMProperties, "contexts")
@@ -22,23 +22,25 @@ colour_list = [
]
def does_keyword_exist(pattern, string):
def does_keyword_exist(pattern, string, context):
string = str(string)
props = context.scene.BIMSearchProperties
if (
bpy.context.scene.BIMSearchProperties.should_use_regex
and bpy.context.scene.BIMSearchProperties.should_ignorecase
props.should_use_regex
and props.should_ignorecase
and re.search(pattern, string, flags=re.IGNORECASE)
):
return True
elif bpy.context.scene.BIMSearchProperties.should_use_regex and re.search(pattern, string):
elif props.should_use_regex and re.search(pattern, string):
return True
elif bpy.context.scene.BIMSearchProperties.should_ignorecase and string.lower() == pattern.lower():
elif props.should_ignorecase and string.lower() == pattern.lower():
return True
elif string == pattern:
return True
class SelectGlobalId(bpy.types.Operator):
"""Click to select the objects that match with the given Global ID"""
bl_idname = "bim.select_global_id"
bl_label = "Select GlobalId"
bl_options = {"REGISTER", "UNDO"}
@@ -59,23 +61,25 @@ class SelectGlobalId(bpy.types.Operator):
class SelectIfcClass(bpy.types.Operator):
"""Click to select all objects that match with the given IFC class"""
bl_idname = "bim.select_ifc_class"
bl_label = "Select IFC Class"
bl_options = {"REGISTER", "UNDO"}
ifc_class: bpy.props.StringProperty()
def execute(self, context):
self.file = IfcStore.get_file()
props = context.scene.BIMSearchProperties
for obj in context.visible_objects:
if not obj.BIMObjectProperties.ifc_definition_id:
continue
element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
if does_keyword_exist(props.ifc_class, element.is_a()):
if does_keyword_exist(self.ifc_class, element.is_a(), context):
obj.select_set(True)
return {"FINISHED"}
class SelectAttribute(bpy.types.Operator):
"""Click to select all objects that match with the given Attribute Name and Value"""
bl_idname = "bim.select_attribute"
bl_label = "Select Attribute"
bl_options = {"REGISTER", "UNDO"}
@@ -94,12 +98,13 @@ class SelectAttribute(bpy.types.Operator):
value = next((v for k, v in data.items() if k.lower() == attribute_name.lower()), None)
else:
value = getattr(element, attribute_name, None)
if does_keyword_exist(pattern, value):
if does_keyword_exist(pattern, value, context):
obj.select_set(True)
return {"FINISHED"}
class SelectPset(bpy.types.Operator):
"""Click to select all objects that match with the given Pset Name, Properties Name and Value"""
bl_idname = "bim.select_pset"
bl_label = "Select Pset"
bl_options = {"REGISTER", "UNDO"}
@@ -126,19 +131,20 @@ class SelectPset(bpy.types.Operator):
else:
props = props or psets.get(search_pset_name, {})
value = props.get(search_prop_name, None)
if does_keyword_exist(pattern, value):
if does_keyword_exist(pattern, value, context):
obj.select_set(True)
return {"FINISHED"}
class ColourByAttribute(bpy.types.Operator):
"""Click to colour different objects according to given Attribute Name"""
bl_idname = "bim.colour_by_attribute"
bl_label = "Colour by Attribute"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
IfcStore.begin_transaction(self)
self.store_state()
self.store_state(context)
result = self._execute(context)
IfcStore.add_transaction_operation(self)
IfcStore.end_transaction(self)
@@ -161,13 +167,13 @@ class ColourByAttribute(bpy.types.Operator):
if value not in values:
values[value] = next(colours)
obj.color = values[value]
areas = [a for a in bpy.context.screen.areas if a.type == "VIEW_3D"]
areas = [a for a in context.screen.areas if a.type == "VIEW_3D"]
if areas:
areas[0].spaces[0].shading.color_type = "OBJECT"
return {"FINISHED"}
def store_state(self):
areas = [a for a in bpy.context.screen.areas if a.type == "VIEW_3D"]
def store_state(self, context):
areas = [a for a in context.screen.areas if a.type == "VIEW_3D"]
if areas:
self.transaction_data = {"area": areas[0], "color_type": areas[0].spaces[0].shading.color_type}
@@ -181,13 +187,14 @@ class ColourByAttribute(bpy.types.Operator):
class ColourByPset(bpy.types.Operator):
"""Click to colour different objects according to given Prop Name"""
bl_idname = "bim.colour_by_pset"
bl_label = "Colour by Pset"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
IfcStore.begin_transaction(self)
self.store_state()
self.store_state(context)
result = self._execute(context)
IfcStore.add_transaction_operation(self)
IfcStore.end_transaction(self)
@@ -218,13 +225,13 @@ class ColourByPset(bpy.types.Operator):
if value not in values:
values[value] = next(colours)
obj.color = values[value]
areas = [a for a in bpy.context.screen.areas if a.type == "VIEW_3D"]
areas = [a for a in context.screen.areas if a.type == "VIEW_3D"]
if areas:
areas[0].spaces[0].shading.color_type = "OBJECT"
return {"FINISHED"}
def store_state(self):
areas = [a for a in bpy.context.screen.areas if a.type == "VIEW_3D"]
def store_state(self, context):
areas = [a for a in context.screen.areas if a.type == "VIEW_3D"]
if areas:
self.transaction_data = {"area": areas[0], "color_type": areas[0].spaces[0].shading.color_type}
@@ -238,13 +245,14 @@ class ColourByPset(bpy.types.Operator):
class ColourByClass(bpy.types.Operator):
"""Click to colour different objects according to their IFC Classes"""
bl_idname = "bim.colour_by_class"
bl_label = "Colour by Class"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
IfcStore.begin_transaction(self)
self.store_state()
self.store_state(context)
result = self._execute(context)
IfcStore.add_transaction_operation(self)
IfcStore.end_transaction(self)
@@ -254,7 +262,7 @@ class ColourByClass(bpy.types.Operator):
self.file = IfcStore.get_file()
colours = cycle(colour_list)
ifc_classes = {}
for obj in bpy.context.visible_objects:
for obj in context.visible_objects:
if not obj.BIMObjectProperties.ifc_definition_id:
continue
element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
@@ -262,13 +270,13 @@ class ColourByClass(bpy.types.Operator):
if ifc_class not in ifc_classes:
ifc_classes[ifc_class] = next(colours)
obj.color = ifc_classes[ifc_class]
areas = [a for a in bpy.context.screen.areas if a.type == "VIEW_3D"]
areas = [a for a in context.screen.areas if a.type == "VIEW_3D"]
if areas:
areas[0].spaces[0].shading.color_type = "OBJECT"
return {"FINISHED"}
def store_state(self):
areas = [a for a in bpy.context.screen.areas if a.type == "VIEW_3D"]
def store_state(self, context):
areas = [a for a in context.screen.areas if a.type == "VIEW_3D"]
if areas:
self.transaction_data = {"area": areas[0], "color_type": areas[0].spaces[0].shading.color_type}
@@ -282,10 +290,11 @@ class ColourByClass(bpy.types.Operator):
class ResetObjectColours(bpy.types.Operator):
"""Reset the colour of selected objects"""
bl_idname = "bim.reset_object_colours"
bl_label = "Reset Colours"
def execute(self, context):
for obj in bpy.context.selected_objects:
for obj in context.selected_objects:
obj.color = (1, 1, 1, 1)
return {"FINISHED"}
@@ -28,11 +28,11 @@ class BIM_PT_search(Panel):
row = self.layout.row(align=True)
row.prop(props, "global_id", text="", icon="TRACKER")
row.operator("bim.select_global_id", text="", icon="VIEWZOOM")
row.operator("bim.select_global_id", text="", icon="VIEWZOOM").global_id = props.global_id
row = self.layout.row(align=True)
row.prop(props, "ifc_class", text="", icon="OBJECT_DATA")
row.operator("bim.select_ifc_class", text="", icon="VIEWZOOM")
row.operator("bim.select_ifc_class", text="", icon="VIEWZOOM").ifc_class = props.ifc_class
row.operator("bim.colour_by_class", text="", icon="BRUSH_DATA")
row = self.layout.row(align=True)
@@ -753,7 +753,7 @@ class AssignProduct(bpy.types.Operator):
def _execute(self, context):
relating_products = (
[bpy.data.objects.get(self.relating_product)] if self.relating_product else bpy.context.selected_objects
[bpy.data.objects.get(self.relating_product)] if self.relating_product else context.selected_objects
)
for relating_product in relating_products:
self.file = IfcStore.get_file()
@@ -779,7 +779,7 @@ class UnassignProduct(bpy.types.Operator):
def _execute(self, context):
relating_products = (
[bpy.data.objects.get(self.relating_product)] if self.relating_product else bpy.context.selected_objects
[bpy.data.objects.get(self.relating_product)] if self.relating_product else context.selected_objects
)
for relating_product in relating_products:
self.file = IfcStore.get_file()
@@ -805,7 +805,7 @@ class AssignProcess(bpy.types.Operator):
def _execute(self, context):
related_objects = (
[bpy.data.objects.get(self.related_object)] if self.related_object else bpy.context.selected_objects
[bpy.data.objects.get(self.related_object)] if self.related_object else context.selected_objects
)
for related_object in related_objects:
self.file = IfcStore.get_file()
@@ -831,7 +831,7 @@ class UnassignProcess(bpy.types.Operator):
def _execute(self, context):
related_objects = (
[bpy.data.objects.get(self.related_object)] if self.related_object else bpy.context.selected_objects
[bpy.data.objects.get(self.related_object)] if self.related_object else context.selected_objects
)
for related_object in related_objects:
self.file = IfcStore.get_file()
@@ -864,10 +864,10 @@ class GenerateGanttChart(bpy.types.Operator):
}
for task_id in Data.work_schedules[self.work_schedule]["RelatedObjects"]:
self.create_new_task_json(task_id)
with open(os.path.join(bpy.context.scene.BIMProperties.data_dir, "gantt", "index.html"), "w") as f:
with open(os.path.join(bpy.context.scene.BIMProperties.data_dir, "gantt", "index.mustache"), "r") as t:
with open(os.path.join(context.scene.BIMProperties.data_dir, "gantt", "index.html"), "w") as f:
with open(os.path.join(context.scene.BIMProperties.data_dir, "gantt", "index.mustache"), "r") as t:
f.write(pystache.render(t.read(), {"json_data": json.dumps(self.json)}))
webbrowser.open("file://" + os.path.join(bpy.context.scene.BIMProperties.data_dir, "gantt", "index.html"))
webbrowser.open("file://" + os.path.join(context.scene.BIMProperties.data_dir, "gantt", "index.html"))
return {"FINISHED"}
def create_new_task_json(self, task_id):
@@ -1568,7 +1568,7 @@ class SelectTaskRelatedProducts(bpy.types.Operator):
related_products = ifcopenshell.api.run(
"sequence.get_related_products", self.file, **{"related_object": self.file.by_id(self.task)}
)
for obj in bpy.context.visible_objects:
for obj in context.visible_objects:
obj.select_set(False)
if obj.BIMObjectProperties.ifc_definition_id in related_products:
obj.select_set(True)
@@ -1644,7 +1644,7 @@ class VisualiseWorkScheduleDateRange(bpy.types.Operator):
self.finish = parser.parse(self.props.visualisation_finish, dayfirst=True, fuzzy=True)
self.duration = self.finish - self.start
self.start_frame = 1
self.total_frames = self.calculate_total_frames()
self.total_frames = self.calculate_total_frames(context)
self.preprocess_tasks()
for obj in bpy.data.objects:
if not obj.BIMObjectProperties.ifc_definition_id:
@@ -1742,7 +1742,7 @@ class VisualiseWorkScheduleDateRange(bpy.types.Operator):
obj.keyframe_insert(data_path="color", frame=product_frame["COMPLETED"])
obj.keyframe_insert(data_path="hide_viewport", frame=product_frame["COMPLETED"])
def calculate_total_frames(self):
def calculate_total_frames(self, context):
if self.props.speed_types == "FRAME_SPEED":
return self.calculate_using_frames(
self.start,
@@ -1754,7 +1754,7 @@ class VisualiseWorkScheduleDateRange(bpy.types.Operator):
return self.calculate_using_duration(
self.start,
self.finish,
bpy.context.scene.render.fps,
context.scene.render.fps,
isodate.parse_duration(self.props.speed_animation_duration),
isodate.parse_duration(self.props.speed_real_duration),
)
@@ -1762,7 +1762,7 @@ class VisualiseWorkScheduleDateRange(bpy.types.Operator):
return self.calculate_using_multiplier(
self.start,
self.finish,
bpy.context.scene.render.fps,
context.scene.render.fps,
self.props.speed_multiplier,
)
@@ -17,7 +17,8 @@ class BIM_PT_work_plans(Panel):
@classmethod
def poll(cls, context):
return IfcStore.get_file()
file = IfcStore.get_file()
return file and hasattr(file, "schema") and file.schema != "IFC2X3"
def draw(self, context):
if not Data.is_loaded:
@@ -88,7 +89,8 @@ class BIM_PT_work_schedules(Panel):
@classmethod
def poll(cls, context):
return IfcStore.get_file()
file = IfcStore.get_file()
return file and hasattr(file, "schema") and file.schema != "IFC2X3"
def draw(self, context):
self.props = context.scene.BIMWorkScheduleProperties
@@ -429,7 +431,8 @@ class BIM_PT_work_calendars(Panel):
@classmethod
def poll(cls, context):
return IfcStore.get_file()
file = IfcStore.get_file()
return file and hasattr(file, "schema") and file.schema != "IFC2X3"
def draw(self, context):
if not Data.is_loaded:
@@ -17,9 +17,12 @@ class AssignContainer(bpy.types.Operator):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
# The active object pointer is lost when the object is unlinked from all collections in the view layer
# We need to store it first, then restore it at the end
active_object = context.active_object
self.file = IfcStore.get_file()
related_elements = (
[bpy.data.objects.get(self.related_element)] if self.related_element else bpy.context.selected_objects
[bpy.data.objects.get(self.related_element)] if self.related_element else context.selected_objects
)
sprops = context.scene.BIMSpatialProperties
relating_structure = (
@@ -49,7 +52,7 @@ class AssignContainer(bpy.types.Operator):
relating_collection = bpy.data.collections.get(relating_structure_obj.name)
if aggregate_collection:
self.remove_collection(bpy.context.scene.collection, aggregate_collection)
self.remove_collection(context.scene.collection, aggregate_collection)
for collection in bpy.data.collections:
self.remove_collection(collection, aggregate_collection)
relating_collection.children.link(aggregate_collection)
@@ -57,6 +60,8 @@ class AssignContainer(bpy.types.Operator):
for collection in related_element.users_collection:
collection.objects.unlink(related_element)
relating_collection.objects.link(related_element)
# Restore the active object :
context.view_layer.objects.active = active_object
return {"FINISHED"}
def remove_collection(self, parent, child):
@@ -72,7 +77,7 @@ class EnableEditingContainer(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
bpy.context.active_object.BIMObjectSpatialProperties.is_editing = True
context.active_object.BIMObjectSpatialProperties.is_editing = True
getSpatialContainers(self, context)
return {"FINISHED"}
@@ -95,7 +100,7 @@ class DisableEditingContainer(bpy.types.Operator):
obj: bpy.props.StringProperty()
def execute(self, context):
obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
obj.BIMObjectSpatialProperties.is_editing = False
return {"FINISHED"}
@@ -110,7 +115,7 @@ class RemoveContainer(bpy.types.Operator):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
oprops = obj.BIMObjectProperties
self.file = IfcStore.get_file()
ifcopenshell.api.run(
@@ -120,14 +125,14 @@ class RemoveContainer(bpy.types.Operator):
aggregate_collection = bpy.data.collections.get(obj.name)
if aggregate_collection:
self.remove_collection(bpy.context.scene.collection, aggregate_collection)
self.remove_collection(context.scene.collection, aggregate_collection)
for collection in bpy.data.collections:
self.remove_collection(collection, spatial_collection)
bpy.context.scene.collection.children.link(aggregate_collection)
context.scene.collection.children.link(aggregate_collection)
else:
for collection in obj.users_collection:
collection.objects.unlink(obj)
bpy.context.scene.collection.objects.link(obj)
context.scene.collection.objects.link(obj)
return {"FINISHED"}
def remove_collection(self, parent, child):
@@ -148,7 +153,7 @@ class CopyToContainer(bpy.types.Operator):
def _execute(self, context):
self.file = IfcStore.get_file()
objects = [bpy.data.objects.get(self.obj)] if self.obj else bpy.context.selected_objects
objects = [bpy.data.objects.get(self.obj)] if self.obj else context.selected_objects
sprops = context.scene.BIMSpatialProperties
container_ids = [c.ifc_definition_id for c in sprops.spatial_elements if c.is_selected]
for obj in objects:
@@ -20,7 +20,7 @@ class AddStructuralMemberConnection(bpy.types.Operator):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
obj = bpy.context.active_object
obj = context.active_object
oprops = obj.BIMObjectProperties
props = obj.BIMStructuralProperties
file = IfcStore.get_file()
@@ -46,7 +46,7 @@ class EnableEditingStructuralConnectionCondition(bpy.types.Operator):
connects_structural_member: bpy.props.IntProperty()
def execute(self, context):
obj = bpy.context.active_object
obj = context.active_object
oprops = obj.BIMObjectProperties
props = obj.BIMStructuralProperties
applied_condition_id = Data.connects_structural_members[self.connects_structural_member]["AppliedCondition"]
@@ -60,7 +60,7 @@ class DisableEditingStructuralConnectionCondition(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
obj = bpy.context.active_object
obj = context.active_object
props = obj.BIMStructuralProperties
props.active_connects_structural_member = 0
return {"FINISHED"}
@@ -423,7 +423,7 @@ class EnableEditingStructuralItemAxis(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
obj = bpy.context.active_object
obj = context.active_object
oprops = obj.BIMObjectProperties
props = obj.BIMStructuralProperties
@@ -463,7 +463,7 @@ class DisableEditingStructuralItemAxis(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
obj = bpy.context.active_object
obj = context.active_object
props = obj.BIMStructuralProperties
props.is_editing_axis = False
if props.axis_empty:
@@ -479,7 +479,7 @@ class EditStructuralItemAxis(bpy.types.Operator):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
obj = bpy.context.active_object
obj = context.active_object
oprops = obj.BIMObjectProperties
props = obj.BIMStructuralProperties
relative_matrix = props.axis_empty.matrix_world @ obj.matrix_world.inverted()
@@ -501,7 +501,7 @@ class EnableEditingStructuralConnectionCS(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
obj = bpy.context.active_object
obj = context.active_object
oprops = obj.BIMObjectProperties
props = obj.BIMStructuralProperties
@@ -553,7 +553,7 @@ class DisableEditingStructuralConnectionCS(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
obj = bpy.context.active_object
obj = context.active_object
props = obj.BIMStructuralProperties
props.is_editing_connection_cs = False
if props.ccs_empty:
@@ -570,7 +570,7 @@ class EditStructuralConnectionCS(bpy.types.Operator):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
obj = bpy.context.active_object
obj = context.active_object
oprops = obj.BIMObjectProperties
props = obj.BIMStructuralProperties
relative_matrix = props.ccs_empty.matrix_world @ obj.matrix_world.inverted()
@@ -31,7 +31,7 @@ def getApplicableStructuralLoadTypes(self, context):
element_classes = set(
[
ifc_file.by_id(o.BIMObjectProperties.ifc_definition_id).is_a()
for o in bpy.context.selected_objects
for o in context.selected_objects
if o.BIMObjectProperties.ifc_definition_id
]
)
@@ -261,7 +261,8 @@ class BIM_PT_structural_analysis_models(Panel):
@classmethod
def poll(cls, context):
return IfcStore.get_file()
file = IfcStore.get_file()
return file and hasattr(file, "schema") and file.schema != "IFC2X3"
def draw(self, context):
if not Data.is_loaded:
@@ -345,7 +346,8 @@ class BIM_PT_structural_load_cases(Panel):
@classmethod
def poll(cls, context):
return IfcStore.get_file()
file = IfcStore.get_file()
return file and hasattr(file, "schema") and file.schema != "IFC2X3"
def draw(self, context):
self.props = context.scene.BIMStructuralProperties
@@ -449,7 +451,8 @@ class BIM_PT_structural_loads(Panel):
@classmethod
def poll(cls, context):
return IfcStore.get_file()
file = IfcStore.get_file()
return file and hasattr(file, "schema") and file.schema != "IFC2X3"
def draw(self, context):
if not Data.is_loaded:
@@ -516,7 +519,8 @@ class BIM_PT_boundary_conditions(Panel):
@classmethod
def poll(cls, context):
return IfcStore.get_file()
file = IfcStore.get_file()
return file and hasattr(file, "schema") and file.schema != "IFC2X3"
def draw(self, context):
if not Data.is_loaded:
@@ -32,7 +32,7 @@ class UpdateStyleColours(bpy.types.Operator):
def _execute(self, context):
self.file = IfcStore.get_file()
material = bpy.data.materials.get(self.material) if self.material else bpy.context.active_object.active_material
material = bpy.data.materials.get(self.material) if self.material else context.active_object.active_material
settings = get_colour_settings(material)
settings["style"] = self.file.by_id(material.BIMMaterialProperties.ifc_style_id)
ifcopenshell.api.run("style.edit_style_colours", self.file, **settings)
@@ -50,7 +50,7 @@ class RemoveStyle(bpy.types.Operator):
def _execute(self, context):
self.file = IfcStore.get_file()
material = bpy.data.materials.get(self.material) if self.material else bpy.context.active_object.active_material
material = bpy.data.materials.get(self.material) if self.material else context.active_object.active_material
ifcopenshell.api.run(
"style.remove_style", self.file, style=self.file.by_id(material.BIMMaterialProperties.ifc_style_id)
)
@@ -69,12 +69,12 @@ class AddStyle(bpy.types.Operator):
def _execute(self, context):
self.file = IfcStore.get_file()
material = bpy.data.materials.get(self.material) if self.material else bpy.context.active_object.active_material
material = bpy.data.materials.get(self.material) if self.material else context.active_object.active_material
settings = get_colour_settings(material)
settings["name"] = material.name
settings["external_definition"] = None # TODO: Implement. See #1222
style = ifcopenshell.api.run("style.add_style", self.file, **settings)
material.BIMMaterialProperties.ifc_style_id = style.id()
IfcStore.link_element(style, material)
if material.BIMObjectProperties.ifc_definition_id:
context = ifcopenshell.util.representation.get_context(self.file, "Model", "Body", "MODEL_VIEW")
if context:
@@ -108,7 +108,7 @@ class EnableEditingStyle(bpy.types.Operator):
material: bpy.props.StringProperty()
def execute(self, context):
material = bpy.data.materials.get(self.material) if self.material else bpy.context.active_object.active_material
material = bpy.data.materials.get(self.material) if self.material else context.active_object.active_material
props = material.BIMStyleProperties
while len(props.attributes) > 0:
props.attributes.remove(0)
@@ -126,7 +126,7 @@ class DisableEditingStyle(bpy.types.Operator):
material: bpy.props.StringProperty()
def execute(self, context):
material = bpy.data.materials.get(self.material) if self.material else bpy.context.active_object.active_material
material = bpy.data.materials.get(self.material) if self.material else context.active_object.active_material
props = material.BIMStyleProperties
props.is_editing_attributes = False
return {"FINISHED"}
@@ -141,7 +141,7 @@ class EditStyle(bpy.types.Operator):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
material = bpy.context.active_object.active_material
material = context.active_object.active_material
props = material.BIMStyleProperties
attributes = blenderbim.bim.helper.export_attributes(props.attributes)
self.file = IfcStore.get_file()
@@ -20,14 +20,13 @@ class BIM_PT_style(Panel):
)
def draw(self, context):
props = context.active_object.active_material.BIMMaterialProperties
material = context.active_object.active_material
props = material.BIMMaterialProperties
row = self.layout.row(align=True)
if props.ifc_style_id:
row.operator("bim.update_style_colours", icon="GREASEPENCIL")
op = row.operator("bim.unlink_style", icon="UNLINKED", text="")
op.material = context.active_object.active_material.name
op = row.operator("bim.remove_style", icon="X", text="")
op.material = context.active_object.active_material.name
row.operator("bim.update_style_colours", icon="GREASEPENCIL").material = material.name
row.operator("bim.unlink_style", icon="UNLINKED", text="").material = material.name
row.operator("bim.remove_style", icon="X", text="").material = material.name
else:
row.operator("bim.add_style", icon="ADD")
@@ -0,0 +1,27 @@
import bpy
from . import ui, prop, operator
classes = (
operator.LoadSystems,
operator.DisableSystemEditingUI,
operator.AddSystem,
operator.EditSystem,
operator.RemoveSystem,
operator.AssignSystem,
operator.UnassignSystem,
operator.EnableEditingSystem,
operator.DisableEditingSystem,
operator.SelectSystemProducts,
prop.System,
prop.BIMSystemProperties,
ui.BIM_PT_systems,
ui.BIM_UL_systems,
)
def register():
bpy.types.Scene.BIMSystemProperties = bpy.props.PointerProperty(type=prop.BIMSystemProperties)
def unregister():
del bpy.types.Scene.BIMSystemProperties
@@ -0,0 +1,196 @@
import bpy
import ifcopenshell.util.attribute
import ifcopenshell.api
from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.system.data import Data
class LoadSystems(bpy.types.Operator):
bl_idname = "bim.load_systems"
bl_label = "Load Systems"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
props = context.scene.BIMSystemProperties
while len(props.systems) > 0:
props.systems.remove(0)
for ifc_definition_id, system in Data.systems.items():
new = props.systems.add()
new.ifc_definition_id = ifc_definition_id
new.name = system["Name"]
props.is_editing = True
bpy.ops.bim.disable_editing_system()
return {"FINISHED"}
class DisableSystemEditingUI(bpy.types.Operator):
bl_idname = "bim.disable_system_editing_ui"
bl_label = "Disable System Editing UI"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
context.scene.BIMSystemProperties.is_editing = False
return {"FINISHED"}
class AddSystem(bpy.types.Operator):
bl_idname = "bim.add_system"
bl_label = "Add System"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
result = ifcopenshell.api.run("system.add_system", IfcStore.get_file())
Data.load(IfcStore.get_file())
bpy.ops.bim.load_systems()
bpy.ops.bim.enable_editing_system(system=result.id())
return {"FINISHED"}
class EditSystem(bpy.types.Operator):
bl_idname = "bim.edit_system"
bl_label = "Edit System"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
props = context.scene.BIMSystemProperties
attributes = {}
for attribute in props.system_attributes:
if attribute.is_null:
attributes[attribute.name] = None
else:
attributes[attribute.name] = attribute.string_value
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"system.edit_system", self.file, **{"system": self.file.by_id(props.active_system_id), "attributes": attributes}
)
Data.load(IfcStore.get_file())
bpy.ops.bim.load_systems()
return {"FINISHED"}
class RemoveSystem(bpy.types.Operator):
bl_idname = "bim.remove_system"
bl_label = "Remove System"
bl_options = {"REGISTER", "UNDO"}
system: bpy.props.IntProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
props = context.scene.BIMSystemProperties
self.file = IfcStore.get_file()
ifcopenshell.api.run("system.remove_system", self.file, **{"system": self.file.by_id(self.system)})
Data.load(IfcStore.get_file())
bpy.ops.bim.load_systems()
return {"FINISHED"}
class EnableEditingSystem(bpy.types.Operator):
bl_idname = "bim.enable_editing_system"
bl_label = "Enable Editing System"
bl_options = {"REGISTER", "UNDO"}
system: bpy.props.IntProperty()
def execute(self, context):
props = context.scene.BIMSystemProperties
while len(props.system_attributes) > 0:
props.system_attributes.remove(0)
data = Data.systems[self.system]
for attribute in IfcStore.get_schema().declaration_by_name("IfcSystem").all_attributes():
data_type = ifcopenshell.util.attribute.get_primitive_type(attribute)
if data_type == "entity":
continue
new = props.system_attributes.add()
new.name = attribute.name()
new.is_null = data[attribute.name()] is None
new.is_optional = attribute.optional()
new.string_value = "" if new.is_null else data[attribute.name()]
props.active_system_id = self.system
return {"FINISHED"}
class DisableEditingSystem(bpy.types.Operator):
bl_idname = "bim.disable_editing_system"
bl_label = "Disable Editing System"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
context.scene.BIMSystemProperties.active_system_id = 0
return {"FINISHED"}
class AssignSystem(bpy.types.Operator):
bl_idname = "bim.assign_system"
bl_label = "Assign System"
bl_options = {"REGISTER", "UNDO"}
product: bpy.props.StringProperty()
system: bpy.props.IntProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
product = bpy.data.objects.get(self.product) if self.product else context.active_object
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"system.assign_system",
self.file,
**{
"product": self.file.by_id(product.BIMObjectProperties.ifc_definition_id),
"system": self.file.by_id(self.system),
}
)
Data.load(IfcStore.get_file())
return {"FINISHED"}
class UnassignSystem(bpy.types.Operator):
bl_idname = "bim.unassign_system"
bl_label = "Unassign System"
bl_options = {"REGISTER", "UNDO"}
product: bpy.props.StringProperty()
system: bpy.props.IntProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
product = bpy.data.objects.get(self.product) if self.product else context.active_object
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"system.unassign_system",
self.file,
**{
"product": self.file.by_id(product.BIMObjectProperties.ifc_definition_id),
"system": self.file.by_id(self.system),
}
)
Data.load(IfcStore.get_file())
return {"FINISHED"}
class SelectSystemProducts(bpy.types.Operator):
bl_idname = "bim.select_system_products"
bl_label = "Select System Products"
bl_options = {"REGISTER", "UNDO"}
system: bpy.props.IntProperty()
def execute(self, context):
self.file = IfcStore.get_file()
for obj in context.visible_objects:
obj.select_set(False)
if not obj.BIMObjectProperties.ifc_definition_id:
continue
product_systems = Data.products.get(obj.BIMObjectProperties.ifc_definition_id, [])
if self.system in product_systems:
obj.select_set(True)
return {"FINISHED"}
@@ -0,0 +1,26 @@
import bpy
from blenderbim.bim.prop import StrProperty, Attribute
from bpy.types import PropertyGroup
from bpy.props import (
PointerProperty,
StringProperty,
EnumProperty,
BoolProperty,
IntProperty,
FloatProperty,
FloatVectorProperty,
CollectionProperty,
)
class System(PropertyGroup):
name: StringProperty(name="Name")
ifc_definition_id: IntProperty(name="IFC Definition ID")
class BIMSystemProperties(PropertyGroup):
system_attributes: CollectionProperty(name="System Attributes", type=Attribute)
is_editing: BoolProperty(name="Is Editing", default=False)
systems: CollectionProperty(name="Systems", type=System)
active_system_index: IntProperty(name="Active System Index")
active_system_id: IntProperty(name="Active System Id")
@@ -0,0 +1,85 @@
from bpy.types import Panel, UIList
from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.system.data import Data
class BIM_PT_systems(Panel):
bl_label = "IFC Systems"
bl_idname = "BIM_PT_systems"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
@classmethod
def poll(cls, context):
return IfcStore.get_file()
def draw(self, context):
if not Data.is_loaded:
Data.load(IfcStore.get_file())
self.props = context.scene.BIMSystemProperties
row = self.layout.row(align=True)
row.label(text="{} Systems Found".format(len(Data.systems)), icon="OUTLINER")
if self.props.is_editing:
row.operator("bim.add_system", text="", icon="ADD")
row.operator("bim.disable_system_editing_ui", text="", icon="CANCEL")
else:
row.operator("bim.load_systems", text="", icon="GREASEPENCIL")
if self.props.is_editing:
self.layout.template_list(
"BIM_UL_systems",
"",
self.props,
"systems",
self.props,
"active_system_index",
)
if self.props.active_system_id:
self.draw_editable_ui(context)
def draw_editable_ui(self, context):
for attribute in self.props.system_attributes:
row = self.layout.row(align=True)
row.prop(attribute, "string_value", text=attribute.name)
if attribute.is_optional:
row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
class BIM_UL_systems(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
if item:
row = layout.row(align=True)
row.label(text=item.name)
if context.active_object:
oprops = context.active_object.BIMObjectProperties
if (
oprops.ifc_definition_id in Data.products
and item.ifc_definition_id in Data.products[oprops.ifc_definition_id]
):
op = row.operator("bim.unassign_system", text="", icon="KEYFRAME_HLT", emboss=False)
op.system = item.ifc_definition_id
else:
op = row.operator("bim.assign_system", text="", icon="KEYFRAME", emboss=False)
op.system = item.ifc_definition_id
if context.scene.BIMSystemProperties.active_system_id == item.ifc_definition_id:
op = row.operator("bim.select_system_products", text="", icon="RESTRICT_SELECT_OFF")
op.system = item.ifc_definition_id
row.operator("bim.edit_system", text="", icon="CHECKMARK")
row.operator("bim.disable_editing_system", text="", icon="CANCEL")
elif context.scene.BIMSystemProperties.active_system_id:
op = row.operator("bim.select_system_products", text="", icon="RESTRICT_SELECT_OFF")
op.system = item.ifc_definition_id
row.operator("bim.remove_system", text="", icon="X").system = item.ifc_definition_id
else:
op = row.operator("bim.select_system_products", text="", icon="RESTRICT_SELECT_OFF")
op.system = item.ifc_definition_id
op = row.operator("bim.enable_editing_system", text="", icon="GREASEPENCIL")
op.system = item.ifc_definition_id
row.operator("bim.remove_system", text="", icon="X").system = item.ifc_definition_id
@@ -7,6 +7,7 @@ classes = (
operator.EnableEditingType,
operator.DisableEditingType,
operator.SelectSimilarType,
operator.SelectTypeObjects,
prop.BIMTypeProperties,
prop.BIMTypeObjectProperties,
ui.BIM_PT_type,
@@ -22,7 +22,9 @@ class AssignType(bpy.types.Operator):
self.file = IfcStore.get_file()
relating_type = self.relating_type or int(context.active_object.BIMTypeProperties.relating_type)
related_objects = (
[bpy.data.objects.get(self.related_object)] if self.related_object else bpy.context.selected_objects
[bpy.data.objects.get(self.related_object)]
if self.related_object
else context.selected_objects or [context.active_object]
)
for related_object in related_objects:
oprops = related_object.BIMObjectProperties
@@ -39,15 +41,19 @@ class AssignType(bpy.types.Operator):
MaterialData.load(IfcStore.get_file(), oprops.ifc_definition_id)
representation_ids = GeometryData.products[oprops.ifc_definition_id]
if not representation_ids:
pass # TODO: clear geometry? Make void? Make none type?
pass # TODO: clear geometry? Make void? Make none type?
has_switched = False
for representation_id in representation_ids:
representation = GeometryData.representations[representation_id]
if representation["ContextOfItems"]["ContextIdentifier"] == "Body":
bpy.ops.bim.switch_representation(obj=related_object.name, ifc_definition_id=representation_id)
bpy.ops.bim.switch_representation(
obj=related_object.name, ifc_definition_id=representation_id, should_switch_all_meshes=False
)
has_switched = True
if not has_switched and representation_ids:
bpy.ops.bim.switch_representation(obj=related_object.name, ifc_definition_id=representation_id)
bpy.ops.bim.switch_representation(
obj=related_object.name, ifc_definition_id=representation_id, should_switch_all_meshes=False
)
bpy.ops.bim.disable_editing_type(obj=related_object.name)
MaterialData.load(self.file)
@@ -66,7 +72,7 @@ class UnassignType(bpy.types.Operator):
def _execute(self, context):
self.file = IfcStore.get_file()
related_objects = (
[bpy.data.objects.get(self.related_object)] if self.related_object else bpy.context.selected_objects
[bpy.data.objects.get(self.related_object)] if self.related_object else context.selected_objects
)
for related_object in related_objects:
oprops = related_object.BIMObjectProperties
@@ -87,7 +93,7 @@ class EnableEditingType(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
bpy.context.active_object.BIMTypeProperties.is_editing_type = True
context.active_object.BIMTypeProperties.is_editing_type = True
return {"FINISHED"}
@@ -98,7 +104,7 @@ class DisableEditingType(bpy.types.Operator):
obj: bpy.props.StringProperty()
def execute(self, context):
obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
obj.BIMTypeProperties.is_editing_type = False
return {"FINISHED"}
@@ -111,7 +117,7 @@ class SelectSimilarType(bpy.types.Operator):
def execute(self, context):
self.file = IfcStore.get_file()
related_object = bpy.data.objects.get(self.related_object) if self.related_object else bpy.context.active_object
related_object = bpy.data.objects.get(self.related_object) if self.related_object else context.active_object
oprops = related_object.BIMObjectProperties
product = self.file.by_id(oprops.ifc_definition_id)
declaration = IfcStore.get_schema().declaration_by_name(product.is_a())
@@ -123,7 +129,24 @@ class SelectSimilarType(bpy.types.Operator):
related_objects = ifcopenshell.api.run(
"type.get_related_objects", self.file, **{"related_object": self.file.by_id(oprops.ifc_definition_id)}
)
for obj in bpy.context.visible_objects:
for obj in context.visible_objects:
if obj.BIMObjectProperties.ifc_definition_id in related_objects:
obj.select_set(True)
return {"FINISHED"}
class SelectTypeObjects(bpy.types.Operator):
bl_idname = "bim.select_type_objects"
bl_label = "Select Type Objects"
bl_options = {"REGISTER", "UNDO"}
relating_type: bpy.props.StringProperty()
def execute(self, context):
self.file = IfcStore.get_file()
relating_type = bpy.data.objects.get(self.relating_type) if self.relating_type else context.active_object
oprops = relating_type.BIMObjectProperties
related_objects = Data.types[oprops.ifc_definition_id]
for obj in context.visible_objects:
if obj.BIMObjectProperties.ifc_definition_id in related_objects:
obj.select_set(True)
return {"FINISHED"}
@@ -17,20 +17,32 @@ class BIM_PT_type(Panel):
return False
if not IfcStore.get_element(props.ifc_definition_id):
return False
if props.ifc_definition_id not in Data.products:
if props.ifc_definition_id not in Data.products and props.ifc_definition_id not in Data.types:
Data.load(IfcStore.get_file(), props.ifc_definition_id)
if not Data.products[props.ifc_definition_id]:
if props.ifc_definition_id not in Data.products and props.ifc_definition_id not in Data.types:
return False
if not Data.products.get(props.ifc_definition_id, None) and not Data.types.get(props.ifc_definition_id, None):
return False
return True
def draw(self, context):
oprops = context.active_object.BIMObjectProperties
if oprops.ifc_definition_id in Data.products:
self.draw_product_ui(context)
else:
self.draw_type_ui(context)
def draw_type_ui(self, context):
props = context.active_object.BIMTypeProperties
oprops = context.active_object.BIMObjectProperties
row = self.layout.row(align=True)
row.label(text=f"{len(Data.types[oprops.ifc_definition_id])} Typed Objects")
row.operator("bim.select_type_objects", icon="RESTRICT_SELECT_OFF", text="")
def draw_product_ui(self, context):
props = context.active_object.BIMTypeProperties
oprops = context.active_object.BIMObjectProperties
if not oprops.ifc_definition_id:
return
if oprops.ifc_definition_id not in Data.products:
Data.load(IfcStore.get_file(), oprops.ifc_definition_id)
if props.is_editing_type:
row = self.layout.row(align=True)
@@ -13,26 +13,27 @@ class AssignUnit(bpy.types.Operator):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
ifcopenshell.api.run("unit.assign_unit", IfcStore.get_file(), **self.get_units())
ifcopenshell.api.run("unit.assign_unit", IfcStore.get_file(), **self.get_units(context))
Data.load(IfcStore.get_file())
return {"FINISHED"}
def get_units(self):
def get_units(self, context):
scene = context.scene
units = {
"length": {
"ifc": None,
"is_metric": bpy.context.scene.unit_settings.system != "IMPERIAL",
"raw": bpy.context.scene.unit_settings.length_unit,
"is_metric": scene.unit_settings.system != "IMPERIAL",
"raw": scene.unit_settings.length_unit,
},
"area": {
"ifc": None,
"is_metric": bpy.context.scene.unit_settings.system != "IMPERIAL",
"raw": bpy.context.scene.unit_settings.length_unit,
"is_metric": scene.unit_settings.system != "IMPERIAL",
"raw": scene.unit_settings.length_unit,
},
"volume": {
"ifc": None,
"is_metric": bpy.context.scene.unit_settings.system != "IMPERIAL",
"raw": bpy.context.scene.unit_settings.length_unit,
"is_metric": scene.unit_settings.system != "IMPERIAL",
"raw": scene.unit_settings.length_unit,
},
}
for data in units.values():
@@ -6,6 +6,7 @@ classes = (
operator.RemoveOpening,
operator.AddFilling,
operator.RemoveFilling,
operator.ToggleOpeningVisibility,
prop.VoidProperties,
ui.BIM_PT_voids,
)
@@ -16,7 +16,7 @@ class AddOpening(bpy.types.Operator):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
opening = bpy.data.objects.get(self.opening)
opening.display_type = "WIRE"
if not opening.BIMObjectProperties.ifc_definition_id:
@@ -69,7 +69,7 @@ class RemoveOpening(bpy.types.Operator):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
self.file = IfcStore.get_file()
for modifier in obj.modifiers:
if modifier.type != "BOOLEAN":
@@ -97,7 +97,7 @@ class AddFilling(bpy.types.Operator):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
opening = bpy.data.objects.get(self.opening) if self.opening else context.scene.VoidProperties.desired_opening
self.file = IfcStore.get_file()
element_id = obj.BIMObjectProperties.ifc_definition_id
@@ -123,10 +123,22 @@ class RemoveFilling(bpy.types.Operator):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"void.remove_filling", self.file, **{"element": self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)}
)
Data.load(IfcStore.get_file(), obj.BIMObjectProperties.ifc_definition_id)
return {"FINISHED"}
class ToggleOpeningVisibility(bpy.types.Operator):
bl_idname = "bim.toggle_opening_visibility"
bl_label = "Toggle Opening Visibility"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
for project in [c for c in context.view_layer.layer_collection.children if "IfcProject" in c.name]:
for collection in [c for c in project.children if "IfcOpeningElements" in c.name]:
collection.hide_viewport = not collection.hide_viewport
return {"FINISHED"}
+68 -62
View File
@@ -2,12 +2,10 @@ import os
import bpy
import time
import json
import tempfile
import logging
import webbrowser
import ifcopenshell
# Deleting the below drawing import breaks svgwrite's ElementTree appending because ... magic?
import blenderbim.bim.module.drawing
from . import export_ifc
from . import import_ifc
from . import schema
@@ -31,8 +29,8 @@ class ExportIFC(bpy.types.Operator):
if not IfcStore.get_file():
self.report({"ERROR"}, "No IFC project is available for export - create or import a project first.")
return {"FINISHED"}
if bpy.context.scene.BIMProperties.ifc_file:
self.filepath = bpy.context.scene.BIMProperties.ifc_file
if context.scene.BIMProperties.ifc_file:
self.filepath = context.scene.BIMProperties.ifc_file
return self.execute(context)
if not self.filepath:
self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ".ifc")
@@ -46,8 +44,11 @@ class ExportIFC(bpy.types.Operator):
def _execute(self, context):
start = time.time()
logger = logging.getLogger("ExportIFC")
path_log = os.path.join(bpy.context.scene.BIMProperties.data_dir, "process.log"),
if not os.access(bpy.context.scene.BIMProperties.data_dir, os.W_OK):
path_log = os.path.join(tempfile.mkdtemp(), "process.log")
logging.basicConfig(
filename=os.path.join(context.scene.BIMProperties.data_dir, "process.log"),
filename=path_log,
filemode="a",
level=logging.DEBUG,
)
@@ -68,11 +69,12 @@ class ExportIFC(bpy.types.Operator):
ifc_exporter.export()
settings.logger.info("Export finished in {:.2f} seconds".format(time.time() - start))
print("Export finished in {:.2f} seconds".format(time.time() - start))
if not bpy.context.scene.DocProperties.ifc_files:
new = bpy.context.scene.DocProperties.ifc_files.add()
scene = context.scene
if not scene.DocProperties.ifc_files:
new = scene.DocProperties.ifc_files.add()
new.name = output_file
if not bpy.context.scene.BIMProperties.ifc_file:
bpy.context.scene.BIMProperties.ifc_file = output_file
if not scene.BIMProperties.ifc_file:
scene.BIMProperties.ifc_file = output_file
if bpy.data.is_saved and bpy.data.is_dirty and bpy.data.filepath:
bpy.ops.wm.save_mainfile(filepath=bpy.data.filepath)
return {"FINISHED"}
@@ -108,8 +110,11 @@ class ImportIFC(bpy.types.Operator, ImportHelper):
def execute(self, context):
start = time.time()
logger = logging.getLogger("ImportIFC")
path_log = os.path.join(context.scene.BIMProperties.data_dir, "process.log"),
if not os.access(context.scene.BIMProperties.data_dir, os.W_OK):
path_log = os.path.join(tempfile.mkdtemp(), "process.log")
logging.basicConfig(
filename=os.path.join(bpy.context.scene.BIMProperties.data_dir, "process.log"),
filename=path_log,
filemode="a",
level=logging.DEBUG,
)
@@ -156,7 +161,8 @@ class SelectIfcFile(bpy.types.Operator):
filter_glob: bpy.props.StringProperty(default="*.ifc;*.ifczip;*.ifcxml", options={"HIDDEN"})
def execute(self, context):
bpy.context.scene.BIMProperties.ifc_file = self.filepath
if os.path.exists(self.filepath) and "ifc" in os.path.splitext(self.filepath)[1]:
context.scene.BIMProperties.ifc_file = self.filepath
return {"FINISHED"}
def invoke(self, context, event):
@@ -171,7 +177,7 @@ class SelectDataDir(bpy.types.Operator):
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def execute(self, context):
bpy.context.scene.BIMProperties.data_dir = self.filepath
context.scene.BIMProperties.data_dir = os.path.dirname(self.filepath)
return {"FINISHED"}
def invoke(self, context, event):
@@ -186,7 +192,7 @@ class SelectSchemaDir(bpy.types.Operator):
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def execute(self, context):
bpy.context.scene.BIMProperties.schema_dir = self.filepath
context.scene.BIMProperties.schema_dir = os.path.dirname(self.filepath)
return {"FINISHED"}
def invoke(self, context, event):
@@ -217,36 +223,36 @@ class AddSectionPlane(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
obj = self.create_section_obj()
obj = self.create_section_obj(context)
if not self.has_section_override_node():
self.create_section_compare_node()
self.create_section_override_node(obj)
self.create_section_override_node(obj, context)
else:
self.append_obj_to_section_override_node(obj)
self.add_default_material_if_none_exists()
self.add_default_material_if_none_exists(context)
self.override_materials()
return {"FINISHED"}
def create_section_obj(self):
def create_section_obj(self, context):
section = bpy.data.objects.new("Section", None)
section.empty_display_type = "SINGLE_ARROW"
section.empty_display_size = 5
section.show_in_front = True
if (
bpy.context.active_object
and bpy.context.active_object.select_get()
and isinstance(bpy.context.active_object.data, bpy.types.Camera)
context.active_object
and context.active_object.select_get()
and isinstance(context.active_object.data, bpy.types.Camera)
):
section.matrix_world = (
bpy.context.active_object.matrix_world @ Euler((radians(180.0), 0.0, 0.0), "XYZ").to_matrix().to_4x4()
context.active_object.matrix_world @ Euler((radians(180.0), 0.0, 0.0), "XYZ").to_matrix().to_4x4()
)
else:
section.rotation_euler = Euler((radians(180.0), 0.0, 0.0), "XYZ")
section.location = bpy.context.scene.cursor.location
section.location = context.scene.cursor.location
collection = bpy.data.collections.get("Sections")
if not collection:
collection = bpy.data.collections.new("Sections")
bpy.context.scene.collection.children.link(collection)
context.scene.collection.children.link(collection)
collection.objects.link(section)
return section
@@ -278,7 +284,7 @@ class AddSectionPlane(bpy.types.Operator):
group.links.new(add.outputs[0], compare.inputs[0])
group.links.new(compare.outputs[0], group_output.inputs[""])
def create_section_override_node(self, obj):
def create_section_override_node(self, obj, context):
group = bpy.data.node_groups.new("Section Override", type="ShaderNodeTree")
group_input = group.nodes.new(type="NodeGroupInput")
@@ -287,7 +293,7 @@ class AddSectionPlane(bpy.types.Operator):
backfacing = group.nodes.new(type="ShaderNodeNewGeometry")
backfacing_mix = group.nodes.new(type="ShaderNodeMixShader")
emission = group.nodes.new(type="ShaderNodeEmission")
emission.inputs[0].default_value = list(bpy.context.scene.BIMProperties.section_plane_colour) + [1]
emission.inputs[0].default_value = list(context.scene.BIMProperties.section_plane_colour) + [1]
group.links.new(backfacing.outputs["Backfacing"], backfacing_mix.inputs[0])
group.links.new(group_input.outputs[""], backfacing_mix.inputs[1])
@@ -332,16 +338,16 @@ class AddSectionPlane(bpy.types.Operator):
section_compare.name = "Last Section Compare"
def add_default_material_if_none_exists(self):
def add_default_material_if_none_exists(self, context):
material = bpy.data.materials.get("Section Override")
if not material:
material = bpy.data.materials.new("Section Override")
material.use_nodes = True
if bpy.context.scene.BIMProperties.should_section_selected_objects:
objects = list(bpy.context.selected_objects)
if context.scene.BIMProperties.should_section_selected_objects:
objects = list(context.selected_objects)
else:
objects = list(bpy.context.visible_objects)
objects = list(context.visible_objects)
for obj in objects:
aggregate = obj.instance_collection
@@ -385,7 +391,7 @@ class RemoveSectionPlane(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
name = bpy.context.active_object.name
name = context.active_object.name
section_override = bpy.data.node_groups.get("Section Override")
if not section_override:
return {"FINISHED"}
@@ -401,7 +407,7 @@ class RemoveSectionPlane(bpy.types.Operator):
else: # If it links to section_compare.inputs[0]
if section_compare.inputs[1].links[0].from_node.name == "Mock Section":
# Then it is the very last section. Purge everything.
self.purge_all_section_data()
self.purge_all_section_data(context)
return {"FINISHED"}
section_override.links.new(
section_compare.inputs[1].links[0].from_socket, section_compare.outputs[0].links[0].to_socket
@@ -414,10 +420,10 @@ class RemoveSectionPlane(bpy.types.Operator):
section_mix = section_override.nodes.get("Section Mix")
new_last_compare = section_mix.inputs[0].links[0].from_node
new_last_compare.name = "Last Section Compare"
bpy.ops.object.delete({"selected_objects": [bpy.context.active_object]})
bpy.ops.object.delete({"selected_objects": [context.active_object]})
return {"FINISHED"}
def purge_all_section_data(self):
def purge_all_section_data(self, context):
bpy.data.materials.remove(bpy.data.materials.get("Section Override"))
for material in bpy.data.materials:
if not material.node_tree:
@@ -431,7 +437,7 @@ class RemoveSectionPlane(bpy.types.Operator):
material.node_tree.nodes.remove(override)
bpy.data.node_groups.remove(bpy.data.node_groups.get("Section Override"))
bpy.data.node_groups.remove(bpy.data.node_groups.get("Section Compare"))
bpy.ops.object.delete({"selected_objects": [bpy.context.active_object]})
bpy.ops.object.delete({"selected_objects": [context.active_object]})
class ReloadIfcFile(bpy.types.Operator):
@@ -450,7 +456,7 @@ class AddIfcFile(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
bpy.context.scene.DocProperties.ifc_files.add()
context.scene.DocProperties.ifc_files.add()
return {"FINISHED"}
@@ -461,7 +467,7 @@ class RemoveIfcFile(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
bpy.context.scene.DocProperties.ifc_files.remove(self.index)
context.scene.DocProperties.ifc_files.remove(self.index)
return {"FINISHED"}
@@ -472,9 +478,9 @@ class SetOverrideColour(bpy.types.Operator):
def execute(self, context):
result = 0
for obj in bpy.context.selected_objects:
obj.color = bpy.context.scene.BIMProperties.override_colour
area = next(area for area in bpy.context.screen.areas if area.type == "VIEW_3D")
for obj in context.selected_objects:
obj.color = context.scene.BIMProperties.override_colour
area = next(area for area in context.screen.areas if area.type == "VIEW_3D")
area.spaces[0].shading.color_type = "OBJECT"
return {"FINISHED"}
@@ -500,7 +506,7 @@ class LinkIfc(bpy.types.Operator):
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def execute(self, context):
# bpy.context.active_object.active_material.BIMMaterialProperties.location = self.filepath
# context.active_object.active_material.BIMMaterialProperties.location = self.filepath
# coll_name = "MyCollection"
with bpy.data.libraries.load(self.filepath, link=True) as (data_from, data_to):
@@ -529,13 +535,13 @@ class SnapSpacesTogether(bpy.types.Operator):
def execute(self, context):
threshold = 0.5
processed_polygons = set()
for obj in bpy.context.selected_objects:
for obj in context.selected_objects:
if obj.type != "MESH":
continue
for polygon in obj.data.polygons:
center = obj.matrix_world @ polygon.center
distance = None
for obj2 in bpy.context.selected_objects:
for obj2 in context.selected_objects:
if obj2 == obj or obj.type != "MESH":
continue
result = obj2.ray_cast(obj2.matrix_world.inverted() @ center, polygon.normal, distance=threshold)
@@ -571,7 +577,7 @@ class SelectExternalMaterialDir(bpy.types.Operator):
def execute(self, context):
# TODO: this is dead code, awaiting reimplementation. See #1222.
bpy.context.active_object.active_material.BIMMaterialProperties.location = self.filepath
context.active_object.active_material.BIMMaterialProperties.location = self.filepath
return {"FINISHED"}
def invoke(self, context, event):
@@ -585,28 +591,28 @@ class FetchExternalMaterial(bpy.types.Operator):
def execute(self, context):
# TODO: this is dead code, awaiting reimplementation. See #1222.
location = bpy.context.active_object.active_material.BIMMaterialProperties.location
location = context.active_object.active_material.BIMMaterialProperties.location
if location[-6:] != ".mpass":
return {"FINISHED"}
if not os.path.isabs(location):
location = os.path.join(bpy.context.scene.BIMProperties.data_dir, location)
location = os.path.join(context.scene.BIMProperties.data_dir, location)
with open(location) as f:
self.material_pass = json.load(f)
if bpy.context.scene.render.engine == "BLENDER_EEVEE" and "eevee" in self.material_pass:
self.fetch_eevee_or_cycles("eevee")
elif bpy.context.scene.render.engine == "CYCLES" and "cycles" in self.material_pass:
self.fetch_eevee_or_cycles("cycles")
if context.scene.render.engine == "BLENDER_EEVEE" and "eevee" in self.material_pass:
self.fetch_eevee_or_cycles("eevee", context)
elif context.scene.render.engine == "CYCLES" and "cycles" in self.material_pass:
self.fetch_eevee_or_cycles("cycles", context)
return {"FINISHED"}
def fetch_eevee_or_cycles(self, name):
identification = bpy.context.active_object.active_material.BIMMaterialProperties.identification
def fetch_eevee_or_cycles(self, name, context):
identification = context.active_object.active_material.BIMMaterialProperties.identification
uri = self.material_pass[name]["uri"]
if not os.path.isabs(uri):
uri = os.path.join(bpy.context.scene.BIMProperties.data_dir, uri)
uri = os.path.join(context.scene.BIMProperties.data_dir, uri)
bpy.ops.wm.link(filename=identification, directory=os.path.join(uri, "Material"))
for material in bpy.data.materials:
if material.name == identification and material.library:
bpy.context.active_object.material_slots[0].material = material
context.active_object.material_slots[0].material = material
return
@@ -616,15 +622,15 @@ class FetchObjectPassport(bpy.types.Operator):
def execute(self, context):
# TODO: this is dead code, awaiting reimplementation. See #1222.
for reference in bpy.context.active_object.BIMObjectProperties.document_references:
reference = bpy.context.scene.BIMProperties.document_references[reference.name]
for reference in context.active_object.BIMObjectProperties.document_references:
reference = context.scene.BIMProperties.document_references[reference.name]
if reference.location[-6:] == ".blend":
self.fetch_blender(reference)
self.fetch_blender(reference, context)
return {"FINISHED"}
def fetch_blender(self, reference):
def fetch_blender(self, reference, context):
bpy.ops.wm.link(filename=reference.name, directory=os.path.join(reference.location, "Mesh"))
bpy.context.active_object.data = bpy.data.meshes[reference.name]
context.active_object.data = bpy.data.meshes[reference.name]
class CopyPropertyToSelection(bpy.types.Operator):
@@ -636,7 +642,7 @@ class CopyPropertyToSelection(bpy.types.Operator):
def execute(self, context):
# TODO: this is dead code, awaiting reimplementation. See #1222.
for obj in bpy.context.selected_objects:
for obj in context.selected_objects:
if "/" not in obj.name:
continue
pset = obj.BIMObjectProperties.psets.get(self.pset_name)
@@ -666,9 +672,9 @@ class CopyAttributeToSelection(bpy.types.Operator):
def execute(self, context):
# TODO: this is dead code, awaiting reimplementation. See #1222.
self.schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(bpy.context.scene.BIMProperties.export_schema)
self.schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(context.scene.BIMProperties.export_schema)
self.applicable_attributes_cache = {}
for obj in bpy.context.selected_objects:
for obj in context.selected_objects:
if "/" not in obj.name:
continue
attribute = obj.BIMObjectProperties.attributes.get(self.attribute_name)
+4
View File
@@ -77,3 +77,7 @@ class IfcSchema:
ifc = IfcSchema()
def reload():
global ifc
ifc = IfcSchema()
+3 -3
View File
@@ -15,7 +15,7 @@ class BIM_PT_section_plane(Panel):
def draw(self, context):
layout = self.layout
layout.use_property_split = True
props = bpy.context.scene.BIMProperties
props = context.scene.BIMProperties
row = layout.row()
row.prop(props, "should_section_selected_objects")
@@ -91,7 +91,7 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
def ifc_units(self, context):
scene = context.scene
props = context.scene.BIMProperties
props = scene.BIMProperties
layout = self.layout
layout.use_property_decorate = False
layout.use_property_split = True
@@ -100,7 +100,7 @@ def ifc_units(self, context):
row = layout.row()
row.prop(props, "volume_unit")
row = layout.row()
if bpy.context.scene.unit_settings.system == "IMPERIAL":
if scene.unit_settings.system == "IMPERIAL":
row.prop(props, "imperial_precision")
else:
row.prop(props, "metric_precision")
+86
View File
@@ -0,0 +1,86 @@
import ifcopenshell
import ifcopenshell.api
import ifcopenshell.util.element
class LibraryGenerator:
def generate(self):
self.file = ifcopenshell.api.run("project.create_file")
self.project = ifcopenshell.api.run(
"root.create_entity", self.file, ifc_class="IfcProjectLibrary", name="BlenderBIM Demo Library"
)
ifcopenshell.api.run("unit.assign_unit", self.file, length={"is_metric": True, "raw": "METERS"})
self.material = ifcopenshell.api.run("material.add_material", self.file, name="Unknown")
self.create_layer_type("IfcWallType", "DEMO50", 0.05)
self.create_layer_type("IfcWallType", "DEMO100", 0.1)
self.create_layer_type("IfcWallType", "DEMO200", 0.2)
self.create_layer_type("IfcWallType", "DEMO300", 0.3)
self.create_layer_type("IfcSlabType", "DEMO150", 0.2)
self.create_layer_type("IfcSlabType", "DEMO250", 0.3)
profile = self.file.create_entity("IfcRectangleProfileDef", ProfileType="AREA", XDim=0.5, YDim=0.6)
self.create_profile_type("IfcColumnType", "DEMO1", profile)
profile = self.file.create_entity(
"IfcCircleHollowProfileDef", ProfileType="AREA", Radius=0.25, WallThickness=0.005
)
self.create_profile_type("IfcColumnType", "DEMO2", profile)
profile = self.file.create_entity(
"IfcRectangleHollowProfileDef",
ProfileType="AREA",
XDim=0.075,
YDim=0.15,
WallThickness=0.005,
InnerFilletRadius=0.005,
OuterFilletRadius=0.005,
)
self.create_profile_type("IfcColumnType", "DEMO3", profile)
profile = self.file.create_entity(
"IfcIShapeProfileDef",
ProfileType="AREA",
OverallWidth=0.1,
OverallDepth=0.2,
WebThickness=0.005,
FlangeThickness=0.01,
FilletRadius=0.005,
)
self.create_profile_type("IfcBeamType", "DEMO1", profile)
profile = self.file.create_entity(
"IfcCShapeProfileDef",
ProfileType="AREA",
Depth=0.2,
Width=0.1,
WallThickness=0.0015,
Girth=0.03,
InternalFilletRadius=0.005,
)
self.create_profile_type("IfcBeamType", "DEMO2", profile)
self.file.write("blenderbim-demo-library.ifc")
def create_layer_type(self, ifc_class, name, thickness):
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class=ifc_class, name=name)
ifcopenshell.api.run("material.assign_material", self.file, product=element, type="IfcMaterialLayerSet")
layer_set = ifcopenshell.util.element.get_material(element)
layer = ifcopenshell.api.run("material.add_layer", self.file, layer_set=layer_set, material=self.material)
layer.LayerThickness = thickness
ifcopenshell.api.run("project.assign_declaration", self.file, definition=element, relating_context=self.project)
def create_profile_type(self, ifc_class, name, profile):
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class=ifc_class, name=name)
ifcopenshell.api.run("material.assign_material", self.file, product=element, type="IfcMaterialProfileSet")
profile_set = ifcopenshell.util.element.get_material(element)
material_profile = ifcopenshell.api.run(
"material.add_profile", self.file, profile_set=profile_set, material=self.material
)
ifcopenshell.api.run("material.assign_profile", self.file, material_profile=material_profile, profile=profile)
ifcopenshell.api.run("project.assign_declaration", self.file, definition=element, relating_context=self.project)
LibraryGenerator().generate()
@@ -20,4 +20,7 @@ use_step_matcher("parse")
from bimtester.features.steps.project_setup import de, en, fr, it, nl
use_step_matcher("parse")
from bimtester.features.steps.aggregation import en
from bimtester.features.steps.aggregation import en
use_step_matcher("parse")
from bimtester.features.steps.attributes_psets import en, de
@@ -0,0 +1,8 @@
from behave import step, use_step_matcher
use_step_matcher("parse")
@step("An alle {ifc_class} Bauteile ist im PSet {pset} das Attribut {aproperty} angehängt")
def step_impl(context, ifc_class, aproperty, pset):
context.execute_steps(f"all {ifc_class} elements have an {aproperty} property in the {pset} pset")
@@ -0,0 +1,91 @@
from behave import step, use_step_matcher
from bimtester.ifc import IfcStore
from bimtester.util import assert_elements
from bimtester.lang import _
@step("All {ifc_class} elements have an {aproperty} property in the {pset} pset")
def step_impl(context, ifc_class, aproperty, pset):
eleclass_has_property_in_pset(
context,
ifc_class,
aproperty,
pset
)
# ------------------------------------------------------------------------
# STEPS with Regular Expression Matcher ("re")
# ------------------------------------------------------------------------
use_step_matcher("re")
@step(r"All (?P<ifc_class>.*) elements have an? (?P<property_path>.*\..*) property")
def step_impl(context, ifc_class, property_path):
import re
pset, aproperty = property_path.split(".")
eleclass_has_property_in_pset(
context,
ifc_class,
aproperty,
pset
)
@step(r'All (?P<ifc_class>.*) elements have an? (?P<property_path>.*\..*) property value matching the pattern "(?P<pattern>.*)"'
)
def step_impl(context, ifc_class, property_path, pattern):
import re
from ifcopenshell.util.element import get_psets
pset_name, property_name = property_path.split(".")
elements = IfcStore.file.by_type(ifc_class)
for element in elements:
psets = get_psets(element)
if not pset_name in psets:
assert False
pset = psets[pset_name]
if not property_name in pset:
assert False
prop = pset[property_name]
# get_psets returns just strings
if not re.search(pattern, prop):
assert False
def eleclass_has_property_in_pset(
context, ifc_class, aproperty, pset
):
context.falseelems = []
context.falseguids = []
context.falseprops = {}
from ifcopenshell.util.element import get_psets
elements = IfcStore.file.by_type(ifc_class)
for elem in elements:
psets = get_psets(elem)
if not (pset in psets and aproperty in psets[pset]):
context.falseelems.append(str(elem))
context.falseguids.append(elem.GlobalId)
context.falseprops[elem.id()] = str(psets)
context.elemcount = len(elements)
context.falsecount = len(context.falseelems)
assert_elements(
ifc_class,
context.elemcount,
context.falsecount,
context.falseelems,
# TODO: Translate these messages into other languages
message_all_falseelems=_("All {elemcount} {ifc_class} elements are missing the property {parameter} in the pset."),
message_some_falseelems=_("The following {falsecount} of {elemcount} {ifc_class} elements are missing the property {parameter} in the pset: {falseelems}"),
message_no_elems=_("There are no {ifc_class} elements in the IFC file."),
parameter=aproperty
)
# the pset name is missing in the failing message, but it is in the step test name
+1
View File
@@ -2,6 +2,7 @@ import datetime
import json
import os
import pystache
import sys
from bimtester.lang import _
@@ -1,53 +0,0 @@
from behave import step
import attributes_psets_methods as apm
from utils import assert_elements
from utils import IfcFile
from utils import switch_locale
the_lang = "en"
@step("all {ifc_class} elements have an {aproperty} property in the {pset} pset")
def step_impl(context, ifc_class, aproperty, pset):
switch_locale(context.localedir, the_lang)
apm.eleclass_has_property_in_pset(
context,
ifc_class,
aproperty,
pset
)
# ------------------------------------------------------------------------
# STEPS with Regular Expression Matcher ("re")
# ------------------------------------------------------------------------
use_step_matcher("re")
@step("all (?P<ifc_class>.*) elements have an? (?P<property_path>.*\..*) property")
def step_impl(context, ifc_class, property_path):
pset_name, property_name = property_path.split(".")
elements = IfcFile.get().by_type(ifc_class)
for element in elements:
if not IfcFile.get_property(element, pset_name, property_name):
assert False
@step(
'all (?P<ifc_class>.*) elements have an? (?P<property_path>.*\..*) property value matching the pattern "(?P<pattern>.*)"'
)
def step_impl(context, ifc_class, property_path, pattern):
import re
pset_name, property_name = property_path.split(".")
elements = IfcFile.get().by_type(ifc_class)
for element in elements:
prop = IfcFile.get_property(element, pset_name, property_name)
if not prop:
assert False
# For now, we only check single values
if prop.is_a("IfcPropertySingleValue"):
if not (prop.NominalValue and re.search(pattern, prop.NominalValue.wrappedValue)):
assert False
@@ -1,18 +0,0 @@
from behave import step
import attributes_psets_methods as apm
from utils import switch_locale
the_lang = "de"
@step("An alle {ifc_class} Bauteile ist im PSet {pset} das Attribut {aproperty} angehängt")
def step_impl(context, ifc_class, aproperty, pset):
switch_locale(context.localedir, the_lang)
apm.eleclass_has_property_in_pset(
context,
ifc_class,
aproperty,
pset
)
@@ -1,21 +0,0 @@
from behave import step
import attributes_psets_methods as apm
from utils import switch_locale
the_lang = "fr"
"""
# TODO the next line needs translation
@step("All {ifc_class} elements have an {aproperty} property in the {pset} pset")
def step_impl(context, ifc_class, aproperty, pset):
switch_locale(context.localedir, the_lang)
apm.eleclass_has_property_in_pset(
context,
ifc_class,
aproperty,
pset
)
"""
@@ -1,36 +0,0 @@
import gettext # noqa
from utils import assert_elements
from utils import IfcFile
def eleclass_has_property_in_pset(
context, ifc_class, aproperty, pset
):
context.falseelems = []
context.falseguids = []
context.falseprops = {}
from ifcopenshell.util.element import get_psets
elements = IfcFile.get().by_type(ifc_class)
for elem in elements:
psets = get_psets(elem)
if not (pset in psets and aproperty in psets[pset]):
context.falseelems.append(str(elem))
context.falseguids.append(elem.GlobalId)
context.falseprops[elem.id()] = str(psets)
context.elemcount = len(elements)
context.falsecount = len(context.falseelems)
assert_elements(
ifc_class,
context.elemcount,
context.falsecount,
context.falseelems,
message_all_falseelems=_("All {elemcount} {ifc_class} elements are missing the property {parameter} in the pset."),
message_some_falseelems=_("The following {falsecount} of {elemcount} {ifc_class} elements are missing the property {parameter} in the pset: {falseelems}"),
message_no_elems=_("There are no {ifc_class} elements in the IFC file."),
parameter=aproperty
)
# the pset name is missing in the failing message, but it is in the step test name
+96
View File
@@ -0,0 +1,96 @@
import hppfcl
import numpy as np
from aabbtree import AABB
from aabbtree import AABBTree
class Collider:
def __init__(self):
self.groups = {}
def create_group(self, name):
self.groups[name] = {"tree": AABBTree(), "objects": {}}
def create_object(self, group_name, id, shape):
obj = hppfcl.CollisionObject(
self.create_bvh(shape.geometry), self.create_transform(shape.transformation.matrix.data)
)
aabb = obj.getAABB()
c = aabb.center()
x = aabb.width()
y = aabb.height()
z = aabb.depth()
aabb = AABB([(c[0] - x / 2, c[0] + x / 2), (c[1] - y / 2, c[1] + y / 2), (c[2] - z / 2, c[2] + z / 2)])
self.groups[group_name]["tree"].add(aabb, id)
self.groups[group_name]["objects"][id] = (aabb, obj)
def collide_internal(self, name):
print('starting internal collision')
return self.collide_narrowphase(self.collide_broadphase(name, name))
def collide_group(self, name1, name2):
print('starting group collision')
return self.collide_narrowphase(self.collide_broadphase(name1, name2))
def collide_broadphase(self, name1, name2):
print('Begin broad phase')
potential_collisions = []
checked_collisions = set()
i = 0
for id, obj_data in self.groups[name1]["objects"].items():
aabb, obj = obj_data
collision_stack = [self.groups[name2]["tree"]]
checked_collisions.add(id)
i += 1
while i % 1000 == 0:
print(i, '...')
while collision_stack:
node = collision_stack.pop()
if node.value == id or node.value in checked_collisions:
continue
if node.does_overlap(aabb):
if node.is_leaf:
potential_collisions.append(
{
"id1": id,
"obj1": obj,
"id2": node.value,
"obj2": self.groups[name2]["objects"][node.value][1],
}
)
else:
collision_stack.append(node.left)
collision_stack.append(node.right)
return potential_collisions
def collide_narrowphase(self, potential_collisions):
print('Begin narrow phase')
collisions = []
for data in potential_collisions:
result = hppfcl.CollisionResult()
hppfcl.collide(data["obj1"], data["obj2"], hppfcl.CollisionRequest(), result)
if result.isCollision():
collisions.append({"id1": data["id1"], "id2": data["id2"], "collision": result})
print({"id1": data["id1"], "id2": data["id2"], "collision": result})
return collisions
def create_transform(self, m):
mat = np.array([[m[0], m[3], m[6], m[9]], [m[1], m[4], m[7], m[10]], [m[2], m[5], m[8], m[11]], [0, 0, 0, 1]])
mat.transpose()
return hppfcl.Transform3f(mat[:3, :3], mat[:3, 3])
def create_bvh(self, mesh):
v = mesh.verts
f = mesh.faces
mesh_verts = np.array([[v[i], v[i + 1], v[i + 2]] for i in range(0, len(v), 3)])
mesh_faces = [(int(f[i]), int(f[i + 1]), int(f[i + 2])) for i in range(0, len(f), 3)]
bvh = hppfcl.BVHModelOBB()
bvh.beginModel(num_tris=len(mesh.faces), num_vertices=len(mesh_verts))
vertices = hppfcl.StdVec_Vec3f()
[vertices.append(v) for v in mesh_verts]
triangles = hppfcl.StdVec_Triangle()
[triangles.append(hppfcl.Triangle(f[0], f[1], f[2])) for f in mesh_faces]
bvh.addSubModel(vertices, triangles)
bvh.endModel()
return bvh
+302
View File
@@ -0,0 +1,302 @@
#!/usr/bin/env python3
import ifcopenshell
import ifcopenshell.geom
import ifcopenshell.util.selector
import multiprocessing
import numpy as np
import json
import sys
import argparse
import logging
from . import collider
class Clasher:
def __init__(self, settings):
self.settings = settings
self.geom_settings = ifcopenshell.geom.settings()
self.clash_sets = []
self.collider = collider.Collider()
self.selector = ifcopenshell.util.selector.Selector()
self.ifcs = {}
def clash(self):
existing_limit = sys.getrecursionlimit()
sys.setrecursionlimit(100000)
for clash_set in self.clash_sets:
self.process_clash_set(clash_set)
sys.setrecursionlimit(existing_limit)
def process_clash_set(self, clash_set):
print("proccessings", clash_set)
self.collider.create_group("a")
for source in clash_set["a"]:
self.add_collision_objects(
"a", self.load_ifc(source["file"]), source.get("mode", None), source.get("selector", None)
)
if "b" in clash_set:
self.collider.create_group("b")
for source in clash_set["b"]:
self.add_collision_objects(
"b", self.load_ifc(source["file"]), source.get("mode", None), source.get("selector", None)
)
results = self.collider.collide_group("a", "b")
else:
results = self.collider.collide_internal("a")
for result in results:
print("*" * 10)
print("Is Collision:", result["collision"].isCollision())
print(result["id1"], result["id2"])
print("Number of contacts:", result["collision"].numContacts())
for contact in result["collision"].getContacts():
print(contact)
def load_ifc(self, path):
ifc = self.ifcs.get(path, None)
if not ifc:
ifc = ifcopenshell.open(path)
self.ifcs[path] = ifc
return ifc
def add_collision_objects(self, name, ifc_file, mode=None, selector=None):
print('adding collision objects', name)
if not mode:
iterator = ifcopenshell.geom.iterator(
self.geom_settings,
ifc_file,
multiprocessing.cpu_count(),
exclude=(ifc_file.by_type("IfcSpatialStructureElement")),
)
elif mode == "e":
iterator = ifcopenshell.geom.iterator(
self.geom_settings,
ifc_file,
multiprocessing.cpu_count(),
exclude=selector.parse(ifc_file, selector),
)
elif mode == "i":
iterator = ifcopenshell.geom.iterator(
self.geom_settings,
ifc_file,
multiprocessing.cpu_count(),
include=selector.parse(ifc_file, selector),
)
valid_file = iterator.initialize()
if not valid_file:
return False
old_progress = -1
while True:
shape = iterator.get()
self.collider.create_object(name, shape.guid, shape)
if not iterator.next():
break
def export(self):
if len(self.settings.output) > 4 and self.settings.output[-4:] == ".bcf":
return self.export_bcfxml()
self.export_json()
def export_bcfxml(self):
import bcf
import bcf.bcfxml
for i, clash_set in enumerate(self.clash_sets):
bcfxml = bcf.bcfxml.BcfXml()
bcfxml.new_project()
bcfxml.project.name = clash_set["name"]
bcfxml.edit_project()
for key, clash in clash_set["clashes"].items():
topic = bcf.data.Topic()
topic.title = "{}/{} and {}/{}".format(
clash["a_ifc_class"], clash["a_name"], clash["b_ifc_class"], clash["b_name"]
)
topic = bcfxml.add_topic(topic)
viewpoint = bcf.data.Viewpoint()
viewpoint.perspective_camera = bcf.data.PerspectiveCamera()
position = np.array(clash["position"])
point = position + np.array((5, 5, 5)) # Dumb, but works!
viewpoint.perspective_camera.camera_view_point.x = point[0]
viewpoint.perspective_camera.camera_view_point.y = point[1]
viewpoint.perspective_camera.camera_view_point.z = point[2]
mat = self.get_track_to_matrix(point, position)
viewpoint.perspective_camera.camera_direction.x = mat[0][2] * -1
viewpoint.perspective_camera.camera_direction.y = mat[1][2] * -1
viewpoint.perspective_camera.camera_direction.z = mat[2][2] * -1
viewpoint.perspective_camera.camera_up_vector.x = mat[0][1]
viewpoint.perspective_camera.camera_up_vector.y = mat[1][1]
viewpoint.perspective_camera.camera_up_vector.z = mat[2][1]
viewpoint.components = bcf.data.Components()
c1 = bcf.data.Component()
c1.ifc_guid = clash["a_global_id"]
c2 = bcf.data.Component()
c2.ifc_guid = clash["b_global_id"]
viewpoint.components.selection.append(c1)
viewpoint.components.selection.append(c2)
viewpoint.components.visibility = bcf.data.ComponentVisibility()
viewpoint.components.visibility.default_visibility = True
viewpoint.snapshot = self.get_viewpoint_snapshot(viewpoint, mat)
bcfxml.add_viewpoint(topic, viewpoint)
if i == 0:
bcfxml.save_project(self.settings.output)
else:
bcfxml.save_project(self.settings.output + f".{i}")
def get_viewpoint_snapshot(self, viewpoint, mat):
return None # Possible to overload this function in a GUI application if used as a library
# https://blender.stackexchange.com/questions/68834/recreate-to-track-quat-with-two-vectors-using-python/141706#141706
def get_track_to_matrix(self, camera_position, target_position):
camera_direction = camera_position - target_position
camera_direction = camera_direction / np.linalg.norm(camera_direction)
camera_right = np.cross(np.array([0.0, 0.0, 1.0]), camera_direction)
camera_right = camera_right / np.linalg.norm(camera_right)
camera_up = np.cross(camera_direction, camera_right)
camera_up = camera_up / np.linalg.norm(camera_up)
rotation_transform = np.zeros((4, 4))
rotation_transform[0, :3] = camera_right
rotation_transform[1, :3] = camera_up
rotation_transform[2, :3] = camera_direction
rotation_transform[-1, -1] = 1
translation_transform = np.eye(4)
translation_transform[:3, -1] = -camera_position
look_at_transform = np.matmul(rotation_transform, translation_transform)
return np.linalg.inv(look_at_transform)
def export_json(self):
results = self.clash_sets.copy()
for result in results:
del result["a_cm"]
del result["b_cm"]
for ab in ["a", "b"]:
for data in result[ab]:
if "ifc" in data:
del data["ifc"]
with open(self.settings.output, "w", encoding="utf-8") as clashes_file:
json.dump(results, clashes_file, indent=4)
def get_element(self, clash_group, global_id):
for data in clash_group:
try:
element = data["ifc"].by_guid(global_id)
if element:
return element
except:
pass
def smart_group_clashes(self, clash_sets, max_clustering_distance):
from sklearn.cluster import OPTICS
from collections import defaultdict
count_of_input_clashes = 0
count_of_clash_sets = 0
count_of_smart_groups = 0
count_of_final_clash_sets = 0
count_of_clash_sets = len(clash_sets)
for clash_set in clash_sets:
if not "clashes" in clash_set.keys():
print(f"Skipping clash set [{clash_set['name']}] since it contains no clash results.")
continue
clashes = clash_set["clashes"]
if len(clashes) == 0:
print(f"Skipping clash set [{clash_set['name']}] since it contains no clash results.")
continue
count_of_input_clashes += len(clashes)
positions = []
for clash in clashes.values():
positions.append(clash["position"])
data = np.array(positions)
# INPUTS
# set the desired maximum distance between the grouped points
if max_clustering_distance > 0:
max_distance_between_grouped_points = max_clustering_distance
else:
max_distance_between_grouped_points = 3
model = OPTICS(min_samples=2, max_eps=max_distance_between_grouped_points)
model.fit_predict(data)
pred = model.fit_predict(data)
# Insert the smart groups into the clashes
if len(pred) == len(clashes.values()):
i = 0
for clash in clashes.values():
int_prediction = int(pred[i])
if int_prediction == -1:
# ungroup this clash since it's a single clash that we were not able to group.
new_clash_group_number = np.amax(pred).item() + 1 + i
clash["smart_group"] = new_clash_group_number
else:
clash["smart_group"] = int_prediction
i += 1
# Create JSON with smart_groups that contain GlobalIDs
output_clash_sets = defaultdict(list)
for clash_set in clash_sets:
if not "clashes" in clash_set.keys():
continue
smart_groups = defaultdict(list)
for clash_id, content in clash_set["clashes"].items():
if "smart_group" in content:
object_id_list = list()
# Clash has been grouped, let's extract it.
object_id_list.append(content["a_global_id"])
object_id_list.append(content["b_global_id"])
smart_groups[content["smart_group"]].append(object_id_list)
count_of_smart_groups += len(smart_groups)
output_clash_sets[clash_set["name"]].append(smart_groups)
# Rename the clash groups to something more sensible
for clash_set, smart_groups in output_clash_sets.items():
clash_set_name = clash_set
# Only select the clashes that correspond to the actively selected IFC Clash Set
i = 1
new_smart_group_name = ""
for smart_group, global_id_pairs in list(smart_groups[0].items()):
new_smart_group_name = f"{clash_set_name} - {i}"
smart_groups[0][new_smart_group_name] = smart_groups[0].pop(smart_group)
i += 1
count_of_final_clash_sets = len(output_clash_sets)
print(
f"Took {count_of_input_clashes} clashes in {count_of_clash_sets} clash sets and turned",
f"them into {count_of_smart_groups} smart groups in {count_of_final_clash_sets} clash sets",
)
return output_clash_sets
class ClashSettings:
def __init__(self):
self.logger = None
self.output = "clashes.json"
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Clashes geometry between two IFC files")
parser.add_argument("input", type=str, help="A JSON dataset describing a series of clashsets")
parser.add_argument(
"-o", "--output", type=str, help="The JSON diff file to output. Defaults to output.json", default="output.json"
)
args = parser.parse_args()
settings = ClashSettings()
settings.output = args.output
settings.logger = logging.getLogger("Clash")
settings.logger.setLevel(logging.DEBUG)
handler = logging.StreamHandler(sys.stdout)
handler.setLevel(logging.DEBUG)
settings.logger.addHandler(handler)
ifc_clasher = Clasher(settings)
with open(args.input, "r") as clash_sets_file:
ifc_clasher.clash_sets = json.loads(clash_sets_file.read())
ifc_clasher.clash()
ifc_clasher.export()

Some files were not shown because too many files have changed in this diff Show More