mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-13 02:47:48 +00:00
Merge branch 'v0.8.0' of https://github.com/IfcOpenShell/IfcOpenShell into v0.8.0
This commit is contained in:
@@ -47,6 +47,7 @@ jobs:
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install xmlschema xsdata numpy lxml pytest isodate lark networkx tabulate python-dateutil shapely
|
||||
pip install mathutils
|
||||
pip install src/bcf --no-deps
|
||||
pip install https://github.com/Andrej730/aud/archive/refs/heads/master-reduced-size.zip
|
||||
|
||||
@@ -125,4 +126,9 @@ jobs:
|
||||
python tests.py
|
||||
cd ../src/ifcopenshell-python
|
||||
mv ifcopenshell ifcopenshell-local # Force testing on installed module
|
||||
make test-safe
|
||||
make test
|
||||
cd ../bcf && make test
|
||||
pip install requests
|
||||
cd ../bsdd && make test
|
||||
cd ../ifcpatch && make test
|
||||
cd ../ifctester && make test
|
||||
|
||||
+29
-2
@@ -1,7 +1,34 @@
|
||||
[tool.black]
|
||||
line-length = 120
|
||||
include = 'src/(bcf|bcfserver|blenderbim|bsdd|foundationserver|ifc2ca|ifc4d|ifc5d|ifcbimtester|ifcblender|ifccityjson|ifcclash|ifccsv|ifcdiff|ifcfm|ifcpatch|ifctester|ifcopenshell-python|ifcsverchok|opencdeserver)/.*.py$'
|
||||
extend-exclude = '(src/ifcopenshell-python/ifcopenshell/express/*|src/ifc2ca/templates/*)'
|
||||
include = '''
|
||||
src/(
|
||||
bcf
|
||||
|bcfserver
|
||||
|blenderbim
|
||||
|bsdd
|
||||
|foundationserver
|
||||
|ifc2ca
|
||||
|ifc4d
|
||||
|ifc5d
|
||||
|ifcbimtester
|
||||
|ifcblender
|
||||
|ifccityjson
|
||||
|ifcclash
|
||||
|ifccsv
|
||||
|ifcdiff
|
||||
|ifcfm
|
||||
|ifcpatch
|
||||
|ifctester
|
||||
|ifcopenshell-python
|
||||
|ifcsverchok
|
||||
|opencdeserver
|
||||
)/.*.py$
|
||||
'''
|
||||
extend-exclude = '''
|
||||
src/ifcopenshell-python/ifcopenshell/express/*
|
||||
|src/ifcopenshell-python/ifcopenshell/mvd/*
|
||||
|src/ifc2ca/templates/*
|
||||
'''
|
||||
|
||||
[tool.pyright]
|
||||
reportInvalidTypeForm = false
|
||||
|
||||
@@ -33,6 +33,10 @@ models:
|
||||
cd src && xsdata generate -p bcf.v2.model --unnest-classes --kw-only --slots -ds Google bcf/v2/xsd
|
||||
cd src && xsdata generate -p bcf.v3.model --unnest-classes --kw-only --slots -ds Google bcf/v3/xsd
|
||||
|
||||
.PHONY: test
|
||||
test:
|
||||
pytest -p no:pytest-blender tests
|
||||
|
||||
# .PHONY
|
||||
# api:
|
||||
# openapi-python-client generate --url https://api.swaggerhub.com/apis/buildingSMART/BCF/3.0
|
||||
|
||||
@@ -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
|
||||
|
||||
+3
-1
@@ -22,4 +22,6 @@ include ../common.mk
|
||||
|
||||
.PHONY: test
|
||||
test:
|
||||
pytest -p no:pytest-blender test
|
||||
# Using python -m pytest vs just using pytest is important here.
|
||||
# As just using pytest does not add current directory to PATH.
|
||||
python -m pytest -p no:pytest-blender tests
|
||||
|
||||
@@ -21,10 +21,12 @@ import json
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.api.pset
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.unit
|
||||
import ifcopenshell.util.selector
|
||||
import multiprocessing
|
||||
from collections import namedtuple
|
||||
from typing import Any
|
||||
|
||||
|
||||
Function = namedtuple("Function", ["measure", "name", "description"])
|
||||
@@ -47,7 +49,7 @@ def quantify(ifc_file: ifcopenshell.file, elements: set[ifcopenshell.entity_inst
|
||||
return results
|
||||
|
||||
|
||||
def edit_qtos(ifc_file, results) -> None:
|
||||
def edit_qtos(ifc_file: ifcopenshell.file, results: dict[ifcopenshell.entity_instance, Any]) -> None:
|
||||
for element, qtos in results.items():
|
||||
for name, quantities in qtos.items():
|
||||
qto = ifcopenshell.util.element.get_pset(element, name, should_inherit=False)
|
||||
@@ -59,7 +61,7 @@ def edit_qtos(ifc_file, results) -> None:
|
||||
|
||||
|
||||
class SI2ProjectUnitConverter:
|
||||
def __init__(self, ifc_file):
|
||||
def __init__(self, ifc_file: ifcopenshell.file):
|
||||
self.project_units = {
|
||||
"IfcAreaMeasure": ifcopenshell.util.unit.get_project_unit(ifc_file, "AREAUNIT"),
|
||||
"IfcLengthMeasure": ifcopenshell.util.unit.get_project_unit(ifc_file, "LENGTHUNIT"),
|
||||
|
||||
@@ -391,6 +391,31 @@ bool IfcGeom::OpenCascadeKernel::convert_openings(const IfcUtil::IfcBaseEntity*
|
||||
return true;
|
||||
}
|
||||
|
||||
#include <BRepPrimAPI_MakeRevol.hxx>
|
||||
|
||||
bool IfcGeom::OpenCascadeKernel::convert_impl(const taxonomy::revolve::ptr r, IfcGeom::ConversionResults& results) {
|
||||
|
||||
|
||||
gp_Ax1 ax(
|
||||
convert_xyz<gp_Pnt>(*r->axis_origin),
|
||||
convert_xyz<gp_Dir>(*r->direction));
|
||||
|
||||
TopoDS_Shape face;
|
||||
if (!convert(taxonomy::cast<taxonomy::face>(r->basis), face)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
TopoDS_Shape shape = BRepPrimAPI_MakeRevol(face, ax);
|
||||
|
||||
results.emplace_back(ConversionResult(
|
||||
r->instance->data().id(),
|
||||
r->matrix,
|
||||
new OpenCascadeShape(shape),
|
||||
r->surface_style
|
||||
));
|
||||
return true;
|
||||
}
|
||||
|
||||
// IfcSchema::IfcRelVoidsElement::list::ptr IfcGeom::Kernel::find_openings(IfcSchema::IfcProduct* product) {
|
||||
// std::vector<IfcSchema::IfcRelVoidsElement*> rs;
|
||||
//
|
||||
|
||||
@@ -128,7 +128,7 @@ public:
|
||||
{}
|
||||
|
||||
bool convert(const ifcopenshell::geometry::taxonomy::extrusion::ptr, TopoDS_Shape&);
|
||||
bool convert(const ifcopenshell::geometry::taxonomy::face::ptr, TopoDS_Shape&);
|
||||
bool convert(const ifcopenshell::geometry::taxonomy::face::ptr, TopoDS_Shape&, bool reversed_surface = false);
|
||||
bool convert(const ifcopenshell::geometry::taxonomy::loop::ptr, TopoDS_Wire&);
|
||||
bool convert(const ifcopenshell::geometry::taxonomy::matrix4::ptr, gp_GTrsf&);
|
||||
bool convert(const ifcopenshell::geometry::taxonomy::shell::ptr, TopoDS_Shape&);
|
||||
@@ -143,6 +143,7 @@ public:
|
||||
virtual bool convert_impl(const ifcopenshell::geometry::taxonomy::solid::ptr, IfcGeom::ConversionResults&);
|
||||
virtual bool convert_impl(const ifcopenshell::geometry::taxonomy::shell::ptr, IfcGeom::ConversionResults&);
|
||||
virtual bool convert_impl(const ifcopenshell::geometry::taxonomy::extrusion::ptr, IfcGeom::ConversionResults&);
|
||||
virtual bool convert_impl(const ifcopenshell::geometry::taxonomy::revolve::ptr, IfcGeom::ConversionResults&);
|
||||
virtual bool convert_impl(const ifcopenshell::geometry::taxonomy::boolean_result::ptr, IfcGeom::ConversionResults&);
|
||||
virtual bool convert_impl(const ifcopenshell::geometry::taxonomy::loft::ptr, IfcGeom::ConversionResults&);
|
||||
virtual bool convert_impl(const ifcopenshell::geometry::taxonomy::sweep_along_curve::ptr, IfcGeom::ConversionResults&);
|
||||
|
||||
@@ -229,7 +229,7 @@ Handle(Geom_Surface) OpenCascadeKernel::convert_surface(const taxonomy::ptr surf
|
||||
}
|
||||
}
|
||||
|
||||
bool OpenCascadeKernel::convert(const taxonomy::face::ptr face, TopoDS_Shape& result) {
|
||||
bool OpenCascadeKernel::convert(const taxonomy::face::ptr face, TopoDS_Shape& result, bool reversed_surface) {
|
||||
#ifdef IFOPSH_DEBUG
|
||||
std::ostringstream oss;
|
||||
face->print(oss);
|
||||
@@ -382,7 +382,11 @@ bool OpenCascadeKernel::convert(const taxonomy::face::ptr face, TopoDS_Shape& re
|
||||
}
|
||||
}
|
||||
} else if (!fd.all_outer()) {
|
||||
BRepBuilderAPI_MakeFace mf(fd.surface(), fd.outer_wire());
|
||||
auto surf = fd.surface();
|
||||
if (reversed_surface) {
|
||||
surf = surf->UReversed();
|
||||
}
|
||||
BRepBuilderAPI_MakeFace mf(surf, fd.outer_wire());
|
||||
|
||||
if (mf.IsDone()) {
|
||||
// Is this necessary
|
||||
@@ -434,10 +438,20 @@ bool OpenCascadeKernel::convert(const taxonomy::face::ptr face, TopoDS_Shape& re
|
||||
for (; jt.More(); jt.Next()) {
|
||||
Message_ListIteratorOfListOfMsg kt(jt.Value());
|
||||
for (; kt.More(); kt.Next()) {
|
||||
char* c = new char[kt.Value().Value().LengthOfCString() + 1];
|
||||
kt.Value().Value().ToUTF8CString(c);
|
||||
Logger::Notice(c, face->instance);
|
||||
char* c = new char[kt.Value().Original().LengthOfCString() + 1];
|
||||
kt.Value().Original().ToUTF8CString(c);
|
||||
std::string message = c;
|
||||
delete[] c;
|
||||
#if OCC_VERSION_MAJOR==7 && OCC_VERSION_MINOR == 7
|
||||
if (!reversed_surface && !fd.surface().IsNull() && fd.surface()->IsUPeriodic() && message == "Unknown message invoked with the keyword FixAdvFace.FixOrientation.MSG0") {
|
||||
Logger::Notice("Detected reversed wire, reattempting with reversed basis surface");
|
||||
TopoDS_Face reversed_result;
|
||||
convert(face, reversed_result, true);
|
||||
result = reversed_result;
|
||||
return true;
|
||||
} else
|
||||
#endif
|
||||
Logger::Warning(message, face->instance);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ possible at compile-time when using C++ and at run-time when using Python.
|
||||
:caption: Contents:
|
||||
|
||||
ifcopenshell/installation
|
||||
ifcopenshell/getting_started
|
||||
ifcopenshell/geometry_iterator
|
||||
ifcopenshell/geometry_settings
|
||||
ifcopenshell/boolean_process
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
Getting Started with IFC parsing
|
||||
================================
|
||||
|
||||
The basis of all parsing and getting information from the IFC starts with obtaining a ``IfcParse::IfcFile`` object and validating that it is good for use.
|
||||
|
||||
.. code-block:: c++
|
||||
|
||||
std::string input_file_path = "/path/to/my/model.ifc";
|
||||
IfcParse::IfcFile file(input_file_path);
|
||||
if (!file.good()) {
|
||||
std::cerr << "Unable to parse .ifc file" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
Schema-agnostic parsing of IFCs
|
||||
-------------------------------
|
||||
|
||||
It is advisable to design your programme in a schema-agnostic fashion to be able to process all schema versions of input IFCs.
|
||||
While different options are presented `here <http://blog.ifcopenshell.org/2019/12/v060.html>`__, the most comfortable approach for modern C++ developers
|
||||
would be with the use of templates as shown below.
|
||||
|
||||
.. code-block:: c++
|
||||
|
||||
// necessary includes
|
||||
|
||||
// For BOOST_PP_SEQ_FOR_EACH and BOOST_PP_STRINGIZE preprocessor macro
|
||||
#include <boost/preprocessor/seq/for_each.hpp>
|
||||
// Include all possible schema types that could be parsed
|
||||
#include "ifcparse/Ifc2x3.h"
|
||||
#include "ifcparse/Ifc4.h"
|
||||
#include "ifcparse/Ifc4x3_add2.h"
|
||||
|
||||
#define IFC_SCHEMA_SEQ (4x3_rc2)(4)(2x3) // TODO: Enumerate through all IFC schemas you want to be able to process
|
||||
#define EXPAND_AND_CONCATENATE(elem) Ifc##elem
|
||||
#define PROCESS_FOR_SCHEMA(r, data, elem) if (schema_version == BOOST_PP_STRINGIZE(elem)) { parseIfc<EXPAND_AND_CONCATENATE(elem)>(file); } else
|
||||
|
||||
template<typename Schema>
|
||||
void parseIfc(IfcParse::IfcFile &file) {
|
||||
const typename Schema::IfcProduct::list::ptr elements = file.instances_by_type<typename Schema::IfcProduct>();
|
||||
for (typename Schema::IfcProduct::list::it it = elements->begin();
|
||||
it != elements->end(); ++it) {
|
||||
Schema::IfcProduct *ifcProduct = *it;
|
||||
// TODO: Do something with ifcProduct
|
||||
}
|
||||
}
|
||||
|
||||
void process(const std::string &schema_version, IfcParse::IfcFile &file) {
|
||||
// Syntactic sugar for iterating through all IFC schemas and passing them to main processing method
|
||||
BOOST_PP_SEQ_FOR_EACH(PROCESS_FOR_SCHEMA, ,IFC_SCHEMA_SEQ)
|
||||
{ // The final else to catch unhandled schema version
|
||||
throw std::invalid_argument("IFC Schema " + schema_version + " not supported");
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
// file of IfcParse::IfcFile previously defined
|
||||
auto schema_version = file.schema()->name();
|
||||
schema_version = schema_version.substr(3);
|
||||
std::transform(schema_version.begin(), schema_version.end(), schema_version.begin(), [](unsigned char c) { return std::tolower(c); });
|
||||
process(schema_version, file);
|
||||
// TODO: Handle more cases of IFC schema versions here
|
||||
}
|
||||
|
||||
Reading out attributes of an IfcProduct
|
||||
---------------------------------------
|
||||
|
||||
The properties of an ``IfcProduct``, and by extension any derived class, can be read as by calling the method of the same name, e.g. ``GlobalId()``, ``Name()``.
|
||||
Note that optional properties like name, long name, or description, among others, are wrapped with a ``boost::optional`` (`documentation <https://www.boost.org/doc/libs/1_84_0/libs/optional/doc/html/index.html>`__) container object. Except for pointer types such as IfcRoot::OwnerHistory, here the developer is still responsible for performing comparison to `nullptr` before using the value.
|
||||
|
||||
Navigating relationships in IFC
|
||||
-------------------------------
|
||||
|
||||
While it is natural to look at the IFC format as a representation of a physical building model where physical objects are related to each other in a tree structure (e.g. site > building > storey > slab),
|
||||
the IFC format allows for the representation of information in a graph-like manner, joining physical objects to meta-information through relationships.
|
||||
A detailed explanation of relationships in IFC is provided in this `blog post <https://constructingdata.wordpress.com/2018/04/09/ifc-for-the-layman-part-3-relationships/>`__.
|
||||
|
||||
The following function shows how property sets can be extracted from a given ``IfcObject``.
|
||||
|
||||
.. code-block:: c++
|
||||
|
||||
template<typename Schema>
|
||||
void extractPropertySets(const typename Schema::IfcObject &obj,
|
||||
typename std::vector<typename Schema::IfcPropertySet *> &property_sets) {
|
||||
const auto &is_defined_by = obj.IsDefinedBy();
|
||||
if (is_defined_by == nullptr)
|
||||
return;
|
||||
std::vector<typename Schema::IfcRelDefines *> rels(is_defined_by->size());
|
||||
typename std::vector<typename Schema::IfcRelDefines *>::iterator it = std::copy_if(
|
||||
is_defined_by->begin(), is_defined_by->end(), rels.begin(),
|
||||
[](const typename Schema::IfcRelDefines *x) {
|
||||
if (x == nullptr) return false;
|
||||
const typename Schema::IfcRelDefinesByProperties *defines_by_properties =
|
||||
x->template as<typename Schema::IfcRelDefinesByProperties>();
|
||||
if (defines_by_properties == nullptr) return false;
|
||||
const auto *relating_property_definition =
|
||||
defines_by_properties->RelatingPropertyDefinition();
|
||||
if (relating_property_definition == nullptr) return false;
|
||||
return relating_property_definition->template as<typename Schema::IfcPropertySet>() !=
|
||||
nullptr;
|
||||
});
|
||||
rels.resize(std::distance(rels.begin(), it));
|
||||
property_sets.resize(rels.size());
|
||||
std::transform(rels.begin(), rels.end(), property_sets.begin(),
|
||||
[](const typename Schema::IfcRelDefines *x) {
|
||||
const typename Schema::IfcRelDefinesByProperties *defines_by_properties = x->template as<typename Schema::IfcRelDefinesByProperties>();
|
||||
return defines_by_properties->RelatingPropertyDefinition()->template as<typename Schema::IfcPropertySet>();
|
||||
});
|
||||
}
|
||||
|
||||
Defensive programming with IfcOpenshell
|
||||
---------------------------------------
|
||||
|
||||
The need for (down-)casting object pointers when accessing various properties in an IFC entity is evident from the previous code sample as the methods and properties usually
|
||||
return the abstract class of the entity. It is hence important to check for ``nullptr`` when performing such casts.
|
||||
The existence of certain fields and properties should also be checked. Also note that IfcOpenShell has as of now not been tested explicitly against malicious inputs. Schema validation (the correctness of attribute types and conformance to schema where rules) can currently only be assessed in Python (using `ifcopenshell.validate --rules`).
|
||||
@@ -17,14 +17,15 @@
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ifcopenshell
|
||||
from typing import Optional, Literal
|
||||
import ifcopenshell.util.representation
|
||||
from typing import Optional
|
||||
|
||||
|
||||
def add_context(
|
||||
file: ifcopenshell.file,
|
||||
context_type: Optional[Literal["Model", "Plan"]] = None,
|
||||
context_identifier: Optional[str] = None,
|
||||
target_view: Optional[str] = None,
|
||||
context_type: Optional[ifcopenshell.util.representation.CONTEXT_TYPE] = None,
|
||||
context_identifier: Optional[ifcopenshell.util.representation.REPRESENTATION_IDENTIFIER] = None,
|
||||
target_view: Optional[ifcopenshell.util.representation.TARGET_VIEW] = None,
|
||||
parent: Optional[ifcopenshell.entity_instance] = None,
|
||||
) -> ifcopenshell.entity_instance:
|
||||
"""Adds a new geometric representation context
|
||||
@@ -106,7 +107,6 @@ def add_context(
|
||||
the common target views above or consult the IFC documentation
|
||||
(under the IfcShapeRepresentation page) for more details. Optional
|
||||
for contexts, but mandatory for subcontexts.
|
||||
:type target_view: str, optional
|
||||
:param parent: the parent context. Must be left as None (the default)
|
||||
for contexts, and only set for subcontexts. Note that there are only
|
||||
contexts and subcontexts, a subcontext cannot have any children.
|
||||
|
||||
@@ -382,10 +382,10 @@ class file:
|
||||
for (attr_index, _), attr_name in zip(kwargs_attrs, kwargs):
|
||||
if attr_index == 0xFFFFFFFF:
|
||||
invalid_attrs.append(attr_name)
|
||||
raise ValueError(
|
||||
"entity instance of type '%s' doesn't have the following attributes: %s."
|
||||
% (e.is_a(True), ", ".join(invalid_attrs))
|
||||
)
|
||||
raise ValueError(
|
||||
"entity instance of type '%s' doesn't have the following attributes: %s."
|
||||
% (e.is_a(True), ", ".join(invalid_attrs))
|
||||
)
|
||||
|
||||
# Restore transaction status
|
||||
if attrs:
|
||||
|
||||
@@ -41,6 +41,7 @@ except ImportError:
|
||||
|
||||
class shape_tuple(NamedTuple):
|
||||
"""A tuple containing IfcOpenShell serialized element/shape and pythonOCC shape."""
|
||||
|
||||
data: Union[ifcopenshell_wrapper.SerializedElement, ifcopenshell_wrapper.Serialization]
|
||||
geometry: TopoDS.TopoDS_Shape
|
||||
styles: tuple[tuple[float, float, float, float], ...]
|
||||
|
||||
@@ -26,6 +26,7 @@ import typing
|
||||
import inspect
|
||||
import collections
|
||||
import importlib
|
||||
import importlib.util
|
||||
from typing import Union
|
||||
|
||||
|
||||
@@ -177,6 +178,10 @@ def _extract_docs(cls, method_name, boilerplate_args):
|
||||
param_name = line.split(":")[1].strip().replace("param ", "")
|
||||
if param_name in inputs:
|
||||
inputs[param_name]["description"] = line.split(":")[2].strip()
|
||||
elif line.startswith(":filter_glob"):
|
||||
param_name = line.split(":")[1].strip().replace("filter_glob ", "")
|
||||
if param_name in inputs:
|
||||
inputs[param_name]["filter_glob"] = line.split(":")[2].strip()
|
||||
elif i == 2:
|
||||
description += line
|
||||
elif i > 2:
|
||||
|
||||
@@ -17,12 +17,15 @@
|
||||
# along with IfcPatch. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.util.pset
|
||||
import ifcopenshell.util.element
|
||||
from logging import Logger
|
||||
from typing import Union
|
||||
|
||||
|
||||
class Patcher:
|
||||
def __init__(self, src, file, logger, property_name=None, quantity_name=None):
|
||||
def __init__(self, src: str, file: ifcopenshell.file, logger: Logger, property_name: str, quantity_name: str):
|
||||
"""Converts a property to a standardised quantity
|
||||
|
||||
IFC can store arbitrary key value metadata associated with a elements
|
||||
@@ -40,14 +43,12 @@ class Patcher:
|
||||
|
||||
:param property_name: The name of the property to convert into a
|
||||
quantity. The name of the property set is not considered.
|
||||
:type property_name: str
|
||||
:param quantity_name: The name of the quantity that this property should
|
||||
be stored in. This should be a standard name that is one of the
|
||||
quantity names of a buildingSMART quantity template. For example, it
|
||||
may be "NetSideArea" for walls, which exists in
|
||||
Qto_WallBaseQuantities. The quantity set name will be based on the
|
||||
standard buildingSMART quantity template.
|
||||
:type quantity_name: str
|
||||
|
||||
Example:
|
||||
|
||||
@@ -63,7 +64,7 @@ class Patcher:
|
||||
self.source_property_name = property_name
|
||||
self.destination_quantity_name = quantity_name
|
||||
|
||||
def patch(self):
|
||||
def patch(self) -> None:
|
||||
self.qto_template_cache = {}
|
||||
self.psetqto = ifcopenshell.util.pset.get_template("IFC4")
|
||||
|
||||
@@ -76,7 +77,9 @@ class Patcher:
|
||||
[r.RelatingPropertyDefinition for r in product.IsDefinedBy if r.is_a("IfcRelDefinesByProperties")],
|
||||
)
|
||||
|
||||
def process_product(self, product, definitions):
|
||||
def process_product(
|
||||
self, product: ifcopenshell.entity_instance, definitions: list[ifcopenshell.entity_instance]
|
||||
) -> None:
|
||||
value = None
|
||||
has_quantity = False
|
||||
qtos = {}
|
||||
@@ -98,12 +101,12 @@ class Patcher:
|
||||
"pset.edit_qto", self.file, qto=qto, Properties={self.destination_quantity_name: value}
|
||||
)
|
||||
|
||||
def get_qto_name(self, ifc_class):
|
||||
def get_qto_name(self, ifc_class: str) -> Union[str, None]:
|
||||
for template in self.get_qto_templates(ifc_class):
|
||||
if self.destination_quantity_name in [t.Name for t in template.HasPropertyTemplates]:
|
||||
return template.Name
|
||||
|
||||
def get_qto_templates(self, ifc_class):
|
||||
def get_qto_templates(self, ifc_class: str) -> list[ifcopenshell.entity_instance]:
|
||||
if ifc_class not in self.qto_template_cache:
|
||||
self.qto_template_cache[ifc_class] = self.psetqto.get_applicable(ifc_class, qto_only=True)
|
||||
return self.qto_template_cache[ifc_class]
|
||||
|
||||
@@ -40,6 +40,8 @@ class Patcher:
|
||||
:param filepath: The filepath of the second IFC model to merge into the
|
||||
first. The first model is already specified as the input to
|
||||
IfcPatch.
|
||||
:type filepath: Union[str, ifcopenshell.file]
|
||||
:filter_glob filepath: *.ifc;*.ifczip;*.ifcxml
|
||||
|
||||
Example:
|
||||
|
||||
@@ -53,11 +55,15 @@ class Patcher:
|
||||
self.filepath = filepath
|
||||
|
||||
def patch(self):
|
||||
if isinstance(self.filepath, ifcopenshell.file):
|
||||
other = self.filepath
|
||||
else:
|
||||
other = ifcopenshell.open(self.filepath)
|
||||
for filepath in self.filepath:
|
||||
if isinstance(filepath, ifcopenshell.file):
|
||||
other = filepath
|
||||
else:
|
||||
other = ifcopenshell.open(filepath)
|
||||
|
||||
self.merge(other)
|
||||
|
||||
def merge(self, other):
|
||||
if (main_unit := self.get_unit_name(self.file)) != self.get_unit_name(other):
|
||||
other = ifcopenshell.util.unit.convert_file_length_units(other, main_unit)
|
||||
|
||||
|
||||
@@ -20,10 +20,23 @@ import math
|
||||
import numpy as np
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.placement
|
||||
import typing
|
||||
|
||||
|
||||
class Patcher:
|
||||
def __init__(self, src, file, logger, x=None, y=None, z=None, should_rotate_first=True, ax=None, ay=None, az=None):
|
||||
def __init__(
|
||||
self,
|
||||
src,
|
||||
file,
|
||||
logger,
|
||||
x: typing.Union[str, float] = "0",
|
||||
y: typing.Union[str, float] = "0",
|
||||
z: typing.Union[str, float] = "0",
|
||||
should_rotate_first: bool = True,
|
||||
ax: typing.Optional[typing.Union[str, float]] = "",
|
||||
ay: typing.Optional[typing.Union[str, float]] = "",
|
||||
az: typing.Optional[typing.Union[str, float]] = "",
|
||||
):
|
||||
"""Offset and rotate all object placements in a model
|
||||
|
||||
Every physical object in an IFC model has an object placement, a
|
||||
@@ -41,11 +54,11 @@ class Patcher:
|
||||
entire IFC model.
|
||||
|
||||
:param x: The X coordinate to offset by in project length units.
|
||||
:type x: float
|
||||
:type x: typing.Union[str, float]
|
||||
:param y: The Y coordinate to offset by in project length units.
|
||||
:type y: float
|
||||
:type y: typing.Union[str, float]
|
||||
:param z: The Z coordinate to offset by in project length units.
|
||||
:type z: float
|
||||
:type z: typing.Union[str, float]
|
||||
:param should_rotate_first: Whether or not to rotate first and then
|
||||
translate, or to first translate and rotate afterwards. Defaults to
|
||||
rotate first then translate.
|
||||
@@ -55,13 +68,13 @@ class Patcher:
|
||||
around the Z axis). If all angle parameters are specified, then it
|
||||
is treated as the angle to rotate around the X axis. Angles are in
|
||||
decimal degrees and positive is anticlockwise.
|
||||
:type ax: float,optional
|
||||
:type ax: typing.Union[str, float],optional
|
||||
:param ay: An optional angle to rotate by for 3D rotations along the Y
|
||||
axis. Angles are in decimal degrees and positive is anticlockwise.
|
||||
:type ay: float,optional
|
||||
:type ay: typing.Union[str, float],optional
|
||||
:param az: An optional angle to rotate by for 3D rotations along the Z
|
||||
axis. Angles are in decimal degrees and positive is anticlockwise.
|
||||
:type az: float,optional
|
||||
:type az: typing.Union[str, float],optional
|
||||
|
||||
Example:
|
||||
|
||||
@@ -79,13 +92,21 @@ class Patcher:
|
||||
self.src = src
|
||||
self.file = file
|
||||
self.logger = logger
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.z = z
|
||||
self.x = float(x)
|
||||
self.y = float(y)
|
||||
self.z = float(z)
|
||||
self.should_rotate_first = should_rotate_first
|
||||
self.ax = ax
|
||||
self.ay = ay
|
||||
self.az = az
|
||||
self.ax = None
|
||||
self.ay = None
|
||||
self.az = None
|
||||
|
||||
try:
|
||||
self.ax = float(ax)
|
||||
self.ay = float(ay)
|
||||
self.az = float(az)
|
||||
except Exception:
|
||||
print("At least one input angle is not specified.")
|
||||
|
||||
if self.ax is None:
|
||||
self.angle_type = None
|
||||
elif self.ay is None:
|
||||
|
||||
@@ -16,15 +16,17 @@
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcPatch. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import typing
|
||||
|
||||
|
||||
class Patcher:
|
||||
def __init__(self, src, file, logger, z=None):
|
||||
def __init__(self, src, file, logger, z: typing.Union[str, float] = "0"):
|
||||
"""Offset building storeys by a particular Z value
|
||||
|
||||
All objects placed relative to the storeys will also be shifted.
|
||||
|
||||
:param z: The Z value in project length units to offset storeys by.
|
||||
:type z: float
|
||||
:type z: typing.Union[str, float]
|
||||
|
||||
Example:
|
||||
|
||||
@@ -36,7 +38,7 @@ class Patcher:
|
||||
self.src = src
|
||||
self.file = file
|
||||
self.logger = logger
|
||||
self.z = z
|
||||
self.z = float(z)
|
||||
|
||||
def patch(self):
|
||||
project = self.file.by_type("IfcProject")[0]
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api.georeference
|
||||
from ifcpatch.recipes import OffsetObjectPlacements, SetWorldCoordinateSystem
|
||||
import typing
|
||||
|
||||
|
||||
class Patcher:
|
||||
@@ -28,14 +29,14 @@ class Patcher:
|
||||
file,
|
||||
logger,
|
||||
name: str = "EPSG:1234",
|
||||
x: float = 0,
|
||||
y: float = 0,
|
||||
z: float = 0,
|
||||
e: float = 0,
|
||||
n: float = 0,
|
||||
h: float = 0,
|
||||
gn_angle: float = 0,
|
||||
rotate_angle: float = 0,
|
||||
x: typing.Union[str, float] = "0",
|
||||
y: typing.Union[str, float] = "0",
|
||||
z: typing.Union[str, float] = "0",
|
||||
e: typing.Union[str, float] = "0",
|
||||
n: typing.Union[str, float] = "0",
|
||||
h: typing.Union[str, float] = "0",
|
||||
gn_angle: typing.Union[str, float] = "0",
|
||||
rotate_angle: typing.Union[str, float] = "0",
|
||||
):
|
||||
"""Sets a false origin with a map conversion in a model
|
||||
|
||||
@@ -62,14 +63,14 @@ class Patcher:
|
||||
self.file = file
|
||||
self.logger = logger
|
||||
self.name = name
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.z = z
|
||||
self.e = e
|
||||
self.n = n
|
||||
self.h = h
|
||||
self.gn_angle = gn_angle
|
||||
self.rotate_angle = rotate_angle
|
||||
self.x = float(x)
|
||||
self.y = float(y)
|
||||
self.z = float(z)
|
||||
self.e = float(e)
|
||||
self.n = float(n)
|
||||
self.h = float(h)
|
||||
self.gn_angle = float(gn_angle)
|
||||
self.rotate_angle = float(rotate_angle)
|
||||
|
||||
def patch(self):
|
||||
SetWorldCoordinateSystem.Patcher(self.src, self.file, self.logger, x=0, y=0, z=0, ax=0, ay=0, az=0).patch()
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user