Compare commits

...

75 Commits

Author SHA1 Message Date
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
99 changed files with 8690 additions and 2460 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,
@@ -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:
+4 -3
View File
@@ -13,6 +13,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 +24,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"]:
@@ -122,12 +123,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
+64 -12
View File
@@ -54,9 +54,24 @@ 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_surface_style(element)
if self.parse_representations(element):
self.assign_material_slots_to_faces()
def add_default_material_surface_style(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
def load_existing_materials(self):
for material in bpy.data.materials:
if material.BIMObjectProperties.ifc_definition_id:
@@ -139,7 +154,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 +174,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 +223,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 +251,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 +347,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 +475,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 +482,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 +578,19 @@ 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()
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 +604,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 +964,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):
@@ -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,7 @@ from . import ui, prop, operator
classes = (
operator.PrintIfcFile,
operator.PrintObjectPlacement,
operator.ValidateIfcFile,
operator.ProfileImportIFC,
operator.CreateAllShapes,
@@ -1,6 +1,7 @@
import bpy
import logging
import ifcopenshell
import ifcopenshell.util.placement
import blenderbim.bim.import_ifc as import_ifc
from blenderbim.bim.ifc import IfcStore
@@ -191,3 +192,13 @@ class InspectFromObject(bpy.types.Operator):
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"}
@@ -59,6 +59,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=""
@@ -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(
[
@@ -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):
@@ -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,6 +161,7 @@ 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
@@ -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()
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())
@@ -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)
@@ -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):
@@ -366,7 +375,9 @@ class UpdateParametricRepresentation(bpy.types.Operator):
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"}
@@ -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
@@ -205,6 +205,7 @@ class AddProfile(bpy.types.Operator):
},
)
Data.load_profiles()
ProfileData.load(self.file)
return {"FINISHED"}
@@ -223,6 +224,7 @@ class RemoveProfile(bpy.types.Operator):
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"}
@@ -282,6 +284,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"}
@@ -473,7 +476,6 @@ class EditAssignedMaterial(bpy.types.Operator):
obj = bpy.data.objects.get(self.obj) if self.obj else bpy.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 +484,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 +499,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"):
@@ -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,
@@ -1,7 +1,7 @@
import bpy
import ifcopenshell
import ifcopenshell.api
from blenderbim.bim.module.model import product, wall, slab, column
from blenderbim.bim.module.model import product, wall, slab, profile, opening
from blenderbim.bim.ifc import IfcStore
from bpy.app.handlers import persistent
@@ -17,6 +17,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 +53,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,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
@@ -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)
y_distances = self.get_axis_distances(point, active_y_axis)
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):
results = []
for obj in bpy.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)
@@ -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 = bpy.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
@@ -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()
@@ -0,0 +1,9 @@
classes = ()
def register():
pass
def unregister():
pass
@@ -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")
@@ -129,6 +129,8 @@ class AssignClass(bpy.types.Operator):
if product.is_a("IfcElementType"):
self.place_in_types_collection(obj)
elif product.is_a("IfcOpeningElement"):
self.place_in_openings_collection(obj)
elif (
product.is_a("IfcSpatialElement")
or product.is_a("IfcSpatialStructureElement")
@@ -152,6 +154,18 @@ class AssignClass(bpy.types.Operator):
break
break
def place_in_openings_collection(self, obj):
for project in [c for c in bpy.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):
for collection in obj.users_collection:
if collection.name == obj.name:
@@ -265,13 +279,29 @@ class CopyClass(bpy.types.Operator):
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)
@@ -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"):
@@ -62,15 +62,15 @@ class SelectIfcClass(bpy.types.Operator):
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()):
obj.select_set(True)
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)
@@ -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 bpy.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 bpy.context.selected_objects or [bpy.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)
@@ -127,3 +133,20 @@ class SelectSimilarType(bpy.types.Operator):
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 bpy.context.active_object
oprops = relating_type.BIMObjectProperties
related_objects = Data.types[oprops.ifc_definition_id]
for obj in bpy.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)
@@ -6,6 +6,7 @@ classes = (
operator.RemoveOpening,
operator.AddFilling,
operator.RemoveFilling,
operator.ToggleOpeningVisibility,
prop.VoidProperties,
ui.BIM_PT_voids,
)
@@ -130,3 +130,15 @@ class RemoveFilling(bpy.types.Operator):
)
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 bpy.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"}
@@ -5,9 +5,6 @@ import json
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
+82
View File
@@ -0,0 +1,82 @@
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_wall_type("DEMO50", 0.05)
self.create_wall_type("DEMO100", 0.1)
self.create_wall_type("DEMO200", 0.2)
self.create_wall_type("DEMO300", 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_wall_type(self, name, thickness):
wall = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWallType", name=name)
ifcopenshell.api.run("material.assign_material", self.file, product=wall, type="IfcMaterialLayerSet")
layer_set = ifcopenshell.util.element.get_material(wall)
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=wall, 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,80 @@
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):
pset_name, property_name = property_path.split(".")
elements = IfcStore.file.by_type(ifc_class)
for element in elements:
if not IfcStore.file.get_property(element, pset_name, property_name):
assert False
@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
pset_name, property_name = property_path.split(".")
elements = IfcStore.file.by_type(ifc_class)
for element in elements:
prop = IfcStore.file.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
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,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
+1 -1
View File
@@ -745,7 +745,7 @@ int main(int argc, char** argv) {
std::uniform_int_distribution<int> index_dist('A', 'Z');
{
std::string v = ".ifcopenshell.";
output_temp_filename += path_t(v.begin(), v.end());
output_temp_filename = path_t(v.begin(), v.end());
}
for (int i = 0; i < 8; ++i) {
output_temp_filename.push_back(static_cast<path_t::value_type>(index_dist(rng)));
+8
View File
@@ -0,0 +1,8 @@
# IfcFM
IfcFM is a library that handles the extraction and analysis of IFC data for the
purposes of facility management.
It is currently a prototype and will supersede the IfcCOBie library. It is
planned to support workflows related to the old COBie standard, as well as
upcoming IFC Facility Management related MVDs.
+380
View File
@@ -0,0 +1,380 @@
import datetime
import ifcopenshell
import ifcopenshell.util.fm
import ifcopenshell.util.selector
import ifcopenshell.util.date
import ifcopenshell.util.schema
class Parser:
def __init__(self, logger):
self.logger = logger
self.file = None
self.sheets = [
"contacts",
"facilities",
"floors",
"spaces",
"zones",
"types",
"components",
"systems",
"assemblies",
"connections",
"spares",
"resources",
"jobs",
"impacts",
"documents",
"attributes",
"coordinates",
"issues",
]
for sheet in self.sheets:
setattr(self, sheet, {})
self.picklists = {
"Category-Role": [],
"Category-Facility": [],
"FloorType": [],
"Category-Space": [],
"ZoneType": [],
"Category-Product": [],
"AssetType": [],
"DurationUnit": ["day"], # See note about hardcoded day below
"Category-Element": [],
"SpareType": [],
"ApprovalBy": [],
"StageType": [],
"objType": [],
}
self.default_date = (datetime.datetime(1970, 1, 1) + datetime.timedelta(seconds=-2177452801)).isoformat()
def parse(self, files):
self.files = files
# self.file = ifcopenshell.open(file)
# self.type_assets = self.selector.parse(self.file, type_query)
# self.component_assets = self.selector.parse(self.file, component_query)
self.get_contacts()
self.get_facilities()
self.get_floors()
self.get_spaces()
self.get_zones()
self.get_types()
self.get_components()
self.get_systems()
# self.get_assemblies()
# self.get_connections()
# self.get_spares()
# self.get_resources()
# self.get_jobs()
# self.get_impacts()
self.get_documents()
# self.get_attributes()
# self.get_coordinates()
# self.get_issues()
def get_contacts(self):
for element in self.files["arch"].by_type("IfcOrganization"):
if "IfcApplication" in [e.is_a() for e in self.files["arch"].get_inverse(element)]:
continue
name = element.Name
self.contacts[name] = self.get_organisation(element)
def get_organisation(self, element):
return {
"Name": element.Name,
"Category": self.get_organisation_category(element),
"Email": self.get_organisation_address(element, "ElectronicMailAddresses"),
"Phone": self.get_organisation_address(element, "TelephoneNumbers"),
"Department": self.get_organisation_address(element, "InternalLocation"),
"Street": self.get_organisation_address(element, "AddressLines"),
"PostalBox": self.get_organisation_address(element, "PostalBox"),
"Town": self.get_organisation_address(element, "Town"),
"StateRegion": self.get_organisation_address(element, "Region"),
"PostalCode": self.get_organisation_address(element, "PostalCode"),
"Country": self.get_organisation_address(element, "Country"),
"CompanyURL": self.get_organisation_address(element, "WWWHomePageURL"),
}
def get_organisation_category(self, element):
for role in element.Roles or []:
if role.UserDefinedRole:
return role.UserDefinedRole
def get_organisation_address(self, element, name):
for address in element.Addresses or []:
if hasattr(address, name) and getattr(address, name, None):
result = getattr(address, name)
if isinstance(result, tuple):
return result[0]
return result
def get_facilities(self):
element = self.files["arch"].by_type("IfcBuilding")[0]
self.facilities[element.Name] = {
"Name": element.Name,
"AuthorOrganizationName": element.OwnerHistory.OwningUser.TheOrganization.Name,
"AuthorDate": ifcopenshell.util.date.ifc2datetime(
self.files["arch"].by_type("IfcProject")[0].OwnerHistory.CreationDate
).isoformat(),
"Category": self.get_classification(element),
"ProjectName": element.Decomposes[0].RelatingObject.Decomposes[0].RelatingObject.Name,
"SiteName": element.Decomposes[0].RelatingObject.Name,
"LinearUnits": "millimeters",
"AreaUnits": "square meters",
"AreaMeasurement": "TODO",
"Phase": element.Decomposes[0].RelatingObject.Decomposes[0].RelatingObject.Phase,
"ModelSoftware": element.OwnerHistory.OwningApplication.ApplicationFullName,
"ModelProjectID": self.files["arch"].by_type("IfcProject")[0].GlobalId,
"ModelSiteID": element.Decomposes[0].RelatingObject.GlobalId,
"ModelBuildingID": element.GlobalId,
}
def get_classification(self, element):
rel = [r for r in element.HasAssociations or [] if r.is_a("IfcRelAssociatesClassification")]
if rel:
classification = rel[0].RelatingClassification
if getattr(classification, "Identification", None) and getattr(classification, "Name", None):
return "{}:{}".format(classification.Identification, classification.Name)
elif getattr(classification, "ItemReference", None) and getattr(classification, "Name", None):
return "{}:{}".format(classification.ItemReference, classification.Name)
def get_floors(self):
storeys = self.files["arch"].by_type("IfcBuildingStorey")
self.floors["Site"] = {
"Name": "Site",
"AuthorOrganizationName": storeys[0].OwnerHistory.OwningUser.TheOrganization.Name,
"AuthorDate": datetime.datetime.now().replace(microsecond=0).isoformat(),
"Category": "Site",
"ModelSoftware": "IfcFM",
"ModelObject": "IfcExternalSpatialElement",
"ModelID": ifcopenshell.guid.new(), # TODO
"Elevation": None,
}
for element in storeys:
self.get_floor(element)
def get_floor(self, element):
name = element.Name
elevation = element.ObjectPlacement.RelativePlacement.Location.Coordinates[2]
self.floors[name] = {
"Name": name,
"AuthorOrganizationName": element.OwnerHistory.OwningUser.TheOrganization.Name,
"AuthorDate": ifcopenshell.util.date.ifc2datetime(element.OwnerHistory.CreationDate).isoformat(),
"Category": "Level",
"ModelSoftware": element.OwnerHistory.OwningApplication.ApplicationFullName,
"ModelObject": element.is_a(),
"ModelID": element.GlobalId,
"Elevation": elevation,
}
def get_spaces(self):
primary_keys = []
for element in self.files["arch"].by_type("IfcSpace"):
name = element.Name
primary_keys.append(name)
# TODO: not correct mapping
psets = ifcopenshell.util.element.get_psets(element)
category = None
if "Data" in psets and "COBie.Space.Category" in psets["Data"]:
category = psets["Data"]["COBie.Space.Category"].replace(" : ", ":")
usable_height = None
if "Data" in psets and "COBie.Space.Category" in psets["Data"]:
usable_height = round(psets["Data"]["COBie.Space.UsableHeight"], 2) or None
area_gross = None
if "Data" in psets and "COBie.Space.GrossArea" in psets["Data"]:
area_gross = round(psets["Data"]["COBie.Space.GrossArea"], 2) or None
area_net = None
if "Data" in psets and "COBie.Space.NetArea" in psets["Data"]:
area_net = round(psets["Data"]["COBie.Space.NetArea"], 2) or None
self.spaces[name] = {
"Name": name,
"AuthorOrganizationName": element.OwnerHistory.OwningUser.TheOrganization.Name,
"AuthorDate": ifcopenshell.util.date.ifc2datetime(element.OwnerHistory.CreationDate).isoformat(),
"Category": category,
"LevelName": element.Decomposes[0].RelatingObject.Name,
"Description": element.LongName,
"ModelSoftware": element.OwnerHistory.OwningApplication.ApplicationFullName,
"ModelID": element.GlobalId,
"BuildingRoomNumber": None,
"UsableHeight": usable_height,
"AreaGross": area_gross,
"AreaNet": area_net,
}
def get_zones(self):
for element in self.files["arch"].by_type("IfcZone"):
for rel in element.IsGroupedBy:
for space in rel.RelatedObjects:
if not space.is_a("IfcSpace"):
continue
self.zones[element.Name + space.Name] = {
"Name": element.Name,
"AuthorOrganizationName": element.OwnerHistory.OwningUser.TheOrganization.Name,
"AuthorDate": ifcopenshell.util.date.ifc2datetime(
element.OwnerHistory.CreationDate
).isoformat(),
"Category": "Occupancy",
"SpaceName": space.Name,
"ModelSoftware": element.OwnerHistory.OwningApplication.ApplicationFullName,
"ModelID": element.GlobalId,
"ParentZoneName": None,
}
def get_systems(self):
for discipline, ifc in self.files.items():
# if discipline == "arch":
# continue
for element in ifc.by_type("IfcSystem"):
name = element.Name
self.systems[name] = {
"Name": name,
"AuthorOrganizationName": element.OwnerHistory.OwningUser.TheOrganization.Name,
"AuthorDate": ifcopenshell.util.date.ifc2datetime(element.OwnerHistory.CreationDate).isoformat(),
"Category": None,
"ModelSoftware": element.OwnerHistory.OwningApplication.ApplicationFullName,
"ModelID": element.GlobalId,
"ParentSystemName": None,
}
def get_types(self):
for discipline, ifc in self.files.items():
self.get_types_from_file(discipline)
def get_types_from_file(self, ifc_file):
primary_keys = []
for element in ifcopenshell.util.fm.get_fmhem_types(self.files[ifc_file]):
name = element.Name
primary_keys.append(name)
psets = ifcopenshell.util.element.get_psets(element)
# Hack
category = None
if ifc_file == "arch":
#if "Data" in psets and "COBie.Type.Category" in psets["Data"]:
# if ":" in psets["Data"]["COBie.Type.Category"]:
# category = psets["Data"]["COBie.Type.Category"].replace(" : ", ":")
if "Data" in psets and "Classification.Uniclass.Pr.Number" in psets["Data"]:
category = f"{psets['Data']['Classification.Uniclass.Pr.Number']}:{psets['Data']['Classification.Uniclass.Pr.Description']}"
elif ifc_file == "hyd":
if "Data" in psets and "Classification.Uniclass.Pr.Number" in psets["Data"]:
category = f"{psets['Data']['Classification.Uniclass.Pr.Number']}:{psets['Data']['Classification.Uniclass.Pr.Description']}"
self.types[name] = {
"Name": name,
"AuthorOrganizationName": element.OwnerHistory.OwningUser.TheOrganization.Name,
"AuthorDate": ifcopenshell.util.date.ifc2datetime(element.OwnerHistory.CreationDate).isoformat(),
"Category": category,
"ProcurementMethod": None,
"Description": element.Description,
"ManufacturerOrganizationName": None,
"SupplierOrganizationName": None,
"ModelNumber": None,
"WarrantyOrganizationName": None,
"WarrantyDuration": None,
"ModelSoftware": element.OwnerHistory.OwningApplication.ApplicationFullName,
"ModelObject": element.is_a(),
"ModelID": element.GlobalId,
"SpecificationSection": None,
"SubmittalID": None,
"ProductURL": None,
}
def get_components(self):
for discipline, ifc in self.files.items():
self.get_components_from_file(discipline)
def get_components_from_file(self, ifc_file):
for element_type in ifcopenshell.util.fm.get_fmhem_types(self.files[ifc_file]):
for element in element_type.ObjectTypeOf[0].RelatedObjects:
name = element.Name
system = None
for rel in element.HasAssignments:
if rel.is_a("IfcRelAssignsToGroup") and rel.RelatingGroup.is_a("IfcSystem"):
system = rel.RelatingGroup.Name
space_name = None
# Nasty hack
if ifc_file == "arch":
psets = ifcopenshell.util.element.get_psets(element)
if "Data" in psets and "COBie.Component.Space" in psets["Data"]:
space_name = psets["Data"]["COBie.Component.Space"]
if space_name not in self.spaces:
space_name = None
else:
space = element.ContainedInStructure[0].RelatingStructure
if space.is_a("IfcSpace"):
space_name = space.Name
self.components[name] = {
"Name": name,
"AuthorOrganizationName": element.OwnerHistory.OwningUser.TheOrganization.Name,
"AuthorDate": ifcopenshell.util.date.ifc2datetime(element.OwnerHistory.CreationDate).isoformat(),
"TypeName": element_type.Name,
"SpaceName": space_name,
"InstallationDate": None,
"WarrantyStartDate": None,
"ModelSoftware": element.OwnerHistory.OwningApplication.ApplicationFullName,
"ModelObject": element.is_a(),
"ModelID": element.GlobalId,
"InstalledModelNumber": None,
"SerialNumber": None,
"BarCode": None,
"TagNumber": None,
"OwnerAssetID": None,
"SystemName": system,
"FluidHotFeedName": None,
"FluidColdFeedName": None,
"ElectricPanelName": None,
"ElectricCircuitName": None,
"ControlledByName": None,
"InterlockedWithName": None,
"PartOfAssemblyName": None,
}
def get_documents(self):
for rel in self.files["arch"].by_type("IfcRelAssociatesDocument"):
element = rel.RelatingDocument
for related_object in rel.RelatedObjects:
name = element.Name
worksheet_row = related_object.Name
if self.files["arch"].schema == "IFC2X3":
submittal_id = element.ItemReference
referenced_document = element.ReferenceToDocument[0]
author_date = None
if referenced_document.CreationTime:
author_date = ifcopenshell.util.date.ifc2datetime(referenced_document.CreationTime).isoformat()
else:
submittal_id = element.Identification
referenced_document = element.ReferencedDocument
author_date = referenced_document.CreationTime
worksheet_name = None
if related_object.is_a("IfcSpace"):
worksheet_name = "Space"
elif related_object.is_a("IfcTypeObject"):
worksheet_name = "Type"
self.documents[element.Name + worksheet_name + worksheet_row] = {
"Name": name,
"AuthorOrganizationName": referenced_document.DocumentOwner.Name,
"AuthorDate": author_date,
"Category": referenced_document.Purpose,
"WorksheetName": worksheet_name,
"WorksheetRow": worksheet_row,
"Revision": referenced_document.Revision,
"Location": referenced_document.Name,
"Description": referenced_document.Description,
"SpecificationSection": None,
"SubmittalID": submittal_id,
"SourceURL": None,
}
+581
View File
@@ -0,0 +1,581 @@
import csv
try:
from xlsxwriter import Workbook
except:
pass # No XLSX support
try:
from odf.opendocument import OpenDocumentSpreadsheet
from odf.style import Style, TableCellProperties
from odf.table import Table, TableRow, TableCell
from odf.text import P
except:
pass # No ODF support
# https://stackoverflow.com/questions/1143671/how-to-sort-objects-by-multiple-keys-in-python
from operator import itemgetter as i
from functools import cmp_to_key
def cmp(x, y):
"""
Replacement for built-in function cmp that was removed in Python 3
Compare the two objects x and y and return an integer according to
the outcome. The return value is negative if x < y, zero if x == y
and strictly positive if x > y.
https://portingguide.readthedocs.io/en/latest/comparisons.html#the-cmp-function
"""
try:
return (x > y) - (x < y)
except:
return 0
def multikeysort(items, columns):
comparers = [((i(col[1:].strip()), -1) if col.startswith("-") else (i(col.strip()), 1)) for col in columns]
def comparer(left, right):
comparer_iter = (cmp(fn(left), fn(right)) * mult for fn, mult in comparers)
return next((result for result in comparer_iter if result), 0)
return sorted(items, key=cmp_to_key(comparer))
class Writer:
def __init__(self, parser, filename=None):
self.filename = filename
self.parser = parser
self.sheets = []
self.sheet_data = {}
self.colours = {
"r": "fdff8e", # Required
"i": "fdcd94", # Internal reference
"e": "cd95ff", # External reference
"o": "cdffc8", # Optional
"s": "c0c0c0", # Secondary information
"p": "9ccaff", # Project specific
"n": "000000", # Not used
}
self.colours = {
"r": "dc8774", # Required
"i": "eda786", # Internal reference
"e": "96c7d0", # External reference
"o": "ddb873", # Optional or edd889
"s": "dddddd", # Secondary information
"p": "b8dd73", # Project specific
"n": "000000", # Not used
}
def write(self):
self.sheets = [
"Contact",
"Facility",
"Floor",
"Space",
"Zone",
"Type",
"Component",
"System",
# "Assembly",
# "Connection",
# "Spare",
# "Resource",
# "Job",
# "Impact",
"Document",
# "Attribute",
# "Coordinate",
# "Issue",
]
self.write_data(
"Contact",
self.parser.contacts,
[
"Name",
"Category",
"Email",
"Phone",
"Department",
"Street",
"PostalBox",
"Town",
"StateRegion",
"PostalCode",
"Country",
"CompanyURL",
],
"rirrrrrrrrrr",
["Name"],
)
self.write_data(
"Facility",
self.parser.facilities,
[
"Name",
"AuthorOrganizationName",
"AuthorDate",
"Category",
"ProjectName",
"SiteName",
"LinearUnits",
"AreaUnits",
"AreaMeasurement",
"Phase",
"ModelSoftware",
"ModelProjectID",
"ModelSiteID",
"ModelBuildingID",
],
"ririrrrrrreeee",
["Name"],
)
self.write_data(
"Floor",
self.parser.floors,
[
"Name",
"AuthorOrganizationName",
"AuthorDate",
"Category",
"ModelSoftware",
"ModelObject",
"ModelID",
"Elevation",
],
"ririeeer",
["Elevation"],
)
self.write_data(
"Space",
self.parser.spaces,
[
"Name",
"AuthorOrganizationName",
"AuthorDate",
"Category",
"LevelName",
"Description",
"ModelSoftware",
"ModelID",
"BuildingRoomNumber",
"UsableHeight",
"AreaGross",
"AreaNet",
],
"ririireerrrr",
["LevelName", "Name"],
)
self.write_data(
"Zone",
self.parser.zones,
[
"Name",
"AuthorOrganizationName",
"AuthorDate",
"Category",
"SpaceName",
"ModelSoftware",
"ModelID",
"ParentZoneName",
],
"ririieei",
["Name", "SpaceName"],
)
self.write_data(
"Type",
self.parser.types,
[
"Name",
"AuthorOrganizationName",
"AuthorDate",
"Category",
"ProcurementMethod",
"Description",
"ManufacturerOrganizationName",
"SupplierOrganizationName",
"ModelNumber",
"WarrantyOrganizationName",
"WarrantyDuration",
"ModelSoftware",
"ModelObject",
"ModelID",
"SpecificationSection",
"SubmittalID",
"ProductURL",
],
"ririiriirireeerrr",
["ModelObject", "Name"],
)
self.write_data(
"Component",
self.parser.components,
[
"Name",
"AuthorOrganizationName",
"AuthorDate",
"TypeName",
"SpaceName",
"InstallationDate",
"WarrantyStartDate",
"ModelSoftware",
"ModelObject",
"ModelID",
"InstalledModelNumber",
"SerialNumber",
"BarCode",
"TagNumber",
"OwnerAssetID",
"SystemName",
"FluidHotFeedName",
"FluidColdFeedName",
"ElectricPanelName",
"ElectricCircuitName",
"ControlledByName",
"InterlockedWithName",
"PartOfAssemblyName",
],
"ririirreeerrrrriiiiiiii",
["ModelObject", "Name"],
)
self.write_data(
"System",
self.parser.systems,
[
"Name",
"AuthorOrganizationName",
"AuthorDate",
"Category",
"ModelSoftware",
"ModelID",
"ParentSystemName",
],
"ririeei",
["Name"],
)
# self.write_data(
# "Assembly",
# self.parser.assemblies,
# "Name",
# [
# "Name",
# "CreatedBy",
# "CreatedOn",
# "SheetName",
# "ParentName",
# "ChildNames",
# "AssemblyType",
# "ExtSystem",
# "ExtObject",
# "ExtIdentifier",
# "Description",
# ],
# "rirrrrreeeo",
# self.parser.custom_data["assemblies"],
# )
# self.write_data(
# "Connection",
# self.parser.connections,
# "Name",
# [
# "Name",
# "CreatedBy",
# "CreatedOn",
# "ConnectionType",
# "SheetName",
# "RowName1",
# "RowName2",
# "RealizingElement",
# "PortName1",
# "PortName2",
# "ExtSystem",
# "ExtObject",
# "ExtIdentifier",
# "Description",
# ],
# "ririiiiiiieeeo",
# self.parser.custom_data["connections"],
# )
# self.write_data(
# "Spare",
# self.parser.spares,
# "Name",
# [
# "Name",
# "CreatedBy",
# "CreatedOn",
# "Category",
# "TypeName",
# "Suppliers",
# "ExtSystem",
# "ExtObject",
# "ExtIdentifier",
# "Description",
# "SetNumber",
# "PartNumber",
# ],
# "ririiieeeooo",
# self.parser.custom_data["spares"],
# )
# self.write_data(
# "Resource",
# self.parser.resources,
# "Name",
# [
# "Name",
# "CreatedBy",
# "CreatedOn",
# "Category",
# "ExtSystem",
# "ExtObject",
# "ExtIdentifier",
# "Description",
# ],
# "ririeeeo",
# self.parser.custom_data["resources"],
# )
# self.write_data(
# "Job",
# self.parser.jobs,
# "Name",
# [
# "Name",
# "CreatedBy",
# "CreatedOn",
# "Category",
# "Status",
# "TypeName",
# "Description",
# "Duration",
# "DurationUnit",
# "Start",
# "TaskStartUnit",
# "Frequency",
# "FrequencyUnit",
# "ExtSystem",
# "ExtObject",
# "ExtIdentifier",
# "TaskNumber",
# "Priors",
# "ResourceNames",
# ],
# "ririiirriririeeeoii",
# self.parser.custom_data["jobs"],
# )
# self.write_data(
# "Impact",
# self.parser.impacts,
# "Name",
# [
# "Name",
# "CreatedBy",
# "CreatedOn",
# "ImpactType",
# "ImpactStage",
# "SheetName",
# "RowName",
# "Value",
# "Unit",
# "LeadInTime",
# "Duration",
# "LeadOutTime",
# "ExtSystem",
# "ExtObject",
# "ExtIdentifier",
# "Description",
# ],
# "ririiiirioooeeeo",
# self.parser.custom_data["impacts"],
# )
self.write_data(
"Document",
self.parser.documents,
[
"Name",
"AuthorOrganizationName",
"AuthorDate",
"Category",
"WorksheetName",
"WorksheetRow",
"Revision",
"Location",
"Description",
"SpecificationSection",
"SubmittalID",
"SourceURL",
],
"ririiirrrrer",
["Name"],
)
# self.write_data(
# "Attribute",
# self.parser.attributes,
# "Name",
# [
# "Name",
# "CreatedBy",
# "CreatedOn",
# "Category",
# "SheetName",
# "RowName",
# "Value",
# "Unit",
# "ExtSystem",
# "ExtObject",
# "ExtIdentifier",
# "Description",
# "AllowedValues",
# ],
# "ririiirreeeoo",
# )
# self.write_data(
# "Coordinate",
# self.parser.coordinates,
# "Name",
# [
# "Name",
# "CreatedBy",
# "CreatedOn",
# "Category",
# "SheetName",
# "RowName",
# "CoordinateXAxis",
# "CoordinateYAxis",
# "CoordinateZAxis",
# "ExtSystem",
# "ExtObject",
# "ExtIdentifier",
# "ClockwiseRotation",
# "ElevationalRotation",
# "YawRotation",
# ],
# "ririiooooeeeooo",
# )
# self.write_data(
# "Issue",
# self.parser.issues,
# "Name",
# [
# "Name",
# "CreatedBy",
# "CreatedOn",
# "Type",
# "Risk",
# "Chance",
# "Impact",
# "SheetName1",
# "RowName1",
# "SheetName2",
# "RowName2",
# "Description",
# "Owner",
# "Mitigation",
# "ExtSystem",
# "ExtObject",
# "ExtIdentifier",
# ],
# "ririooooooooooeee",
# )
def write_data(self, sheet, data, fieldnames, colours, sort_fields, custom_data={}):
self.sheet_data[sheet] = {"headers": fieldnames + list(custom_data.keys()), "colours": colours, "rows": []}
for row in multikeysort(list(data.values()), sort_fields):
values = []
for fieldname in fieldnames:
values.append(row[fieldname])
for fieldname in custom_data.keys():
values.append(row[fieldname])
self.sheet_data[sheet]["rows"].append(values)
class CsvWriter(Writer):
def write(self):
super().write()
for sheet, data in self.sheet_data.items():
with open(os.path.join(self.filename, "{}.csv".format(sheet)), "w", newline="", encoding="utf-8") as file:
writer = csv.writer(file)
writer.writerow(data["headers"])
for row in data["rows"]:
writer.writerow(row)
class XlsWriter(Writer):
def write(self):
super().write()
self.workbook = Workbook(self.filename + ".xlsx")
self.cell_formats = {}
for key, value in self.colours.items():
self.cell_formats[key] = self.workbook.add_format()
self.cell_formats[key].set_bg_color(value)
for sheet in self.sheets:
self.write_worksheet(sheet)
self.workbook.close()
def write_worksheet(self, name):
worksheet = self.workbook.add_worksheet(name)
r = 0
c = 0
for header in self.sheet_data[name]["headers"]:
cell = worksheet.write(r, c, header, self.cell_formats["s"])
c += 1
c = 0
r += 1
for row in self.sheet_data[name]["rows"]:
c = 0
for col in row:
if c >= len(self.sheet_data[name]["colours"]):
cell_format = "p"
else:
cell_format = self.sheet_data[name]["colours"][c]
cell = worksheet.write(r, c, col, self.cell_formats[cell_format])
c += 1
r += 1
class OdsWriter(Writer):
def write(self):
super().write()
self.doc = OpenDocumentSpreadsheet()
self.cell_formats = {}
for key, value in self.colours.items():
style = Style(name=key, family="table-cell")
style.addElement(TableCellProperties(backgroundcolor="#" + value))
self.doc.automaticstyles.addElement(style)
self.cell_formats[key] = style
for sheet in self.sheets:
self.write_table(sheet)
self.doc.save(self.filename, True)
def write_table(self, name):
table = Table(name=name)
tr = TableRow()
for header in self.sheet_data[name]["headers"]:
tc = TableCell(valuetype="string", stylename="s")
tc.addElement(P(text=header))
tr.addElement(tc)
table.addElement(tr)
for row in self.sheet_data[name]["rows"]:
tr = TableRow()
c = 0
for col in row:
if c >= len(self.sheet_data[name]["colours"]):
cell_format = "p"
else:
cell_format = self.sheet_data[name]["colours"][c]
tc = TableCell(valuetype="string", stylename=cell_format)
if col is None:
col = "NULL"
tc.addElement(P(text=col))
tr.addElement(tc)
c += 1
table.addElement(tr)
self.doc.spreadsheet.addElement(table)
+1 -1
View File
@@ -490,7 +490,7 @@ public:
prs_styles.push_back(style_l);
}
}
#if defined SCHEMA_HAS_IfcStyleAssignmentSelect or defined SCHEMA_HAS_IfcPresentationStyleAssignment
#if defined(SCHEMA_HAS_IfcStyleAssignmentSelect) || defined(SCHEMA_HAS_IfcPresentationStyleAssignment)
}
#endif
@@ -45,7 +45,7 @@
* *
* IfcGeom::Iterator::get() *
* returns a pointer to the current IfcGeom::Element *
* *
* *
* IfcGeom::Iterator::next() *
* returns true iff a following entity is available for a successive call to *
* IfcGeom::Iterator::get() *
@@ -69,9 +69,9 @@
#endif
namespace IfcGeom {
template <typename P = double, typename PP = P>
class Iterator {
class IFC_GEOM_API Iterator {
private:
Iterator(const Iterator&); // N/I
Iterator& operator=(const Iterator&); // N/I
@@ -8,9 +8,9 @@ IteratorFactoryImplementation<P, PP>& iterator_implementations() {
return impl;
}
template IteratorFactoryImplementation<float, float>& iterator_implementations<float, float>();
template IteratorFactoryImplementation<float, double>& iterator_implementations<float, double>();
template IteratorFactoryImplementation<double, double>& iterator_implementations<double, double>();
template IFC_GEOM_API IteratorFactoryImplementation<float, float>& iterator_implementations<float, float>();
template IFC_GEOM_API IteratorFactoryImplementation<float, double>& iterator_implementations<float, double>();
template IFC_GEOM_API IteratorFactoryImplementation<double, double>& iterator_implementations<double, double>();
#ifdef HAS_SCHEMA_2x3
template <typename P, typename PP>
@@ -46,19 +46,19 @@ template <typename P, typename PP>
IteratorFactoryImplementation<P, PP>::IteratorFactoryImplementation() {
#ifdef HAS_SCHEMA_2x3
init_IteratorImplementation_Ifc2x3(this);
#endif
#endif
#ifdef HAS_SCHEMA_4
init_IteratorImplementation_Ifc4(this);
#endif
#endif
#ifdef HAS_SCHEMA_4x1
init_IteratorImplementation_Ifc4x1(this);
#endif
#endif
#ifdef HAS_SCHEMA_4x2
init_IteratorImplementation_Ifc4x2(this);
#endif
#endif
#ifdef HAS_SCHEMA_4x3_rc1
init_IteratorImplementation_Ifc4x3_rc1(this);
#endif
#endif
#ifdef HAS_SCHEMA_4x3_rc2
init_IteratorImplementation_Ifc4x3_rc2(this);
#endif
@@ -46,7 +46,7 @@ struct get_factory_type<double, double> {
};
template <typename P, typename PP>
class IteratorFactoryImplementation : public std::map<std::string, typename get_factory_type<P, PP>::type> {
class IFC_GEOM_API IteratorFactoryImplementation : public std::map<std::string, typename get_factory_type<P, PP>::type> {
public:
IteratorFactoryImplementation();
void bind(const std::string& schema_name, typename get_factory_type<P, PP>::type fn);
@@ -78,4 +78,4 @@ namespace IfcGeom {
}
#endif
#endif
@@ -97,3 +97,53 @@ def remove_post_listener(usecase_path, name, callback):
def remove_all_listeners():
pre_listeners.clear()
post_listeners.clear()
def extract_docs(module, usecase):
import typing
import inspect
import collections
results = []
inputs = collections.OrderedDict()
function_init = getattr(getattr(ifcopenshell.api, module), usecase).Usecase.__init__
function_execute = getattr(getattr(ifcopenshell.api, module), usecase).Usecase.execute
node_data = {"module": module, "usecase": usecase}
signature = inspect.signature(function_init)
for name, parameter in signature.parameters.items():
if name == "self":
continue
inputs[name] = {"name": name}
if isinstance(parameter.default, (str, float, int, bool)):
inputs[name]["default"] = parameter.default
type_hints = typing.get_type_hints(function_init)
for name, socket_data in inputs.items():
type_hint = type_hints[name]
if isinstance(type_hint, typing._UnionGenericAlias):
inputs[name]["type"] = [t.__name__ for t in typing.get_args(type_hint)]
else:
inputs[name]["type"] = type_hint.__name__
description = ""
for i, line in enumerate(function_init.__doc__.split("\n")):
line = line.strip()
if i == 0:
node_data["name"] = line
elif line.startswith(":return:"):
node_data["output"] = {"name": line.split(":")[2].strip(), "description": line.split(":")[3].strip()}
elif line.startswith(":param"):
param_name = line.split(":")[1].strip().replace("param ", "")
inputs[param_name]["description"] = line.split(":")[2].strip()
elif i >= 2:
description += line
if "output" in node_data:
node_data["output"]["type"] = typing.get_type_hints(function_execute)["return"].__name__
node_data["description"] = description.strip()
node_data["inputs"] = inputs
return node_data
@@ -22,8 +22,6 @@ class Usecase:
"unit_scale": None, # A scale factor to apply for all vectors in case the unit is different
"should_force_faceted_brep": False, # If we should force faceted breps for meshes
"should_force_triangulation": False, # If we should force triangulation for meshes
"is_wireframe": False, # If the geometry is a wireframe
"is_curve": False, # If the geometry is a Blender curve
"is_point_cloud": False, # If the geometry is a point cloud
# Possible IFC representation classes:
# IfcExtrudedAreaSolid/IfcRectangleProfileDef
@@ -204,14 +202,14 @@ class Usecase:
)
def create_variable_representation(self):
if self.settings["is_wireframe"]:
return self.create_wireframe_representation()
elif self.settings["is_curve"]:
if isinstance(self.settings["geometry"], bpy.types.Curve):
return self.create_curve3d_representation()
elif isinstance(self.settings["geometry"], bpy.types.Camera):
return self.create_camera_block_representation()
elif not len(self.settings["geometry"].polygons):
return self.create_curve3d_representation()
elif self.settings["is_point_cloud"]:
return self.create_point_cloud_representation()
elif isinstance(self.settings["geometry"], bpy.types.Camera):
return self.create_camera_block_representation()
elif self.settings["ifc_representation_class"] == "IfcExtrudedAreaSolid/IfcRectangleProfileDef":
return self.create_rectangle_extrusion_representation()
elif self.settings["ifc_representation_class"] == "IfcExtrudedAreaSolid/IfcCircleProfileDef":
@@ -1,5 +1,6 @@
import numpy as np
import ifcopenshell.api
import ifcopenshell.util.unit
import ifcopenshell.util.element
import ifcopenshell.util.placement
@@ -37,12 +38,10 @@ class Usecase:
placement_rel_to = relating_object.ObjectPlacement if hasattr(relating_object, "ObjectPlacement") else None
placement = self.file.createIfcLocalPlacement(placement_rel_to, self.get_relative_placement(placement_rel_to))
if self.settings["product"].ObjectPlacement:
old_placement = self.settings["product"].ObjectPlacement
old_placement = self.settings["product"].ObjectPlacement
if old_placement and len(self.file.get_inverse(old_placement)) == 1:
old_placement.PlacementRelTo = None
self.settings["product"].ObjectPlacement = None
for inverse in self.file.get_inverse(old_placement):
ifcopenshell.util.element.replace_attribute(inverse, old_placement, placement)
ifcopenshell.util.element.remove_deep(self.file, old_placement)
self.settings["product"].ObjectPlacement = placement
@@ -0,0 +1,10 @@
class Usecase:
def __init__(self, file, **settings):
self.file = file
self.settings = {"usage": None, "attributes": {}}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
for name, value in self.settings["attributes"].items():
setattr(self.settings["usage"], name, value)
@@ -6,6 +6,8 @@ class Usecase:
self.settings[key] = value
def execute(self):
address = self.file.create_entity(self.settings["ifc_class"], "OFFICE")
addresses = list(self.settings["assigned_object"].Addresses) if self.settings["assigned_object"].Addresses else []
addresses.append(self.file.create_entity(self.settings["ifc_class"], "OFFICE"))
addresses.append(address)
self.settings["assigned_object"].Addresses = addresses
return address
@@ -6,6 +6,8 @@ class Usecase:
self.settings[key] = value
def execute(self):
element = self.file.createIfcActorRole("ARCHITECT")
roles = list(self.settings["assigned_object"].Roles) if self.settings["assigned_object"].Roles else []
roles.append(self.file.createIfcActorRole("ARCHITECT"))
roles.append(element)
self.settings["assigned_object"].Roles = roles
return element
@@ -3,14 +3,18 @@ import ifcopenshell
class Usecase:
def __init__(self, **settings):
self.settings = {"version": "IFC4"}
for key, value in settings.items():
self.settings[key] = value
def __init__(self, version: str = "IFC4"):
"""Create File
def execute(self):
Create a new IFC file object
:param version: The schema version of the IFC file. Choose from "IFC2X3" or "IFC4".
:return: file: The created IFC file object.
"""
self.settings = {"version": version}
def execute(self) -> ifcopenshell.file:
self.file = ifcopenshell.file(schema=self.settings["version"])
# TODO: add all metadata, pending bug #747
self.file.wrapped_data.header.file_name.name = "/dev/null" # Hehehe
self.file.wrapped_data.header.file_name.time_stamp = (
datetime.datetime.utcnow()
@@ -22,5 +26,5 @@ class Usecase:
self.file.wrapped_data.header.file_name.preprocessor_version = "IfcOpenShell {}".format(ifcopenshell.version)
self.file.wrapped_data.header.file_name.originating_system = "IfcOpenShell {}".format(ifcopenshell.version)
self.file.wrapped_data.header.file_name.authorization = "Nobody"
self.file.wrapped_data.header.file_description.description = ('ViewDefinition[DesignTransferView]',)
self.file.wrapped_data.header.file_description.description = ("ViewDefinition[DesignTransferView]",)
return self.file
@@ -22,7 +22,7 @@ class Data:
return
product = file.by_id(product_id)
cls.products[product_id] = {"psets": set(), "qtos": set()}
if product.is_a("IfcElementType"):
if product.is_a("IfcTypeObject"):
cls.add_type_product_psets(product, product_id)
elif product.is_a("IfcMaterialDefinition"):
cls.add_material_psets(product, product_id)
@@ -0,0 +1,17 @@
import ifcopenshell
import ifcopenshell.api
class Usecase:
def __init__(self, file, **settings):
self.file = file
self.settings = {}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
return self.file.create_entity("IfcSystem", **{
"GlobalId": ifcopenshell.guid.new(),
"OwnerHistory": ifcopenshell.api.run("owner.create_owner_history", self.file),
"Name": "Unnamed"
})
@@ -0,0 +1,27 @@
import ifcopenshell
import ifcopenshell.api
class Usecase:
def __init__(self, file, **settings):
self.file = file
self.settings = {
"product": None,
"system": None,
}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
if not self.settings["system"].IsGroupedBy:
return self.file.create_entity("IfcRelAssignsToGroup", **{
"GlobalId": ifcopenshell.guid.new(),
"OwnerHistory": ifcopenshell.api.run("owner.create_owner_history", self.file),
"RelatedObjects": [self.settings["product"]],
"RelatingGroup": self.settings["system"]
})
rel = self.settings["system"].IsGroupedBy[0]
related_objects = set(rel.RelatedObjects) or set()
related_objects.add(self.settings["product"])
rel.RelatedObjects = list(related_objects)
ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": rel})
@@ -0,0 +1,24 @@
class Data:
is_loaded = False
products = {}
systems = {}
@classmethod
def purge(cls):
cls.is_loaded = False
cls.products = {}
cls.systems = {}
@classmethod
def load(cls, file):
cls.products = {}
cls.systems = {}
for system in file.by_type("IfcSystem", include_subtypes=False):
if system.IsGroupedBy:
for rel in system.IsGroupedBy:
for product in rel.RelatedObjects:
cls.products.setdefault(product.id(), []).append(system.id())
data = system.get_info()
del data["OwnerHistory"]
cls.systems[system.id()] = data
cls.is_loaded=True
@@ -0,0 +1,13 @@
class Usecase:
def __init__(self, file, **settings):
self.file = file
self.settings = {
"system": None,
"attributes": {}
}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
for name, value in self.settings["attributes"].items():
setattr(self.settings["system"], name, value)
@@ -0,0 +1,11 @@
class Usecase:
def __init__(self, file, **settings):
self.file = file
self.settings = {"system": None}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
for rel in self.settings["system"].IsGroupedBy or []:
self.file.remove(rel)
self.file.remove(self.settings["system"])
@@ -0,0 +1,25 @@
import ifcopenshell
import ifcopenshell.api
class Usecase:
def __init__(self, file, **settings):
self.file = file
self.settings = {
"product": None,
"system": None,
}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
if not self.settings["system"].IsGroupedBy:
return
rel = self.settings["system"].IsGroupedBy[0]
related_objects = set(rel.RelatedObjects) or set()
related_objects.remove(self.settings["product"])
if len(related_objects):
rel.RelatedObjects = list(related_objects)
ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": rel})
else:
self.file.remove(rel)
@@ -1,18 +1,40 @@
class Data:
products = {}
types = {}
@classmethod
def purge(cls):
cls.products = {}
cls.types = {}
@classmethod
def load(cls, file, product_id):
if not file:
return
cls.file = file
product = file.by_id(product_id)
if file.schema == "IFC2X3" and not hasattr(product, "IsDefinedBy"):
if product.is_a("IfcTypeObject"):
cls.load_type(product_id)
else:
cls.load_product(product_id)
@classmethod
def load_type(cls, product_id):
product = cls.file.by_id(product_id)
cls.types[product_id] = None
if cls.file.schema == "IFC2X3":
if hasattr(product, "ObjectTypeOf"):
cls.types[product_id] = [o.id() for o in product.ObjectTypeOf[0].RelatedObjects]
else:
if hasattr(product, "Types"):
cls.types[product_id] = [o.id() for o in product.Types[0].RelatedObjects]
@classmethod
def load_product(cls, product_id):
product = cls.file.by_id(product_id)
if cls.file.schema == "IFC2X3" and not hasattr(product, "IsDefinedBy"):
cls.products[product_id] = None
elif file.schema != "IFC2X3" and not hasattr(product, "IsTypedBy"):
elif cls.file.schema != "IFC2X3" and not hasattr(product, "IsTypedBy"):
cls.products[product_id] = None
elif hasattr(product, "IsTypedBy") and product.IsTypedBy:
type = product.IsTypedBy[0].RelatingType
@@ -48,7 +48,7 @@ class entity_instance(object):
>>> #423=IfcProductDefinitionShape($,$,(#409,#421))
"""
def __init__(self, e, file):
def __init__(self, e, file=None):
if isinstance(e, tuple):
e = ifcopenshell_wrapper.new_IfcBaseClass(*e)
super(entity_instance, self).__setattr__("wrapped_data", e)
@@ -126,7 +126,7 @@ class entity_instance(object):
return entity_instance.wrap_value(self.wrapped_data.get_argument(key), self.wrapped_data.file)
def __setitem__(self, idx, value):
if self.wrapped_data.file.transaction:
if self.wrapped_data.file and self.wrapped_data.file.transaction:
self.wrapped_data.file.transaction.store_edit(self, idx, value)
attr_type = real_attr_type = self.attribute_type(idx).title().replace(" ", "")
@@ -176,12 +176,14 @@ const IfcParse::enumeration_type& %(schema_name)s::%(name)s::Class() { return *%
}
%(schema_name)s::%(name)s::%(name)s(Value v) {
data_ = new IfcEntityInstanceData(%(schema_name_upper)s_%(name)s_type);
IfcWrite::IfcWriteArgument* attr = new IfcWrite::IfcWriteArgument();
attr->set(IfcWrite::IfcWriteArgument::EnumerationReference(v,ToString(v)));
data_->setArgument(0,attr);
}
%(schema_name)s::%(name)s::%(name)s(const std::string& v) {
data_ = new IfcEntityInstanceData(%(schema_name_upper)s_%(name)s_type);
IfcWrite::IfcWriteArgument* attr = new IfcWrite::IfcWriteArgument();
attr->set(IfcWrite::IfcWriteArgument::EnumerationReference(FromString(v),ToString(FromString(v))));
data_->setArgument(0,attr);
+27 -2
View File
@@ -264,19 +264,44 @@ class file(object):
eid = kwargs.pop("id", -1)
except:
pass
e = entity_instance((self.schema, type), self)
self.wrapped_data.add(e.wrapped_data, eid)
e.wrapped_data.this.disown()
# Create pairs of {attribute index, attribute value}.
# Keyword arguments are mapped to their corresponding
# numeric index with get_argument_index().
# @todo we should probably check that values for
# attributes are not passed as duplicates using
# both regular arguments and keyword arguments.
attrs = list(enumerate(args)) + [(e.wrapped_data.get_argument_index(name), arg) for name, arg in kwargs.items()]
# Don't store these attributes as transactions
# as the creation it self is already stored with
# it's arguments
if attrs:
transaction = self.transaction
self.transaction = None
for idx, arg in attrs:
e[idx] = arg
# Restore transaction status
if attrs:
self.transaction = transaction
# Once the values are populated add the instance
# to the file.
self.wrapped_data.add(e.wrapped_data, eid)
# The file container now handles the lifetime of
# this instance. Tell SWIG that it is no longer
# the owner.
e.wrapped_data.this.disown()
if self.transaction:
self.transaction.store_create(e)
return e
def __getattr__(self, attr):
@@ -42,7 +42,7 @@ def ifc2datetime(element):
element.DateComponent.DayComponent,
element.TimeComponent.HourComponent,
element.TimeComponent.MinuteComponent,
element.TimeComponent.SecondComponent,
int(element.TimeComponent.SecondComponent),
# TODO: implement TimeComponent timezone
)
elif element.is_a("IfcCalendarDate"):
@@ -0,0 +1,107 @@
cobie_type_classes = [
"IfcDoorStyle",
"IfcBuildingElementProxyType",
"IfcChimneyType",
"IfcCoveringType",
"IfcDoorType",
"IfcFootingType",
"IfcPileType",
"IfcRoofType",
"IfcShadingDeviceType",
"IfcWindowType",
"IfcDistributionControlElementType",
"IfcDistributionChamberElementType",
"IfcEnergyConversionDeviceType",
"IfcFlowControllerType",
"IfcFlowMovingDeviceType",
"IfcFlowStorageDeviceType",
"IfcFlowTerminalType",
"IfcFlowTreatmentDeviceType",
"IfcElementAssemblyType",
"IfcBuildingElementPartType",
"IfcDiscreteAccessoryType",
"IfcMechanicalFastenerType",
"IfcReinforcingElementType",
"IfcVibrationIsolatorType",
"IfcFurnishingElementType",
"IfcGeographicElementType",
"IfcTransportElementType",
"IfcSpatialZoneType",
"IfcWindowStyle",
]
cobie_component_classes = [
"IfcBuildingElementProxy",
"IfcChimney",
"IfcCovering",
"IfcDoor",
"IfcShadingDevice",
"IfcWindow",
"IfcDistributionControlElement",
"IfcDistributionChamberElement",
"IfcEnergyConversionDevice",
"IfcFlowController",
"IfcFlowMovingDevice",
"IfcFlowStorageDevice",
"IfcFlowTerminal",
"IfcFlowTreatmentDevice",
"IfcDiscreteAccessory",
"IfcTendon",
"IfcTendonAnchor",
"IfcVibrationIsolator",
"IfcFurnishingElement",
"IfcGeographicElement",
"IfcTransportElement",
]
fmhem_classes = [
"IfcDoorStyle",
"IfcWindowStyle",
"IfcDoorType",
"IfcWindowType",
"IfcRoofType",
"IfcShadingDeviceType",
"IfcDistributionControlElementType",
"IfcEnergyConversionDeviceType",
"IfcFlowControllerType",
"IfcJunctionBoxType",
"IfcFlowMovingDeviceType",
"IfcFlowStorageDeviceType",
"IfcFlowTerminalType",
"IfcFlowTreatmentDeviceType",
"IfcFurnishingElementType",
"IfcTransportElementType",
]
def get_cobie_types(ifc_file):
elements = []
for ifc_class in cobie_type_classes:
try:
elements += self.file.by_type(ifc_class)
except:
pass
return elements
def get_cobie_components(ifc_file):
elements = []
for ifc_class in cobie_component_classes:
try:
elements += self.file.by_type(ifc_class)
except:
pass
return elements
def get_fmhem_types(ifc_file):
elements = []
for ifc_class in fmhem_classes:
try:
if ifc_class == "IfcEnergyConversionDeviceType":
elements += [e for e in ifc_file.by_type(ifc_class) if not e.is_a("IfcCooledBeamType")]
else:
elements += ifc_file.by_type(ifc_class)
except:
pass
return elements
@@ -1,62 +1,8 @@
import ifcopenshell.util
import ifcopenshell.util.fm
import ifcopenshell.util.element
import lark
cobie_type_assets = [
"IfcDoorStyle",
"IfcBuildingElementProxyType",
"IfcChimneyType",
"IfcCoveringType",
"IfcDoorType",
"IfcFootingType",
"IfcPileType",
"IfcRoofType",
"IfcShadingDeviceType",
"IfcWindowType",
"IfcDistributionControlElementType",
"IfcDistributionChamberElementType",
"IfcEnergyConversionDeviceType",
"IfcFlowControllerType",
"IfcFlowMovingDeviceType",
"IfcFlowStorageDeviceType",
"IfcFlowTerminalType",
"IfcFlowTreatmentDeviceType",
"IfcElementAssemblyType",
"IfcBuildingElementPartType",
"IfcDiscreteAccessoryType",
"IfcMechanicalFastenerType",
"IfcReinforcingElementType",
"IfcVibrationIsolatorType",
"IfcFurnishingElementType",
"IfcGeographicElementType",
"IfcTransportElementType",
"IfcSpatialZoneType",
"IfcWindowStyle",
]
cobie_component_assets = [
"IfcBuildingElementProxy",
"IfcChimney",
"IfcCovering",
"IfcDoor",
"IfcShadingDevice",
"IfcWindow",
"IfcDistributionControlElement",
"IfcDistributionChamberElement",
"IfcEnergyConversionDevice",
"IfcFlowController",
"IfcFlowMovingDevice",
"IfcFlowStorageDevice",
"IfcFlowTerminal",
"IfcFlowTreatmentDevice",
"IfcDiscreteAccessory",
"IfcTendon",
"IfcTendonAnchor",
"IfcVibrationIsolator",
"IfcFurnishingElement",
"IfcGeographicElement",
"IfcTransportElement",
]
class Selector:
def parse(self, ifc_file, query):
@@ -174,19 +120,11 @@ class Selector:
def get_class_selector(self, class_selector):
if class_selector.children[0] == "COBie":
elements = []
for ifc_class in cobie_component_assets:
try:
elements += self.file.by_type(ifc_class)
except:
pass
ifcopenshell.util.fm.get_cobie_components(self.file)
elif class_selector.children[0] == "COBieType":
elements = []
for ifc_class in cobie_type_assets:
try:
elements += self.file.by_type(ifc_class)
except:
pass
ifcopenshell.util.fm.get_cobie_types(self.file)
elif class_selector.children[0] == "FMHEM":
ifcopenshell.util.fm.get_fmhem_types(self.file)
else:
elements = self.file.by_type(class_selector.children[0])
if len(class_selector.children) > 1 and class_selector.children[1].data == "filter":
@@ -34,7 +34,7 @@ class json_logger:
self.instance = instance
def log(self, level, message, *args, **kwargs):
self.statements.append(log_entry_type(level, message % args, kwargs.get('instance'))._asdict())
self.statements.append(log_entry_type(level, message % args, kwargs.get("instance"))._asdict())
def __getattr__(self, level):
return functools.partial(self.log, level, instance=self.instance)
@@ -75,7 +75,7 @@ def assert_valid(attr, val, schema):
while isinstance(attr_type, type_wrappers):
attr_type = attr_type.declared_type()
invalid = False
if isinstance(attr_type, simple_type):
@@ -120,10 +120,10 @@ def validate(f, logger):
numeric identifiers or invalidate entity names are not caught by this function. Some of these might have been
logged and can be retrieved by calling `ifcopenshell.get_log()`. A verification of the type, entity and global
WHERE rules is also not implemented.
For every entity instance in the model, it is checked that the entity is not abstract that every attribute value
is of the correct type and that the inverse attributes are of the correct cardinality.
Express simple types are checked for their valuation type. For select types it is asserted that the value conforms
to one of the leaves. For enumerations it is checked that the value is indeed on of the items. For aggregations it
is checked that the elements and the cardinality conforms. Type declarations (IfcInteger which is an integer) are
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+32 -7
View File
@@ -1,3 +1,28 @@
/********************************************************************************
* *
* This file is part of IfcOpenShell. *
* *
* IfcOpenShell is free software: you can redistribute it and/or modify *
* it under the terms of the Lesser GNU General Public License as published by *
* the Free Software Foundation, either version 3.0 of the License, or *
* (at your option) any later version. *
* *
* IfcOpenShell is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* Lesser GNU General Public License for more details. *
* *
* You should have received a copy of the Lesser GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
* *
********************************************************************************/
/********************************************************************************
* *
* This file has been generated from IFC4x3_RC2.exp. Do not make modifications *
* but instead modify the python script that has been used to generate this. *
* *
********************************************************************************/
#define SCHEMA_HAS_IfcAbsorbedDoseMeasure
#define SCHEMA_HAS_IfcAccelerationMeasure
@@ -262,6 +287,7 @@
#define SCHEMA_HAS_IfcOutletTypeEnum
#define SCHEMA_HAS_IfcPHMeasure
#define SCHEMA_HAS_IfcParameterValue
#define SCHEMA_HAS_IfcPavementTypeEnum
#define SCHEMA_HAS_IfcPerformanceHistoryTypeEnum
#define SCHEMA_HAS_IfcPermeableCoveringOperationEnum
#define SCHEMA_HAS_IfcPermitTypeEnum
@@ -507,12 +533,8 @@
#define SCHEMA_IfcAlignmentCantSegment_HAS_StartCantRight
#define SCHEMA_IfcAlignmentCantSegment_HAS_EndCantRight
#define SCHEMA_IfcAlignmentCantSegment_EndCantRight_IS_OPTIONAL
#define SCHEMA_IfcAlignmentCantSegment_HAS_SmoothingLength
#define SCHEMA_IfcAlignmentCantSegment_SmoothingLength_IS_OPTIONAL
#define SCHEMA_IfcAlignmentCantSegment_HAS_PredefinedType
#define SCHEMA_HAS_IfcAlignmentHorizontal
#define SCHEMA_IfcAlignmentHorizontal_HAS_StartDistAlong
#define SCHEMA_IfcAlignmentHorizontal_StartDistAlong_IS_OPTIONAL
#define SCHEMA_HAS_IfcAlignmentHorizontalSegment
#define SCHEMA_IfcAlignmentHorizontalSegment_HAS_StartPoint
#define SCHEMA_IfcAlignmentHorizontalSegment_HAS_StartDirection
@@ -1231,6 +1253,7 @@
#define SCHEMA_IfcDirectrixCurveSweptAreaSolid_StartParam_IS_OPTIONAL
#define SCHEMA_IfcDirectrixCurveSweptAreaSolid_HAS_EndParam
#define SCHEMA_IfcDirectrixCurveSweptAreaSolid_EndParam_IS_OPTIONAL
#define SCHEMA_HAS_IfcDirectrixDerivedReferenceSweptAreaSolid
#define SCHEMA_HAS_IfcDirectrixDistanceSweptAreaSolid
#define SCHEMA_IfcDirectrixDistanceSweptAreaSolid_HAS_Directrix
#define SCHEMA_IfcDirectrixDistanceSweptAreaSolid_HAS_StartDistance
@@ -2217,10 +2240,10 @@
#define SCHEMA_HAS_IfcPath
#define SCHEMA_IfcPath_HAS_EdgeList
#define SCHEMA_HAS_IfcPavement
#define SCHEMA_IfcPavement_HAS_Flexible
#define SCHEMA_IfcPavement_Flexible_IS_OPTIONAL
#define SCHEMA_IfcPavement_HAS_PredefinedType
#define SCHEMA_IfcPavement_PredefinedType_IS_OPTIONAL
#define SCHEMA_HAS_IfcPavementType
#define SCHEMA_IfcPavementType_HAS_Flexible
#define SCHEMA_IfcPavementType_HAS_PredefinedType
#define SCHEMA_HAS_IfcPcurve
#define SCHEMA_IfcPcurve_HAS_BasisSurface
#define SCHEMA_IfcPcurve_HAS_ReferenceCurve
@@ -3711,6 +3734,8 @@
#define SCHEMA_IfcThirdOrderPolynomialSpiral_QuadraticTerm_IS_OPTIONAL
#define SCHEMA_IfcThirdOrderPolynomialSpiral_HAS_LinearTerm
#define SCHEMA_IfcThirdOrderPolynomialSpiral_LinearTerm_IS_OPTIONAL
#define SCHEMA_IfcThirdOrderPolynomialSpiral_HAS_ConstantTerm
#define SCHEMA_IfcThirdOrderPolynomialSpiral_ConstantTerm_IS_OPTIONAL
#define SCHEMA_HAS_IfcTimePeriod
#define SCHEMA_IfcTimePeriod_HAS_StartTime
#define SCHEMA_IfcTimePeriod_HAS_EndTime
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
+8 -2
View File
@@ -1216,8 +1216,10 @@ void IfcEntityInstanceData::setArgument(size_t i, Argument* a, IfcUtil::Argument
// Remove leading and trailing '.'
enum_literal = enum_literal.substr(1, enum_literal.size() - 2);
const IfcParse::enumeration_type* enum_type = type()->as_entity()->
attribute_by_index(i)->type_of_attribute()->as_named_type()->declared_type()->as_enumeration_type();
const IfcParse::enumeration_type* enum_type = type()->as_enumeration_type()
? type()->as_enumeration_type()
: type()->as_entity()->attribute_by_index(i)->type_of_attribute()->
as_named_type()->declared_type()->as_enumeration_type();
std::vector<std::string>::const_iterator it = std::find(
enum_type->enumeration_items().begin(),
@@ -1792,6 +1794,10 @@ IfcUtil::IfcBaseClass* IfcFile::addEntity(IfcUtil::IfcBaseClass* entity, int id)
build_inverses_(new_entity);
}
// @todo the id isn't actually used here, but instead
// clears the entire inverse cache map.
mark_entity_as_modified(0);
return new_entity;
}
@@ -0,0 +1,31 @@
import ifcopenshell
import ifcopenshell.util.element
class Patcher:
def __init__(self, src, file, logger, args=None):
self.src = src
self.file = file
self.logger = logger
self.args = args
def patch(self):
curve_map = {}
for curve in self.file.by_type("IfcIndexedPolyCurve"):
if "IfcArcIndex" in [s.is_a() for s in curve.Segments]:
print("Could not convert curve due to arcs", curve)
continue
coordinates = curve.Points.CoordList
points = []
for i, segment in enumerate(curve.Segments):
segment = segment.wrappedValue
if i == 0:
points.append(self.file.createIfcCartesianPoint(coordinates[segment[0] - 1]))
points.append(self.file.createIfcCartesianPoint(coordinates[segment[1] - 1]))
polyline = self.file.create_entity("IfcPolyline", points)
curve_map[curve] = polyline
for curve, polyline in curve_map.items():
for inverse in self.file.get_inverse(curve):
ifcopenshell.util.element.replace_attribute(inverse, curve, polyline)
@@ -0,0 +1,21 @@
import ifcopenshell
import ifcopenshell.util.element
class Patcher:
def __init__(self, src, file, logger, args=None):
self.src = src
self.file = file
self.logger = logger
self.args = args
def patch(self):
tags = {}
for element in self.file.by_type("IfcTypeObject"):
original_element = tags.get(element.Tag, None)
if original_element:
for inverse in self.file.get_inverse(element):
ifcopenshell.util.element.replace_attribute(inverse, element, original_element)
self.file.remove(element)
else:
tags[element.Tag] = element
@@ -0,0 +1,27 @@
import ifcopenshell
class Patcher:
def __init__(self, src, file, logger, args=None):
self.src = src
self.file = file
self.logger = logger
self.args = args
def patch(self):
if self.args and self.args[0] == "DUPLICATE":
guids = set()
for element in self.file.by_type("IfcRoot"):
if element.GlobalId in guids:
element.GlobalId = ifcopenshell.guid.new()
elif len(element.GlobalId) != 22 or element.GlobalId[0] not in "0123":
element.GlobalId = ifcopenshell.guid.new()
else:
try:
ifcopenshell.guid.expand(element.GlobalId)
except:
element.GlobalId = ifcopenshell.guid.new()
guids.add(element.GlobalId)
else:
for element in self.file.by_type("IfcRoot"):
element.GlobalId = ifcopenshell.guid.new()
+1
View File
@@ -43,6 +43,7 @@ def nodes_index():
("ifc.get_property", "SvIfcGetProperty"),
("ifc.get_attribute", "SvIfcGetAttribute"),
("ifc.select_blender_objects", "SvIfcSelectBlenderObjects"),
("ifc.api", "SvIfcApi"),
],
)
]
+88
View File
@@ -0,0 +1,88 @@
import bpy
import ifcopenshell
import ifcopenshell.api
import ifcsverchok.helper
from bpy.props import StringProperty, EnumProperty
from sverchok.node_tree import SverchCustomTreeNode
from sverchok.data_structure import updateNode
def update_usecase(self, context):
module_usecase = self.get_module_usecase()
if module_usecase:
self.generate_node(*module_usecase)
class SvIfcTooltip(bpy.types.Operator):
bl_idname = "node.sv_ifc_tooltip"
bl_label = "IFC Info"
tooltip: bpy.props.StringProperty()
@classmethod
def description(cls, context, properties):
return properties.tooltip
def execute(self, context):
return {"FINISHED"}
class SvIfcApi(bpy.types.Node, SverchCustomTreeNode, ifcsverchok.helper.SvIfcCore):
bl_idname = "SvIfcApi"
bl_label = "IFC API"
tooltip: StringProperty(name="Tooltip")
usecase: StringProperty(name="Usecase", update=update_usecase)
def sv_init(self, context):
self.inputs.new("SvStringsSocket", "usecase").prop_name = "usecase"
self.outputs.new("SvVerticesSocket", "file")
def draw_buttons(self, context, layout):
op = layout.operator("node.sv_ifc_tooltip", text="", icon="QUESTION", emboss=False)
op.tooltip = self.tooltip
def process(self):
print("process")
module_usecase = self.get_module_usecase()
if module_usecase:
self.sv_input_names = [i.name for i in self.inputs]
super().process()
def get_module_usecase(self):
usecase = self.inputs["usecase"].sv_get()[0][0]
if usecase:
return usecase.split(".")
def generate_node(self, module, usecase):
try:
node_data = ifcopenshell.api.extract_docs(module, usecase)
except:
print("Node not yet implemented:", module, usecase)
return
while len(self.inputs) > 1:
self.inputs.remove(self.inputs[-1])
self.tooltip = ""
for name, data in node_data["inputs"].items():
setattr(SvIfcApi, name, StringProperty(name=name, update=updateNode))
self.inputs.new("SvStringsSocket", name).prop_name = name
if "default" in data:
self.tooltip = f"{name} ({data['default']}): {data['description']}\n"
else:
self.tooltip = f"{name}: {data['description']}\n"
self.tooltip = self.tooltip.strip()
def process_ifc(self, usecase, *setting_values):
if usecase:
settings = dict(zip(self.sv_input_names[1:], setting_values))
settings = {k: v for k, v in settings.items() if v != ""}
self.outputs["file"].sv_set([ifcopenshell.api.run(usecase, **settings)])
def register():
bpy.utils.register_class(SvIfcTooltip)
bpy.utils.register_class(SvIfcApi)
def unregister():
bpy.utils.unregister_class(SvIfcApi)
bpy.utils.unregister_class(SvIfcTooltip)