mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-09 05:46:51 +00:00
Merge branch 'v0.8.0' of https://github.com/IfcOpenShell/IfcOpenShell into v0.8.0
This commit is contained in:
@@ -195,19 +195,20 @@ endif
|
||||
cd build/blenderbim/bim/data/gantt/ && wget https://raw.githubusercontent.com/jsGanttImproved/jsgantt-improved/master/dist/jsgantt.css
|
||||
|
||||
# Provides IFCJSON functionality
|
||||
cd build && wget https://github.com/IFCJSON-Team/IFC2JSON_python/archive/master.zip
|
||||
cd build && unzip master.zip && rm master.zip
|
||||
# TODO: replace with main repo if https://github.com/IFCJSON-Team/IFC2JSON_python/pull/3 is merged.
|
||||
cd build && wget -O ifc2json.zip https://github.com/Moult/IFC2JSON_python/archive/refs/heads/feature-ios-v0.8.0.zip
|
||||
cd build && unzip ifc2json.zip && rm ifc2json.zip
|
||||
# IFCJSON doesn't have pyproject.toml, so we use python command.
|
||||
cd build && . env/$(VENV_ACTIVATE) && cd IFC2JSON_python-master/file_converters && \
|
||||
cd build && . env/$(VENV_ACTIVATE) && cd IFC2JSON_python-*/file_converters && \
|
||||
$(PYTHON) -c "from setuptools import setup; \
|
||||
setup( \
|
||||
name='ifcjson', \
|
||||
version='0.0.0', \
|
||||
version='0.0.1', \
|
||||
author='Jan Brouwer', \
|
||||
author_email='jan@brewsky.nl', \
|
||||
packages=['ifcjson'], \
|
||||
)" bdist_wheel
|
||||
cp -r build/IFC2JSON_python-master/file_converters/dist/*.whl build/wheels/
|
||||
cp -r build/IFC2JSON_python-*/file_converters/dist/*.whl build/wheels/
|
||||
|
||||
# Brickschema requires pkg_resources which is provided by Blender.
|
||||
# Provides Brickschema functionality
|
||||
|
||||
@@ -121,6 +121,7 @@ classes = [
|
||||
prop.StrProperty,
|
||||
operator.BIM_OT_enum_property_search, # /!\ Register AFTER prop.StrProperty
|
||||
prop.ObjProperty,
|
||||
prop.MultipleFileSelect,
|
||||
prop.Attribute,
|
||||
prop.BIMAreaProperties,
|
||||
prop.BIMTabProperties,
|
||||
@@ -133,7 +134,6 @@ classes = [
|
||||
prop.BIMMeshProperties,
|
||||
prop.BIMFacet,
|
||||
prop.BIMFilterGroup,
|
||||
prop.MultipleFileSelect,
|
||||
ui.BIM_UL_clipping_plane,
|
||||
ui.BIM_UL_generic,
|
||||
ui.BIM_UL_topics,
|
||||
|
||||
@@ -2,7 +2,9 @@ import sys
|
||||
import os
|
||||
import webbrowser
|
||||
|
||||
blenderbim_lib_path = os.environ.get("blenderbim_lib_path")
|
||||
blenderbim_lib_path = os.environ.get("BLENDERBIM_LIB_PATH")
|
||||
blenderbim_version = os.environ.get("BLENDERBIM_VERSION")
|
||||
|
||||
if blenderbim_lib_path:
|
||||
sys.path.insert(0, blenderbim_lib_path)
|
||||
|
||||
@@ -52,9 +54,16 @@ class WebNamespace(socketio.AsyncNamespace):
|
||||
"web_operator",
|
||||
data,
|
||||
namespace="/blender",
|
||||
room=data["blenderId"],
|
||||
room=data.get("blenderId", None),
|
||||
)
|
||||
|
||||
async def on_get_svg(self, sid, data):
|
||||
print("hello world!")
|
||||
file_path = data["path"]
|
||||
with open(file_path, "r") as file:
|
||||
svg_data = file.read()
|
||||
await sio.emit("svg_data", svg_data, room=sid, namespace="/web")
|
||||
|
||||
async def send_cached_messages(self, sid):
|
||||
# Send cached messages to the connected web client
|
||||
for blenderId, messages in blender_messages.items():
|
||||
@@ -98,6 +107,12 @@ class BlenderNamespace(socketio.AsyncNamespace):
|
||||
blender_messages[sid]["gantt_data"] = data
|
||||
await sio.emit("gantt_data", {"blenderId": sid, "data": data}, namespace="/web")
|
||||
|
||||
async def on_drawings_data(self, sid, data):
|
||||
print(f"Drawings directory from Blender client {sid}")
|
||||
print(data)
|
||||
blender_messages[sid]["drwings_data"] = data
|
||||
await sio.emit("drawings_data", {"blenderId": sid, "data": data}, namespace="/web")
|
||||
|
||||
|
||||
# Attach namespaces
|
||||
sio.register_namespace(WebNamespace("/web"))
|
||||
@@ -108,26 +123,45 @@ sio.register_namespace(BlenderNamespace("/blender"))
|
||||
async def index(request):
|
||||
with open("templates/index.html", "r") as f:
|
||||
template = f.read()
|
||||
html_content = pystache.render(template, {"port": sio_port})
|
||||
html_content = pystache.render(template, {"port": sio_port, "version": blenderbim_version})
|
||||
return web.Response(text=html_content, content_type="text/html")
|
||||
|
||||
|
||||
async def gantt(request):
|
||||
with open("templates/gantt.html", "r") as f:
|
||||
template = f.read()
|
||||
html_content = pystache.render(template, {"port": sio_port, "version": blenderbim_version})
|
||||
return web.Response(text=html_content, content_type="text/html")
|
||||
|
||||
|
||||
async def drawings(request):
|
||||
with open("templates/drawings.html", "r") as f:
|
||||
template = f.read()
|
||||
html_content = pystache.render(template, {"port": sio_port})
|
||||
return web.Response(text=html_content, content_type="text/html")
|
||||
|
||||
|
||||
async def open_web_browser(app):
|
||||
webbrowser.open(f"http://127.0.0.1:{sio_port}/")
|
||||
async def on_startup(app):
|
||||
pid_file = "running_pid.json"
|
||||
|
||||
if os.path.exists(pid_file):
|
||||
with open(pid_file, "r") as f:
|
||||
pids = json.load(f)
|
||||
else:
|
||||
pids = {}
|
||||
|
||||
pids[str(os.getpid())] = sio_port
|
||||
|
||||
with open(pid_file, "w") as f:
|
||||
json.dump(pids, f, indent=4)
|
||||
|
||||
|
||||
app.router.add_get("/", index)
|
||||
app.router.add_get("/drawings", drawings)
|
||||
app.router.add_get("/gantt", gantt)
|
||||
app.router.add_static("/jsgantt/", path="../gantt", name="jsgantt")
|
||||
app.router.add_static("/static/", path="./static", name="static")
|
||||
app.on_startup.append(open_web_browser)
|
||||
app.on_startup.append(on_startup)
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
@@ -0,0 +1,249 @@
|
||||
:root {
|
||||
--font-family: Arial, sans-serif;
|
||||
--base-font-size: 16px;
|
||||
--margin-tiny: 0.125rem;
|
||||
--margin-small: 0.625rem;
|
||||
--margin-medium: 1.25rem;
|
||||
--margin-large: 2.5rem;
|
||||
--padding-tiny: 0.125rem;
|
||||
--padding-small: 0.625rem;
|
||||
--padding-medium: 1rem;
|
||||
--font-size-large: 1.2rem;
|
||||
--logo-height: 2.5rem;
|
||||
--nav-height: 2.5rem;
|
||||
}
|
||||
|
||||
:root.dark {
|
||||
color-scheme: dark;
|
||||
--bg-color: #252525;
|
||||
--text-color: #e0e0e0;
|
||||
--nav-bg-color: #121212;
|
||||
--nav-border-color: #25682a;
|
||||
--nav-link-color: #fff;
|
||||
--nav-link-hover-color: #3fb449;
|
||||
--warning-color: #FFDB8F;
|
||||
--border-color: #464444;
|
||||
}
|
||||
|
||||
:root.light {
|
||||
color-scheme: light;
|
||||
--bg-color: #ffffff;
|
||||
--text-color: #000000;
|
||||
--nav-bg-color: #f8f8f8;
|
||||
--nav-border-color: #cccccc;
|
||||
--nav-link-color: #000000;
|
||||
--nav-link-hover-color: #38a63d;
|
||||
--warning-color: #FF4500;
|
||||
--border-color: #222;
|
||||
}
|
||||
|
||||
html {
|
||||
font-size: var(--base-font-size);
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--bg-color);
|
||||
color: var(--text-color);
|
||||
margin: 0;
|
||||
font-family: var(--font-family);
|
||||
}
|
||||
|
||||
#container {
|
||||
margin-top: var(--margin-medium);
|
||||
margin-left: var(--margin-small);
|
||||
margin-right: var(--margin-small);
|
||||
margin-bottom: var(--margin-medium);
|
||||
height: calc(100vh - var(--nav-height));
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
nav {
|
||||
background-color: var(--nav-bg-color);
|
||||
height: var(--nav-height);
|
||||
padding: var(--padding-medium) 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
border-bottom: 2px solid var(--nav-border-color);
|
||||
}
|
||||
|
||||
nav .logo {
|
||||
margin-left: var(--margin-large);
|
||||
height: var(--logo-height);
|
||||
}
|
||||
|
||||
nav ul {
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
margin-left: 0.0625rem;
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
nav ul li {
|
||||
margin-right: 3.125rem;
|
||||
}
|
||||
|
||||
nav ul li a {
|
||||
text-decoration: none;
|
||||
color: var(--nav-link-color);
|
||||
font-size: var(--font-size-large);
|
||||
}
|
||||
|
||||
nav ul li a:hover,
|
||||
nav ul li a.active {
|
||||
color: var(--nav-link-hover-color);
|
||||
}
|
||||
|
||||
.warning {
|
||||
color: var(--warning-color);
|
||||
margin-bottom: var(--margin-small);
|
||||
padding: var(--padding-tiny);
|
||||
display: none;
|
||||
}
|
||||
|
||||
#toggle-theme {
|
||||
border: none;
|
||||
background: none;
|
||||
cursor: pointer;
|
||||
font-size: var(--font-size-large);
|
||||
margin-right: var(--margin-medium);
|
||||
}
|
||||
|
||||
#toggle-theme:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
#client-list {
|
||||
position: absolute;
|
||||
top: calc(0 + var(--nav-height));
|
||||
overflow-y: auto;
|
||||
overflow: hidden;
|
||||
transition: opacity 0.3s ease-out, visibility 0.3s ease-out;
|
||||
border: 1px solid var(--table-border-color);
|
||||
background-color: var(--nav-bg-color);
|
||||
z-index: 1;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
#client-list.show {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
#connected-list-div {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.client {
|
||||
padding: var(--margin-small);
|
||||
border-bottom: 1px solid var(--table-border-color);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.client-details {
|
||||
max-height: 0;
|
||||
overflow: hidden;
|
||||
padding-left: var(--padding-small);
|
||||
transition: max-height 0.2s ease-out, padding 0.2s ease-out, opacity 0.1s ease-out;
|
||||
background-color: var(--nav-bg-color);
|
||||
margin-top: var(--margin-tiny);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.client-details.show {
|
||||
max-height: none;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.client-detail {
|
||||
border-bottom: 1px solid var(--table-border-color);
|
||||
padding: var(--padding-small);
|
||||
}
|
||||
|
||||
#show-connected-button {
|
||||
width: auto;
|
||||
background-color: var(--bg-color);
|
||||
border: none;
|
||||
font-size: var(--base-font-size);
|
||||
}
|
||||
|
||||
#show-connected-button:hover,
|
||||
.scroll-button:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.scroll-button {
|
||||
font-size: var(--base-font-size);
|
||||
margin-left: var(--margin-tiny);
|
||||
margin-top: var(--margin-small);
|
||||
}
|
||||
|
||||
|
||||
.left {
|
||||
flex: 1;
|
||||
background-color: var(--bg-color);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.right {
|
||||
flex: 4;
|
||||
background-color: var(--bg-color);
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
#svg-container {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.panel {
|
||||
background-color: var(--nav-bg-color);
|
||||
border: 1px solid var(--border-color);
|
||||
padding: 10px;
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.card-header,
|
||||
.card-body {
|
||||
background-color: var(--bg-color);
|
||||
border: 1px solid var(--border-color);
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.card {
|
||||
background-color: var(--nav-bg-color);
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
li.svg-name {
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
#dropdown-menu:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/* remove bootstrap css confilcts */
|
||||
*,
|
||||
::after,
|
||||
::before {
|
||||
box-sizing: unset;
|
||||
}
|
||||
|
||||
.btn,
|
||||
.btn:hover {
|
||||
color: var(--nav-link-hover-color);
|
||||
}
|
||||
@@ -1,13 +1,17 @@
|
||||
:root {
|
||||
--font-family: Arial, sans-serif;
|
||||
--base-font-size: 16px;
|
||||
--margin-tiny: 0.125rem;
|
||||
--margin-small: 0.625rem;
|
||||
--margin-medium: 1.25rem;
|
||||
--margin-large: 2.5rem;
|
||||
--padding-small: 0.125rem;
|
||||
--padding-tiny: 0.125rem;
|
||||
--padding-small: 0.625rem;
|
||||
--padding-medium: 1rem;
|
||||
--font-size-small: 1rem;
|
||||
--font-size-large: 1.2rem;
|
||||
--logo-height: 2.5rem;
|
||||
--nav-height: 2.5rem;
|
||||
--folder-collapse-font-size: 0.75rem;
|
||||
--folder-collapse-font-family: Courier, "Courier New", monospace;
|
||||
--box-shadow: 0 0 0.7rem #5f5f5f66;
|
||||
@@ -31,6 +35,7 @@
|
||||
--group-item-bg-color: #4b4b4b;
|
||||
--input-bg-color: #3b3b3b;
|
||||
--input-border-color: #000;
|
||||
--details-border-color: #464444;
|
||||
}
|
||||
|
||||
:root.light {
|
||||
@@ -42,6 +47,7 @@
|
||||
--nav-link-color: #000000;
|
||||
--nav-link-hover-color: #38a63d;
|
||||
--warning-color: #FF4500;
|
||||
--details-border-color: #222;
|
||||
}
|
||||
|
||||
html {
|
||||
@@ -53,9 +59,13 @@ body {
|
||||
color: var(--primary-text-color);
|
||||
margin: 0;
|
||||
font-family: var(--font-family);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
#container {
|
||||
flex: 1;
|
||||
margin-top: var(--margin-medium);
|
||||
margin-left: var(--margin-small);
|
||||
margin-right: var(--margin-small);
|
||||
@@ -107,7 +117,7 @@ nav ul li a.active {
|
||||
.warning {
|
||||
color: var(--warning-color);
|
||||
margin-bottom: var(--margin-small);
|
||||
padding: var(--padding-small);
|
||||
padding: var(--padding-tiny);
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -141,6 +151,87 @@ nav ul li a.active {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
#client-list {
|
||||
position: absolute;
|
||||
top: calc(0 + var(--nav-height));
|
||||
overflow-y: auto;
|
||||
overflow: hidden;
|
||||
transition: opacity 0.3s ease-out, visibility 0.3s ease-out;
|
||||
border: 1px solid var(--table-border-color);
|
||||
background-color: var(--nav-bg-color);
|
||||
z-index: 1;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
#client-list.show {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
#connected-list-div {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.client {
|
||||
padding: var(--margin-small);
|
||||
border-bottom: 1px solid var(--table-border-color);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.client-details {
|
||||
max-height: 0;
|
||||
overflow: hidden;
|
||||
padding-left: var(--padding-small);
|
||||
transition: max-height 0.2s ease-out, padding 0.2s ease-out, opacity 0.1s ease-out;
|
||||
background-color: var(--nav-bg-color);
|
||||
margin-top: var(--margin-tiny);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.client-details.show {
|
||||
max-height: none;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.client-detail {
|
||||
border-bottom: 1px solid var(--table-border-color);
|
||||
padding: var(--padding-small);
|
||||
}
|
||||
|
||||
#show-connected-button {
|
||||
width: auto;
|
||||
background-color: var(--bg-color);
|
||||
border: none;
|
||||
font-size: var(--base-font-size);
|
||||
}
|
||||
|
||||
#show-connected-button:hover,
|
||||
.scroll-button:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.scroll-button {
|
||||
font-size: var(--base-font-size);
|
||||
margin-left: var(--margin-tiny);
|
||||
margin-top: var(--margin-small);
|
||||
}
|
||||
|
||||
footer {
|
||||
background-color: var(--nav-bg-color);
|
||||
text-align: right;
|
||||
padding: var(--padding-tiny);
|
||||
border-top: 1px solid var(--nav-border-color);
|
||||
}
|
||||
|
||||
footer p {
|
||||
margin: 0;
|
||||
color: var(--text-color);
|
||||
margin-right: var(--margin-small);
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.gantt-info {
|
||||
margin: 0.5rem;
|
||||
font-size: var(--font-size-small);
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
:root {
|
||||
--font-family: Arial, sans-serif;
|
||||
--base-font-size: 16px;
|
||||
--margin-tiny: 0.125rem;
|
||||
--margin-small: 0.625rem;
|
||||
--margin-medium: 1.25rem;
|
||||
--margin-large: 2.5rem;
|
||||
--padding-small: 0.125rem;
|
||||
--padding-tiny: 0.125rem;
|
||||
--padding-small: 0.625rem;
|
||||
--padding-medium: 1rem;
|
||||
--font-size-large: 1.2rem;
|
||||
--logo-height: 2.5rem;
|
||||
--nav-height: 2.5rem;
|
||||
}
|
||||
|
||||
:root.dark {
|
||||
@@ -18,6 +22,7 @@
|
||||
--nav-link-color: #fff;
|
||||
--nav-link-hover-color: #3fb449;
|
||||
--warning-color: #FFDB8F;
|
||||
--table-border-color: #464444;
|
||||
}
|
||||
|
||||
:root.light {
|
||||
@@ -29,6 +34,7 @@
|
||||
--nav-link-color: #000000;
|
||||
--nav-link-hover-color: #38a63d;
|
||||
--warning-color: #FF4500;
|
||||
--table-border-color: #222;
|
||||
}
|
||||
|
||||
html {
|
||||
@@ -40,29 +46,26 @@ body {
|
||||
color: var(--text-color);
|
||||
margin: 0;
|
||||
font-family: var(--font-family);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
#container {
|
||||
flex: 1;
|
||||
margin-top: var(--margin-medium);
|
||||
margin-left: var(--margin-small);
|
||||
margin-right: var(--margin-small);
|
||||
margin-bottom: var(--margin-medium);
|
||||
}
|
||||
|
||||
.table-container {
|
||||
margin-bottom: var(--margin-large);
|
||||
}
|
||||
|
||||
.csv-table {
|
||||
margin-top: var(--margin-small);
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
nav {
|
||||
background-color: var(--nav-bg-color);
|
||||
height: var(--nav-height);
|
||||
padding: var(--padding-medium) 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -102,7 +105,7 @@ nav ul li a.active {
|
||||
.warning {
|
||||
color: var(--warning-color);
|
||||
margin-bottom: var(--margin-small);
|
||||
padding: var(--padding-small);
|
||||
padding: var(--padding-tiny);
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -118,6 +121,95 @@ nav ul li a.active {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
#client-list {
|
||||
position: absolute;
|
||||
top: calc(0 + var(--nav-height));
|
||||
overflow-y: auto;
|
||||
overflow: hidden;
|
||||
transition: opacity 0.3s ease-out, visibility 0.3s ease-out;
|
||||
border: 1px solid var(--table-border-color);
|
||||
background-color: var(--nav-bg-color);
|
||||
z-index: 1;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
#client-list.show {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
#connected-list-div {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.client {
|
||||
padding: var(--margin-small);
|
||||
border-bottom: 1px solid var(--table-border-color);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.client-details {
|
||||
max-height: 0;
|
||||
overflow: hidden;
|
||||
padding-left: var(--padding-small);
|
||||
transition: max-height 0.2s ease-out, padding 0.2s ease-out, opacity 0.1s ease-out;
|
||||
background-color: var(--nav-bg-color);
|
||||
margin-top: var(--margin-tiny);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.client-details.show {
|
||||
max-height: none;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.client-detail {
|
||||
border-bottom: 1px solid var(--table-border-color);
|
||||
padding: var(--padding-small);
|
||||
}
|
||||
|
||||
#show-connected-button {
|
||||
width: auto;
|
||||
background-color: var(--bg-color);
|
||||
border: none;
|
||||
font-size: var(--base-font-size);
|
||||
}
|
||||
|
||||
#show-connected-button:hover,
|
||||
.scroll-button:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.scroll-button {
|
||||
font-size: var(--base-font-size);
|
||||
margin-left: var(--margin-tiny);
|
||||
margin-top: var(--margin-small);
|
||||
}
|
||||
|
||||
footer {
|
||||
background-color: var(--nav-bg-color);
|
||||
text-align: right;
|
||||
padding: var(--padding-tiny);
|
||||
border-top: 1px solid var(--nav-border-color);
|
||||
}
|
||||
|
||||
footer p {
|
||||
margin: 0;
|
||||
color: var(--text-color);
|
||||
margin-right: var(--margin-small);
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
margin-bottom: var(--margin-large);
|
||||
}
|
||||
|
||||
.csv-table {
|
||||
margin-top: var(--margin-small);
|
||||
}
|
||||
|
||||
/* ------------ Overwriting Tabulator CSS rules ------------ */
|
||||
|
||||
:root.light .tabulator-header,
|
||||
@@ -127,6 +219,6 @@ nav ul li a.active {
|
||||
}
|
||||
|
||||
:root.light .tabulator {
|
||||
border-top: 1px solid #222;
|
||||
border-bottom: 2px solid #222;
|
||||
border-top: 1px solid var(--table-border-color);
|
||||
border-bottom: 2px solid var(--table-border-color);
|
||||
}
|
||||
@@ -0,0 +1,263 @@
|
||||
// keeps track of blenders connected
|
||||
const connectedClients = {};
|
||||
const allDrawings = {};
|
||||
let socket;
|
||||
|
||||
// Document ready function
|
||||
$(document).ready(function () {
|
||||
var systemTheme = window.matchMedia("(prefers-color-scheme: light)").matches
|
||||
? "light"
|
||||
: "dark";
|
||||
var theme = localStorage.getItem("theme") || systemTheme;
|
||||
setTheme(theme);
|
||||
|
||||
$("#dropdown-menu").change(function () {
|
||||
const blenderId = $(this).attr("id");
|
||||
const ifcFile = $(this).val();
|
||||
displayDrawingsNames(blenderId, ifcFile);
|
||||
});
|
||||
|
||||
connectSocket();
|
||||
});
|
||||
|
||||
// Function to connect to Socket.IO server
|
||||
function connectSocket() {
|
||||
const url = "ws://localhost:" + SOCKET_PORT + "/web";
|
||||
socket = io(url);
|
||||
console.log("socket: ", socket);
|
||||
|
||||
// Register socket event handlers
|
||||
socket.on("blender_connect", handleBlenderConnect);
|
||||
socket.on("blender_disconnect", handleBlenderDisconnect);
|
||||
socket.on("connect", handleWebConnect);
|
||||
socket.on("drawings_data", handleDrawingsData);
|
||||
socket.on("svg_data", handleSvgData);
|
||||
// socket.on("default_data", handleDefaultData);
|
||||
}
|
||||
|
||||
// function used to get drawings data from blenderbim
|
||||
function handleWebConnect() {
|
||||
const msg = {
|
||||
sourcePage: "drawings",
|
||||
operator: {
|
||||
type: "getDrawings",
|
||||
},
|
||||
};
|
||||
socket.emit("web_operator", msg);
|
||||
}
|
||||
|
||||
// Function to handle 'blender_connect' event
|
||||
function handleBlenderConnect(blenderId) {
|
||||
console.log("blender_connect: ", blenderId);
|
||||
if (!connectedClients.hasOwnProperty(blenderId)) {
|
||||
connectedClients[blenderId] = {
|
||||
shown: false,
|
||||
ifc_file: "",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Function to handle 'blender_disconnect' event
|
||||
function handleBlenderDisconnect(blenderId) {
|
||||
console.log("blender_disconnect: ", blenderId);
|
||||
if (connectedClients.hasOwnProperty(blenderId)) {
|
||||
delete connectedClients[blenderId];
|
||||
// remove(blenderId);
|
||||
}
|
||||
|
||||
$("#blender-count").text(function (i, text) {
|
||||
return parseInt(text, 10) - 1;
|
||||
});
|
||||
}
|
||||
|
||||
function handleDrawingsData(data) {
|
||||
const blenderId = data["blenderId"];
|
||||
|
||||
console.log(data);
|
||||
|
||||
const filename = data["data"]["ifc_file"];
|
||||
const drawings = data["data"]["drawings_data"]["drawings"];
|
||||
const sheets = data["data"]["drawings_data"]["sheets"];
|
||||
|
||||
allDrawings[filename] = { drawings: drawings, sheets: sheets };
|
||||
|
||||
console.log(connectedClients);
|
||||
|
||||
if (connectedClients.hasOwnProperty(blenderId)) {
|
||||
if (!connectedClients[blenderId].shown) {
|
||||
connectedClients[blenderId] = {
|
||||
shown: true,
|
||||
ifc_file: filename,
|
||||
};
|
||||
addSelectOption(blenderId, filename);
|
||||
} else {
|
||||
// update(blenderId, data, filename);
|
||||
}
|
||||
} else {
|
||||
connectedClients[blenderId] = {
|
||||
shown: true,
|
||||
ifc_file: filename,
|
||||
};
|
||||
addSelectOption(blenderId, filename);
|
||||
}
|
||||
}
|
||||
|
||||
function handleSvgData(data) {
|
||||
const svgContainer = document.getElementById("svg-container");
|
||||
svgContainer.innerHTML = "";
|
||||
svgContainer.style.backgroundColor = "white";
|
||||
|
||||
const dom = SVG(svgContainer).svg(data);
|
||||
const svgElement = dom.node.children[0];
|
||||
|
||||
// to make the svg not grow outside the svg container
|
||||
svgElement.setAttribute("width", "100%");
|
||||
svgElement.setAttribute("height", "100%");
|
||||
svgElement.style.maxWidth = "100%";
|
||||
svgElement.style.maxHeight = "100%";
|
||||
svgElement.style.width = "100%";
|
||||
svgElement.style.height = "100%";
|
||||
svgElement.style.boxSizing = "border-box"; // Ensure padding and borders are included in the element's total width and height
|
||||
|
||||
const panZoomControls = svgPanZoom(svgElement, {
|
||||
zoomEnabled: true,
|
||||
controlIconsEnabled: true,
|
||||
fit: true,
|
||||
center: true,
|
||||
});
|
||||
}
|
||||
|
||||
function addSelectOption(blenderId, filename) {
|
||||
const filenameOption = $("<option></option>")
|
||||
.attr("id", "blender-" + blenderId)
|
||||
.val(filename)
|
||||
.text(filename);
|
||||
$("#dropdown-menu").append(filenameOption);
|
||||
}
|
||||
|
||||
function displayDrawingsNames(blenderId, ifcFile) {
|
||||
drawings = allDrawings[ifcFile].drawings;
|
||||
sheets = allDrawings[ifcFile].sheets;
|
||||
|
||||
function createSvgNames(text, index, type) {
|
||||
var label = $("<li></lio>")
|
||||
.text(text)
|
||||
.attr("id", type + "-" + index)
|
||||
.addClass("svg-name")
|
||||
.click(function () {
|
||||
const id = $(this).attr("id").split("-");
|
||||
const index = parseInt(id[1]);
|
||||
const type = id[0];
|
||||
const ifcFile = $("#dropdown-menu").val();
|
||||
|
||||
var path = "";
|
||||
|
||||
if (type === "drawing") {
|
||||
path = allDrawings[ifcFile].drawings[index].path;
|
||||
} else if (type === "sheet") {
|
||||
path = allDrawings[ifcFile].sheets[index].path;
|
||||
}
|
||||
|
||||
const msg = {
|
||||
path: path,
|
||||
};
|
||||
console.log(msg);
|
||||
socket.emit("get_svg", msg);
|
||||
});
|
||||
|
||||
if (type === "drawing") $("#drawings-sub-panel").append(label);
|
||||
else if (type === "sheet") $("#sheets-sub-panel").append(label);
|
||||
}
|
||||
|
||||
$("#drawings-sub-panel").empty();
|
||||
drawings.forEach(function (drawing, index) {
|
||||
createSvgNames(drawing.name, index, "drawing");
|
||||
});
|
||||
|
||||
$("#sheets-sub-panel").empty();
|
||||
sheets.forEach(function (sheet, index) {
|
||||
createSvgNames(sheet.name, index, "sheet");
|
||||
});
|
||||
}
|
||||
|
||||
function setTheme(theme) {
|
||||
if (theme === "light") {
|
||||
$("html").removeClass("dark").addClass("light");
|
||||
$("#toggle-theme").html('<i class="fas fa-sun"></i>');
|
||||
} else {
|
||||
$("html").removeClass("light").addClass("dark");
|
||||
$("#toggle-theme").html('<i class="fas fa-moon"></i>');
|
||||
}
|
||||
localStorage.setItem("theme", theme);
|
||||
}
|
||||
|
||||
function toggleTheme() {
|
||||
if ($("html").hasClass("dark")) {
|
||||
setTheme("light");
|
||||
} else {
|
||||
setTheme("dark");
|
||||
}
|
||||
}
|
||||
|
||||
function toggleClientList() {
|
||||
var clientList = $("#client-list");
|
||||
|
||||
if (clientList.hasClass("show")) {
|
||||
clientList.removeClass("show");
|
||||
return;
|
||||
}
|
||||
|
||||
clientList.empty();
|
||||
|
||||
$.each(connectedClients, function (id, client) {
|
||||
if (!client.shown) return;
|
||||
|
||||
const dropdownIcon = $("<i>")
|
||||
.addClass("fas fa-chevron-down")
|
||||
.css("margin-left", "0.5rem");
|
||||
|
||||
const clientDiv = $("<div>").addClass("client").text(client.ifc_file);
|
||||
|
||||
clientDiv.append(dropdownIcon);
|
||||
|
||||
const clientDetailsDiv = $("<div>").addClass("client-details");
|
||||
|
||||
if (id) {
|
||||
const clientId = $("<div>")
|
||||
.addClass("client-detail")
|
||||
.text(`Blender ID: ${id}`);
|
||||
clientDetailsDiv.append(clientId);
|
||||
}
|
||||
|
||||
if (client.headers && client.headers.length) {
|
||||
const clientHeaders = $("<div>")
|
||||
.addClass("client-detail")
|
||||
.text(`Table Headers: ${client.headers.join(", ")}`);
|
||||
|
||||
const scrollButton = $("<button>")
|
||||
.addClass("scroll-button")
|
||||
.text("Scroll to Table")
|
||||
.on("click", function () {
|
||||
$("html, body").animate(
|
||||
{
|
||||
scrollTop: $("#table-" + id).offset().top,
|
||||
},
|
||||
600
|
||||
);
|
||||
clientList.removeClass("show");
|
||||
});
|
||||
|
||||
clientDetailsDiv.append(clientHeaders);
|
||||
clientDetailsDiv.append(scrollButton);
|
||||
}
|
||||
|
||||
clientDiv.append(clientDetailsDiv);
|
||||
|
||||
clientDiv.on("click", function () {
|
||||
clientDetailsDiv.toggleClass("show");
|
||||
});
|
||||
|
||||
clientList.append(clientDiv);
|
||||
});
|
||||
clientList.addClass("show");
|
||||
}
|
||||
@@ -60,6 +60,10 @@ function handleBlenderDisconnect(blenderId) {
|
||||
delete connectedClients[blenderId];
|
||||
removeGanttElement(blenderId);
|
||||
}
|
||||
|
||||
$("#blender-count").text(function (i, text) {
|
||||
return parseInt(text, 10) - 1;
|
||||
});
|
||||
}
|
||||
|
||||
// Function to handle 'gantt_data' event
|
||||
@@ -77,6 +81,7 @@ function handleGanttData(data) {
|
||||
if (!connectedClients[blenderId].shown) {
|
||||
connectedClients[blenderId] = {
|
||||
shown: true,
|
||||
ifc_file: filename,
|
||||
ganttTasks: ganttTasks,
|
||||
workSchedule: ganttWorkSched,
|
||||
};
|
||||
@@ -89,6 +94,7 @@ function handleGanttData(data) {
|
||||
} else {
|
||||
connectedClients[blenderId] = {
|
||||
shown: true,
|
||||
ifc_file: filename,
|
||||
ganttTasks: ganttTasks,
|
||||
workSchedule: ganttWorkSched,
|
||||
};
|
||||
@@ -105,6 +111,10 @@ function handleDefaultData(data) {
|
||||
|
||||
// Function to add a new gantt with data and filename
|
||||
function addGanttElement(blenderId, tasks, workSched, filename) {
|
||||
$("#blender-count").text(function (i, text) {
|
||||
return parseInt(text, 10) + 1;
|
||||
});
|
||||
|
||||
const ganttContainer = $("<div></div>")
|
||||
.addClass("gantt-container")
|
||||
.attr("id", "container-" + blenderId);
|
||||
@@ -382,3 +392,75 @@ function toggleTheme() {
|
||||
setTheme("dark");
|
||||
}
|
||||
}
|
||||
|
||||
function toggleClientList() {
|
||||
var clientList = $("#client-list");
|
||||
|
||||
if (clientList.hasClass("show")) {
|
||||
clientList.removeClass("show");
|
||||
return;
|
||||
}
|
||||
|
||||
clientList.empty();
|
||||
|
||||
$.each(connectedClients, function (id, client) {
|
||||
if (!client.shown) return;
|
||||
|
||||
const dropdownIcon = $("<i>")
|
||||
.addClass("fas fa-chevron-down")
|
||||
.css("margin-left", "0.5rem");
|
||||
|
||||
const clientDiv = $("<div>").addClass("client").text(client.ifc_file);
|
||||
|
||||
clientDiv.append(dropdownIcon);
|
||||
|
||||
const clientDetailsDiv = $("<div>").addClass("client-details");
|
||||
|
||||
if (id) {
|
||||
const clientId = $("<div>")
|
||||
.addClass("client-detail")
|
||||
.text(`Blender ID: ${id}`);
|
||||
clientDetailsDiv.append(clientId);
|
||||
}
|
||||
|
||||
if (client.workSchedule && client.gantt) {
|
||||
const clientScheduleName = $("<div>")
|
||||
.addClass("client-detail")
|
||||
.text(`Schedule Name: ${client.workSchedule.Name}`);
|
||||
|
||||
const clientScheduleDate = $("<div>")
|
||||
.addClass("client-detail")
|
||||
.text(
|
||||
`Schedule Date: ${new Date(
|
||||
client.workSchedule.CreationDate
|
||||
).toLocaleDateString()}`
|
||||
);
|
||||
|
||||
const scrollButton = $("<button>")
|
||||
.addClass("scroll-button")
|
||||
.text("Scroll to Gantt Chart")
|
||||
.on("click", function () {
|
||||
$("html, body").animate(
|
||||
{
|
||||
scrollTop: $("#gantt-" + id).offset().top,
|
||||
},
|
||||
600
|
||||
);
|
||||
clientList.removeClass("show");
|
||||
});
|
||||
|
||||
clientDetailsDiv.append(clientScheduleName);
|
||||
clientDetailsDiv.append(clientScheduleDate);
|
||||
clientDetailsDiv.append(scrollButton);
|
||||
}
|
||||
|
||||
clientDiv.append(clientDetailsDiv);
|
||||
|
||||
clientDiv.on("click", function () {
|
||||
clientDetailsDiv.toggleClass("show");
|
||||
});
|
||||
|
||||
clientList.append(clientDiv);
|
||||
});
|
||||
clientList.addClass("show");
|
||||
}
|
||||
|
||||
@@ -42,6 +42,10 @@ function handleBlenderDisconnect(blenderId) {
|
||||
delete connectedClients[blenderId];
|
||||
removeTableElement(blenderId);
|
||||
}
|
||||
|
||||
$("#blender-count").text(function (i, text) {
|
||||
return parseInt(text, 10) - 1;
|
||||
});
|
||||
}
|
||||
|
||||
// Function to handle 'csv_data' event
|
||||
@@ -81,6 +85,10 @@ function handleDefaultData(data) {
|
||||
|
||||
// Function to add a new table with data and filename
|
||||
function addTableElement(blenderId, csvData, filename) {
|
||||
$("#blender-count").text(function (i, text) {
|
||||
return parseInt(text, 10) + 1;
|
||||
});
|
||||
|
||||
// store headers of the csv data
|
||||
const firstLine = csvData.indexOf("\n");
|
||||
const csvHeaders = csvData.substring(0, firstLine).split(",");
|
||||
@@ -274,3 +282,66 @@ function toggleTheme() {
|
||||
setTheme("dark");
|
||||
}
|
||||
}
|
||||
|
||||
function toggleClientList() {
|
||||
var clientList = $("#client-list");
|
||||
|
||||
if (clientList.hasClass("show")) {
|
||||
clientList.removeClass("show");
|
||||
return;
|
||||
}
|
||||
|
||||
clientList.empty();
|
||||
|
||||
$.each(connectedClients, function (id, client) {
|
||||
if (!client.shown) return;
|
||||
|
||||
const dropdownIcon = $("<i>")
|
||||
.addClass("fas fa-chevron-down")
|
||||
.css("margin-left", "0.5rem");
|
||||
|
||||
const clientDiv = $("<div>").addClass("client").text(client.ifc_file);
|
||||
|
||||
clientDiv.append(dropdownIcon);
|
||||
|
||||
const clientDetailsDiv = $("<div>").addClass("client-details");
|
||||
|
||||
if (id) {
|
||||
const clientId = $("<div>")
|
||||
.addClass("client-detail")
|
||||
.text(`Blender ID: ${id}`);
|
||||
clientDetailsDiv.append(clientId);
|
||||
}
|
||||
|
||||
if (client.headers && client.headers.length) {
|
||||
const clientHeaders = $("<div>")
|
||||
.addClass("client-detail")
|
||||
.text(`Table Headers: ${client.headers.join(", ")}`);
|
||||
|
||||
const scrollButton = $("<button>")
|
||||
.addClass("scroll-button")
|
||||
.text("Scroll to Table")
|
||||
.on("click", function () {
|
||||
$("html, body").animate(
|
||||
{
|
||||
scrollTop: $("#table-" + id).offset().top,
|
||||
},
|
||||
600
|
||||
);
|
||||
clientList.removeClass("show");
|
||||
});
|
||||
|
||||
clientDetailsDiv.append(clientHeaders);
|
||||
clientDetailsDiv.append(scrollButton);
|
||||
}
|
||||
|
||||
clientDiv.append(clientDetailsDiv);
|
||||
|
||||
clientDiv.on("click", function () {
|
||||
clientDetailsDiv.toggleClass("show");
|
||||
});
|
||||
|
||||
clientList.append(clientDiv);
|
||||
});
|
||||
clientList.addClass("show");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="dark">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Web Client</title>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css"
|
||||
/>
|
||||
<link rel="stylesheet" href="/static/css/drawings.css" />
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.6.0/css/all.min.css"
|
||||
/>
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="https://code.jquery.com/jquery-3.6.0.min.js"
|
||||
></script>
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="https://cdnjs.cloudflare.com/ajax/libs/svg.js/3.1.1/svg.min.js"
|
||||
></script>
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="https://cdn.jsdelivr.net/npm/svg-pan-zoom@3.6.1/dist/svg-pan-zoom.min.js"
|
||||
></script>
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"
|
||||
></script>
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="https://cdn.socket.io/4.0.0/socket.io.min.js"
|
||||
></script>
|
||||
<script>
|
||||
var SOCKET_PORT = {{port}};
|
||||
</script>
|
||||
<script defer src="./static/js/drawings.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<nav>
|
||||
<img
|
||||
src="https://blenderbim.org/assets/images/blender/blender-logo.png"
|
||||
alt="Logo"
|
||||
class="logo"
|
||||
/>
|
||||
<ul>
|
||||
<li><a href="/">IFC Data</a></li>
|
||||
<li><a href="/gantt">Gantt Chart</a></li>
|
||||
<li><a href="/drawings" class="active">Drawings</a></li>
|
||||
</ul>
|
||||
<button id="toggle-theme" onclick="toggleTheme()">
|
||||
<i class="fas fa-moon"></i>
|
||||
</button>
|
||||
</nav>
|
||||
<div id="connected-list-div">
|
||||
<button id="show-connected-button" onclick="toggleClientList()">
|
||||
Connected Blenders:
|
||||
<span id="blender-count">0</span>
|
||||
<i class="fas fa-chevron-down"></i>
|
||||
</button>
|
||||
<div id="client-list"></div>
|
||||
</div>
|
||||
<div class="container-fluid d-flex" id="container">
|
||||
<div class="col-2 left">
|
||||
<!-- Dropdown menu -->
|
||||
<div class="dropdown mb-3">
|
||||
<label for="dropdown-menu">IFC File:</label>
|
||||
<select class="p-0 pl-2" id="dropdown-menu">
|
||||
<!-- IFC files will be dynamically populated here -->
|
||||
<option value="" selected disabled hidden>IFC File</option>
|
||||
</select>
|
||||
</div>
|
||||
<!-- Panel with sub-panels -->
|
||||
<div class="panel">
|
||||
<div id="accordion">
|
||||
<div class="card pb-2">
|
||||
<div class="card-header overflow-hidden" id="headingOne">
|
||||
<button
|
||||
class="btn p-0 w-100 text-left"
|
||||
data-toggle="collapse"
|
||||
data-target="#collapseOne"
|
||||
>
|
||||
Drawings
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div id="collapseOne" class="collapse show">
|
||||
<div class="card-body">
|
||||
<ul class="sub-panel pl-2" id="drawings-sub-panel">
|
||||
<!-- Drawings will be dynamically populated here -->
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card pb-2">
|
||||
<div class="card-header overflow-hidden" id="headingTwo">
|
||||
<button
|
||||
class="btn p-0 w-100 text-left"
|
||||
data-toggle="collapse"
|
||||
data-target="#collapseTwo"
|
||||
>
|
||||
Sheets
|
||||
</button>
|
||||
</div>
|
||||
<div id="collapseTwo" class="collapse show">
|
||||
<div class="card-body">
|
||||
<ul class="sub-panel pl-2" id="sheets-sub-panel">
|
||||
<!-- Sheets will be dynamically populated here -->
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-10 right pl-3">
|
||||
<div id="svg-container"></div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -34,11 +34,23 @@
|
||||
<ul>
|
||||
<li><a href="/">IFC Data</a></li>
|
||||
<li><a href="/gantt" class="active">Gantt Chart</a></li>
|
||||
<li><a href="/drawings">Drawings</a></li>
|
||||
</ul>
|
||||
<button id="toggle-theme" onclick="toggleTheme()">
|
||||
<i class="fas fa-moon"></i>
|
||||
</button>
|
||||
</nav>
|
||||
<div id="connected-list-div">
|
||||
<button id="show-connected-button" onclick="toggleClientList()">
|
||||
Connected Blenders:
|
||||
<span id="blender-count">0</span>
|
||||
<i class="fas fa-chevron-down"></i>
|
||||
</button>
|
||||
<div id="client-list"></div>
|
||||
</div>
|
||||
<div id="container"></div>
|
||||
<footer>
|
||||
<p>BlenderBIM Version: {{version}}</p>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -41,11 +41,23 @@
|
||||
<ul>
|
||||
<li><a href="/" class="active">IFC Data</a></li>
|
||||
<li><a href="/gantt">Gantt Chart</a></li>
|
||||
<li><a href="/drawings">Drawings</a></li>
|
||||
</ul>
|
||||
<button id="toggle-theme" onclick="toggleTheme()">
|
||||
<i class="fas fa-moon"></i>
|
||||
</button>
|
||||
</nav>
|
||||
<div id="connected-list-div">
|
||||
<button id="show-connected-button" onclick="toggleClientList()">
|
||||
Connected Blenders:
|
||||
<span id="blender-count">0</span>
|
||||
<i class="fas fa-chevron-down"></i>
|
||||
</button>
|
||||
<div id="client-list"></div>
|
||||
</div>
|
||||
<div id="container"></div>
|
||||
<footer>
|
||||
<p>BlenderBIM Version: {{version}}</p>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -170,6 +170,11 @@ def subscribe_to(obj, data_path, callback):
|
||||
|
||||
|
||||
def refresh_ui_data():
|
||||
"""Refresh cached UI data.
|
||||
|
||||
Note that calling non-ifc-operators by itself doesn't refresh the UI data
|
||||
and it need to be refreshed manually if needed.
|
||||
"""
|
||||
from blenderbim.bim import modules
|
||||
|
||||
for name, value in modules.items():
|
||||
|
||||
@@ -50,6 +50,8 @@ def draw_attribute(attribute, layout, copy_operator=None):
|
||||
return
|
||||
if value_name == "enum_value":
|
||||
prop_with_search(layout, attribute, "enum_value", text=attribute.name)
|
||||
elif value_name == "filepath_value":
|
||||
attribute.filepath_value.layout_file_select(layout, filter_glob=attribute.filter_glob, text=attribute.name)
|
||||
elif attribute.name in ["ScheduleDuration", "ActualDuration", "FreeFloat", "TotalFloat"]:
|
||||
propis = bpy.context.scene.BIMWorkScheduleProperties
|
||||
for item in propis.durations_attributes:
|
||||
|
||||
@@ -167,7 +167,7 @@ class BIM_OT_add_aggregate(bpy.types.Operator, tool.Ifc.Operator):
|
||||
tool.Ifc,
|
||||
tool.Collector,
|
||||
tool.Spatial,
|
||||
structure_obj=tool.Ifc.get_object(current_container),
|
||||
container=current_container,
|
||||
element_obj=aggregate,
|
||||
)
|
||||
core.assign_object(tool.Ifc, tool.Aggregate, tool.Collector, relating_obj=aggregate, related_obj=obj)
|
||||
|
||||
@@ -21,6 +21,7 @@ import json
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.guid
|
||||
import ifcopenshell.util.element
|
||||
import blenderbim.bim.helper
|
||||
import blenderbim.bim.handler
|
||||
import blenderbim.tool as tool
|
||||
@@ -36,7 +37,7 @@ class EnableEditingAttributes(bpy.types.Operator):
|
||||
|
||||
def execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
obj = bpy.data.objects.get(self.obj)
|
||||
obj = bpy.data.objects[self.obj]
|
||||
props = obj.BIMAttributeProperties
|
||||
props.attributes.clear()
|
||||
|
||||
|
||||
@@ -1472,12 +1472,9 @@ class ActivateModel(bpy.types.Operator):
|
||||
|
||||
CutDecorator.uninstall()
|
||||
|
||||
# save current visibility statuses for Views and Types collections
|
||||
# save current visibility statuses
|
||||
visibility_status: dict[bpy.types.Object, bool] = {}
|
||||
for col in bpy.data.collections["Views"].children:
|
||||
for obj in col.objects:
|
||||
visibility_status[obj] = obj.hide_get()
|
||||
for obj in bpy.data.collections["Types"].objects:
|
||||
for obj in bpy.data.objects:
|
||||
visibility_status[obj] = obj.hide_get()
|
||||
|
||||
if not bpy.app.background:
|
||||
@@ -1508,6 +1505,7 @@ class ActivateModel(bpy.types.Operator):
|
||||
obj.hide_set(hide_status)
|
||||
|
||||
tool.Blender.update_viewport()
|
||||
blenderbim.bim.handler.refresh_ui_data()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
|
||||
@@ -116,7 +116,7 @@ class GeoreferenceDecorator:
|
||||
arc_mid = angle_half @ arc_start
|
||||
self.draw_text_at_position(context, f"{self.tn_angle}deg", arc_mid)
|
||||
|
||||
if (wcs := GeoreferenceData.data["world_coordinate_system"]) and ["has_transformation"]:
|
||||
if (wcs := GeoreferenceData.data["world_coordinate_system"]) and wcs["has_transformation"]:
|
||||
text = "WCS"
|
||||
if props.has_blender_offset:
|
||||
text += f"\nBlender Coordinates ({GeoreferenceData.data['local_unit_symbol']}) X: {wcs['blender_x']}, Y: {wcs['blender_y']}, Z: {wcs['blender_z']}"
|
||||
@@ -281,7 +281,7 @@ class GeoreferenceDecorator:
|
||||
verts, edges = arc_segments
|
||||
self.draw_batch("LINES", verts, decorator_color_special, edges)
|
||||
|
||||
if (wcs := GeoreferenceData.data["world_coordinate_system"]) and ["has_transformation"]:
|
||||
if (wcs := GeoreferenceData.data["world_coordinate_system"]) and wcs["has_transformation"]:
|
||||
if wcs["blender_location"].length < 1000:
|
||||
verts = [Vector((0, 0, 0)), wcs["blender_location"]]
|
||||
edges = [[0, 1]]
|
||||
|
||||
@@ -143,7 +143,6 @@ classes = (
|
||||
stair.EnableEditingStair,
|
||||
stair.RemoveStair,
|
||||
pie.OpenPieClass,
|
||||
pie.PieUpdateContainer,
|
||||
pie.PieAddOpening,
|
||||
pie.VIEW3D_MT_PIE_bim,
|
||||
pie.VIEW3D_MT_PIE_bim_class,
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
import bpy
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.element
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.core.covering as core
|
||||
|
||||
@@ -31,9 +32,7 @@ class AddInstanceFlooringCoveringFromCursor(bpy.types.Operator, tool.Ifc.Operato
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
relating_type_id = int(bpy.data.scenes["Scene"].BIMModelProperties.relating_type_id)
|
||||
relating_type = ifcopenshell.util.element.get_predefined_type(tool.Ifc.get().by_id(relating_type_id))
|
||||
return relating_type == "FLOORING"
|
||||
return tool.Covering.covering_poll_relating_type_check(cls, context, "FLOORING")
|
||||
|
||||
def _execute(self, context):
|
||||
try:
|
||||
@@ -50,9 +49,7 @@ class AddInstanceCeilingCoveringFromCursor(bpy.types.Operator, tool.Ifc.Operator
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
relating_type_id = int(bpy.data.scenes["Scene"].BIMModelProperties.relating_type_id)
|
||||
relating_type = ifcopenshell.util.element.get_predefined_type(tool.Ifc.get().by_id(relating_type_id))
|
||||
return relating_type == "CEILING"
|
||||
return tool.Covering.covering_poll_relating_type_check(cls, context, "CEILING")
|
||||
|
||||
def _execute(self, context):
|
||||
try:
|
||||
@@ -69,8 +66,10 @@ class RegenSelectedCoveringObject(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
element = tool.Ifc.get_entity(bpy.context.active_object)
|
||||
return element and element.is_a("IfcCovering")
|
||||
if (obj := context.active_object) and (element := tool.Ifc.get_entity(obj)) and element.is_a("IfcCovering"):
|
||||
return True
|
||||
cls.poll_message_set("IfcCovering must be selected.")
|
||||
return False
|
||||
|
||||
def _execute(self, context):
|
||||
try:
|
||||
@@ -89,11 +88,7 @@ class AddInstanceFlooringCoveringsFromWalls(bpy.types.Operator, tool.Ifc.Operato
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
element = tool.Ifc.get_entity(bpy.context.active_object)
|
||||
relating_type_id = int(bpy.data.scenes["Scene"].BIMModelProperties.relating_type_id)
|
||||
relating_type = ifcopenshell.util.element.get_predefined_type(tool.Ifc.get().by_id(relating_type_id))
|
||||
if element and element.is_a("IfcWall") and tool.Model.get_usage_type(element) == "LAYER2":
|
||||
return context.selected_objects and relating_type == "FLOORING"
|
||||
return tool.Covering.covering_poll_wall_selected(cls, context, "FLOORING")
|
||||
|
||||
def _execute(self, context):
|
||||
# This only works based on a 2D plan only considering the standard
|
||||
@@ -117,11 +112,7 @@ class AddInstanceCeilingCoveringsFromWalls(bpy.types.Operator, tool.Ifc.Operator
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
element = tool.Ifc.get_entity(bpy.context.active_object)
|
||||
relating_type_id = int(bpy.data.scenes["Scene"].BIMModelProperties.relating_type_id)
|
||||
relating_type = ifcopenshell.util.element.get_predefined_type(tool.Ifc.get().by_id(relating_type_id))
|
||||
if element and element.is_a("IfcWall") and tool.Model.get_usage_type(element) == "LAYER2":
|
||||
return context.selected_objects and relating_type == "CEILING"
|
||||
return tool.Covering.covering_poll_wall_selected(cls, context, "CEILING")
|
||||
|
||||
def _execute(self, context):
|
||||
# This only works based on a 2D plan only considering the standard
|
||||
|
||||
@@ -63,28 +63,6 @@ class PieAddOpening(bpy.types.Operator):
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class PieUpdateContainer(bpy.types.Operator):
|
||||
bl_idname = "bim.pie_update_container"
|
||||
bl_label = "Update Spatial Container"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
for obj in context.selected_objects:
|
||||
if not obj.BIMObjectProperties.ifc_definition_id:
|
||||
continue
|
||||
for collection in obj.users_collection:
|
||||
spatial_obj = collection.BIMCollectionProperties.obj
|
||||
if spatial_obj and spatial_obj.BIMObjectProperties.ifc_definition_id:
|
||||
blenderbim.core.spatial.assign_container(
|
||||
tool.Ifc, tool.Collector, tool.Spatial, structure_obj=spatial_obj, element_obj=obj
|
||||
)
|
||||
break
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class VIEW3D_MT_PIE_bim(bpy.types.Menu):
|
||||
bl_label = "Geometry"
|
||||
|
||||
@@ -93,7 +71,6 @@ class VIEW3D_MT_PIE_bim(bpy.types.Menu):
|
||||
pie.operator("bim.edit_object_placement")
|
||||
pie.operator("bim.update_representation").ifc_representation_class = ""
|
||||
pie.operator("bim.pie_add_opening")
|
||||
pie.operator("bim.pie_update_container")
|
||||
pie.operator("bim.open_pie_class", text="Assign IFC Class")
|
||||
pie.operator("bim.aggregate_assign_object", text="Assign Aggregation")
|
||||
pie.operator("bim.aggregate_unassign_object", text="Unassign Aggregation")
|
||||
|
||||
@@ -225,9 +225,8 @@ class AddConstrTypeInstance(bpy.types.Operator):
|
||||
else:
|
||||
parent = ifcopenshell.util.element.get_container(building_element)
|
||||
if parent:
|
||||
parent_obj = tool.Ifc.get_object(parent)
|
||||
blenderbim.core.spatial.assign_container(
|
||||
tool.Ifc, tool.Collector, tool.Spatial, structure_obj=parent_obj, element_obj=obj
|
||||
tool.Ifc, tool.Collector, tool.Spatial, container=parent, element_obj=obj
|
||||
)
|
||||
|
||||
# set occurrences properties for the types defined with modifiers
|
||||
|
||||
@@ -122,9 +122,14 @@ class UpdateIfcPatchArguments(bpy.types.Operator):
|
||||
new_attr = patch_args.add()
|
||||
data_type = arg_info.get("type", "str")
|
||||
if isinstance(data_type, list):
|
||||
if "file" in data_type:
|
||||
data_type = ["file"]
|
||||
|
||||
data_type = [dt for dt in data_type if dt != "NoneType"][0]
|
||||
|
||||
new_attr.data_type = {
|
||||
"Literal": "enum",
|
||||
"file": "file",
|
||||
"str": "string",
|
||||
"float": "float",
|
||||
"int": "integer",
|
||||
@@ -136,6 +141,11 @@ class UpdateIfcPatchArguments(bpy.types.Operator):
|
||||
new_attr.enum_value = arg_info.get("default", new_attr.get_value_default())
|
||||
continue
|
||||
|
||||
if new_attr.data_type == "file":
|
||||
new_attr.filepath_value.single_file = arg_info.get("default", new_attr.get_value_default())
|
||||
new_attr.filter_glob = arg_info.get("filter_glob", "*.ifc;*.ifczip;*.ifcxml")
|
||||
continue
|
||||
|
||||
new_attr.set_value(arg_info.get("default", new_attr.get_value_default()))
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -28,16 +28,15 @@ classes = (
|
||||
operator.BIM_OT_load_clipping_planes,
|
||||
operator.BIM_OT_save_clipping_planes,
|
||||
operator.ChangeLibraryElement,
|
||||
operator.ClearRecentIFCProjects,
|
||||
operator.CreateClippingPlane,
|
||||
operator.CreateProject,
|
||||
operator.ClearRecentIFCProjects,
|
||||
operator.DisableCulling,
|
||||
operator.DisableEditingHeader,
|
||||
operator.EditHeader,
|
||||
operator.EnableCulling,
|
||||
operator.EnableEditingHeader,
|
||||
operator.ExportIFC,
|
||||
operator.ExportIFCDeprecated,
|
||||
operator.FlipClippingPlane,
|
||||
operator.LinkIfc,
|
||||
operator.LoadLink,
|
||||
@@ -92,6 +91,7 @@ def register():
|
||||
bpy.utils.register_tool(workspace.ExploreTool, after={"builtin.transform"}, separator=True, group=False)
|
||||
bpy.types.Scene.BIMProjectProperties = bpy.props.PointerProperty(type=prop.BIMProjectProperties)
|
||||
bpy.app.handlers.load_post.append(decorator.toggle_decorations_on_load)
|
||||
bpy.types.TOPBAR_MT_file_import.append(ui.file_import_menu)
|
||||
bpy.types.TOPBAR_MT_file.prepend(ui.file_menu)
|
||||
bpy.types.TOPBAR_MT_file_context_menu.prepend(ui.file_menu)
|
||||
wm = bpy.context.window_manager
|
||||
|
||||
@@ -730,12 +730,12 @@ class LoadProject(bpy.types.Operator, IFCFileSelector):
|
||||
if not self.is_existing_ifc_file():
|
||||
return {"FINISHED"}
|
||||
|
||||
if tool.Blender.is_default_scene():
|
||||
if self.should_start_fresh_session and tool.Blender.is_default_scene():
|
||||
for obj in bpy.data.objects:
|
||||
bpy.data.objects.remove(obj)
|
||||
|
||||
filepath = Path(self.get_filepath())
|
||||
context.scene.BIMProperties.ifc_file = str(filepath)
|
||||
context.scene.BIMProperties.ifc_file = filepath.as_posix()
|
||||
context.scene.BIMProjectProperties.is_loading = True
|
||||
context.scene.BIMProjectProperties.total_elements = len(tool.Ifc.get().by_type("IfcElement"))
|
||||
tool.Blender.register_toolbar()
|
||||
@@ -743,6 +743,8 @@ class LoadProject(bpy.types.Operator, IFCFileSelector):
|
||||
|
||||
if not self.is_advanced:
|
||||
bpy.ops.bim.load_project_elements()
|
||||
if not self.should_start_fresh_session:
|
||||
bpy.ops.bim.convert_to_blender()
|
||||
except:
|
||||
blenderbim.last_error = traceback.format_exc()
|
||||
raise
|
||||
@@ -1235,6 +1237,10 @@ class ExportIFCBase:
|
||||
if bpy.data.is_saved:
|
||||
layout.prop(self, "use_relative_path")
|
||||
|
||||
layout.separator()
|
||||
layout.label(text="Supported formats for export:")
|
||||
layout.label(text=".ifc, .ifczip, .ifcjson")
|
||||
|
||||
def invoke(self, context, event):
|
||||
if not tool.Ifc.get():
|
||||
bpy.ops.wm.save_mainfile("INVOKE_DEFAULT")
|
||||
@@ -1333,18 +1339,6 @@ class ExportIFC(ExportIFCBase, bpy.types.Operator):
|
||||
pass
|
||||
|
||||
|
||||
# TODO: remove as deprecated, better wait couple releases since
|
||||
# this operator is used for saving IFC files in user scripts.
|
||||
class ExportIFCDeprecated(ExportIFCBase, bpy.types.Operator):
|
||||
bl_idname = "export_ifc.bim"
|
||||
|
||||
def execute(self, context):
|
||||
msg = f"'{ExportIFCDeprecated.bl_idname}' operator name is deprecated, use '{ExportIFC.bl_idname}'."
|
||||
self.report({"WARNING"}, msg)
|
||||
print(msg)
|
||||
return super().execute(context)
|
||||
|
||||
|
||||
class LoadLinkedProject(bpy.types.Operator):
|
||||
bl_idname = "bim.load_linked_project"
|
||||
bl_label = "Load a project for viewing only."
|
||||
|
||||
@@ -25,6 +25,11 @@ from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.module.project.data import ProjectData, LinksData
|
||||
|
||||
|
||||
def file_import_menu(self, context):
|
||||
op = self.layout.operator("bim.load_project", text="IFC (Geometry Only) (.ifc/.ifczip/.ifcxml)")
|
||||
op.should_start_fresh_session = False
|
||||
|
||||
|
||||
class BIM_MT_project(Menu):
|
||||
bl_idname = "BIM_MT_project"
|
||||
bl_label = "New IFC Project"
|
||||
@@ -294,9 +299,8 @@ class BIM_PT_new_project_wizard(Panel):
|
||||
row.prop(props, "volume_unit", text="Volume Unit")
|
||||
prop_with_search(self.layout, pprops, "template_file", text="Template")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row = self.layout.row()
|
||||
row.operator("bim.create_project")
|
||||
row.operator("bim.load_project").should_start_fresh_session = False
|
||||
|
||||
|
||||
class BIM_PT_project_library(Panel):
|
||||
|
||||
@@ -25,6 +25,7 @@ classes = (
|
||||
operator.CalculateFaceAreas,
|
||||
operator.CalculateFormworkArea,
|
||||
operator.CalculateObjectVolumes,
|
||||
operator.CalculateSideFormworkArea,
|
||||
operator.CalculateSingleQuantity,
|
||||
operator.PerformQuantityTakeOff,
|
||||
prop.BIMQtoProperties,
|
||||
|
||||
@@ -18,25 +18,28 @@
|
||||
|
||||
import bpy
|
||||
import bmesh
|
||||
from typing import Callable
|
||||
|
||||
|
||||
def calculate_height(obj):
|
||||
def calculate_height(obj: bpy.types.Object) -> float:
|
||||
return obj.dimensions[2]
|
||||
|
||||
|
||||
def calculate_edges_lengths(objs, context):
|
||||
def calculate_edges_lengths(objs: list[bpy.types.Object], context: bpy.types.Context):
|
||||
return calculate_mesh_quantity(objs, context, lambda bm: sum((e.calc_length() for e in bm.edges if e.select)))
|
||||
|
||||
|
||||
def calculate_faces_areas(objs, context):
|
||||
def calculate_faces_areas(objs: list[bpy.types.Object], context: bpy.types.Context) -> float:
|
||||
return calculate_mesh_quantity(objs, context, lambda bm: sum((f.calc_area() for f in bm.faces if f.select)))
|
||||
|
||||
|
||||
def calculate_volumes(objs, context):
|
||||
def calculate_volumes(objs: list[bpy.types.Object], context: bpy.types.Context) -> float:
|
||||
return calculate_mesh_quantity(objs, context, lambda bm: bm.calc_volume())
|
||||
|
||||
|
||||
def calculate_mesh_quantity(objs: bpy.types.Object, context, operation):
|
||||
def calculate_mesh_quantity(
|
||||
objs: list[bpy.types.Object], context: bpy.types.Context, operation: Callable[[bmesh.types.BMesh], float]
|
||||
) -> float:
|
||||
"""Get the sum of the target quantity on all passed mesh objects
|
||||
|
||||
:param objs: iterable of mesh object
|
||||
@@ -58,7 +61,7 @@ def calculate_mesh_quantity(objs: bpy.types.Object, context, operation):
|
||||
return result
|
||||
|
||||
|
||||
def calculate_formwork_area(objs, context):
|
||||
def calculate_formwork_area(objs: list[bpy.types.Object], context: bpy.types.Context) -> float:
|
||||
"""
|
||||
Formwork is defined as the surface area required to cover all exposed
|
||||
surfaces of one or more objects, excluding top surfaces (i.e. that have a
|
||||
@@ -85,6 +88,7 @@ def calculate_formwork_area(objs, context):
|
||||
copied_obj.name = "Formwork"
|
||||
copied_obj.BIMObjectProperties.ifc_definition_id = 0
|
||||
modifier = copied_obj.modifiers.new("Formwork", "REMESH")
|
||||
assert isinstance(modifier, bpy.types.RemeshModifier)
|
||||
modifier.mode = "SHARP"
|
||||
# This hardcoded value may be optimised through a better understanding of the octree division.
|
||||
# These values are based off some trial and error heuristics I've learned through experience.
|
||||
@@ -108,7 +112,7 @@ def calculate_formwork_area(objs, context):
|
||||
return result
|
||||
|
||||
|
||||
def calculate_side_formwork_area(objs, context):
|
||||
def calculate_side_formwork_area(objs: list[bpy.types.Object], context: bpy.types.Context) -> float:
|
||||
"""
|
||||
Side formwork is defined as the surface area required to cover all exposed
|
||||
surfaces of one or more objects, excluding top and bottom surfaces (i.e.
|
||||
|
||||
@@ -99,6 +99,21 @@ class CalculateFormworkArea(bpy.types.Operator):
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class CalculateSideFormworkArea(bpy.types.Operator):
|
||||
bl_idname = "bim.calculate_side_formwork_area"
|
||||
bl_label = "Calculate Side Formwork Area"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.selected_objects and context.active_object
|
||||
|
||||
def execute(self, context):
|
||||
result = helper.calculate_side_formwork_area([o for o in context.selected_objects if o.type == "MESH"], context)
|
||||
context.scene.BIMQtoProperties.qto_result = str(round(result, 3))
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class CalculateSingleQuantity(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.calculate_single_quantity"
|
||||
bl_label = "Calculate Single Quantity"
|
||||
@@ -137,7 +152,10 @@ class PerformQuantityTakeOff(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.perform_quantity_take_off"
|
||||
bl_label = "Perform Quantity Take-off"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Perform a quantity take off based of a QTO rule configuration"
|
||||
bl_description = (
|
||||
"Perform a quantity take off on selected objects based of a QTO rule configuration."
|
||||
"If no objects are selected, quantities calculated for all available IfcElements."
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
@@ -162,4 +180,6 @@ class PerformQuantityTakeOff(bpy.types.Operator, tool.Ifc.Operator):
|
||||
ifc_file = tool.Ifc.get()
|
||||
results = ifc5d.qto.quantify(ifc_file, elements, rules)
|
||||
ifc5d.qto.edit_qtos(ifc_file, results)
|
||||
|
||||
self.report({"INFO"}, f"Quantities are calculated for {len(elements)} elements.")
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -175,6 +175,9 @@ class Search(Operator):
|
||||
props = context.scene.CsvProperties
|
||||
elif self.property_group == "BIMSearchProperties":
|
||||
props = context.scene.BIMSearchProperties
|
||||
else:
|
||||
raise Exception(f"bim.search - unexpected property group name '{self.property_group}'.")
|
||||
|
||||
results = ifcopenshell.util.selector.filter_elements(
|
||||
tool.Ifc.get(), tool.Search.export_filter_query(props.filter_groups)
|
||||
)
|
||||
|
||||
@@ -29,11 +29,21 @@ import blenderbim.bim.handler
|
||||
class ReferenceStructure(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.reference_structure"
|
||||
bl_label = "Reference Structure"
|
||||
bl_description = (
|
||||
"Reference selected objects from all selected structures.\n\n"
|
||||
"Currently we do not support referencing structures in other structures "
|
||||
"though it is allowed in IFC4X3"
|
||||
)
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
objs = tool.Spatial.get_selected_objects_without_containers()
|
||||
if not objs:
|
||||
self.report({"INFO"}, "No non-spatial objects are selected.")
|
||||
return
|
||||
|
||||
containers = tool.Spatial.get_selected_containers()
|
||||
for obj in context.selected_objects:
|
||||
for obj in objs:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element:
|
||||
continue
|
||||
@@ -44,11 +54,21 @@ class ReferenceStructure(bpy.types.Operator, tool.Ifc.Operator):
|
||||
class DereferenceStructure(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.dereference_structure"
|
||||
bl_label = "Dereference Structure"
|
||||
bl_description = (
|
||||
"Dereference selected objects from all selected structures.\n\n"
|
||||
"Currently we do not support referencing structures in other structures "
|
||||
"though it is allowed in IFC4X3"
|
||||
)
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
objs = tool.Spatial.get_selected_objects_without_containers()
|
||||
if not objs:
|
||||
self.report({"INFO"}, "No non-spatial objects are selected.")
|
||||
return
|
||||
|
||||
containers = tool.Spatial.get_selected_containers()
|
||||
for obj in context.selected_objects:
|
||||
for obj in objs:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element:
|
||||
continue
|
||||
@@ -59,6 +79,7 @@ class DereferenceStructure(bpy.types.Operator, tool.Ifc.Operator):
|
||||
class AssignContainer(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.assign_container"
|
||||
bl_label = "Assign Container"
|
||||
bl_description = "Assign current default container to the selected objects"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
@@ -99,29 +120,36 @@ class RemoveContainer(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
class CopyToContainer(bpy.types.Operator, tool.Ifc.Operator):
|
||||
"""
|
||||
Copies selected 3D elements in the viewport to checkmarked spatial containers
|
||||
Copies selected 3D elements in the viewport to the selected spatial containers
|
||||
|
||||
Example: bulk copy a wall to multiple storeys
|
||||
|
||||
1. Select one or more 3D elements in the viewport
|
||||
2. Enable the checkmark next to one or more containers in the container list below to select it
|
||||
2. Select one or more spatial containers in the viewport
|
||||
3. Press this button
|
||||
4. The copied elements will have a new position relative to the destination containers
|
||||
"""
|
||||
|
||||
Copying containers to other containers currently is not supported."""
|
||||
|
||||
bl_idname = "bim.copy_to_container"
|
||||
bl_label = "Copy To Container"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
# Track decompositions so they can be recreated after the operation
|
||||
relationships = tool.Root.get_decomposition_relationships(context.selected_objects)
|
||||
old_to_new = {}
|
||||
objs = tool.Spatial.get_selected_objects_without_containers()
|
||||
if not objs:
|
||||
self.report({"INFO"}, "No non-spatial objects are selected.")
|
||||
return
|
||||
|
||||
containers = tool.Spatial.get_selected_containers()
|
||||
for obj in context.selected_objects:
|
||||
# Track decompositions so they can be recreated after the operation
|
||||
relationships = tool.Root.get_decomposition_relationships(objs)
|
||||
old_to_new = {}
|
||||
for obj in objs:
|
||||
result_objs = core.copy_to_container(tool.Ifc, tool.Collector, tool.Spatial, obj=obj, containers=containers)
|
||||
if result_objs:
|
||||
old_to_new[tool.Ifc.get_entity(obj)] = result_objs
|
||||
|
||||
# Recreate decompositions
|
||||
tool.Root.recreate_decompositions(relationships, old_to_new)
|
||||
blenderbim.bim.handler.refresh_ui_data()
|
||||
|
||||
@@ -32,6 +32,7 @@ from bpy.props import (
|
||||
)
|
||||
import blenderbim.tool as tool
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.element
|
||||
|
||||
|
||||
def get_subelement_class(self, context):
|
||||
@@ -71,6 +72,7 @@ def update_relating_container_from_object(self, context):
|
||||
return
|
||||
container = ifcopenshell.util.element.get_container(element)
|
||||
if container:
|
||||
# TODO: currently broken and relating_container_object is not used in UI.
|
||||
bpy.ops.bim.assign_container(structure=container.id())
|
||||
else:
|
||||
bpy.ops.bim.disable_editing_container()
|
||||
|
||||
@@ -36,6 +36,10 @@ class BIM_PT_spatial(Panel):
|
||||
return SpatialData.data["poll"]
|
||||
|
||||
def draw(self, context):
|
||||
# TODO: expose relating_container_object so users could
|
||||
# assign container without switching default container back and forth
|
||||
# just for 1 operation.
|
||||
|
||||
if not SpatialData.is_loaded:
|
||||
SpatialData.load()
|
||||
|
||||
@@ -77,9 +81,15 @@ class BIM_PT_spatial(Panel):
|
||||
else:
|
||||
row.label(text="No Spatial Container")
|
||||
row.operator("bim.enable_editing_container", icon="GREASEPENCIL", text="")
|
||||
for reference in SpatialData.data["references"]:
|
||||
|
||||
references = SpatialData.data["references"]
|
||||
if references:
|
||||
self.layout.label(text="Referenced In Structures:")
|
||||
for reference in references:
|
||||
row = self.layout.row()
|
||||
row.label(text=reference, icon="LINKED")
|
||||
else:
|
||||
self.layout.label(text="No References In Structures")
|
||||
|
||||
|
||||
class BIM_PT_spatial_decomposition(Panel):
|
||||
|
||||
@@ -567,5 +567,5 @@ class PurgeUnusedTypes(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
purged_types = core.purge_unused_types(tool.Ifc, tool.Type)
|
||||
purged_types = core.purge_unused_types(tool.Ifc, tool.Type, tool.Geometry)
|
||||
self.report({"INFO"}, f"{purged_types} types were purged.")
|
||||
|
||||
@@ -147,7 +147,7 @@ class BIM_OT_multiple_file_selector(bpy.types.Operator):
|
||||
"""Open Blender's file explorer to select one or multiple files."""
|
||||
|
||||
bl_idname = "bim.multiple_file_selector"
|
||||
bl_label = "Select Multiple Files"
|
||||
bl_label = "Select File(s)"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
files: bpy.props.CollectionProperty(name="File Path", type=bpy.types.OperatorFileListElement)
|
||||
|
||||
@@ -156,6 +156,34 @@ class ObjProperty(PropertyGroup):
|
||||
obj: bpy.props.PointerProperty(type=bpy.types.Object)
|
||||
|
||||
|
||||
def update_single_file(self, context):
|
||||
self.file_list.clear()
|
||||
new = self.file_list.add()
|
||||
new.name = self.single_file
|
||||
|
||||
|
||||
class MultipleFileSelect(PropertyGroup):
|
||||
single_file: bpy.props.StringProperty(name="Single File Path", description="", update=update_single_file)
|
||||
file_list: bpy.props.CollectionProperty(type=StrProperty)
|
||||
|
||||
def set_file_list(self, dirname: str, files: list[str]):
|
||||
self.file_list.clear()
|
||||
|
||||
for f in files:
|
||||
new = self.file_list.add()
|
||||
new.name = os.path.join(dirname, f)
|
||||
|
||||
def layout_file_select(self, layout, filter_glob="", text=""):
|
||||
if len(self.file_list) > 1:
|
||||
layout.label(text=f"{len(self.file_list)} Files Selected")
|
||||
else:
|
||||
layout.prop(self, "single_file", text=text)
|
||||
|
||||
layout.context_pointer_set("file_props", self)
|
||||
op = layout.operator("bim.multiple_file_selector", icon="FILE_FOLDER", text="")
|
||||
op.filter_glob = filter_glob
|
||||
|
||||
|
||||
def update_attribute_value(self, context):
|
||||
value_name = self.get_value_name()
|
||||
if value_name:
|
||||
@@ -248,6 +276,8 @@ class Attribute(PropertyGroup):
|
||||
enum_items: StringProperty(name="Value")
|
||||
enum_descriptions: CollectionProperty(type=StrProperty)
|
||||
enum_value: EnumProperty(items=get_attribute_enum_values, name="Value", update=update_attribute_value)
|
||||
filepath_value: PointerProperty(type=MultipleFileSelect)
|
||||
filter_glob: StringProperty()
|
||||
is_null: BoolProperty(name="Is Null", update=update_is_null)
|
||||
is_optional: BoolProperty(name="Is Optional")
|
||||
is_uri: BoolProperty(name="Is Uri", default=False)
|
||||
@@ -263,6 +293,8 @@ class Attribute(PropertyGroup):
|
||||
return None
|
||||
if self.data_type == "string":
|
||||
return self.string_value.replace("\\n", "\n")
|
||||
if self.data_type == "file":
|
||||
return [f.name for f in self.filepath_value.file_list]
|
||||
return getattr(self, str(self.get_value_name()), None)
|
||||
|
||||
def get_value_default(self):
|
||||
@@ -276,6 +308,8 @@ class Attribute(PropertyGroup):
|
||||
return False
|
||||
elif self.data_type == "enum":
|
||||
return "0"
|
||||
elif self.data_type == "file":
|
||||
return ""
|
||||
|
||||
def get_value_name(self, display_only=False):
|
||||
if self.data_type == "string":
|
||||
@@ -290,6 +324,8 @@ class Attribute(PropertyGroup):
|
||||
return "float_value"
|
||||
elif self.data_type == "enum":
|
||||
return "enum_value"
|
||||
elif self.data_type == "file":
|
||||
return "filepath_value"
|
||||
|
||||
def set_value(self, value):
|
||||
if isinstance(value, str):
|
||||
@@ -478,31 +514,3 @@ class BIMFacet(PropertyGroup):
|
||||
|
||||
class BIMFilterGroup(PropertyGroup):
|
||||
filters: CollectionProperty(type=BIMFacet, name="filters")
|
||||
|
||||
|
||||
def update_single_file(self, context):
|
||||
self.file_list.clear()
|
||||
new = self.file_list.add()
|
||||
new.name = self.single_file
|
||||
|
||||
|
||||
class MultipleFileSelect(bpy.types.PropertyGroup):
|
||||
single_file: bpy.props.StringProperty(name="Single File Path", description="", update=update_single_file)
|
||||
file_list: bpy.props.CollectionProperty(type=StrProperty)
|
||||
|
||||
def set_file_list(self, dirname: str, files: list[str]):
|
||||
self.file_list.clear()
|
||||
|
||||
for f in files:
|
||||
new = self.file_list.add()
|
||||
new.name = os.path.join(dirname, f)
|
||||
|
||||
def layout_file_select(self, layout, filter_glob="", text=""):
|
||||
if len(self.file_list) > 1:
|
||||
layout.label(text=f"{len(self.file_list)} Files Selected")
|
||||
else:
|
||||
layout.prop(self, "single_file", text=text)
|
||||
|
||||
layout.context_pointer_set("file_props", self)
|
||||
op = layout.operator("bim.multiple_file_selector", icon="FILE_FOLDER", text="")
|
||||
op.filter_glob = filter_glob
|
||||
|
||||
@@ -16,8 +16,16 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from __future__ import annotations
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
|
||||
def add_instance_flooring_covering_from_cursor(ifc, root, spatial):
|
||||
if TYPE_CHECKING:
|
||||
import bpy
|
||||
import ifcopenshell
|
||||
import blenderbim.tool as tool
|
||||
|
||||
|
||||
def add_instance_flooring_covering_from_cursor(ifc: tool.Ifc, root: tool.Root, spatial: tool.Spatial) -> None:
|
||||
if not root.get_default_container():
|
||||
raise NoDefaultContainer()
|
||||
|
||||
@@ -32,7 +40,7 @@ def add_instance_flooring_covering_from_cursor(ifc, root, spatial):
|
||||
relating_type = None
|
||||
|
||||
if selected_objects and active_obj:
|
||||
x, y, z, h, mat = spatial.get_x_y_z_h_mat_from_active_obj(active_obj)
|
||||
x, y, z, h, mat = spatial.get_x_y_z_h_mat_from_obj(active_obj)
|
||||
else:
|
||||
x, y, z, h, mat = spatial.get_x_y_z_h_mat_from_cursor()
|
||||
|
||||
@@ -59,7 +67,9 @@ def add_instance_flooring_covering_from_cursor(ifc, root, spatial):
|
||||
spatial.regen_obj_representation(obj, body)
|
||||
|
||||
|
||||
def add_instance_ceiling_covering_from_cursor(ifc, root, covering, spatial):
|
||||
def add_instance_ceiling_covering_from_cursor(
|
||||
ifc: tool.Ifc, root: tool.Root, covering: tool.Covering, spatial: tool.Spatial
|
||||
) -> None:
|
||||
if not root.get_default_container():
|
||||
raise NoDefaultContainer()
|
||||
|
||||
@@ -74,7 +84,7 @@ def add_instance_ceiling_covering_from_cursor(ifc, root, covering, spatial):
|
||||
relating_type = None
|
||||
|
||||
if selected_objects and active_obj:
|
||||
x, y, z, h, mat = spatial.get_x_y_z_h_mat_from_active_obj(active_obj)
|
||||
x, y, z, h, mat = spatial.get_x_y_z_h_mat_from_obj(active_obj)
|
||||
else:
|
||||
x, y, z, h, mat = spatial.get_x_y_z_h_mat_from_cursor()
|
||||
ceiling_height = covering.get_z_from_ceiling_height()
|
||||
@@ -101,7 +111,7 @@ def add_instance_ceiling_covering_from_cursor(ifc, root, covering, spatial):
|
||||
spatial.regen_obj_representation(obj, body)
|
||||
|
||||
|
||||
def regen_selected_covering_object(root, spatial):
|
||||
def regen_selected_covering_object(root: tool.Root, spatial: tool.Spatial) -> None:
|
||||
if not root.get_default_container():
|
||||
raise NoDefaultContainer()
|
||||
|
||||
@@ -109,7 +119,7 @@ def regen_selected_covering_object(root, spatial):
|
||||
selected_objects = spatial.get_selected_objects()
|
||||
|
||||
if selected_objects and active_obj:
|
||||
x, y, z, h, mat = spatial.get_x_y_z_h_mat_from_active_obj(active_obj)
|
||||
x, y, z, h, mat = spatial.get_x_y_z_h_mat_from_obj(active_obj)
|
||||
|
||||
space_polygon = spatial.get_space_polygon_from_context_visible_objects(x, y)
|
||||
|
||||
@@ -132,7 +142,7 @@ def regen_selected_covering_object(root, spatial):
|
||||
|
||||
|
||||
# TODO CHECK IF IT IS POSSIBLE TO CREATE ONLY ONE CORE FUNCTION FOR _FROM_WALLS
|
||||
def add_instance_flooring_coverings_from_walls(root, spatial):
|
||||
def add_instance_flooring_coverings_from_walls(root: tool.Root, spatial: tool.Spatial) -> None:
|
||||
if not root.get_default_container():
|
||||
raise NoDefaultContainer()
|
||||
|
||||
@@ -158,7 +168,7 @@ def add_instance_flooring_coverings_from_walls(root, spatial):
|
||||
spatial.regen_obj_representation(obj, body)
|
||||
|
||||
|
||||
def add_instance_ceiling_coverings_from_walls(root, spatial, covering):
|
||||
def add_instance_ceiling_coverings_from_walls(root: tool.Root, spatial: tool.Spatial, covering: tool.Covering) -> None:
|
||||
if not root.get_default_container():
|
||||
raise NoDefaultContainer()
|
||||
|
||||
|
||||
@@ -18,11 +18,12 @@
|
||||
|
||||
from __future__ import annotations
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
from typing import TYPE_CHECKING, Optional, Union
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import bpy
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.representation
|
||||
import blenderbim.tool as tool
|
||||
|
||||
|
||||
@@ -225,7 +226,11 @@ def disable_editing_drawings(drawing: tool.Drawing) -> None:
|
||||
|
||||
|
||||
def add_drawing(
|
||||
ifc: tool.Ifc, collector: tool.Collector, drawing: tool.Drawing, target_view=None, location_hint=None
|
||||
ifc: tool.Ifc,
|
||||
collector: tool.Collector,
|
||||
drawing: tool.Drawing,
|
||||
target_view: Union[ifcopenshell.util.representation.TARGET_VIEW, None] = None,
|
||||
location_hint: Union[str, None] = None,
|
||||
) -> None:
|
||||
drawing_name = drawing.ensure_unique_drawing_name(drawing.generate_drawing_name(target_view, location_hint))
|
||||
drawing_matrix = drawing.generate_drawing_matrix(target_view, location_hint)
|
||||
|
||||
@@ -126,8 +126,7 @@ def switch_representation(
|
||||
if not current_obj_data and geometry.is_text_literal(representation):
|
||||
return
|
||||
|
||||
use_immediate_repr = apply_openings and bool(getattr(entity, "HasOpenings", None))
|
||||
use_immediate_repr = use_immediate_repr or geometry.has_material_style_override(entity)
|
||||
use_immediate_repr = geometry.should_use_immediate_representation(entity, apply_openings)
|
||||
if use_immediate_repr:
|
||||
# if it has openings make sure to switch to element's mapped representation
|
||||
representation = geometry.unresolve_type_representation(representation, entity)
|
||||
@@ -169,8 +168,11 @@ def get_representation_ifc_parameters(
|
||||
def remove_representation(
|
||||
ifc: tool.Ifc, geometry: tool.Geometry, obj: bpy.types.Object, representation: ifcopenshell.entity_instance
|
||||
) -> None:
|
||||
"""Consider changing obj representation before using the function,
|
||||
otherwise it will replace object with empty."""
|
||||
"""Remove IFC representation from an object.
|
||||
|
||||
If removed representation is active will automatically change it to some other one.
|
||||
If it is the object's only representation, object will be recreated as an empty.
|
||||
"""
|
||||
|
||||
element = ifc.get_entity(obj)
|
||||
assert element
|
||||
|
||||
@@ -95,7 +95,7 @@ def copy_to_container(
|
||||
copied_obj = spatial.duplicate_object_and_data(obj)
|
||||
spatial.set_relative_object_matrix(copied_obj, to_container_obj, matrix)
|
||||
result_objs.append(spatial.run_root_copy_class(obj=copied_obj))
|
||||
spatial.run_spatial_assign_container(structure_obj=to_container_obj, element_obj=copied_obj)
|
||||
spatial.run_spatial_assign_container(container=to_container, element_obj=copied_obj)
|
||||
spatial.disable_editing(obj)
|
||||
return result_objs
|
||||
|
||||
@@ -173,7 +173,7 @@ def generate_space(ifc: tool.Ifc, model: tool.Model, root: tool.Root, spatial: t
|
||||
relating_type = None
|
||||
|
||||
if selected_objects and active_obj:
|
||||
x, y, z, h, mat = spatial.get_x_y_z_h_mat_from_active_obj(active_obj)
|
||||
x, y, z, h, mat = spatial.get_x_y_z_h_mat_from_obj(active_obj)
|
||||
element = ifc.get_entity(active_obj)
|
||||
else:
|
||||
x, y, z, h, mat = spatial.get_x_y_z_h_mat_from_cursor()
|
||||
|
||||
@@ -65,10 +65,12 @@ class Demo:
|
||||
|
||||
@interface
|
||||
class Aggregate:
|
||||
def can_aggregate(cls, relating_object, related_object): pass
|
||||
def can_aggregate(cls, relating_obj, related_obj): pass
|
||||
def has_physical_body_representation(cls, element): pass
|
||||
def disable_editing(cls, obj): pass
|
||||
def enable_editing(cls, obj): pass
|
||||
def get_container(cls, element): pass
|
||||
def get_relating_object(cls, related_element): pass
|
||||
|
||||
|
||||
@interface
|
||||
@@ -380,6 +382,7 @@ class Geometry:
|
||||
def clear_modifiers(cls, obj): pass
|
||||
def clear_scale(cls, obj): pass
|
||||
def delete_data(cls, data): pass
|
||||
def delete_ifc_object(cls, obj): pass
|
||||
def does_representation_id_exist(cls, representation_id): pass
|
||||
def duplicate_object_data(cls, obj): pass
|
||||
def get_cartesian_point_coordinate_offset(cls, obj): pass
|
||||
@@ -420,6 +423,9 @@ class Geometry:
|
||||
def unresolve_type_representation(cls, representation, element): pass
|
||||
def delete_opening_object_placement(cls, opening): pass
|
||||
def switch_from_representation(cls, obj, representation): pass
|
||||
def get_blender_offset_type(cls, obj): pass
|
||||
def has_material_style_override(cls, obj): pass
|
||||
def should_use_immediate_representation(cls, entity, apply_openings): pass
|
||||
|
||||
|
||||
@interface
|
||||
@@ -447,6 +453,7 @@ class Georeference:
|
||||
def set_cursor_location(cls, coordinates): pass
|
||||
def set_ifc_grid_north(cls): pass
|
||||
def set_ifc_true_north(cls): pass
|
||||
def set_model_origin(cls): pass
|
||||
def set_vector_coordinates(cls, vector_coordinates, type): pass
|
||||
def set_wcs(cls, matrix): pass
|
||||
def xyz2enh(cls, coordinates): pass
|
||||
@@ -845,11 +852,10 @@ class Sequence:
|
||||
|
||||
@interface
|
||||
class Spatial:
|
||||
def can_contain(cls, structure_obj, element_obj): pass
|
||||
def can_contain(cls, container, element_obj): pass
|
||||
def can_reference(cls, structure, element): pass
|
||||
def contract_container(cls, container): pass
|
||||
def copy_xy(cls, src_obj, destination_obj): pass
|
||||
def import_spatial_element(cls, element, level_index): pass
|
||||
def deselect_objects(cls): pass
|
||||
def disable_editing(cls, obj): pass
|
||||
def duplicate_object_and_data(cls, obj): pass
|
||||
@@ -857,6 +863,9 @@ class Spatial:
|
||||
def edit_container_name(cls, container, name): pass
|
||||
def enable_editing(cls, obj): pass
|
||||
def expand_container(cls, container): pass
|
||||
def filter_elements_by_class(cls, elements, ifc_class): pass
|
||||
def filter_elements_by_relating_type(cls, elements, relating_type): pass
|
||||
def filter_elements_by_untyped(cls, elements): pass
|
||||
def filter_products(cls, products, action): pass
|
||||
def get_active_container(cls): pass
|
||||
def get_container(cls, element): pass
|
||||
@@ -865,24 +874,27 @@ class Spatial:
|
||||
def get_relative_object_matrix(cls, target_obj, relative_to_obj): pass
|
||||
def get_selected_product_types(cls): pass
|
||||
def get_selected_products(cls): pass
|
||||
def import_containers(cls, parent=None): pass
|
||||
def import_spatial_decomposition(cls): pass
|
||||
def run_root_copy_class(cls, obj=None): pass
|
||||
def run_spatial_assign_container(cls, structure_obj=None, element_obj=None): pass
|
||||
def import_spatial_element(cls, element, level_index): pass
|
||||
def load_contained_elements(cls): pass
|
||||
def run_root_copy_class(cls, obj): pass
|
||||
def run_spatial_assign_container(cls, container, element_obj): pass
|
||||
def run_spatial_import_spatial_decomposition(cls): pass
|
||||
def select_object(cls, obj): pass
|
||||
def select_products(cls, products, unhide=False): pass
|
||||
def set_active_object(cls, obj): pass
|
||||
def set_relative_object_matrix(cls, target_obj, relative_to_obj, matrix): pass
|
||||
def set_default_container(cls, container): pass
|
||||
def show_scene_objects(cls): pass
|
||||
#HERE STARTS SPATIAL TOOL
|
||||
|
||||
# HERE STARTS SPATIAL TOOL
|
||||
def is_bounding_class(cls, visible_element): pass
|
||||
def get_space_polygon_from_context_visible_objects(cls, x, y): pass
|
||||
def get_boundary_lines_from_context_visible_objects(cls, visible_objects): pass
|
||||
def debug_shape(cls, foo): pass
|
||||
def debug_line(cls, start, end): pass
|
||||
def get_boundary_lines_from_context_visible_objects(cls): pass
|
||||
def get_gross_mesh_from_element(cls, visible_element): pass
|
||||
def create_mesh_from_shape(cls, shape): pass
|
||||
def get_x_y_z_h_mat_from_active_obj(cls, active_obj): pass
|
||||
def get_x_y_z_h_mat_from_obj(cls, obj): pass
|
||||
def get_x_y_z_h_mat_from_cursor(cls): pass
|
||||
def get_union_shape_from_selected_objects(cls): pass
|
||||
def get_boundary_elements(cls, selected_objects): pass
|
||||
@@ -906,17 +918,20 @@ class Spatial:
|
||||
def get_active_obj_z(cls): pass
|
||||
def get_active_obj_height(cls): pass
|
||||
def get_relating_type_id(cls): pass
|
||||
def translate_obj_to_z_location(cls, obj): pass
|
||||
def translate_obj_to_z_location(cls, obj, z): pass
|
||||
def get_2d_vertices_from_obj(cls, obj): pass
|
||||
def get_scaled_2d_vertices(cls, points): pass
|
||||
def assign_swept_area_outer_curve_from_2d_vertices(cls, obj, vertices): pass
|
||||
def get_body_representation(cls, obj): pass
|
||||
def assign_ifcspace_class_to_obj(cls, obj): pass
|
||||
def assign_type_to_obj(cls, obj): pass
|
||||
def assign_relating_type_to_element(cls, ifc, Type, element, relating_type): pass
|
||||
def regen_obj_representation(cls, ifc, geometry, obj, body): pass
|
||||
def assign_relating_type_to_element(cls, ifc, type, element, relating_type): pass
|
||||
def regen_obj_representation(cls, obj, body): pass
|
||||
def toggle_spaces_visibility_wired_and_textured(cls, spaces): pass
|
||||
def toggle_hide_spaces(cls, spaces): pass
|
||||
def set_default_container(cls, container): pass
|
||||
def guess_default_container(cls): pass
|
||||
def get_selected_containers(cls): pass
|
||||
|
||||
@interface
|
||||
class Covering:
|
||||
@@ -1007,7 +1022,6 @@ class Type:
|
||||
def get_representation_context(cls, representation): pass
|
||||
def get_type_occurrences(cls, element_type): pass
|
||||
def has_material_usage(cls, element): pass
|
||||
def remove_object(cls, obj): pass
|
||||
def run_geometry_add_representation(cls, obj=None, context=None, ifc_representation_class=None, profile_set_usage=None): pass
|
||||
def run_geometry_switch_representation(cls, obj=None, representation=None, should_reload=None, is_global=None): pass
|
||||
|
||||
|
||||
@@ -40,15 +40,15 @@ def assign_type(
|
||||
type_tool.disable_editing(obj)
|
||||
|
||||
|
||||
def purge_unused_types(ifc: tool.Ifc, type: tool.Type) -> int:
|
||||
def purge_unused_types(ifc: tool.Ifc, type: tool.Type, geometry: tool.Geometry) -> int:
|
||||
"""Remove all types without occurrences, return an amount of the removed types."""
|
||||
purged_types = 0
|
||||
for element_type in type.get_model_types():
|
||||
if not type.get_type_occurrences(element_type):
|
||||
obj = ifc.get_object(element_type)
|
||||
ifc.run("root.remove_product", product=element_type)
|
||||
purged_types += 1
|
||||
if obj:
|
||||
ifc.unlink(element=element_type)
|
||||
type.remove_object(obj)
|
||||
geometry.delete_ifc_object(obj)
|
||||
else:
|
||||
ifc.run("root.remove_product", product=element_type)
|
||||
purged_types += 1
|
||||
return purged_types
|
||||
|
||||
@@ -6,6 +6,10 @@ def connect_websocket_server(web, port):
|
||||
# check if port already has a server listening to it
|
||||
if web.is_port_available(port):
|
||||
web.start_websocket_server(port)
|
||||
if web.has_started(port):
|
||||
web.connect_websocket_server(port)
|
||||
web.open_web_browser(port)
|
||||
return
|
||||
|
||||
web.connect_websocket_server(port)
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ import bpy
|
||||
import bmesh
|
||||
import shapely
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.element
|
||||
import blenderbim.core.tool
|
||||
import blenderbim.core.root
|
||||
import blenderbim.core.spatial
|
||||
@@ -33,23 +34,46 @@ from shapely import Polygon, MultiPolygon
|
||||
|
||||
class Covering(blenderbim.core.tool.Covering):
|
||||
@classmethod
|
||||
def get_z_from_ceiling_height(cls):
|
||||
def get_z_from_ceiling_height(cls) -> float:
|
||||
props = bpy.context.scene.BIMCoveringProperties
|
||||
return props.ceiling_height
|
||||
|
||||
# def toggle_spaces_visibility_wired_and_textured(cls, spaces):
|
||||
# first_obj = tool.Ifc.get_object(spaces[0])
|
||||
# if bpy.data.objects[first_obj.name].display_type == "TEXTURED":
|
||||
# for space in spaces:
|
||||
# obj = tool.Ifc.get_object(space)
|
||||
# bpy.data.objects[obj.name].show_wire = True
|
||||
# bpy.data.objects[obj.name].display_type = "WIRE"
|
||||
# return
|
||||
#
|
||||
# elif bpy.data.objects[first_obj.name].display_type == "WIRE":
|
||||
# for space in spaces:
|
||||
# obj = tool.Ifc.get_object(space)
|
||||
# bpy.data.objects[obj.name].show_wire = False
|
||||
# bpy.data.objects[obj.name].display_type = "TEXTURED"
|
||||
# return
|
||||
|
||||
# def toggle_spaces_visibility_wired_and_textured(cls, spaces):
|
||||
# first_obj = tool.Ifc.get_object(spaces[0])
|
||||
# if bpy.data.objects[first_obj.name].display_type == "TEXTURED":
|
||||
# for space in spaces:
|
||||
# obj = tool.Ifc.get_object(space)
|
||||
# bpy.data.objects[obj.name].show_wire = True
|
||||
# bpy.data.objects[obj.name].display_type = "WIRE"
|
||||
# return
|
||||
#
|
||||
# elif bpy.data.objects[first_obj.name].display_type == "WIRE":
|
||||
# for space in spaces:
|
||||
# obj = tool.Ifc.get_object(space)
|
||||
# bpy.data.objects[obj.name].show_wire = False
|
||||
# bpy.data.objects[obj.name].display_type = "TEXTURED"
|
||||
# return
|
||||
@classmethod
|
||||
def covering_poll_wall_selected(
|
||||
cls, operator: type[bpy.types.Operator], context: bpy.types.Context, covering_type: str
|
||||
) -> bool:
|
||||
if not context.selected_objects or not context.active_object:
|
||||
operator.poll_message_set("No objects selected.")
|
||||
return False
|
||||
element = tool.Ifc.get_entity(context.active_object)
|
||||
if not element or not element.is_a("IfcWall") or not tool.Model.get_usage_type(element) == "LAYER2":
|
||||
operator.poll_message_set("LAYER2 based IfcWall must be selected.")
|
||||
return False
|
||||
return cls.covering_poll_relating_type_check(operator, context, covering_type)
|
||||
|
||||
@classmethod
|
||||
def covering_poll_relating_type_check(
|
||||
cls, operator: type[bpy.types.Operator], context: bpy.types.Context, covering_type: str
|
||||
) -> bool:
|
||||
relating_type_id = int(context.scene.BIMModelProperties.relating_type_id)
|
||||
relating_type = ifcopenshell.util.element.get_predefined_type(tool.Ifc.get().by_id(relating_type_id))
|
||||
if relating_type != covering_type:
|
||||
operator.poll_message_set(f"Select IfcCoveringType with predefined type '{covering_type}'.")
|
||||
return False
|
||||
return True
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
import os
|
||||
import re
|
||||
import collections
|
||||
import collections.abc
|
||||
import bpy
|
||||
import math
|
||||
import json
|
||||
@@ -79,11 +80,11 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
# fmt: on
|
||||
|
||||
@classmethod
|
||||
def canonicalise_class_name(cls, name):
|
||||
def canonicalise_class_name(cls, name: str) -> str:
|
||||
return re.sub("[^0-9a-zA-Z]+", "", name)
|
||||
|
||||
@classmethod
|
||||
def copy_representation(cls, source, dest):
|
||||
def copy_representation(cls, source: ifcopenshell.entity_instance, dest: ifcopenshell.entity_instance) -> None:
|
||||
if source.Representation:
|
||||
dest.Representation = ifcopenshell.util.element.copy_deep(
|
||||
tool.Ifc.get(), source.Representation, exclude=["IfcGeometricRepresentationContext"]
|
||||
@@ -114,7 +115,9 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
return obj
|
||||
|
||||
@classmethod
|
||||
def ensure_annotation_in_drawing_plane(cls, obj, camera=None):
|
||||
def ensure_annotation_in_drawing_plane(
|
||||
cls, obj: bpy.types.Object, camera: Optional[bpy.types.Object] = None
|
||||
) -> None:
|
||||
"""Make sure annotation object is going to be visible in the camera view"""
|
||||
|
||||
def get_camera_from_annotation_object(obj):
|
||||
@@ -138,7 +141,9 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
ANNOTATION_TYPES_SUPPORT_SETUP = ("STAIR_ARROW", "TEXT", "REVISION_CLOUD", "FILL_AREA")
|
||||
|
||||
@classmethod
|
||||
def setup_annotation_object(cls, obj, object_type, related_object=None):
|
||||
def setup_annotation_object(
|
||||
cls, obj: bpy.types.Object, object_type: str, related_object: Optional[bpy.types.Object] = None
|
||||
) -> None:
|
||||
"""Finish object's adjustments after both object and entity are created"""
|
||||
|
||||
if not related_object:
|
||||
@@ -205,7 +210,9 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
tool.Drawing.update_text_value(obj)
|
||||
|
||||
@classmethod
|
||||
def is_annotation_object_type(cls, element, object_types):
|
||||
def is_annotation_object_type(
|
||||
cls, element: ifcopenshell.entity_instance, object_types: Union[str, list[str]]
|
||||
) -> bool:
|
||||
if not isinstance(object_types, collections.abc.Iterable):
|
||||
object_types = [object_types]
|
||||
|
||||
@@ -226,7 +233,9 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def get_annotation_representation(cls, element):
|
||||
def get_annotation_representation(
|
||||
cls, element: ifcopenshell.entity_instance
|
||||
) -> Union[ifcopenshell.entity_instance, None]:
|
||||
rep = ifcopenshell.util.representation.get_representation(
|
||||
element, "Plan", "Annotation"
|
||||
) or ifcopenshell.util.representation.get_representation(element, "Model", "Annotation")
|
||||
@@ -237,7 +246,9 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
return rep
|
||||
|
||||
@classmethod
|
||||
def create_camera(cls, name, matrix, location_hint):
|
||||
def create_camera(
|
||||
cls, name: str, matrix: Matrix, location_hint: Literal["PERPSECTIVE", "ORTHOGRAPHIC"]
|
||||
) -> bpy.types.Object:
|
||||
camera = bpy.data.objects.new(name, bpy.data.cameras.new(name))
|
||||
camera.location = (0, 0, 1.5) # The view shall be 1.5m above the origin
|
||||
camera.data.show_limits = True
|
||||
@@ -257,7 +268,7 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
return camera
|
||||
|
||||
@classmethod
|
||||
def create_svg_schedule(cls, schedule):
|
||||
def create_svg_schedule(cls, schedule: ifcopenshell.entity_instance) -> None:
|
||||
import blenderbim.bim.module.drawing.scheduler as scheduler
|
||||
|
||||
schedule_creator = scheduler.Scheduler()
|
||||
@@ -276,7 +287,7 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
return uri
|
||||
|
||||
@classmethod
|
||||
def add_drawings(cls, sheet):
|
||||
def add_drawings(cls, sheet: ifcopenshell.entity_instance) -> None:
|
||||
import blenderbim.bim.module.drawing.sheeter as sheeter
|
||||
|
||||
sheet_builder = sheeter.SheetBuilder()
|
||||
@@ -293,7 +304,7 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
sheet_builder.add_drawing(drawing_references[drawing_annotation.Name], drawing_annotation, sheet)
|
||||
|
||||
@classmethod
|
||||
def delete_collection(cls, collection):
|
||||
def delete_collection(cls, collection: bpy.types.Collection) -> None:
|
||||
bpy.data.collections.remove(collection, do_unlink=True)
|
||||
|
||||
@classmethod
|
||||
@@ -312,19 +323,19 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
bpy.data.objects.remove(obj)
|
||||
|
||||
@classmethod
|
||||
def disable_editing_drawings(cls):
|
||||
def disable_editing_drawings(cls) -> None:
|
||||
bpy.context.scene.DocProperties.is_editing_drawings = False
|
||||
|
||||
@classmethod
|
||||
def disable_editing_schedules(cls):
|
||||
def disable_editing_schedules(cls) -> None:
|
||||
bpy.context.scene.DocProperties.is_editing_schedules = False
|
||||
|
||||
@classmethod
|
||||
def disable_editing_references(cls):
|
||||
def disable_editing_references(cls) -> None:
|
||||
bpy.context.scene.DocProperties.is_editing_references = False
|
||||
|
||||
@classmethod
|
||||
def disable_editing_sheets(cls):
|
||||
def disable_editing_sheets(cls) -> None:
|
||||
bpy.context.scene.DocProperties.is_editing_sheets = False
|
||||
|
||||
@classmethod
|
||||
@@ -344,19 +355,19 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
bpy.ops.object.mode_set(mode="EDIT")
|
||||
|
||||
@classmethod
|
||||
def enable_editing_drawings(cls):
|
||||
def enable_editing_drawings(cls) -> None:
|
||||
bpy.context.scene.DocProperties.is_editing_drawings = True
|
||||
|
||||
@classmethod
|
||||
def enable_editing_schedules(cls):
|
||||
def enable_editing_schedules(cls) -> None:
|
||||
bpy.context.scene.DocProperties.is_editing_schedules = True
|
||||
|
||||
@classmethod
|
||||
def enable_editing_references(cls):
|
||||
def enable_editing_references(cls) -> None:
|
||||
bpy.context.scene.DocProperties.is_editing_references = True
|
||||
|
||||
@classmethod
|
||||
def enable_editing_sheets(cls):
|
||||
def enable_editing_sheets(cls) -> None:
|
||||
bpy.context.scene.DocProperties.is_editing_sheets = True
|
||||
|
||||
@classmethod
|
||||
@@ -368,14 +379,14 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
obj.BIMAssignedProductProperties.is_editing_product = True
|
||||
|
||||
@classmethod
|
||||
def ensure_unique_drawing_name(cls, name):
|
||||
def ensure_unique_drawing_name(cls, name: str) -> str:
|
||||
names = [e.Name for e in tool.Ifc.get().by_type("IfcAnnotation") if e.ObjectType == "DRAWING"]
|
||||
while name in names:
|
||||
name += "-X"
|
||||
return name
|
||||
|
||||
@classmethod
|
||||
def ensure_unique_identification(cls, identification):
|
||||
def ensure_unique_identification(cls, identification: str) -> str:
|
||||
if tool.Ifc.get_schema() == "IFC2X3":
|
||||
ids = [d.DocumentId for d in tool.Ifc.get().by_type("IfcDocumentInformation") if d.Scope == "DOCUMENTATION"]
|
||||
else:
|
||||
@@ -431,7 +442,7 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
return ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Model", "Annotation", target_view)
|
||||
|
||||
@classmethod
|
||||
def get_body_context(cls):
|
||||
def get_body_context(cls) -> ifcopenshell.entity_instance:
|
||||
return ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Model", "Body", "MODEL_VIEW")
|
||||
|
||||
@classmethod
|
||||
@@ -462,15 +473,15 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
return os.path.splitext(os.path.basename(path))[0]
|
||||
|
||||
@classmethod
|
||||
def get_path_with_ext(cls, path, ext):
|
||||
def get_path_with_ext(cls, path: str, ext: str) -> str:
|
||||
return os.path.splitext(path)[0] + f".{ext}"
|
||||
|
||||
@classmethod
|
||||
def get_unit_system(cls):
|
||||
def get_unit_system(cls) -> Literal["NONE", "METRIC", "IMPERIAL"]:
|
||||
return bpy.context.scene.unit_settings.system
|
||||
|
||||
@classmethod
|
||||
def get_drawing_collection(cls, drawing):
|
||||
def get_drawing_collection(cls, drawing: ifcopenshell.entity_instance) -> Union[bpy.types.Collection, None]:
|
||||
obj = tool.Ifc.get_object(drawing)
|
||||
if obj:
|
||||
return obj.BIMObjectProperties.collection
|
||||
@@ -488,8 +499,8 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
return rel.RelatingDocument
|
||||
|
||||
@classmethod
|
||||
def get_drawing_references(cls, drawing):
|
||||
results = set()
|
||||
def get_drawing_references(cls, drawing: ifcopenshell.entity_instance) -> set[ifcopenshell.entity_instance]:
|
||||
results: set[ifcopenshell.entity_instance] = set()
|
||||
for inverse in tool.Ifc.get().get_inverse(drawing):
|
||||
if inverse.is_a("IfcRelAssignsToProduct") and inverse.RelatingProduct == drawing:
|
||||
results.update(inverse.RelatedObjects)
|
||||
@@ -505,7 +516,7 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
return rel.RelatedObjects
|
||||
|
||||
@classmethod
|
||||
def get_ifc_representation_class(cls, object_type):
|
||||
def get_ifc_representation_class(cls, object_type: str) -> str:
|
||||
if object_type == "TEXT":
|
||||
return "IfcTextLiteral"
|
||||
elif object_type == "TEXT_LEADER":
|
||||
@@ -517,7 +528,9 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
return element.Name
|
||||
|
||||
@classmethod
|
||||
def generate_drawing_matrix(cls, target_view, location_hint):
|
||||
def generate_drawing_matrix(
|
||||
cls, target_view: ifcopenshell.util.representation.TARGET_VIEW, location_hint: str
|
||||
) -> Matrix:
|
||||
x, y, z = (0, 0, 0) if location_hint == 0 else bpy.context.scene.cursor.matrix.translation
|
||||
if target_view == "PLAN_VIEW":
|
||||
if location_hint:
|
||||
@@ -554,12 +567,14 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
return mathutils.Matrix()
|
||||
|
||||
@classmethod
|
||||
def generate_sheet_identification(cls):
|
||||
def generate_sheet_identification(cls) -> str:
|
||||
number = len([d for d in tool.Ifc.get().by_type("IfcDocumentInformation") if d.Scope == "DOCUMENTATION"])
|
||||
return "A" + str(number).zfill(2)
|
||||
|
||||
@classmethod
|
||||
def get_text_literal(cls, obj, return_list=False):
|
||||
def get_text_literal(
|
||||
cls, obj: bpy.types.Object, return_list: bool = False
|
||||
) -> Union[ifcopenshell.entity_instance, None, list[ifcopenshell.entity_instance]]:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element:
|
||||
return
|
||||
@@ -575,11 +590,11 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
return items[0]
|
||||
|
||||
@classmethod
|
||||
def is_editing_sheets(cls):
|
||||
def is_editing_sheets(cls) -> bool:
|
||||
return bpy.context.scene.DocProperties.is_editing_sheets
|
||||
|
||||
@classmethod
|
||||
def remove_literal_from_annotation(cls, obj, literal):
|
||||
def remove_literal_from_annotation(cls, obj: bpy.types.Object, literal: ifcopenshell.entity_instance) -> None:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element:
|
||||
return
|
||||
@@ -617,7 +632,9 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
cls.remove_literal_from_annotation(obj, literal)
|
||||
|
||||
@classmethod
|
||||
def add_literal_to_annotation(cls, obj, Literal="Literal", Path="RIGHT", BoxAlignment="bottom-left"):
|
||||
def add_literal_to_annotation(
|
||||
cls, obj: bpy.types.Object, Literal: str = "Literal", Path: str = "RIGHT", BoxAlignment: str = "bottom-left"
|
||||
) -> Union[ifcopenshell.entity_instance, None]:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element:
|
||||
return
|
||||
@@ -831,7 +848,7 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
new.identification = schedule.Identification
|
||||
|
||||
@classmethod
|
||||
def import_sheets(cls):
|
||||
def import_sheets(cls) -> None:
|
||||
props = bpy.context.scene.DocProperties
|
||||
expanded_sheets = {s.ifc_definition_id for s in props.sheets if s.is_expanded}
|
||||
props.sheets.clear()
|
||||
@@ -868,7 +885,7 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
new.reference_type = reference_description
|
||||
|
||||
@classmethod
|
||||
def get_active_sheet(cls, context):
|
||||
def get_active_sheet(cls, context: bpy.types.Context) -> bpy.types.PropertyGroup:
|
||||
props = context.scene.DocProperties
|
||||
return next(s for s in props.sheets[: props.active_sheet_index + 1][::-1] if s.is_sheet)
|
||||
|
||||
@@ -904,7 +921,7 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
obj.BIMAssignedProductProperties.relating_product = None
|
||||
|
||||
@classmethod
|
||||
def open_with_user_command(cls, user_command, path):
|
||||
def open_with_user_command(cls, user_command: str, path: str) -> None:
|
||||
if user_command:
|
||||
commands = json.loads(user_command)
|
||||
replacements = {"path": path}
|
||||
@@ -920,15 +937,15 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
subprocess.call(("xdg-open", path))
|
||||
|
||||
@classmethod
|
||||
def open_spreadsheet(cls, uri):
|
||||
def open_spreadsheet(cls, uri: str) -> None:
|
||||
cls.open_with_user_command(tool.Blender.get_addon_preferences().spreadsheet_command, uri)
|
||||
|
||||
@classmethod
|
||||
def open_svg(cls, uri):
|
||||
def open_svg(cls, uri: str) -> None:
|
||||
cls.open_with_user_command(tool.Blender.get_addon_preferences().svg_command, uri)
|
||||
|
||||
@classmethod
|
||||
def open_layout_svg(cls, uri):
|
||||
def open_layout_svg(cls, uri: str) -> None:
|
||||
cls.open_with_user_command(tool.Blender.get_addon_preferences().layout_svg_command, uri)
|
||||
|
||||
@classmethod
|
||||
@@ -954,15 +971,17 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def set_drawing_collection_name(cls, drawing, collection):
|
||||
def set_drawing_collection_name(
|
||||
cls, drawing: ifcopenshell.entity_instance, collection: bpy.types.Collection
|
||||
) -> None:
|
||||
collection.name = tool.Loader.get_name(drawing)
|
||||
|
||||
@classmethod
|
||||
def set_name(cls, element, name):
|
||||
def set_name(cls, element: ifcopenshell.entity_instance, name: str) -> None:
|
||||
element.Name = name
|
||||
|
||||
@classmethod
|
||||
def show_decorations(cls):
|
||||
def show_decorations(cls) -> None:
|
||||
bpy.context.scene.DocProperties.should_draw_decorations = True
|
||||
|
||||
@classmethod
|
||||
@@ -1013,15 +1032,15 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
# TODO below this point is highly experimental prototype code with no tests
|
||||
|
||||
@classmethod
|
||||
def does_file_exist(cls, uri):
|
||||
def does_file_exist(cls, uri: str) -> bool:
|
||||
return os.path.exists(uri)
|
||||
|
||||
@classmethod
|
||||
def delete_file(cls, uri):
|
||||
def delete_file(cls, uri: str) -> None:
|
||||
os.remove(uri)
|
||||
|
||||
@classmethod
|
||||
def move_file(cls, src, dest):
|
||||
def move_file(cls, src: str, dest: str) -> None:
|
||||
try:
|
||||
shutil.move(src, dest)
|
||||
except:
|
||||
@@ -1029,7 +1048,9 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
shutil.copy(src, dest)
|
||||
|
||||
@classmethod
|
||||
def generate_drawing_name(cls, target_view, location_hint):
|
||||
def generate_drawing_name(
|
||||
cls, target_view: ifcopenshell.util.representation.TARGET_VIEW, location_hint: str
|
||||
) -> str:
|
||||
if target_view in ("PLAN_VIEW", "REFLECTED_PLAN_VIEW") and location_hint:
|
||||
location = tool.Ifc.get().by_id(location_hint)
|
||||
if target_view == "REFLECTED_PLAN_VIEW":
|
||||
@@ -1042,7 +1063,7 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
return target_view
|
||||
|
||||
@classmethod
|
||||
def get_default_layout_path(cls, identification, name):
|
||||
def get_default_layout_path(cls, identification: str, name: str) -> str:
|
||||
project = tool.Ifc.get().by_type("IfcProject")[0]
|
||||
layouts_dir = (
|
||||
ifcopenshell.util.element.get_pset(project, "BBIM_Documentation", "LayoutsDir")
|
||||
@@ -1051,7 +1072,7 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
return os.path.join(layouts_dir, cls.sanitise_filename(f"{identification} - {name}.svg")).replace("\\", "/")
|
||||
|
||||
@classmethod
|
||||
def get_default_sheet_path(cls, identification, name):
|
||||
def get_default_sheet_path(cls, identification: str, name: str) -> str:
|
||||
project = tool.Ifc.get().by_type("IfcProject")[0]
|
||||
sheets_dir = (
|
||||
ifcopenshell.util.element.get_pset(project, "BBIM_Documentation", "SheetsDir")
|
||||
@@ -1060,7 +1081,7 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
return os.path.join(sheets_dir, cls.sanitise_filename(f"{identification} - {name}.svg")).replace("\\", "/")
|
||||
|
||||
@classmethod
|
||||
def get_default_titleblock_path(cls, name):
|
||||
def get_default_titleblock_path(cls, name: str) -> str:
|
||||
project = tool.Ifc.get().by_type("IfcProject")[0]
|
||||
titleblocks_dir = (
|
||||
ifcopenshell.util.element.get_pset(project, "BBIM_Documentation", "TitleblocksDir")
|
||||
@@ -1069,7 +1090,7 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
return os.path.join(titleblocks_dir, cls.sanitise_filename(f"{name}.svg")).replace("\\", "/")
|
||||
|
||||
@classmethod
|
||||
def get_default_drawing_path(cls, name):
|
||||
def get_default_drawing_path(cls, name: str) -> str:
|
||||
project = tool.Ifc.get().by_type("IfcProject")[0]
|
||||
drawings_dir = (
|
||||
ifcopenshell.util.element.get_pset(project, "BBIM_Documentation", "DrawingsDir")
|
||||
@@ -1078,11 +1099,11 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
return os.path.join(drawings_dir, cls.sanitise_filename(f"{name}.svg")).replace("\\", "/")
|
||||
|
||||
@classmethod
|
||||
def sanitise_filename(cls, name):
|
||||
def sanitise_filename(cls, name: str) -> str:
|
||||
return "".join(x for x in name if (x.isalnum() or x in "._- "))
|
||||
|
||||
@classmethod
|
||||
def get_default_drawing_resource_path(cls, resource):
|
||||
def get_default_drawing_resource_path(cls, resource: str) -> Union[str, None]:
|
||||
project = tool.Ifc.get().by_type("IfcProject")[0]
|
||||
resource_path = ifcopenshell.util.element.get_pset(project, "BBIM_Documentation", f"{resource}Path") or getattr(
|
||||
bpy.context.scene.DocProperties, f"{resource.lower()}_path"
|
||||
@@ -1091,12 +1112,12 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
return resource_path.replace("\\", "/")
|
||||
|
||||
@classmethod
|
||||
def get_default_shading_style(cls):
|
||||
def get_default_shading_style(cls) -> str:
|
||||
dprops = bpy.context.scene.DocProperties
|
||||
return dprops.shadingstyle_default
|
||||
|
||||
@classmethod
|
||||
def setup_shading_styles_path(cls, resource_path):
|
||||
def setup_shading_styles_path(cls, resource_path: str) -> None:
|
||||
resource_path = tool.Ifc.resolve_uri(resource_path)
|
||||
os.makedirs(os.path.dirname(resource_path), exist_ok=True)
|
||||
if not os.path.exists(resource_path):
|
||||
@@ -1387,7 +1408,7 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
clipping = is_ortho and target_view in ("PLAN_VIEW", "REFLECTED_PLAN_VIEW")
|
||||
elevating = is_ortho and target_view in ("ELEVATION_VIEW", "SECTION_VIEW")
|
||||
|
||||
def clone(src):
|
||||
def clone(src: bpy.types.Object) -> bpy.types.Object:
|
||||
dst = src.copy()
|
||||
dst.data = dst.data.copy()
|
||||
dst.name = dst.name.replace("IfcGridAxis/", "")
|
||||
@@ -1395,13 +1416,13 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
dst.data.BIMMeshProperties.ifc_definition_id = 0
|
||||
return dst
|
||||
|
||||
def disassemble(obj):
|
||||
def disassemble(obj: bpy.types.Object) -> tuple[bpy.types.Object, bmesh.types.BMesh]:
|
||||
mesh = bmesh.new()
|
||||
mesh.verts.ensure_lookup_table()
|
||||
mesh.from_mesh(obj.data)
|
||||
return obj, mesh
|
||||
|
||||
def assemble(obj, mesh):
|
||||
def assemble(obj: bpy.types.Object, mesh: bmesh.types.BMesh) -> bpy.types.Object:
|
||||
mesh.to_mesh(obj.data)
|
||||
return obj
|
||||
|
||||
@@ -1413,7 +1434,7 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
obj.matrix_world.translation += annotation_offset
|
||||
return obj, mesh
|
||||
|
||||
def clip_to_camera_boundary(mesh):
|
||||
def clip_to_camera_boundary(mesh: bmesh.types.BMesh) -> bmesh.types.BMesh:
|
||||
mesh.verts.ensure_lookup_table()
|
||||
points = [v.co for v in mesh.verts[0:2]]
|
||||
points = helper.clip_segment(bounds, points)
|
||||
@@ -1423,7 +1444,7 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
mesh.verts[1].co = points[1]
|
||||
return mesh
|
||||
|
||||
def draw_grids_vertically(mesh):
|
||||
def draw_grids_vertically(mesh: bmesh.types.BMesh) -> bmesh.types.BMesh:
|
||||
mesh.verts.ensure_lookup_table()
|
||||
points = [v.co for v in mesh.verts[0:2]]
|
||||
points = helper.elevate_segment(bounds, points)
|
||||
@@ -1534,11 +1555,11 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
return text
|
||||
|
||||
@classmethod
|
||||
def sync_object_representation(cls, obj):
|
||||
def sync_object_representation(cls, obj: bpy.types.Object) -> None:
|
||||
bpy.ops.bim.update_representation(obj=obj.name)
|
||||
|
||||
@classmethod
|
||||
def sync_object_placement(cls, obj):
|
||||
def sync_object_placement(cls, obj: bpy.types.Object) -> Union[ifcopenshell.entity_instance, None]:
|
||||
blender_matrix = np.array(obj.matrix_world)
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if (obj.scale - mathutils.Vector((1.0, 1.0, 1.0))).length > 1e-4:
|
||||
@@ -1552,7 +1573,7 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
return element
|
||||
|
||||
@classmethod
|
||||
def sync_grid_axis_object_placement(cls, obj, element):
|
||||
def sync_grid_axis_object_placement(cls, obj: bpy.types.Object, element: ifcopenshell.entity_instance) -> None:
|
||||
grid = (element.PartOfU or element.PartOfV or element.PartOfW)[0]
|
||||
grid_obj = tool.Ifc.get_object(grid)
|
||||
if grid_obj:
|
||||
@@ -1568,11 +1589,11 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
return document.HasDocumentReferences or []
|
||||
|
||||
@classmethod
|
||||
def get_references_with_location(cls, location):
|
||||
def get_references_with_location(cls, location: Union[str, None]) -> list[ifcopenshell.entity_instance]:
|
||||
return [r for r in tool.Ifc.get().by_type("IfcDocumentReference") if r.Location == location]
|
||||
|
||||
@classmethod
|
||||
def update_embedded_svg_location(cls, uri, reference, new_location):
|
||||
def update_embedded_svg_location(cls, uri: str, reference: ifcopenshell.entity_instance, new_location: str) -> None:
|
||||
tree = etree.parse(uri)
|
||||
root = tree.getroot()
|
||||
rel_location = os.path.relpath(new_location, os.path.dirname(uri))
|
||||
@@ -1612,11 +1633,13 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
return attributes
|
||||
|
||||
@classmethod
|
||||
def get_reference_location(cls, reference):
|
||||
def get_reference_location(cls, reference: ifcopenshell.entity_instance) -> Union[str, None]:
|
||||
return reference.Location
|
||||
|
||||
@classmethod
|
||||
def get_reference_element(cls, reference):
|
||||
def get_reference_element(
|
||||
cls, reference: ifcopenshell.entity_instance
|
||||
) -> Union[ifcopenshell.entity_instance, None]:
|
||||
if tool.Ifc.get_schema() == "IFC2X3":
|
||||
refs = [r for r in tool.Ifc.get().by_type("IfcRelAssociatesDocument") if r.RelatingDocument == reference]
|
||||
else:
|
||||
@@ -1625,12 +1648,12 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
return refs[0].RelatedObjects[0]
|
||||
|
||||
@classmethod
|
||||
def get_drawing_human_scale(cls, drawing):
|
||||
def get_drawing_human_scale(cls, drawing: ifcopenshell.entity_instance) -> str:
|
||||
pset = ifcopenshell.util.element.get_pset(drawing, "EPset_Drawing") or {}
|
||||
return "NTS" if pset.get("IsNTS", False) else pset.get("HumanScale", "NTS")
|
||||
|
||||
@classmethod
|
||||
def get_drawing_metadata(cls, drawing):
|
||||
def get_drawing_metadata(cls, drawing: ifcopenshell.entity_instance) -> list[str]:
|
||||
# fmt: off
|
||||
return [
|
||||
v.strip()
|
||||
@@ -1642,7 +1665,7 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
# fmt: on
|
||||
|
||||
@classmethod
|
||||
def get_annotation_z_index(cls, drawing):
|
||||
def get_annotation_z_index(cls, drawing: ifcopenshell.entity_instance) -> float:
|
||||
return ifcopenshell.util.element.get_pset(drawing, "EPset_Annotation", "ZIndex") or 0
|
||||
|
||||
@classmethod
|
||||
@@ -1654,11 +1677,11 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
return symbol
|
||||
|
||||
@classmethod
|
||||
def has_linework(cls, drawing):
|
||||
def has_linework(cls, drawing: ifcopenshell.entity_instance) -> bool:
|
||||
return ifcopenshell.util.element.get_psets(drawing).get("EPset_Drawing", {}).get("HasLinework", False)
|
||||
|
||||
@classmethod
|
||||
def has_annotation(cls, drawing):
|
||||
def has_annotation(cls, drawing: ifcopenshell.entity_instance) -> bool:
|
||||
return ifcopenshell.util.element.get_psets(drawing).get("EPset_Drawing", {}).get("HasAnnotation", False)
|
||||
|
||||
@classmethod
|
||||
@@ -1723,19 +1746,21 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
return elements
|
||||
|
||||
@classmethod
|
||||
def get_annotation_element(cls, element):
|
||||
def get_annotation_element(cls, element: ifcopenshell.entity_instance) -> Union[ifcopenshell.entity_instance, None]:
|
||||
for rel in element.HasAssignments:
|
||||
if rel.is_a("IfcRelAssignsToProduct"):
|
||||
return rel.RelatingProduct
|
||||
|
||||
@classmethod
|
||||
def get_drawing_reference(cls, drawing):
|
||||
def get_drawing_reference(cls, drawing: ifcopenshell.entity_instance) -> Union[ifcopenshell.entity_instance, None]:
|
||||
for rel in drawing.HasAssociations:
|
||||
if rel.is_a("IfcRelAssociatesDocument"):
|
||||
return rel.RelatingDocument
|
||||
|
||||
@classmethod
|
||||
def get_reference_document(cls, reference):
|
||||
def get_reference_document(
|
||||
cls, reference: ifcopenshell.entity_instance
|
||||
) -> Union[ifcopenshell.entity_instance, None]:
|
||||
if tool.Ifc.get_schema() == "IFC2X3":
|
||||
return reference.ReferenceToDocument[0]
|
||||
return reference.ReferencedDocument
|
||||
@@ -1749,13 +1774,13 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
tool.Ifc.get_object(product).select_set(True)
|
||||
|
||||
@classmethod
|
||||
def is_drawing_active(cls):
|
||||
def is_drawing_active(cls) -> bool:
|
||||
camera = bpy.context.scene.camera
|
||||
area = tool.Blender.get_view3d_area()
|
||||
return camera and camera.type == "CAMERA" and camera.BIMObjectProperties.ifc_definition_id and area
|
||||
|
||||
@classmethod
|
||||
def is_camera_orthographic(cls):
|
||||
def is_camera_orthographic(cls) -> bool:
|
||||
camera = bpy.context.scene.camera
|
||||
return True if (camera and camera.data.type == "ORTHO") else False
|
||||
|
||||
@@ -1764,7 +1789,7 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
return drawing.id() == bpy.context.scene.DocProperties.active_drawing_id
|
||||
|
||||
@classmethod
|
||||
def run_drawing_activate_model(cls):
|
||||
def run_drawing_activate_model(cls) -> None:
|
||||
bpy.ops.bim.activate_model()
|
||||
|
||||
@classmethod
|
||||
@@ -1906,7 +1931,15 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def is_in_camera_view(cls, obj, camera_inverse_matrix, x, y, clip_start, clip_end):
|
||||
def is_in_camera_view(
|
||||
cls,
|
||||
obj: bpy.types.Object,
|
||||
camera_inverse_matrix: Matrix,
|
||||
x: float,
|
||||
y: float,
|
||||
clip_start: float,
|
||||
clip_end: float,
|
||||
) -> bool:
|
||||
local_bbox = [camera_inverse_matrix @ obj.matrix_world @ Vector(v) for v in obj.bound_box]
|
||||
local_x = [v.x for v in local_bbox]
|
||||
local_y = [v.y for v in local_bbox]
|
||||
@@ -1921,14 +1954,14 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def is_intersecting_camera(cls, obj, camera):
|
||||
def is_intersecting_camera(cls, obj: bpy.types.Object, camera: bpy.types.Object) -> bool:
|
||||
# Based on separating axis theorem
|
||||
plane_co = camera.matrix_world.translation
|
||||
plane_no = camera.matrix_world.col[2].xyz
|
||||
return cls.is_intersecting_plane(obj, plane_co, plane_no)
|
||||
|
||||
@classmethod
|
||||
def is_intersecting_plane(cls, obj, plane_co, plane_no):
|
||||
def is_intersecting_plane(cls, obj: bpy.types.Object, plane_co: Vector, plane_no: Vector) -> bool:
|
||||
# Broadphase check using the bounding box
|
||||
bounding_box_world_coords = [obj.matrix_world @ Vector(coord) for coord in obj.bound_box]
|
||||
bounding_box_signed_distances = [plane_no.dot(v - plane_co) for v in bounding_box_world_coords]
|
||||
@@ -1958,7 +1991,7 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
return pos_exists and neg_exists
|
||||
|
||||
@classmethod
|
||||
def bisect_mesh(cls, obj, camera):
|
||||
def bisect_mesh(cls, obj: bpy.types.Object, camera: bpy.types.Object) -> tuple[list[Vector], list[list[int]]]:
|
||||
camera_matrix = obj.matrix_world.inverted() @ camera.matrix_world
|
||||
plane_co = camera_matrix.translation
|
||||
plane_no = camera_matrix.col[2].xyz
|
||||
@@ -1969,7 +2002,9 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
return cls.bisect_mesh_with_plane(obj, plane_co, plane_no, global_offset=global_offset)
|
||||
|
||||
@classmethod
|
||||
def bisect_mesh_with_plane(cls, obj, plane_co, plane_no, global_offset=None):
|
||||
def bisect_mesh_with_plane(
|
||||
cls, obj: bpy.types.Object, plane_co: Vector, plane_no: Vector, global_offset: Optional[Vector] = None
|
||||
) -> tuple[list[Vector], list[list[int]]]:
|
||||
if global_offset is None:
|
||||
global_offset = Vector()
|
||||
|
||||
@@ -1980,9 +2015,9 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
geom = bm.verts[:] + bm.edges[:] + bm.faces[:]
|
||||
results = bmesh.ops.bisect_plane(bm, geom=geom, dist=0.0001, plane_co=plane_co, plane_no=plane_no)
|
||||
|
||||
vert_map = {}
|
||||
verts = []
|
||||
edges = []
|
||||
vert_map: dict[int, int] = {}
|
||||
verts: list[Vector] = []
|
||||
edges: list[list[int]] = []
|
||||
i = 0
|
||||
for geom in results["geom_cut"]:
|
||||
if isinstance(geom, bmesh.types.BMVert):
|
||||
@@ -1998,12 +2033,12 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
return verts, edges
|
||||
|
||||
@classmethod
|
||||
def get_scale_ratio(cls, scale):
|
||||
def get_scale_ratio(cls, scale: str) -> float:
|
||||
numerator, denominator = scale.split("/")
|
||||
return float(numerator) / float(denominator)
|
||||
|
||||
@classmethod
|
||||
def get_diagram_scale(cls, obj):
|
||||
def get_diagram_scale(cls, obj: bpy.types.Object) -> dict[str, float]:
|
||||
props = obj.data.BIMCameraProperties
|
||||
scale = props.diagram_scale
|
||||
if scale != "CUSTOM":
|
||||
@@ -2029,7 +2064,7 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
return {"HumanScale": human_scale, "Scale": scale}
|
||||
|
||||
@classmethod
|
||||
def convert_scale_string(cls, value):
|
||||
def convert_scale_string(cls, value: str) -> float:
|
||||
try:
|
||||
return float(value)
|
||||
except:
|
||||
@@ -2060,7 +2095,7 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
return result * 0.0254
|
||||
|
||||
@classmethod
|
||||
def extend_line(cls, start, end, distance):
|
||||
def extend_line(cls, start: Vector, end: Vector, distance: float) -> tuple[list[float], list[float]]:
|
||||
start = np.array(start)
|
||||
end = np.array(end)
|
||||
direction = end - start
|
||||
@@ -2068,8 +2103,8 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
return (start - offset).tolist(), (end + offset).tolist()
|
||||
|
||||
@classmethod
|
||||
def get_sheet_references(cls, drawing):
|
||||
sheet_references = []
|
||||
def get_sheet_references(cls, drawing: ifcopenshell.entity_instance) -> list[ifcopenshell.entity_instance]:
|
||||
sheet_references: list[ifcopenshell.entity_instance] = []
|
||||
drawing_reference = cls.get_drawing_document(drawing)
|
||||
for sheet in tool.Ifc.get().by_type("IfcDocumentInformation"):
|
||||
if not sheet.Scope == "SHEET":
|
||||
@@ -2082,7 +2117,7 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
return sheet_references
|
||||
|
||||
@classmethod
|
||||
def get_camera_matrix(cls, camera):
|
||||
def get_camera_matrix(cls, camera: bpy.types.Object) -> Matrix:
|
||||
matrix_world = camera.matrix_world.copy().normalized()
|
||||
location, rotation, scale = matrix_world.decompose()
|
||||
if scale.x < 0 or scale.y < 0 or scale.z < 0:
|
||||
|
||||
@@ -927,6 +927,7 @@ class Geometry(blenderbim.core.tool.Geometry):
|
||||
|
||||
new_representation = None
|
||||
for r in cls.get_representations_iter(element):
|
||||
r = tool.Geometry.resolve_mapped_representation(r)
|
||||
if r != representation:
|
||||
new_representation = r
|
||||
break
|
||||
@@ -1227,3 +1228,9 @@ class Geometry(blenderbim.core.tool.Geometry):
|
||||
return
|
||||
material_style = tool.Material.get_style(materials[0])
|
||||
return material_style
|
||||
|
||||
@classmethod
|
||||
def should_use_immediate_representation(cls, element: ifcopenshell.entity_instance, apply_openings: bool) -> bool:
|
||||
use_immediate_repr = apply_openings and bool(getattr(element, "HasOpenings", None))
|
||||
use_immediate_repr = use_immediate_repr or cls.has_material_style_override(element)
|
||||
return use_immediate_repr
|
||||
|
||||
@@ -21,6 +21,9 @@ import json
|
||||
import numpy as np
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api.georeference
|
||||
import ifcopenshell.util.geolocation
|
||||
import ifcopenshell.util.placement
|
||||
import ifcopenshell.util.unit
|
||||
import blenderbim.core.tool
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.bim.helper
|
||||
|
||||
@@ -1,3 +1,22 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on 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
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from __future__ import annotations
|
||||
import os
|
||||
import re
|
||||
import bpy
|
||||
@@ -5,6 +24,7 @@ import logging
|
||||
from blenderbim.bim import import_ifc
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
import blenderbim.tool as tool
|
||||
from typing import TYPE_CHECKING, Union
|
||||
|
||||
# allows git import even if git executable isn't found
|
||||
os.environ["GIT_PYTHON_REFRESH"] = "quiet"
|
||||
@@ -13,15 +33,20 @@ try:
|
||||
except ImportError:
|
||||
print("Warning: GitPython not available.")
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import git
|
||||
|
||||
|
||||
class IfcGit:
|
||||
STEP_IDS = dict[str, set[int]]
|
||||
|
||||
@classmethod
|
||||
def init_repo(cls, path_dir):
|
||||
def init_repo(cls, path_dir: str) -> None:
|
||||
IfcGitRepo.repo = git.Repo.init(path_dir)
|
||||
cls.config_info_attributes(IfcGitRepo.repo)
|
||||
|
||||
@classmethod
|
||||
def clone_repo(cls, remote_url, local_folder):
|
||||
def clone_repo(cls, remote_url: str, local_folder: str) -> git.Repo:
|
||||
IfcGitRepo.repo = git.Repo.clone_from(
|
||||
url=remote_url,
|
||||
to_path=local_folder,
|
||||
@@ -30,7 +55,7 @@ class IfcGit:
|
||||
return IfcGitRepo.repo
|
||||
|
||||
@classmethod
|
||||
def load_anyifc(cls, repo):
|
||||
def load_anyifc(cls, repo: git.Repo) -> bool:
|
||||
working_dir = repo.working_dir
|
||||
for item in os.listdir(working_dir):
|
||||
path = os.path.join(working_dir, item)
|
||||
@@ -40,11 +65,11 @@ class IfcGit:
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def get_path_dir(cls, path_ifc):
|
||||
def get_path_dir(cls, path_ifc: str) -> str:
|
||||
return os.path.abspath(os.path.dirname(path_ifc))
|
||||
|
||||
@classmethod
|
||||
def repo_from_path(cls, path):
|
||||
def repo_from_path(cls, path: str) -> Union[git.Repo, None]:
|
||||
"""Returns a Git repository object or None"""
|
||||
|
||||
if os.path.isdir(path):
|
||||
@@ -72,7 +97,7 @@ class IfcGit:
|
||||
return repo
|
||||
|
||||
@classmethod
|
||||
def add_file_to_repo(cls, repo, path_file):
|
||||
def add_file_to_repo(cls, repo: git.Repo, path_file: str) -> None:
|
||||
if os.name == "nt":
|
||||
cls.dos2unix(path_file)
|
||||
repo.index.add(path_file)
|
||||
@@ -80,11 +105,11 @@ class IfcGit:
|
||||
bpy.ops.ifcgit.refresh()
|
||||
|
||||
@classmethod
|
||||
def git_checkout(cls, path_file):
|
||||
def git_checkout(cls, path_file: str) -> None:
|
||||
IfcGitRepo.repo.git.checkout(path_file)
|
||||
|
||||
@classmethod
|
||||
def checkout_new_branch(cls, path_file):
|
||||
def checkout_new_branch(cls, path_file: str) -> None:
|
||||
"""Create a branch and move uncommitted changes to this branch"""
|
||||
props = bpy.context.scene.IfcGitProperties
|
||||
if props.new_branch_name:
|
||||
@@ -94,7 +119,7 @@ class IfcGit:
|
||||
bpy.ops.ifcgit.refresh()
|
||||
|
||||
@classmethod
|
||||
def git_commit(cls, path_file):
|
||||
def git_commit(cls, path_file: str) -> None:
|
||||
props = bpy.context.scene.IfcGitProperties
|
||||
repo = IfcGitRepo.repo
|
||||
if os.name == "nt":
|
||||
@@ -104,7 +129,7 @@ class IfcGit:
|
||||
props.commit_message = ""
|
||||
|
||||
@classmethod
|
||||
def add_tag(cls, repo):
|
||||
def add_tag(cls, repo: git.Repo) -> None:
|
||||
props = bpy.context.scene.IfcGitProperties
|
||||
item = props.ifcgit_commits[props.commit_index]
|
||||
repo.create_tag(props.new_tag_name, ref=item.hexsha, message=props.new_tag_message)
|
||||
@@ -112,19 +137,19 @@ class IfcGit:
|
||||
props.new_tag_message = ""
|
||||
|
||||
@classmethod
|
||||
def delete_tag(cls, repo, tag_name):
|
||||
def delete_tag(cls, repo: git.Repo, tag_name: git.TagReference) -> None:
|
||||
if tag_name in repo.tags:
|
||||
repo.delete_tag(tag_name)
|
||||
|
||||
@classmethod
|
||||
def add_remote(cls, repo):
|
||||
def add_remote(cls, repo: git.Repo) -> None:
|
||||
props = bpy.context.scene.IfcGitProperties
|
||||
repo.create_remote(name=props.remote_name, url=props.remote_url)
|
||||
props.remote_name = ""
|
||||
props.remote_url = ""
|
||||
|
||||
@classmethod
|
||||
def delete_remote(cls, repo):
|
||||
def delete_remote(cls, repo: git.Repo) -> None:
|
||||
props = bpy.context.scene.IfcGitProperties
|
||||
remote_name = props.select_remote
|
||||
if remote_name in repo.remotes:
|
||||
@@ -133,7 +158,7 @@ class IfcGit:
|
||||
props.select_remote = repo.remotes[0].name
|
||||
|
||||
@classmethod
|
||||
def push(cls, repo, remote_name, branch_name):
|
||||
def push(cls, repo: git.Repo, remote_name: str, branch_name: str) -> Union[str, None]:
|
||||
cls.config_push(repo)
|
||||
remote = repo.remotes[remote_name]
|
||||
try:
|
||||
@@ -142,7 +167,7 @@ class IfcGit:
|
||||
return exc.stderr
|
||||
|
||||
@classmethod
|
||||
def create_new_branch(cls):
|
||||
def create_new_branch(cls) -> None:
|
||||
"""Convert a detached HEAD into a branch"""
|
||||
props = bpy.context.scene.IfcGitProperties
|
||||
repo = IfcGitRepo.repo
|
||||
@@ -154,7 +179,7 @@ class IfcGit:
|
||||
bpy.ops.ifcgit.refresh()
|
||||
|
||||
@classmethod
|
||||
def clear_commits_list(cls):
|
||||
def clear_commits_list(cls) -> None:
|
||||
area = next(area for area in bpy.context.screen.areas if area.type == "VIEW_3D")
|
||||
area.spaces[0].shading.color_type = "MATERIAL"
|
||||
props = bpy.context.scene.IfcGitProperties
|
||||
@@ -163,7 +188,7 @@ class IfcGit:
|
||||
props.ifcgit_commits.clear()
|
||||
|
||||
@classmethod
|
||||
def get_commits_list(cls, path_ifc, lookup):
|
||||
def get_commits_list(cls, path_ifc: str, lookup: dict[str, Any]) -> None:
|
||||
|
||||
props = bpy.context.scene.IfcGitProperties
|
||||
repo = cls.repo_from_path(path_ifc)
|
||||
@@ -205,14 +230,14 @@ class IfcGit:
|
||||
list_item.tags[-1].message = tag.tag.message
|
||||
|
||||
@classmethod
|
||||
def refresh_revision_list(cls, path_ifc):
|
||||
def refresh_revision_list(cls, path_ifc: str) -> None:
|
||||
repo = cls.repo_from_path(path_ifc)
|
||||
cls.clear_commits_list()
|
||||
lookup = cls.tags_by_hexsha(repo)
|
||||
cls.get_commits_list(path_ifc, lookup)
|
||||
|
||||
@classmethod
|
||||
def is_valid_ref_format(cls, string):
|
||||
def is_valid_ref_format(cls, string: str) -> Union[re.Match[str], None]:
|
||||
"""Check a bare branch or tag name is valid"""
|
||||
|
||||
return re.match(
|
||||
@@ -221,7 +246,7 @@ class IfcGit:
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def load_project(cls, path_ifc=""):
|
||||
def load_project(cls, path_ifc: str = "") -> None:
|
||||
"""Clear and load an ifc project"""
|
||||
|
||||
if path_ifc:
|
||||
@@ -248,7 +273,7 @@ class IfcGit:
|
||||
bpy.ops.object.select_all(action="DESELECT")
|
||||
|
||||
@classmethod
|
||||
def branches_by_hexsha(cls, repo):
|
||||
def branches_by_hexsha(cls, repo: git.Repo) -> dict[str, Any]:
|
||||
"""reverse lookup for branches"""
|
||||
|
||||
result = {}
|
||||
@@ -267,7 +292,7 @@ class IfcGit:
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
def tags_by_hexsha(cls, repo):
|
||||
def tags_by_hexsha(cls, repo: git.Repo) -> dict[str, Any]:
|
||||
"""reverse lookup for tags"""
|
||||
|
||||
result = {}
|
||||
@@ -279,7 +304,7 @@ class IfcGit:
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
def ifc_diff_ids(cls, repo, hash_a, hash_b, path_ifc):
|
||||
def ifc_diff_ids(cls, repo: git.Repo, hash_a: str, hash_b: str, path_ifc: str) -> STEP_IDS:
|
||||
"""Given two revision hashes and a filename, retrieve"""
|
||||
"""step-ids of modified, added and removed entities"""
|
||||
|
||||
@@ -309,7 +334,7 @@ class IfcGit:
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def get_revisions_step_ids(cls):
|
||||
def get_revisions_step_ids(cls) -> Union[STEP_IDS, None]:
|
||||
|
||||
path_ifc = bpy.data.scenes["Scene"].BIMProperties.ifc_file
|
||||
props = bpy.context.scene.IfcGitProperties
|
||||
@@ -341,7 +366,7 @@ class IfcGit:
|
||||
return step_ids
|
||||
|
||||
@classmethod
|
||||
def get_modified_shape_object_step_ids(cls, step_ids):
|
||||
def get_modified_shape_object_step_ids(cls, step_ids: STEP_IDS) -> STEP_IDS:
|
||||
model = tool.Ifc.get()
|
||||
modified_shape_object_step_ids = {"modified": []}
|
||||
|
||||
@@ -353,7 +378,7 @@ class IfcGit:
|
||||
return modified_shape_object_step_ids
|
||||
|
||||
@classmethod
|
||||
def update_step_ids(cls, step_ids, modified_shape_object_step_ids):
|
||||
def update_step_ids(cls, step_ids: STEP_IDS, modified_shape_object_step_ids: STEP_IDS) -> STEP_IDS:
|
||||
|
||||
final_step_ids = {}
|
||||
final_step_ids["added"] = step_ids["added"]
|
||||
@@ -362,7 +387,7 @@ class IfcGit:
|
||||
return final_step_ids
|
||||
|
||||
@classmethod
|
||||
def colourise(cls, step_ids):
|
||||
def colourise(cls, step_ids: STEP_IDS) -> None:
|
||||
area = next(area for area in bpy.context.screen.areas if area.type == "VIEW_3D")
|
||||
area.spaces[0].shading.color_type = "OBJECT"
|
||||
bpy.ops.object.select_all(action="DESELECT")
|
||||
@@ -384,7 +409,7 @@ class IfcGit:
|
||||
obj.color = (1.0, 1.0, 1.0, 0.5)
|
||||
|
||||
@classmethod
|
||||
def switch_to_revision_item(cls):
|
||||
def switch_to_revision_item(cls) -> None:
|
||||
props = bpy.context.scene.IfcGitProperties
|
||||
repo = IfcGitRepo.repo
|
||||
item = props.ifcgit_commits[props.commit_index]
|
||||
@@ -399,13 +424,13 @@ class IfcGit:
|
||||
repo.git.checkout(item.hexsha)
|
||||
|
||||
@classmethod
|
||||
def delete_collection(cls, blender_collection):
|
||||
def delete_collection(cls, blender_collection: bpy.types.Collection) -> None:
|
||||
for obj in blender_collection.objects:
|
||||
bpy.data.objects.remove(obj, do_unlink=True)
|
||||
bpy.data.collections.remove(blender_collection)
|
||||
|
||||
@classmethod
|
||||
def is_valid_branch_name(cls, new_branch_name):
|
||||
def is_valid_branch_name(cls, new_branch_name: str):
|
||||
"""Check if a branch name is valid and doesn't conflict with existing branches"""
|
||||
if not cls.is_valid_ref_format(new_branch_name):
|
||||
return False
|
||||
@@ -414,7 +439,7 @@ class IfcGit:
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def config_ifcmerge(cls):
|
||||
def config_ifcmerge(cls) -> None:
|
||||
config_reader = IfcGitRepo.repo.config_reader()
|
||||
section = 'mergetool "ifcmerge"'
|
||||
if not config_reader.has_section(section):
|
||||
@@ -428,7 +453,7 @@ class IfcGit:
|
||||
config_writer.set_value(section, "trustExitCode", True)
|
||||
|
||||
@classmethod
|
||||
def config_push(cls, repo):
|
||||
def config_push(cls, repo: git.Repo) -> None:
|
||||
"""Set push.autoSetupRemote"""
|
||||
config_reader = repo.config_reader()
|
||||
if not config_reader.has_section("push"):
|
||||
@@ -437,7 +462,7 @@ class IfcGit:
|
||||
config_writer.set_value("push", "autoSetupRemote", True)
|
||||
|
||||
@classmethod
|
||||
def config_info_attributes(cls, repo):
|
||||
def config_info_attributes(cls, repo: git.Repo) -> None:
|
||||
"""Set IFC files as text in .git/info/attributes"""
|
||||
path_attributes = os.path.join(repo.git_dir, "info", "attributes")
|
||||
if not os.path.exists(path_attributes):
|
||||
@@ -446,7 +471,7 @@ class IfcGit:
|
||||
f.write("*.ifc text")
|
||||
|
||||
@classmethod
|
||||
def dos2unix(cls, path_file):
|
||||
def dos2unix(cls, path_file: str) -> None:
|
||||
with open(path_file, "rb") as infile:
|
||||
content = infile.read()
|
||||
with open(path_file, "wb") as output:
|
||||
@@ -454,7 +479,7 @@ class IfcGit:
|
||||
output.write(line + b"\n")
|
||||
|
||||
@classmethod
|
||||
def execute_merge(cls, path_ifc, operator):
|
||||
def execute_merge(cls, path_ifc: str, operator: bpy.types.Operator) -> Union[None, False]:
|
||||
props = bpy.context.scene.IfcGitProperties
|
||||
repo = IfcGitRepo.repo
|
||||
item = props.ifcgit_commits[props.commit_index]
|
||||
@@ -498,7 +523,7 @@ class IfcGit:
|
||||
cls.refresh_revision_list(path_ifc)
|
||||
|
||||
@classmethod
|
||||
def entity_log(cls, path_ifc, step_id):
|
||||
def entity_log(cls, path_ifc: str, step_id: int) -> str:
|
||||
"""Raw git log for this entity"""
|
||||
repo = IfcGitRepo.repo
|
||||
if not repo:
|
||||
@@ -514,4 +539,4 @@ class IfcGit:
|
||||
|
||||
|
||||
class IfcGitRepo:
|
||||
repo = None
|
||||
repo: git.Repo = None
|
||||
|
||||
@@ -695,7 +695,7 @@ class Loader(blenderbim.core.tool.Loader):
|
||||
cls.settings.false_origin = ifcopenshell.util.geolocation.auto_xyz2enh(
|
||||
ifc_file, *offset_point, should_return_in_map_units=False
|
||||
)
|
||||
if angle := ifcopenshell.util.geolocation.get_grid_north(ifc_file):
|
||||
if (angle := ifcopenshell.util.geolocation.get_grid_north(ifc_file)) and not tool.Cad.is_x(angle, 0):
|
||||
cls.settings.project_north = angle
|
||||
cls.set_manual_blender_offset(ifc_file)
|
||||
|
||||
|
||||
@@ -114,10 +114,10 @@ class Spatial(blenderbim.core.tool.Spatial):
|
||||
|
||||
@classmethod
|
||||
def run_spatial_assign_container(
|
||||
cls, structure_obj: bpy.types.Object, element_obj: bpy.types.Object
|
||||
cls, container: ifcopenshell.entity_instance, element_obj: bpy.types.Object
|
||||
) -> Union[ifcopenshell.entity_instance, None]:
|
||||
return blenderbim.core.spatial.assign_container(
|
||||
tool.Ifc, tool.Collector, tool.Spatial, structure_obj=structure_obj, element_obj=element_obj
|
||||
tool.Ifc, tool.Collector, tool.Spatial, container=container, element_obj=element_obj
|
||||
)
|
||||
|
||||
@classmethod
|
||||
@@ -392,10 +392,11 @@ class Spatial(blenderbim.core.tool.Spatial):
|
||||
continue
|
||||
|
||||
old_mesh = obj.data
|
||||
assert isinstance(old_mesh, bpy.types.Mesh)
|
||||
if visible_element.HasOpenings:
|
||||
new_mesh = cls.get_gross_mesh_from_element(visible_element)
|
||||
else:
|
||||
new_mesh = obj.data.copy()
|
||||
new_mesh = old_mesh.copy()
|
||||
obj.data = new_mesh
|
||||
|
||||
# Boundary objects are likely triangulated. If a triangulated quad
|
||||
@@ -463,15 +464,15 @@ class Spatial(blenderbim.core.tool.Spatial):
|
||||
return mesh
|
||||
|
||||
@classmethod
|
||||
def get_x_y_z_h_mat_from_active_obj(cls, active_obj: bpy.types.Object) -> tuple[float, float, float, float, Matrix]:
|
||||
mat = active_obj.matrix_world
|
||||
local_bbox_center = 0.125 * sum((Vector(b) for b in active_obj.bound_box), Vector())
|
||||
def get_x_y_z_h_mat_from_obj(cls, obj: bpy.types.Object) -> tuple[float, float, float, float, Matrix]:
|
||||
mat = obj.matrix_world
|
||||
local_bbox_center = 0.125 * sum((Vector(b) for b in obj.bound_box), Vector())
|
||||
global_bbox_center = mat @ local_bbox_center
|
||||
x = global_bbox_center.x
|
||||
y = global_bbox_center.y
|
||||
z = (mat @ Vector(active_obj.bound_box[0])).z
|
||||
z = (mat @ Vector(obj.bound_box[0])).z
|
||||
|
||||
h = active_obj.dimensions.z
|
||||
h = obj.dimensions.z
|
||||
return x, y, z, h, mat
|
||||
|
||||
@classmethod
|
||||
@@ -489,9 +490,15 @@ class Spatial(blenderbim.core.tool.Spatial):
|
||||
selected_objects = bpy.context.selected_objects
|
||||
boundary_elements = cls.get_boundary_elements(selected_objects)
|
||||
polys = cls.get_polygons(boundary_elements)
|
||||
converted_tolerance = cls.get_converted_tolerance(tolerance=0.03)
|
||||
union = shapely.ops.unary_union(polys).buffer(converted_tolerance, cap_style=2, join_style=2)
|
||||
union = cls.get_purged_inner_holes_poly(union_geom=union, min_area=cls.get_converted_tolerance(tolerance=0.1))
|
||||
converted_tolerance = cls.get_converted_tolerance(tolerance_si=0.03)
|
||||
union = shapely.ops.unary_union(polys).buffer(
|
||||
converted_tolerance,
|
||||
cap_style=shapely.constructive.BufferCapStyle.flat,
|
||||
join_style=shapely.constructive.BufferJoinStyle.mitre,
|
||||
)
|
||||
union = cls.get_purged_inner_holes_poly(
|
||||
union_geom=union, min_area=cls.get_converted_tolerance(tolerance_si=0.1)
|
||||
)
|
||||
return union
|
||||
|
||||
@classmethod
|
||||
@@ -521,28 +528,20 @@ class Spatial(blenderbim.core.tool.Spatial):
|
||||
|
||||
@classmethod
|
||||
def get_obj_base_points(cls, obj: bpy.types.Object) -> dict[str, tuple[float, float]]:
|
||||
x_values = [(obj.matrix_world @ Vector(v)).x for v in obj.bound_box]
|
||||
y_values = [(obj.matrix_world @ Vector(v)).y for v in obj.bound_box]
|
||||
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
bbox_ws = [obj.matrix_world @ Vector(v) / si_conversion for v in obj.bound_box]
|
||||
return {
|
||||
"low_left": (x_values[0], y_values[0]),
|
||||
"high_left": (x_values[3], y_values[3]),
|
||||
"low_right": (x_values[4], y_values[4]),
|
||||
"high_right": (x_values[7], y_values[7]),
|
||||
"low_left": (bbox_ws[0].x, bbox_ws[0].y),
|
||||
"high_left": (bbox_ws[3].x, bbox_ws[3].y),
|
||||
"low_right": (bbox_ws[4].x, bbox_ws[4].y),
|
||||
"high_right": (bbox_ws[7].x, bbox_ws[7].y),
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def get_converted_tolerance(cls, tolerance: float) -> float:
|
||||
def get_converted_tolerance(cls, tolerance_si: float) -> float:
|
||||
model = tool.Ifc.get()
|
||||
project_unit = ifcopenshell.util.unit.get_project_unit(model, "LENGTHUNIT")
|
||||
prefix = getattr(project_unit, "Prefix", None)
|
||||
|
||||
return ifcopenshell.util.unit.convert(
|
||||
value=tolerance,
|
||||
from_prefix=None,
|
||||
from_unit="METRE",
|
||||
to_prefix=prefix,
|
||||
to_unit=project_unit.Name,
|
||||
)
|
||||
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(model)
|
||||
return tolerance_si / si_conversion
|
||||
|
||||
@classmethod
|
||||
def get_purged_inner_holes_poly(cls, union_geom: Polygon, min_area: float) -> Polygon:
|
||||
@@ -575,8 +574,13 @@ class Spatial(blenderbim.core.tool.Spatial):
|
||||
@classmethod
|
||||
def get_buffered_poly_from_linear_ring(cls, linear_ring: shapely.LinearRing) -> Polygon:
|
||||
poly = Polygon(linear_ring)
|
||||
converted_tolerance = cls.get_converted_tolerance(tolerance=0.03)
|
||||
poly = poly.buffer(converted_tolerance, single_sided=True, cap_style=2, join_style=2)
|
||||
converted_tolerance = cls.get_converted_tolerance(tolerance_si=0.03)
|
||||
poly = poly.buffer(
|
||||
converted_tolerance,
|
||||
single_sided=True,
|
||||
cap_style=shapely.BufferCapStyle.flat,
|
||||
join_style=shapely.BufferJoinStyle.mitre,
|
||||
)
|
||||
return poly
|
||||
|
||||
@classmethod
|
||||
@@ -587,8 +591,10 @@ class Spatial(blenderbim.core.tool.Spatial):
|
||||
bm.edges.index_update()
|
||||
|
||||
mat_invert = mat.inverted()
|
||||
|
||||
new_verts = [bm.verts.new(mat_invert @ Vector([v[0], v[1], 0])) for v in poly.exterior.coords[0:-1]]
|
||||
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
new_verts = [
|
||||
bm.verts.new(mat_invert @ (Vector([v[0], v[1], 0]) * si_conversion)) for v in poly.exterior.coords[0:-1]
|
||||
]
|
||||
[bm.edges.new((new_verts[i], new_verts[i + 1])) for i in range(len(new_verts) - 1)]
|
||||
bm.edges.new((new_verts[len(new_verts) - 1], new_verts[0]))
|
||||
|
||||
@@ -866,3 +872,18 @@ class Spatial(blenderbim.core.tool.Spatial):
|
||||
if (element := tool.Ifc.get_entity(obj)) and tool.Root.is_spatial_element(element):
|
||||
results.append(element)
|
||||
return results
|
||||
|
||||
@classmethod
|
||||
def get_selected_objects_without_containers(cls) -> list[bpy.types.Object]:
|
||||
"""Get selected objects skipping spatial elements.
|
||||
|
||||
Useful for operators that are using selected objects to identify selected containers.
|
||||
Note that those operators are typically have a limitation since they can't tell
|
||||
objects to operate on from containers that should be used in the operation.
|
||||
|
||||
E.g. we cannot bim.copy_to_container containers to other containers."""
|
||||
results: list[bpy.types.Object] = []
|
||||
for obj in tool.Blender.get_selected_objects():
|
||||
if (element := tool.Ifc.get_entity(obj)) and not tool.Root.is_spatial_element(element):
|
||||
results.append(obj)
|
||||
return results
|
||||
|
||||
@@ -99,10 +99,6 @@ class Type(blenderbim.core.tool.Type):
|
||||
return "Usage" in material.is_a()
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def remove_object(cls, obj: bpy.types.Object) -> None:
|
||||
bpy.data.objects.remove(obj)
|
||||
|
||||
@classmethod
|
||||
def run_geometry_add_representation(
|
||||
cls,
|
||||
|
||||
@@ -22,6 +22,7 @@ import blenderbim.core.tool
|
||||
import blenderbim.tool as tool
|
||||
import ifcopenshell.api.sequence
|
||||
from typing import Any, Dict, Optional
|
||||
import time
|
||||
import socket
|
||||
import sys
|
||||
import os
|
||||
@@ -122,7 +123,8 @@ class Web(blenderbim.core.tool.Web):
|
||||
blenderbim_lib_path = os.path.join(blenderbim_path, "libs", "site", "packages")
|
||||
|
||||
env = os.environ.copy()
|
||||
env["blenderbim_lib_path"] = str(blenderbim_lib_path)
|
||||
env["BLENDERBIM_LIB_PATH"] = str(blenderbim_lib_path)
|
||||
env["BLENDERBIM_VERSION"] = tool.Blender.get_blenderbim_version()
|
||||
|
||||
ws_process = subprocess.Popen(
|
||||
[sys.executable, ws_path, "--p", str(port), "--host", "127.0.0.1"],
|
||||
@@ -130,19 +132,6 @@ class Web(blenderbim.core.tool.Web):
|
||||
env=env,
|
||||
)
|
||||
|
||||
pid_file = os.path.join(webui_path, "running_pid.json")
|
||||
|
||||
if os.path.exists(pid_file):
|
||||
with open(pid_file, "r") as f:
|
||||
pids = json.load(f)
|
||||
else:
|
||||
pids = {}
|
||||
|
||||
pids[str(ws_process.pid)] = port
|
||||
|
||||
with open(pid_file, "w") as f:
|
||||
json.dump(pids, f, indent=4)
|
||||
|
||||
cls.set_is_running(True)
|
||||
|
||||
@classmethod
|
||||
@@ -223,12 +212,29 @@ class Web(blenderbim.core.tool.Web):
|
||||
with open(pid_file, "w") as f:
|
||||
json.dump(pids, f, indent=4)
|
||||
|
||||
ws_process.terminate()
|
||||
ws_process.wait()
|
||||
ws_process.kill()
|
||||
ws_process = None
|
||||
|
||||
cls.set_is_running(False)
|
||||
print("Websocket server terminated successfully")
|
||||
print("Websocket server killed successfully")
|
||||
|
||||
@classmethod
|
||||
def has_started(cls, port):
|
||||
max_time = 5
|
||||
start = time.time()
|
||||
while True:
|
||||
if time.time() - start > max_time:
|
||||
return False
|
||||
webui_path = os.path.join(bpy.context.scene.BIMProperties.data_dir, "webui")
|
||||
pid_file = os.path.join(webui_path, "running_pid.json")
|
||||
try:
|
||||
with open(pid_file, "r") as f:
|
||||
data = json.load(f)
|
||||
if port in data.values():
|
||||
return True
|
||||
except:
|
||||
pass
|
||||
time.sleep(0.1)
|
||||
|
||||
@classmethod
|
||||
def send_webui_data(
|
||||
@@ -279,6 +285,8 @@ class Web(blenderbim.core.tool.Web):
|
||||
cls.handle_csv_operator(operator["operator"])
|
||||
elif operator["sourcePage"] == "gantt":
|
||||
cls.handle_gantt_operator(operator["operator"])
|
||||
elif operator["sourcePage"] == "drawings":
|
||||
cls.handle_drawings_operator(operator["operator"])
|
||||
return 1.0
|
||||
|
||||
@classmethod
|
||||
@@ -323,6 +331,29 @@ class Web(blenderbim.core.tool.Web):
|
||||
gantt_data = {"tasks": task_json, "work_schedule": work_schedule.get_info(recursive=True)}
|
||||
cls.send_webui_data(data=gantt_data, data_key="gantt_data", event="gantt_data")
|
||||
|
||||
@classmethod
|
||||
def handle_drawings_operator(cls, operator_data: dict) -> None:
|
||||
if operator_data["type"] == "getDrawings":
|
||||
drawings_data = []
|
||||
sheets_data = []
|
||||
ifc_file_dir = os.path.dirname(bpy.context.scene.BIMProperties.ifc_file)
|
||||
|
||||
sheets = [d for d in tool.Ifc.get().by_type("IfcDocumentInformation") if d.Scope == "SHEET"]
|
||||
for sheet in sorted(sheets, key=lambda s: getattr(s, "Identification", getattr(s, "DocumentId", None))):
|
||||
for reference in tool.Drawing.get_document_references(sheet):
|
||||
reference_description = tool.Drawing.get_reference_description(reference)
|
||||
reference_location = tool.Drawing.get_reference_location(reference)
|
||||
reference_name = os.path.basename(reference_location)
|
||||
reference_path = os.path.join(ifc_file_dir, reference_location)
|
||||
if reference_description == "SHEET":
|
||||
sheets_data.append({"name": reference_name, "path": reference_path})
|
||||
if reference_description == "DRAWING":
|
||||
drawings_data.append({"name": reference_name, "path": reference_path})
|
||||
|
||||
cls.send_webui_data(
|
||||
data={"drawings": drawings_data, "sheets": sheets_data}, data_key="drawings_data", event="drawings_data"
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def open_web_browser(cls, port: int) -> None:
|
||||
webbrowser.open(f"http://127.0.0.1:{port}/")
|
||||
|
||||
@@ -150,7 +150,6 @@ Required Python modules to be stored in ``libs/site/packages/`` are:
|
||||
ifcopenshell
|
||||
bcf
|
||||
ifcclash
|
||||
bimtester
|
||||
ifccobie
|
||||
ifccsv
|
||||
ifcdiff
|
||||
@@ -172,7 +171,6 @@ Required Python modules to be stored in ``libs/site/packages/`` are:
|
||||
elementpath
|
||||
six
|
||||
lark-parser
|
||||
behave
|
||||
parse
|
||||
parse_type
|
||||
xlsxwriter
|
||||
@@ -184,8 +182,7 @@ Required Python modules to be stored in ``libs/site/packages/`` are:
|
||||
Notes:
|
||||
|
||||
1. ``ifcopenshell`` almost always requires the latest version due to the fast paced nature of the add-on development.
|
||||
2. ``behave`` requires `patches <https://github.com/IfcOpenShell/IfcOpenShell/tree/v0.8.0/src/ifcbimtester/patch>`__.
|
||||
3. ``ifcjson`` can be found `here <https://github.com/IFCJSON-Team/IFC2JSON_python/tree/master/file_converters>`__.
|
||||
2. ``ifcjson`` can be found `here <https://github.com/IFCJSON-Team/IFC2JSON_python/tree/master/file_converters>`__.
|
||||
|
||||
Required static assets are:
|
||||
::
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
markers =
|
||||
aggregate
|
||||
attribute
|
||||
bimtester
|
||||
brick
|
||||
classification
|
||||
context
|
||||
|
||||
@@ -22,9 +22,7 @@ Scenario: Assign object
|
||||
And I press "bim.enable_editing_aggregate"
|
||||
And the variable "relating_object" is "tool.Ifc.get().by_type('IfcSite')[0].id()"
|
||||
When I press "bim.aggregate_assign_object(relating_object={relating_object})"
|
||||
Then the object "IfcSite/My Site" is in the collection "IfcSite/My Site"
|
||||
And the object "IfcBuildingStorey/My Storey" is in the collection "IfcBuildingStorey/My Storey"
|
||||
And the collection "IfcBuildingStorey/My Storey" is in the collection "IfcSite/My Site"
|
||||
Then the object "IfcBuildingStorey/My Storey" is aggregated by object "IfcSite/My Site"
|
||||
|
||||
Scenario: Unassign object
|
||||
Given an empty IFC project
|
||||
@@ -34,29 +32,7 @@ Scenario: Unassign object
|
||||
And I press "bim.aggregate_assign_object(relating_object={relating_object})"
|
||||
And the object "IfcBuildingStorey/My Storey" is selected
|
||||
When I press "bim.aggregate_unassign_object"
|
||||
Then the object "IfcSite/My Site" is in the collection "IfcSite/My Site"
|
||||
And the object "IfcBuildingStorey/My Storey" is in the collection "IfcBuildingStorey/My Storey"
|
||||
And the collection "IfcBuildingStorey/My Storey" is in the collection "IfcProject/My Project"
|
||||
|
||||
Scenario: Unassign object - multiple objects are contained again to their indirect container
|
||||
Given an empty IFC project
|
||||
And I add a cube
|
||||
And the object "Cube" is selected
|
||||
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
|
||||
And I set "scene.BIMRootProperties.ifc_class" to "IfcMember"
|
||||
And I press "bim.assign_class"
|
||||
And I add a cube
|
||||
And the object "Cube" is selected
|
||||
And I set "scene.BIMRootProperties.ifc_class" to "IfcCovering"
|
||||
And I press "bim.assign_class"
|
||||
And the object "IfcMember/Cube" is selected
|
||||
And additionally the object "IfcCovering/Cube" is selected
|
||||
And I press "bim.add_aggregate(ifc_class='IfcWall')"
|
||||
And the object "IfcMember/Cube" is selected
|
||||
And additionally the object "IfcCovering/Cube" is selected
|
||||
When I press "bim.aggregate_unassign_object"
|
||||
Then the object "IfcMember/Cube" is in the collection "IfcBuildingStorey/My Storey"
|
||||
And the object "IfcCovering/Cube" is in the collection "IfcBuildingStorey/My Storey"
|
||||
Then the object "IfcBuildingStorey/My Storey" has no aggregate
|
||||
|
||||
Scenario: Add aggregate
|
||||
Given an empty IFC project
|
||||
@@ -67,23 +43,9 @@ Scenario: Add aggregate
|
||||
And I press "bim.assign_class"
|
||||
And the object "IfcWall/Cube" is selected
|
||||
When I press "bim.add_aggregate"
|
||||
Then the object "IfcWall/Cube" is in the collection "IfcElementAssembly/Assembly"
|
||||
And the object "IfcElementAssembly/Assembly" is in the collection "IfcElementAssembly/Assembly"
|
||||
And the collection "IfcElementAssembly/Assembly" is in the collection "IfcBuildingStorey/My Storey"
|
||||
|
||||
Scenario: Add aggregate - with the aggregate inheriting the existing spatial collection
|
||||
Given an empty IFC project
|
||||
And I add a cube
|
||||
And the object "Cube" is selected
|
||||
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
|
||||
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
|
||||
And I press "bim.assign_class"
|
||||
And the object "IfcWall/Cube" is placed in the collection "IfcBuildingStorey/My Storey"
|
||||
And the object "IfcWall/Cube" is selected
|
||||
When I press "bim.add_aggregate"
|
||||
Then the object "IfcWall/Cube" is in the collection "IfcElementAssembly/Assembly"
|
||||
And the object "IfcElementAssembly/Assembly" is in the collection "IfcElementAssembly/Assembly"
|
||||
And the collection "IfcElementAssembly/Assembly" is in the collection "IfcBuildingStorey/My Storey"
|
||||
Then the object "IfcElementAssembly/Default_Name" exists
|
||||
And the object "IfcWall/Cube" is aggregated by object "IfcElementAssembly/Default_Name"
|
||||
And the object "IfcElementAssembly/Default_Name" is contained in object "IfcBuildingStorey/My Storey"
|
||||
|
||||
Scenario: Add aggregate - add a nested aggregate
|
||||
Given an empty IFC project
|
||||
@@ -92,15 +54,15 @@ Scenario: Add aggregate - add a nested aggregate
|
||||
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
|
||||
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
|
||||
And I press "bim.assign_class"
|
||||
And the object "IfcWall/Cube" is placed in the collection "IfcBuildingStorey/My Storey"
|
||||
And the object "IfcWall/Cube" is selected
|
||||
When I press "bim.add_aggregate"
|
||||
And the object "IfcWall/Cube" is selected
|
||||
And I press "bim.add_aggregate"
|
||||
Then the object "IfcWall/Cube" is in the collection "IfcElementAssembly/Assembly.001"
|
||||
And the object "IfcElementAssembly/Assembly.001" is in the collection "IfcElementAssembly/Assembly.001"
|
||||
And the collection "IfcElementAssembly/Assembly.001" is in the collection "IfcElementAssembly/Assembly"
|
||||
And the collection "IfcElementAssembly/Assembly" is in the collection "IfcBuildingStorey/My Storey"
|
||||
And I press "bim.add_aggregate(aggregate_name='Default_Name2')"
|
||||
Then the object "IfcElementAssembly/Default_Name" exists
|
||||
And the object "IfcElementAssembly/Default_Name" is contained in object "IfcBuildingStorey/My Storey"
|
||||
And the object "IfcElementAssembly/Default_Name2" exists
|
||||
And the object "IfcWall/Cube" is aggregated by object "IfcElementAssembly/Default_Name2"
|
||||
And the object "IfcElementAssembly/Default_Name2" is aggregated by object "IfcElementAssembly/Default_Name"
|
||||
|
||||
Scenario: Add aggregate - add multiple elements to a custom aggregate class
|
||||
Given an empty IFC project
|
||||
@@ -115,8 +77,7 @@ Scenario: Add aggregate - add multiple elements to a custom aggregate class
|
||||
And I press "bim.assign_class"
|
||||
And the object "IfcMember/Cube" is selected
|
||||
And additionally the object "IfcCovering/Cube" is selected
|
||||
When I press "bim.add_aggregate(ifc_class='IfcWall')"
|
||||
Then the object "IfcMember/Cube" is in the collection "IfcWall/Assembly"
|
||||
And the object "IfcCovering/Cube" is in the collection "IfcWall/Assembly"
|
||||
And the object "IfcWall/Assembly" is in the collection "IfcWall/Assembly"
|
||||
And the collection "IfcWall/Assembly" is in the collection "IfcBuildingStorey/My Storey"
|
||||
When I press "bim.add_aggregate(ifc_class='IfcWall', aggregate_name='Assembly')"
|
||||
Then the object "IfcMember/Cube" is aggregated by object "IfcWall/Assembly"
|
||||
And the object "IfcCovering/Cube" is aggregated by object "IfcWall/Assembly"
|
||||
And the object "IfcWall/Assembly" is contained in object "IfcBuildingStorey/My Storey"
|
||||
|
||||
@@ -4,34 +4,34 @@ Feature: Attribute
|
||||
Scenario: Enable editing attributes
|
||||
Given an empty IFC project
|
||||
And the object "IfcSite/My Site" is selected
|
||||
When I press "bim.enable_editing_attributes(obj='IfcSite/My Site', obj_type='Object')"
|
||||
When I press "bim.enable_editing_attributes(obj='IfcSite/My Site')"
|
||||
Then nothing happens
|
||||
|
||||
Scenario: Disable editing attributes
|
||||
Given an empty IFC project
|
||||
And the object "IfcSite/My Site" is selected
|
||||
And I press "bim.enable_editing_attributes(obj='IfcSite/My Site', obj_type='Object')"
|
||||
When I press "bim.disable_editing_attributes(obj='IfcSite/My Site', obj_type='Object')"
|
||||
And I press "bim.enable_editing_attributes(obj='IfcSite/My Site')"
|
||||
When I press "bim.disable_editing_attributes(obj='IfcSite/My Site')"
|
||||
Then nothing happens
|
||||
|
||||
Scenario: Edit attributes
|
||||
Given an empty IFC project
|
||||
And the object "IfcSite/My Site" is selected
|
||||
And I press "bim.enable_editing_attributes(obj='IfcSite/My Site', obj_type='Object')"
|
||||
And I press "bim.enable_editing_attributes(obj='IfcSite/My Site')"
|
||||
And I set "active_object.BIMAttributeProperties.attributes[1].string_value" to "Name"
|
||||
When I press "bim.edit_attributes(obj='IfcSite/My Site', obj_type='Object')"
|
||||
When I press "bim.edit_attributes(obj='IfcSite/My Site')"
|
||||
Then the object "IfcSite/Name" is an "IfcSite"
|
||||
And the object "IfcSite/My Site" does not exist
|
||||
|
||||
Scenario: Edit attributes - longitude / latitude
|
||||
Given an empty IFC project
|
||||
And the object "IfcSite/My Site" is selected
|
||||
And I press "bim.enable_editing_attributes(obj='IfcSite/My Site', obj_type='Object')"
|
||||
And I press "bim.enable_editing_attributes(obj='IfcSite/My Site')"
|
||||
Then "active_object.BIMAttributeProperties.attributes[6].name" is "RefLatitude"
|
||||
When I set "active_object.BIMAttributeProperties.attributes[6].string_value" to "[1,2]"
|
||||
And I press "bim.edit_attributes(obj='IfcSite/My Site', obj_type='Object')"
|
||||
And I press "bim.enable_editing_attributes(obj='IfcSite/My Site', obj_type='Object')"
|
||||
And I press "bim.edit_attributes(obj='IfcSite/My Site', obj_type='Object')"
|
||||
And I press "bim.edit_attributes(obj='IfcSite/My Site')"
|
||||
And I press "bim.enable_editing_attributes(obj='IfcSite/My Site')"
|
||||
And I press "bim.edit_attributes(obj='IfcSite/My Site')"
|
||||
Then nothing happens
|
||||
|
||||
Scenario: Copy attribute to selected
|
||||
@@ -48,7 +48,7 @@ Scenario: Copy attribute to selected
|
||||
And I press "bim.assign_class"
|
||||
And the object "IfcWall/Cube" is selected
|
||||
And additionally the object "IfcWall/Cube.001" is selected
|
||||
And I press "bim.enable_editing_attributes(obj='IfcWall/Cube.001', obj_type='Object')"
|
||||
And I press "bim.enable_editing_attributes(obj='IfcWall/Cube.001')"
|
||||
And I set "active_object.BIMAttributeProperties.attributes[2].string_value" to "Foo"
|
||||
When I press "bim.copy_attribute_to_selection(name='Description')"
|
||||
Then nothing happens
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
@bimtester
|
||||
Feature: Bimtester
|
||||
|
||||
Scenario: Execute Bimtester
|
||||
Given an empty IFC project
|
||||
When I set "scene.BimTesterProperties.should_load_from_memory" to "True"
|
||||
And I set "scene.BimTesterProperties.feature" to "{cwd}/test/files/sample.feature"
|
||||
And I press "bim.execute_bim_tester"
|
||||
Then nothing happens
|
||||
@@ -377,7 +377,7 @@ Scenario: Assign cost item quantity - quantity based
|
||||
And I press "bim.assign_class"
|
||||
And the object "IfcWall/Cube" is selected
|
||||
And I press "bim.add_qto(obj='IfcWall/Cube', obj_type='Object')"
|
||||
And I press "bim.calculate_all_quantities"
|
||||
And I press "bim.perform_quantity_take_off"
|
||||
When I press "bim.assign_cost_item_quantity(cost_item={cost_item}, related_object_type='PRODUCT', prop_name='NetVolume')"
|
||||
Then nothing happens
|
||||
|
||||
|
||||
@@ -8,8 +8,36 @@ Scenario: Execute generate flooring coverings from walls
|
||||
And I set "scene.BIMModelProperties.ifc_class" to "IfcWallType"
|
||||
And the variable "element_type" is "[e for e in {ifc}.by_type('IfcWallType') if e.Name == 'WAL100'][0].id()"
|
||||
And I set "scene.BIMModelProperties.relating_type_id" to "{element_type}"
|
||||
# 1st wall
|
||||
And I press "bim.add_constr_type_instance"
|
||||
And the object "IfcWall/Wall" is selected
|
||||
When I press "bim.add_instance_flooring_coverings_from_walls"
|
||||
Then nothing happens
|
||||
|
||||
And I press "bim.change_layer_length(length=3.6)"
|
||||
# 2nd wall
|
||||
And the cursor is at "3.6,0.1,3"
|
||||
And I set "scene.BIMModelProperties.length" to "2.0"
|
||||
And I press "bim.add_constr_type_instance"
|
||||
# 3rd wall
|
||||
And the cursor is at "3.5,2.1,3"
|
||||
And I set "scene.BIMModelProperties.length" to "3.5"
|
||||
And I press "bim.add_constr_type_instance"
|
||||
# 4th wall
|
||||
And the cursor is at "0,2.0,0"
|
||||
And I set "scene.BIMModelProperties.length" to "1.9"
|
||||
And I press "bim.add_constr_type_instance"
|
||||
# add_instance_flooring_coverings_from_walls is expecting FLOORING predefined type.
|
||||
And the object "IfcCoveringType/COV30" is selected
|
||||
And I press "bim.enable_editing_attributes(obj='IfcCoveringType/COV30')"
|
||||
And I set "active_object.BIMAttributeProperties.attributes[6].enum_value" to "FLOORING"
|
||||
And I press "bim.edit_attributes(obj='IfcCoveringType/COV30')"
|
||||
# Run the operator.
|
||||
When the object "IfcWall/Wall" is selected
|
||||
And additionally the object "IfcWall/Wall.001" is selected
|
||||
And additionally the object "IfcWall/Wall.002" is selected
|
||||
And additionally the object "IfcWall/Wall.003" is selected
|
||||
And I set "scene.BIMModelProperties.ifc_class" to "IfcCoveringType"
|
||||
And the variable "element_type" is "[e for e in {ifc}.by_type('IfcCoveringType') if e.Name == 'COV30'][0].id()"
|
||||
And I set "scene.BIMModelProperties.relating_type_id" to "{element_type}"
|
||||
And I press "bim.add_instance_flooring_coverings_from_walls"
|
||||
Then the object "IfcCovering/Covering0" exists
|
||||
And the object "IfcCovering/Covering0" is at "1.8,1.05,0.0"
|
||||
And the object "IfcCovering/Covering0" dimensions are "3.4,1.9,0.03"
|
||||
|
||||
@@ -93,7 +93,6 @@ Scenario: Remove drawing - deleting active drawing
|
||||
And the collection "IfcAnnotation/PLAN_VIEW" exists
|
||||
And I set "scene.DocProperties.active_drawing_index" to "0"
|
||||
And I press "bim.activate_drawing(drawing={drawing})"
|
||||
And the object "IfcAnnotation/PLAN_VIEW" is selected
|
||||
When I press "bim.override_object_delete"
|
||||
When the object "IfcAnnotation/PLAN_VIEW" is selected
|
||||
And I press "bim.override_object_delete"
|
||||
Then the collection "IfcAnnotation/PLAN_VIEW" does not exist
|
||||
|
||||
|
||||
@@ -93,7 +93,7 @@ Scenario: Switch representation - current edited representation is updated prior
|
||||
And I press "bim.switch_representation(ifc_definition_id={representation}, should_reload=True)"
|
||||
And the variable "representation" is "[r for r in {ifc}.by_type('IfcShapeRepresentation') if r.RepresentationType=='Annotation2D'][0].id()"
|
||||
And I press "bim.switch_representation(ifc_definition_id={representation}, should_reload=True)"
|
||||
When I press "export_ifc.bim(filepath='{cwd}/test/files/temp/export.ifc')"
|
||||
When I press "bim.save_project(filepath='{cwd}/test/files/temp/export.ifc')"
|
||||
Then the object "IfcWall/Cube" dimensions are "4,4,0"
|
||||
|
||||
Scenario: Switch representation - current edited representation is discarded if switching to a box
|
||||
@@ -108,7 +108,7 @@ Scenario: Switch representation - current edited representation is discarded if
|
||||
And I press "bim.switch_representation(obj='IfcWall/Cube', ifc_definition_id={representation}, should_reload=True)"
|
||||
And the variable "representation" is "{ifc}.by_type('IfcShapeRepresentation')[0].id()"
|
||||
And I press "bim.switch_representation(obj='IfcWall/Cube', ifc_definition_id={representation}, should_reload=True)"
|
||||
When I press "export_ifc.bim(filepath='{cwd}/test/files/temp/export.ifc')"
|
||||
When I press "bim.save_project(filepath='{cwd}/test/files/temp/export.ifc')"
|
||||
Then the object "IfcWall/Cube" dimensions are "2,2,2"
|
||||
|
||||
Scenario: Switch representation - existing Blender modifiers must be purged
|
||||
@@ -131,8 +131,10 @@ Scenario: Remove representation - remove an active representation
|
||||
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
|
||||
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
|
||||
And I press "bim.assign_class"
|
||||
When the variable "representation" is "{ifc}.by_type('IfcShapeRepresentation')[0].id()"
|
||||
And I press "bim.remove_representation(representation_id={representation})"
|
||||
When the variable "representation_body" is "{ifc}.by_type('IfcShapeRepresentation')[0].id()"
|
||||
And the variable "representation_bbox" is "{ifc}.by_type('IfcShapeRepresentation')[1].id()"
|
||||
And I press "bim.remove_representation(representation_id={representation_body})"
|
||||
And I press "bim.remove_representation(representation_id={representation_bbox})"
|
||||
Then the object "IfcWall/Cube" has no data
|
||||
|
||||
Scenario: Remove representation - remove an unloaded representation
|
||||
@@ -160,8 +162,10 @@ Scenario: Remove representation - remove an instanced representation from an act
|
||||
And I press "bim.add_constr_type_instance"
|
||||
And I press "bim.add_constr_type_instance"
|
||||
And the object "IfcWallType/Cube" is selected
|
||||
When the variable "representation" is "{ifc}.by_type('IfcWallType')[0].RepresentationMaps[1].MappedRepresentation.id()"
|
||||
And I press "bim.remove_representation(representation_id={representation})"
|
||||
When the variable "representation_body" is "{ifc}.by_type('IfcWallType')[0].RepresentationMaps[1].MappedRepresentation.id()"
|
||||
And the variable "representation_bbox" is "{ifc}.by_type('IfcWallType')[0].RepresentationMaps[0].MappedRepresentation.id()"
|
||||
And I press "bim.remove_representation(representation_id={representation_body})"
|
||||
And I press "bim.remove_representation(representation_id={representation_bbox})"
|
||||
Then the object "IfcWallType/Cube" has no data
|
||||
Then the object "IfcWall/Wall" has no data
|
||||
Then the object "IfcWall/Wall.001" has no data
|
||||
@@ -179,8 +183,10 @@ Scenario: Remove representation - remove an instanced representation from an act
|
||||
And I press "bim.add_constr_type_instance"
|
||||
And I press "bim.add_constr_type_instance"
|
||||
And the object "IfcWall/Wall" is selected
|
||||
When the variable "representation" is "{ifc}.by_type('IfcWall')[0].Representation.Representations[1].id()"
|
||||
And I press "bim.remove_representation(representation_id={representation})"
|
||||
When the variable "representation_body" is "{ifc}.by_type('IfcWall')[0].Representation.Representations[1].id()"
|
||||
And the variable "representation_bbox" is "{ifc}.by_type('IfcWall')[0].Representation.Representations[0].id()"
|
||||
And I press "bim.remove_representation(representation_id={representation_body})"
|
||||
And I press "bim.remove_representation(representation_id={representation_bbox})"
|
||||
Then the object "IfcWallType/Cube" has no data
|
||||
Then the object "IfcWall/Wall" has no data
|
||||
Then the object "IfcWall/Wall.001" has no data
|
||||
@@ -339,7 +345,7 @@ Scenario: Override duplicate move - copying a coloured representation
|
||||
And the variable "style" is "{ifc}.by_type('IfcSurfaceStyle')[0].id()"
|
||||
And I press "bim.assign_style_to_selected(style_id={style})"
|
||||
When I duplicate the selected objects
|
||||
And I press "export_ifc.bim(filepath='{cwd}/test/files/temp/export.ifc')"
|
||||
And I press "bim.save_project(filepath='{cwd}/test/files/temp/export.ifc')"
|
||||
And an empty Blender session is started
|
||||
And I press "bim.load_project(filepath='{cwd}/test/files/temp/export.ifc', should_start_fresh_session=False)"
|
||||
Then the material "Style" colour is "1,0,0,1"
|
||||
@@ -427,18 +433,14 @@ Scenario: Override duplicate move - copying an aggregate
|
||||
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
|
||||
And I press "bim.assign_class"
|
||||
And the object "IfcWall/Cube" is selected
|
||||
When I press "bim.add_aggregate"
|
||||
Then the object "IfcWall/Cube" is in the collection "IfcElementAssembly/Assembly"
|
||||
And the object "IfcElementAssembly/Assembly" is in the collection "IfcElementAssembly/Assembly"
|
||||
And the collection "IfcElementAssembly/Assembly" is in the collection "IfcBuildingStorey/My Storey"
|
||||
When I press "bim.add_aggregate(aggregate_name='Assembly')"
|
||||
When the object "IfcWall/Cube" is selected
|
||||
And additionally the object "IfcElementAssembly/Assembly" is selected
|
||||
When I duplicate the selected objects
|
||||
Then the object "IfcWall/Cube.001" exists
|
||||
And the object "IfcWall/Cube.001" is in the collection "IfcElementAssembly/Assembly.001"
|
||||
And the object "IfcElementAssembly/Assembly.001" exists
|
||||
And the object "IfcElementAssembly/Assembly.001" is in the collection "IfcElementAssembly/Assembly.001"
|
||||
And the collection "IfcElementAssembly/Assembly.001" is in the collection "IfcBuildingStorey/My Storey"
|
||||
And the object "IfcWall/Cube.001" is aggregated by object "IfcElementAssembly/Assembly.001"
|
||||
And the object "IfcElementAssembly/Assembly.001" is contained in object "IfcBuildingStorey/My Storey"
|
||||
|
||||
Scenario: Override duplicate move - copying objects with connection
|
||||
Given an empty IFC project
|
||||
@@ -451,7 +453,7 @@ Scenario: Override duplicate move - copying objects with connection
|
||||
And the object "IfcWall/Wall" dimensions are "1,0.1,3"
|
||||
And the object "IfcWall/Wall" bottom left corner is at "0,0,0"
|
||||
When I set "scene.BIMModelProperties.ifc_class" to "IfcSlabType"
|
||||
And the variable "element_type" is "[e for e in {ifc}.by_type('IfcSlabType') if e.Name == 'FLR150'][0].id()"
|
||||
And the variable "element_type" is "[e for e in {ifc}.by_type('IfcSlabType') if e.Name == 'FLR200'][0].id()"
|
||||
And I set "scene.BIMModelProperties.relating_type_id" to "{element_type}"
|
||||
When I press "bim.add_constr_type_instance"
|
||||
Then the object "IfcSlab/Slab" is an "IfcSlab"
|
||||
@@ -607,4 +609,3 @@ Scenario: Refresh linked aggregate - after duplicating an object
|
||||
Then the object "IfcWall/Wall_01.001" exists
|
||||
And the object "IfcWall/Wall_02.001" exists
|
||||
And the object "IfcWall/Wall_03.001" exists
|
||||
|
||||
|
||||
@@ -157,7 +157,7 @@ Scenario: Unassign material - removing inherited material
|
||||
And I press "bim.assign_type(relating_type={type}, related_object='IfcWall/Cube')"
|
||||
|
||||
Then the object "IfcWall/Cube" has a "100" thick layered material containing the material "Default"
|
||||
|
||||
|
||||
When I press "bim.unassign_material"
|
||||
Then the object "IfcWall/Cube" has no IFC materials
|
||||
|
||||
|
||||
@@ -33,7 +33,8 @@ Scenario: Resize to storey
|
||||
And I press "bim.assign_class"
|
||||
And the object "IfcWall/Cube" is selected
|
||||
And the variable "storey" is "tool.Ifc.get().by_type('IfcBuildingStorey')[0].id()"
|
||||
And I press "bim.assign_container(structure={storey})"
|
||||
And I press "bim.set_default_container(container={storey})"
|
||||
And I press "bim.assign_container()"
|
||||
When I press "bim.resize_to_storey(total_storeys=1)"
|
||||
Then nothing happens
|
||||
|
||||
@@ -56,4 +57,4 @@ Scenario: Enabling and disabling IFC Sverchok
|
||||
And I press "preferences.addon_enable(module="sverchok")"
|
||||
And I press "preferences.addon_enable(module="ifcsverchok")"
|
||||
And I press "preferences.addon_disable(module="sverchok")"
|
||||
And I press "preferences.addon_disable(module="ifcsverchok")"
|
||||
And I press "preferences.addon_disable(module="ifcsverchok")"
|
||||
|
||||
@@ -66,14 +66,6 @@ Scenario: Add grid
|
||||
And the object "IfcGridAxis/02" is an "IfcGridAxis"
|
||||
And the object "IfcGridAxis/03" is an "IfcGridAxis"
|
||||
|
||||
Scenario: Pie update container
|
||||
Given an empty Blender session
|
||||
And I press "bim.load_project(filepath='{cwd}/test/files/basic.ifc')"
|
||||
Then the object "IfcSlab/Slab" is in the collection "IfcBuildingStorey/Ground Floor"
|
||||
When the object "IfcSlab/Slab" is placed in the collection "IfcBuildingStorey/Level 1"
|
||||
And I press "bim.pie_update_container"
|
||||
Then nothing happens
|
||||
|
||||
Scenario: Add a wall
|
||||
Given an empty IFC project
|
||||
And I load the demo construction library
|
||||
@@ -272,7 +264,7 @@ Scenario: Add a slab
|
||||
Given an empty IFC project
|
||||
And I load the demo construction library
|
||||
And I set "scene.BIMModelProperties.ifc_class" to "IfcSlabType"
|
||||
And the variable "element_type" is "[e for e in {ifc}.by_type('IfcSlabType') if e.Name == 'FLR150'][0].id()"
|
||||
And the variable "element_type" is "[e for e in {ifc}.by_type('IfcSlabType') if e.Name == 'FLR200'][0].id()"
|
||||
And I set "scene.BIMModelProperties.relating_type_id" to "{element_type}"
|
||||
When I press "bim.hotkey(hotkey='S_A')"
|
||||
Then the object "IfcSlab/Slab" is an "IfcSlab"
|
||||
@@ -284,7 +276,7 @@ Scenario: Enable editing a slab profile
|
||||
Given an empty IFC project
|
||||
And I load the demo construction library
|
||||
And I set "scene.BIMModelProperties.ifc_class" to "IfcSlabType"
|
||||
And the variable "element_type" is "[e for e in {ifc}.by_type('IfcSlabType') if e.Name == 'FLR150'][0].id()"
|
||||
And the variable "element_type" is "[e for e in {ifc}.by_type('IfcSlabType') if e.Name == 'FLR200'][0].id()"
|
||||
And I set "scene.BIMModelProperties.relating_type_id" to "{element_type}"
|
||||
And I press "bim.hotkey(hotkey='S_A')"
|
||||
And the object "IfcSlab/Slab" is selected
|
||||
@@ -297,7 +289,7 @@ Scenario: Disable editing a slab profile
|
||||
Given an empty IFC project
|
||||
And I load the demo construction library
|
||||
And I set "scene.BIMModelProperties.ifc_class" to "IfcSlabType"
|
||||
And the variable "element_type" is "[e for e in {ifc}.by_type('IfcSlabType') if e.Name == 'FLR150'][0].id()"
|
||||
And the variable "element_type" is "[e for e in {ifc}.by_type('IfcSlabType') if e.Name == 'FLR200'][0].id()"
|
||||
And I set "scene.BIMModelProperties.relating_type_id" to "{element_type}"
|
||||
And I press "bim.hotkey(hotkey='S_A')"
|
||||
And the object "IfcSlab/Slab" is selected
|
||||
@@ -311,7 +303,7 @@ Scenario: Edit a slab profile
|
||||
Given an empty IFC project
|
||||
And I load the demo construction library
|
||||
And I set "scene.BIMModelProperties.ifc_class" to "IfcSlabType"
|
||||
And the variable "element_type" is "[e for e in {ifc}.by_type('IfcSlabType') if e.Name == 'FLR150'][0].id()"
|
||||
And the variable "element_type" is "[e for e in {ifc}.by_type('IfcSlabType') if e.Name == 'FLR200'][0].id()"
|
||||
And I set "scene.BIMModelProperties.relating_type_id" to "{element_type}"
|
||||
And I press "bim.hotkey(hotkey='S_A')"
|
||||
And the object "IfcSlab/Slab" is selected
|
||||
|
||||
@@ -833,31 +833,31 @@ Scenario: Unlink IFC
|
||||
|
||||
Scenario: Export IFC - blank project
|
||||
Given an empty IFC project
|
||||
When I press "export_ifc.bim(filepath='{cwd}/test/files/temp/export.ifc')"
|
||||
When I press "bim.save_project(filepath='{cwd}/test/files/temp/export.ifc')"
|
||||
Then nothing happens
|
||||
|
||||
Scenario: Export IFC - with basic contents
|
||||
Given an empty Blender session
|
||||
And I press "bim.load_project(filepath='{cwd}/test/files/basic.ifc')"
|
||||
When I press "export_ifc.bim(filepath='{cwd}/test/files/temp/export.ifc')"
|
||||
When I press "bim.save_project(filepath='{cwd}/test/files/temp/export.ifc')"
|
||||
Then "scene.BIMProperties.ifc_file" is "{cwd}/test/files/temp/export.ifc"
|
||||
|
||||
Scenario: Export IFC - with basic contents and saving as another file
|
||||
Given an empty Blender session
|
||||
And I press "bim.load_project(filepath='{cwd}/test/files/basic.ifc')"
|
||||
When I press "export_ifc.bim(filepath='{cwd}/test/files/temp/export.ifc', should_save_as=True)"
|
||||
When I press "bim.save_project(filepath='{cwd}/test/files/temp/export.ifc', should_save_as=True)"
|
||||
Then "scene.BIMProperties.ifc_file" is "{cwd}/test/files/temp/export.ifc"
|
||||
|
||||
Scenario: Export IFC - with basic contents and saving as IfcJSON where import is not supported
|
||||
Given an empty Blender session
|
||||
And I press "bim.load_project(filepath='{cwd}/test/files/basic.ifc')"
|
||||
When I press "export_ifc.bim(filepath='{cwd}/test/files/temp/export.ifcjson', should_save_as=True)"
|
||||
When I press "bim.save_project(filepath='{cwd}/test/files/temp/export.ifcjson', should_save_as=True)"
|
||||
Then "scene.BIMProperties.ifc_file" is "{cwd}/test/files/basic.ifc"
|
||||
|
||||
Scenario: Export IFC - with basic contents and round-tripping an IfcZip
|
||||
Given an empty Blender session
|
||||
And I press "bim.load_project(filepath='{cwd}/test/files/basic.ifc')"
|
||||
When I press "export_ifc.bim(filepath='{cwd}/test/files/temp/export.ifczip', should_save_as=True)"
|
||||
When I press "bim.save_project(filepath='{cwd}/test/files/temp/export.ifczip', should_save_as=True)"
|
||||
Then "scene.BIMProperties.ifc_file" is "{cwd}/test/files/basic.ifc"
|
||||
When an empty Blender session is started
|
||||
And I press "bim.load_project(filepath='{cwd}/test/files/temp/export.ifczip')"
|
||||
@@ -867,14 +867,14 @@ Scenario: Export IFC - with basic contents and saving as a relative path
|
||||
Given an empty Blender session
|
||||
And I press "bim.load_project(filepath='{cwd}/test/files/basic.ifc')"
|
||||
When I press "wm.save_mainfile(filepath='{cwd}/test/files/temp/export.blend')"
|
||||
And I press "export_ifc.bim(filepath='{cwd}/test/files/temp/export.ifc', use_relative_path=True, save_as_invoked=True)"
|
||||
And I press "bim.save_project(filepath='{cwd}/test/files/temp/export.ifc', use_relative_path=True, save_as_invoked=True)"
|
||||
Then "scene.BIMProperties.ifc_file" is "export.ifc"
|
||||
|
||||
Scenario: Export IFC - with deleted objects synchronised
|
||||
Given an empty IFC project
|
||||
When the object "IfcBuildingStorey/My Storey" is selected
|
||||
And I delete the selected objects
|
||||
And I press "export_ifc.bim(filepath='{cwd}/test/files/temp/export.ifc')"
|
||||
And I press "bim.save_project(filepath='{cwd}/test/files/temp/export.ifc')"
|
||||
And an empty Blender session is started
|
||||
And I press "bim.load_project(filepath='{cwd}/test/files/temp/export.ifc')"
|
||||
Then the object "IfcBuildingStorey/My Storey" does not exist
|
||||
@@ -882,7 +882,7 @@ Scenario: Export IFC - with deleted objects synchronised
|
||||
Scenario: Export IFC - with moved object location synchronised
|
||||
Given an empty IFC project
|
||||
When the object "IfcBuildingStorey/My Storey" is moved to "0,0,1"
|
||||
And I press "export_ifc.bim(filepath='{cwd}/test/files/temp/export.ifc')"
|
||||
And I press "bim.save_project(filepath='{cwd}/test/files/temp/export.ifc')"
|
||||
And an empty Blender session is started
|
||||
And I press "bim.load_project(filepath='{cwd}/test/files/temp/export.ifc')"
|
||||
Then the object "IfcBuildingStorey/My Storey" is at "0,0,1"
|
||||
@@ -891,7 +891,7 @@ Scenario: Export IFC - with moved grid axis location synchronised
|
||||
Given an empty IFC project
|
||||
And I press "mesh.add_grid"
|
||||
When the object "IfcGridAxis/01" is moved to "1,0,0"
|
||||
And I press "export_ifc.bim(filepath='{cwd}/test/files/temp/export.ifc')"
|
||||
And I press "bim.save_project(filepath='{cwd}/test/files/temp/export.ifc')"
|
||||
And an empty Blender session is started
|
||||
And I press "bim.load_project(filepath='{cwd}/test/files/temp/export.ifc')"
|
||||
Then the object "IfcGridAxis/01" bottom left corner is at "1,-2,0"
|
||||
@@ -905,7 +905,7 @@ Scenario: Export IFC - with changed object scale synchronised
|
||||
And I press "bim.assign_class"
|
||||
And the object "IfcWall/Cube" is selected
|
||||
When the object "IfcWall/Cube" is scaled to "2"
|
||||
And I press "export_ifc.bim(filepath='{cwd}/test/files/temp/export.ifc')"
|
||||
And I press "bim.save_project(filepath='{cwd}/test/files/temp/export.ifc')"
|
||||
And an empty Blender session is started
|
||||
And I press "bim.load_project(filepath='{cwd}/test/files/temp/export.ifc')"
|
||||
Then the object "IfcWall/Cube" dimensions are "4,4,4"
|
||||
|
||||
@@ -380,15 +380,15 @@ Scenario: Edit pset length property
|
||||
Scenario: Edit qset length property
|
||||
Given an empty IFC project
|
||||
And I press "mesh.add_clever_stair"
|
||||
And I press "bim.calculate_all_quantities"
|
||||
And I press "bim.perform_quantity_take_off"
|
||||
And the variable "pset" is "tool.Pset.get_element_pset(tool.Ifc.get_entity(bpy.context.active_object), 'Qto_StairFlightBaseQuantities').id()"
|
||||
And the variable "si_conversion" is "ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())"
|
||||
And I press "bim.enable_pset_editing(pset_id={pset}, obj='IfcStairFlight/StairFlight', obj_type='Object')"
|
||||
|
||||
# Testing Q_LENGTH type of prop
|
||||
Then "active_object.PsetProperties.properties['Length'].metadata.special_type" is "LENGTH"
|
||||
And "active_object.PsetProperties.properties['Length'].metadata.float_value" is roughly "2492.57397"
|
||||
And "active_object.PsetProperties.properties['Length'].metadata.length_value" is roughly "2.49257"
|
||||
And "active_object.PsetProperties.properties['Length'].metadata.float_value" is roughly "2156.485"
|
||||
And "active_object.PsetProperties.properties['Length'].metadata.length_value" is roughly "2.156"
|
||||
|
||||
When I set "active_object.PsetProperties.properties['Length'].metadata.float_value" to "350"
|
||||
Then "active_object.PsetProperties.properties['Length'].metadata.length_value" is roughly "0.35"
|
||||
|
||||
@@ -35,34 +35,23 @@ Scenario: Execute qto method - formwork areas
|
||||
And I add a cube of size "1" at "1,0,0"
|
||||
And the object "Cube" is selected
|
||||
And additionally the object "Cube.001" is selected
|
||||
When I set "scene.BIMQtoProperties.qto_methods" to "FORMWORK"
|
||||
And I press "bim.execute_qto_method"
|
||||
When I press "bim.calculate_formwork_area"
|
||||
Then "scene.BIMQtoProperties.qto_result" is "21.5"
|
||||
|
||||
Scenario: Execute qto method - side formwork areas
|
||||
Given an empty Blender session
|
||||
And I add a cube
|
||||
And the object "Cube" is selected
|
||||
When I set "scene.BIMQtoProperties.qto_methods" to "SIDE_FORMWORK"
|
||||
And I press "bim.execute_qto_method"
|
||||
When I press "bim.calculate_side_formwork_area"
|
||||
Then "scene.BIMQtoProperties.qto_result" is "16.0"
|
||||
|
||||
Scenario: Assign objects base qto
|
||||
Given an empty IFC project
|
||||
And I add a cube
|
||||
And the object "Cube" is selected
|
||||
And I press "bim.assign_class(ifc_class='IfcWall', predefined_type='SOLIDWALL')"
|
||||
When I press "bim.assign_objects_base_qto"
|
||||
Then "active_object.PsetProperties.qto_name" is "Qto_WallBaseQuantities"
|
||||
|
||||
Scenario: Calculate all quantities
|
||||
Given an empty IFC project
|
||||
And I add a cube
|
||||
And the object "Cube" is selected
|
||||
And I press "bim.assign_class(ifc_class='IfcWall', predefined_type='SOLIDWALL')"
|
||||
When I press "bim.calculate_all_quantities"
|
||||
When I press "bim.perform_quantity_take_off"
|
||||
And the variable "qset_id" is "{ifc}.by_type('IfcElementQuantity')[0].id()"
|
||||
And I press "bim.enable_pset_editing(pset_id={qset_id}, obj='IfcWall/Cube', obj_type='Object')"
|
||||
And I press "bim.disable_pset_editing(obj='IfcWall/Cube', obj_type='Object')"
|
||||
Then "active_object.PsetProperties.qto_name" is "Qto_WallBaseQuantities"
|
||||
Then "active_object.PsetProperties.properties['Length'].metadata.float_value" is "2000.0"
|
||||
Then "active_object.PsetProperties.active_pset_name" is "Qto_WallBaseQuantities"
|
||||
And "active_object.PsetProperties.properties['Length'].metadata.float_value" is roughly "2000.0"
|
||||
|
||||
@@ -32,7 +32,7 @@ Scenario: Add Labor Resource
|
||||
Given an empty IFC project
|
||||
When I press "bim.load_resources"
|
||||
When I press "bim.add_resource(ifc_class="IfcCrewResource")"
|
||||
And the variable "crew_resource" is "IfcStore.get_file().by_type('IfcCrewResource')[0].id()"
|
||||
And the variable "crew_resource" is "IfcStore.get_file().by_type('IfcCrewResource')[0].id()"
|
||||
When I press "bim.add_resource(ifc_class="IfcLaborResource", parent_resource={crew_resource})"
|
||||
Then nothing happens
|
||||
|
||||
@@ -41,7 +41,7 @@ Scenario: Enable Editing Resource
|
||||
Given an empty IFC project
|
||||
When I press "bim.load_resources"
|
||||
When I press "bim.add_resource(ifc_class="IfcCrewResource")"
|
||||
And the variable "crew_resource" is "IfcStore.get_file().by_type('IfcCrewResource')[0].id()"
|
||||
And the variable "crew_resource" is "IfcStore.get_file().by_type('IfcCrewResource')[0].id()"
|
||||
When I press "bim.enable_editing_resource(resource={crew_resource})"
|
||||
Then nothing happens
|
||||
|
||||
@@ -49,7 +49,7 @@ Scenario: Edit Resource
|
||||
Given an empty IFC project
|
||||
When I press "bim.load_resources"
|
||||
When I press "bim.add_resource(ifc_class="IfcCrewResource")"
|
||||
And the variable "crew_resource" is "IfcStore.get_file().by_type('IfcCrewResource')[0].id()"
|
||||
And the variable "crew_resource" is "IfcStore.get_file().by_type('IfcCrewResource')[0].id()"
|
||||
When I press "bim.enable_editing_resource(resource={crew_resource})"
|
||||
And I set "scene.BIMResourceProperties.resource_attributes.get('Name').string_value" to "Foo"
|
||||
When I press "bim.edit_resource()"
|
||||
@@ -59,7 +59,7 @@ Scenario: Remove Resource
|
||||
Given an empty IFC project
|
||||
When I press "bim.load_resources"
|
||||
When I press "bim.add_resource(ifc_class="IfcCrewResource")"
|
||||
And the variable "crew_resource" is "IfcStore.get_file().by_type('IfcCrewResource')[0].id()"
|
||||
And the variable "crew_resource" is "IfcStore.get_file().by_type('IfcCrewResource')[0].id()"
|
||||
When I press "bim.enable_editing_resource(resource={crew_resource})"
|
||||
And I set "scene.BIMResourceProperties.resource_attributes.get('Name').string_value" to "Foo"
|
||||
When I press "bim.edit_resource()"
|
||||
@@ -70,9 +70,9 @@ Scenario: Remove Parent Resource
|
||||
Given an empty IFC project
|
||||
When I press "bim.load_resources"
|
||||
When I press "bim.add_resource(ifc_class="IfcCrewResource")"
|
||||
And the variable "crew_resource" is "IfcStore.get_file().by_type('IfcCrewResource')[0].id()"
|
||||
And the variable "crew_resource" is "IfcStore.get_file().by_type('IfcCrewResource')[0].id()"
|
||||
When I press "bim.add_resource(ifc_class="IfcLaborResource", parent_resource={crew_resource})"
|
||||
And the variable "labor_resource" is "IfcStore.get_file().by_type('IfcLaborResource')[0].id()"
|
||||
And the variable "labor_resource" is "IfcStore.get_file().by_type('IfcLaborResource')[0].id()"
|
||||
When I press "bim.enable_editing_resource_time(resource={labor_resource})"
|
||||
And I set "scene.BIMResourceProperties.resource_time_attributes.get('Name').string_value" to "TimelyFoo"
|
||||
When I press "bim.edit_resource_time()"
|
||||
@@ -83,9 +83,9 @@ Scenario: Enable Editing Resource time
|
||||
Given an empty IFC project
|
||||
When I press "bim.load_resources"
|
||||
When I press "bim.add_resource(ifc_class="IfcCrewResource")"
|
||||
And the variable "crew_resource" is "IfcStore.get_file().by_type('IfcCrewResource')[0].id()"
|
||||
And the variable "crew_resource" is "IfcStore.get_file().by_type('IfcCrewResource')[0].id()"
|
||||
When I press "bim.add_resource(ifc_class="IfcLaborResource", parent_resource={crew_resource})"
|
||||
And the variable "labor_resource" is "IfcStore.get_file().by_type('IfcLaborResource')[0].id()"
|
||||
And the variable "labor_resource" is "IfcStore.get_file().by_type('IfcLaborResource')[0].id()"
|
||||
When I press "bim.enable_editing_resource_time(resource={labor_resource})"
|
||||
Then nothing happens
|
||||
|
||||
@@ -93,9 +93,9 @@ Scenario: Disable editing resource time
|
||||
Given an empty IFC project
|
||||
When I press "bim.load_resources"
|
||||
When I press "bim.add_resource(ifc_class="IfcCrewResource")"
|
||||
And the variable "crew_resource" is "IfcStore.get_file().by_type('IfcCrewResource')[0].id()"
|
||||
And the variable "crew_resource" is "IfcStore.get_file().by_type('IfcCrewResource')[0].id()"
|
||||
When I press "bim.add_resource(ifc_class="IfcLaborResource", parent_resource={crew_resource})"
|
||||
And the variable "labor_resource" is "IfcStore.get_file().by_type('IfcLaborResource')[0].id()"
|
||||
And the variable "labor_resource" is "IfcStore.get_file().by_type('IfcLaborResource')[0].id()"
|
||||
And I press "bim.enable_editing_resource_time(resource={labor_resource})"
|
||||
When I press "bim.disable_editing_resource()"
|
||||
Then nothing happens
|
||||
@@ -104,9 +104,9 @@ Scenario: Edit Resource time
|
||||
Given an empty IFC project
|
||||
When I press "bim.load_resources"
|
||||
When I press "bim.add_resource(ifc_class="IfcCrewResource")"
|
||||
And the variable "crew_resource" is "IfcStore.get_file().by_type('IfcCrewResource')[0].id()"
|
||||
And the variable "crew_resource" is "IfcStore.get_file().by_type('IfcCrewResource')[0].id()"
|
||||
When I press "bim.add_resource(ifc_class="IfcLaborResource", parent_resource={crew_resource})"
|
||||
And the variable "labor_resource" is "IfcStore.get_file().by_type('IfcLaborResource')[0].id()"
|
||||
And the variable "labor_resource" is "IfcStore.get_file().by_type('IfcLaborResource')[0].id()"
|
||||
When I press "bim.enable_editing_resource_time(resource={labor_resource})"
|
||||
And I set "scene.BIMResourceProperties.resource_time_attributes.get('ScheduleUsage').float_value" to "305.25"
|
||||
When I press "bim.edit_resource_time()"
|
||||
@@ -116,9 +116,9 @@ Scenario: Enable Editing Resource Costs
|
||||
Given an empty IFC project
|
||||
When I press "bim.load_resources"
|
||||
When I press "bim.add_resource(ifc_class="IfcCrewResource")"
|
||||
And the variable "crew_resource" is "IfcStore.get_file().by_type('IfcCrewResource')[0].id()"
|
||||
And the variable "crew_resource" is "IfcStore.get_file().by_type('IfcCrewResource')[0].id()"
|
||||
When I press "bim.add_resource(ifc_class="IfcLaborResource", parent_resource={crew_resource})"
|
||||
And the variable "labor_resource" is "IfcStore.get_file().by_type('IfcLaborResource')[0].id()"
|
||||
And the variable "labor_resource" is "IfcStore.get_file().by_type('IfcLaborResource')[0].id()"
|
||||
When I press "bim.enable_editing_resource_costs(resource={labor_resource})"
|
||||
Then nothing happens
|
||||
|
||||
@@ -126,9 +126,9 @@ Scenario: Disable Editing Resource Costs
|
||||
Given an empty IFC project
|
||||
When I press "bim.load_resources"
|
||||
When I press "bim.add_resource(ifc_class="IfcCrewResource")"
|
||||
And the variable "crew_resource" is "IfcStore.get_file().by_type('IfcCrewResource')[0].id()"
|
||||
And the variable "crew_resource" is "IfcStore.get_file().by_type('IfcCrewResource')[0].id()"
|
||||
When I press "bim.add_resource(ifc_class="IfcLaborResource", parent_resource={crew_resource})"
|
||||
And the variable "labor_resource" is "IfcStore.get_file().by_type('IfcLaborResource')[0].id()"
|
||||
And the variable "labor_resource" is "IfcStore.get_file().by_type('IfcLaborResource')[0].id()"
|
||||
When I press "bim.enable_editing_resource_costs(resource={labor_resource})"
|
||||
When I press "bim.disable_editing_resource()"
|
||||
Then nothing happens
|
||||
@@ -138,9 +138,9 @@ Scenario: Add Resource Fixed Cost Value
|
||||
Given an empty IFC project
|
||||
When I press "bim.load_resources"
|
||||
When I press "bim.add_resource(ifc_class="IfcCrewResource")"
|
||||
And the variable "crew_resource" is "IfcStore.get_file().by_type('IfcCrewResource')[0].id()"
|
||||
And the variable "crew_resource" is "IfcStore.get_file().by_type('IfcCrewResource')[0].id()"
|
||||
When I press "bim.add_resource(ifc_class="IfcLaborResource", parent_resource={crew_resource})"
|
||||
And the variable "labor_resource" is "IfcStore.get_file().by_type('IfcLaborResource')[0].id()"
|
||||
And the variable "labor_resource" is "IfcStore.get_file().by_type('IfcLaborResource')[0].id()"
|
||||
When I press "bim.enable_editing_resource_costs(resource={labor_resource})"
|
||||
When I press "bim.add_cost_value(parent={labor_resource}, cost_type="FIXED")"
|
||||
Then nothing happens
|
||||
@@ -149,12 +149,12 @@ Scenario: Edit Resource Cost Value
|
||||
Given an empty IFC project
|
||||
When I press "bim.load_resources"
|
||||
When I press "bim.add_resource(ifc_class="IfcCrewResource")"
|
||||
And the variable "crew_resource" is "IfcStore.get_file().by_type('IfcCrewResource')[0].id()"
|
||||
And the variable "crew_resource" is "IfcStore.get_file().by_type('IfcCrewResource')[0].id()"
|
||||
When I press "bim.add_resource(ifc_class="IfcLaborResource", parent_resource={crew_resource})"
|
||||
And the variable "labor_resource" is "IfcStore.get_file().by_type('IfcLaborResource')[0].id()"
|
||||
And the variable "labor_resource" is "IfcStore.get_file().by_type('IfcLaborResource')[0].id()"
|
||||
When I press "bim.enable_editing_resource_costs(resource={labor_resource})"
|
||||
When I press "bim.add_cost_value(parent={labor_resource}, cost_type="FIXED")"
|
||||
And the variable "cost_value" is "IfcStore.get_file().by_type('IfcCostValue')[0].id()"
|
||||
And the variable "cost_value" is "IfcStore.get_file().by_type('IfcCostValue')[0].id()"
|
||||
When I press "bim.enable_editing_resource_cost_value(cost_value={cost_value})"
|
||||
Then nothing happens
|
||||
|
||||
@@ -162,12 +162,12 @@ Scenario: Enable Editing Resource Cost Value Formula
|
||||
Given an empty IFC project
|
||||
When I press "bim.load_resources"
|
||||
When I press "bim.add_resource(ifc_class="IfcCrewResource")"
|
||||
And the variable "crew_resource" is "IfcStore.get_file().by_type('IfcCrewResource')[0].id()"
|
||||
And the variable "crew_resource" is "IfcStore.get_file().by_type('IfcCrewResource')[0].id()"
|
||||
When I press "bim.add_resource(ifc_class="IfcLaborResource", parent_resource={crew_resource})"
|
||||
And the variable "labor_resource" is "IfcStore.get_file().by_type('IfcLaborResource')[0].id()"
|
||||
And the variable "labor_resource" is "IfcStore.get_file().by_type('IfcLaborResource')[0].id()"
|
||||
When I press "bim.enable_editing_resource_costs(resource={labor_resource})"
|
||||
When I press "bim.add_cost_value(parent={labor_resource}, cost_type="FIXED")"
|
||||
And the variable "cost_value" is "IfcStore.get_file().by_type('IfcCostValue')[0].id()"
|
||||
And the variable "cost_value" is "IfcStore.get_file().by_type('IfcCostValue')[0].id()"
|
||||
When I press "bim.enable_editing_resource_cost_value(cost_value={cost_value})"
|
||||
Then nothing happens
|
||||
|
||||
@@ -175,12 +175,12 @@ Scenario: Edit Resource Cost Value Formula
|
||||
Given an empty IFC project
|
||||
When I press "bim.load_resources"
|
||||
When I press "bim.add_resource(ifc_class="IfcCrewResource")"
|
||||
And the variable "crew_resource" is "IfcStore.get_file().by_type('IfcCrewResource')[0].id()"
|
||||
And the variable "crew_resource" is "IfcStore.get_file().by_type('IfcCrewResource')[0].id()"
|
||||
When I press "bim.add_resource(ifc_class="IfcLaborResource", parent_resource={crew_resource})"
|
||||
And the variable "labor_resource" is "IfcStore.get_file().by_type('IfcLaborResource')[0].id()"
|
||||
And the variable "labor_resource" is "IfcStore.get_file().by_type('IfcLaborResource')[0].id()"
|
||||
When I press "bim.enable_editing_resource_costs(resource={labor_resource})"
|
||||
When I press "bim.add_cost_value(parent={labor_resource}, cost_type="FIXED")"
|
||||
And the variable "cost_value" is "IfcStore.get_file().by_type('IfcCostValue')[0].id()"
|
||||
And the variable "cost_value" is "IfcStore.get_file().by_type('IfcCostValue')[0].id()"
|
||||
When I press "bim.enable_editing_resource_cost_value(cost_value={cost_value})"
|
||||
And I set "scene.BIMResourceProperties.cost_value_formula" to "220*0.2"
|
||||
When I press "bim.edit_resource_cost_value_formula(cost_value={cost_value})"
|
||||
@@ -190,12 +190,12 @@ Scenario: Edit Resource Cost Value
|
||||
Given an empty IFC project
|
||||
When I press "bim.load_resources"
|
||||
When I press "bim.add_resource(ifc_class="IfcCrewResource")"
|
||||
And the variable "crew_resource" is "IfcStore.get_file().by_type('IfcCrewResource')[0].id()"
|
||||
And the variable "crew_resource" is "IfcStore.get_file().by_type('IfcCrewResource')[0].id()"
|
||||
When I press "bim.add_resource(ifc_class="IfcLaborResource", parent_resource={crew_resource})"
|
||||
And the variable "labor_resource" is "IfcStore.get_file().by_type('IfcLaborResource')[0].id()"
|
||||
And the variable "labor_resource" is "IfcStore.get_file().by_type('IfcLaborResource')[0].id()"
|
||||
When I press "bim.enable_editing_resource_costs(resource={labor_resource})"
|
||||
When I press "bim.add_cost_value(parent={labor_resource}, cost_type="FIXED")"
|
||||
And the variable "cost_value" is "IfcStore.get_file().by_type('IfcCostValue')[0].id()"
|
||||
And the variable "cost_value" is "IfcStore.get_file().by_type('IfcCostValue')[0].id()"
|
||||
When I press "bim.enable_editing_resource_cost_value(cost_value={cost_value})"
|
||||
And I set "scene.BIMResourceProperties.cost_value_attributes.get('AppliedValue').float_value" to "1.00"
|
||||
When I press "bim.edit_resource_cost_value(cost_value={cost_value})"
|
||||
@@ -205,11 +205,11 @@ Scenario: Enable Editing Resource Quantity
|
||||
Given an empty IFC project
|
||||
When I press "bim.load_resources"
|
||||
When I press "bim.add_resource(ifc_class="IfcCrewResource")"
|
||||
And the variable "crew_resource" is "IfcStore.get_file().by_type('IfcCrewResource')[0].id()"
|
||||
And the variable "crew_resource" is "IfcStore.get_file().by_type('IfcCrewResource')[0].id()"
|
||||
When I press "bim.add_resource(ifc_class="IfcLaborResource", parent_resource={crew_resource})"
|
||||
And the variable "labor_resource" is "IfcStore.get_file().by_type('IfcLaborResource')[0].id()"
|
||||
And the variable "labor_resource" is "IfcStore.get_file().by_type('IfcLaborResource')[0].id()"
|
||||
When I press "bim.enable_editing_resource_base_quantity(resource={labor_resource})"
|
||||
When I press "bim.add_resource_quantity(resource={labor_resource}, ifc_class="IfcQuantityArea")"
|
||||
When I press "bim.add_resource_quantity(resource={labor_resource}, ifc_class="IfcQuantityTime")"
|
||||
When I press "bim.enable_editing_resource_quantity(resource={labor_resource})"
|
||||
Then nothing happens
|
||||
|
||||
@@ -217,27 +217,26 @@ Scenario: Edit Resource Quantity
|
||||
Given an empty IFC project
|
||||
When I press "bim.load_resources"
|
||||
When I press "bim.add_resource(ifc_class="IfcCrewResource")"
|
||||
And the variable "crew_resource" is "IfcStore.get_file().by_type('IfcCrewResource')[0].id()"
|
||||
And the variable "crew_resource" is "IfcStore.get_file().by_type('IfcCrewResource')[0].id()"
|
||||
When I press "bim.add_resource(ifc_class="IfcLaborResource", parent_resource={crew_resource})"
|
||||
And the variable "labor_resource" is "IfcStore.get_file().by_type('IfcLaborResource')[0].id()"
|
||||
And the variable "labor_resource" is "IfcStore.get_file().by_type('IfcLaborResource')[0].id()"
|
||||
When I press "bim.enable_editing_resource_base_quantity(resource={labor_resource})"
|
||||
When I press "bim.add_resource_quantity(resource={labor_resource}, ifc_class="IfcQuantityArea")"
|
||||
When I press "bim.add_resource_quantity(resource={labor_resource}, ifc_class="IfcQuantityTime")"
|
||||
When I press "bim.enable_editing_resource_quantity(resource={labor_resource})"
|
||||
And I set "scene.BIMResourceProperties.quantity_attributes.get('AreaValue').float_value" to "50.00"
|
||||
And the variable "quantity_area" is "IfcStore.get_file().by_type('IfcQuantityArea')[0].id()"
|
||||
When I press "bim.edit_resource_quantity(physical_quantity={quantity_area})"
|
||||
And I set "scene.BIMResourceProperties.quantity_attributes.get('TimeValue').float_value" to "50.00"
|
||||
And the variable "quantity_time" is "IfcStore.get_file().by_type('IfcQuantityTime')[0].id()"
|
||||
When I press "bim.edit_resource_quantity(physical_quantity={quantity_time})"
|
||||
Then nothing happens
|
||||
|
||||
Scenario: Remove Resource Quantity
|
||||
Given an empty IFC project
|
||||
When I press "bim.load_resources"
|
||||
When I press "bim.add_resource(ifc_class="IfcCrewResource")"
|
||||
And the variable "crew_resource" is "IfcStore.get_file().by_type('IfcCrewResource')[0].id()"
|
||||
And the variable "crew_resource" is "IfcStore.get_file().by_type('IfcCrewResource')[0].id()"
|
||||
When I press "bim.add_resource(ifc_class="IfcLaborResource", parent_resource={crew_resource})"
|
||||
And the variable "labor_resource" is "IfcStore.get_file().by_type('IfcLaborResource')[0].id()"
|
||||
And the variable "labor_resource" is "IfcStore.get_file().by_type('IfcLaborResource')[0].id()"
|
||||
When I press "bim.enable_editing_resource_base_quantity(resource={labor_resource})"
|
||||
When I press "bim.add_resource_quantity(resource={labor_resource}, ifc_class="IfcQuantityArea")"
|
||||
And the variable "quantity_area" is "IfcStore.get_file().by_type('IfcQuantityArea')[0].id()"
|
||||
When I press "bim.add_resource_quantity(resource={labor_resource}, ifc_class="IfcQuantityTime")"
|
||||
When I press "bim.remove_resource_quantity(resource={labor_resource})"
|
||||
Then nothing happens
|
||||
|
||||
@@ -245,9 +244,9 @@ Scenario: Add Productivity data
|
||||
Given an empty IFC project
|
||||
When I press "bim.load_resources"
|
||||
When I press "bim.add_resource(ifc_class="IfcCrewResource")"
|
||||
And the variable "crew_resource" is "IfcStore.get_file().by_type('IfcCrewResource')[0].id()"
|
||||
And the variable "crew_resource" is "IfcStore.get_file().by_type('IfcCrewResource')[0].id()"
|
||||
When I press "bim.add_resource(ifc_class="IfcLaborResource", parent_resource={crew_resource})"
|
||||
And the variable "labor_resource" is "IfcStore.get_file().by_type('IfcLaborResource')[0].id()"
|
||||
And the variable "labor_resource" is "IfcStore.get_file().by_type('IfcLaborResource')[0].id()"
|
||||
And I set "scene.BIMResourceProperties.active_resource_index" to "1"
|
||||
And I press "bim.add_productivity_data"
|
||||
And I set "scene.BIMResourceProperties.should_show_resource_tools" to "True"
|
||||
@@ -284,9 +283,9 @@ Scenario: Calculate Resource Work
|
||||
And I press "bim.edit_pset(obj='IfcWall/Cube', obj_type='Object')"
|
||||
When I press "bim.load_resources"
|
||||
When I press "bim.add_resource(ifc_class="IfcCrewResource")"
|
||||
And the variable "crew_resource" is "IfcStore.get_file().by_type('IfcCrewResource')[0].id()"
|
||||
And the variable "crew_resource" is "IfcStore.get_file().by_type('IfcCrewResource')[0].id()"
|
||||
When I press "bim.add_resource(ifc_class="IfcLaborResource", parent_resource={crew_resource})"
|
||||
And the variable "labor_resource" is "IfcStore.get_file().by_type('IfcLaborResource')[0].id()"
|
||||
And the variable "labor_resource" is "IfcStore.get_file().by_type('IfcLaborResource')[0].id()"
|
||||
And I set "scene.BIMResourceProperties.active_resource_index" to "1"
|
||||
And I press "bim.add_productivity_data"
|
||||
And I set "scene.BIMResourceProperties.should_show_resource_tools" to "True"
|
||||
@@ -294,41 +293,41 @@ Scenario: Calculate Resource Work
|
||||
And I set "scene.BIMResourceProductivity.quantity_produced_name" to "GrossSideArea"
|
||||
And I set "scene.BIMResourceProductivity.quantity_consumed[0].hours" to "5"
|
||||
When I press "bim.edit_productivity_data()"
|
||||
And the variable "productivity_data" is "IfcStore.get_file().by_type('IfcPropertySet')[-1].id()"
|
||||
And the variable "productivity_data" is "IfcStore.get_file().by_type('IfcPropertySet')[-1].id()"
|
||||
When I press "bim.calculate_resource_work(resource={labor_resource})"
|
||||
Then nothing happens
|
||||
Then nothing happens
|
||||
|
||||
|
||||
Scenario: Assign Resource
|
||||
Given an empty IFC project
|
||||
When I press "bim.load_resources"
|
||||
When I press "bim.add_resource(ifc_class="IfcCrewResource")"
|
||||
And the variable "crew_resource" is "IfcStore.get_file().by_type('IfcCrewResource')[0].id()"
|
||||
And the variable "crew_resource" is "IfcStore.get_file().by_type('IfcCrewResource')[0].id()"
|
||||
When I press "bim.add_resource(ifc_class="IfcLaborResource", parent_resource={crew_resource})"
|
||||
And the variable "labor_resource" is "IfcStore.get_file().by_type('IfcLaborResource')[0].id()"
|
||||
And the variable "labor_resource" is "IfcStore.get_file().by_type('IfcLaborResource')[0].id()"
|
||||
And I add a cube
|
||||
And the object "Cube" is selected
|
||||
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
|
||||
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
|
||||
And I press "bim.assign_class"
|
||||
And the object "IfcWall/Cube" is selected
|
||||
And I press "bim.assign_resource(resource={labor_resource}, related_object="")"
|
||||
And I press "bim.assign_resource(resource={labor_resource})"
|
||||
Then nothing happens
|
||||
|
||||
Scenario: UnAssign Resource
|
||||
Scenario: Unassign Resource
|
||||
Given an empty IFC project
|
||||
When I press "bim.load_resources"
|
||||
When I press "bim.add_resource(ifc_class="IfcCrewResource")"
|
||||
And the variable "crew_resource" is "IfcStore.get_file().by_type('IfcCrewResource')[0].id()"
|
||||
And the variable "crew_resource" is "IfcStore.get_file().by_type('IfcCrewResource')[0].id()"
|
||||
When I press "bim.add_resource(ifc_class="IfcLaborResource", parent_resource={crew_resource})"
|
||||
And the variable "labor_resource" is "IfcStore.get_file().by_type('IfcLaborResource')[0].id()"
|
||||
And the variable "labor_resource" is "IfcStore.get_file().by_type('IfcLaborResource')[0].id()"
|
||||
And I add a cube
|
||||
And the object "Cube" is selected
|
||||
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
|
||||
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
|
||||
And I press "bim.assign_class"
|
||||
And the object "IfcWall/Cube" is selected
|
||||
And I press "bim.assign_resource(resource={labor_resource}, related_object="")"
|
||||
And I press "bim.assign_resource(resource={labor_resource})"
|
||||
And the object "IfcWall/Cube" is selected
|
||||
And I press "bim.unassign_resource(resource={labor_resource}, related_object="")"
|
||||
Then nothing happens
|
||||
And I press "bim.unassign_resource(resource={labor_resource})"
|
||||
Then nothing happens
|
||||
|
||||
@@ -64,7 +64,7 @@ Scenario: Assign a type class to a cube
|
||||
And I set "scene.BIMRootProperties.ifc_class" to "IfcWallType"
|
||||
And I press "bim.assign_class"
|
||||
Then the object "IfcWallType/Cube" is an "IfcWallType"
|
||||
And the object "IfcWallType/Cube" is in the collection "Types"
|
||||
And the object "IfcWallType/Cube" is in the collection "IfcTypeProduct"
|
||||
And the object "IfcWallType/Cube" has a "Tessellation" representation of "Model/Body/MODEL_VIEW"
|
||||
|
||||
Scenario: Assign a spatial class to a cube
|
||||
@@ -88,7 +88,6 @@ Scenario: Assign a spatial class to a cube already in a collection
|
||||
And I press "bim.assign_class"
|
||||
Then the object "IfcSpace/Cube" is an "IfcSpace"
|
||||
And the object "IfcSpace/Cube" is in the collection "IfcSpace/Cube"
|
||||
And the collection "IfcSpace/Cube" is in the collection "IfcBuildingStorey/My Storey"
|
||||
And the object "IfcSpace/Cube" has a "Tessellation" representation of "Model/Body/MODEL_VIEW"
|
||||
|
||||
Scenario: Assign a class to a cube in a collection
|
||||
@@ -118,4 +117,3 @@ Scenario: Copy a storey
|
||||
Then the object "IfcBuildingStorey/My Storey" and "IfcBuildingStorey/My Storey.001" are different elements
|
||||
And the object "IfcBuildingStorey/My Storey" is in the collection "IfcBuildingStorey/My Storey"
|
||||
And the object "IfcBuildingStorey/My Storey.001" is in the collection "IfcBuildingStorey/My Storey.001"
|
||||
And the collection "IfcBuildingStorey/My Storey.001" is in the collection "IfcBuilding/My Building"
|
||||
|
||||
@@ -12,5 +12,5 @@ Scenario: Select all walls
|
||||
And I set "scene.BIMSearchProperties.facet" to "entity"
|
||||
And I press "bim.add_filter(index=0, type='entity', module='search')"
|
||||
And I set "scene.BIMSearchProperties.filter_groups[0].filters[0].value" to "IfcWall"
|
||||
When I press "bim.search"
|
||||
When I press "bim.search(property_group='BIMSearchProperties')"
|
||||
Then the object "IfcWall/Cube" is selected
|
||||
|
||||
@@ -27,7 +27,8 @@ Scenario: Assign container
|
||||
And the object "IfcWall/Cube" is selected
|
||||
And I press "bim.enable_editing_container"
|
||||
And the variable "site" is "tool.Ifc.get().by_type('IfcSite')[0].id()"
|
||||
When I press "bim.assign_container(structure={site})"
|
||||
And I press "bim.set_default_container(container={site})"
|
||||
When I press "bim.assign_container()"
|
||||
Then the object "IfcWall/Cube" is in the collection "IfcSite/My Site"
|
||||
|
||||
Scenario: Copy to container
|
||||
@@ -37,10 +38,10 @@ Scenario: Copy to container
|
||||
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
|
||||
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
|
||||
And I press "bim.assign_class"
|
||||
And the object "IfcWall/Cube" is selected
|
||||
And the object "IfcSite/My Site" is selected
|
||||
And additionally the object "IfcWall/Cube" is selected
|
||||
And I press "bim.enable_editing_container"
|
||||
When I set "scene.BIMSpatialProperties.containers[0].is_selected" to "True"
|
||||
And I press "bim.copy_to_container"
|
||||
When I press "bim.copy_to_container"
|
||||
Then the object "IfcWall/Cube.001" is in the collection "IfcSite/My Site"
|
||||
|
||||
Scenario: Reference structure
|
||||
@@ -50,10 +51,10 @@ Scenario: Reference structure
|
||||
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
|
||||
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
|
||||
And I press "bim.assign_class"
|
||||
And the object "IfcWall/Cube" is selected
|
||||
And the object "IfcBuilding/My Building" is selected
|
||||
And additionally the object "IfcWall/Cube" is selected
|
||||
And I press "bim.enable_editing_container"
|
||||
When I set "scene.BIMSpatialProperties.containers[0].is_selected" to "True"
|
||||
And I press "bim.reference_structure"
|
||||
When I press "bim.reference_structure"
|
||||
Then nothing happens
|
||||
|
||||
Scenario: Dereference structure
|
||||
@@ -63,10 +64,10 @@ Scenario: Dereference structure
|
||||
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
|
||||
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
|
||||
And I press "bim.assign_class"
|
||||
And the object "IfcWall/Cube" is selected
|
||||
And the object "IfcBuilding/My Building" is selected
|
||||
And additionally the object "IfcWall/Cube" is selected
|
||||
And I press "bim.enable_editing_container"
|
||||
When I set "scene.BIMSpatialProperties.containers[0].is_selected" to "True"
|
||||
And I press "bim.reference_structure"
|
||||
When I press "bim.reference_structure"
|
||||
And I press "bim.dereference_structure"
|
||||
Then nothing happens
|
||||
|
||||
@@ -80,7 +81,8 @@ Scenario: Select container
|
||||
And the object "IfcWall/Cube" is selected
|
||||
And I press "bim.enable_editing_container"
|
||||
And the variable "site" is "tool.Ifc.get().by_type('IfcSite')[0].id()"
|
||||
And I press "bim.assign_container(structure={site})"
|
||||
And I press "bim.set_default_container(container={site})"
|
||||
And I press "bim.assign_container()"
|
||||
When I press "bim.select_container"
|
||||
Then nothing happens
|
||||
|
||||
@@ -94,7 +96,8 @@ Scenario: Select similar container
|
||||
And the object "IfcWall/Cube" is selected
|
||||
And I press "bim.enable_editing_container"
|
||||
And the variable "site" is "tool.Ifc.get().by_type('IfcSite')[0].id()"
|
||||
And I press "bim.assign_container(structure={site})"
|
||||
And I press "bim.set_default_container(container={site})"
|
||||
And I press "bim.assign_container()"
|
||||
When I press "bim.select_similar_container"
|
||||
Then nothing happens
|
||||
|
||||
|
||||
@@ -75,7 +75,6 @@ Scenario: Disable editing style
|
||||
|
||||
Scenario: Load styles
|
||||
Given an empty IFC project
|
||||
And I press "bim.add_style"
|
||||
When I press "bim.load_styles(style_type='IfcSurfaceStyle')"
|
||||
Then nothing happens
|
||||
|
||||
|
||||
@@ -684,17 +684,17 @@ def prop_is_roughly_value(prop, value):
|
||||
value = replace_variables(value)
|
||||
is_value = False
|
||||
try:
|
||||
exec(f'assert round(bpy.context.{prop}, 5) == "{value}"')
|
||||
exec(f'assert round(bpy.context.{prop}, 3) == "{value}"')
|
||||
is_value = True
|
||||
except:
|
||||
try:
|
||||
exec(f"assert round(bpy.context.{prop}, 5) == {value}")
|
||||
exec(f"assert round(bpy.context.{prop}, 3) == {value}")
|
||||
is_value = True
|
||||
except:
|
||||
pass
|
||||
if not is_value:
|
||||
print(f"bpy.context.{prop}")
|
||||
actual_value = round(eval(f"bpy.context.{prop}"), 5)
|
||||
actual_value = round(eval(f"bpy.context.{prop}"), 3)
|
||||
assert False, f"Value is {actual_value}"
|
||||
|
||||
|
||||
@@ -868,6 +868,37 @@ def the_object_name_is_contained_in_container_name(name, container_name):
|
||||
assert container.Name == container_name, f'Object "{name}" is in {container}'
|
||||
|
||||
|
||||
@then(parsers.parse('the object "{name}" is contained in object "{container_name}"'))
|
||||
def the_object_name_is_contained_in_object_container_name(name: str, container_name: str) -> None:
|
||||
ifc = an_ifc_file_exists()
|
||||
element = ifc.by_id(the_object_name_exists(name).BIMObjectProperties.ifc_definition_id)
|
||||
container = ifcopenshell.util.element.get_container(element)
|
||||
if not container:
|
||||
assert False, f'Object "{name}" is not in any container'
|
||||
container_obj = tool.Ifc.get_object(container)
|
||||
assert container_obj.name == container_name, f'Object "{name}" is in {container_obj}'
|
||||
|
||||
|
||||
@then(parsers.parse('the object "{name}" is aggregated by object "{aggregate_name}"'))
|
||||
def the_object_name_is_aggregated_by_object_aggregate_name(name: str, aggregate_name: str) -> None:
|
||||
ifc = an_ifc_file_exists()
|
||||
element = ifc.by_id(the_object_name_exists(name).BIMObjectProperties.ifc_definition_id)
|
||||
aggregate = ifcopenshell.util.element.get_aggregate(element)
|
||||
if not aggregate:
|
||||
assert False, f'Object "{name}" is not aggregated by any element'
|
||||
aggregate_obj = tool.Ifc.get_object(aggregate)
|
||||
assert aggregate_obj.name == aggregate_name, f'Object "{name}" is aggregated by {aggregate_obj}'
|
||||
|
||||
|
||||
@then(parsers.parse('the object "{name}" has no aggregate'))
|
||||
def the_object_name_has_no_aggregate(name: str) -> None:
|
||||
ifc = an_ifc_file_exists()
|
||||
element = ifc.by_id(the_object_name_exists(name).BIMObjectProperties.ifc_definition_id)
|
||||
aggregate = ifcopenshell.util.element.get_aggregate(element)
|
||||
if aggregate:
|
||||
assert False, f'Object "{name}" is aggregated by element "{aggregate}"'
|
||||
|
||||
|
||||
@then(parsers.parse('the file "{name}" should contain "{value}"'))
|
||||
def the_file_name_should_contain_value(name, value):
|
||||
name = replace_variables(name)
|
||||
|
||||
@@ -36,6 +36,7 @@ class TestAssignObject:
|
||||
def test_run(self, ifc, aggregate, collector):
|
||||
aggregate.can_aggregate("relating_obj", "related_obj").should_be_called().will_return(True)
|
||||
ifc.get_entity("relating_obj").should_be_called().will_return("relating_object")
|
||||
aggregate.has_physical_body_representation("relating_object").should_be_called().will_return(False)
|
||||
ifc.get_entity("related_obj").should_be_called().will_return("related_object")
|
||||
ifc.run(
|
||||
"aggregate.assign_object", products=["related_object"], relating_object="relating_object"
|
||||
|
||||
@@ -26,6 +26,7 @@ class TestEditObjectPlacement:
|
||||
ifc.get_entity("obj").should_be_called().will_return("element")
|
||||
geometry.clear_cache("element").should_be_called()
|
||||
geometry.clear_scale("obj").should_be_called()
|
||||
geometry.get_blender_offset_type("obj").should_be_called()
|
||||
surveyor.get_absolute_matrix("obj").should_be_called().will_return("matrix")
|
||||
ifc.run("geometry.edit_object_placement", product="element", matrix="matrix").should_be_called()
|
||||
geometry.record_object_position("obj").should_be_called()
|
||||
@@ -193,6 +194,7 @@ class TestSwitchRepresentation:
|
||||
def test_switching_to_a_freshly_loaded_representation(self, ifc, geometry):
|
||||
geometry.is_edited("obj").should_be_called().will_return(False)
|
||||
geometry.get_object_data("obj").should_be_called().will_return("current_obj_data")
|
||||
geometry.should_use_immediate_representation("element", True).should_be_called().will_return(False)
|
||||
geometry.resolve_mapped_representation("mapped_rep").should_be_called().will_return("representation")
|
||||
geometry.get_representation_data("representation").should_be_called().will_return(None)
|
||||
geometry.import_representation("obj", "representation", apply_openings=True).should_be_called().will_return(
|
||||
@@ -220,6 +222,7 @@ class TestSwitchRepresentation:
|
||||
def test_switching_to_a_reloaded_representation_and_deleting_the_existing_data(self, ifc, geometry):
|
||||
geometry.is_edited("obj").should_be_called().will_return(False)
|
||||
geometry.get_object_data("obj").should_be_called().will_return("current_obj_data")
|
||||
geometry.should_use_immediate_representation("element", True).should_be_called().will_return(False)
|
||||
geometry.resolve_mapped_representation("mapped_rep").should_be_called().will_return("representation")
|
||||
geometry.get_representation_data("representation").should_be_called().will_return("existing_data")
|
||||
geometry.import_representation("obj", "representation", apply_openings=True).should_be_called().will_return(
|
||||
@@ -248,13 +251,35 @@ class TestSwitchRepresentation:
|
||||
|
||||
def test_switching_to_an_existing_representation(self, ifc, geometry):
|
||||
geometry.is_edited("obj").should_be_called().will_return(False)
|
||||
ifc.get_entity("obj").should_be_called().will_return("element")
|
||||
geometry.get_object_data("obj").should_be_called().will_return("current_obj_data")
|
||||
geometry.should_use_immediate_representation("element", True).should_be_called().will_return(False)
|
||||
geometry.resolve_mapped_representation("mapped_rep").should_be_called().will_return("representation")
|
||||
geometry.get_representation_data("representation").should_be_called().will_return("data")
|
||||
geometry.change_object_data("obj", "data", is_global=True).should_be_called()
|
||||
geometry.record_object_materials("obj").should_be_called()
|
||||
geometry.clear_modifiers("obj").should_be_called()
|
||||
geometry.clear_cache("element").should_be_called()
|
||||
subject.switch_representation(
|
||||
ifc,
|
||||
geometry,
|
||||
obj="obj",
|
||||
representation="mapped_rep",
|
||||
should_reload=False,
|
||||
is_global=True,
|
||||
should_sync_changes_first=True,
|
||||
)
|
||||
|
||||
def test_switching_to_an_existing_representation_reuse_representation(self, ifc, geometry):
|
||||
geometry.is_edited("obj").should_be_called().will_return(False)
|
||||
ifc.get_entity("obj").should_be_called().will_return("element")
|
||||
geometry.get_object_data("obj").should_be_called().will_return("current_obj_data")
|
||||
geometry.should_use_immediate_representation("element", True).should_be_called().will_return(True)
|
||||
geometry.unresolve_type_representation("mapped_rep", "element").should_be_called().will_return("representation")
|
||||
geometry.get_representation_data("representation").should_be_called().will_return("data")
|
||||
geometry.change_object_data("obj", "data", is_global=False).should_be_called()
|
||||
geometry.record_object_materials("obj").should_be_called()
|
||||
geometry.clear_modifiers("obj").should_be_called()
|
||||
geometry.clear_cache("element").should_be_called()
|
||||
subject.switch_representation(
|
||||
ifc,
|
||||
@@ -273,6 +298,7 @@ class TestSwitchRepresentation:
|
||||
geometry.run_geometry_update_representation(obj="obj").should_be_called()
|
||||
geometry.does_representation_id_exist("representation_id").should_be_called().will_return(True)
|
||||
geometry.get_object_data("obj").should_be_called().will_return("current_obj_data")
|
||||
geometry.should_use_immediate_representation("element", True).should_be_called().will_return(False)
|
||||
geometry.resolve_mapped_representation("mapped_rep").should_be_called().will_return("representation")
|
||||
geometry.get_representation_data("representation").should_be_called().will_return("data")
|
||||
geometry.change_object_data("obj", "data", is_global=False).should_be_called()
|
||||
|
||||
@@ -18,13 +18,13 @@
|
||||
|
||||
|
||||
import blenderbim.core.project as subject
|
||||
from test.core.bootstrap import ifc, project
|
||||
from test.core.bootstrap import ifc, project, spatial, georeference
|
||||
|
||||
|
||||
class TestCreateProject:
|
||||
def test_do_nothing_if_a_project_already_exists(self, ifc, project):
|
||||
def test_do_nothing_if_a_project_already_exists(self, ifc, georeference, project, spatial):
|
||||
ifc.get().should_be_called().will_return("ifc")
|
||||
subject.create_project(ifc, project, schema="IFC4", template=None)
|
||||
subject.create_project(ifc, georeference, project, spatial, schema="IFC4", template=None)
|
||||
|
||||
def check_contexts(self, project):
|
||||
project.run_context_add_context(
|
||||
@@ -67,7 +67,7 @@ class TestCreateProject:
|
||||
context_type="Plan", context_identifier="Annotation", target_view="PLAN_VIEW", parent="plan"
|
||||
).should_be_called()
|
||||
|
||||
def test_create_an_ifc4_project(self, ifc, project):
|
||||
def test_create_an_ifc4_project(self, ifc, georeference, project, spatial):
|
||||
ifc.get().should_be_called().will_return(None)
|
||||
ifc.run("project.create_file", version="IFC4").should_be_called().will_return("ifc")
|
||||
ifc.set("ifc").should_be_called()
|
||||
@@ -92,14 +92,17 @@ class TestCreateProject:
|
||||
project.run_aggregate_assign_object(relating_obj="building", related_obj="storey").should_be_called()
|
||||
|
||||
project.set_context("body").should_be_called()
|
||||
spatial.run_spatial_import_spatial_decomposition().should_be_called()
|
||||
spatial.guess_default_container().should_be_called().will_return(None)
|
||||
|
||||
project.load_default_thumbnails().should_be_called()
|
||||
project.set_default_context().should_be_called()
|
||||
project.set_default_modeling_dimensions().should_be_called()
|
||||
georeference.set_model_origin().should_be_called()
|
||||
|
||||
subject.create_project(ifc, project, schema="IFC4", template=None)
|
||||
subject.create_project(ifc, georeference, project, spatial, schema="IFC4", template=None)
|
||||
|
||||
def test_appending_project_template_types_if_specified(self, ifc, project):
|
||||
def test_create_an_ifc4_project_with_guessing_default_container(self, ifc, georeference, project, spatial):
|
||||
ifc.get().should_be_called().will_return(None)
|
||||
ifc.run("project.create_file", version="IFC4").should_be_called().will_return("ifc")
|
||||
ifc.set("ifc").should_be_called()
|
||||
@@ -124,16 +127,55 @@ class TestCreateProject:
|
||||
project.run_aggregate_assign_object(relating_obj="building", related_obj="storey").should_be_called()
|
||||
|
||||
project.set_context("body").should_be_called()
|
||||
spatial.run_spatial_import_spatial_decomposition().should_be_called()
|
||||
spatial.guess_default_container().should_be_called().will_return("default_container")
|
||||
spatial.set_default_container("default_container").should_be_called()
|
||||
|
||||
project.load_default_thumbnails().should_be_called()
|
||||
project.set_default_context().should_be_called()
|
||||
project.set_default_modeling_dimensions().should_be_called()
|
||||
georeference.set_model_origin().should_be_called()
|
||||
|
||||
subject.create_project(ifc, georeference, project, spatial, schema="IFC4", template=None)
|
||||
|
||||
def test_appending_project_template_types_if_specified(self, ifc, georeference, project, spatial):
|
||||
ifc.get().should_be_called().will_return(None)
|
||||
ifc.run("project.create_file", version="IFC4").should_be_called().will_return("ifc")
|
||||
ifc.set("ifc").should_be_called()
|
||||
|
||||
project.create_empty("My Project").should_be_called().will_return("project")
|
||||
project.create_empty("My Site").should_be_called().will_return("site")
|
||||
project.create_empty("My Building").should_be_called().will_return("building")
|
||||
project.create_empty("My Storey").should_be_called().will_return("storey")
|
||||
project.run_root_assign_class(
|
||||
obj="project", ifc_class="IfcProject", should_add_representation=False
|
||||
).should_be_called()
|
||||
project.run_unit_assign_scene_units().should_be_called()
|
||||
|
||||
self.check_contexts(project)
|
||||
|
||||
project.run_root_assign_class(obj="site", ifc_class="IfcSite", context="body").should_be_called()
|
||||
project.run_root_assign_class(obj="building", ifc_class="IfcBuilding", context="body").should_be_called()
|
||||
project.run_root_assign_class(obj="storey", ifc_class="IfcBuildingStorey", context="body").should_be_called()
|
||||
|
||||
project.run_aggregate_assign_object(relating_obj="project", related_obj="site").should_be_called()
|
||||
project.run_aggregate_assign_object(relating_obj="site", related_obj="building").should_be_called()
|
||||
project.run_aggregate_assign_object(relating_obj="building", related_obj="storey").should_be_called()
|
||||
|
||||
project.set_context("body").should_be_called()
|
||||
spatial.run_spatial_import_spatial_decomposition().should_be_called()
|
||||
spatial.guess_default_container().should_be_called().will_return(None)
|
||||
|
||||
project.append_all_types_from_template("template").should_be_called()
|
||||
|
||||
project.load_default_thumbnails().should_be_called()
|
||||
project.set_default_context().should_be_called()
|
||||
project.set_default_modeling_dimensions().should_be_called()
|
||||
georeference.set_model_origin().should_be_called()
|
||||
|
||||
subject.create_project(ifc, project, schema="IFC4", template="template")
|
||||
subject.create_project(ifc, georeference, project, spatial, schema="IFC4", template="template")
|
||||
|
||||
def test_create_an_ifc2x3_project_with_owner_defaults(self, ifc, project):
|
||||
def test_create_an_ifc2x3_project_with_owner_defaults(self, ifc, georeference, project, spatial):
|
||||
ifc.get().should_be_called().will_return(None)
|
||||
ifc.run("project.create_file", version="IFC2X3").should_be_called().will_return("ifc")
|
||||
ifc.set("ifc").should_be_called()
|
||||
@@ -165,9 +207,12 @@ class TestCreateProject:
|
||||
project.run_aggregate_assign_object(relating_obj="building", related_obj="storey").should_be_called()
|
||||
|
||||
project.set_context("body").should_be_called()
|
||||
spatial.run_spatial_import_spatial_decomposition().should_be_called()
|
||||
spatial.guess_default_container().should_be_called().will_return(None)
|
||||
|
||||
project.load_default_thumbnails().should_be_called()
|
||||
project.set_default_context().should_be_called()
|
||||
project.set_default_modeling_dimensions().should_be_called()
|
||||
georeference.set_model_origin().should_be_called()
|
||||
|
||||
subject.create_project(ifc, project, schema="IFC2X3", template=None)
|
||||
subject.create_project(ifc, georeference, project, spatial, schema="IFC2X3", template=None)
|
||||
|
||||
@@ -116,7 +116,9 @@ class TestAssignClass:
|
||||
ifc_representation_class="ifc_representation_class",
|
||||
)
|
||||
|
||||
def test_assign_a_class_with_geometry_and_autodetected_spatial_container(self, ifc, collector, root):
|
||||
def test_assign_a_class_with_geometry_and_autodetected_spatial_container_spatial_element(
|
||||
self, ifc, collector, root
|
||||
):
|
||||
ifc.get_entity("obj").should_be_called().will_return(None)
|
||||
root.get_object_name("obj").should_be_called().will_return("name")
|
||||
ifc.run(
|
||||
@@ -127,7 +129,11 @@ class TestAssignClass:
|
||||
root.run_geometry_add_representation(
|
||||
obj="obj", context="context", ifc_representation_class="ifc_representation_class", profile_set_usage=None
|
||||
).should_be_called()
|
||||
collector.sync("obj").should_be_called()
|
||||
|
||||
root.get_default_container().should_be_called().will_return("default_container")
|
||||
root.is_spatial_element("element").should_be_called().will_return(True)
|
||||
ifc.run("aggregate.assign_object", products=["element"], relating_object="default_container").should_be_called()
|
||||
|
||||
collector.assign("obj").should_be_called()
|
||||
subject.assign_class(
|
||||
ifc,
|
||||
@@ -141,7 +147,9 @@ class TestAssignClass:
|
||||
ifc_representation_class="ifc_representation_class",
|
||||
)
|
||||
|
||||
def test_not_adding_a_representation_if_requested(self, ifc, collector, root):
|
||||
def test_assign_a_class_with_geometry_and_autodetected_spatial_container_non_spatial_containable(
|
||||
self, ifc, collector, root
|
||||
):
|
||||
ifc.get_entity("obj").should_be_called().will_return(None)
|
||||
root.get_object_name("obj").should_be_called().will_return("name")
|
||||
ifc.run(
|
||||
@@ -149,7 +157,39 @@ class TestAssignClass:
|
||||
).should_be_called().will_return("element")
|
||||
root.set_object_name("obj", "element").should_be_called()
|
||||
ifc.link("element", "obj").should_be_called()
|
||||
collector.sync("obj").should_be_called()
|
||||
root.run_geometry_add_representation(
|
||||
obj="obj", context="context", ifc_representation_class="ifc_representation_class", profile_set_usage=None
|
||||
).should_be_called()
|
||||
|
||||
root.get_default_container().should_be_called().will_return("default_container")
|
||||
root.is_spatial_element("element").should_be_called().will_return(False)
|
||||
root.is_containable("element").should_be_called().will_return(True)
|
||||
ifc.run(
|
||||
"spatial.assign_container", products=["element"], relating_structure="default_container"
|
||||
).should_be_called()
|
||||
|
||||
collector.assign("obj").should_be_called()
|
||||
subject.assign_class(
|
||||
ifc,
|
||||
collector,
|
||||
root,
|
||||
obj="obj",
|
||||
ifc_class="ifc_class",
|
||||
predefined_type="predefined_type",
|
||||
should_add_representation=True,
|
||||
context="context",
|
||||
ifc_representation_class="ifc_representation_class",
|
||||
)
|
||||
|
||||
def test_not_adding_a_representation_if_requested_no_default_container(self, ifc, collector, root):
|
||||
ifc.get_entity("obj").should_be_called().will_return(None)
|
||||
root.get_object_name("obj").should_be_called().will_return("name")
|
||||
ifc.run(
|
||||
"root.create_entity", ifc_class="ifc_class", predefined_type="predefined_type", name="name"
|
||||
).should_be_called().will_return("element")
|
||||
root.set_object_name("obj", "element").should_be_called()
|
||||
ifc.link("element", "obj").should_be_called()
|
||||
root.get_default_container().should_be_called().will_return(None)
|
||||
collector.assign("obj").should_be_called()
|
||||
subject.assign_class(
|
||||
ifc,
|
||||
|
||||
@@ -38,24 +38,21 @@ class TestDereferenceStructure:
|
||||
|
||||
class TestAssignContainer:
|
||||
def test_run(self, ifc, collector, spatial):
|
||||
spatial.can_contain("structure_obj", "element_obj").should_be_called().will_return(True)
|
||||
ifc.get_entity("structure_obj").should_be_called().will_return("structure")
|
||||
spatial.can_contain("container", "element_obj").should_be_called().will_return(True)
|
||||
ifc.get_entity("element_obj").should_be_called().will_return("element")
|
||||
ifc.run(
|
||||
"spatial.assign_container", products=["element"], relating_structure="structure"
|
||||
"spatial.assign_container", products=["element"], relating_structure="container"
|
||||
).should_be_called().will_return("rel")
|
||||
spatial.disable_editing("element_obj").should_be_called()
|
||||
collector.assign("element_obj").should_be_called()
|
||||
assert (
|
||||
subject.assign_container(ifc, collector, spatial, structure_obj="structure_obj", element_obj="element_obj")
|
||||
== "rel"
|
||||
subject.assign_container(ifc, collector, spatial, container="container", element_obj="element_obj") == "rel"
|
||||
)
|
||||
|
||||
|
||||
class TestEnableEditingContainer:
|
||||
def test_run(self, spatial):
|
||||
spatial.enable_editing("obj").should_be_called()
|
||||
spatial.import_containers().should_be_called()
|
||||
subject.enable_editing_container(spatial, obj="obj")
|
||||
|
||||
|
||||
@@ -65,12 +62,6 @@ class TestDisableEditingContainer:
|
||||
subject.disable_editing_container(spatial, obj="obj")
|
||||
|
||||
|
||||
class TestChangeSpatialLevel:
|
||||
def test_run(self, spatial):
|
||||
spatial.import_containers(parent="parent").should_be_called()
|
||||
subject.change_spatial_level(spatial, parent="parent")
|
||||
|
||||
|
||||
class TestRemoveContainer:
|
||||
def test_run(self, ifc, collector):
|
||||
ifc.get_entity("obj").should_be_called().will_return("element")
|
||||
@@ -90,7 +81,7 @@ class TestCopyToContainer:
|
||||
spatial.duplicate_object_and_data("obj").should_be_called().will_return("new_obj")
|
||||
spatial.set_relative_object_matrix("new_obj", "to_container_obj", "matrix").should_be_called()
|
||||
spatial.run_root_copy_class(obj="new_obj").should_be_called()
|
||||
spatial.run_spatial_assign_container(structure_obj="to_container_obj", element_obj="new_obj").should_be_called()
|
||||
spatial.run_spatial_assign_container(container="to_container", element_obj="new_obj").should_be_called()
|
||||
|
||||
spatial.disable_editing("obj").should_be_called()
|
||||
|
||||
@@ -105,7 +96,7 @@ class TestCopyToContainer:
|
||||
spatial.duplicate_object_and_data("obj").should_be_called().will_return("new_obj")
|
||||
spatial.set_relative_object_matrix("new_obj", "to_container_obj", "matrix").should_be_called()
|
||||
spatial.run_root_copy_class(obj="new_obj").should_be_called()
|
||||
spatial.run_spatial_assign_container(structure_obj="to_container_obj", element_obj="new_obj").should_be_called()
|
||||
spatial.run_spatial_assign_container(container="to_container", element_obj="new_obj").should_be_called()
|
||||
|
||||
spatial.disable_editing("obj").should_be_called()
|
||||
|
||||
@@ -114,11 +105,9 @@ class TestCopyToContainer:
|
||||
|
||||
class TestSelectContainer:
|
||||
def test_run(self, ifc, spatial):
|
||||
ifc.get_entity("obj").should_be_called().will_return("element")
|
||||
spatial.get_container("element").should_be_called().will_return("container")
|
||||
ifc.get_object("container").should_be_called().will_return("container_obj")
|
||||
spatial.set_active_object("container_obj").should_be_called()
|
||||
subject.select_container(ifc, spatial, obj="obj")
|
||||
subject.select_container(ifc, spatial, container="container")
|
||||
|
||||
|
||||
class TestSelectSimilarContainer:
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import blenderbim.core.type as subject
|
||||
from test.core.bootstrap import ifc, type
|
||||
from test.core.bootstrap import ifc, type, geometry
|
||||
|
||||
|
||||
class TestAssignType:
|
||||
@@ -42,11 +42,16 @@ class TestAssignType:
|
||||
|
||||
|
||||
class TestPurgeUnusedTypes:
|
||||
def test_run(self, ifc, type):
|
||||
def test_purge_types_obj_found(self, ifc, type, geometry):
|
||||
type.get_model_types().should_be_called().will_return(["element_type"])
|
||||
type.get_type_occurrences("element_type").should_be_called().will_return([])
|
||||
ifc.run("root.remove_product", product="element_type").should_be_called()
|
||||
ifc.get_object("element_type").should_be_called().will_return("obj")
|
||||
ifc.unlink(element="element_type").should_be_called()
|
||||
type.remove_object("obj").should_be_called()
|
||||
subject.purge_unused_types(ifc, type)
|
||||
geometry.delete_ifc_object("obj").should_be_called()
|
||||
subject.purge_unused_types(ifc, type, geometry)
|
||||
|
||||
def test_purge_types_obj_not_found(self, ifc, type, geometry):
|
||||
type.get_model_types().should_be_called().will_return(["element_type"])
|
||||
type.get_type_occurrences("element_type").should_be_called().will_return([])
|
||||
ifc.get_object("element_type").should_be_called().will_return(None)
|
||||
ifc.run("root.remove_product", product="element_type").should_be_called()
|
||||
subject.purge_unused_types(ifc, type, geometry)
|
||||
|
||||
@@ -40,7 +40,7 @@ class TestCanContain(NewFile):
|
||||
element = ifc.createIfcWall()
|
||||
element_obj = bpy.data.objects.new("Object", None)
|
||||
tool.Ifc.link(element, element_obj)
|
||||
assert subject.can_contain(structure_obj, element_obj) is True
|
||||
assert subject.can_contain(structure, element_obj) is True
|
||||
|
||||
def test_a_spatial_structure_element_can_contain_an_element_ifc2x3(self):
|
||||
ifc = ifcopenshell.file(schema="IFC2X3")
|
||||
@@ -51,7 +51,7 @@ class TestCanContain(NewFile):
|
||||
element = ifc.createIfcWall()
|
||||
element_obj = bpy.data.objects.new("Object", None)
|
||||
tool.Ifc.link(element, element_obj)
|
||||
assert subject.can_contain(structure_obj, element_obj) is True
|
||||
assert subject.can_contain(structure, element_obj) is True
|
||||
|
||||
def test_a_spatial_zone_element_cannot_contain_an_element(self):
|
||||
ifc = ifcopenshell.file()
|
||||
@@ -62,7 +62,7 @@ class TestCanContain(NewFile):
|
||||
element = ifc.createIfcWall()
|
||||
element_obj = bpy.data.objects.new("Object", None)
|
||||
tool.Ifc.link(element, element_obj)
|
||||
assert subject.can_contain(structure_obj, element_obj) is False
|
||||
assert subject.can_contain(structure, element_obj) is False
|
||||
|
||||
def test_unlinked_elements_cannot_contain_anything(self):
|
||||
structure_obj = bpy.data.objects.new("Object", None)
|
||||
@@ -78,7 +78,7 @@ class TestCanContain(NewFile):
|
||||
element = ifc.createIfcWall()
|
||||
element_obj = bpy.data.objects.new("Object", None)
|
||||
tool.Ifc.link(element, element_obj)
|
||||
assert subject.can_contain(structure_obj, element_obj) is False
|
||||
assert subject.can_contain(structure, element_obj) is False
|
||||
|
||||
def test_a_non_element_cannot_be_contained(self):
|
||||
ifc = ifcopenshell.file()
|
||||
@@ -89,7 +89,7 @@ class TestCanContain(NewFile):
|
||||
element = ifc.createIfcTask()
|
||||
element_obj = bpy.data.objects.new("Object", None)
|
||||
tool.Ifc.link(element, element_obj)
|
||||
assert subject.can_contain(structure_obj, element_obj) is False
|
||||
assert subject.can_contain(structure, element_obj) is False
|
||||
|
||||
def test_other_non_elements_that_have_a_contained_in_structure_attribute_can_be_contained(self):
|
||||
ifc = ifcopenshell.file()
|
||||
@@ -100,7 +100,7 @@ class TestCanContain(NewFile):
|
||||
element = ifc.createIfcGrid()
|
||||
element_obj = bpy.data.objects.new("Object", None)
|
||||
tool.Ifc.link(element, element_obj)
|
||||
assert subject.can_contain(structure_obj, element_obj) is True
|
||||
assert subject.can_contain(structure, element_obj) is True
|
||||
|
||||
|
||||
class TestCanReference(NewFile):
|
||||
@@ -181,46 +181,6 @@ class TestGetRelativeObjectMatrix(NewFile):
|
||||
assert subject.get_relative_object_matrix(obj, relative_obj)[0][3] == -1
|
||||
|
||||
|
||||
class TestImportContainers(NewFile):
|
||||
def test_run(self):
|
||||
bpy.ops.bim.create_project()
|
||||
subject.import_containers()
|
||||
props = bpy.context.scene.BIMSpatialProperties
|
||||
assert len(props.containers) == 1
|
||||
assert props.containers[0].name == "My Site"
|
||||
assert props.containers[0].long_name == ""
|
||||
assert props.containers[0].has_decomposition is True
|
||||
assert props.containers[0].ifc_definition_id == tool.Ifc.get().by_type("IfcSite")[0].id()
|
||||
assert props.active_container_id == tool.Ifc.get().by_type("IfcProject")[0].id()
|
||||
|
||||
def test_importing_with_a_specified_parent(self):
|
||||
bpy.ops.bim.create_project()
|
||||
site = tool.Ifc.get().by_type("IfcSite")[0]
|
||||
subject.import_containers(site)
|
||||
props = bpy.context.scene.BIMSpatialProperties
|
||||
assert len(props.containers) == 1
|
||||
assert props.containers[0].name == "My Building"
|
||||
assert props.containers[0].long_name == ""
|
||||
assert props.containers[0].has_decomposition is True
|
||||
assert props.containers[0].ifc_definition_id == tool.Ifc.get().by_type("IfcBuilding")[0].id()
|
||||
assert props.active_container_id == site.id()
|
||||
|
||||
def test_importing_sorted_by_z_placement(self):
|
||||
bpy.ops.bim.create_project()
|
||||
building = tool.Ifc.get().by_type("IfcBuilding")[0]
|
||||
storey1 = tool.Ifc.get().by_type("IfcBuildingStorey")[0]
|
||||
storey1.Name = "Higher"
|
||||
bpy.ops.bim.copy_class(obj=tool.Ifc.get_object(storey1).name)
|
||||
storey2 = tool.Ifc.get().by_type("IfcBuildingStorey")[1]
|
||||
storey2.Name = "Lower"
|
||||
storey2.ObjectPlacement.RelativePlacement.Location.Coordinates = (0.0, 0.0, -100.0)
|
||||
subject.import_containers(building)
|
||||
props = bpy.context.scene.BIMSpatialProperties
|
||||
assert len(props.containers) == 2
|
||||
assert props.containers[0].name == "Lower"
|
||||
assert props.containers[1].name == "Higher"
|
||||
|
||||
|
||||
class TestRunRootCopyClass(NewFile):
|
||||
def test_nothing(self):
|
||||
pass
|
||||
|
||||
@@ -168,13 +168,6 @@ class TestHasMaterialUsage(NewFile):
|
||||
assert subject.has_material_usage(element) is True
|
||||
|
||||
|
||||
class TestRemoveObject(NewFile):
|
||||
def test_run(self):
|
||||
obj = bpy.data.objects.new("Object", None)
|
||||
subject.remove_object(obj)
|
||||
assert not bpy.data.objects.get("Object")
|
||||
|
||||
|
||||
class TestRunGeometryAddRepresentation(NewFile):
|
||||
def test_nothing(self):
|
||||
pass
|
||||
|
||||
Reference in New Issue
Block a user