mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-24 22:06:48 +00:00
Compare commits
75 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 52f15d27b1 | |||
| cfa0575263 | |||
| f5e089e0fd | |||
| 5d246a8716 | |||
| 1f241912f2 | |||
| f7765c531b | |||
| 3b401ec28f | |||
| 77206cb761 | |||
| f8e9f81b9b | |||
| 186c4dd118 | |||
| aa9b6b61fa | |||
| 6337d04efc | |||
| c00bcc78e9 | |||
| 07f19ad80a | |||
| 8ca6183313 | |||
| 492df6845b | |||
| 43f8d8547d | |||
| 6e3c72707d | |||
| 08d6c0a2d8 | |||
| 8c1eff5c26 | |||
| fe3ffb7771 | |||
| 6660714308 | |||
| 2420ad2fa7 | |||
| 16fd06a4e9 | |||
| beb5dda6dd | |||
| c04afe1a4b | |||
| d2a1e0193b | |||
| f2f4057c98 | |||
| 347aae9696 | |||
| 1a2d0d702b | |||
| e791449d3d | |||
| 8071e079b3 | |||
| 1c71af058f | |||
| c4f3633ab8 | |||
| c40fd91730 | |||
| a7252273b4 | |||
| 6e0a443cca | |||
| 4574887709 | |||
| f14d349be0 | |||
| 023eb719f5 | |||
| 8ba1b1c9b4 | |||
| 7cb163efef | |||
| e68c57e9ef | |||
| 7d17b3170c | |||
| a968f2b04d | |||
| 9a14406f92 | |||
| 32062e4085 | |||
| 8da17cf4a0 | |||
| 2d6e941c41 | |||
| 0fffbac519 | |||
| db62faa5ae | |||
| 02dc7b5274 | |||
| 1bf136e93c | |||
| cccab9e751 | |||
| 99e74533c3 | |||
| 0f39b09396 | |||
| ac2324a65f | |||
| ca85927a51 | |||
| e3786423c2 | |||
| 8d352ea07a | |||
| fd04066940 | |||
| d34956c2c7 | |||
| ad2585c571 | |||
| a08cf23d63 | |||
| 37fe04ffd2 | |||
| 248b21eab3 | |||
| 63f578315d | |||
| 19b6765994 | |||
| 7456c270ff | |||
| b480b43435 | |||
| 3614587556 | |||
| 86ce367ba8 | |||
| d320c1dac1 | |||
| cf48661a35 | |||
| 5e03846783 |
@@ -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
|
||||
|
||||
@@ -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
@@ -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.
|
||||
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
+99
-33
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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)));
|
||||
|
||||
@@ -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.
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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);
|
||||
|
||||
@@ -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):
|
||||
|
||||
Submodule src/ifcopenshell-python/ifcopenshell/mvd updated: 2f0ce53d56...667894b629
@@ -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
@@ -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
|
||||
|
||||
+2031
-1981
File diff suppressed because it is too large
Load Diff
+586
-25
File diff suppressed because it is too large
Load Diff
+69
-25
File diff suppressed because one or more lines are too long
@@ -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()
|
||||
@@ -43,6 +43,7 @@ def nodes_index():
|
||||
("ifc.get_property", "SvIfcGetProperty"),
|
||||
("ifc.get_attribute", "SvIfcGetAttribute"),
|
||||
("ifc.select_blender_objects", "SvIfcSelectBlenderObjects"),
|
||||
("ifc.api", "SvIfcApi"),
|
||||
],
|
||||
)
|
||||
]
|
||||
|
||||
@@ -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)
|
||||
Reference in New Issue
Block a user