mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-10 17:58:20 +00:00
Merge branch 'v0.8.0' of https://github.com/IfcOpenShell/IfcOpenShell into v0.8.0
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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/**'
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
@@ -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/
|
||||
|
||||
@@ -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(
|
||||
"⚠ 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(
|
||||
"⚠ 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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
|
||||
import os
|
||||
import bpy
|
||||
import addon_utils
|
||||
import platform
|
||||
from pathlib import Path
|
||||
from bpy.types import Panel
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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"))
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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);
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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": [
|
||||
|
||||
Reference in New Issue
Block a user