This commit is contained in:
Chirag Singh
2024-07-24 12:15:08 +05:30
49 changed files with 1027 additions and 470 deletions
+1 -1
View File
@@ -32,7 +32,7 @@ jobs:
run: |
pip install build
cd src/bcf &&
make dist
make dist IS_STABLE=TRUE
- name: Publish a Python distribution to PyPI
uses: ortega2247/pypi-upload-action@master
with:
+1 -1
View File
@@ -6,7 +6,7 @@ on:
- '.github/workflows/ci-blenderbim-daily.yml'
- 'src/blenderbim/**'
- 'src/ifcopenshell-python/ifcopenshell/**'
- 'src/bcf/src/bcf/**'
- 'src/bcf/bcf/**'
- 'src/ifcclash/ifcclash/**'
- 'src/ifccobie/**'
- 'src/ifcdiff/**'
+14 -4
View File
@@ -1,5 +1,10 @@
name: ci-blenderbim
# Differences from ci-blenderbim-daily.yml:
# - make has IS_STABLE=TRUE
# - action is never triggered and executed only manually
# - doesn't add a current date to the release and tag
on:
workflow_dispatch:
@@ -48,15 +53,20 @@ jobs:
run: echo "version=$(cat VERSION)" >> $GITHUB_OUTPUT
- name: Compile
run: |
cp -r src/blenderbim src/blenderbim_${{ matrix.config.short_name }}_${{ matrix.pyver }} &&
cd src/blenderbim_${{ matrix.config.short_name }}_${{ matrix.pyver }} &&
cd src/blenderbim &&
make dist PLATFORM=${{ matrix.config.short_name }} PYVERSION=${{ matrix.pyver }} IS_STABLE=TRUE
- name: Find zip file name
id: find_zip
run: |
filepath=$(ls src/blenderbim/dist/blenderbim_*.zip)
echo "filepath=$filepath" >> $GITHUB_OUTPUT
echo "filename=$(basename $filepath)" >> $GITHUB_OUTPUT
- name: Upload zip file to release
uses: svenstaro/upload-release-action@v2
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: src/blenderbim_${{ matrix.config.short_name }}_${{ matrix.pyver }}/dist/blenderbim-${{steps.version.outputs.version}}-${{ matrix.pyver }}-${{ matrix.config.short_name }}.zip
asset_name: blenderbim-${{steps.version.outputs.version}}-${{ matrix.pyver }}-${{ matrix.config.short_name }}.zip
file: ${{ steps.find_zip.outputs.filepath }}
asset_name: ${{ steps.find_zip.outputs.filename }}
release_name: "blenderbim-${{steps.version.outputs.version}}"
tag: "blenderbim-${{steps.version.outputs.version}}"
overwrite: true
+1 -1
View File
@@ -26,7 +26,7 @@ jobs:
run: |
pip install build
cd src/bsdd &&
make dist
make dist IS_STABLE=TRUE
- name: Publish a Python distribution to PyPI
uses: ortega2247/pypi-upload-action@master
with:
+1 -1
View File
@@ -26,7 +26,7 @@ jobs:
run: |
pip install build
cd src/ifc4d &&
make dist
make dist IS_STABLE=TRUE
- name: Publish a Python distribution to PyPI
uses: ortega2247/pypi-upload-action@master
with:
+1 -1
View File
@@ -26,7 +26,7 @@ jobs:
run: |
pip install build
cd src/ifc5d &&
make dist
make dist IS_STABLE=TRUE
- name: Publish a Python distribution to PyPI
uses: ortega2247/pypi-upload-action@master
with:
+1 -1
View File
@@ -26,7 +26,7 @@ jobs:
run: |
pip install build
cd src/ifccityjson &&
make dist
make dist IS_STABLE=TRUE
- name: Publish a Python distribution to PyPI
uses: ortega2247/pypi-upload-action@master
with:
+1 -1
View File
@@ -26,7 +26,7 @@ jobs:
run: |
pip install build
cd src/ifcclash &&
make dist
make dist IS_STABLE=TRUE
- name: Publish a Python distribution to PyPI
uses: ortega2247/pypi-upload-action@master
with:
+1 -1
View File
@@ -26,7 +26,7 @@ jobs:
run: |
pip install build
cd src/ifccsv &&
make dist
make dist IS_STABLE=TRUE
- name: Publish a Python distribution to PyPI
uses: ortega2247/pypi-upload-action@master
with:
+1 -1
View File
@@ -26,7 +26,7 @@ jobs:
run: |
pip install build
cd src/ifcdiff &&
make dist
make dist IS_STABLE=TRUE
- name: Publish a Python distribution to PyPI
uses: ortega2247/pypi-upload-action@master
with:
+1 -1
View File
@@ -26,7 +26,7 @@ jobs:
run: |
pip install build
cd src/ifcfm &&
make dist
make dist IS_STABLE=TRUE
- name: Publish a Python distribution to PyPI
uses: ortega2247/pypi-upload-action@master
with:
@@ -57,7 +57,7 @@ jobs:
pip install build
cp -r src/ifcopenshell-python src/ifcopenshell_${{ matrix.config.short_name }}_${{ matrix.pyver }} &&
cd src/ifcopenshell_${{ matrix.config.short_name }}_${{ matrix.pyver }} &&
make dist PLATFORM=${{ matrix.config.short_name }} PYVERSION=${{ matrix.pyver }}
make dist PLATFORM=${{ matrix.config.short_name }} PYVERSION=${{ matrix.pyver }} IS_STABLE=TRUE
- name: Publish a Python distribution to PyPI
uses: ortega2247/pypi-upload-action@master
with:
+1 -1
View File
@@ -26,7 +26,7 @@ jobs:
run: |
pip install build
cd src/ifcpatch &&
make dist
make dist IS_STABLE=TRUE
- name: Publish a Python distribution to PyPI
uses: ortega2247/pypi-upload-action@master
with:
+1 -1
View File
@@ -26,7 +26,7 @@ jobs:
run: |
pip install build
cd src/ifctester &&
make dist
make dist IS_STABLE=TRUE
- name: Publish a Python distribution to PyPI
uses: ortega2247/pypi-upload-action@master
with:
+46 -13
View File
@@ -114,19 +114,18 @@ endif
mkdir -p build/wheels
# Provides IfcOpenShell Python functionality
cd ../ifcopenshell-python && make dist PLATFORM=$(PLATFORM)64 PYVERSION=$(PYVERSION) && cp dist/*.whl ../blenderbim/build/wheels/
cd ../bcf && make dist && cp dist/*.whl ../blenderbim/build/wheels/
cd ../ifcclash && make dist && cp dist/*.whl ../blenderbim/build/wheels/
cd ../ifcbimtester && make dist && cp dist/*.whl ../blenderbim/build/wheels/
cd ../ifctester && make dist && cp dist/*.whl ../blenderbim/build/wheels/
cd ../ifcfm && make dist && cp dist/*.whl ../blenderbim/build/wheels/
cd ../bsdd && make dist && cp dist/*.whl ../blenderbim/build/wheels/
cd ../ifcdiff && make dist && cp dist/*.whl ../blenderbim/build/wheels/
cd ../ifccsv && make dist && cp dist/*.whl ../blenderbim/build/wheels/
cd ../ifcpatch && make dist && cp dist/*.whl ../blenderbim/build/wheels/
cd ../ifc4d && make dist && cp dist/*.whl ../blenderbim/build/wheels/
cd ../ifc5d && make dist && cp dist/*.whl ../blenderbim/build/wheels/
cd ../ifccityjson && make dist && cp dist/*.whl ../blenderbim/build/wheels/
cd ../ifcopenshell-python && make dist PLATFORM=$(PLATFORM)64 PYVERSION=$(PYVERSION) && mv dist/*.whl ../blenderbim/build/wheels/
cd ../bcf && make dist && mv dist/*.whl ../blenderbim/build/wheels/
cd ../ifcclash && make dist && mv dist/*.whl ../blenderbim/build/wheels/
cd ../ifctester && make dist && mv dist/*.whl ../blenderbim/build/wheels/
cd ../ifcfm && make dist && mv dist/*.whl ../blenderbim/build/wheels/
cd ../bsdd && make dist && mv dist/*.whl ../blenderbim/build/wheels/
cd ../ifcdiff && make dist && mv dist/*.whl ../blenderbim/build/wheels/
cd ../ifccsv && make dist && mv dist/*.whl ../blenderbim/build/wheels/
cd ../ifcpatch && make dist && mv dist/*.whl ../blenderbim/build/wheels/
cd ../ifc4d && make dist && mv dist/*.whl ../blenderbim/build/wheels/
cd ../ifc5d && make dist && mv dist/*.whl ../blenderbim/build/wheels/
cd ../ifccityjson && make dist && mv dist/*.whl ../blenderbim/build/wheels/
cd build && . env/$(VENV_ACTIVATE) && $(PIP) download GitPython --dest=./wheels
# Provides audio playback for costing
# This is a REALLY IMPORTANT feature
@@ -252,9 +251,22 @@ endif
$(SED) "s/os-arch/$(BLENDER_PLATFORM)/" build/blenderbim/blender_manifest.toml
# Provides BlenderBIM Add-on functionality
ifeq ($(IS_STABLE), TRUE)
$(SED) 's/version = "0.0.0"/version = "$(VERSION)"/' build/pyproject.toml
else
$(SED) 's/version = "0.0.0"/version = "$(VERSION)a$(VERSION_DATE)"/' build/pyproject.toml
endif
cd build && . env/$(VENV_ACTIVATE) && $(PYTHON) -m build
cp build/dist/*.whl build/wheels/
# Safeguard for non-wheel dependencies slipping in.
wheels=$$(find build/wheels/* ! -name "*.whl"); \
if [ -n "$$wheels" ]; then \
echo "Found non-wheel dependencies:"; \
echo "$$wheels"; \
echo "Error: non-wheel dependencies are not supported by Blender!"; \
exit 1; \
fi
mv build/wheels/*.whl build/blenderbim/wheels/
# Ugly patch to accomodate MacOS universal builds not being recognized by Blender.
@@ -297,6 +309,27 @@ ifeq ($(PLATFORM), macos)
mv "$$prev_whl_name" "$$whl_name";
endif
# Safeguard for unhandled universal files.
wheels=$$(find build/blenderbim/wheels/*universal2*.whl); \
if [ -n "$$wheels" ]; then \
echo "Found universal2 wheel files:"; \
echo "$$wheels"; \
echo "Error: universal2 wheel files are not supported by Blender!"; \
exit 1; \
fi
ifneq ($(PLATFORM), linux)
# Safeguard: in case one of `pip download` will break,
# it will produce a linux wheel for non-linux build (our github action machine is using linux).
wheels=$$(find build/blenderbim/wheels/*manylinux_*.whl); \
if [ -n "$$wheels" ]; then \
echo "Found linux wheel files in non-linux build:"; \
echo "$$wheels"; \
echo "Error: linux wheels are incompatible with build $(PLATFORM)!"; \
exit 1; \
fi
endif
$(PYTHON) scripts/get_wheels.py build/blenderbim/wheels build/blenderbim/blender_manifest.toml
rm -rf build/blenderbim/bim/
rm -rf build/blenderbim/core/
+104 -20
View File
@@ -27,15 +27,16 @@ import sys
IN_BLENDER = sys.modules.get("bpy", None)
if IN_BLENDER:
import bpy
import addon_utils
import re
import platform
import traceback
import webbrowser
import uuid
import shutil
from collections import deque
from pathlib import Path
from typing import Union, Any
from typing import Union, Any, Generator
last_commit_hash = "8888888"
@@ -52,19 +53,35 @@ def get_last_commit_hash() -> Union[str, None]:
last_error = None
last_actions: deque = deque(maxlen=10)
bbim_semver: dict[str, Any] = {}
def initialize_bbim_semver():
"""Initialize `bbim_semver` dictionary.
Blender doesn't seem to store a full extension version (only major-minor-patch)
in `addon_utils.modules()->bl_info['version']`,
therefore we just parse it from .toml.
"""
import tomllib
toml_path = Path(__file__).parent / "blender_manifest.toml"
with open(toml_path, "rb") as f:
manifest = tomllib.load(f)
semver_pattern = r"^(?P<major>0|[1-9]\d*)\.(?P<minor>0|[1-9]\d*)\.(?P<patch>0|[1-9]\d*)(?:-(?P<prerelease>(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?P<buildmetadata>[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$"
version_str = manifest["version"]
re_version = re.match(semver_pattern, version_str)
assert re_version
global bbim_semver
bbim_semver = re_version.groupdict()
bbim_semver["version"] = version_str
initialize_bbim_semver()
def get_debug_info():
version = ".".join(
[
str(x)
for x in [
addon.bl_info.get("version", (-1, -1, -1))
for addon in addon_utils.modules()
if addon.bl_info["name"] == "BlenderBIM"
][0]
]
)
bbim_version = bbim_semver["version"]
return {
"os": platform.system(),
@@ -74,7 +91,7 @@ def get_debug_info():
"machine": platform.machine(),
"processor": platform.processor(),
"blender_version": bpy.app.version_string,
"blenderbim_version": version,
"blenderbim_version": bbim_version,
"blenderbim_commit_hash": get_last_commit_hash(),
"last_actions": last_actions,
"last_error": last_error,
@@ -92,6 +109,68 @@ def format_debug_info(info: dict):
return text.strip()
def get_binaries(path: Path) -> Generator[Path, None, None]:
yield from path.glob("**/*.pyd")
yield from path.glob("**/*.dll")
# pyradiance is using .so files on windows for some reason.
yield from path.glob("**/*.so")
def safe_link_dlls() -> None:
# Blender 4.2+ has a problem on Windows for disabling/enabling/reinstalling extensions
# with loaded binary dependencies (on Windows you can't remove a binary if it's loaded by some program).
# To avoid this issue we temporary hard link dlls to our temp directory on unregister()
# (unregister is executed before Blender will try to uninstall dependencies and the issue will arise).
# Then, Blender won't have a problem unlinking unloaded dlls as they are still linked somewhere.
# On register() we clean up our temp directory with binaries.
#
# TODO: If user uninstalls BlenderBIM to never use it again, temporary directory won't be cleared.
#
# See: https://projects.blender.org/blender/blender/issues/125049
import bpy
ext_path = Path(bpy.utils.user_resource("EXTENSIONS"))
local_path = ext_path / ".local"
# We use random hash subfolder as user may try to enable/disable addon multiple times.
random_hash = uuid.uuid4().hex[:8]
temp_local = ext_path / ".local_temp" / random_hash
temp_local.mkdir(parents=True)
for filepath in get_binaries(local_path):
dest_path = temp_local / filepath.relative_to(local_path)
dest_path.parent.mkdir(exist_ok=True, parents=True)
os.link(filepath, dest_path)
def clean_up_dlls_safe_links() -> None:
import bpy
ext_path = Path(bpy.utils.user_resource("EXTENSIONS"))
temp_path = ext_path / ".local_temp"
if not temp_path.exists():
return
for filepath in get_binaries(temp_path):
try:
os.unlink(filepath)
except PermissionError:
pass
def is_empty_directory(directory: Path) -> bool:
return not any(directory.iterdir())
def remove_empty_folders(folder: Path) -> None:
for subfolder in folder.iterdir():
if subfolder.is_dir():
remove_empty_folders(subfolder)
if is_empty_directory(folder):
folder.rmdir()
remove_empty_folders(temp_path)
if IN_BLENDER:
def get_binary_info() -> dict[str, Any]:
@@ -132,8 +211,7 @@ if IN_BLENDER:
# We can't just use __file__ as blenderbim/__init__.py is typically not symlinked
# as Blender have errors symlinking main addon package file.
path = Path(__file__).parent / "bim" / "__init__.py"
path = path.resolve().parent
path = Path(__file__).resolve().parent
repo = git.Repo(str(path), search_parent_directories=True)
last_commit_hash = repo.head.object.hexsha
except:
@@ -154,11 +232,17 @@ if IN_BLENDER:
ifcopenshell.api.add_pre_listener("*", "action_logger", log_api)
def register():
if platform.system() == "Windows":
clean_up_dlls_safe_links()
import blenderbim.bim
blenderbim.bim.register()
def unregister():
if platform.system() == "Windows":
safe_link_dlls()
import blenderbim.bim
blenderbim.bim.unregister()
@@ -229,15 +313,15 @@ if IN_BLENDER:
bbim_version = info["blenderbim_version"]
py_tag = py.replace(".", "")
if "Linux" in info["os"]:
os = "linux"
os = "linux-x64"
elif "Darwin" in info["os"]:
if "arm64" in info["machine"]:
os = "macosm1"
os = "macos-arm64"
else:
os = "macos"
os = "macos-x64"
else:
os = "win"
op.uri = f"https://github.com/IfcOpenShell/IfcOpenShell/releases/download/blenderbim-{bbim_version}/blenderbim-{bbim_version}-py{py_tag}-{os}.zip"
os = "windows-x64"
op.uri = f"https://github.com/IfcOpenShell/IfcOpenShell/releases/download/blenderbim-{bbim_version}/blenderbim_py{py_tag}-{bbim_version}-{os}.zip"
class OpenUri(bpy.types.Operator):
bl_idname = "bim.open_uri"
@@ -196,7 +196,6 @@ for mod in modules.values():
addon_keymaps = []
icons = None
is_registering = False
last_commit_hash = "8888888"
original_scene_panels_polls: dict[bpy.types.Panel, Union[Callable, None]] = dict()
@@ -1,3 +1,11 @@
import sys
import os
import webbrowser
blenderbim_lib_path = os.environ.get("blenderbim_lib_path")
if blenderbim_lib_path:
sys.path.insert(0, blenderbim_lib_path)
import argparse
from aiohttp import web
import socketio
@@ -21,6 +29,8 @@ sio = socketio.AsyncServer(
app = web.Application()
sio.attach(app)
# represents a caching for blender messages
blender_messages = {}
@@ -31,6 +41,8 @@ class WebNamespace(socketio.AsyncNamespace):
async def on_connect(self, sid, environ):
print(f"Web client connected: {sid}")
if blender_messages:
await self.send_cached_messages(sid)
async def on_disconnect(self, sid):
print(f"Web client disconnected: {sid}")
@@ -43,6 +55,14 @@ class WebNamespace(socketio.AsyncNamespace):
room=data["blenderId"],
)
async def send_cached_messages(self, sid):
# Send cached messages to the connected web client
for blenderId, messages in blender_messages.items():
if "csv_data" in messages:
await self.emit("csv_data", {"blenderId": blenderId, "data": messages["csv_data"]}, room=sid)
if "gantt_data" in messages:
await self.emit("gantt_data", {"blenderId": blenderId, "data": messages["gantt_data"]}, room=sid)
# Blender namespace
class BlenderNamespace(socketio.AsyncNamespace):
@@ -65,7 +85,7 @@ class BlenderNamespace(socketio.AsyncNamespace):
async def on_data(self, sid, data):
print(f"Data from Blender client {sid}")
# blender_messages[sid]["default_data"] = data
# await sio.emit("default_data", {"blenderId": sid, "data": data}, namespace="/web")
await sio.emit("default_data", {"blenderId": sid, "data": data}, namespace="/web")
async def on_csv_data(self, sid, data):
print(f"CSV data from Blender client {sid}")
@@ -99,10 +119,15 @@ async def gantt(request):
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}/")
app.router.add_get("/", index)
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)
def main():
@@ -1,19 +1,65 @@
:root {
--font-family: Arial, sans-serif;
--margin-small: 0.625rem;
--margin-medium: 1.25rem;
--margin-large: 2.5rem;
--padding-small: 0.125rem;
--padding-medium: 1rem;
--font-size-small: 1rem;
--font-size-large: 1.2rem;
--logo-height: 2.5rem;
--folder-collapse-font-size: 0.75rem;
--folder-collapse-font-family: Courier, "Courier New", monospace;
--box-shadow: 0 0 0.7rem #5f5f5f66;
}
:root.dark {
color-scheme: dark;
--bg-color: #252525;
--primary-text-color: #e0e0e0;
--secondary-text-color: #c7c7c7;
--nav-bg-color: #121212;
--nav-border-color: #25682a;
--nav-link-color: #fff;
--nav-link-hover-color: #3fb449;
--warning-color: #FFDB8F;
--border-color: #464444;
--hover-bg-color: #3a3a3a;
--highlight-color: #009136;
--task-complete-bg-color: #777777;
--milestone-border-color: #ffffff;
--group-item-bg-color: #4b4b4b;
--input-bg-color: #3b3b3b;
--input-border-color: #000;
}
:root.light {
color-scheme: light;
--bg-color: #ffffff;
--primary-text-color: #000000;
--nav-bg-color: #f8f8f8;
--nav-border-color: #cccccc;
--nav-link-color: #000000;
--nav-link-hover-color: #38a63d;
--warning-color: #FF4500;
}
html {
font-size: var(--base-font-size);
}
body {
background-color: #252525;
color: #e0e0e0;
background-color: var(--bg-color);
color: var(--primary-text-color);
margin: 0;
font-family: Arial, sans-serif;
font-family: var(--font-family);
}
#container {
margin-top: 20px;
margin-left: 10px;
margin-right: 10px;
margin-bottom: 20px;
margin-top: var(--margin-medium);
margin-left: var(--margin-small);
margin-right: var(--margin-small);
margin-bottom: var(--margin-medium);
}
h3 {
@@ -21,79 +67,89 @@ h3 {
}
nav {
background-color: #121212;
padding: 1em 0;
background-color: var(--nav-bg-color);
padding: var(--padding-medium) 0;
display: flex;
align-items: center;
position: relative;
border-bottom: 2px solid var(--nav-border-color);
}
nav .logo {
margin-left: 40px;
height: 40px;
/* Adjust height as needed */
margin-left: var(--margin-large);
height: var(--logo-height);
}
nav ul {
list-style-type: none;
margin: 0;
margin-left: 1px;
margin-left: 0.0625rem;
display: flex;
flex-grow: 1;
justify-content: center;
}
nav ul li {
margin-right: 50px;
margin-right: 3.125rem;
}
nav ul li a {
text-decoration: none;
color: #fff;
font-size: 1.2em;
color: var(--nav-link-color);
font-size: var(--font-size-large);
}
nav ul li a:hover,
nav ul li a.active {
color: #3fb449;
color: var(--nav-link-hover-color);
}
nav .bottom-bar {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 2px;
background-color: #25682a;
.warning {
color: var(--warning-color);
margin-bottom: var(--margin-small);
padding: var(--padding-small);
display: none;
}
.table-description {
margin-bottom: 1rem;
margin-bottom: var(--margin-medium);
width: 50%;
background-color: transparent;
border-collapse: collapse;
border-spacing: 0;
border-color: #464444;
border: 1px solid #464444;
border-color: var(--border-color);
border: 1px solid var(--border-color);
border-radius: 0.25rem;
box-shadow: 0 0 0.7rem rgba(95, 95, 95, 0.4);
box-shadow: var(--box-shadow);
text-align: center;
}
.table-description tr {
display: table-row;
border-bottom: 1px solid #464444;
border-bottom: 1px solid var(--border-color);
}
#toggle-theme {
border: none;
background: none;
cursor: pointer;
font-size: var(--font-size-large);
margin-right: var(--margin-medium);
}
#toggle-theme:focus {
outline: none;
}
.gantt-info {
margin: 0.5rem;
font-size: 1rem;
font-size: var(--font-size-small);
font-weight: bold;
}
.btn {
margin: 0.5rem;
font-size: 1rem;
font-size: var(--font-size-small);
cursor: pointer;
}
@@ -104,86 +160,76 @@ nav .bottom-bar {
}
/* ------------ Overwriting JSGantt CSS rules ------------ */
div.gantt {
background-color: #252525;
color: #c7c7c7;
:root.dark div.gantt {
background-color: var(--bg-color);
color: var(--secondary-text-color);
}
.gantt table {
border-color: #464444;
;
}
.gantt td {
border-color: #464444;
;
:root.dark .gantt table,
:root.dark .gantt td {
border-color: var(--border-color);
}
/* Headings and cell defaults */
.gmajorheading,
.gminorheading,
.gminorheadingwkend,
.gtaskcell,
.gtaskcellcurrent,
.gtaskcellwkend,
.gname,
.ggroupitem,
.gtaskheading {
background-color: #252525;
border-color: #464444;
;
:root.dark .gmajorheading,
:root.dark .gminorheading,
:root.dark .gminorheadingwkend,
:root.dark .gtaskcell,
:root.dark .gtaskcellcurrent,
:root.dark .gtaskcellwkend,
:root.dark .gname,
:root.dark .ggroupitem,
:root.dark .gtaskheading {
background-color: var(--bg-color);
border-color: var(--border-color);
}
.gtaskheading,
.gname,
.gtaskname,
.gres,
.gdur,
.gcomp,
.gstartdate,
.gplanstartdate,
.gplanenddate,
.gcost,
.gchartlbl,
.gcontainercol,
.genddate {
color: #c7c7c7;
border-color: #464444;
;
:root.dark .gtaskheading,
:root.dark .gname,
:root.dark .gtaskname,
:root.dark .gres,
:root.dark .gdur,
:root.dark .gcomp,
:root.dark .gstartdate,
:root.dark .gplanstartdate,
:root.dark .gplanenddate,
:root.dark .gcost,
:root.dark .gchartlbl,
:root.dark .gcontainercol,
:root.dark .genddate {
color: var(--secondary-text-color);
border-color: var(--border-color);
}
.gtaskname>div {
color: #c7c7c7;
:root.dark .gtaskname>div {
color: var(--secondary-text-color);
}
.gtaskbarcontainer.gplan,
.gchartlbl.gcontainercol {
background: #252525;
border-color: #464444;
:root.dark .gtaskbarcontainer.gplan,
:root.dark .gchartlbl.gcontainercol {
background: var(--bg-color);
border-color: var(--border-color);
}
.gtaskname div,
.gtaskheading div,
.gtaskname span {
color: #c7c7c7;
:root.dark .gtaskname div,
:root.dark .gtaskheading div,
:root.dark .gtaskname span {
color: var(--secondary-text-color);
}
.gtasklist,
.gadditional {
border: #464444 1px solid
:root.dark .gtasklist,
:root.dark .gadditional {
border: var(--border-color) 1px solid;
}
.gchartgrid {
background-color: #252525;
:root.dark .gchartgrid {
background-color: var(--bg-color);
}
.glistgrid,
.glistlbl {
background-color: #252525;
border-color: #464444;
;
:root.dark .glistgrid,
:root.dark .glistlbl {
background-color: var(--bg-color);
border-color: var(--border-color);
}
/* .gTaskInfo {
@@ -193,88 +239,81 @@ div.gantt {
} */
/* Dark mode scrollbar */
.frame::-webkit-scrollbar-thumb {
background-color: #252525;
}
.frame::-webkit-scrollbar-track {
background-color: #252525;
:root.dark .frame::-webkit-scrollbar-thumb,
:root.dark .frame::-webkit-scrollbar-track {
background-color: var(--bg-color);
}
/* Highlight row */
.gitemhighlight td {
background-color: #009136;
color: #ffffff;
:root.dark .gitemhighlight td {
background-color: var(--highlight-color);
color: var(--nav-link-color);
}
/* Differentiate Group, Milestone and Ordinary task items (applied to row) */
.ggroupitem {
background-color: #252525;
:root.dark .ggroupitem {
background-color: var(--bg-color);
font-weight: bold;
border-color: #464444;
border-color: var(--border-color);
}
.gmileitem,
.glineitem {
background-color: #252525;
:root.dark .gmileitem,
:root.dark .glineitem {
background-color: var(--bg-color);
}
/* Task bar caption text styles */
.gmilecaption,
.ggroupcaption,
.gcaption {
color: #ffffff;
:root.dark .gmilecaption,
:root.dark .ggroupcaption,
:root.dark .gcaption {
color: var(--nav-link-color);
}
/* Task complete %age bar */
.gtaskcomplete {
background-color: #777777;
:root.dark .gtaskcomplete {
background-color: var(--task-complete-bg-color);
}
/* Milestones */
.gmdtop {
border-bottom: 5px solid #ffffff;
:root.dark .gmdtop,
:root.dark .gmdbottom {
border-bottom: 5px solid var(--milestone-border-color);
}
.gmdbottom {
border-top: 5px solid #ffffff;
}
.gfoldercollapse {
color: #c7c7c7;
:root.dark .gfoldercollapse {
color: var(--secondary-text-color);
cursor: pointer;
font-weight: bold;
font-size: 12px;
font-family: Courier, "Courier New", monospace;
font-size: var(--folder-collapse-font-size);
font-family: var(--folder-collapse-font-family);
}
/* Highlight for collapsible row */
.gname.ggroupitem {
background-color: rgb(75, 75, 75);
:root.dark .gname.ggroupitem {
background-color: var(--group-item-bg-color);
}
/* Form label and selected highlighting */
.gformlabel {
background-color: #2e2e2e;
color: #c7c7c7;
border: #4a4a4a 1px solid;
:root.dark .gformlabel {
background-color: var(--formlabel-bg-color);
color: var(--secondary-text-color);
border: var(--formlabel-border-color) 1px solid;
}
span.gformlabel:hover {
background-color: #3a3a3a;
border-color: #565656;
:root.dark span.gformlabel:hover {
background-color: var(--hover-bg-color);
border-color: var(--border-color);
}
span.gselected {
background-color: #565656;
border-color: #777777;
color: #ffffff;
:root.dark span.gselected {
background-color: var(--formlabel-selected-bg-color);
border-color: var(--task-complete-bg-color);
color: var(--nav-link-color);
}
.gantt-inputtable {
background-color: #3b3b3b;
:root.dark .gantt-inputtable {
background-color: var(--input-bg-color);
box-sizing: border-box;
border: 1px solid #000;
color: #c7c7c7;
border: 1px solid var(--input-border-color);
color: var(--secondary-text-color);
}
@@ -1,27 +1,60 @@
:root {
--font-family: Arial, sans-serif;
--margin-small: 0.625rem;
--margin-medium: 1.25rem;
--margin-large: 2.5rem;
--padding-small: 0.125rem;
--padding-medium: 1rem;
--font-size-large: 1.2rem;
--logo-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;
}
: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;
}
html {
font-size: var(--base-font-size);
}
body {
background-color: #252525;
color: #e0e0e0;
background-color: var(--bg-color);
color: var(--text-color);
margin: 0;
font-family: Arial, sans-serif;
font-family: var(--font-family);
}
#container {
margin-top: 20px;
margin-left: 10px;
margin-right: 10px;
margin-bottom: 20px
margin-top: var(--margin-medium);
margin-left: var(--margin-small);
margin-right: var(--margin-small);
margin-bottom: var(--margin-medium);
}
.table-container {
margin-bottom: 40px;
margin-bottom: var(--margin-large);
}
.csv-table {
margin-top: 10px;
margin-top: var(--margin-small);
}
h3 {
@@ -29,48 +62,71 @@ h3 {
}
nav {
background-color: #121212;
padding: 1em 0;
background-color: var(--nav-bg-color);
padding: var(--padding-medium) 0;
display: flex;
align-items: center;
position: relative;
border-bottom: 2px solid var(--nav-border-color);
}
nav .logo {
margin-left: 40px;
height: 40px;
/* Adjust height as needed */
margin-left: var(--margin-large);
height: var(--logo-height);
}
nav ul {
list-style-type: none;
margin: 0;
margin-left: 1px;
margin-left: 0.0625rem;
display: flex;
flex-grow: 1;
justify-content: center;
}
nav ul li {
margin-right: 50px;
margin-right: 3.125rem;
}
nav ul li a {
text-decoration: none;
color: #fff;
font-size: 1.2em;
color: var(--nav-link-color);
font-size: var(--font-size-large);
}
nav ul li a:hover,
nav ul li a.active {
color: #3fb449;
color: var(--nav-link-hover-color);
}
nav .bottom-bar {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 2px;
background-color: #25682a;
.warning {
color: var(--warning-color);
margin-bottom: var(--margin-small);
padding: var(--padding-small);
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;
}
/* ------------ Overwriting Tabulator CSS rules ------------ */
:root.light .tabulator-header,
:root.light .tabulator .tabulator-header .tabulator-col {
background: var(--nav-bg-color) !important;
color: var(--text-color);
}
:root.light .tabulator {
border-top: 1px solid #222;
border-bottom: 2px solid #222;
}
@@ -19,6 +19,12 @@ const pageSizes = [
// 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);
connectSocket();
});
@@ -32,6 +38,7 @@ function connectSocket() {
socket.on("blender_connect", handleBlenderConnect);
socket.on("blender_disconnect", handleBlenderDisconnect);
socket.on("gantt_data", handleGanttData);
socket.on("default_data", handleDefaultData);
}
// Function to handle 'blender_connect' event
@@ -61,7 +68,7 @@ function handleGanttData(data) {
console.log(data);
const filename = data["data"]["IFC_File"];
const filename = data["data"]["ifc_file"];
const ganttTasks = data["data"]["gantt_data"]["tasks"];
const ganttWorkSched = data["data"]["gantt_data"]["work_schedule"];
// const ganttWorkSched = {};
@@ -89,6 +96,13 @@ function handleGanttData(data) {
}
}
function handleDefaultData(data) {
const blenderId = data["blenderId"];
const isDirty = data["data"]["is_dirty"];
showWarning(blenderId, isDirty);
console.log(data);
}
// Function to add a new gantt with data and filename
function addGanttElement(blenderId, tasks, workSched, filename) {
const ganttContainer = $("<div></div>")
@@ -101,6 +115,13 @@ function addGanttElement(blenderId, tasks, workSched, filename) {
.addClass("no-print")
.css("margin-bottom", "10px");
const warning = $("<div></div>")
.attr("id", "warning-" + blenderId)
.html(
"&#9888; Warning: This Gantt Chart may contain outdated data due to recent changes in Blender."
)
.addClass("warning no-print");
const workSchedDiv = $("<div></div>").attr("id", "workSched" + blenderId);
const scheduleTable = $("<table></table>")
@@ -148,6 +169,7 @@ function addGanttElement(blenderId, tasks, workSched, filename) {
workSchedDiv.append(scheduleTable);
ganttContainer.append(ganttTitle);
ganttContainer.append(warning);
ganttContainer.append(workSchedDiv);
ganttContainer.append(ganttInfoDiv);
ganttContainer.append(ganttDiv);
@@ -233,8 +255,7 @@ function addGanttElement(blenderId, tasks, workSched, filename) {
addEventListener("afterprint", (event) => {
g.setEditable(true);
},);
});
let css =
"@media print {\n" +
@@ -280,6 +301,7 @@ function updateGanttElement(blenderId, tasks, workSched, filename) {
g.Draw();
$("#title-" + blenderId).text(filename);
$("#warning-" + blenderId).css("display", "none");
}
// Function to remove gantt element
@@ -287,6 +309,10 @@ function removeGanttElement(blenderId) {
$("#container-" + blenderId).remove();
}
function showWarning(blenderId, isDirty) {
$("#warning-" + blenderId).css("display", "block");
}
// Utility function to create a tooltip for the gantt chars
function generateTooltip(task) {
var dataObject = task.getDataObject();
@@ -337,3 +363,22 @@ function editValue(list, task, event, cell, column) {
};
socket.emit("web_operator", msg);
}
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");
}
}
@@ -5,6 +5,12 @@ 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);
connectSocket();
});
@@ -18,6 +24,7 @@ function connectSocket() {
socket.on("blender_connect", handleBlenderConnect);
socket.on("blender_disconnect", handleBlenderDisconnect);
socket.on("csv_data", handleCsvData);
socket.on("default_data", handleDefaultData);
}
// Function to handle 'blender_connect' event
@@ -41,7 +48,7 @@ function handleBlenderDisconnect(blenderId) {
function handleCsvData(data) {
const blenderId = data["blenderId"];
const csvData = data["data"]["csv_data"];
const filename = data["data"]["IFC_File"];
const filename = data["data"]["ifc_file"];
console.log(data);
if (connectedClients.hasOwnProperty(blenderId)) {
@@ -65,6 +72,13 @@ function handleCsvData(data) {
}
}
function handleDefaultData(data) {
const blenderId = data["blenderId"];
const isDirty = data["data"]["is_dirty"];
showWarning(blenderId, isDirty);
console.log(data);
}
// Function to add a new table with data and filename
function addTableElement(blenderId, csvData, filename) {
// store headers of the csv data
@@ -82,11 +96,19 @@ function addTableElement(blenderId, csvData, filename) {
.text(filename)
.css("margin-bottom", "10px");
const warning = $("<div></div>")
.attr("id", "warning-" + blenderId)
.html(
"&#9888; Warning: This table may contain outdated data due to recent changes in Blender."
)
.addClass("warning");
const tableDiv = $("<div></div>")
.addClass("csv-table")
.attr("id", "table-" + blenderId);
tableContainer.append(tableTitle);
tableContainer.append(warning);
tableContainer.append(tableDiv);
$("#container").append(tableContainer);
@@ -199,6 +221,7 @@ function updateTableElement(blenderId, csvData, filename) {
connectedClients[blenderId].headers = newHeaders;
}
$("#title-" + blenderId).text(filename);
$("#warning-" + blenderId).css("display", "none");
}
}
@@ -207,6 +230,10 @@ function removeTableElement(blenderId) {
$("#container-" + blenderId).remove();
}
function showWarning(blenderId, isDirty) {
$("#warning-" + blenderId).css("display", "block");
}
// Utility function to compare two csv header
function compareHeaders(headers1, headers2) {
if (headers1.length !== headers2.length) {
@@ -219,3 +246,31 @@ function compareHeaders(headers1, headers2) {
}
return true;
}
function setTheme(theme) {
var stylesheet = $("#tabulator-stylesheet");
if (theme === "light") {
stylesheet.attr(
"href",
"https://unpkg.com/tabulator-tables/dist/css/tabulator_site.min.css"
);
$("html").removeClass("dark").addClass("light");
$("#toggle-theme").html('<i class="fas fa-sun"></i>');
} else {
stylesheet.attr(
"href",
"https://unpkg.com/tabulator-tables/dist/css/tabulator_site_dark.min.css"
);
$("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");
}
}
@@ -1,11 +1,15 @@
<!DOCTYPE html>
<html lang="en">
<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" type="text/css" href="/jsgantt/jsgantt.css" />
<link rel="stylesheet" href="/static/css/gantt.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"
@@ -22,12 +26,18 @@
</head>
<body>
<nav class="no-print">
<img src="https://blenderbim.org/assets/images/blender/blender-logo.png" alt="Logo" class="logo" />
<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" class="active">Gantt Chart</a></li>
</ul>
<div class="bottom-bar"></div>
<button id="toggle-theme" onclick="toggleTheme()">
<i class="fas fa-moon"></i>
</button>
</nav>
<div id="container"></div>
</body>
@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="en">
<html lang="en" class="dark">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
@@ -7,8 +7,13 @@
<link rel="stylesheet" href="/static/css/index.css" />
<link
rel="stylesheet"
id="tabulator-stylesheet"
href="https://unpkg.com/tabulator-tables/dist/css/tabulator_site_dark.min.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"
@@ -28,12 +33,18 @@
</head>
<body>
<nav>
<img src="https://blenderbim.org/assets/images/blender/blender-logo.png" alt="Logo" class="logo" />
<img
src="https://blenderbim.org/assets/images/blender/blender-logo.png"
alt="Logo"
class="logo"
/>
<ul>
<li><a href="/" class="active">IFC Data</a></li>
<li><a href="/gantt">Gantt Chart</a></li>
</ul>
<div class="bottom-bar"></div>
<button id="toggle-theme" onclick="toggleTheme()">
<i class="fas fa-moon"></i>
</button>
</nav>
<div id="container"></div>
</body>
@@ -167,21 +167,6 @@ class IfcExporter:
def get_application_name(self) -> str:
return "BlenderBIM"
def get_application_version(self) -> str:
version = ".".join(
[
str(x)
for x in [
addon.bl_info.get("version", (-1, -1, -1))
for addon in addon_utils.modules()
if addon.bl_info["name"] == "BlenderBIM"
][0]
]
)
if commit_hash := tool.Blender.get_last_commit_hash():
version += f"-{commit_hash}"
return version
class IfcExportSettings:
def __init__(self):
+1 -14
View File
@@ -222,7 +222,7 @@ def redo_post(scene):
def get_application(ifc: ifcopenshell.file) -> ifcopenshell.entity_instance:
# TODO: cache this for even faster application retrieval. It honestly makes a difference on long scripts.
version = get_application_version()
version = tool.Blender.get_blenderbim_version()
for element in ifc.by_type("IfcApplication"):
if element.ApplicationIdentifier == "BlenderBIM" and element.Version == version:
return element
@@ -248,19 +248,6 @@ def get_user(ifc: ifcopenshell.file) -> Union[ifcopenshell.entity_instance, None
return pao
def get_application_version() -> str:
return ".".join(
[
str(x)
for x in [
addon.bl_info.get("version", (-1, -1, -1))
for addon in addon_utils.modules()
if addon.bl_info["name"] == "BlenderBIM"
][0]
]
)
def viewport_shading_changed_callback(area):
shading = area.spaces.active.shading.type
if shading == "RENDERED":
@@ -26,6 +26,7 @@ import requests
import tempfile
import datetime
import addon_utils
import blenderbim.tool as tool
class AuginLogin(bpy.types.Operator):
@@ -77,16 +78,7 @@ class AuginCreateNewModel(bpy.types.Operator):
props.project_filename = "BBM{}{}".format(
datetime.datetime.now().strftime("%Y%m%d%H%M%S"), "".join(random.choice(chars) for _ in range(6))
)
addon_version = ".".join(
[
str(x)
for x in [
addon.bl_info.get("version", (-1, -1, -1))
for addon in addon_utils.modules()
if addon.bl_info["name"] == "BlenderBIM"
][0]
]
)
addon_version = tool.Blender.get_blenderbim_version()
payload = {
"user_token": props.token,
@@ -254,9 +254,10 @@ class ExportIfcCsv(bpy.types.Operator):
schedule_creator = scheduler.Scheduler()
schedule_creator.schedule(self.filepath, tool.Drawing.get_path_with_ext(self.filepath, "svg"))
if props.format == "web":
tool.Web.send_webui_data(
extra_data=ifc_csv.dataframe.to_csv(index=False), extra_data_key="csv_data", event="csv_data"
)
if not context.scene.WebProperties.is_running:
bpy.ops.bim.connect_websocket_server()
tool.Web.send_webui_data(data=ifc_csv.dataframe.to_csv(index=False), data_key="csv_data", event="csv_data")
self.report({"INFO"}, f"Data is exported to {props.format.upper()}.")
return {"FINISHED"}
@@ -36,6 +36,7 @@ classes = (
operator.ProfileImportIFC,
operator.PurgeHdf5Cache,
operator.PurgeUnusedElementsByClass,
operator.RestartBlender,
operator.RewindInspector,
operator.SelectExpressFile,
operator.SelectHighPolygonMeshes,
@@ -770,3 +770,15 @@ class ToggleDetailedIOSLogs(bpy.types.Operator):
wrapper.turn_off_detailed_logging()
self.report({"INFO"}, "Detailed IfcOpenShell logs turned off.")
return {"FINISHED"}
class RestartBlender(bpy.types.Operator):
bl_idname = "bim.restart_blender"
bl_label = "Restart Blender"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
import os
path = bpy.app.binary_path
os.execv(path, sys.argv)
@@ -78,6 +78,9 @@ class BIM_PT_debug(Panel):
row = layout.row()
row.operator("bim.toggle_detailed_ios_logs")
row = layout.row()
row.operator("bim.restart_blender")
row = layout.split(factor=0.5, align=True)
row.operator("bim.create_shape_from_step_id").should_include_curves = False
row.operator("bim.create_shape_from_step_id", text="", icon="IPO_ELASTIC").should_include_curves = True
@@ -26,6 +26,7 @@ from blenderbim.bim.ifc import IfcStore
class OpenPieClass(bpy.types.Operator):
bl_idname = "bim.open_pie_class"
bl_label = "Open Pie Class"
bl_description = "Assign the IFC Class to the selected objects"
def execute(self, context):
bpy.ops.wm.call_menu_pie(name="VIEW3D_MT_PIE_bim_class")
@@ -166,7 +166,7 @@ class AssignClass(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.assign_class"
bl_label = "Assign IFC Class"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Assign the IFC Class to the selected object"
bl_description = "Assign the IFC Class to the selected objects"
obj: bpy.props.StringProperty()
ifc_class: bpy.props.StringProperty()
predefined_type: bpy.props.StringProperty()
@@ -175,6 +175,13 @@ class AssignClass(bpy.types.Operator, tool.Ifc.Operator):
should_add_representation: bpy.props.BoolProperty(default=True)
ifc_representation_class: bpy.props.StringProperty()
@classmethod
def poll(cls, context):
if not context.active_object and not context.selected_objects:
cls.poll_message_set("No object selected.")
return False
return True
def _execute(self, context):
props = context.scene.BIMRootProperties
objects = [bpy.data.objects.get(self.obj)] if self.obj else context.selected_objects or [context.active_object]
@@ -18,6 +18,7 @@
import bpy
import blenderbim.tool as tool
import os
def refresh():
@@ -31,10 +32,16 @@ class WebData:
@classmethod
def load(cls):
cls.data = {
"IFC_File": cls.ifc_file_name(),
"ifc_file": cls.get_ifc_file_name(),
"is_dirty": cls.get_is_dirty(),
}
cls.is_loaded = True
@classmethod
def ifc_file_name(cls):
return bpy.context.scene.BIMProperties.ifc_file
def get_ifc_file_name(cls):
filename = os.path.basename(bpy.context.scene.BIMProperties.ifc_file)
return filename
@classmethod
def get_is_dirty(cls):
return bpy.context.scene.BIMProperties.is_dirty
-1
View File
@@ -18,7 +18,6 @@
import os
import bpy
import addon_utils
import platform
from pathlib import Path
from bpy.types import Panel
+11 -13
View File
@@ -26,8 +26,8 @@ import ifcopenshell.util.element
import blenderbim.core.tool
import blenderbim.tool as tool
import blenderbim.bim
import addon_utils
import types
import importlib
from mathutils import Vector
from pathlib import Path
from blenderbim.bim.ifc import IFC_CONNECTED_TYPE
@@ -920,7 +920,8 @@ class Blender(blenderbim.core.tool.Blender):
@classmethod
def get_last_commit_hash(cls) -> Union[str, None]:
"""Get 8 symbols of last commit hash if it's present or return None otherwise."""
commit_hash = blenderbim.bim.last_commit_hash
bbim = cls.get_bbim_extension_package()
commit_hash = bbim.last_commit_hash
# Commit hash is unset - user is using __init__ from repo
# without setting up git repository.
@@ -930,17 +931,9 @@ class Blender(blenderbim.core.tool.Blender):
return commit_hash[:7]
@classmethod
def get_blenderbim_version(cls):
version = ".".join(
[
str(x)
for x in [
addon.bl_info.get("version", (-1, -1, -1))
for addon in addon_utils.modules()
if addon.bl_info["name"] == "BlenderBIM"
][0]
]
)
def get_blenderbim_version(cls) -> str:
bbim = cls.get_bbim_extension_package()
version = bbim.bbim_semver["version"]
if commit_hash := cls.get_last_commit_hash():
version += f"-{commit_hash}"
return version
@@ -1114,6 +1107,11 @@ class Blender(blenderbim.core.tool.Blender):
return package_name
return "blenderbim"
@classmethod
def get_bbim_extension_package(cls) -> types.ModuleType:
name = cls.get_blender_addon_package_name()
return importlib.import_module(name)
@classmethod
def get_addon_preferences(cls) -> blenderbim.bim.ui.BIM_ADDON_preferences:
blender_package_name = cls.get_blender_addon_package_name()
+5 -3
View File
@@ -1546,14 +1546,16 @@ class Sequence(blenderbim.core.tool.Sequence):
) -> None:
if bpy.context.scene.WebProperties.is_connected:
gantt_data = {"tasks": task_json, "work_schedule": work_schedule.get_info(recursive=True)}
tool.Web.send_webui_data(extra_data=gantt_data, extra_data_key="gantt_data", event="gantt_data")
tool.Web.send_webui_data(data=gantt_data, data_key="gantt_data", event="gantt_data")
return
with open(os.path.join(bpy.context.scene.BIMProperties.data_dir, "gantt", "index.html"), "w") as f:
with open(os.path.join(bpy.context.scene.BIMProperties.data_dir, "gantt", "index.mustache"), "r") as t:
task_b64 = base64.b64encode(bytes(json.dumps(task_json), "utf-8")).decode("utf-8")
f.write(
pystache.render(t.read(), {"json_data": task_b64, "data": json.dumps(work_schedule.get_info(recursive=True))})
pystache.render(
t.read(), {"json_data": task_b64, "data": json.dumps(work_schedule.get_info(recursive=True))}
)
)
webbrowser.open("file://" + os.path.join(bpy.context.scene.BIMProperties.data_dir, "gantt", "index.html"))
+186 -39
View File
@@ -21,32 +21,47 @@ from blenderbim.bim.module.web.data import WebData
import blenderbim.core.tool
import blenderbim.tool as tool
import ifcopenshell.api.sequence
from typing import Any, Dict, Optional
import socket
import sys
import os
import errno
import subprocess
import webbrowser
import asyncio
import socketio
import threading
import queue
import json
from time import sleep
from pathlib import Path
sio = None
ws_process = None
ws_thread = None
web_operator_queue = queue.Queue()
RECONNECTION_ATTEMPTS = 3
RECONNECTION_DELAY = 2
IFC_TASK_ATTRIBUTE_MAP = {
"pStart": "ScheduleStart",
"pEnd": "ScheduleFinish",
"pName": "Name",
"pRes": "",
}
class Web(blenderbim.core.tool.Web):
@classmethod
def generate_port_number(cls):
def generate_port_number(cls) -> int:
"""
Generate a free port number.
This method creates a temporary socket to bind to a free port.
It then retrieves the port number, and returns it.
Returns:
int: The port number that was generated.
"""
print("Generating port number")
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.bind(("localhost", 0)) # Bind to a free port
@@ -55,35 +70,104 @@ class Web(blenderbim.core.tool.Web):
return port
@classmethod
def is_port_available(cls, port):
def is_port_available(cls, port: int) -> bool:
"""
Attempts to connect to the specified port on localhost.
If the connection is refused, the port is available for use; otherwise, it is in use.
Args:
- port (int): The port number to check.
Returns:
bool: True if the port is available, False if it is in use.
"""
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
# connect_ex returns 0 if the connection succeeds
# indicating the port is in use
# otherwise returns 10061 if no server is listening
# indicating the port is available for use
return s.connect_ex(("localhost", port)) == 10061
# connect_ex returns errno.SUCCESS (0) if the connection succeeds
# otherwise returns errno.ECONNREFUSED (111 or 10061) if no server is listening
return s.connect_ex(("localhost", port)) == errno.ECONNREFUSED
@classmethod
def start_websocket_server(cls, port):
def start_websocket_server(cls, port: int) -> None:
"""
Starts a WebSocket server on the specified port.
This method sets up the environment, locates paths, and starts
the WebSocket server process. It also handles the creation and updating of a PID file to keep track
of running server instances.
Args:
- port (int): The port number on which to start the WebSocket server.
"""
import addon_utils
global ws_process
webui_path = os.path.join(bpy.context.scene.BIMProperties.data_dir, "webui")
ws_path = os.path.join(webui_path, "sioserver.py")
ws_process = subprocess.Popen(["python", ws_path, "--p", str(port), "--host", "127.0.0.1"], cwd=webui_path)
cls.open_web_browser(port)
py_version = sys.version_info
if bpy.app.version >= (4, 2, 0):
blenderbim_lib_path = (
Path(bpy.utils.user_resource("EXTENSIONS"))
/ ".local"
/ "lib"
/ f"python{py_version.major}.{py_version.minor}"
/ "site-packages"
)
else:
addon = [a for a in addon_utils.modules() if a.bl_info["name"] == "BlenderBIM"][0]
blenderbim_path = os.path.dirname(addon.__file__)
blenderbim_lib_path = os.path.join(blenderbim_path, "libs", "site", "packages")
env = os.environ.copy()
env["blenderbim_lib_path"] = str(blenderbim_lib_path)
ws_process = subprocess.Popen(
[sys.executable, ws_path, "--p", str(port), "--host", "127.0.0.1"],
cwd=webui_path,
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
def connect_websocket_server(cls, port):
def connect_websocket_server(cls, port: int) -> None:
"""
Connect to a WebSocket server on the specified port.
This method sets up an asynchronous Socket.IO client with
reconnection attempts, starts an asyncio thread, connects to the WebSocket server, and sets the connection status.
Args:
- port (int): The port number to connect to the WebSocket server.
"""
global ws_thread, sio
if bpy.context.scene.WebProperties.is_connected:
print(f"Already connected to websocket server on port: {port}")
return
sio = socketio.AsyncClient(reconnection=True, reconnection_attempts=3, reconnection_delay=2, logger=True)
sio = socketio.AsyncClient(
reconnection=True,
reconnection_attempts=RECONNECTION_ATTEMPTS,
reconnection_delay=RECONNECTION_DELAY,
logger=True,
)
ws_thread = AsyncioThread()
ws_thread.daemon = True
@@ -93,10 +177,15 @@ class Web(blenderbim.core.tool.Web):
ws_thread.run_coro(cls.sio_connect(ws_url))
cls.set_is_connected(True)
bpy.app.timers.register(cls.check_operator_queue)
cls.send_webui_data()
@classmethod
def disconnect_websocket_server(cls):
def disconnect_websocket_server(cls) -> None:
"""
Disconnects the WebSocket server and stops the associated thread.
This method is responsible for disconnecting the WebSocket server, stopping the asyncio thread,
and resetting the global variables related to the WebSocket connection.
"""
global ws_thread, sio
ws_thread.run_coro(cls.sio_disconnect())
ws_thread.stop()
@@ -105,7 +194,13 @@ class Web(blenderbim.core.tool.Web):
cls.set_is_connected(False)
@classmethod
def kill_websocket_server(cls):
def kill_websocket_server(cls) -> None:
"""
Terminate the currently running WebSocket server.
This method checks if there is an active WebSocket server process. If so, it disconnects it (if connected),
removes its PID from the PID file, terminates the process, and updates the server's running status.
"""
global ws_process
if ws_process is None:
@@ -115,36 +210,62 @@ class Web(blenderbim.core.tool.Web):
if bpy.context.scene.WebProperties.is_connected:
cls.disconnect_websocket_server()
sleep(0.5)
# sleep(0.5)
webui_path = os.path.join(bpy.context.scene.BIMProperties.data_dir, "webui")
pid_file = os.path.join(webui_path, "running_pid.json")
with open(pid_file, "r") as f:
pids = json.load(f)
if str(ws_process.pid) in pids:
del pids[str(ws_process.pid)]
# Write the updated PIDs back to the file
with open(pid_file, "w") as f:
json.dump(pids, f, indent=4)
ws_process.terminate()
ws_process.wait()
ws_process = None
cls.set_is_running(False)
print("Websocket server terminated successfully")
@classmethod
def send_webui_data(
cls, extra_data=None, extra_data_key="data", event="data", namespace="/blender", use_web_data=True
):
cls,
data: Optional[Any] = None,
data_key: str = "data",
event: str = "data",
namespace: str = "/blender",
use_web_data: bool = True,
) -> None:
"""
Sends data to the Web UI via Websocket connection.
Args:
- data (Optional[Any]): The data to send. If None, just sends data from WebData.
- data_key (str): The key under which to store the data in the payload. Defaults to "data".
- event (str): The WebSocket event to emit. Defaults to "data".
- namespace (str): The namespace for the WebSocket event. Defaults to "/blender".
- use_web_data (bool): Whether to use data from WebData. Defaults to True.
"""
global ws_thread
data = {}
payload = {}
if use_web_data:
if not WebData.is_loaded:
WebData.load()
data = WebData.data
payload = WebData.data
if extra_data is not None:
data[extra_data_key] = extra_data
if data is not None:
payload[data_key] = data
if ws_thread is not None and bpy.context.scene.WebProperties.is_connected:
ws_thread.run_coro(cls.sio_send(data, event, namespace))
ws_thread.run_coro(cls.sio_send(payload, event, namespace))
@classmethod
def check_operator_queue(cls):
def check_operator_queue(cls) -> None | float:
if not bpy.context.scene.WebProperties.is_connected:
with web_operator_queue.mutex:
web_operator_queue.queue.clear()
@@ -161,7 +282,7 @@ class Web(blenderbim.core.tool.Web):
return 1.0
@classmethod
def handle_csv_operator(cls, operator_data):
def handle_csv_operator(cls, operator_data: dict) -> None:
if operator_data["type"] == "selection":
bpy.ops.object.select_all(action="DESELECT")
guid = operator_data["globalId"]
@@ -171,7 +292,7 @@ class Web(blenderbim.core.tool.Web):
obj.select_set(True)
@classmethod
def handle_gantt_operator(cls, operator_data):
def handle_gantt_operator(cls, operator_data: dict) -> None:
ifc_file = tool.Ifc.get()
if operator_data["type"] == "editTask":
task_id = int(operator_data["taskId"])
@@ -200,55 +321,81 @@ class Web(blenderbim.core.tool.Web):
work_schedule = ifc_file.by_id(operator_data["workScheduleId"])
task_json = tool.Sequence.create_tasks_json(work_schedule)
gantt_data = {"tasks": task_json, "work_schedule": work_schedule.get_info(recursive=True)}
cls.send_webui_data(extra_data=gantt_data, extra_data_key="gantt_data", event="gantt_data")
cls.send_webui_data(data=gantt_data, data_key="gantt_data", event="gantt_data")
@classmethod
def open_web_browser(cls, port):
def open_web_browser(cls, port: int) -> None:
webbrowser.open(f"http://127.0.0.1:{port}/")
@classmethod
async def sio_connect(cls, url):
async def sio_connect(cls, url: str) -> None:
await sio.connect(url, transports=["websocket"], namespaces="/blender")
sio.on("web_operator", cls.sio_listen_web_operator, namespace="/blender")
@classmethod
async def sio_disconnect(cls):
async def sio_disconnect(cls) -> None:
await sio.disconnect()
@classmethod
async def sio_send(cls, data, event="data", namespace="/blender"):
async def sio_send(cls, data: Any, event: str = "data", namespace: str = "/blender") -> None:
await sio.emit(event, data, namespace=namespace)
@classmethod
async def sio_listen_web_operator(cls, data):
async def sio_listen_web_operator(cls, data: dict) -> None:
try:
web_operator_queue.put_nowait(data)
except queue.Full:
pass
@classmethod
def set_is_running(cls, is_running):
def set_is_running(cls, is_running: bool) -> None:
bpy.context.scene.WebProperties.is_running = is_running
@classmethod
def set_is_connected(cls, is_connected):
def set_is_connected(cls, is_connected: bool) -> None:
bpy.context.scene.WebProperties.is_connected = is_connected
class AsyncioThread(threading.Thread):
def __init__(self, *args, loop=None, **kwargs):
"""
Initialize an instance of AsyncioThread.
This class represents a thread that runs an asyncio event loop. It is used to handle asynchronous tasks
in a separate thread from the main thread.
Args:
- *args: Variable length argument list. These arguments are passed to the superclass constructor.
- loop: An existing asyncio event loop. If None, a new event loop is created.
- **kwargs: Arbitrary keyword arguments. These arguments are passed to the superclass constructor.
"""
super().__init__(*args, **kwargs)
self.loop = loop or asyncio.new_event_loop()
self.running = False
def run(self):
"""
Start the asyncio event loop and mark the thread as running.
"""
self.running = True
self.loop.run_forever()
def run_coro(self, coro):
def run_coro(self, coro) -> Any:
"""
Run a coroutine in the asyncio event loop from a separate thread.
Args:
- coro: The coroutine to be run.
Returns:
The result of the coroutine.
"""
return asyncio.run_coroutine_threadsafe(coro, loop=self.loop).result()
def stop(self):
def stop(self) -> None:
"""
Stop the asyncio event loop and join the thread.
"""
self.loop.call_soon_threadsafe(self.loop.stop)
self.join()
self.running = False
+8 -129
View File
@@ -22,8 +22,8 @@ You will need to choose which build to download.
- If you are on Blender >=4.1, choose py311
- If you are on Blender >=3.1 and <=4.0, choose py310
- If you are on Blender >=2.93 and <3.1, choose py39
- Choose ``linux``, ``macos`` (Apple Intel), ``macosm1`` (Apple Silicon), or
``win`` depending on your operating system
- Choose ``linux-x64``, ``macos-x64`` (Apple Intel), ``macos-arm64`` (Apple Silicon), or
``windows-x64`` depending on your operating system
For users who don't follow the `VFX Platform <https://vfxplatform.com/>`_
standard, we also provide py312 builds.
@@ -75,138 +75,17 @@ restart Blender to see changes).
For Linux or Mac:
.. code-block:: bash
git clone https://github.com/IfcOpenShell/IfcOpenShell.git
cd IfcOpenShell
# path to BlenderBIM addon
# default path on Mac: "/Users/$USER/Library/Application Support/Blender/X.X/scripts/addons/blenderbim"
# default path on Linux: "$HOME/.config/blender/X.X/"
BLENDER_ADDON_PATH="/path/to/blender/X.XX/scripts/addons/blenderbim"
# Remove the Blender add-on Python code
rm -r $BLENDER_ADDON_PATH/core/
rm -r $BLENDER_ADDON_PATH/tool/
rm -r $BLENDER_ADDON_PATH/bim/
# Replace them with links to the Git repository
ln -s $PWD/src/blenderbim/blenderbim/core $BLENDER_ADDON_PATH/core
ln -s $PWD/src/blenderbim/blenderbim/tool $BLENDER_ADDON_PATH/tool
ln -s $PWD/src/blenderbim/blenderbim/bim $BLENDER_ADDON_PATH/bim
# Copy over compiled IfcOpenShell files
cp $BLENDER_ADDON_PATH/libs/site/packages/ifcopenshell/*_wrapper* $PWD/src/ifcopenshell-python/ifcopenshell/
# Remove the IfcOpenShell dependency
rm -r $BLENDER_ADDON_PATH/libs/site/packages/ifcopenshell
# Replace them with links to the Git repository
ln -s $PWD/src/ifcopenshell-python/ifcopenshell $BLENDER_ADDON_PATH/libs/site/packages/ifcopenshell
# Remove and link other IfcOpenShell utilities
rm -r $BLENDER_ADDON_PATH/libs/site/packages/ifccsv.py
rm -r $BLENDER_ADDON_PATH/libs/site/packages/ifcdiff.py
rm -r $BLENDER_ADDON_PATH/libs/site/packages/bsdd.py
rm -r $BLENDER_ADDON_PATH/libs/site/packages/bcf
rm -r $BLENDER_ADDON_PATH/libs/site/packages/ifc4d
rm -r $BLENDER_ADDON_PATH/libs/site/packages/ifc5d
rm -r $BLENDER_ADDON_PATH/libs/site/packages/ifccityjson
rm -r $BLENDER_ADDON_PATH/libs/site/packages/ifcclash
rm -r $BLENDER_ADDON_PATH/libs/site/packages/ifcpatch
rm -r $BLENDER_ADDON_PATH/libs/site/packages/ifctester
rm -r $BLENDER_ADDON_PATH/libs/site/packages/ifcfm
rm -r $BLENDER_ADDON_PATH/libs/Desktop
ln -s $PWD/src/ifccsv/ifccsv.py $BLENDER_ADDON_PATH/libs/site/packages/ifccsv.py
ln -s $PWD/src/ifcdiff/ifcdiff.py $BLENDER_ADDON_PATH/libs/site/packages/ifcdiff.py
ln -s $PWD/src/bsdd/bsdd.py $BLENDER_ADDON_PATH/libs/site/packages/bsdd.py
ln -s $PWD/src/bcf/src/bcf $BLENDER_ADDON_PATH/libs/site/packages/bcf
ln -s $PWD/src/ifc4d/ifc4d $BLENDER_ADDON_PATH/libs/site/packages/ifc4d
ln -s $PWD/src/ifc5d/ifc5d $BLENDER_ADDON_PATH/libs/site/packages/ifc5d
ln -s $PWD/src/ifccityjson/ifccityjson $BLENDER_ADDON_PATH/libs/site/packages/ifccityjson
ln -s $PWD/src/ifcclash/ifcclash $BLENDER_ADDON_PATH/libs/site/packages/ifcclash
ln -s $PWD/src/ifcpatch/ifcpatch $BLENDER_ADDON_PATH/libs/site/packages/ifcpatch
ln -s $PWD/src/ifctester/ifctester $BLENDER_ADDON_PATH/libs/site/packages/ifctester
ln -s $PWD/src/ifcfm/ifcfm $BLENDER_ADDON_PATH/libs/site/packages/ifcfm
ln -s $PWD/src/blenderbim/blenderbim/libs/desktop $BLENDER_ADDON_PATH/libs/Desktop
# Manually download some third party dependencies
cd $BLENDER_ADDON_PATH/bim/data/gantt
wget https://raw.githubusercontent.com/jsGanttImproved/jsgantt-improved/master/dist/jsgantt.js
wget https://raw.githubusercontent.com/jsGanttImproved/jsgantt-improved/master/dist/jsgantt.css
cd $BLENDER_ADDON_PATH/bim/schema
wget https://github.com/BrickSchema/Brick/releases/download/nightly/Brick.ttl
.. literalinclude:: ../../scripts/installation/dev_environment.sh
:language: bash
:caption: dev_environment.sh
Or, if you're on Windows, you can use the batch script below. You need to run
it as an administrator. Before running it follow the instructions descibed
in the `rem` tags.
.. code-block:: bat
@echo off
rem SETUP BLENDER-BIM LIVE DEVELOPMENT ENVIRONMENT
rem Setup blenderbim addon location below (probably just need to change "x.x" for your Blender version).
rem Put the script to the folder where IfcOpenShell git repository is located
rem (script will try to clone IfcOpenShell.git if it's not present).
SET blenderbim=%appdata%\Blender Foundation\Blender\x.x\scripts\addons\blenderbim
git clone https://github.com/IfcOpenShell/IfcOpenShell.git
cd IfcOpenShell
echo Removing the Blender add-on Python code...
rd /S /Q "%blenderbim%\core\"
rd /S /Q "%blenderbim%\tool\"
rd /S /Q "%blenderbim%\bim\"
echo Replacing them with links to the Git repository...
mklink /D "%blenderbim%\core" "%cd%\src\blenderbim\blenderbim\core"
mklink /D "%blenderbim%\tool" "%cd%\src\blenderbim\blenderbim\tool"
mklink /D "%blenderbim%\bim" "%cd%\src\blenderbim\blenderbim\bim"
echo Copy over compiled IfcOpenShell files...
copy "%blenderbim%\libs\site\packages\ifcopenshell\*_wrapper*" "%cd%\src\ifcopenshell-python\ifcopenshell\"
echo Remove the IfcOpenShell dependency...
rd /S /Q "%blenderbim%\libs\site\packages\ifcopenshell"
echo Replace them with links to the Git repository...
mklink /D "%blenderbim%\libs\site\packages\ifcopenshell" "%cd%\src\ifcopenshell-python\ifcopenshell"
echo Remove and link other IfcOpenShell utilities...
del "%blenderbim%\libs\site\packages\ifccsv.py"
del "%blenderbim%\libs\site\packages\ifcdiff.py"
del "%blenderbim%\libs\site\packages\bsdd.py"
rd /S /Q "%blenderbim%\libs\site\packages\bcf"
rd /S /Q "%blenderbim%\libs\site\packages\ifc4d"
rd /S /Q "%blenderbim%\libs\site\packages\ifc5d"
rd /S /Q "%blenderbim%\libs\site\packages\ifccityjson"
rd /S /Q "%blenderbim%\libs\site\packages\ifcclash"
rd /S /Q "%blenderbim%\libs\site\packages\ifcpatch"
rd /S /Q "%blenderbim%\libs\site\packages\ifctester"
rd /S /Q "%blenderbim%\libs\site\packages\ifcfm"
rd /S /Q "%blenderbim%\libs\desktop"
mklink "%blenderbim%\libs\site\packages\ifccsv.py" "%cd%\src\ifccsv\ifccsv.py"
mklink "%blenderbim%\libs\site\packages\ifcdiff.py" "%cd%\src\ifcdiff\ifcdiff.py"
mklink "%blenderbim%\libs\site\packages\bsdd.py" "%cd%\src\bsdd\bsdd.py"
mklink /D "%blenderbim%\libs\site\packages\bcf" "%cd%\src\bcf\src\bcf"
mklink /D "%blenderbim%\libs\site\packages\ifc4d" "%cd%\src\ifc4d\ifc4d"
mklink /D "%blenderbim%\libs\site\packages\ifc5d" "%cd%\src\ifc5d\ifc5d"
mklink /D "%blenderbim%\libs\site\packages\ifccityjson" "%cd%\src\ifccityjson\ifccityjson"
mklink /D "%blenderbim%\libs\site\packages\ifcclash" "%cd%\src\ifcclash\ifcclash"
mklink /D "%blenderbim%\libs\site\packages\ifcpatch" "%cd%\src\ifcpatch\ifcpatch"
mklink /D "%blenderbim%\libs\site\packages\ifctester" "%cd%\src\ifctester\ifctester"
mklink /D "%blenderbim%\libs\site\packages\ifcfm" "%cd%\src\ifcfm\ifcfm"
mklink /D "%blenderbim%\libs\desktop" "%cd%\src\blenderbim\blenderbim\libs\desktop"
echo Manually downloading some third party dependencies...
curl https://raw.githubusercontent.com/jsGanttImproved/jsgantt-improved/master/dist/jsgantt.js -o "%blenderbim%\bim\data\gantt\jsgantt.js"
curl https://raw.githubusercontent.com/jsGanttImproved/jsgantt-improved/master/dist/jsgantt.css -o "%blenderbim%\bim\data\gantt\jsgantt.css"
curl -L https://github.com/BrickSchema/Brick/releases/download/nightly/Brick.ttl -o "%blenderbim%\bim\schema\Brick.ttl"
pause
.. literalinclude:: ../../scripts/installation/dev_environment.bat
:language: bat
:caption: dev_environment.bat
After you modify your code in the Git repository, you will need to restart
Blender for the changes to take effect.
@@ -1,5 +1,8 @@
try:
import bpy
# Ensure it's not fake-bpy-module.
if not hasattr(bpy, "context"):
raise ModuleNotFoundError
import bl_i18n_utils
import addon_utils
@@ -0,0 +1,66 @@
@echo off
rem SETUP BLENDER-BIM LIVE DEVELOPMENT ENVIRONMENT
rem Update BLENDER_VERSION, PYTHON_VERSION, BBIM_REPO in the script bellow.
rem Put the script to the folder where IfcOpenShell git repository is located
rem (script will try to clone IfcOpenShell.git if it's not present).
SET BLENDER_VERSION=4.2
SET PYTHON_VERSION=3.11
SET BBIM_REPO=user_default
SET BLENDER=%appdata%\Blender Foundation\Blender\%BLENDER_VERSION%
SET BLENDER_LOCAL=%BLENDER%\extensions\.local\lib\python%PYTHON_VERSION%\site-packages
SET BLENDERBIM=%BLENDER_LOCAL%\blenderbim
SET BLENDER_EXT=%BLENDER%\extensions\%BBIM_REPO%\blenderbim
git clone https://github.com/IfcOpenShell/IfcOpenShell.git
cd IfcOpenShell
echo Removing the Blender add-on Python code...
del "%BLENDER_EXT%\__init__.py"
rd /S /Q "%BLENDERBIM%"
echo Replacing them with links to the Git repository...
mklink "%BLENDER_EXT%\__init__.py" "%CD%\src\blenderbim\blenderbim\__init__.py"
mklink /D "%BLENDERBIM%" "%CD%\src\blenderbim\blenderbim"
echo Copy over compiled IfcOpenShell files...
copy "%BLENDER_LOCAL%\ifcopenshell\*_wrapper*" "%CD%\src\ifcopenshell-python\ifcopenshell\"
echo Remove the IfcOpenShell dependency...
rd /S /Q "%BLENDER_LOCAL%\ifcopenshell"
echo Replace them with links to the Git repository...
mklink /D "%BLENDER_LOCAL%\ifcopenshell" "%CD%\src\ifcopenshell-python\ifcopenshell"
echo Remove and link other IfcOpenShell utilities...
del "%BLENDER_LOCAL%\ifccsv.py"
del "%BLENDER_LOCAL%\ifcdiff.py"
del "%BLENDER_LOCAL%\bsdd.py"
rd /S /Q "%BLENDER_LOCAL%\bcf"
rd /S /Q "%BLENDER_LOCAL%\ifc4d"
rd /S /Q "%BLENDER_LOCAL%\ifc5d"
rd /S /Q "%BLENDER_LOCAL%\ifccityjson"
rd /S /Q "%BLENDER_LOCAL%\ifcclash"
rd /S /Q "%BLENDER_LOCAL%\ifcpatch"
rd /S /Q "%BLENDER_LOCAL%\ifctester"
rd /S /Q "%BLENDER_LOCAL%\ifcfm"
mklink "%BLENDER_LOCAL%\ifccsv.py" "%CD%\src\ifccsv\ifccsv.py"
mklink "%BLENDER_LOCAL%\ifcdiff.py" "%CD%\src\ifcdiff\ifcdiff.py"
mklink "%BLENDER_LOCAL%\bsdd.py" "%CD%\src\bsdd\bsdd.py"
mklink /D "%BLENDER_LOCAL%\bcf" "%CD%\src\bcf\bcf"
mklink /D "%BLENDER_LOCAL%\ifc4d" "%CD%\src\ifc4d\ifc4d"
mklink /D "%BLENDER_LOCAL%\ifc5d" "%CD%\src\ifc5d\ifc5d"
mklink /D "%BLENDER_LOCAL%\ifccityjson" "%CD%\src\ifccityjson\ifccityjson"
mklink /D "%BLENDER_LOCAL%\ifcclash" "%CD%\src\ifcclash\ifcclash"
mklink /D "%BLENDER_LOCAL%\ifcpatch" "%CD%\src\ifcpatch\ifcpatch"
mklink /D "%BLENDER_LOCAL%\ifctester" "%CD%\src\ifctester\ifctester"
mklink /D "%BLENDER_LOCAL%\ifcfm" "%CD%\src\ifcfm\ifcfm"
echo Manually downloading some third party dependencies...
curl https://raw.githubusercontent.com/jsGanttImproved/jsgantt-improved/master/dist/jsgantt.js -o "%BLENDERBIM%\bim\data\gantt\jsgantt.js"
curl https://raw.githubusercontent.com/jsGanttImproved/jsgantt-improved/master/dist/jsgantt.css -o "%BLENDERBIM%\bim\data\gantt\jsgantt.css"
curl -L https://github.com/BrickSchema/Brick/releases/download/nightly/Brick.ttl -o "%BLENDERBIM%\bim\schema\Brick.ttl"
pause
@@ -0,0 +1,60 @@
git clone https://github.com/IfcOpenShell/IfcOpenShell.git
cd IfcOpenShell
# path to BlenderBIM addon
# default path on Mac: "/Users/$USER/Library/Application Support/Blender/X.X/scripts/addons/blenderbim"
# default path on Linux: "$HOME/.config/blender/X.X/"
BLENDER_ADDON_PATH="/path/to/blender/X.XX/scripts/addons/blenderbim"
# Remove the Blender add-on Python code
rm -r $BLENDER_ADDON_PATH/core/
rm -r $BLENDER_ADDON_PATH/tool/
rm -r $BLENDER_ADDON_PATH/bim/
# Replace them with links to the Git repository
ln -s $PWD/src/blenderbim/blenderbim/core $BLENDER_ADDON_PATH/core
ln -s $PWD/src/blenderbim/blenderbim/tool $BLENDER_ADDON_PATH/tool
ln -s $PWD/src/blenderbim/blenderbim/bim $BLENDER_ADDON_PATH/bim
# Copy over compiled IfcOpenShell files
cp $BLENDER_ADDON_PATH/libs/site/packages/ifcopenshell/*_wrapper* $PWD/src/ifcopenshell-python/ifcopenshell/
# Remove the IfcOpenShell dependency
rm -r $BLENDER_ADDON_PATH/libs/site/packages/ifcopenshell
# Replace them with links to the Git repository
ln -s $PWD/src/ifcopenshell-python/ifcopenshell $BLENDER_ADDON_PATH/libs/site/packages/ifcopenshell
# Remove and link other IfcOpenShell utilities
rm -r $BLENDER_ADDON_PATH/libs/site/packages/ifccsv.py
rm -r $BLENDER_ADDON_PATH/libs/site/packages/ifcdiff.py
rm -r $BLENDER_ADDON_PATH/libs/site/packages/bsdd.py
rm -r $BLENDER_ADDON_PATH/libs/site/packages/bcf
rm -r $BLENDER_ADDON_PATH/libs/site/packages/ifc4d
rm -r $BLENDER_ADDON_PATH/libs/site/packages/ifc5d
rm -r $BLENDER_ADDON_PATH/libs/site/packages/ifccityjson
rm -r $BLENDER_ADDON_PATH/libs/site/packages/ifcclash
rm -r $BLENDER_ADDON_PATH/libs/site/packages/ifcpatch
rm -r $BLENDER_ADDON_PATH/libs/site/packages/ifctester
rm -r $BLENDER_ADDON_PATH/libs/site/packages/ifcfm
rm -r $BLENDER_ADDON_PATH/libs/Desktop
ln -s $PWD/src/ifccsv/ifccsv.py $BLENDER_ADDON_PATH/libs/site/packages/ifccsv.py
ln -s $PWD/src/ifcdiff/ifcdiff.py $BLENDER_ADDON_PATH/libs/site/packages/ifcdiff.py
ln -s $PWD/src/bsdd/bsdd.py $BLENDER_ADDON_PATH/libs/site/packages/bsdd.py
ln -s $PWD/src/bcf/bcf $BLENDER_ADDON_PATH/libs/site/packages/bcf
ln -s $PWD/src/ifc4d/ifc4d $BLENDER_ADDON_PATH/libs/site/packages/ifc4d
ln -s $PWD/src/ifc5d/ifc5d $BLENDER_ADDON_PATH/libs/site/packages/ifc5d
ln -s $PWD/src/ifccityjson/ifccityjson $BLENDER_ADDON_PATH/libs/site/packages/ifccityjson
ln -s $PWD/src/ifcclash/ifcclash $BLENDER_ADDON_PATH/libs/site/packages/ifcclash
ln -s $PWD/src/ifcpatch/ifcpatch $BLENDER_ADDON_PATH/libs/site/packages/ifcpatch
ln -s $PWD/src/ifctester/ifctester $BLENDER_ADDON_PATH/libs/site/packages/ifctester
ln -s $PWD/src/ifcfm/ifcfm $BLENDER_ADDON_PATH/libs/site/packages/ifcfm
ln -s $PWD/src/blenderbim/blenderbim/libs/desktop $BLENDER_ADDON_PATH/libs/Desktop
# Manually download some third party dependencies
cd $BLENDER_ADDON_PATH/bim/data/gantt
wget https://raw.githubusercontent.com/jsGanttImproved/jsgantt-improved/master/dist/jsgantt.js
wget https://raw.githubusercontent.com/jsGanttImproved/jsgantt-improved/master/dist/jsgantt.css
cd $BLENDER_ADDON_PATH/bim/schema
wget https://github.com/BrickSchema/Brick/releases/download/nightly/Brick.ttl
+11
View File
@@ -1,6 +1,8 @@
IS_STABLE:=FALSE
PYTHON:=python3.11
PIP:=pip3.11
VERSION:=$(shell cat ../../VERSION)
VERSION_DATE:=$(shell date '+%y%m%d')
SED:=sed -i
VENV_BIN:=bin
@@ -23,11 +25,20 @@ dist:
mkdir -p dist
cp -r $(PACKAGE_NAME) build/
cp pyproject.toml build/
ifeq ($(IS_STABLE), TRUE)
$(SED) 's/version = "0.0.0"/version = "$(VERSION)"/' build/pyproject.toml
ifdef IS_MODULE
$(SED) 's/version = "0.0.0"/version = "$(VERSION)"/' build/$(PACKAGE_NAME)
else
$(SED) 's/version = "0.0.0"/version = "$(VERSION)"/' build/$(PACKAGE_NAME)/__init__.py
endif
else
$(SED) 's/version = "0.0.0"/version = "$(VERSION)a$(VERSION_DATE)"/' build/pyproject.toml
ifdef IS_MODULE
$(SED) 's/version = "0.0.0"/version = "$(VERSION)-alpha$(VERSION_DATE)"/' build/$(PACKAGE_NAME)
else
$(SED) 's/version = "0.0.0"/version = "$(VERSION)-alpha$(VERSION_DATE)"/' build/$(PACKAGE_NAME)/__init__.py
endif
endif
cd build && $(PYTHON) -m venv env && . env/$(VENV_ACTIVATE) && $(PIP) install build
cd build && . env/$(VENV_ACTIVATE) && $(PYTHON) -m build
@@ -42,7 +42,7 @@ IfcGeom::OpenCascadeKernel::faceset_helper::faceset_helper(
loops.push_back(l);
for (auto& e : l->children) {
// @todo make sure only cartesian points are provided here
auto& p = boost::get<ifcopenshell::geometry::taxonomy::point3::ptr>(e->start);
auto& p = boost::get<ifcopenshell::geometry::taxonomy::point3::ptr>(e->orientation.get_value_or(true) ? e->start : e->end);
if (point_identities_visited.find(p->identity()) == point_identities_visited.end()) {
point_identities_visited.insert(p->identity());
points.push_back(p);
@@ -201,10 +201,10 @@ void IfcGeom::OpenCascadeKernel::faceset_helper::loop_(const ifcopenshell::geome
return;
}
auto a = boost::get<ifcopenshell::geometry::taxonomy::point3::ptr>(ps->children.back()->start);
auto a = boost::get<ifcopenshell::geometry::taxonomy::point3::ptr>(ps->children.back()->orientation.get_value_or(true) ? ps->children.back()->start : ps->children.back()->end);
auto A = a->identity();
for (auto& b : ps->children) {
auto B = boost::get<ifcopenshell::geometry::taxonomy::point3::ptr>(b->start)->identity();
auto B = boost::get<ifcopenshell::geometry::taxonomy::point3::ptr>(b->orientation.get_value_or(true) ? b->start : b->end)->identity();
auto C = vertex_mapping_[A], D = vertex_mapping_[B];
bool fwd = C < D;
if (!b->orientation) {
@@ -52,7 +52,8 @@ taxonomy::ptr mapping::map_impl(const IfcSchema::IfcSectionedSolidHorizontal* in
auto pwf = taxonomy::dcast<taxonomy::piecewise_function>(dir);
if (!pwf) {
// Only implement on alignment curves
return nullptr;
Logger::Warning("IfcSectionedSolidHorizontal is only implemented for piecewise function Directrix curves", inst);
return nullptr;
}
{
@@ -111,6 +112,7 @@ taxonomy::ptr mapping::map_impl(const IfcSchema::IfcSectionedSolidHorizontal* in
loft->axis = nullptr;
// @todo currently only the case is handled where directrix returns a piecewise_function
// @todo this "if" statement is not really required because the function returns at the start if the Directrix is not a piecewise function
if (pwf) {
double start = std::max(0., cross_sections.front().dist_along);
double end = std::min(pwf->length(), cross_sections.back().dist_along);
+10 -2
View File
@@ -341,10 +341,10 @@ namespace {
return item;
}
while (item->declaration().is(IfcSchema::IfcBooleanClippingResult::Class())) {
while (auto booleanresult = item->as<IfcSchema::IfcBooleanClippingResult>()) {
// All instantiations of IfcBooleanOperand (type of FirstOperand) are subtypes of
// IfcGeometricRepresentationItem
item = (IfcSchema::IfcGeometricRepresentationItem*) ((IfcSchema::IfcBooleanClippingResult*) item)->FirstOperand();
item = booleanresult->FirstOperand()->as<IfcSchema::IfcRepresentationItem>();
if (item->StyledByItem()->size()) {
return item;
}
@@ -756,6 +756,14 @@ void mapping::initialize_units_() {
if (!angle_unit_encountered) {
Logger::Warning("No plane angle unit encountered");
}
// @todo move to a more descriptive function
if (settings_.get<settings::BuildingLocalPlacement>().get()) {
placement_rel_to_type_ = file_->schema()->declaration_by_name("IfcBuilding");
}
if (settings_.get<settings::SiteLocalPlacement>().get()) {
placement_rel_to_type_ = file_->schema()->declaration_by_name("IfcSite");
}
}
void mapping::initialize_settings() {
+13 -2
View File
@@ -1,4 +1,6 @@
IS_STABLE:=FALSE
VERSION:=$(shell cat ../../VERSION)
VERSION_DATE:=$(shell date '+%y%m%d')
PYVERSION:=py311
PLATFORM:=linux64
@@ -120,11 +122,20 @@ endif
cp -r build/botbuild/ifcopenshell/*ifcopenshell_wrapper* build/ifcopenshell/
cp ../../README.md build/
cp pyproject.toml build/
ifeq ($(IS_STABLE), TRUE)
$(SED) 's/version = "0.0.0"/version = "$(VERSION)"/' build/pyproject.toml
$(SED) 's/version = "0.0.0"/version = "$(VERSION)"/' build/ifcopenshell/__init__.py
else
$(SED) 's/version = "0.0.0"/version = "$(VERSION)a$(VERSION_DATE)"/' build/pyproject.toml
$(SED) 's/version = "0.0.0"/version = "$(VERSION)-alpha$(VERSION_DATE)"/' build/ifcopenshell/__init__.py
endif
cd build && $(PYTHON) -m venv env && . env/$(VENV_ACTIVATE) && $(PIP) install build
cd build && . env/$(VENV_ACTIVATE) && $(PYTHON) -m build
mv build/dist/ifcopenshell-$(VERSION)-py3-none-any.whl dist/ifcopenshell-$(VERSION)-$(PYVERSION)-none-$(PLATFORMTAG).whl
rm -rf build
ifeq ($(IS_STABLE), TRUE)
mv build/dist/ifcopenshell-*.whl dist/ifcopenshell-$(VERSION)-$(PYVERSION)-none-$(PLATFORMTAG).whl
else
cp build/dist/ifcopenshell-*.whl dist/ifcopenshell-$(VERSION)a$(VERSION_DATE)-$(PYVERSION)-none-$(PLATFORMTAG).whl
endif
# rm -rf build
+2 -2
View File
@@ -81,10 +81,10 @@ autoapi_add_toctree_entry = True
autoapi_type = "python"
# autoapi works by reading source code instead of importing modules
autoapi_dirs = ['../ifcopenshell', '../../bcf/src', '../../bsdd', '../../ifccsv', '../../ifcdiff', '../../ifcpatch/ifcpatch', '../../ifctester/ifctester']
autoapi_dirs = ['../ifcopenshell', '../../bcf/bcf', '../../bsdd', '../../ifccsv', '../../ifcdiff', '../../ifcpatch/ifcpatch', '../../ifctester/ifctester']
# autoapi_dirs = ['../../ifcdiff']
# autoapi_dirs = ['../../ifcdiff', '../ifcopenshell/util']
# autoapi_dirs = ['../../bcf/src', '../../bsdd', '../../ifccsv', '../../ifcdiff', '../../ifcpatch/ifcpatch', '../../ifctester/ifctester']
# autoapi_dirs = ['../../bcf/bcf', '../../bsdd', '../../ifccsv', '../../ifcdiff', '../../ifcpatch/ifcpatch', '../../ifctester/ifctester']
# These are auto-generated based on the IFC schema, so exclude them
autoapi_ignore = ['*ifcopenshell/express/rules*']
@@ -21,6 +21,14 @@
},
{
"name": "Capping"
},
{
"name": "Screed",
"predefined_type": "FLOORING"
},
{
"name": "Screed",
"predefined_type": "TOPPING"
}
],
"IfcDistributionElement": [