mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-26 06:46:47 +00:00
Compare commits
50 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 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,6 +29,7 @@ if bpy is not None:
|
||||
"cost": None,
|
||||
"sequence": None,
|
||||
"group": None,
|
||||
"system": None,
|
||||
"structural": None,
|
||||
"boundary": None,
|
||||
"material": 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"]:
|
||||
|
||||
@@ -159,8 +159,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 +208,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 +236,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 +332,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()
|
||||
|
||||
@@ -546,12 +556,21 @@ 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 +584,24 @@ 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 = []
|
||||
for element in self.file.by_type("IfcElement"):
|
||||
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"))
|
||||
|
||||
@@ -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=""
|
||||
|
||||
@@ -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,7 +3,8 @@ 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,
|
||||
|
||||
@@ -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
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from bpy.app.handlers import persistent
|
||||
|
||||
@@ -51,17 +51,17 @@ 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_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,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
|
||||
)
|
||||
|
||||
+34
-15
@@ -31,7 +31,7 @@ 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)
|
||||
@@ -43,7 +43,7 @@ def mode_callback(obj, data):
|
||||
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 +53,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 +75,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 +99,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,15 +135,17 @@ 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)
|
||||
@@ -165,4 +182,6 @@ 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
|
||||
)
|
||||
@@ -36,21 +36,6 @@ def mode_callback(obj, data):
|
||||
IfcStore.edited_objs.add(obj)
|
||||
|
||||
|
||||
class AddWall(bpy.types.Operator):
|
||||
bl_idname = "bim.add_wall"
|
||||
bl_label = "Add Wall"
|
||||
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))
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class JoinWall(bpy.types.Operator):
|
||||
bl_idname = "bim.join_wall"
|
||||
bl_label = "Join Wall"
|
||||
@@ -65,7 +50,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 +318,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 +348,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 +453,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 +482,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,73 @@ 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", "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", "X")]}),
|
||||
("bim.hotkey", {"type": "C", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "C")]}),
|
||||
("bim.hotkey", {"type": "V", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "V")]}),
|
||||
)
|
||||
|
||||
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.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")
|
||||
|
||||
if props.ifc_class == "IfcWallType":
|
||||
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="Align")
|
||||
|
||||
|
||||
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_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_E(self):
|
||||
if self.props.ifc_class == "IfcWallType":
|
||||
bpy.ops.bim.join_wall(join_type="T")
|
||||
|
||||
def hotkey_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_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")
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -265,13 +265,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)
|
||||
|
||||
@@ -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()
|
||||
@@ -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)));
|
||||
|
||||
@@ -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 not isinstance(parameter.default, object):
|
||||
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":
|
||||
|
||||
@@ -37,12 +37,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):
|
||||
|
||||
@@ -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"):
|
||||
|
||||
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)
|
||||
@@ -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,65 @@
|
||||
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
|
||||
#from blenderbim.bim.module.root.prop import getIfcClasses, getIfcProducts, refreshClasses, refreshPredefinedTypes
|
||||
|
||||
|
||||
class SvIfcApi(bpy.types.Node, SverchCustomTreeNode, ifcsverchok.helper.SvIfcCore):
|
||||
bl_idname = "SvIfcApi"
|
||||
bl_label = "IFC API"
|
||||
usecase: StringProperty(name="Usecase", update=updateNode)
|
||||
#schema: StringProperty(name="schema", update=updateNode, default="IFC4")
|
||||
#ifc_product: EnumProperty(items=getIfcProducts, name="Products", update=refreshClasses)
|
||||
#ifc_class: EnumProperty(items=getIfcClasses, name="Class", update=refreshPredefinedTypes)
|
||||
#custom_ifc_class: StringProperty(name="Custom Ifc Class", update=updateNode)
|
||||
|
||||
def sv_init(self, context):
|
||||
self.inputs.new("SvStringsSocket", "usecase").prop_name = "usecase"
|
||||
#self.inputs.new("SvStringsSocket", "schema").prop_name = "schema"
|
||||
#self.inputs.new("SvStringsSocket", "ifc_product").prop_name = "ifc_product"
|
||||
#self.inputs.new("SvStringsSocket", "ifc_class").prop_name = "ifc_class"
|
||||
#self.inputs.new("SvStringsSocket", "custom_ifc_class").prop_name = "custom_ifc_class"
|
||||
self.outputs.new("SvVerticesSocket", "file")
|
||||
|
||||
def process(self):
|
||||
print('process')
|
||||
#self.sv_input_names = ["file", "ifc_product", "ifc_class", "custom_ifc_class"]
|
||||
usecase = self.inputs["usecase"].sv_get()[0][0]
|
||||
if usecase:
|
||||
self.generate_node(*usecase.split("."))
|
||||
self.sv_input_names = ["usecase"]
|
||||
super().process()
|
||||
|
||||
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])
|
||||
|
||||
for name, data in node_data["inputs"].items():
|
||||
setattr(SvIfcApi, name, StringProperty(name=name))
|
||||
self.inputs.new("SvStringsSocket", name).prop_name = name
|
||||
|
||||
#def process_ifc(self, file, ifc_product, ifc_class, custom_ifc_class):
|
||||
def process_ifc(self, usecase):
|
||||
print('run')
|
||||
#self.outputs["file"].sv_set([ifcopenshell.api.run("project.create_file", version=schema)])
|
||||
#if custom_ifc_class:
|
||||
# self.outputs["entity"].sv_set([file.by_type(custom_ifc_class)])
|
||||
#else:
|
||||
# self.outputs["entity"].sv_set([file.by_type(ifc_class)])
|
||||
|
||||
|
||||
def register():
|
||||
bpy.utils.register_class(SvIfcApi)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_class(SvIfcApi)
|
||||
Reference in New Issue
Block a user