diff --git a/.github/workflows/ci-bcf-pypi.yml b/.github/workflows/ci-bcf-pypi.yml index 7fed2dc053..4b70c77e0a 100644 --- a/.github/workflows/ci-bcf-pypi.yml +++ b/.github/workflows/ci-bcf-pypi.yml @@ -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: diff --git a/.github/workflows/ci-blenderbim-daily.yml b/.github/workflows/ci-blenderbim-daily.yml index 7ac376895f..0e4321aece 100644 --- a/.github/workflows/ci-blenderbim-daily.yml +++ b/.github/workflows/ci-blenderbim-daily.yml @@ -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/**' diff --git a/.github/workflows/ci-blenderbim.yml b/.github/workflows/ci-blenderbim.yml index fff54a5909..f683594063 100644 --- a/.github/workflows/ci-blenderbim.yml +++ b/.github/workflows/ci-blenderbim.yml @@ -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 diff --git a/.github/workflows/ci-bsdd-pypi.yaml b/.github/workflows/ci-bsdd-pypi.yaml index 67da096296..a1f68bf236 100644 --- a/.github/workflows/ci-bsdd-pypi.yaml +++ b/.github/workflows/ci-bsdd-pypi.yaml @@ -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: diff --git a/.github/workflows/ci-ifc4d-pypi.yaml b/.github/workflows/ci-ifc4d-pypi.yaml index 79707a5881..d2fa648ddb 100644 --- a/.github/workflows/ci-ifc4d-pypi.yaml +++ b/.github/workflows/ci-ifc4d-pypi.yaml @@ -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: diff --git a/.github/workflows/ci-ifc5d-pypi.yaml b/.github/workflows/ci-ifc5d-pypi.yaml index b626316267..c4669e1af2 100644 --- a/.github/workflows/ci-ifc5d-pypi.yaml +++ b/.github/workflows/ci-ifc5d-pypi.yaml @@ -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: diff --git a/.github/workflows/ci-ifccityjson-pypi.yaml b/.github/workflows/ci-ifccityjson-pypi.yaml index 2003acf169..f3c921b094 100644 --- a/.github/workflows/ci-ifccityjson-pypi.yaml +++ b/.github/workflows/ci-ifccityjson-pypi.yaml @@ -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: diff --git a/.github/workflows/ci-ifcclash-pypi.yaml b/.github/workflows/ci-ifcclash-pypi.yaml index bd2875e138..e53c12ab67 100644 --- a/.github/workflows/ci-ifcclash-pypi.yaml +++ b/.github/workflows/ci-ifcclash-pypi.yaml @@ -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: diff --git a/.github/workflows/ci-ifccsv-pypi.yaml b/.github/workflows/ci-ifccsv-pypi.yaml index 50803918d7..d1a40f94ab 100644 --- a/.github/workflows/ci-ifccsv-pypi.yaml +++ b/.github/workflows/ci-ifccsv-pypi.yaml @@ -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: diff --git a/.github/workflows/ci-ifcdiff-pypi.yaml b/.github/workflows/ci-ifcdiff-pypi.yaml index 654357409b..6fc88a6059 100644 --- a/.github/workflows/ci-ifcdiff-pypi.yaml +++ b/.github/workflows/ci-ifcdiff-pypi.yaml @@ -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: diff --git a/.github/workflows/ci-ifcfm-pypi.yaml b/.github/workflows/ci-ifcfm-pypi.yaml index 177ba6d01e..576d150dc4 100644 --- a/.github/workflows/ci-ifcfm-pypi.yaml +++ b/.github/workflows/ci-ifcfm-pypi.yaml @@ -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: diff --git a/.github/workflows/ci-ifcopenshell-python-pypi.yml b/.github/workflows/ci-ifcopenshell-python-pypi.yml index 6a87361157..73199521bb 100644 --- a/.github/workflows/ci-ifcopenshell-python-pypi.yml +++ b/.github/workflows/ci-ifcopenshell-python-pypi.yml @@ -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: diff --git a/.github/workflows/ci-ifcpatch-pypi.yaml b/.github/workflows/ci-ifcpatch-pypi.yaml index 2b39a77bea..cdeb78c27d 100644 --- a/.github/workflows/ci-ifcpatch-pypi.yaml +++ b/.github/workflows/ci-ifcpatch-pypi.yaml @@ -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: diff --git a/.github/workflows/ci-ifctester-pypi.yml b/.github/workflows/ci-ifctester-pypi.yml index f7e29ac111..51835418e7 100644 --- a/.github/workflows/ci-ifctester-pypi.yml +++ b/.github/workflows/ci-ifctester-pypi.yml @@ -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: diff --git a/src/blenderbim/Makefile b/src/blenderbim/Makefile index 1c0976a156..f1e4f1a01f 100644 --- a/src/blenderbim/Makefile +++ b/src/blenderbim/Makefile @@ -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/ diff --git a/src/blenderbim/blenderbim/__init__.py b/src/blenderbim/blenderbim/__init__.py index 37e778d398..62d3a49d30 100644 --- a/src/blenderbim/blenderbim/__init__.py +++ b/src/blenderbim/blenderbim/__init__.py @@ -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"^(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)(?:-(?P(?: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[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" diff --git a/src/blenderbim/blenderbim/bim/__init__.py b/src/blenderbim/blenderbim/bim/__init__.py index 3f1808562d..51f2c02614 100644 --- a/src/blenderbim/blenderbim/bim/__init__.py +++ b/src/blenderbim/blenderbim/bim/__init__.py @@ -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() diff --git a/src/blenderbim/blenderbim/bim/data/webui/sioserver.py b/src/blenderbim/blenderbim/bim/data/webui/sioserver.py index d367c4d579..fdbef0b50c 100644 --- a/src/blenderbim/blenderbim/bim/data/webui/sioserver.py +++ b/src/blenderbim/blenderbim/bim/data/webui/sioserver.py @@ -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(): diff --git a/src/blenderbim/blenderbim/bim/data/webui/static/css/gantt.css b/src/blenderbim/blenderbim/bim/data/webui/static/css/gantt.css index 82354d6760..1256b09080 100644 --- a/src/blenderbim/blenderbim/bim/data/webui/static/css/gantt.css +++ b/src/blenderbim/blenderbim/bim/data/webui/static/css/gantt.css @@ -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); } \ No newline at end of file diff --git a/src/blenderbim/blenderbim/bim/data/webui/static/css/index.css b/src/blenderbim/blenderbim/bim/data/webui/static/css/index.css index ddcf94cace..7a480b42c8 100644 --- a/src/blenderbim/blenderbim/bim/data/webui/static/css/index.css +++ b/src/blenderbim/blenderbim/bim/data/webui/static/css/index.css @@ -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; } \ No newline at end of file diff --git a/src/blenderbim/blenderbim/bim/data/webui/static/js/gantt.js b/src/blenderbim/blenderbim/bim/data/webui/static/js/gantt.js index b318aa0788..e822c600d8 100644 --- a/src/blenderbim/blenderbim/bim/data/webui/static/js/gantt.js +++ b/src/blenderbim/blenderbim/bim/data/webui/static/js/gantt.js @@ -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 = $("
") @@ -101,6 +115,13 @@ function addGanttElement(blenderId, tasks, workSched, filename) { .addClass("no-print") .css("margin-bottom", "10px"); + const warning = $("
") + .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 = $("
").attr("id", "workSched" + blenderId); const scheduleTable = $("
") @@ -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(''); + } else { + $("html").removeClass("light").addClass("dark"); + $("#toggle-theme").html(''); + } + localStorage.setItem("theme", theme); +} + +function toggleTheme() { + if ($("html").hasClass("dark")) { + setTheme("light"); + } else { + setTheme("dark"); + } +} diff --git a/src/blenderbim/blenderbim/bim/data/webui/static/js/index.js b/src/blenderbim/blenderbim/bim/data/webui/static/js/index.js index 39e2bee442..0d4db42480 100644 --- a/src/blenderbim/blenderbim/bim/data/webui/static/js/index.js +++ b/src/blenderbim/blenderbim/bim/data/webui/static/js/index.js @@ -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 = $("
") + .attr("id", "warning-" + blenderId) + .html( + "⚠ Warning: This table may contain outdated data due to recent changes in Blender." + ) + .addClass("warning"); + const tableDiv = $("
") .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(''); + } else { + stylesheet.attr( + "href", + "https://unpkg.com/tabulator-tables/dist/css/tabulator_site_dark.min.css" + ); + $("html").removeClass("light").addClass("dark"); + $("#toggle-theme").html(''); + } + localStorage.setItem("theme", theme); +} + +function toggleTheme() { + if ($("html").hasClass("dark")) { + setTheme("light"); + } else { + setTheme("dark"); + } +} diff --git a/src/blenderbim/blenderbim/bim/data/webui/templates/gantt.html b/src/blenderbim/blenderbim/bim/data/webui/templates/gantt.html index 7f0d22fbb8..78044d45a3 100644 --- a/src/blenderbim/blenderbim/bim/data/webui/templates/gantt.html +++ b/src/blenderbim/blenderbim/bim/data/webui/templates/gantt.html @@ -1,11 +1,15 @@ - + Web Client +