Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9aba274070 | |||
| 3b7d44a1c6 | |||
| bf722ecfe3 | |||
| 61cff3df43 | |||
| 8b4b1da8a4 | |||
| 69997bf9d8 | |||
| 53907accd9 | |||
| a4f881517b | |||
| 8acaf0ff26 | |||
| 105bfb5647 | |||
| 866e3b0f66 | |||
| 79d6801315 | |||
| a2539b659f | |||
| 3e154b6344 | |||
| c1fa735f9c | |||
| 9e5f73701c | |||
| 7fa8ebfbd1 | |||
| d47e5c1167 | |||
| d142fa5e62 | |||
| 27ef2b5d49 |
@@ -22,7 +22,7 @@ jobs:
|
||||
- uses: actions/checkout@v2 # https://github.com/actions/checkout
|
||||
- uses: actions/setup-python@v2 # https://github.com/actions/setup-python
|
||||
with:
|
||||
python-version: '3.11' # Version range or exact version of a Python version to use, using SemVer's version range syntax
|
||||
python-version: '3.10' # Version range or exact version of a Python version to use, using SemVer's version range syntax
|
||||
architecture: 'x64' # optional x64 or x86. Defaults to x64 if not specified
|
||||
- run: echo ${{ env.DATE }}
|
||||
- name: Get current date
|
||||
|
||||
@@ -142,12 +142,6 @@ jobs:
|
||||
sverchok_zip="$(pwd)/dist/$(ls dist)"
|
||||
blender --command extension install-file -r user_default $sverchok_zip
|
||||
|
||||
# Install Sun Position extension.
|
||||
blender --online-mode --command extension sync
|
||||
blender --online-mode --background --python-expr "import bpy; \
|
||||
bpy.ops.extensions.package_install(repo_index=0, pkg_id='sun_position'); \
|
||||
bpy.ops.preferences.addon_enable(module='bl_ext.blender_org.sun_position'); bpy.ops.wm.save_userpref()"
|
||||
|
||||
cd ../bonsai
|
||||
pip install pytest-blender
|
||||
blender --background --python scripts/setup_pytest.py
|
||||
|
||||
@@ -24,7 +24,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
pyver: [py311, py312]
|
||||
pyver: [py310, py311, py312]
|
||||
config:
|
||||
- {
|
||||
name: "Windows Build",
|
||||
@@ -32,15 +32,15 @@ jobs:
|
||||
}
|
||||
- {
|
||||
name: "Linux Build",
|
||||
short_name: linux,
|
||||
short_name: linux
|
||||
}
|
||||
- {
|
||||
name: "MacOS Build",
|
||||
short_name: macos,
|
||||
short_name: macos
|
||||
}
|
||||
- {
|
||||
name: "MacOS ARM Build",
|
||||
short_name: macosm1,
|
||||
short_name: macosm1
|
||||
}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
@@ -53,7 +53,8 @@ jobs:
|
||||
run: echo "version=$(cat VERSION)" >> $GITHUB_OUTPUT
|
||||
- name: Compile
|
||||
run: |
|
||||
cd src/bonsai && make dist PLATFORM=${{ matrix.config.short_name }} PYVERSION=${{ matrix.pyver }} IS_STABLE=TRUE
|
||||
cd src/bonsai &&
|
||||
make dist PLATFORM=${{ matrix.config.short_name }} PYVERSION=${{ matrix.pyver }} IS_STABLE=TRUE
|
||||
- name: Find zip file name
|
||||
id: find_zip
|
||||
run: |
|
||||
|
||||
@@ -93,7 +93,7 @@ jobs:
|
||||
lfs: true
|
||||
|
||||
- name: Download
|
||||
uses: actions/download-artifact@v4.1.7
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
# Artifact name
|
||||
name: ifcos-artifacts
|
||||
|
||||
@@ -73,6 +73,9 @@ src/ifcopenshell-python/test/build
|
||||
# mypy cache
|
||||
.mypy_cache
|
||||
|
||||
# bonsai libs
|
||||
src/bonsai/bonsai/libs
|
||||
|
||||
# bonsai i18n
|
||||
src/bonsai/bonsai/translations.py
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ is implemented for the IFC releases [IFC2x3 TC1] and [IFC4 Add2 TC1]. Extending
|
||||
is possible at compile-time when using C++ and at run-time when using Python.
|
||||
|
||||
In addition to a C++ and Python API, IfcOpenShell comes with an ecosystem of tools, notably including IfcConvert (an application
|
||||
to convert IFC models to other formats), Bonsai (an add-on to Blender providing a graphical IFC authoring platform),
|
||||
to convert IFC models to other formats), the BlenderBIM Add-on (an add-on to Blender providing a graphical IFC authoring platform),
|
||||
and many other libraries, CLI apps, and more. Support is also provided for auxiliary standards such as BCF and IDS.
|
||||
|
||||
For more information, see:
|
||||
@@ -24,8 +24,8 @@ For more information, see:
|
||||
* [IfcOpenShell Python Hello World Tutorial](https://docs.ifcopenshell.org/ifcopenshell-python/hello_world.html)
|
||||
* [Bonsai Website](https://bonsaibim.org)
|
||||
* [Bonsai Documentation](https://docs.bonsaibim.org/index.html)
|
||||
* [Add-on Installation](https://docs.bonsaibim.org/quickstart/installation.html)
|
||||
* [Exploring an IFC model](https://docs.bonsaibim.org/quickstart/explore_model.html)
|
||||
* [Add-on Installation](https://docs.bonsaibim.org/users/installation.html)
|
||||
* [Exploring an IFC model](https://docs.bonsaibim.org/users/exploring_an_ifc_model.html)
|
||||
|
||||
Development is sponsored through your generous donations!
|
||||
|
||||
@@ -37,7 +37,7 @@ Contents
|
||||
| Name | Description | License | Service |
|
||||
| ------------------------- | --------------------------------------------------------------------- | ------------------- | ------- |
|
||||
| bcf | Library to read and write BCF-XML and query OpenCDE BCF-API modules | LGPL-3.0-or-later | [](https://pypi.org/project/bcf-client/) |
|
||||
| bonsai | Add-on to Blender providing a graphical native IFC authoring platform | GPL-3.0-or-later | [](https://bonsaibim.org/download.html) [](https://github.com/IfcOpenShell/IfcOpenShell/releases?q=bonsai&expanded=true) [](https://community.chocolatey.org/packages/blenderbim-nightly/) |
|
||||
| blenderbim | Add-on to Blender providing a graphical native IFC authoring platform | GPL-3.0-or-later | [](https://bonsaibim.org/download.html) [](https://github.com/IfcOpenShell/IfcOpenShell/releases?q=blenderbim&expanded=true) [](https://community.chocolatey.org/packages/blenderbim-nightly/) |
|
||||
| bsdd | Library to query the bSDD API | LGPL-3.0-or-later | [](https://pypi.org/project/bsdd/) |
|
||||
| ifc2ca | Utility to convert IFC structural analysis models to Code_Aster | LGPL-3.0-or-later |
|
||||
| ifc4d | Convert to and from IFC and project management software | LGPL-3.0-or-later | [](https://pypi.org/project/ifc4d/) |
|
||||
|
||||
@@ -29,7 +29,7 @@ endif()
|
||||
|
||||
cmake_policy(SET CMP0048 NEW)
|
||||
cmake_policy(SET CMP0074 NEW)
|
||||
cmake_policy(SET CMP0078 NEW)
|
||||
cmake_policy(SET CMP0078 OLD)
|
||||
cmake_policy(SET CMP0086 NEW)
|
||||
if (POLICY CMP0144)
|
||||
cmake_policy(SET CMP0144 NEW) # find_package() uses upper-case <PACKAGENAME>_ROOT variables.
|
||||
@@ -730,10 +730,6 @@ if(MSVC)
|
||||
# endif()
|
||||
|
||||
add_definitions(-D_ENABLE_EXTENDED_ALIGNED_STORAGE)
|
||||
# See #5158.
|
||||
if(CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 19.40)
|
||||
add_definitions(-D_DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR)
|
||||
endif()
|
||||
else()
|
||||
add_definitions(-Wall -Wextra)
|
||||
|
||||
|
||||
@@ -81,7 +81,7 @@ ADD_COMMIT_SHA = os.getenv("ADD_COMMIT_SHA")
|
||||
PYTHON_VERSIONS = ["3.9.11", "3.10.3", "3.11.8", "3.12.1"]
|
||||
JSON_VERSION = "v3.6.1"
|
||||
OCE_VERSION = "0.18.3"
|
||||
OCCT_VERSION = "7.7.2"
|
||||
OCCT_VERSION = "7.7.1"
|
||||
BOOST_VERSION = "1.80.0"
|
||||
PCRE_VERSION = "8.41"
|
||||
LIBXML2_VERSION = "2.9.11"
|
||||
@@ -588,9 +588,6 @@ if USE_OCCT and "occ" in targets:
|
||||
if OCCT_VERSION == "7.7.1":
|
||||
patches.append("./patches/occt/no_ExpToCasExe.patch")
|
||||
|
||||
if OCCT_VERSION == "7.7.2":
|
||||
patches.append("./patches/occt/no_ExpToCasExe_7_7_2.patch")
|
||||
|
||||
if "wasm" in flags:
|
||||
patches.append("./patches/occt/no_em_js.patch")
|
||||
|
||||
@@ -966,7 +963,7 @@ if "IfcOpenShell-Python" in targets:
|
||||
|
||||
logger.info(f"\rBuilding python {python_version} wrapper... ")
|
||||
|
||||
run([make, f"-j{IFCOS_NUM_BUILD_PROCS}", "ifcopenshell_wrapper"], cwd=python_dir)
|
||||
run([make, f"-j{IFCOS_NUM_BUILD_PROCS}", "_ifcopenshell_wrapper"], cwd=python_dir)
|
||||
run([make, "install/local"], cwd=os.path.join(python_dir, "ifcwrap"))
|
||||
|
||||
if python_executable:
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||
index 1bacca1a48..11f931ad39 100644
|
||||
--- a/CMakeLists.txt
|
||||
+++ b/CMakeLists.txt
|
||||
@@ -820,6 +820,8 @@ else()
|
||||
OCCT_CHECK_AND_UNSET ("3RDPARTY_DOT_EXECUTABLE")
|
||||
endif()
|
||||
|
||||
+list (REMOVE_ITEM BUILD_TOOLKITS ExpToCasExe)
|
||||
+
|
||||
# bison
|
||||
if (BUILD_YACCLEX)
|
||||
OCCT_INCLUDE_CMAKE_FILE ("adm/cmake/bison")
|
||||
@@ -4,7 +4,7 @@ include = '''
|
||||
src/(
|
||||
bcf
|
||||
|bcfserver
|
||||
|bonsai
|
||||
|blenderbim
|
||||
|bsdd
|
||||
|foundationserver
|
||||
|ifc2ca
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
import bcf.v2.model
|
||||
import bcf.v3.model
|
||||
from typing import Union
|
||||
|
||||
BimSnippet = Union[bcf.v2.model.BimSnippet, bcf.v3.model.BimSnippet]
|
||||
BitMap = Union[bcf.v2.model.VisualizationInfoBitmap, bcf.v3.model.Bitmap]
|
||||
DocumentReference = Union[bcf.v2.model.TopicDocumentReference, bcf.v3.model.DocumentReference]
|
||||
HeaderFile = Union[bcf.v2.model.HeaderFile, bcf.v3.model.File]
|
||||
Topic = Union[bcf.v2.model.Topic, bcf.v3.model.Topic]
|
||||
ViewPoint = Union[bcf.v2.model.ViewPoint, bcf.v3.model.ViewPoint]
|
||||
@@ -1,101 +0,0 @@
|
||||
import tempfile
|
||||
import bcf.v2.bcfxml
|
||||
import bcf.v2.model
|
||||
import bcf.v2.topic
|
||||
import bcf.v3.bcfxml
|
||||
import bcf.v3.model
|
||||
import bcf.v3.topic
|
||||
import bcf.agnostic.model as mdl
|
||||
from pathlib import Path
|
||||
from typing import Union, Optional
|
||||
from typing_extensions import assert_never
|
||||
|
||||
TopicHandler = Union[bcf.v2.topic.TopicHandler, bcf.v3.topic.TopicHandler]
|
||||
|
||||
|
||||
def extract_file(
|
||||
topic: TopicHandler,
|
||||
entity: Union[mdl.HeaderFile, mdl.BimSnippet, mdl.DocumentReference, mdl.BitMap],
|
||||
bcfxml: Optional[Union[bcf.v2.bcfxml.BcfXml, bcf.v3.bcfxml.BcfXml]] = None,
|
||||
outfile: Optional[Path] = None,
|
||||
) -> Union[Path, str, None]:
|
||||
"""Extracts an element with a file into a temporary directory
|
||||
|
||||
These include header files, bim snippets, document references, and
|
||||
viewpoint bitmaps. External reference are not downloaded. Instead, the
|
||||
URI reference is returned.
|
||||
|
||||
:param entity: The entity with a file reference to extract
|
||||
:param outfile: If provided, save the header file to that location.
|
||||
Otherwise, a temporary directory is created and the filename is
|
||||
derived from the header's original filename.
|
||||
:param bcfxml: The BCF XML file to use for resolving document references files.
|
||||
Required only for BCF v3 document references (in BCF v3 internal documents
|
||||
are stored at BCF root, not in the topic).
|
||||
:return: The filepath of the extracted file. It may be a URL if the
|
||||
header file is external.
|
||||
"""
|
||||
if isinstance(entity, mdl.DocumentReference):
|
||||
if isinstance(entity, bcf.v2.model.TopicDocumentReference):
|
||||
reference = entity.referenced_document
|
||||
else:
|
||||
reference = entity.document_guid
|
||||
else:
|
||||
reference = entity.reference
|
||||
|
||||
if not reference:
|
||||
return None
|
||||
|
||||
# For v3 document references external documents are detected by empty document_guid.
|
||||
# External bitmaps are not supported by bcf.
|
||||
if not isinstance(entity, (bcf.v3.model.DocumentReference, mdl.BitMap)) and entity.is_external:
|
||||
return reference
|
||||
|
||||
if isinstance(entity, bcf.v3.model.DocumentReference):
|
||||
# Extract document reference filename and contents.
|
||||
if not bcfxml:
|
||||
raise TypeError("bcfxml is required for BCF v3 document references.")
|
||||
assert isinstance(bcfxml, bcf.v3.bcfxml.BcfXml)
|
||||
error_msg = f"BCF XML is missing document with guid '{reference}'."
|
||||
if not bcfxml.documents:
|
||||
raise Exception(error_msg)
|
||||
definition_docs = bcfxml.documents.definition.documents
|
||||
if not definition_docs:
|
||||
raise Exception(error_msg)
|
||||
docs = next((doc for doc in definition_docs.document if doc.guid == reference), None)
|
||||
if not docs:
|
||||
raise Exception(error_msg)
|
||||
filename = docs.filename
|
||||
bytes_data = bcfxml.documents.documents[filename]
|
||||
else:
|
||||
if isinstance(entity, mdl.BimSnippet):
|
||||
bytes_data = topic.bim_snippet
|
||||
assert isinstance(bytes_data, bytes)
|
||||
elif isinstance(entity, mdl.HeaderFile):
|
||||
bytes_data = topic.reference_files[reference]
|
||||
elif isinstance(entity, mdl.BitMap):
|
||||
bytes_data = next(
|
||||
byte_data
|
||||
for vp in topic.viewpoints.values()
|
||||
for data_reference, byte_data in vp.bitmaps.items()
|
||||
if data_reference == reference
|
||||
)
|
||||
elif isinstance(entity, bcf.v2.model.TopicDocumentReference):
|
||||
assert isinstance(topic, bcf.v2.topic.TopicHandler)
|
||||
bytes_data = topic.document_references[reference]
|
||||
else:
|
||||
assert_never(entity)
|
||||
|
||||
# We don't really need it if 'outfile' is None, just keeping type checker happy.
|
||||
if isinstance(entity, mdl.HeaderFile) and entity.filename:
|
||||
filename = entity.filename
|
||||
else:
|
||||
filename = Path(reference).name
|
||||
|
||||
if not outfile:
|
||||
outfile = Path(tempfile.mkdtemp()) / filename
|
||||
|
||||
with open(outfile, "wb") as f:
|
||||
f.write(bytes_data)
|
||||
|
||||
return outfile
|
||||
@@ -1,5 +0,0 @@
|
||||
import bcf.v2.visinfo
|
||||
import bcf.v3.visinfo
|
||||
from typing import Union
|
||||
|
||||
VisualizationInfoHandler = Union[bcf.v2.visinfo.VisualizationInfoHandler, bcf.v3.visinfo.VisualizationInfoHandler]
|
||||
@@ -63,21 +63,11 @@ class TopicHandler:
|
||||
"""Return the header of the topic."""
|
||||
return self.markup.header if self.markup else None
|
||||
|
||||
@header.setter
|
||||
def header(self, header: mdl.Header) -> None:
|
||||
"""Set the header of the topic."""
|
||||
self.markup.header = header
|
||||
|
||||
@property
|
||||
def comments(self) -> list[mdl.Comment]:
|
||||
"""Return the comments of the topic."""
|
||||
return self.markup.comment if self.markup else []
|
||||
|
||||
@comments.setter
|
||||
def comments(self, comments: list[mdl.Comment]) -> None:
|
||||
assert self.markup
|
||||
self.markup.comment = comments
|
||||
|
||||
@property
|
||||
def bim_snippet(self) -> Optional[bytes]:
|
||||
if not self._bim_snippet and self._topic_dir:
|
||||
@@ -240,6 +230,54 @@ class TopicHandler:
|
||||
real_path = real_path.parent if path_part == ".." else real_path.joinpath(path_part)
|
||||
destination_zip.writestr(real_path.at, self.document_references[doc.referenced_document])
|
||||
|
||||
def extract_file(
|
||||
self, entity: Union[mdl.HeaderFile, mdl.BimSnippet, mdl.TopicDocumentReference], outfile: Optional[Path] = None
|
||||
) -> Union[Path, str, None]:
|
||||
"""Extracts an element with a file into a temporary directory
|
||||
|
||||
These include header files, bim snippets, document references, and
|
||||
viewpoint bitmaps. External reference are not downloaded. Instead, the
|
||||
URI reference is returned.
|
||||
|
||||
:param entity: The entity with a file reference to extract
|
||||
:param outfile: If provided, save the header file to that location.
|
||||
Otherwise, a temporary directory is created and the filename is
|
||||
derived from the header's original filename.
|
||||
:return: The filepath of the extracted file. It may be a URL if the
|
||||
header file is external.
|
||||
:rtype: Path
|
||||
"""
|
||||
if hasattr(entity, "reference"):
|
||||
reference = entity.reference
|
||||
else:
|
||||
reference = entity.referenced_document
|
||||
|
||||
if not reference:
|
||||
return
|
||||
|
||||
if getattr(entity, "is_external", False):
|
||||
return entity.reference
|
||||
|
||||
resolved_reference = self._topic_dir
|
||||
|
||||
for part in Path(reference).parts:
|
||||
if part == "..":
|
||||
resolved_reference = resolved_reference.parent
|
||||
else:
|
||||
resolved_reference = resolved_reference.joinpath(part)
|
||||
|
||||
if not outfile:
|
||||
if getattr(entity, "filename", None):
|
||||
filename = entity.filename
|
||||
else:
|
||||
filename = resolved_reference.name
|
||||
outfile = Path(tempfile.mkdtemp()) / filename
|
||||
|
||||
with open(outfile, "wb") as f:
|
||||
f.write(resolved_reference.read_bytes())
|
||||
|
||||
return outfile
|
||||
|
||||
def add_viewpoint(self, element: entity_instance) -> VisualizationInfoHandler:
|
||||
"""Add a viewpoint pointed at the placement of an IFC element to the topic.
|
||||
|
||||
|
||||
@@ -27,7 +27,6 @@ class TopicHandler:
|
||||
) -> None:
|
||||
self._markup: Optional[mdl.Markup] = None
|
||||
self._viewpoints: Optional[dict[str, VisualizationInfoHandler]] = None
|
||||
self._reference_files: Optional[dict[str, bytes]] = None
|
||||
self._bim_snippet: Optional[bytes] = None
|
||||
self._xml_handler = xml_handler or XmlParserSerializer()
|
||||
self._topic_dir = topic_dir
|
||||
@@ -50,36 +49,22 @@ class TopicHandler:
|
||||
return self.markup.topic
|
||||
|
||||
@property
|
||||
def guid(self) -> str:
|
||||
def guid(self) -> Optional[str]:
|
||||
"""Return the GUID of the topic."""
|
||||
if self._markup:
|
||||
return self.topic.guid
|
||||
return self._topic_dir.name if self._topic_dir else ""
|
||||
return self._topic_dir.name if self._topic_dir else None
|
||||
|
||||
@property
|
||||
def header(self) -> Optional[mdl.Header]:
|
||||
"""Return the header of the topic."""
|
||||
return self.markup.header
|
||||
|
||||
@header.setter
|
||||
def header(self, header: mdl.Header) -> None:
|
||||
"""Set the header of the topic."""
|
||||
self.markup.header = header
|
||||
|
||||
@property
|
||||
def comments(self) -> list[mdl.Comment]:
|
||||
"""Return the comments of the topic."""
|
||||
return self.topic.comments.comment if self.topic.comments else []
|
||||
|
||||
@comments.setter
|
||||
def comments(self, comments: list[mdl.Comment]) -> None:
|
||||
topic_comments = self.topic.comments
|
||||
if topic_comments is None:
|
||||
if not comments:
|
||||
return
|
||||
self.topic.comments = (topic_comments := mdl.TopicComments())
|
||||
topic_comments.comment = comments
|
||||
|
||||
@property
|
||||
def bim_snippet(self) -> Optional[bytes]:
|
||||
if not self._bim_snippet and self._topic_dir:
|
||||
@@ -109,27 +94,6 @@ class TopicHandler:
|
||||
return bim_snippet_path.read_bytes()
|
||||
return None
|
||||
|
||||
@property
|
||||
def reference_files(self) -> dict[str, bytes]:
|
||||
if self._reference_files is not None:
|
||||
return self._reference_files
|
||||
|
||||
self._reference_files = {}
|
||||
if not self.header:
|
||||
return self._reference_files
|
||||
|
||||
if not self.header.files:
|
||||
return self._reference_files
|
||||
|
||||
for ref in self.header.files.file:
|
||||
if ref.is_external:
|
||||
continue
|
||||
real_path = self._topic_dir
|
||||
for path_part in ref.reference.split("/"):
|
||||
real_path = real_path.parent if path_part == ".." else real_path.joinpath(path_part)
|
||||
self._reference_files[ref.reference] = real_path.read_bytes()
|
||||
return self._reference_files
|
||||
|
||||
@classmethod
|
||||
def create_new(
|
||||
cls,
|
||||
@@ -181,7 +145,6 @@ class TopicHandler:
|
||||
self._save_xml(destination_zip, self._markup, "markup.bcf")
|
||||
self._save_viewpoints(destination_zip, topic_dir)
|
||||
self._save_bim_snippet(destination_zip)
|
||||
self._save_reference_files(destination_zip)
|
||||
|
||||
def _save_viewpoints(self, destination_zip: ZipFileInterface, topic_dir: str) -> None:
|
||||
if not self.topic.viewpoints or not (viewpoints := self.topic.viewpoints.view_point):
|
||||
@@ -202,19 +165,6 @@ class TopicHandler:
|
||||
if self.bim_snippet:
|
||||
destination_zip.writestr(f"{self.topic.guid}/{ref_filename}", self.bim_snippet)
|
||||
|
||||
def _save_reference_files(self, destination_zip: ZipFileInterface) -> None:
|
||||
if not self.header:
|
||||
return
|
||||
if not self.header.files:
|
||||
return
|
||||
for ref in self.header.files.file:
|
||||
if ref.is_external or not ref.reference:
|
||||
continue
|
||||
real_path = self._topic_dir
|
||||
for path_part in ref.reference.split("/"):
|
||||
real_path = real_path.parent if path_part == ".." else real_path.joinpath(path_part)
|
||||
destination_zip.writestr(real_path.at, self.reference_files[ref.reference])
|
||||
|
||||
def add_viewpoint(self, element: entity_instance) -> VisualizationInfoHandler:
|
||||
"""
|
||||
Add a viewpoint tergeting an IFC element to the topic.
|
||||
|
||||
@@ -81,12 +81,9 @@ BLENDER_PLATFORM:=windows-x64
|
||||
endif
|
||||
|
||||
# Current build commit hash.
|
||||
OLD:=f5e02d1
|
||||
OLD:=d51fa2c
|
||||
.PHONY: bump
|
||||
bump:
|
||||
ifndef NEW
|
||||
$(error ERROR: 'NEW' variable is not set (should be set with new commmit hash). Example use for 'bump' command: 'make bump NEW=0123456'.)
|
||||
endif
|
||||
cd . && $(SED) -b "s/$(OLD)/$(NEW)/" Makefile
|
||||
cd ../ifcopenshell-python/ && $(SED) -b "s/$(OLD)/$(NEW)/" Makefile
|
||||
|
||||
@@ -133,8 +130,6 @@ endif
|
||||
cd build && . env/$(VENV_ACTIVATE) && $(PIP) wheel odfpy --wheel-dir=./wheels
|
||||
# Required by IFCCityJSON
|
||||
cd build && . env/$(VENV_ACTIVATE) && $(PIP) download cjio --dest=./wheels
|
||||
# Required in general for sorting all sorts of stuff in a nice way
|
||||
cd build && . env/$(VENV_ACTIVATE) && $(PIP) download natsort --dest=./wheels
|
||||
# Provides express rule validation for ifcopenshell.validate
|
||||
cd build && . env/$(VENV_ACTIVATE) && $(PIP) download pytest --dest=./wheels
|
||||
# Provides Brickschema functionality
|
||||
|
||||
@@ -56,10 +56,9 @@ bbim_semver: dict[str, Any] = {}
|
||||
|
||||
# Accessed from bonsai dependency:
|
||||
last_error = None
|
||||
last_actions: deque = deque(maxlen=20)
|
||||
last_actions: deque = deque(maxlen=10)
|
||||
FIRST_INSTALLED_BBIM_VERSION: Union[str, None] = None
|
||||
REINSTALLED_BBIM_VERSION: Union[str, None] = None
|
||||
REGISTERED_BBIM_PACKAGE: str
|
||||
|
||||
|
||||
def initialize_bbim_semver():
|
||||
@@ -239,10 +238,6 @@ if IN_BLENDER:
|
||||
if platform.system() == "Windows":
|
||||
clean_up_dlls_safe_links()
|
||||
|
||||
import bonsai
|
||||
|
||||
bonsai.REGISTERED_BBIM_PACKAGE = __package__
|
||||
|
||||
import bonsai.bim
|
||||
|
||||
current_version = bbim_semver["version"]
|
||||
@@ -329,7 +324,7 @@ if IN_BLENDER:
|
||||
|
||||
layout.operator("bim.copy_debug_information", text="Copy Error Message To Clipboard")
|
||||
op = layout.operator("bim.open_uri", text="How Can I Fix This?")
|
||||
op.uri = "https://docs.bonsaibim.org/guides/troubleshooting.html#installation-issues"
|
||||
op.uri = "https://docs.bonsaibim.org/users/troubleshooting.html#installation-issues"
|
||||
|
||||
layout.label(text="Try Reinstalling:", icon="IMPORT")
|
||||
op = layout.operator("bim.open_uri", text="Re-download Add-on")
|
||||
|
||||
@@ -144,7 +144,7 @@ classes = [
|
||||
# Project overview
|
||||
ui.BIM_PT_tab_new_project_wizard,
|
||||
ui.BIM_PT_tab_project_info,
|
||||
ui.BIM_PT_tab_spatial,
|
||||
ui.BIM_PT_tab_spatial_decomposition,
|
||||
ui.BIM_PT_tab_project_setup,
|
||||
ui.BIM_PT_tab_geometry,
|
||||
ui.BIM_PT_tab_stakeholders,
|
||||
|
||||
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 997 B |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 956 B |
|
Before Width: | Height: | Size: 895 B |
|
Before Width: | Height: | Size: 176 B |
|
Before Width: | Height: | Size: 329 B |
|
Before Width: | Height: | Size: 329 B |
|
Before Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 183 B |
|
Before Width: | Height: | Size: 191 B |
|
Before Width: | Height: | Size: 308 B |
|
Before Width: | Height: | Size: 248 B |
|
Before Width: | Height: | Size: 405 B |
|
Before Width: | Height: | Size: 237 B |
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 495 B |
|
Before Width: | Height: | Size: 267 B |
|
Before Width: | Height: | Size: 752 B |
|
Before Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 799 B |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 460 B |
|
Before Width: | Height: | Size: 821 B |
|
Before Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1016 B |
|
Before Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 180 B |
|
Before Width: | Height: | Size: 398 B |
|
Before Width: | Height: | Size: 400 B |
|
Before Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 183 B |
|
Before Width: | Height: | Size: 190 B |
|
Before Width: | Height: | Size: 346 B |
|
Before Width: | Height: | Size: 277 B |
|
Before Width: | Height: | Size: 433 B |
|
Before Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 267 B |
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 571 B |
|
Before Width: | Height: | Size: 270 B |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 925 B |
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 493 B |
|
Before Width: | Height: | Size: 829 B |
@@ -1,6 +1,6 @@
|
||||
import sys
|
||||
import os
|
||||
|
||||
import webbrowser
|
||||
|
||||
bonsai_lib_path = os.environ.get("BONSAI_LIB_PATH")
|
||||
bonsai_version = os.environ.get("BONSAI_VERSION")
|
||||
@@ -13,8 +13,6 @@ from aiohttp import web
|
||||
import socketio
|
||||
import pystache
|
||||
import json
|
||||
import base64
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
sio_port = 8080 # default port
|
||||
|
||||
@@ -22,6 +20,14 @@ sio = socketio.AsyncServer(
|
||||
cors_allowed_origins="*",
|
||||
async_mode="aiohttp",
|
||||
)
|
||||
|
||||
# sio.instrument(
|
||||
# auth={
|
||||
# "username": "admin",
|
||||
# "password": "admin",
|
||||
# }
|
||||
# )
|
||||
|
||||
app = web.Application()
|
||||
sio.attach(app)
|
||||
|
||||
@@ -31,8 +37,6 @@ blender_messages = {}
|
||||
blender_theme = {}
|
||||
|
||||
|
||||
# we define two namespaces, one for Bonsai and one for Web UI
|
||||
# each namespace has its own event handlers that are called when an event is emitted by a client
|
||||
# Web namespace
|
||||
class WebNamespace(socketio.AsyncNamespace):
|
||||
def __init__(self, namespace):
|
||||
@@ -40,9 +44,9 @@ class WebNamespace(socketio.AsyncNamespace):
|
||||
|
||||
async def on_connect(self, sid, environ):
|
||||
print(f"Web client connected: {sid}")
|
||||
await sio.emit("theme_data", blender_theme, namespace="/web", room=sid)
|
||||
if blender_messages:
|
||||
await sio.emit("connected_clients", list(blender_messages.keys()), namespace="/web", room=sid)
|
||||
await sio.emit("theme_data", blender_theme, namespace="/web", room=sid)
|
||||
await self.send_cached_messages(sid)
|
||||
|
||||
async def on_disconnect(self, sid):
|
||||
@@ -57,9 +61,11 @@ class WebNamespace(socketio.AsyncNamespace):
|
||||
)
|
||||
|
||||
async def on_get_svg(self, sid, data):
|
||||
print("hello world!")
|
||||
file_path = data["path"]
|
||||
svg = await self.process_svg(file_path)
|
||||
await sio.emit("svg_data", svg, room=sid, namespace="/web")
|
||||
with open(file_path, "r") as file:
|
||||
svg_data = file.read()
|
||||
await sio.emit("svg_data", svg_data, room=sid, namespace="/web")
|
||||
|
||||
async def send_cached_messages(self, sid):
|
||||
# Send cached messages to the connected web client
|
||||
@@ -68,39 +74,6 @@ class WebNamespace(socketio.AsyncNamespace):
|
||||
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)
|
||||
if "demo_data" in messages:
|
||||
await self.emit("demo_data", {"blenderId": blenderId, "data": messages["demo_data"]}, room=sid)
|
||||
if 'cost_items' in messages:
|
||||
await self.emit("cost_items", {"blenderId": blenderId, "data": messages["cost_items"]}, room=sid)
|
||||
|
||||
async def process_svg(self, file_path):
|
||||
def encode_image(filepath):
|
||||
# Encode file content to base64 string
|
||||
with open(filepath, "rb") as file:
|
||||
encoded_string = base64.b64encode(file.read()).decode("utf-8")
|
||||
return f"data:image;base64,{encoded_string}"
|
||||
|
||||
file_dir = os.path.dirname(file_path)
|
||||
|
||||
ET.register_namespace("", "http://www.w3.org/2000/svg")
|
||||
ET.register_namespace("xlink", "http://www.w3.org/1999/xlink")
|
||||
|
||||
tree = ET.parse(file_path)
|
||||
root = tree.getroot()
|
||||
|
||||
namespaces = {
|
||||
"svg": "http://www.w3.org/2000/svg",
|
||||
"xlink": "http://www.w3.org/1999/xlink",
|
||||
}
|
||||
|
||||
for element in root.findall(".//svg:image[@xlink:href]", namespaces):
|
||||
href = element.get("{http://www.w3.org/1999/xlink}href")
|
||||
if href and href.endswith((".png", ".svg", ".jpeg")):
|
||||
img_path = os.path.join(file_dir, href)
|
||||
if os.path.exists(img_path):
|
||||
base64_data = encode_image(img_path)
|
||||
element.set("{http://www.w3.org/1999/xlink}href", base64_data)
|
||||
return ET.tostring(root, "unicode")
|
||||
|
||||
|
||||
# Blender namespace
|
||||
@@ -144,49 +117,10 @@ class BlenderNamespace(socketio.AsyncNamespace):
|
||||
|
||||
async def on_theme_data(self, sid, data):
|
||||
global blender_theme
|
||||
print(f"Blender theme data")
|
||||
blender_theme = data
|
||||
await sio.emit("theme_data", data, namespace="/web")
|
||||
|
||||
# this function will be called when the event demo_data is emitted
|
||||
async def on_demo_data(self, sid, data):
|
||||
print(f"Demo data from Blender client {sid}")
|
||||
blender_messages[sid]["demo_data"] = data
|
||||
await sio.emit("demo_data", {"blenderId": sid, "data": data}, namespace="/web")
|
||||
|
||||
async def on_work_schedule_info(self, sid, data):
|
||||
print(f"Work schedule info from Blender client {sid}")
|
||||
blender_messages[sid]["work_schedule_info"] = data
|
||||
await sio.emit("work_schedule_info", {"blenderId": sid, "data": data}, namespace="/web")
|
||||
|
||||
async def on_cost_items(self, sid, data):
|
||||
print(f"Cost items data from Blender client {sid}")
|
||||
blender_messages[sid]["cost_items"] = data
|
||||
await sio.emit("cost_items", {"blenderId": sid, "data": data}, namespace="/web")
|
||||
|
||||
async def on_cost_schedules(self, sid, data):
|
||||
print(f"Cost schedule info from Blender client {sid}")
|
||||
blender_messages[sid]["cost_schedules"] = data
|
||||
await sio.emit("cost_schedules", {"blenderId": sid, "data": data}, namespace="/web")
|
||||
|
||||
async def on_cost_values(self, sid, data):
|
||||
print(f"Cost values from Blender client {sid}")
|
||||
blender_messages[sid]["cost_values"] = data
|
||||
await sio.emit("cost_values", {"blenderId": sid, "data": data}, namespace="/web")
|
||||
|
||||
async def on_cost_value(self, sid, data):
|
||||
print(f"Cost values from Blender client {sid}")
|
||||
blender_messages[sid]["cost_value"] = data
|
||||
await sio.emit("cost_value", {"blenderId": sid, "data": data}, namespace="/web")
|
||||
|
||||
async def on_predefined_types(self, sid, data):
|
||||
print(f"Predefined types from Blender client {sid}")
|
||||
blender_messages[sid]["predefined_types"] = data
|
||||
await sio.emit("predefined_types", {"blenderId": sid, "data": data}, namespace="/web")
|
||||
|
||||
async def on_quantities(self, sid, data):
|
||||
print(f"Selected products from Blender client {sid}")
|
||||
blender_messages[sid]["quantities"] = data
|
||||
await sio.emit("quantities", {"blenderId": sid, "data": data}, namespace="/web")
|
||||
|
||||
async def schedules(request):
|
||||
with open("templates/index.html", "r") as f:
|
||||
@@ -194,12 +128,6 @@ async def schedules(request):
|
||||
html_content = pystache.render(template, {"port": sio_port, "version": bonsai_version})
|
||||
return web.Response(text=html_content, content_type="text/html")
|
||||
|
||||
async def costing(request):
|
||||
with open("templates/costing.html", "r") as f:
|
||||
template = f.read()
|
||||
html_content = pystache.render(template, {"port": sio_port, "version": bonsai_version})
|
||||
return web.Response(text=html_content, content_type="text/html")
|
||||
|
||||
|
||||
async def sequencing(request):
|
||||
with open("templates/gantt.html", "r") as f:
|
||||
@@ -215,15 +143,6 @@ async def documentation(request):
|
||||
return web.Response(text=html_content, content_type="text/html")
|
||||
|
||||
|
||||
# This is a request handler for the /demo URL endpoint.
|
||||
# It serves the demo HTML file after using pystache to render the variables
|
||||
async def demo(request):
|
||||
with open("templates/demo.html", "r") as f:
|
||||
template = f.read()
|
||||
html_content = pystache.render(template, {"port": sio_port, "version": bonsai_version})
|
||||
return web.Response(text=html_content, content_type="text/html")
|
||||
|
||||
|
||||
async def on_startup(app):
|
||||
pid_file = "running_pid.json"
|
||||
|
||||
@@ -250,8 +169,6 @@ sio.register_namespace(BlenderNamespace("/blender"))
|
||||
app.router.add_get("/", schedules)
|
||||
app.router.add_get("/documentation", documentation)
|
||||
app.router.add_get("/sequencing", sequencing)
|
||||
app.router.add_get("/costing", costing)
|
||||
app.router.add_get("/demo", demo)
|
||||
|
||||
# Add static files
|
||||
app.router.add_static("/jsgantt/", path="../gantt", name="jsgantt")
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
:root.blender .flex-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
/* CSS for the work schedule cards */
|
||||
:root.blender #work_schedules {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 20px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
:root.blender .card {
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
width: 300px;
|
||||
margin: 10px;
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
|
||||
:root.blender .card:hover {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
|
||||
:root.blender .card-title {
|
||||
font-size: 1.25rem;
|
||||
margin-bottom: 10px;
|
||||
border-bottom: 1px solid #ddd;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
:root.blender .card-text {
|
||||
font-size: 1rem;
|
||||
margin-bottom: 20px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
:root.blender .btn-primary {
|
||||
background-color: #007bff;
|
||||
border: none;
|
||||
color: white;
|
||||
padding: 10px 20px;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
font-size: 1rem;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
:root.blender .btn-primary:hover {
|
||||
background-color: #0056b3;
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
|
||||
th {
|
||||
position: relative;
|
||||
}
|
||||
.resizer {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
width: 5px;
|
||||
height: 100%;
|
||||
cursor: col-resize;
|
||||
user-select: none;
|
||||
}
|
||||
.context-menu {
|
||||
display: none;
|
||||
position: absolute;
|
||||
background-color: rgb(51, 51, 51);
|
||||
border: 1px solid #ccc;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
|
||||
z-index: 1000;
|
||||
color: white;
|
||||
}
|
||||
.context-menu button {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
border: none;
|
||||
background: none;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
}
|
||||
.context-menu button:hover {
|
||||
background-color: #f0f0f0;
|
||||
color: black;
|
||||
}
|
||||
|
||||
/* Context menu button is clicked, should zoom into object and change color */
|
||||
.context-menu button:active {
|
||||
background-color: #ff9634;
|
||||
color: rgba(37, 51, 77, 0.466);
|
||||
/* zoom */
|
||||
transform: scale(0.9);
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
.ribbon-bar {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-bottom: 20px;
|
||||
border: 1px solid #444;
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.ribbon-bar .action-button {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: none;
|
||||
background-color: #444;
|
||||
color: #fff;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s;
|
||||
border-left: 1px solid #666;
|
||||
}
|
||||
|
||||
.ribbon-bar .action-button:hover {
|
||||
background-color: #666;
|
||||
}
|
||||
|
||||
.ribbon-bar .action-button::after {
|
||||
content: attr(data-tooltip);
|
||||
position: absolute;
|
||||
bottom: -30px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
background-color: #333;
|
||||
color: #fff;
|
||||
padding: 5px 10px;
|
||||
border-radius: 5px;
|
||||
white-space: nowrap;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity 0.3s;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.ribbon-bar .action-button:hover::after {
|
||||
opacity: 1;
|
||||
}
|
||||
@@ -1,405 +0,0 @@
|
||||
@import url("./shared.css");
|
||||
@import url("./components/card.css");
|
||||
@import url("./components/contextMenu.css");
|
||||
@import url("./components/headerBar.css");
|
||||
|
||||
:root {
|
||||
--fontSize: 14px;
|
||||
--background-color: #363636;
|
||||
--button-background: #28a745;
|
||||
--button-hover-background: #218838;
|
||||
--scrollbar-track-color: #f1f1f1;
|
||||
--scrollbar-thumb-color: #888;
|
||||
--scrollbar-thumb-hover-color: #555;
|
||||
--padding-small: 5px;
|
||||
--padding-medium: 10px;
|
||||
}
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.floating-form {
|
||||
width: 50vw;
|
||||
max-height: 80vh;
|
||||
position: absolute;
|
||||
background-color: var(--background-color);
|
||||
border: 1px solid black;
|
||||
z-index: 9999;
|
||||
cursor: move;
|
||||
user-select: none;
|
||||
padding-bottom: var(--padding-medium);
|
||||
}
|
||||
|
||||
[id^="enable-editing-quantities"].floating-form {
|
||||
height: 70vh;
|
||||
}
|
||||
|
||||
.form-header {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
background-color: #323936;
|
||||
padding: var(--padding-medium);
|
||||
font-size: large;
|
||||
border-bottom: 1px solid #ddd;
|
||||
border-radius: 6px 6px 0 0;
|
||||
height: 5%;
|
||||
}
|
||||
|
||||
.form-container {
|
||||
height: 95%;
|
||||
padding-left: var(--padding-medium);
|
||||
padding-right: var(--padding-medium);
|
||||
}
|
||||
|
||||
.form-section {
|
||||
margin-bottom: 1em;
|
||||
height: 65%;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.form-section h3 {
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
.summary-section {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
.summary-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 14px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
|
||||
.floating-form table tr {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.action-button {
|
||||
position:relative;
|
||||
background-color: var(--button-background);
|
||||
border: none;
|
||||
color: #fff;
|
||||
padding: 0.5em 1em;
|
||||
cursor: pointer;
|
||||
border-radius: 6px;
|
||||
font-weight: bold;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
transition: background-color 0.3s ease;
|
||||
width: fit-content;
|
||||
margin: 0.5em;
|
||||
z-index: 11000;
|
||||
}
|
||||
|
||||
.action-button::after {
|
||||
content: attr(data-tooltip);
|
||||
position: absolute;
|
||||
bottom: -30px;
|
||||
background-color: #333;
|
||||
color: #fff;
|
||||
padding: 5px 10px;
|
||||
border-radius: 5px;
|
||||
white-space: nowrap;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity 0.3s;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.action-button:hover::after {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.active-btn {
|
||||
background-color: #887821;
|
||||
/* make a nic shadow */
|
||||
box-shadow: 0 0 10px #887821;
|
||||
}
|
||||
|
||||
.action-button:hover {
|
||||
background-color: var(--button-hover-background);
|
||||
}
|
||||
|
||||
.close-button {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
font-size: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.settings-menu {
|
||||
display: none;
|
||||
position: absolute;
|
||||
background-color: var(--background-color);
|
||||
border: 1px solid black;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
||||
z-index: 9999;
|
||||
cursor: move;
|
||||
padding: 20px;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: var(--scrollbar-track-color);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: var(--scrollbar-thumb-color);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--scrollbar-thumb-hover-color);
|
||||
}
|
||||
|
||||
/* Firefox */
|
||||
* {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--scrollbar-thumb-color) var(--scrollbar-track-color);
|
||||
}
|
||||
|
||||
/* Edge, IE */
|
||||
* {
|
||||
-ms-overflow-style: -ms-autohiding-scrollbar;
|
||||
}
|
||||
|
||||
body {
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
margin-top: 1em;
|
||||
overflow-x: auto;
|
||||
overflow-y: auto;
|
||||
position: relative;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.floating-form .table-container {
|
||||
max-height: 80%;
|
||||
}
|
||||
|
||||
#cost-items {
|
||||
max-height: calc(100vh - var(--margin-small));
|
||||
}
|
||||
|
||||
[id^="cost-values-form"] table {
|
||||
max-height: 50%;
|
||||
table-layout: fixed;
|
||||
}
|
||||
|
||||
[id^="selected-products"] .form-container {
|
||||
height: 50vh;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-collapse: collapse;
|
||||
user-select: none;
|
||||
font-size: var(--fontSize);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
|
||||
thead {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
background-color: #727272;
|
||||
}
|
||||
|
||||
tbody {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
td,
|
||||
th {
|
||||
border-right: 1px solid #ddd;
|
||||
text-align: center;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
th {
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
|
||||
td {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
td:first-child {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.nested {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.expand-collapse {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.caret-cell {
|
||||
border: none;
|
||||
background: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
input,
|
||||
select {
|
||||
background-color: var(--blender-button-background, var(--bg-color));
|
||||
color: var(--blender-button-text, var(--text-color));
|
||||
border-color: var(--blender-button-border, var(--border-color));
|
||||
transition: filter 0.2s ease;
|
||||
transition: border-color 0.2s cubic-bezier(0.3, 0, 0.5, 1);
|
||||
}
|
||||
|
||||
input:focus {
|
||||
border-color: #28a745;
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 3px #28a745;
|
||||
color: #ddd;
|
||||
}
|
||||
|
||||
#cost-items input, #cost-items
|
||||
select {
|
||||
border-radius: 6px;
|
||||
padding: 6px 8px;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
margin-left: 0.5em;
|
||||
margin-right: 0.5em;
|
||||
font-size: var(--fontSize);
|
||||
min-width: 50px;
|
||||
}
|
||||
|
||||
form input {
|
||||
border-radius: 6px;
|
||||
padding: 3px 4px;
|
||||
font-size: 12px;
|
||||
line-height: 10px;
|
||||
margin-left: 0.2em;
|
||||
margin-right: 0.2em;
|
||||
font-size: inherit;
|
||||
height: inherit;
|
||||
width: inherit;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
#cost-items tr:hover,
|
||||
.highlighted {
|
||||
background-color: #28a74657;
|
||||
}
|
||||
|
||||
.highlighted {
|
||||
background-color: #28a74657;
|
||||
}
|
||||
|
||||
.cost-values-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.cost-values-table th,
|
||||
.cost-values-table td {
|
||||
border: 1px solid #ddd;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.cost-values-table th {
|
||||
background-color: #444444;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
table {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
td,
|
||||
th {
|
||||
padding: 2px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
input {
|
||||
padding: 2px;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
table {
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
td,
|
||||
th {
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
input {
|
||||
padding: 1px;
|
||||
font-size: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
form table {
|
||||
max-height: 50%;
|
||||
}
|
||||
|
||||
.subtotal-row {
|
||||
background-color: var(--button-hover-background);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
[id^="cost-items"] th:nth-child(1),
|
||||
[id^="cost-items"] td:nth-child(1) {
|
||||
width: auto;
|
||||
}
|
||||
[id^="cost-items"] th:not(:nth-child(1)),
|
||||
[id^="cost-items"] td:not(:nth-child(1)) {
|
||||
width: 10%;
|
||||
}
|
||||
|
||||
[id^="cost-items"] th:last-child,
|
||||
[id^="cost-items"] td:last-child {
|
||||
width: fit-content;
|
||||
min-width: fit-content;
|
||||
max-width: 50%;
|
||||
}
|
||||
|
||||
.actions-column {
|
||||
transition: opacity 0.3s ease-in-out;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
[id^="cost-items"] tr:hover .actions-column {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.clickable-cell:hover {
|
||||
border: 1px solid #94a728;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
}
|
||||
@@ -1,345 +0,0 @@
|
||||
:root {
|
||||
--font-family: Arial, sans-serif;
|
||||
--base-font-size: 16px;
|
||||
--margin-tiny: 0.125rem;
|
||||
--margin-small: 0.625rem;
|
||||
--margin-medium: 1.25rem;
|
||||
--margin-large: 2.5rem;
|
||||
--padding-tiny: 0.125rem;
|
||||
--padding-small: 0.625rem;
|
||||
--padding-medium: 1rem;
|
||||
--font-size-large: 1.2rem;
|
||||
--logo-height: 1.5rem;
|
||||
--nav-height: 0.625rem;
|
||||
--border-radius: 0.3rem;
|
||||
}
|
||||
|
||||
:root.dark {
|
||||
color-scheme: dark;
|
||||
--bg-color: #252525;
|
||||
--text-color: #e0e0e0;
|
||||
--nav-bg-color: #121212;
|
||||
--nav-border-color: #25682a;
|
||||
--nav-link-color: #fff;
|
||||
--nav-link-hover-color: #3fb449;
|
||||
--warning-color: #FFDB8F;
|
||||
--border-color: #464444;
|
||||
}
|
||||
|
||||
:root.light {
|
||||
color-scheme: light;
|
||||
--bg-color: #ffffff;
|
||||
--text-color: #000000;
|
||||
--nav-bg-color: #f8f8f8;
|
||||
--nav-border-color: #cccccc;
|
||||
--nav-link-color: #000000;
|
||||
--nav-link-hover-color: #38a63d;
|
||||
--warning-color: #FF4500;
|
||||
--border-color: #222;
|
||||
}
|
||||
|
||||
html {
|
||||
font-size: var(--base-font-size);
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--blender-window-background, var(--bg-color));
|
||||
color: var(--blender-text, var(--text-color));
|
||||
margin: 0;
|
||||
font-family: var(--font-family);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
#container {
|
||||
flex: 1;
|
||||
padding: var(--margin-small);
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
nav {
|
||||
background-color: var(--blender-top-bar-header, var(--nav-bg-color));
|
||||
height: var(--nav-height);
|
||||
padding: var(--padding-medium) 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
border-bottom: 2px solid var(--blender-tab-outline, var(--nav-border-color));
|
||||
}
|
||||
|
||||
nav .logo {
|
||||
margin-left: var(--margin-large);
|
||||
height: var(--logo-height);
|
||||
}
|
||||
|
||||
nav ul {
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
nav ul li {
|
||||
margin-right: var(--margin-large);
|
||||
}
|
||||
|
||||
nav ul li a {
|
||||
text-decoration: none;
|
||||
color: var(--blender-text, var(--nav-link-color));
|
||||
}
|
||||
|
||||
nav ul li a:hover,
|
||||
nav ul li a.active {
|
||||
color: var(--blender-selected-object, var(--nav-link-hover-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;
|
||||
}
|
||||
|
||||
button,
|
||||
input {
|
||||
background-color: var(--blender-button-background, var(--bg-color));
|
||||
color: var(--blender-button-text, var(--text-color));
|
||||
border-color: var(--blender-button-border, var(--border-color));
|
||||
transition: filter 0.2s ease;
|
||||
}
|
||||
|
||||
footer {
|
||||
background-color: var(--blender-top-bar-header, var(--nav-bg-color));
|
||||
text-align: right;
|
||||
padding: var(--padding-tiny);
|
||||
border-top: 1px solid var(--blender-tab-outline, var(--nav-border-color));
|
||||
}
|
||||
|
||||
footer p {
|
||||
margin: 0;
|
||||
color: var(--blender-text, var(--nav-link-color));
|
||||
margin-right: var(--margin-small);
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
|
||||
/* =========== styling for code blocks and rendering =========== */
|
||||
/* ================== This is auto-generated ================== */
|
||||
|
||||
|
||||
#explanation {
|
||||
font-family: "Segoe WPC", "Segoe UI", "Ubuntu", "Droid Sans", sans-serif;
|
||||
font-size: 16px;
|
||||
padding: 0 26px;
|
||||
line-height: 22px;
|
||||
word-wrap: break-word;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#code-csp-warning {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
color: white;
|
||||
margin: 16px;
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
font-family: sans-serif;
|
||||
background-color: #444444;
|
||||
cursor: pointer;
|
||||
padding: 6px;
|
||||
box-shadow: 1px 1px 1px rgba(0, 0, 0, .25);
|
||||
}
|
||||
|
||||
#code-csp-warning:hover {
|
||||
text-decoration: none;
|
||||
background-color: #007acc;
|
||||
box-shadow: 2px 2px 2px rgba(0, 0, 0, .25);
|
||||
}
|
||||
|
||||
#explanation.scrollBeyondLastLine {
|
||||
margin-bottom: calc(100vh - 22px);
|
||||
}
|
||||
|
||||
#explanation.showEditorSelection .code-line {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#explanation.showEditorSelection .code-active-line:before,
|
||||
#explanation.showEditorSelection .code-line:hover:before {
|
||||
content: "";
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -12px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#explanation.showEditorSelection li.code-active-line:before,
|
||||
#explanation.showEditorSelection li.code-line:hover:before {
|
||||
left: -30px;
|
||||
}
|
||||
|
||||
#explanation a:focus,
|
||||
#explanation input:focus,
|
||||
#explanation select:focus,
|
||||
#explanation textarea:focus {
|
||||
outline: 1px solid -webkit-focus-ring-color;
|
||||
outline-offset: -1px;
|
||||
}
|
||||
|
||||
#explanation hr {
|
||||
border: 0;
|
||||
height: 2px;
|
||||
border-bottom: 2px solid;
|
||||
}
|
||||
|
||||
#explanation h1 {
|
||||
padding-bottom: 0.3em;
|
||||
line-height: 1.2;
|
||||
border-bottom-width: 1px;
|
||||
border-bottom-style: solid;
|
||||
}
|
||||
|
||||
#explanation h1,
|
||||
#explanation h2,
|
||||
#explanation h3 {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
#explanation code {
|
||||
font-family: Menlo, Monaco, Consolas, "Droid Sans Mono", "Courier New", monospace, "Droid Sans Fallback";
|
||||
font-size: 1em;
|
||||
line-height: 1.357em;
|
||||
}
|
||||
|
||||
#explanation .wordWrap pre {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
#explanation pre:not(.hljs),
|
||||
#explanation pre.hljs code>div {
|
||||
padding: 16px;
|
||||
border-radius: 3px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
#explanation pre code {
|
||||
tab-size: 4;
|
||||
}
|
||||
|
||||
#explanation .hljs-comment,
|
||||
#explanation .hljs-quote {
|
||||
color: #B6B18B;
|
||||
}
|
||||
|
||||
#explanation .hljs-variable,
|
||||
#explanation .hljs-template-variable,
|
||||
#explanation .hljs-tag,
|
||||
#explanation .hljs-name,
|
||||
#explanation .hljs-selector-id,
|
||||
#explanation .hljs-selector-class,
|
||||
#explanation .hljs-regexp,
|
||||
#explanation .hljs-deletion {
|
||||
color: #EB3C54;
|
||||
}
|
||||
|
||||
#explanation .hljs-number,
|
||||
#explanation .hljs-built_in,
|
||||
#explanation .hljs-builtin-name,
|
||||
#explanation .hljs-literal,
|
||||
#explanation .hljs-type,
|
||||
#explanation .hljs-params,
|
||||
#explanation .hljs-meta,
|
||||
#explanation .hljs-link {
|
||||
color: #E7CE56;
|
||||
}
|
||||
|
||||
#explanation .hljs-attribute {
|
||||
color: #EE7C2B;
|
||||
}
|
||||
|
||||
#explanation .hljs-string,
|
||||
#explanation .hljs-symbol,
|
||||
#explanation .hljs-bullet,
|
||||
#explanation .hljs-addition {
|
||||
color: #4FB4D7;
|
||||
}
|
||||
|
||||
#explanation .hljs-title,
|
||||
#explanation .hljs-section {
|
||||
color: #78BB65;
|
||||
}
|
||||
|
||||
#explanation .hljs-keyword,
|
||||
#explanation .hljs-selector-tag {
|
||||
color: #B45EA4;
|
||||
}
|
||||
|
||||
#explanation .hljs {
|
||||
display: block;
|
||||
overflow-x: auto;
|
||||
background: #1C1D21;
|
||||
color: #c0c5ce;
|
||||
padding: 0.5em;
|
||||
}
|
||||
|
||||
#explanation .hljs-emphasis {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
#explanation .hljs-strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#explanation {
|
||||
font-family: "Segoe WPC", "Segoe UI", "Ubuntu", "Droid Sans", sans-serif, "Meiryo";
|
||||
padding: 0 12px;
|
||||
}
|
||||
|
||||
#explanation pre {
|
||||
background-color: #f8f8f8;
|
||||
border: 1px solid #cccccc;
|
||||
border-radius: 3px;
|
||||
overflow-x: auto;
|
||||
white-space: pre-wrap;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
#explanation pre:not(.hljs) {
|
||||
padding: 23px;
|
||||
line-height: 19px;
|
||||
}
|
||||
|
||||
#explanation blockquote {
|
||||
background: rgba(127, 127, 127, 0.1);
|
||||
border-color: rgba(0, 122, 204, 0.5);
|
||||
}
|
||||
|
||||
#explanation .emoji {
|
||||
height: 1.4em;
|
||||
}
|
||||
|
||||
#explanation code {
|
||||
font-size: 14px;
|
||||
line-height: 19px;
|
||||
}
|
||||
|
||||
#explanation :not(pre):not(.hljs)>code {
|
||||
color: #C9AE75;
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
#explanation .page {
|
||||
page-break-after: always;
|
||||
}
|
||||
@@ -55,7 +55,10 @@ body {
|
||||
}
|
||||
|
||||
#container {
|
||||
padding: var(--margin-small);
|
||||
margin-top: var(--margin-medium);
|
||||
margin-left: var(--margin-tiny);
|
||||
margin-right: var(--margin-tiny);
|
||||
margin-bottom: var(--margin-medium);
|
||||
height: calc(100vh - var(--nav-height));
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
|
||||
@@ -1,166 +1,289 @@
|
||||
@import url("./components/card.css");
|
||||
@import url("./shared.css");
|
||||
:root {
|
||||
--font-family: Arial, sans-serif;
|
||||
--base-font-size: 16px;
|
||||
--margin-tiny: 0.125rem;
|
||||
--margin-small: 0.625rem;
|
||||
--margin-medium: 1.25rem;
|
||||
--margin-large: 2.5rem;
|
||||
--padding-tiny: 0.125rem;
|
||||
--padding-small: 0.625rem;
|
||||
--padding-medium: 1rem;
|
||||
--font-size-small: 1rem;
|
||||
--font-size-large: 1.2rem;
|
||||
--logo-height: 1.5rem;
|
||||
--nav-height: 0.625rem;
|
||||
--border-radius: 0.3rem;
|
||||
--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;
|
||||
--border-color: #464444;
|
||||
}
|
||||
|
||||
: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;
|
||||
--border-color: #222;
|
||||
}
|
||||
|
||||
|
||||
*:not(td, tr, td, .gmain) {
|
||||
border-radius: var(--border-radius) !important;
|
||||
}
|
||||
|
||||
html {
|
||||
font-size: var(--base-font-size);
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--blender-window-background, var(--bg-color));
|
||||
color: var(--blender-text, var(--primary-text-color));
|
||||
margin: 0;
|
||||
font-family: var(--font-family);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
#container {
|
||||
flex: 1;
|
||||
margin-top: var(--margin-medium);
|
||||
margin-left: var(--margin-small);
|
||||
margin-right: var(--margin-small);
|
||||
margin-bottom: var(--margin-medium);
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
nav {
|
||||
background-color: var(--blender-top-bar-header, var(--nav-bg-color));
|
||||
height: var(--nav-height);
|
||||
padding: var(--padding-medium) 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
border-bottom: 2px solid var(--blender-tab-outline, var(--nav-border-color));
|
||||
}
|
||||
|
||||
nav .logo {
|
||||
margin-left: var(--margin-large);
|
||||
height: var(--logo-height);
|
||||
}
|
||||
|
||||
nav ul {
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
nav ul li {
|
||||
margin-right: var(--margin-large);
|
||||
}
|
||||
|
||||
nav ul li a {
|
||||
text-decoration: none;
|
||||
color: var(--blender-text, var(--nav-link-color));
|
||||
}
|
||||
|
||||
nav ul li a:hover,
|
||||
nav ul li a.active {
|
||||
color: var(--blender-selected-object, var(--nav-link-hover-color));
|
||||
}
|
||||
|
||||
.warning,
|
||||
.warning-icon {
|
||||
color: var(--blender-info-warning, var(--warning-color));
|
||||
}
|
||||
|
||||
.table-description {
|
||||
margin-bottom: var(--margin-medium);
|
||||
width: 50%;
|
||||
background-color: transparent;
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
border-color: var(--blender-tab-outline, var(--border-color));
|
||||
border: 1px solid var(--blender-tab-outline, var(--border-color));
|
||||
border-radius: 0.25rem;
|
||||
box-shadow: var(--box-shadow);
|
||||
text-align: center;
|
||||
margin-bottom: var(--margin-medium);
|
||||
width: 50%;
|
||||
background-color: transparent;
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
border-color: var(--blender-tab-outline, var(--border-color));
|
||||
border: 1px solid var(--blender-tab-outline, var(--border-color));
|
||||
border-radius: 0.25rem;
|
||||
box-shadow: var(--box-shadow);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.table-description tr {
|
||||
display: table-row;
|
||||
border-bottom: 1px solid var(--blender-tab-outline, var(--border-color));
|
||||
display: table-row;
|
||||
border-bottom: 1px solid var(--blender-tab-outline, var(--border-color));
|
||||
}
|
||||
|
||||
.table-description tr:nth-child(even) {
|
||||
background-color: var(--blender-even-row);
|
||||
background-color: var(--blender-even-row);
|
||||
}
|
||||
|
||||
.table-description tr:nth-child(odd) {
|
||||
background-color: var(--blender-odd-row);
|
||||
background-color: var(--blender-odd-row);
|
||||
}
|
||||
|
||||
#toggle-theme {
|
||||
border: none;
|
||||
background: none;
|
||||
cursor: pointer;
|
||||
font-size: var(--font-size-large);
|
||||
margin-right: var(--margin-medium);
|
||||
border: none;
|
||||
background: none;
|
||||
cursor: pointer;
|
||||
font-size: var(--font-size-large);
|
||||
margin-right: var(--margin-medium);
|
||||
}
|
||||
|
||||
#toggle-theme:focus {
|
||||
outline: none;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
#client-list {
|
||||
position: absolute;
|
||||
top: calc(0 + var(--nav-height));
|
||||
overflow-y: auto;
|
||||
overflow: hidden;
|
||||
transition: opacity 0.3s ease-out, visibility 0.3s ease-out;
|
||||
border: 1px solid var(--blender-tab-outline, var(--border-color));
|
||||
background-color: var(--blender-panel-background, var(--nav-bg-color));
|
||||
z-index: 1;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
width: auto;
|
||||
position: absolute;
|
||||
top: calc(0 + var(--nav-height));
|
||||
overflow-y: auto;
|
||||
overflow: hidden;
|
||||
transition: opacity 0.3s ease-out, visibility 0.3s ease-out;
|
||||
border: 1px solid var(--blender-tab-outline, var(--border-color));
|
||||
background-color: var(--blender-panel-background, var(--nav-bg-color));
|
||||
z-index: 1;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
#client-list.show {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
#connected-list-div {
|
||||
display: inline-block;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.client {
|
||||
padding: var(--margin-small);
|
||||
border-bottom: 1px solid var(--blender-tab-outline, var(--border-color));
|
||||
cursor: pointer;
|
||||
padding: var(--margin-small);
|
||||
border-bottom: 1px solid var(--blender-tab-outline, var(--border-color));
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.client-details {
|
||||
max-height: 0;
|
||||
overflow: hidden;
|
||||
padding-left: var(--padding-small);
|
||||
transition: max-height 0.2s ease-out, padding 0.2s ease-out,
|
||||
opacity 0.1s ease-out;
|
||||
background-color: var(--blender-panel-background, var(--nav-bg-color));
|
||||
margin-top: var(--margin-tiny);
|
||||
opacity: 0;
|
||||
max-height: 0;
|
||||
overflow: hidden;
|
||||
padding-left: var(--padding-small);
|
||||
transition: max-height 0.2s ease-out, padding 0.2s ease-out, opacity 0.1s ease-out;
|
||||
background-color: var(--blender-panel-background, var(--nav-bg-color));
|
||||
margin-top: var(--margin-tiny);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.client-details.show {
|
||||
max-height: none;
|
||||
opacity: 1;
|
||||
max-height: none;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.client-detail {
|
||||
border-bottom: 1px solid var(--blender-tab-outline, var(--border-color));
|
||||
padding: var(--padding-small);
|
||||
border-bottom: 1px solid var(--blender-tab-outline, var(--border-color));
|
||||
padding: var(--padding-small);
|
||||
}
|
||||
|
||||
#show-connected-button {
|
||||
width: auto;
|
||||
background-color: var(--blender-panel-header, var(--bg-color));
|
||||
font-size: var(--base-font-size);
|
||||
width: auto;
|
||||
background-color: var(--blender-panel-header, var(--bg-color));
|
||||
font-size: var(--base-font-size);
|
||||
}
|
||||
|
||||
#show-connected-button:hover,
|
||||
.scroll-button:hover {
|
||||
cursor: pointer;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.scroll-button {
|
||||
font-size: var(--base-font-size);
|
||||
margin-left: var(--margin-tiny);
|
||||
margin-top: var(--margin-small);
|
||||
font-size: var(--base-font-size);
|
||||
margin-left: var(--margin-tiny);
|
||||
margin-top: var(--margin-small);
|
||||
}
|
||||
|
||||
footer {
|
||||
background-color: var(--blender-top-bar-header, var(--nav-bg-color));
|
||||
text-align: right;
|
||||
padding: var(--padding-tiny);
|
||||
border-top: 1px solid var(--blender-tab-outline, var(--nav-border-color));
|
||||
background-color: var(--blender-top-bar-header, var(--nav-bg-color));
|
||||
text-align: right;
|
||||
padding: var(--padding-tiny);
|
||||
border-top: 1px solid var(--blender-tab-outline, var(--nav-border-color));
|
||||
}
|
||||
|
||||
footer p {
|
||||
margin: 0;
|
||||
color: var(--blender-text, var(--nav-link-color));
|
||||
margin-right: var(--margin-small);
|
||||
font-size: 0.8rem;
|
||||
margin: 0;
|
||||
color: var(--blender-text, var(--nav-link-color));
|
||||
margin-right: var(--margin-small);
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.gantt-info {
|
||||
margin: 0.5rem;
|
||||
font-size: var(--font-size-small);
|
||||
font-weight: bold;
|
||||
margin: 0.5rem;
|
||||
font-size: var(--font-size-small);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.btn {
|
||||
margin: 0.5rem;
|
||||
font-size: var(--font-size-small);
|
||||
cursor: pointer;
|
||||
margin: 0.5rem;
|
||||
font-size: var(--font-size-small);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@media print {
|
||||
.no-print {
|
||||
display: none;
|
||||
}
|
||||
.no-print {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
button,
|
||||
select {
|
||||
background-color: var(--blender-button-background, var(--bg-color));
|
||||
color: var(--blender-button-text, var(--text-color));
|
||||
border-color: var(--blender-button-border, var(--border-color));
|
||||
transition: filter 0.2s ease;
|
||||
background-color: var(--blender-button-background, var(--bg-color));
|
||||
color: var(--blender-button-text, var(--text-color));
|
||||
border-color: var(--blender-button-border, var(--border-color));
|
||||
transition: filter 0.2s ease;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
filter: brightness(1.2);
|
||||
filter: brightness(1.2);
|
||||
}
|
||||
|
||||
/* ------------ Overwriting JSGantt CSS rules ------------ */
|
||||
/* ------------------- for dark theme ------------------- */
|
||||
|
||||
:root.dark div.gantt {
|
||||
background-color: var(--bg-color);
|
||||
color: var(--secondary-text-color);
|
||||
background-color: var(--bg-color);
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
||||
:root.dark .gantt table,
|
||||
:root.dark .gantt td {
|
||||
border-color: var(--border-color);
|
||||
border-color: var(--border-color);
|
||||
}
|
||||
|
||||
/* Headings and cell defaults */
|
||||
@@ -173,8 +296,8 @@ button:hover {
|
||||
:root.dark .gname,
|
||||
:root.dark .ggroupitem,
|
||||
:root.dark .gtaskheading {
|
||||
background-color: var(--bg-color);
|
||||
border-color: var(--border-color);
|
||||
background-color: var(--bg-color);
|
||||
border-color: var(--border-color);
|
||||
}
|
||||
|
||||
:root.dark .gtaskheading,
|
||||
@@ -190,128 +313,129 @@ button:hover {
|
||||
:root.dark .gchartlbl,
|
||||
:root.dark .gcontainercol,
|
||||
:root.dark .genddate {
|
||||
color: var(--secondary-text-color);
|
||||
border-color: var(--border-color);
|
||||
color: var(--secondary-text-color);
|
||||
border-color: var(--border-color);
|
||||
}
|
||||
|
||||
:root.dark .gtaskname > div {
|
||||
color: var(--secondary-text-color);
|
||||
:root.dark .gtaskname>div {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
||||
:root.dark .gtaskbarcontainer.gplan,
|
||||
:root.dark .gchartlbl.gcontainercol {
|
||||
background: var(--bg-color);
|
||||
border-color: var(--border-color);
|
||||
background: var(--bg-color);
|
||||
border-color: var(--border-color);
|
||||
}
|
||||
|
||||
:root.dark .gtaskname div,
|
||||
:root.dark .gtaskheading div,
|
||||
:root.dark .gtaskname span {
|
||||
color: var(--secondary-text-color);
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
||||
:root.dark .gtasklist,
|
||||
:root.dark .gadditional {
|
||||
border: var(--border-color) 1px solid;
|
||||
border: var(--border-color) 1px solid;
|
||||
}
|
||||
|
||||
:root.dark .gchartgrid {
|
||||
background-color: var(--bg-color);
|
||||
background-color: var(--bg-color);
|
||||
}
|
||||
|
||||
:root.dark .glistgrid,
|
||||
:root.dark .glistlbl {
|
||||
background-color: var(--bg-color);
|
||||
border-color: var(--border-color);
|
||||
background-color: var(--bg-color);
|
||||
border-color: var(--border-color);
|
||||
}
|
||||
|
||||
/* Dark mode scrollbar */
|
||||
:root.dark .frame::-webkit-scrollbar-thumb,
|
||||
:root.dark .frame::-webkit-scrollbar-track {
|
||||
background-color: var(--bg-color);
|
||||
background-color: var(--bg-color);
|
||||
}
|
||||
|
||||
/* Highlight row */
|
||||
:root.dark .gitemhighlight td {
|
||||
background-color: var(--highlight-color);
|
||||
color: var(--nav-link-color);
|
||||
background-color: var(--highlight-color);
|
||||
color: var(--nav-link-color);
|
||||
}
|
||||
|
||||
/* Differentiate Group, Milestone and Ordinary task items (applied to row) */
|
||||
:root.dark .ggroupitem {
|
||||
background-color: var(--bg-color);
|
||||
font-weight: bold;
|
||||
border-color: var(--border-color);
|
||||
background-color: var(--bg-color);
|
||||
font-weight: bold;
|
||||
border-color: var(--border-color);
|
||||
}
|
||||
|
||||
:root.dark .gmileitem,
|
||||
:root.dark .glineitem {
|
||||
background-color: var(--bg-color);
|
||||
background-color: var(--bg-color);
|
||||
}
|
||||
|
||||
/* Task bar caption text styles */
|
||||
:root.dark .gmilecaption,
|
||||
:root.dark .ggroupcaption,
|
||||
:root.dark .gcaption {
|
||||
color: var(--nav-link-color);
|
||||
color: var(--nav-link-color);
|
||||
}
|
||||
|
||||
/* Task complete %age bar */
|
||||
:root.dark .gtaskcomplete {
|
||||
background-color: var(--task-complete-bg-color);
|
||||
background-color: var(--task-complete-bg-color);
|
||||
}
|
||||
|
||||
/* Milestones */
|
||||
:root.dark .gmdtop,
|
||||
:root.dark .gmdbottom {
|
||||
border-bottom: 5px solid var(--milestone-border-color);
|
||||
border-bottom: 5px solid var(--milestone-border-color);
|
||||
}
|
||||
|
||||
:root.dark .gfoldercollapse {
|
||||
color: var(--secondary-text-color);
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
font-size: var(--folder-collapse-font-size);
|
||||
font-family: var(--folder-collapse-font-family);
|
||||
color: var(--secondary-text-color);
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
font-size: var(--folder-collapse-font-size);
|
||||
font-family: var(--folder-collapse-font-family);
|
||||
}
|
||||
|
||||
/* Highlight for collapsible row */
|
||||
:root.dark .gname.ggroupitem {
|
||||
background-color: var(--group-item-bg-color);
|
||||
background-color: var(--group-item-bg-color);
|
||||
}
|
||||
|
||||
/* Form label and selected highlighting */
|
||||
:root.dark .gformlabel {
|
||||
background-color: var(--formlabel-bg-color);
|
||||
color: var(--secondary-text-color);
|
||||
border: var(--formlabel-border-color) 1px solid;
|
||||
background-color: var(--formlabel-bg-color);
|
||||
color: var(--secondary-text-color);
|
||||
border: var(--formlabel-border-color) 1px solid;
|
||||
}
|
||||
|
||||
:root.dark span.gformlabel:hover {
|
||||
background-color: var(--hover-bg-color);
|
||||
border-color: var(--border-color);
|
||||
background-color: var(--hover-bg-color);
|
||||
border-color: var(--border-color);
|
||||
}
|
||||
|
||||
:root.dark span.gselected {
|
||||
background-color: var(--formlabel-selected-bg-color);
|
||||
border-color: var(--task-complete-bg-color);
|
||||
color: var(--nav-link-color);
|
||||
background-color: var(--formlabel-selected-bg-color);
|
||||
border-color: var(--task-complete-bg-color);
|
||||
color: var(--nav-link-color);
|
||||
}
|
||||
|
||||
:root.dark .gantt-inputtable {
|
||||
background-color: var(--input-bg-color);
|
||||
box-sizing: border-box;
|
||||
border: 1px solid var(--input-border-color);
|
||||
color: var(--secondary-text-color);
|
||||
background-color: var(--input-bg-color);
|
||||
box-sizing: border-box;
|
||||
border: 1px solid var(--input-border-color);
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
||||
/* ------------------- for blender generated theme ------------------- */
|
||||
|
||||
|
||||
:root.blender div.gantt,
|
||||
:root.blender .gantt table,
|
||||
:root.blender .gantt td {
|
||||
color: var(--blender-text);
|
||||
border-color: var(--blender-tab-outline);
|
||||
color: var(--blender-text);
|
||||
border-color: var(--blender-tab-outline);
|
||||
}
|
||||
|
||||
:root.blender .gchartgrid,
|
||||
@@ -320,10 +444,11 @@ button:hover {
|
||||
:root.blender .gchartlbl.gcontainercol,
|
||||
:root.blender .gminorheading,
|
||||
:root.blender .gtaskcell {
|
||||
background-color: var(--blender-odd-row);
|
||||
border-color: var(--blender-tab-outline);
|
||||
background-color: var(--blender-odd-row);
|
||||
border-color: var(--blender-tab-outline);
|
||||
}
|
||||
|
||||
|
||||
/* Headings and cell defaults */
|
||||
:root.blender .gtaskheading,
|
||||
:root.blender .gchartlbl.gcontainercol .gmajorheading,
|
||||
@@ -336,13 +461,13 @@ button:hover {
|
||||
:root.blender .gchartlbl.gcontainercol .ggroupitem,
|
||||
:root.blender .glistlbl.gcontainercol .gtaskname,
|
||||
:root.blender .glistlbl.gcontainercol .gtasklist {
|
||||
background-color: var(--blender-panel-header);
|
||||
border-color: var(--blender-tab-outline);
|
||||
background-color: var(--blender-panel-header);
|
||||
border-color: var(--blender-tab-outline);
|
||||
}
|
||||
|
||||
:root.blender .gtaskcellcurrent {
|
||||
background-color: var(--blender-odd-row);
|
||||
border-color: var(--blender-tab-outline);
|
||||
background-color: var(--blender-odd-row);
|
||||
border-color: var(--blender-tab-outline);
|
||||
}
|
||||
|
||||
:root.blender .gtaskheading,
|
||||
@@ -358,102 +483,103 @@ button:hover {
|
||||
:root.blender .gchartlbl,
|
||||
:root.blender .gcontainercol,
|
||||
:root.blender .genddate {
|
||||
color: var(--blender-text);
|
||||
border-color: var(--blender-tab-outline);
|
||||
color: var(--blender-text);
|
||||
border-color: var(--blender-tab-outline);
|
||||
}
|
||||
|
||||
:root.blender .gtaskname > div,
|
||||
:root.blender .gtaskname>div,
|
||||
:root.blender .gtaskname div,
|
||||
:root.blender .gtaskheading div,
|
||||
:root.blender .gtaskname span {
|
||||
color: var(--blender-text);
|
||||
color: var(--blender-text);
|
||||
}
|
||||
|
||||
:root.blender .gantt-chart tr:not(.gname.glineitem):not(.gname.ggroupitem) {
|
||||
background-color: var(--blender-odd-row);
|
||||
background-color: var(--blender-odd-row);
|
||||
}
|
||||
|
||||
:root.blender .gtasklist,
|
||||
:root.blender .gadditional {
|
||||
border: var(--blender-tab-outline) 1px solid;
|
||||
border: var(--blender-tab-outline) 1px solid;
|
||||
}
|
||||
|
||||
:root.blender .glistgrid,
|
||||
:root.blender .glistlbl {
|
||||
/* This is a border that needs background color, somehow. */
|
||||
background-color: var(--blender-tab-outline);
|
||||
border-color: var(--blender-tab-outline);
|
||||
/* This is a border that needs background color, somehow. */
|
||||
background-color: var(--blender-tab-outline);
|
||||
border-color: var(--blender-tab-outline);
|
||||
}
|
||||
|
||||
|
||||
/* Highlight row */
|
||||
:root.blender .gitemhighlight td,
|
||||
:root.blender .gitemhighlight .gantt-inputtable,
|
||||
:root.blender .gitemhighlight .gcaption,
|
||||
:root.blender .gitemhighlight .ggroupcaption {
|
||||
background-color: var(--blender-active-highlight) !important;
|
||||
color: var(--blender-active-text-highlight) !important;
|
||||
background-color: var(--blender-active-highlight) !important;
|
||||
color: var(--blender-active-text-highlight) !important;
|
||||
}
|
||||
|
||||
/* Differentiate Group, Milestone and Ordinary task items (applied to row) */
|
||||
:root.blender .ggroupitem {
|
||||
font-weight: bold;
|
||||
border-color: var(--border-color);
|
||||
font-weight: bold;
|
||||
border-color: var(--border-color);
|
||||
}
|
||||
|
||||
:root.blender .gmileitem,
|
||||
:root.blender .glineitem {
|
||||
background-color: var(--blender-odd-row);
|
||||
background-color: var(--blender-odd-row);
|
||||
}
|
||||
|
||||
/* Task bar caption text styles */
|
||||
:root.blender .gmilecaption,
|
||||
:root.blender .ggroupcaption,
|
||||
:root.blender .gcaption {
|
||||
color: var(--blender-text);
|
||||
color: var(--blender-text);
|
||||
}
|
||||
|
||||
/* Milestones */
|
||||
:root.blender .gmdtop,
|
||||
:root.blender .gmdbottom {
|
||||
border-bottom: 5px solid var(--blender-tab-outline);
|
||||
border-bottom: 5px solid var(--blender-tab-outline);
|
||||
}
|
||||
|
||||
:root.blender .gfoldercollapse {
|
||||
color: var(--blender-text);
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
font-size: var(--folder-collapse-font-size);
|
||||
font-family: var(--folder-collapse-font-family);
|
||||
color: var(--blender-text);
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
font-size: var(--folder-collapse-font-size);
|
||||
font-family: var(--folder-collapse-font-family);
|
||||
}
|
||||
|
||||
/* Highlight for collapsible row */
|
||||
:root.blender .gname.ggroupitem {
|
||||
background-color: var(--blender-panel-background);
|
||||
background-color: var(--blender-panel-background);
|
||||
}
|
||||
|
||||
/* Form label and selected highlighting */
|
||||
:root.blender .gformlabel {
|
||||
background-color: var(--blender-panel-header);
|
||||
color: var(--blender-text);
|
||||
border: var(--blender-tab-outline) 1px solid;
|
||||
background-color: var(--blender-panel-header);
|
||||
color: var(--blender-text);
|
||||
border: var(--blender-tab-outline) 1px solid;
|
||||
}
|
||||
|
||||
:root.blender span.gformlabel:hover,
|
||||
:root.blender span.gselected {
|
||||
background-color: var(--blender-active-highlight);
|
||||
color: var(--blender-active-text-highlight);
|
||||
border-color: var(--blender-tab-outline);
|
||||
background-color: var(--blender-active-highlight);
|
||||
color: var(--blender-active-text-highlight);
|
||||
border-color: var(--blender-tab-outline);
|
||||
}
|
||||
|
||||
:root.blender .gantt-inputtable {
|
||||
background-color: var(--blender-panel-header);
|
||||
box-sizing: border-box;
|
||||
border: 2px solid var(--blender-button-border);
|
||||
color: var(--blender-text);
|
||||
background-color: var(--blender-panel-header);
|
||||
box-sizing: border-box;
|
||||
border: 2px solid var(--blender-button-border);
|
||||
color: var(--blender-text);
|
||||
}
|
||||
|
||||
/* Dark mode scrollbar */
|
||||
:root.blender .frame::-webkit-scrollbar-thumb,
|
||||
:root.blender .frame::-webkit-scrollbar-track {
|
||||
background-color: var(--blender-window-background);
|
||||
}
|
||||
background-color: var(--blender-window-background);
|
||||
}
|
||||
@@ -1,16 +1,228 @@
|
||||
@import url("./shared.css");
|
||||
:root {
|
||||
--font-family: Arial, sans-serif;
|
||||
--base-font-size: 16px;
|
||||
--margin-tiny: 0.125rem;
|
||||
--margin-small: 0.625rem;
|
||||
--margin-medium: 1.25rem;
|
||||
--margin-large: 2.5rem;
|
||||
--padding-tiny: 0.125rem;
|
||||
--padding-small: 0.625rem;
|
||||
--padding-medium: 1rem;
|
||||
--font-size-large: 1.2rem;
|
||||
--logo-height: 1.5rem;
|
||||
--nav-height: 0.625rem;
|
||||
--border-radius: 0.3rem;
|
||||
}
|
||||
|
||||
:root.dark {
|
||||
color-scheme: dark;
|
||||
--bg-color: #252525;
|
||||
--text-color: #e0e0e0;
|
||||
--nav-bg-color: #121212;
|
||||
--nav-border-color: #25682a;
|
||||
--nav-link-color: #fff;
|
||||
--nav-link-hover-color: #3fb449;
|
||||
--warning-color: #FFDB8F;
|
||||
--border-color: #464444;
|
||||
}
|
||||
|
||||
:root.light {
|
||||
color-scheme: light;
|
||||
--bg-color: #ffffff;
|
||||
--text-color: #000000;
|
||||
--nav-bg-color: #f8f8f8;
|
||||
--nav-border-color: #cccccc;
|
||||
--nav-link-color: #000000;
|
||||
--nav-link-hover-color: #38a63d;
|
||||
--warning-color: #FF4500;
|
||||
--border-color: #222;
|
||||
}
|
||||
|
||||
*:not(.tabulator-cell) {
|
||||
border-radius: var(--border-radius) !important;
|
||||
}
|
||||
|
||||
html {
|
||||
font-size: var(--base-font-size);
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--blender-window-background, var(--bg-color));
|
||||
color: var(--blender-text, var(--text-color));
|
||||
margin: 0;
|
||||
font-family: var(--font-family);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
#container {
|
||||
flex: 1;
|
||||
margin-top: var(--margin-medium);
|
||||
margin-left: var(--margin-small);
|
||||
margin-right: var(--margin-small);
|
||||
margin-bottom: var(--margin-medium);
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
nav {
|
||||
background-color: var(--blender-top-bar-header, var(--nav-bg-color));
|
||||
height: var(--nav-height);
|
||||
padding: var(--padding-medium) 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
border-bottom: 2px solid var(--blender-tab-outline, var(--nav-border-color));
|
||||
}
|
||||
|
||||
nav .logo {
|
||||
margin-left: var(--margin-large);
|
||||
height: var(--logo-height);
|
||||
}
|
||||
|
||||
nav ul {
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
nav ul li {
|
||||
margin-right: var(--margin-large);
|
||||
}
|
||||
|
||||
nav ul li a {
|
||||
text-decoration: none;
|
||||
color: var(--blender-text, var(--nav-link-color));
|
||||
}
|
||||
|
||||
nav ul li a:hover,
|
||||
nav ul li a.active {
|
||||
color: var(--blender-selected-object, var(--nav-link-hover-color));
|
||||
}
|
||||
|
||||
.warning,
|
||||
.warning-icon {
|
||||
color: var(--blender-info-warning, var(--warning-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;
|
||||
}
|
||||
|
||||
#client-list {
|
||||
position: absolute;
|
||||
top: calc(0 + var(--nav-height));
|
||||
overflow-y: auto;
|
||||
overflow: hidden;
|
||||
transition: opacity 0.3s ease-out, visibility 0.3s ease-out;
|
||||
border: 1px solid var(--blender-tab-outline, var(--border-color));
|
||||
background-color: var(--blender-panel-background, var(--nav-bg-color));
|
||||
z-index: 1;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
#client-list.show {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
#connected-list-div {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.client {
|
||||
padding: var(--margin-small);
|
||||
border-bottom: 1px solid var(--blender-tab-outline, var(--border-color));
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.client-details {
|
||||
max-height: 0;
|
||||
overflow: hidden;
|
||||
padding-left: var(--padding-small);
|
||||
transition: max-height 0.2s ease-out, padding 0.2s ease-out, opacity 0.1s ease-out;
|
||||
background-color: var(--blender-panel-background, var(--nav-bg-color));
|
||||
margin-top: var(--margin-tiny);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.client-details.show {
|
||||
max-height: none;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.client-detail {
|
||||
border-bottom: 1px solid var(--blender-tab-outline, var(--border-color));
|
||||
padding: var(--padding-small);
|
||||
}
|
||||
|
||||
#show-connected-button {
|
||||
width: auto;
|
||||
background-color: var(--blender-panel-header, var(--bg-color));
|
||||
font-size: var(--base-font-size);
|
||||
color: var(--blender-text, var(--text-color))
|
||||
}
|
||||
|
||||
#show-connected-button:hover,
|
||||
.scroll-button:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.scroll-button {
|
||||
font-size: var(--base-font-size);
|
||||
margin-left: var(--margin-tiny);
|
||||
margin-top: var(--margin-small);
|
||||
}
|
||||
|
||||
footer {
|
||||
background-color: var(--blender-top-bar-header, var(--nav-bg-color));
|
||||
text-align: right;
|
||||
padding: var(--padding-tiny);
|
||||
border-top: 1px solid var(--blender-tab-outline, var(--nav-border-color));
|
||||
}
|
||||
|
||||
footer p {
|
||||
margin: 0;
|
||||
color: var(--blender-text, var(--nav-link-color));
|
||||
margin-right: var(--margin-small);
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
display: block;
|
||||
height: calc(100vh - var(--margin-small));
|
||||
width: 100%;
|
||||
margin-bottom: var(--margin-medium);
|
||||
margin-bottom: var(--margin-large);
|
||||
}
|
||||
|
||||
.csv-table {
|
||||
margin-top: var(--margin-small);
|
||||
}
|
||||
|
||||
button,
|
||||
select {
|
||||
background-color: var(--blender-button-background, var(--bg-color));
|
||||
color: var(--blender-button-text, var(--text-color));
|
||||
border-color: var(--blender-button-border, var(--border-color));
|
||||
transition: filter 0.2s ease;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
filter: brightness(1.2);
|
||||
}
|
||||
|
||||
/* ------------ Overwriting Tabulator CSS rules ------------ */
|
||||
|
||||
:root.light .tabulator-header,
|
||||
@@ -65,28 +277,24 @@
|
||||
color: var(--blender-text);
|
||||
}
|
||||
|
||||
:root.blender .tabulator-selected .tabulator-cell,
|
||||
:root.blender .tabulator-row.tabulator-selected {
|
||||
|
||||
|
||||
:root.blender .tabulator-selected .tabulator-cell {
|
||||
background-color: var(--blender-active-highlight) !important;
|
||||
color: var(--blender-active-text-highlight) !important;
|
||||
}
|
||||
|
||||
:root.blender .tabulator-selected:hover .tabulator-cell,
|
||||
:root.blender .tabulator-row.tabulator-selected:hover {
|
||||
|
||||
:root.blender .tabulator-selected:hover .tabulator-cell {
|
||||
filter: brightness(1.2)
|
||||
}
|
||||
|
||||
:root.blender .tabulator-popup-container {
|
||||
background: var(--blender-panel-background);
|
||||
border: var(--blender-tab-outline);
|
||||
}
|
||||
|
||||
:root.blender .tabulator-menu-item {
|
||||
.tabulator-menu-item {
|
||||
color: var(--blender-text) !important;
|
||||
background-color: var(--blender-panel-background) !important;
|
||||
transition: filter 0.1s ease;
|
||||
}
|
||||
|
||||
:root.blender .tabulator-menu-item:hover {
|
||||
.tabulator-menu-item:hover {
|
||||
filter: brightness(1.4);
|
||||
}
|
||||
@@ -1,238 +0,0 @@
|
||||
:root {
|
||||
--font-family: Arial, sans-serif;
|
||||
--base-font-size: 16px;
|
||||
--margin-tiny: 0.125rem;
|
||||
--margin-small: 0.625rem;
|
||||
--margin-medium: 1.25rem;
|
||||
--margin-large: 2.5rem;
|
||||
--padding-tiny: 0.125rem;
|
||||
--padding-small: 0.625rem;
|
||||
--padding-medium: 1rem;
|
||||
--font-size-small: 1rem;
|
||||
--font-size-large: 1.2rem;
|
||||
--logo-height: 1.5rem;
|
||||
--nav-height: 0.625rem;
|
||||
--border-radius: 0.3rem;
|
||||
--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;
|
||||
--border-color: #464444;
|
||||
}
|
||||
|
||||
: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;
|
||||
--border-color: #222;
|
||||
}
|
||||
|
||||
*:not(td, tr, td, th, button, .gmain) {
|
||||
border-radius: var(--border-radius) !important;
|
||||
}
|
||||
|
||||
html {
|
||||
font-size: var(--base-font-size);
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--blender-window-background, var(--bg-color));
|
||||
color: var(--blender-text, var(--primary-text-color));
|
||||
margin: 0;
|
||||
font-family: var(--font-family);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
#container {
|
||||
flex: 1;
|
||||
padding: var(--margin-small);
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
nav {
|
||||
background-color: var(--blender-top-bar-header, var(--nav-bg-color));
|
||||
height: var(--nav-height);
|
||||
padding: var(--padding-medium) 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
border-bottom: 2px solid var(--blender-tab-outline, var(--nav-border-color));
|
||||
}
|
||||
|
||||
nav .logo {
|
||||
margin-left: var(--margin-large);
|
||||
height: var(--logo-height);
|
||||
}
|
||||
|
||||
nav ul {
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
nav ul li {
|
||||
margin-right: var(--margin-large);
|
||||
}
|
||||
|
||||
nav ul li a {
|
||||
text-decoration: none;
|
||||
color: var(--blender-text, var(--nav-link-color));
|
||||
}
|
||||
|
||||
nav ul li a:hover,
|
||||
nav ul li a.active {
|
||||
color: var(--blender-selected-object, var(--nav-link-hover-color));
|
||||
}
|
||||
|
||||
.warning,
|
||||
.warning-icon {
|
||||
color: var(--blender-info-warning, var(--warning-color));
|
||||
}
|
||||
|
||||
#toggle-theme:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
#client-list {
|
||||
position: absolute;
|
||||
top: calc(0 + var(--nav-height));
|
||||
overflow-y: auto;
|
||||
overflow: hidden;
|
||||
transition: opacity 0.3s ease-out, visibility 0.3s ease-out;
|
||||
border: 1px solid var(--blender-tab-outline, var(--border-color));
|
||||
background-color: var(--blender-panel-background, var(--nav-bg-color));
|
||||
z-index: 1;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
#client-list.show {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
#connected-list-div {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.client {
|
||||
padding: var(--margin-small);
|
||||
border-bottom: 1px solid var(--blender-tab-outline, var(--border-color));
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.client-details {
|
||||
max-height: 0;
|
||||
overflow: hidden;
|
||||
padding-left: var(--padding-small);
|
||||
transition: max-height 0.2s ease-out, padding 0.2s ease-out,
|
||||
opacity 0.1s ease-out;
|
||||
background-color: var(--blender-panel-background, var(--nav-bg-color));
|
||||
margin-top: var(--margin-tiny);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.client-details.show {
|
||||
max-height: none;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.client-detail {
|
||||
border-bottom: 1px solid var(--blender-tab-outline, var(--border-color));
|
||||
padding: var(--padding-small);
|
||||
}
|
||||
|
||||
#show-connected-button {
|
||||
width: auto;
|
||||
background-color: var(--blender-panel-header, var(--bg-color));
|
||||
font-size: var(--base-font-size);
|
||||
color: var(--blender-text, var(--text-color));
|
||||
}
|
||||
|
||||
#show-connected-button:hover,
|
||||
.scroll-button:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.scroll-button {
|
||||
font-size: var(--base-font-size);
|
||||
margin-left: var(--margin-tiny);
|
||||
margin-top: var(--margin-small);
|
||||
}
|
||||
|
||||
footer {
|
||||
background-color: var(--blender-top-bar-header, var(--nav-bg-color));
|
||||
text-align: right;
|
||||
padding: var(--padding-tiny);
|
||||
border-top: 1px solid var(--blender-tab-outline, var(--nav-border-color));
|
||||
}
|
||||
|
||||
footer p {
|
||||
margin: 0;
|
||||
color: var(--blender-text, var(--nav-link-color));
|
||||
margin-right: var(--margin-small);
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
button,
|
||||
select {
|
||||
background-color: var(--blender-button-background, var(--bg-color));
|
||||
color: var(--blender-button-text, var(--text-color));
|
||||
border-color: var(--blender-button-border, var(--border-color));
|
||||
transition: filter 0.2s ease;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
filter: brightness(1.2);
|
||||
}
|
||||
|
||||
.row-container {
|
||||
border-collapse: collapse;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.column-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
label {
|
||||
margin-right: var(--margin-small);
|
||||
margin-bottom: var(--margin-small);
|
||||
}
|
||||
@@ -1,426 +0,0 @@
|
||||
import { CostUI } from "./utilities/costui.js";
|
||||
|
||||
const connectedClients = {};
|
||||
let socket;
|
||||
|
||||
let loadedSchedules = {};
|
||||
|
||||
$(document).ready(function () {
|
||||
var defaultTheme = "blender";
|
||||
var theme = localStorage.getItem("theme") || defaultTheme;
|
||||
setTheme(theme);
|
||||
|
||||
connectSocket();
|
||||
CostUI.createRibbon();
|
||||
});
|
||||
|
||||
function connectSocket() {
|
||||
const url = "ws://localhost:" + SOCKET_PORT + "/web";
|
||||
socket = io(url);
|
||||
|
||||
socket.on("blender_connect", handleBlenderConnect);
|
||||
socket.on("blender_disconnect", handleBlenderDisconnect);
|
||||
socket.on("connected_clients", handleConnectedClients);
|
||||
socket.on("theme_data", handleThemeData);
|
||||
socket.on("connect", handleWebConnect);
|
||||
socket.on("predefined_types", handlePredefinedTypes);
|
||||
socket.on("cost_schedules", handleCostSchedulesData);
|
||||
socket.on("cost_items", handleCostItemsData);
|
||||
socket.on("cost_values", handleCostValuesData);
|
||||
socket.on("cost_value", handleCostValueData);
|
||||
socket.on("quantities", handleEditQuantities);
|
||||
}
|
||||
|
||||
function handleEditQuantities(data) {
|
||||
const costItemId = data.data["quantities"]["cost_item_id"];
|
||||
const products = data.data["quantities"]["selected_products"];
|
||||
const assigned_products = data.data["quantities"]["assigned_products"];
|
||||
const quantityNames = data.data["quantities"]["product_quantity_names"];
|
||||
const costQuantities = data.data["quantities"]["cost_quantities"];
|
||||
|
||||
CostUI.enableEditingQuantities({
|
||||
costItemId: costItemId,
|
||||
selectedProducts: products,
|
||||
assignedProducts: assigned_products,
|
||||
quantityNames: quantityNames,
|
||||
costQuantities: costQuantities,
|
||||
callbacks: {
|
||||
addProductAssignments: addProductAssignments,
|
||||
enableEditingQuantities: enableEditingQuantities,
|
||||
addQuantity: addQuantity,
|
||||
editQuantity: editQuantity,
|
||||
deleteQuantity: deleteQuantity,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function addQuantity(costItemId, ifcClass) {
|
||||
executeOperator({
|
||||
type: "AddCostItemQuantity",
|
||||
costItemId: costItemId,
|
||||
ifcClass: ifcClass,
|
||||
});
|
||||
|
||||
//CostUI.addQuantity(costItemId, quantityName);
|
||||
}
|
||||
|
||||
function editQuantity(costItemId, quantityId, attributes) {
|
||||
executeOperator({
|
||||
type: "editCostItemQuantity",
|
||||
costItemId: costItemId,
|
||||
quantityId: quantityId,
|
||||
attributes: attributes,
|
||||
});
|
||||
}
|
||||
|
||||
function deleteQuantity(costItemId, quantityId) {
|
||||
executeOperator({
|
||||
type: "deleteCostItemQuantity",
|
||||
costItemId: costItemId,
|
||||
quantityId: quantityId,
|
||||
});
|
||||
}
|
||||
|
||||
function handlePredefinedTypes(data) {
|
||||
CostUI.enableAddingCostSchedule(
|
||||
data.data["predefined_types"],
|
||||
addCostSchedule
|
||||
);
|
||||
}
|
||||
|
||||
function addProductAssignments({ costItemId, selectedQuantity, productIds }) {
|
||||
executeOperator({
|
||||
type: "addProductAssignments",
|
||||
costItemId: costItemId,
|
||||
propName: selectedQuantity,
|
||||
productIds: productIds,
|
||||
});
|
||||
}
|
||||
|
||||
function addCostSchedule(name, predefinedType) {
|
||||
executeOperator({
|
||||
type: "addCostSchedule",
|
||||
name: name,
|
||||
predefinedType: predefinedType,
|
||||
});
|
||||
}
|
||||
|
||||
function handleBlenderConnect(blenderId) {
|
||||
if (!connectedClients.hasOwnProperty(blenderId)) {
|
||||
connectedClients[blenderId] = { shown: false, ifc_file: "" };
|
||||
}
|
||||
|
||||
$("#blender-count").text(function (i, text) {
|
||||
return parseInt(text, 10) + 1;
|
||||
});
|
||||
}
|
||||
|
||||
function handleBlenderDisconnect(blenderId) {
|
||||
if (connectedClients.hasOwnProperty(blenderId)) {
|
||||
delete connectedClients[blenderId];
|
||||
removeTableElement(blenderId);
|
||||
}
|
||||
|
||||
$("#blender-count").text(function (i, text) {
|
||||
return parseInt(text, 10) - 1;
|
||||
});
|
||||
}
|
||||
|
||||
function removeTableElement(blenderId) {
|
||||
$("#cost-items-" + blenderId).remove();
|
||||
}
|
||||
|
||||
const costValueCallbacks = {
|
||||
editCostValues: editCostValues,
|
||||
addCostValue: addCostValue,
|
||||
deleteCostValue: deleteCostValue,
|
||||
};
|
||||
|
||||
function handleCostValueData(data) {
|
||||
const costItemId = data.data["cost_value"]["cost_item_id"];
|
||||
const costValueId = data.data["cost_value"]["cost_value_id"];
|
||||
CostUI.addNewCostValueRow(costItemId, costValueId, costValueCallbacks);
|
||||
}
|
||||
|
||||
function deleteCostValue(costItemId, costValueId) {
|
||||
executeOperator({
|
||||
type: "deleteCostValue",
|
||||
costItemId: costItemId,
|
||||
costValueId: costValueId,
|
||||
});
|
||||
}
|
||||
|
||||
function handleConnectedClients(data) {
|
||||
$("#blender-count").text(data.length);
|
||||
|
||||
data.forEach(function (id) {
|
||||
connectedClients[id] = { shown: false, ifc_file: "" };
|
||||
});
|
||||
}
|
||||
|
||||
function handleCostValuesData(data) {
|
||||
CostUI.createCostValuesForm({
|
||||
costValues: data.data["cost_values"]["cost_values"],
|
||||
costItemId: data.data["cost_values"]["cost_item_id"],
|
||||
callbacks: {
|
||||
editCostValues: editCostValues,
|
||||
addCostValue: addCostValue,
|
||||
deleteCostValue: deleteCostValue,
|
||||
},
|
||||
});
|
||||
|
||||
CostUI.highlightElement(data.data["cost_values"]["cost_item_id"]);
|
||||
}
|
||||
|
||||
function addCostValue(costItemId) {
|
||||
executeOperator({ type: "addCostValue", costItemId: costItemId });
|
||||
}
|
||||
|
||||
function editCostValues(costItemId, costValues) {
|
||||
executeOperator({
|
||||
type: "editCostValues",
|
||||
costItemId: costItemId,
|
||||
costValues: costValues,
|
||||
});
|
||||
}
|
||||
|
||||
function deleteCostItem(costItemId) {
|
||||
executeOperator({ type: "deleteCostItem", costItemId: costItemId });
|
||||
}
|
||||
|
||||
function handleThemeData(themeData) {
|
||||
function arrayToRgbString(arr) {
|
||||
const [r, g, b, a] = arr.map((num) => Math.round(num * 255));
|
||||
if (a !== undefined) {
|
||||
return `rgba(${r}, ${g}, ${b}, ${a})`;
|
||||
}
|
||||
return `rgb(${r}, ${g}, ${b})`;
|
||||
}
|
||||
|
||||
function generateCssVariableRule(theme) {
|
||||
let cssVariables = ":root.blender {\n";
|
||||
for (const key in theme) {
|
||||
const cssVariableName = `--blender-${key.replace(/_/g, "-")}`;
|
||||
const cssVariableValue = arrayToRgbString(theme[key]);
|
||||
cssVariables += ` ${cssVariableName}: ${cssVariableValue};\n`;
|
||||
}
|
||||
cssVariables += "}";
|
||||
return cssVariables;
|
||||
}
|
||||
|
||||
const cssRule = generateCssVariableRule(themeData.theme);
|
||||
|
||||
var styleElement = $("#index-stylesheet")[0];
|
||||
if (styleElement) {
|
||||
var sheet = styleElement.sheet || styleElement.styleSheet;
|
||||
sheet.insertRule(cssRule, sheet.cssRules.length);
|
||||
}
|
||||
}
|
||||
|
||||
function setTheme(theme) {
|
||||
$("html").removeClass("light dark blender").addClass(theme);
|
||||
$(":root").css("color-scheme", theme);
|
||||
if (theme === "light") {
|
||||
$("#toggle-theme").html('<i class="fas fa-sun"></i>');
|
||||
} else if (theme === "dark") {
|
||||
$("#toggle-theme").html('<i class="fas fa-moon"></i>');
|
||||
} else if (theme === "blender") {
|
||||
$("#toggle-theme").html('<i class="fas fa-adjust"></i>');
|
||||
}
|
||||
localStorage.setItem("theme", theme);
|
||||
}
|
||||
|
||||
function toggleClientList() {
|
||||
var clientList = $("#client-list");
|
||||
|
||||
if (clientList.hasClass("show")) {
|
||||
clientList.removeClass("show");
|
||||
return;
|
||||
}
|
||||
|
||||
clientList.empty();
|
||||
var counter = 0;
|
||||
|
||||
$.each(connectedClients, function (id, client) {
|
||||
counter++;
|
||||
|
||||
const dropdownIcon = $("<i>")
|
||||
.addClass("fas fa-chevron-down")
|
||||
.css("margin-left", "0.5rem");
|
||||
|
||||
const clientDiv = $("<div>")
|
||||
.addClass("client")
|
||||
.text(client.ifc_file || "Blender " + counter);
|
||||
|
||||
clientDiv.append(dropdownIcon);
|
||||
|
||||
const clientDetailsDiv = $("<div>").addClass("client-details");
|
||||
|
||||
if (id) {
|
||||
const clientId = $("<div>")
|
||||
.addClass("client-detail")
|
||||
.text(`Blender ID: ${id}`);
|
||||
clientDetailsDiv.append(clientId);
|
||||
}
|
||||
|
||||
if (!client.has_drawings) {
|
||||
const clientShown = $("<div>")
|
||||
.addClass("client-detail")
|
||||
.text("No drawings exist for this IFC File yet");
|
||||
clientDetailsDiv.append(clientShown);
|
||||
} else {
|
||||
const clientNumbers = $("<div>")
|
||||
.addClass("client-detail")
|
||||
.text(
|
||||
`${allDrawings[client.ifc_file].drawings.length} Drawing(s),
|
||||
${allDrawings[client.ifc_file].sheets.length} Sheet(s)`
|
||||
);
|
||||
clientDetailsDiv.append(clientNumbers);
|
||||
}
|
||||
|
||||
clientDiv.append(clientDetailsDiv);
|
||||
|
||||
clientDiv.on("click", function () {
|
||||
clientDetailsDiv.toggleClass("show");
|
||||
});
|
||||
|
||||
clientList.append(clientDiv);
|
||||
});
|
||||
clientList.addClass("show");
|
||||
}
|
||||
|
||||
function addCostItem(costItemId) {
|
||||
executeOperator({ type: "addCostItem", costItemId: costItemId });
|
||||
}
|
||||
|
||||
function editCostItemName(costItemId, name) {
|
||||
executeOperator({
|
||||
type: "editCostItemName",
|
||||
costItemId: costItemId,
|
||||
name: name,
|
||||
});
|
||||
}
|
||||
|
||||
function selectAssignedElements(costItemId) {
|
||||
executeOperator({ type: "selectAssignedElements", costItemId: costItemId });
|
||||
}
|
||||
|
||||
function handleWebConnect() {
|
||||
executeOperator({ type: "getPredefinedTypes" });
|
||||
getCostSchedules();
|
||||
}
|
||||
|
||||
function addSummaryCostItem(costScheduleId) {
|
||||
executeOperator({
|
||||
type: "addSummaryCostItem",
|
||||
costScheduleId: costScheduleId,
|
||||
});
|
||||
}
|
||||
|
||||
function handleCostSchedulesData(data) {
|
||||
const blenderId = data.blenderId;
|
||||
const loadedSchedule = loadedSchedules[blenderId]
|
||||
? loadedSchedules[blenderId]
|
||||
: null;
|
||||
const costSchedules = data.data["cost_schedules"]["cost_schedules"];
|
||||
if (costSchedules.length === 0) {
|
||||
return;
|
||||
}
|
||||
const costScheduleDiv = document.getElementById("cost-schedules");
|
||||
costScheduleDiv.innerHTML = "";
|
||||
costSchedules.forEach((costSchedule) => {
|
||||
const predefinedType = CostUI.Text(
|
||||
"Predefined Type: " + costSchedule.PredefinedType,
|
||||
"fa-solid fa-paperclip"
|
||||
);
|
||||
const updatedDate = new Date(costSchedule.UpdateDate).toLocaleDateString();
|
||||
const updatedOn = CostUI.Text(
|
||||
"Updated On: " + updatedDate,
|
||||
"fa-solid fa-pen-nib"
|
||||
);
|
||||
const div = document.createElement("div");
|
||||
|
||||
div.appendChild(predefinedType);
|
||||
div.appendChild(updatedOn);
|
||||
const callback = () => loadCostSchedule(costSchedule.id, blenderId);
|
||||
|
||||
const card = CostUI.createCard(
|
||||
costSchedule.id,
|
||||
costSchedule.Name,
|
||||
div,
|
||||
callback
|
||||
);
|
||||
costScheduleDiv.append(card);
|
||||
});
|
||||
loadedSchedule ? CostUI.highlightElement("schedule-" + loadedSchedule) : null;
|
||||
}
|
||||
|
||||
function handleCostItemsData(data) {
|
||||
const cards = document.querySelectorAll("[id^='schedule-']");
|
||||
cards.forEach((card) => {
|
||||
if (card.id === "schedule-" + data.data["cost_items"]["cost_schedule_id"]) {
|
||||
card.classList.add("highlighted");
|
||||
} else {
|
||||
card.classList.remove("highlighted");
|
||||
}
|
||||
});
|
||||
|
||||
const costScheduleId = data.data["cost_items"]["cost_schedule_id"];
|
||||
loadedSchedules[data.blenderId] = costScheduleId;
|
||||
CostUI.highlightElement("schedule-" + costScheduleId);
|
||||
|
||||
const currency = data.data["cost_items"]["currency"]
|
||||
? data.data["cost_items"]["currency"]["name"]
|
||||
: "Undefined";
|
||||
|
||||
CostUI.createCostSchedule({
|
||||
costItems: data.data["cost_items"]["cost_items"],
|
||||
currency: currency,
|
||||
costScheduleId: costScheduleId,
|
||||
blenderID: data.blenderId,
|
||||
callbacks: {
|
||||
addCostItem: addCostItem,
|
||||
deleteCostItem: deleteCostItem,
|
||||
duplicateCostItem: duplicateCostItem,
|
||||
selectAssignedElements: selectAssignedElements,
|
||||
editCostItemName: editCostItemName,
|
||||
enableEditingCostValues: enableEditingCostValues,
|
||||
addSummaryCostItem: addSummaryCostItem,
|
||||
enableEditingQuantities: enableEditingQuantities,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function enableEditingQuantities(costItemId) {
|
||||
executeOperator({ type: "enableEditingQuantities", costItemId: costItemId });
|
||||
}
|
||||
|
||||
function duplicateCostItem(costItemId) {
|
||||
executeOperator({ type: "duplicateCostItem", costItemId: costItemId });
|
||||
}
|
||||
|
||||
function executeOperator(operator, blenderId) {
|
||||
const msg = {
|
||||
sourcePage: "cost",
|
||||
operator: operator,
|
||||
};
|
||||
if (blenderId !== undefined) {
|
||||
msg.BlenderId = blenderId;
|
||||
}
|
||||
socket.emit("web_operator", msg);
|
||||
}
|
||||
|
||||
function loadCostSchedule(costScheduleId, blenderId) {
|
||||
executeOperator(
|
||||
{ type: "loadCostSchedule", costScheduleId: costScheduleId },
|
||||
blenderId
|
||||
);
|
||||
}
|
||||
|
||||
function getCostSchedules(blenderId) {
|
||||
executeOperator({ type: "getCostSchedules" }, blenderId);
|
||||
}
|
||||
|
||||
function enableEditingCostValues(costItemId) {
|
||||
executeOperator({ type: "enableEditingCostValues", costItemId: costItemId });
|
||||
}
|
||||
@@ -1,146 +0,0 @@
|
||||
// A global variable to hold the WebSocket connection
|
||||
let socket;
|
||||
|
||||
// Document ready function
|
||||
// This function is triggered once the DOM is fully loaded.
|
||||
// It sets the theme for the UI and
|
||||
// establishes the WebSocket connection by calling connectSocket().
|
||||
$(document).ready(function () {
|
||||
var defaultTheme = "blender"; // Default theme to be applied
|
||||
var theme = localStorage.getItem("theme") || defaultTheme; // Retrieve the stored theme or use the default
|
||||
setTheme(theme); // Apply the theme
|
||||
|
||||
connectSocket();
|
||||
});
|
||||
|
||||
// Function to establish WebSocket connection
|
||||
// This function constructs the WebSocket connection URL using the SOCKET_PORT variable
|
||||
// and initializes the socket connection. It also registers event handlers to listen
|
||||
// for various events emitted by the server, such as 'blender_connect', 'blender_disconnect',
|
||||
// 'theme_data', and 'demo_data'.
|
||||
function connectSocket() {
|
||||
const url = "ws://localhost:" + SOCKET_PORT + "/web";
|
||||
socket = io(url);
|
||||
console.log("socket: ", socket);
|
||||
|
||||
// Register socket event handlers
|
||||
socket.on("blender_connect", handleBlenderConnect);
|
||||
socket.on("blender_disconnect", handleBlenderDisconnect);
|
||||
socket.on("theme_data", handleThemeData);
|
||||
socket.on("demo_data", handleDemoData);
|
||||
}
|
||||
|
||||
// Function to handle 'blender_connect' event
|
||||
// This function is triggered when a new Bonsai instance connects to the server.
|
||||
// The 'blenderId' is a unique identifier assigned by the server to each Bonsai instance.
|
||||
// It helps distinguish between different instances of Bonsai connected to the server.
|
||||
// The 'blenderId' is used in various parts of the code to ensure that messages and data
|
||||
// are correctly associated with the appropriate Bonsai instance.
|
||||
function handleBlenderConnect(blenderId) {
|
||||
console.log("blender connected: ", blenderId);
|
||||
}
|
||||
|
||||
// Function to handle 'blender_disconnect' event
|
||||
// This function is triggered when a Bonsai instance disconnects from the server.
|
||||
// The 'blenderId' is used here to log which specific Bonsai instance has disconnected.
|
||||
function handleBlenderDisconnect(blenderId) {
|
||||
console.log("blender disconnected: ", blenderId);
|
||||
}
|
||||
|
||||
// Function to handle 'demo_data' event
|
||||
// This function processes the demo data received from the server.
|
||||
// It extracts the message and the Bonsai instance ID ('blenderId'),
|
||||
// and updates the DOM to display the message. The 'blenderId' is used
|
||||
// to differentiate between messages from different Bonsai instances.
|
||||
// If the message for the specific Bonsai instance is already displayed, it updates the content;
|
||||
// otherwise, it creates a new element to display the message.
|
||||
function handleDemoData(demoData) {
|
||||
console.log(demoData);
|
||||
const message = demoData["data"]["demo_message"];
|
||||
const blenderId = demoData["blenderId"];
|
||||
const id = "message-" + blenderId;
|
||||
const messageHeader = $("#" + id);
|
||||
const messageText = `Bonsai instance with ID: ${blenderId} sent the message: ${message}`;
|
||||
|
||||
if (messageHeader[0] === undefined) {
|
||||
const newMessageElement = $("<h3>", { id: id }).text(messageText);
|
||||
$("#message-container").prepend(newMessageElement);
|
||||
} else {
|
||||
messageHeader.text(messageText);
|
||||
messageHeader.prependTo("#message-container");
|
||||
}
|
||||
}
|
||||
|
||||
// Function to send a message to the server
|
||||
// This function is triggered when the user submits a message.
|
||||
// It collects the message from the input field, constructs a message object,
|
||||
// and emits the 'web_operator' event to the server with the message data.
|
||||
// If 'blenderId' is specified, the message is sent to a specific Bonsai instance.
|
||||
function sendMessage() {
|
||||
const inputMessage = $("#input-message").val();
|
||||
|
||||
const msg = {
|
||||
sourcePage: "demo",
|
||||
// blenderId: BlenderId, // specify the target Bonsai instance
|
||||
operator: {
|
||||
type: "message",
|
||||
message: inputMessage,
|
||||
},
|
||||
};
|
||||
socket.emit("web_operator", msg);
|
||||
}
|
||||
|
||||
function handleThemeData(themeData) {
|
||||
// console.log(themeData);
|
||||
|
||||
function arrayToRgbString(arr) {
|
||||
const [r, g, b, a] = arr.map((num) => Math.round(num * 255));
|
||||
if (a !== undefined) {
|
||||
return `rgba(${r}, ${g}, ${b}, ${a})`;
|
||||
}
|
||||
return `rgb(${r}, ${g}, ${b})`;
|
||||
}
|
||||
|
||||
function generateCssVariableRule(theme) {
|
||||
let cssVariables = ":root.blender {\n";
|
||||
for (const key in theme) {
|
||||
const cssVariableName = `--blender-${key.replace(/_/g, "-")}`;
|
||||
const cssVariableValue = arrayToRgbString(theme[key]);
|
||||
cssVariables += ` ${cssVariableName}: ${cssVariableValue};\n`;
|
||||
}
|
||||
cssVariables += "}";
|
||||
return cssVariables;
|
||||
}
|
||||
|
||||
const cssRule = generateCssVariableRule(themeData.theme);
|
||||
console.log(cssRule);
|
||||
|
||||
var styleElement = $("#demo-stylesheet")[0];
|
||||
if (styleElement) {
|
||||
var sheet = styleElement.sheet || styleElement.styleSheet;
|
||||
sheet.insertRule(cssRule, sheet.cssRules.length);
|
||||
}
|
||||
}
|
||||
|
||||
function setTheme(theme) {
|
||||
$("html").removeClass("light dark blender").addClass(theme);
|
||||
$(":root").css("color-scheme", theme);
|
||||
if (theme === "light") {
|
||||
$("#toggle-theme").html('<i class="fas fa-sun"></i>');
|
||||
} else if (theme === "dark") {
|
||||
$("#toggle-theme").html('<i class="fas fa-moon"></i>');
|
||||
} else if (theme === "blender") {
|
||||
$("#toggle-theme").html('<i class="fas fa-adjust"></i>');
|
||||
}
|
||||
localStorage.setItem("theme", theme);
|
||||
}
|
||||
|
||||
function toggleTheme() {
|
||||
if ($("html").hasClass("light")) {
|
||||
setTheme("dark");
|
||||
} else if ($("html").hasClass("dark")) {
|
||||
setTheme("blender");
|
||||
} else {
|
||||
setTheme("light");
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,7 @@ function connectSocket() {
|
||||
socket.on("blender_disconnect", handleBlenderDisconnect);
|
||||
socket.on("connect", handleWebConnect);
|
||||
socket.on("connected_clients", handleConnectedClients);
|
||||
//socket.on("default_data", handleDefaultData);
|
||||
socket.on("default_data", handleDefaultData);
|
||||
socket.on("theme_data", handleThemeData);
|
||||
socket.on("drawings_data", handleDrawingsData);
|
||||
socket.on("svg_data", handleSvgData);
|
||||
@@ -37,7 +37,6 @@ function connectSocket() {
|
||||
|
||||
// function used to get drawings data from Bonsai
|
||||
function handleWebConnect() {
|
||||
console.log("Handle Web Connect")
|
||||
getDrawingsData();
|
||||
}
|
||||
|
||||
@@ -74,6 +73,7 @@ function handleDefaultData(data) {
|
||||
|
||||
// Function to handle 'blender_disconnect' event
|
||||
function handleBlenderDisconnect(blenderId) {
|
||||
console.log("blender disconnected: ", blenderId);
|
||||
if (connectedClients.hasOwnProperty(blenderId)) {
|
||||
delete connectedClients[blenderId];
|
||||
removeSelectOption(blenderId);
|
||||
@@ -97,6 +97,8 @@ function handleConnectedClients(data) {
|
||||
}
|
||||
|
||||
function handleThemeData(themeData) {
|
||||
// console.log(themeData);
|
||||
|
||||
function arrayToRgbString(arr) {
|
||||
const [r, g, b, a] = arr.map((num) => Math.round(num * 255));
|
||||
if (a !== undefined) {
|
||||
@@ -127,7 +129,6 @@ function handleThemeData(themeData) {
|
||||
}
|
||||
|
||||
function handleDrawingsData(data) {
|
||||
console.log("Running handleDrawingsData");
|
||||
const blenderId = data["blenderId"];
|
||||
|
||||
console.log(data);
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { CostUI } from './utilities/costui.js';
|
||||
|
||||
// keeps track of blenders connected in form of
|
||||
// shown:bool, workSchedule: {}, ganttTasks: {}, warning: bool
|
||||
const connectedClients = {};
|
||||
@@ -32,25 +30,20 @@ $(document).ready(function () {
|
||||
function connectSocket() {
|
||||
const url = "ws://localhost:" + SOCKET_PORT + "/web";
|
||||
socket = io(url);
|
||||
console.log("socket: ", socket);
|
||||
|
||||
// Register socket event handlers
|
||||
socket.on("blender_connect", handleBlenderConnect);
|
||||
socket.on("blender_disconnect", handleBlenderDisconnect);
|
||||
socket.on("connected_clients", handleConnectedClients);
|
||||
socket.on("connect", handleWebConnect);
|
||||
socket.on("theme_data", handleThemeData);
|
||||
socket.on("gantt_data", handleGanttData);
|
||||
socket.on("default_data", handleDefaultData);
|
||||
socket.on("work_schedule_info", handleWorkScheduleData);
|
||||
}
|
||||
|
||||
// function used to get drawings data from Bonsai
|
||||
function handleWebConnect() {
|
||||
getWorkScheduleData();
|
||||
}
|
||||
|
||||
// Function to handle 'blender_connect' event
|
||||
function handleBlenderConnect(blenderId) {
|
||||
console.log("blender connected: ", blenderId);
|
||||
if (!connectedClients.hasOwnProperty(blenderId)) {
|
||||
connectedClients[blenderId] = {
|
||||
shown: false,
|
||||
@@ -66,9 +59,9 @@ function handleBlenderConnect(blenderId) {
|
||||
|
||||
// Function to handle 'blender_disconnect' event
|
||||
function handleBlenderDisconnect(blenderId) {
|
||||
console.log("blender disconnected: ", blenderId);
|
||||
if (connectedClients.hasOwnProperty(blenderId)) {
|
||||
delete connectedClients[blenderId];
|
||||
removeWorkScheduleInfo(blenderId);
|
||||
removeGanttElement(blenderId);
|
||||
}
|
||||
|
||||
@@ -79,6 +72,7 @@ function handleBlenderDisconnect(blenderId) {
|
||||
|
||||
function handleConnectedClients(data) {
|
||||
$("#blender-count").text(data.length);
|
||||
// console.log(data);
|
||||
data.forEach(function (id) {
|
||||
connectedClients[id] = {
|
||||
shown: false,
|
||||
@@ -89,6 +83,8 @@ function handleConnectedClients(data) {
|
||||
}
|
||||
|
||||
function handleThemeData(themeData) {
|
||||
// console.log(themeData);
|
||||
|
||||
function arrayToRgbString(arr) {
|
||||
const [r, g, b, a] = arr.map((num) => Math.round(num * 255));
|
||||
if (a !== undefined) {
|
||||
@@ -109,6 +105,7 @@ function handleThemeData(themeData) {
|
||||
}
|
||||
|
||||
const cssRule = generateCssVariableRule(themeData.theme);
|
||||
console.log(cssRule);
|
||||
|
||||
var styleElement = $("#gantt-stylesheet")[0];
|
||||
if (styleElement) {
|
||||
@@ -117,28 +114,12 @@ function handleThemeData(themeData) {
|
||||
}
|
||||
}
|
||||
|
||||
function handleWorkScheduleData(data) {
|
||||
const blenderId = data["blenderId"];
|
||||
const workSchedulesMainDiv = $("#work_schedules");
|
||||
const workSchedulesDiv = $("<div>").attr("id", "work_schedules-" + blenderId);
|
||||
workSchedulesMainDiv.append(workSchedulesDiv);
|
||||
|
||||
const workSchedules = data["data"]["work_schedule_info"];
|
||||
|
||||
workSchedules.forEach((workSchedule) => {
|
||||
const mainContainer = CostUI.Text(
|
||||
"Created On: " + new Date(workSchedule.CreationDate).toLocaleDateString(),
|
||||
"fa-solid fa-pen-nib"
|
||||
);
|
||||
const callback = () => loadWorkSchedule(workSchedule.id);
|
||||
const card = CostUI.createCard(workSchedule.id, workSchedule.Name,mainContainer, callback);
|
||||
workSchedulesDiv.append(card);
|
||||
});
|
||||
}
|
||||
|
||||
// Function to handle 'gantt_data' event
|
||||
function handleGanttData(data) {
|
||||
const blenderId = data["blenderId"];
|
||||
|
||||
console.log(data);
|
||||
|
||||
const filename = data["data"]["ifc_file"];
|
||||
const ganttTasks = data["data"]["gantt_data"]["tasks"];
|
||||
const ganttWorkSched = data["data"]["gantt_data"]["work_schedule"];
|
||||
@@ -175,6 +156,7 @@ 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
|
||||
@@ -446,6 +428,7 @@ function editValue(list, task, event, cell, column) {
|
||||
value: event.target.value,
|
||||
},
|
||||
};
|
||||
console.log("web operator: " + msg);
|
||||
socket.emit("web_operator", msg);
|
||||
}
|
||||
|
||||
@@ -568,33 +551,3 @@ function toggleClientList() {
|
||||
|
||||
clientList.addClass("show");
|
||||
}
|
||||
|
||||
function loadWorkSchedule(workScheduleId) {
|
||||
const msg = {
|
||||
sourcePage: "gantt",
|
||||
operator: {
|
||||
type: "loadWorkSchedule",
|
||||
workScheduleId: workScheduleId,
|
||||
},
|
||||
};
|
||||
socket.emit("web_operator", msg);
|
||||
}
|
||||
|
||||
function removeWorkScheduleInfo(blenderId) {
|
||||
$("#work_schedules-"+ blenderId).empty();
|
||||
}
|
||||
|
||||
function getWorkScheduleData(blenderId) {
|
||||
const msg = {
|
||||
sourcePage: "gantt",
|
||||
operator: {
|
||||
type: "getWorkSchedules",
|
||||
},
|
||||
};
|
||||
|
||||
if (blenderId !== undefined) {
|
||||
msg.BlenderId = blenderId;
|
||||
}
|
||||
|
||||
socket.emit("web_operator", msg);
|
||||
}
|
||||
|
||||
@@ -144,7 +144,6 @@ function addTableElement(blenderId, csvData, filename) {
|
||||
}
|
||||
|
||||
// console.log(connectedClients[blenderId]);
|
||||
|
||||
const tableContainer = $("<div></div>")
|
||||
.addClass("table-container")
|
||||
.attr("id", "container-" + blenderId);
|
||||
@@ -207,76 +206,26 @@ function addTableElement(blenderId, csvData, filename) {
|
||||
return menu;
|
||||
}
|
||||
|
||||
// create right click context menu for choosing column top calculation
|
||||
function createColumnContextMenu(column) {
|
||||
const field = column.field;
|
||||
const calculations = ["sum", "avg", "max", "min"];
|
||||
|
||||
// label is the item label in the menu
|
||||
// update column defination on clicking an item
|
||||
// specifically the topCalc and pass calc type to the topCalcFormatter function
|
||||
// uisng the topCalcFormatterParams field
|
||||
return calculations.map((calc) => ({
|
||||
label: `Show ${
|
||||
calc.charAt(0).toUpperCase() + calc.slice(1)
|
||||
} for ${field}`,
|
||||
action: (e, cell) =>
|
||||
cell
|
||||
.getColumn()
|
||||
.updateDefinition({ topCalc: calc, topCalcFormatterParams: calc }),
|
||||
}));
|
||||
}
|
||||
|
||||
function calcFormatter(cell, formatterParams, onRendered) {
|
||||
// formatterParams is anything in the topCalcFormatterParams field in the column definition
|
||||
// so we pass the calculation type to display what the calculation type is
|
||||
return `${formatterParams}: ${cell.getValue()}`;
|
||||
}
|
||||
|
||||
var containerHeight = $(".table-container").height();
|
||||
var h3Height = $(".table-container>h3").height();
|
||||
|
||||
var table = new Tabulator("#table-" + blenderId, {
|
||||
placeholder: "No data to display",
|
||||
height: "400px",
|
||||
resizableColumnGuide: true,
|
||||
index: "GlobalId",
|
||||
data: csvData,
|
||||
importFormat: "csv",
|
||||
autoColumns: true,
|
||||
selectableRows: 1,
|
||||
layout: "fitDataFill",
|
||||
height: containerHeight - h3Height - 20 + "px",
|
||||
movableColumns: true,
|
||||
// Our fields are never nested, and we use the "." character in queries
|
||||
nestedFieldSeparator: false,
|
||||
// defines column defualts that are applied to all columns
|
||||
columnDefaults: {
|
||||
visible: true,
|
||||
headerFilter: true,
|
||||
topCalc: "sum",
|
||||
topCalcFormatter: calcFormatter,
|
||||
topCalcFormatterParams: "sum",
|
||||
},
|
||||
// defines a callback to edit the column definition generated by autoColumns
|
||||
// that cannot be set as default
|
||||
layout: "fitColumns",
|
||||
autoColumnsDefinitions: function (definitions) {
|
||||
headerMenu = createHeaderMenu(definitions, table);
|
||||
menu = createHeaderMenu(definitions, table);
|
||||
definitions.forEach((column) => {
|
||||
column.headerMenu = headerMenu;
|
||||
column.contextMenu = createColumnContextMenu(column);
|
||||
column.visible = true;
|
||||
column.headerMenu = menu;
|
||||
});
|
||||
return definitions;
|
||||
},
|
||||
});
|
||||
|
||||
var downloadCsv = $('<a><i class="fa-solid fa-file-csv"></i></a>')
|
||||
.css("margin-left", "10px")
|
||||
.css("cursor", "pointer");
|
||||
tableTitle.append(downloadCsv);
|
||||
downloadCsv.on("click", function () {
|
||||
table.download("csv", "data.csv");
|
||||
});
|
||||
|
||||
table.on("rowSelected", function (row) {
|
||||
var index = row.getIndex(); // the guid of the object
|
||||
|
||||
|
||||
@@ -1,102 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="blender">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>BlenderBIM Web UI</title>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
type="text/css"
|
||||
href="/static/css/cost.css"
|
||||
id="index-stylesheet"
|
||||
/>
|
||||
<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"
|
||||
></script>
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="https://unpkg.com/tabulator-tables/dist/js/tabulator.min.js"
|
||||
></script>
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="https://cdn.socket.io/4.0.0/socket.io.min.js"
|
||||
></script>
|
||||
<script>
|
||||
var SOCKET_PORT = {{port}};
|
||||
</script>
|
||||
<script type="module" defer src="./static/js/cost.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<nav>
|
||||
<img
|
||||
src="https://bonsaibim.org/assets/images/blender/blender-logo.png"
|
||||
alt="Logo"
|
||||
class="logo"
|
||||
/>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="/"><i class="fa-solid fa-table"></i> Schedules</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/costing" class="active"
|
||||
><i class="fa-solid fa-money-bill-trend-up"></i> Costing</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/sequencing"
|
||||
><i class="fa-solid fa-chart-gantt"></i> Construction Sequencing</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/documentation"
|
||||
><i class="fa-solid fa-images"></i> Documentation</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a><i class="fa-solid fa-camera"></i> BCF Topics</a>
|
||||
</li>
|
||||
<li>
|
||||
<a><i class="fa-solid fa-square-check"></i> IDS Audits</a>
|
||||
</li>
|
||||
<li>
|
||||
<a><i class="fa-solid fa-hotel"></i> Facility Management</a>
|
||||
</li>
|
||||
</ul>
|
||||
<button id="toggle-theme" onclick="toggleTheme()">
|
||||
<i class="fas fa-moon"></i>
|
||||
</button>
|
||||
<div id="connected-list-div">
|
||||
<button id="show-connected-button" onclick="toggleClientList()">
|
||||
Connected Blenders:
|
||||
<span id="blender-count">0</span>
|
||||
<i class="fas fa-chevron-down"></i>
|
||||
</button>
|
||||
<div id="client-list"></div>
|
||||
</div>
|
||||
</nav>
|
||||
<div class="row-container">
|
||||
<div id="ribbon-bar" class="ribbon-bar">
|
||||
<button
|
||||
id="add-cost-schedule"
|
||||
class="action-button fas fa-plus"
|
||||
data-tooltip="Add New Schedule"
|
||||
></button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="cost-schedules" class="row-container"></div>
|
||||
<div id="cost-items" class="table-container"></div>
|
||||
<footer>
|
||||
<p>BlenderBIM Version: {{version}}</p>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,273 +0,0 @@
|
||||
<!-- this is a demo web UI page -->
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="blender">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Bonsai Web UI</title>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/x-icon"
|
||||
href="https://bonsaibim.org/assets/images/favicon-blender.png"
|
||||
/>
|
||||
<!-- here we request the CSS file from the server, -->
|
||||
<!-- using registered static path in the server -->
|
||||
<link rel="stylesheet" href="/static/css/demo.css" id="demo-stylesheet" />
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.6.0/css/all.min.css"
|
||||
/>
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="https://code.jquery.com/jquery-3.6.0.min.js"
|
||||
></script>
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="https://cdn.socket.io/4.0.0/socket.io.min.js"
|
||||
></script>
|
||||
<script>
|
||||
// here we define the port that the server is listening on.
|
||||
// so that we can connect to the websocket server and request static files
|
||||
// this is set when the HTML file is served by the server when opening the web UI URL.
|
||||
var SOCKET_PORT = {{port}};
|
||||
</script>
|
||||
<!-- here we request the JS file from the server-->
|
||||
<!-- using registered static path in the server -->
|
||||
<script defer src="./static/js/demo.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<!-- the navigation bar at the top of the page. -->
|
||||
<nav>
|
||||
<img
|
||||
src="https://bonsaibim.org/assets/images/blender/blender-logo.png"
|
||||
alt="Logo"
|
||||
class="logo"
|
||||
/>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="/"><i class="fa-solid fa-table"></i> Schedules</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/sequencing"
|
||||
><i class="fa-solid fa-chart-gantt"></i> Construction Sequencing</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/documentation"
|
||||
><i class="fa-solid fa-images"></i> Documentation</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a><i class="fa-solid fa-camera"></i> BCF Topics</a>
|
||||
</li>
|
||||
<li>
|
||||
<a><i class="fa-solid fa-square-check"></i> IDS Audits</a>
|
||||
</li>
|
||||
<li>
|
||||
<a><i class="fa-solid fa-hotel"></i> Facility Management</a>
|
||||
</li>
|
||||
</ul>
|
||||
<button id="toggle-theme" onclick="toggleTheme()">
|
||||
<i class="fas fa-moon"></i>
|
||||
</button>
|
||||
</nav>
|
||||
<!-- the main page container. -->
|
||||
<div id="container">
|
||||
<div id="message-container">
|
||||
<!-- Here we have a text input and a button that calls the SendMessage() function-->
|
||||
<input type="text" id="input-message" placeholder="Send a Message" />
|
||||
<button onclick="sendMessage()">Send Message to Bonsai</button>
|
||||
</div>
|
||||
<!-- The following is the explanation part of the demo page. -->
|
||||
<!-- I suggest reading it from the demo page iteself :) -->
|
||||
<!-- we need to stop formating because formatiing messes up pre tag spacing -->
|
||||
<!-- which in turn messes up code blocks display-->
|
||||
<!-- prettier-ignore-start -->
|
||||
<div id="explanation">
|
||||
<h1 id="welcome-to-the-web-ui-demo-page">Welcome to the web UI demo page!</h1>
|
||||
<p>This demo showcases how Bonsai's Web UI interacts with a WebSocket server. The demo illustrates how the Web UI establishes a connection, exchanges data with the server, and updates the UI dynamically based on incoming data.</p>
|
||||
<p>You can use the above textbox to send a message to Bonsai which will be printed to the console!</p>
|
||||
<p>If you prefer, you can explore the source code directly in the data/webui directory:</p>
|
||||
<ul>
|
||||
<li>sioserver.py</li>
|
||||
<li>templates/demo.html</li>
|
||||
<li>static/js/demo.js</li>
|
||||
</ul>
|
||||
<p>Each file contains detailed comments to help you understand the code. Alternatively, continue reading this demo page to understand how the Web UI operates</p>
|
||||
<h2 id="webscoket-server-sioserverpy">WebScoket Server (sioserver.py)</h2>
|
||||
<p>In the websocket server, there are two key namespaces.</p>
|
||||
<ul>
|
||||
<li>Blender Namespace: Connects all Bonsai instances.</li>
|
||||
<li>Web Namespace: Connects all Web UI clients.</li>
|
||||
</ul>
|
||||
<p>These namespaces are attached to the WebSocket server, allowing event-driven communication.</p>
|
||||
|
||||
<pre class="hljs"><code><div><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">WebNamespace</span><span class="hljs-params">(socketio.AsyncNamespace)</span>:</span>
|
||||
<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">on_connect</span><span class="hljs-params">(self, sid, environ)</span>:</span>
|
||||
...
|
||||
<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">on_disconnect</span><span class="hljs-params">(self, sid)</span>:</span>
|
||||
...
|
||||
<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">on_web_operator</span><span class="hljs-params">(self, sid, data)</span>:</span>
|
||||
...
|
||||
|
||||
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">BlenderNamespace</span><span class="hljs-params">(socketio.AsyncNamespace)</span>:</span>
|
||||
<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">on_connect</span><span class="hljs-params">(self, sid, environ)</span>:</span>
|
||||
...
|
||||
<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">on_disconnect</span><span class="hljs-params">(self, sid)</span>:</span>
|
||||
...
|
||||
<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">on_demo_data</span><span class="hljs-params">(self, sid, data)</span>:</span>
|
||||
...
|
||||
</div></code></pre>
|
||||
|
||||
<p>Each namespace defines event handlers that are triggered when a connected client emits specific events. For example, the on_demo_data function is invoked when a client emits the demo_data event.</p>
|
||||
<p>The server also defines routes, such as the /demo URL, which are tied to specific request handlers.</p>
|
||||
|
||||
<pre class="hljs"><code><div><span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">demo</span><span class="hljs-params">(request)</span>:</span>
|
||||
<span class="hljs-keyword">with</span> open(<span class="hljs-string">"templates/demo.html"</span>, <span class="hljs-string">"r"</span>) <span class="hljs-keyword">as</span> f:
|
||||
template = f.read()
|
||||
html_content = pystache.render(template, {<span class="hljs-string">"port"</span>: sio_port, <span class="hljs-string">"version"</span>: bonsai_version})
|
||||
<span class="hljs-keyword">return</span> web.Response(text=html_content, content_type=<span class="hljs-string">"text/html"</span>)
|
||||
|
||||
app.router.add_get(<span class="hljs-string">"/demo"</span>, demo)
|
||||
</div></code></pre>
|
||||
|
||||
<p>In this example, when the /demo URL is accessed, the server reads the demo.html template, renders it with variables like port and version, and returns the fully rendered HTML to the client's browser.</p>
|
||||
<h2 id="html-file-templatesdemohtml">HTML File (templates/demo.html)</h2>
|
||||
<p>When the Web UI URL is visited, the server follows these steps:</p>
|
||||
<ul>
|
||||
<li>Retrieves the HTML file from the templates directory.</li>
|
||||
<li>uses pystache to render variables, such as SOCKET_PORT, into the HTML template.</li>
|
||||
<li>Sends the fully rendered HTML page to the web browser.</li>
|
||||
</ul>
|
||||
<p>The SOCKET_PORT variable is injected into the HTML and later used by the JavaScript file to establish a WebSocket connection.</p>
|
||||
|
||||
<pre class="hljs"><code><div><span class="hljs-tag"><<span class="hljs-name">script</span>></span><span class="actionscript">
|
||||
<span class="hljs-keyword">var</span> SOCKET_PORT = {{port}};
|
||||
</span><span class="hljs-tag"></<span class="hljs-name">script</span>></span>
|
||||
</div></code></pre>
|
||||
|
||||
<p>After the HTML page loads, the browser automatically requests static files, such as CSS and JavaScript, from the static directory specified in the sioserver.py file.</p>
|
||||
|
||||
<pre class="hljs"><code><div><span class="hljs-tag"><<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"stylesheet"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/static/css/demo.css"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"demo-stylesheet"</span> /></span>
|
||||
<span class="hljs-tag"><<span class="hljs-name">script</span> <span class="hljs-attr">defer</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"./static/js/demo.js"</span>></span><span class="hljs-tag"></<span class="hljs-name">script</span>></span>
|
||||
</div></code></pre>
|
||||
|
||||
<h2 id="javascript-file-staticjsdemojs">JavaScript File (static/js/demo.js)</h2>
|
||||
<p>The JavaScript file is responsible for establishing and managing the WebSocket connection. It starts by ensuring the DOM is fully loaded before executing any scripts.</p>
|
||||
|
||||
<pre class="hljs"><code><div><span class="hljs-keyword">let</span> socket;
|
||||
$(<span class="hljs-built_in">document</span>).ready(<span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{
|
||||
<span class="hljs-keyword">var</span> defaultTheme = <span class="hljs-string">"blender"</span>; <span class="hljs-comment">// Default theme to be applied</span>
|
||||
<span class="hljs-keyword">var</span> theme = localStorage.getItem(<span class="hljs-string">"theme"</span>) || defaultTheme; <span class="hljs-comment">// Retrieve the stored theme or use the default</span>
|
||||
setTheme(theme); <span class="hljs-comment">// Apply the theme</span>
|
||||
|
||||
connectSocket(); <span class="hljs-comment">// Establish WebSocket connection</span>
|
||||
});
|
||||
</div></code></pre>
|
||||
|
||||
<p>Upon page load, the script sets the theme (either from local storage or the default) and then calls connectSocket function to initiate the WebSocket connection.</p>
|
||||
<h3 id="establishing-websocket-connection">Establishing WebSocket connection</h3>
|
||||
<p>The connectSocket function constructs the WebSocket connection URL using the SOCKET_PORT variable and then initializes the connection.</p>
|
||||
|
||||
<pre class="hljs"><code><div><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">connectSocket</span>(<span class="hljs-params"></span>) </span>{
|
||||
<span class="hljs-keyword">const</span> url = <span class="hljs-string">"ws://localhost:"</span> + SOCKET_PORT + <span class="hljs-string">"/web"</span>;
|
||||
socket = io(url);
|
||||
<span class="hljs-built_in">console</span>.log(<span class="hljs-string">"socket: "</span>, socket);
|
||||
|
||||
<span class="hljs-comment">// Register socket event handlers</span>
|
||||
socket.on(<span class="hljs-string">"blender_connect"</span>, handleBlenderConnect);
|
||||
socket.on(<span class="hljs-string">"blender_disconnect"</span>, handleBlenderDisconnect);
|
||||
socket.on(<span class="hljs-string">"theme_data"</span>, handleThemeData);
|
||||
socket.on(<span class="hljs-string">"demo_data"</span>, handleDemoData);
|
||||
}
|
||||
</div></code></pre>
|
||||
|
||||
<p>The socket variable is used to manage the WebSocket connection within the web namespace. After establishing the connection, the script registers event handlers for specific events emitted by the server.</p>
|
||||
<h3 id="receiving-data-from-bonsai">Receiving Data from Bonsai</h3>
|
||||
<p>Event handlers process the data received from the server and manipulate the DOM to reflect the changes on the webpage. For instance, the handleDemoData function processes the demo_data event and updates the demo content on the page.</p>
|
||||
|
||||
<pre class="hljs"><code><div><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">handleDemoData</span>(<span class="hljs-params">demoData</span>) </span>{
|
||||
<span class="hljs-built_in">console</span>.log(demoData);
|
||||
<span class="hljs-keyword">const</span> message = demoData[<span class="hljs-string">"data"</span>][<span class="hljs-string">"demo_message"</span>];
|
||||
<span class="hljs-keyword">const</span> blenderId = demoData[<span class="hljs-string">"blenderId"</span>];
|
||||
<span class="hljs-keyword">const</span> id = <span class="hljs-string">"message-"</span> + blenderId;
|
||||
<span class="hljs-keyword">const</span> messageHeader = $(<span class="hljs-string">"#"</span> + id);
|
||||
<span class="hljs-keyword">const</span> messageText = <span class="hljs-string">`Bonsai instance with ID: <span class="hljs-subst">${blenderId}</span> sent the message: <span class="hljs-subst">${message}</span>`</span>;
|
||||
|
||||
<span class="hljs-keyword">if</span> (messageHeader[<span class="hljs-number">0</span>] === <span class="hljs-literal">undefined</span>) {
|
||||
<span class="hljs-keyword">const</span> newMessageElement = $(<span class="hljs-string">"<h3>"</span>, { <span class="hljs-attr">id</span>: id }).text(messageText);
|
||||
newMessageElement.prependTo(<span class="hljs-string">"#message-container"</span>);
|
||||
} <span class="hljs-keyword">else</span> {
|
||||
messageHeader.text(messageText);
|
||||
messageHeader.prependTo(<span class="hljs-string">"#message-container"</span>);
|
||||
}
|
||||
}
|
||||
</div></code></pre>
|
||||
|
||||
<p>we use the BlenderId, which is a unique identifier given for by the server for each client connedted to it, to differentiate between different Bonsai instances.</p>
|
||||
<p>This functionality is repeated for other event handlers, such as handleBlenderConnect, handleBlenderDisconnect, and handleThemeData, where specific actions are taken based on the event type and the data provided by the server.</p>
|
||||
<h3 id="sending-data-to-bonsai">Sending Data to Bonsai</h3>
|
||||
<p>Data is sent to the server in response to DOM events, such as onclick. These events trigger functions that use the socket variable to emit an event to the server, passing the appropriate data for the specific web operator.</p>
|
||||
|
||||
<pre class="hljs"><code><div><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">SendMessage</span>(<span class="hljs-params"></span>) </span>{
|
||||
<span class="hljs-keyword">const</span> inputMessage = $(<span class="hljs-string">"#input-message"</span>).val();
|
||||
<span class="hljs-keyword">const</span> msg = {
|
||||
<span class="hljs-attr">sourcePage</span>: <span class="hljs-string">"demo"</span>,
|
||||
<span class="hljs-comment">// blenderId: BlenderId,</span>
|
||||
<span class="hljs-attr">operator</span>: {
|
||||
<span class="hljs-attr">type</span>: <span class="hljs-string">"message"</span>,
|
||||
<span class="hljs-attr">message</span>: inputMessage,
|
||||
},
|
||||
};
|
||||
socket.emit(<span class="hljs-string">"web_operator"</span>, msg);
|
||||
}
|
||||
</div></code></pre>
|
||||
|
||||
<p>In this example, the blenderId field specifies which Bonsai instance should receive the operator. If blenderId is not set, the operator is broadcast to all instances.</p>
|
||||
<p>The server forwards this operator to the appropriate Bonsai instance, where it is processed by functions like sio_listen_web_operator and check_operator_queue in tool/web.py.</p>
|
||||
|
||||
<h2 id="web-tool-toolwebpy">Web Tool (tool/web.py)</h2>
|
||||
<p>the sio_listen_web_operator function is automatically called when the event web_operator is emitted to Bonsai. It takes the web operator and attempts to put it in a web operators queue.</p>
|
||||
|
||||
<pre class="hljs"><code><div><span class="hljs-meta">@classmethod</span>
|
||||
<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">sio_listen_web_operator</span><span class="hljs-params">(cls, data)</span>:</span>
|
||||
<span class="hljs-keyword">try</span>:
|
||||
web_operator_queue.put_nowait(data)
|
||||
<span class="hljs-keyword">except</span> queue.Full:
|
||||
<span class="hljs-keyword">pass</span>
|
||||
</div></code></pre>
|
||||
|
||||
<p>then the check_operator_queue function is called by a timer that is run every second to retrieve operators from the queue and handle them</p>
|
||||
|
||||
<pre class="hljs"><code><div><span class="hljs-meta">@classmethod</span>
|
||||
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">check_operator_queue</span><span class="hljs-params">(cls)</span>:</span>
|
||||
<span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> bpy.context.scene.WebProperties.is_connected:
|
||||
<span class="hljs-keyword">with</span> web_operator_queue.mutex:
|
||||
web_operator_queue.queue.clear()
|
||||
<span class="hljs-keyword">return</span> <span class="hljs-literal">None</span> <span class="hljs-comment"># unregister timer if not connected</span>
|
||||
|
||||
<span class="hljs-keyword">while</span> <span class="hljs-keyword">not</span> web_operator_queue.empty():
|
||||
operator = web_operator_queue.get_nowait()
|
||||
<span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> operator:
|
||||
<span class="hljs-keyword">continue</span>
|
||||
<span class="hljs-keyword">if</span> operator[<span class="hljs-string">"sourcePage"</span>] == <span class="hljs-string">"csv"</span>:
|
||||
cls.handle_csv_operator(operator[<span class="hljs-string">"operator"</span>])
|
||||
<span class="hljs-keyword">elif</span> operator[<span class="hljs-string">"sourcePage"</span>] == <span class="hljs-string">"gantt"</span>:
|
||||
cls.handle_gantt_operator(operator[<span class="hljs-string">"operator"</span>])
|
||||
<span class="hljs-keyword">elif</span> operator[<span class="hljs-string">"sourcePage"</span>] == <span class="hljs-string">"drawings"</span>:
|
||||
cls.handle_drawings_operator(operator[<span class="hljs-string">"operator"</span>])
|
||||
<span class="hljs-keyword">elif</span> operator[<span class="hljs-string">"sourcePage"</span>] == <span class="hljs-string">"demo"</span>:
|
||||
message = operator[<span class="hljs-string">"operator"</span>][<span class="hljs-string">"message"</span>]
|
||||
print(<span class="hljs-string">f"Message from demo page: <span class="hljs-subst">{message}</span>"</span>)
|
||||
<span class="hljs-keyword">return</span> <span class="hljs-number">1.0</span>
|
||||
</div></code></pre>
|
||||
|
||||
<p>Here we check the source page of the operator and call the appropriate handler for that page.</p>
|
||||
</div>
|
||||
<!-- prettier-ignore-end -->
|
||||
</div>
|
||||
<footer>
|
||||
<p>Bonsai Version: {{version}}</p>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
@@ -3,12 +3,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Bonsai Web UI</title>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/x-icon"
|
||||
href="https://bonsaibim.org/assets/images/favicon-blender.png"
|
||||
/>
|
||||
<title>BlenderBIM Web UI</title>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css"
|
||||
@@ -58,11 +53,6 @@
|
||||
<li>
|
||||
<a href="/"><i class="fa-solid fa-table"></i> Schedules</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/costing"
|
||||
><i class="fa-solid fa-exclamation-triangle"></i>Costing</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/sequencing"
|
||||
><i class="fa-solid fa-chart-gantt"></i> Construction Sequencing</a
|
||||
@@ -159,7 +149,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<footer>
|
||||
<p>Bonsai Version: {{version}}</p>
|
||||
<p>BlenderBIM Version: {{version}}</p>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -3,12 +3,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Bonsai Web UI</title>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/x-icon"
|
||||
href="https://bonsaibim.org/assets/images/favicon-blender.png"
|
||||
/>
|
||||
<title>BlenderBIM Web UI</title>
|
||||
<link rel="stylesheet" type="text/css" href="/jsgantt/jsgantt.css" />
|
||||
<link rel="stylesheet" href="/static/css/gantt.css" id="gantt-stylesheet" />
|
||||
<link
|
||||
@@ -27,7 +22,7 @@
|
||||
<script>
|
||||
var SOCKET_PORT = {{port}};
|
||||
</script>
|
||||
<script type="module" defer src="./static/js/gantt.js"></script>
|
||||
<script defer src="./static/js/gantt.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<nav class="no-print">
|
||||
@@ -40,11 +35,6 @@
|
||||
<li>
|
||||
<a href="/"><i class="fa-solid fa-table"></i> Schedules</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/costing"
|
||||
><i class="fa-solid fa-exclamation-triangle"></i>Costing</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/sequencing" class="active"
|
||||
><i class="fa-solid fa-chart-gantt"></i> Construction Sequencing</a
|
||||
@@ -81,10 +71,9 @@
|
||||
</button>
|
||||
<div id="client-list" class="no-print"></div>
|
||||
</div>
|
||||
<div id="work_schedules" class="row-container"></div>
|
||||
<div id="container" ></div>
|
||||
<div id="container"></div>
|
||||
<footer>
|
||||
<p>Bonsai Version: {{version}}</p>
|
||||
<p>BlenderBIM Version: {{version}}</p>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -3,12 +3,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Bonsai Web UI</title>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/x-icon"
|
||||
href="https://bonsaibim.org/assets/images/favicon-blender.png"
|
||||
/>
|
||||
<title>BlenderBIM Web UI</title>
|
||||
<link rel="stylesheet" href="/static/css/index.css" id="index-stylesheet" />
|
||||
<link
|
||||
rel="stylesheet"
|
||||
@@ -49,11 +44,6 @@
|
||||
><i class="fa-solid fa-table"></i> Schedules</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/costing"
|
||||
><i class="fa-solid fa-exclamation-triangle"></i>Costing</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/sequencing"
|
||||
><i class="fa-solid fa-chart-gantt"></i> Construction Sequencing</a
|
||||
@@ -88,7 +78,7 @@
|
||||
</div>
|
||||
<div id="container"></div>
|
||||
<footer>
|
||||
<p>Bonsai Version: {{version}}</p>
|
||||
<p>BlenderBIM Version: {{version}}</p>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from __future__ import annotations
|
||||
import os
|
||||
import bpy
|
||||
import uuid
|
||||
@@ -117,34 +116,17 @@ class IfcStore:
|
||||
ifc_key = IfcStore.path + IfcStore.file.wrapped_data.header.file_name.time_stamp
|
||||
ifc_hash = hashlib.md5(ifc_key.encode("utf-8")).hexdigest()
|
||||
IfcStore.cache_path = os.path.join(bpy.context.scene.BIMProperties.data_dir, "cache", f"{ifc_hash}.h5")
|
||||
cache_path = Path(IfcStore.cache_path)
|
||||
cache_settings = ifcopenshell.geom.settings()
|
||||
serializer_settings = ifcopenshell.geom.serializer_settings()
|
||||
cache_preexists = cache_path.exists()
|
||||
try:
|
||||
IfcStore.cache = ifcopenshell.geom.serializers.hdf5(
|
||||
IfcStore.cache_path, cache_settings, serializer_settings
|
||||
)
|
||||
if cache_preexists:
|
||||
print(f"Successfully loaded existing cache: {cache_path.name}.")
|
||||
IfcStore.cache = ifcopenshell.geom.serializers.hdf5(IfcStore.cache_path, cache_settings)
|
||||
except:
|
||||
if os.path.exists(IfcStore.cache_path):
|
||||
os.remove(IfcStore.cache_path)
|
||||
try:
|
||||
IfcStore.cache = ifcopenshell.geom.serializers.hdf5(IfcStore.cache_path, cache_settings)
|
||||
except:
|
||||
return
|
||||
else:
|
||||
print("New cache was created.")
|
||||
except Exception as e:
|
||||
if cache_preexists:
|
||||
print(f"Failed to create a cache from existing file '{cache_path.name}': {str(e)}.")
|
||||
else:
|
||||
print(f"Failed to create a cache: {str(e)}.")
|
||||
# No point to trying again the same operation.
|
||||
return
|
||||
|
||||
os.remove(IfcStore.cache_path)
|
||||
try:
|
||||
IfcStore.cache = ifcopenshell.geom.serializers.hdf5(
|
||||
IfcStore.cache_path, cache_settings, serializer_settings
|
||||
)
|
||||
print("New cache was created.")
|
||||
except Exception as e:
|
||||
print(f"Failed to create a cache: {str(e)}.")
|
||||
return
|
||||
return IfcStore.cache
|
||||
|
||||
@@ -152,8 +134,6 @@ class IfcStore:
|
||||
def update_cache():
|
||||
if not IfcStore.cache:
|
||||
return
|
||||
assert IfcStore.cache_path
|
||||
assert IfcStore.file
|
||||
ifc_key = IfcStore.path + IfcStore.file.wrapped_data.header.file_name.time_stamp
|
||||
ifc_hash = hashlib.md5(ifc_key.encode("utf-8")).hexdigest()
|
||||
new_cache_path = os.path.join(bpy.context.scene.BIMProperties.data_dir, "cache", f"{ifc_hash}.h5")
|
||||
@@ -248,9 +228,7 @@ class IfcStore:
|
||||
IfcStore.commit_link_element(data)
|
||||
|
||||
@staticmethod
|
||||
def link_element(
|
||||
element: ifcopenshell.entity_instance, obj: Union[IFC_CONNECTED_TYPE, tool.Geometry.TYPES_WITH_MESH_PROPERTIES]
|
||||
) -> None:
|
||||
def link_element(element: ifcopenshell.entity_instance, obj: IFC_CONNECTED_TYPE) -> None:
|
||||
# Please use tool.Ifc.link() instead of this method. We want to
|
||||
# refactor this class and deprecate usage of IfcStore in favour of
|
||||
# tools.
|
||||
|
||||
@@ -40,37 +40,29 @@ from typing import Dict, Union, Optional, Any
|
||||
|
||||
|
||||
class MaterialCreator:
|
||||
mesh: bpy.types.Mesh
|
||||
obj: bpy.types.Object
|
||||
|
||||
def __init__(self, ifc_import_settings: IfcImportSettings, ifc_importer: IfcImporter):
|
||||
self.mesh: bpy.types.Mesh = None
|
||||
self.obj: bpy.types.Object = None
|
||||
self.styles: Dict[int, bpy.types.Material] = {}
|
||||
self.parsed_meshes: set[str] = set()
|
||||
self.ifc_import_settings = ifc_import_settings
|
||||
self.ifc_importer = ifc_importer
|
||||
|
||||
def create(
|
||||
self,
|
||||
element: ifcopenshell.entity_instance,
|
||||
obj: bpy.types.Object,
|
||||
mesh: Union[OBJECT_DATA_TYPE, None],
|
||||
shape_has_openings: bool,
|
||||
) -> None:
|
||||
if ((rep := getattr(element, "Representation", ...) is not ...) and not rep) or (
|
||||
(rep := getattr(element, "RepresentationMaps", ...) is not ...) and not rep
|
||||
def create(self, element: ifcopenshell.entity_instance, obj: bpy.types.Object, mesh: OBJECT_DATA_TYPE) -> None:
|
||||
self.mesh = mesh
|
||||
# as ifcopenshell triangulates the mesh, we need to merge it to quads again
|
||||
self.obj = obj
|
||||
if (hasattr(element, "Representation") and not element.Representation) or (
|
||||
hasattr(element, "RepresentationMaps") and not element.RepresentationMaps
|
||||
):
|
||||
return
|
||||
|
||||
if not mesh or mesh.name in self.parsed_meshes:
|
||||
if not self.mesh or self.mesh.name in self.parsed_meshes:
|
||||
return
|
||||
|
||||
# We don't support curve styles yet.
|
||||
if isinstance(mesh, bpy.types.Curve):
|
||||
return
|
||||
|
||||
self.mesh = mesh
|
||||
self.obj = obj
|
||||
|
||||
# mesh["ios_materials"] can contain:
|
||||
# - ifc style id if style assigned to the representation items directly
|
||||
# or through material with a style;
|
||||
@@ -82,7 +74,7 @@ class MaterialCreator:
|
||||
# Though 0 value will not occur as we don't use default materials in IfcImporter.
|
||||
|
||||
self.parsed_meshes.add(self.mesh.name)
|
||||
self.load_texture_maps(shape_has_openings)
|
||||
self.load_texture_maps()
|
||||
self.assign_material_slots_to_faces()
|
||||
tool.Geometry.record_object_materials(obj)
|
||||
del self.mesh["ios_materials"]
|
||||
@@ -108,16 +100,14 @@ class MaterialCreator:
|
||||
print(f"WARNING. IfcTextureMap texture coordinates is not supported.")
|
||||
return
|
||||
|
||||
def load_texture_maps(self, shape_has_openings: bool) -> None:
|
||||
def load_texture_maps(self) -> None:
|
||||
for style_or_material_id in self.mesh["ios_materials"]:
|
||||
if not (material := self.styles.get(style_or_material_id)):
|
||||
continue
|
||||
|
||||
material = self.styles[style_or_material_id]
|
||||
if coords := self.get_ifc_coordinate(material):
|
||||
if shape_has_openings and coords.is_a("IfcIndexedTextureMap"):
|
||||
continue
|
||||
tool.Loader.load_indexed_map(coords, self.mesh)
|
||||
tool.Loader.load_indexed_texture_map(coords, self.mesh)
|
||||
|
||||
def assign_material_slots_to_faces(self) -> None:
|
||||
if not self.mesh["ios_materials"]:
|
||||
@@ -549,6 +539,9 @@ class IfcImporter:
|
||||
shape = tool.Loader.create_generic_shape(grid)
|
||||
grid_obj = self.create_product(grid, shape)
|
||||
grid_placement = self.get_element_matrix(grid)
|
||||
if tool.Blender.get_addon_preferences().lock_grids_on_import:
|
||||
grid_obj.lock_location = (True, True, True)
|
||||
grid_obj.lock_rotation = (True, True, True)
|
||||
self.create_grid_axes(grid.UAxes, grid_obj, grid_placement)
|
||||
self.create_grid_axes(grid.VAxes, grid_obj, grid_placement)
|
||||
if grid.WAxes:
|
||||
@@ -559,6 +552,9 @@ class IfcImporter:
|
||||
shape = tool.Loader.create_generic_shape(axis.AxisCurve)
|
||||
mesh = self.create_mesh(axis, shape)
|
||||
obj = bpy.data.objects.new(tool.Loader.get_name(axis), mesh)
|
||||
if tool.Blender.get_addon_preferences().lock_grids_on_import:
|
||||
obj.lock_location = (True, True, True)
|
||||
obj.lock_rotation = (True, True, True)
|
||||
self.link_element(axis, obj)
|
||||
self.set_matrix_world(obj, tool.Loader.apply_blender_offset_to_matrix_world(obj, grid_placement.copy()))
|
||||
|
||||
@@ -580,10 +576,7 @@ class IfcImporter:
|
||||
mesh = self.meshes.get(mesh_name)
|
||||
if mesh is None:
|
||||
shape = tool.Loader.create_generic_shape(representation)
|
||||
# Skip elements without geometry - e.g. annotations with IfcTextLiterals.
|
||||
if shape and not shape.verts_buffer:
|
||||
pass
|
||||
elif shape:
|
||||
if shape:
|
||||
mesh = self.create_mesh(element, shape)
|
||||
tool.Loader.link_mesh(shape, mesh)
|
||||
self.meshes[mesh_name] = mesh
|
||||
@@ -592,7 +585,7 @@ class IfcImporter:
|
||||
break
|
||||
obj = bpy.data.objects.new(tool.Loader.get_name(element), mesh)
|
||||
self.link_element(element, obj)
|
||||
self.material_creator.create(element, obj, mesh, False)
|
||||
self.material_creator.create(element, obj, mesh)
|
||||
self.type_products[element.GlobalId] = obj
|
||||
|
||||
def create_native_elements(self):
|
||||
@@ -722,17 +715,9 @@ class IfcImporter:
|
||||
if not products:
|
||||
return results
|
||||
if tool.Loader.settings.should_use_cpu_multiprocessing:
|
||||
iterator = ifcopenshell.geom.iterator(
|
||||
settings,
|
||||
self.file,
|
||||
multiprocessing.cpu_count(),
|
||||
include=products,
|
||||
geometry_library=self.ifc_import_settings.geometry_library,
|
||||
)
|
||||
iterator = ifcopenshell.geom.iterator(settings, self.file, multiprocessing.cpu_count(), include=products)
|
||||
else:
|
||||
iterator = ifcopenshell.geom.iterator(
|
||||
settings, self.file, include=products, geometry_library=self.ifc_import_settings.geometry_library
|
||||
)
|
||||
iterator = ifcopenshell.geom.iterator(settings, self.file, include=products)
|
||||
if self.ifc_import_settings.should_cache:
|
||||
cache = IfcStore.get_cache()
|
||||
if cache:
|
||||
@@ -800,9 +785,7 @@ class IfcImporter:
|
||||
self.set_matrix_world(obj, tool.Loader.apply_blender_offset_to_matrix_world(obj, placement_matrix))
|
||||
self.link_element(product, obj)
|
||||
|
||||
def get_pointcloud_representation(
|
||||
self, product: ifcopenshell.entity_instance
|
||||
) -> Union[ifcopenshell.entity_instance, None]:
|
||||
def get_pointcloud_representation(self, product):
|
||||
if hasattr(product, "Representation") and hasattr(product.Representation, "Representations"):
|
||||
representations = product.Representation.Representations
|
||||
elif hasattr(product, "RepresentationMaps") and hasattr(product.RepresentationMaps, "RepresentationMaps"):
|
||||
@@ -842,12 +825,28 @@ class IfcImporter:
|
||||
def create_pointcloud(
|
||||
self, product: ifcopenshell.entity_instance, representation: ifcopenshell.entity_instance
|
||||
) -> Union[ifcopenshell.entity_instance, None]:
|
||||
mesh = tool.Loader.create_point_cloud_mesh(representation)
|
||||
if mesh is None:
|
||||
return
|
||||
placement_matrix = self.get_element_matrix(product)
|
||||
vertex_list = []
|
||||
for item in representation.Items:
|
||||
if item.is_a("IfcCartesianPointList3D"):
|
||||
vertex_list.extend(
|
||||
mathutils.Vector(list(coordinates)) * self.unit_scale for coordinates in item.CoordList
|
||||
)
|
||||
elif item.is_a("IfcCartesianPointList2D"):
|
||||
vertex_list.extend(
|
||||
mathutils.Vector(list(coordinates)).to_3d() * self.unit_scale for coordinates in item.CoordList
|
||||
)
|
||||
elif item.is_a("IfcCartesianPoint"):
|
||||
vertex_list.append(mathutils.Vector(list(item.Coordinates)) * self.unit_scale)
|
||||
|
||||
if len(vertex_list) == 0:
|
||||
return None
|
||||
|
||||
mesh_name = tool.Geometry.get_representation_name(representation)
|
||||
mesh = bpy.data.meshes.new(mesh_name)
|
||||
mesh.from_pydata(vertex_list, [], [])
|
||||
tool.Ifc.link(representation, mesh)
|
||||
|
||||
placement_matrix = self.get_element_matrix(product)
|
||||
obj = bpy.data.objects.new(tool.Loader.get_name(product), mesh)
|
||||
self.set_matrix_world(obj, tool.Loader.apply_blender_offset_to_matrix_world(obj, placement_matrix))
|
||||
self.link_element(product, obj)
|
||||
@@ -856,7 +855,7 @@ class IfcImporter:
|
||||
def create_product(
|
||||
self,
|
||||
element: ifcopenshell.entity_instance,
|
||||
shape: Optional[Union[ifcopenshell.geom.ShapeElementType, ifcopenshell.geom.ShapeType]] = None,
|
||||
shape: Optional[Any] = None,
|
||||
mesh: Optional[OBJECT_DATA_TYPE] = None,
|
||||
) -> Union[bpy.types.Object, None]:
|
||||
if element is None:
|
||||
@@ -892,12 +891,12 @@ class IfcImporter:
|
||||
mat = np.array(shape.transformation.matrix).reshape((4, 4), order="F")
|
||||
self.set_matrix_world(obj, tool.Loader.apply_blender_offset_to_matrix_world(obj, mat))
|
||||
assert mesh # Type checker.
|
||||
self.material_creator.create(element, obj, mesh, tool.Geometry.does_shape_has_openings(shape))
|
||||
elif mesh: # When does this occur?
|
||||
self.material_creator.create(element, obj, mesh)
|
||||
elif mesh:
|
||||
self.set_matrix_world(
|
||||
obj, tool.Loader.apply_blender_offset_to_matrix_world(obj, self.get_element_matrix(element))
|
||||
)
|
||||
self.material_creator.create(element, obj, mesh, False)
|
||||
self.material_creator.create(element, obj, mesh)
|
||||
elif hasattr(element, "ObjectPlacement"):
|
||||
self.set_matrix_world(
|
||||
obj, tool.Loader.apply_blender_offset_to_matrix_world(obj, self.get_element_matrix(element))
|
||||
@@ -1144,7 +1143,11 @@ class IfcImporter:
|
||||
except:
|
||||
# Occurs when reloading a project
|
||||
pass
|
||||
tool.Collector.reset_default_visibility()
|
||||
project_collection = bpy.context.view_layer.layer_collection.children[self.project["blender"].name]
|
||||
if types_collection := project_collection.children.get("IfcTypeProduct"):
|
||||
types_collection.hide_viewport = False
|
||||
for obj in types_collection.collection.objects: # turn off all objects inside Types collection.
|
||||
obj.hide_set(True)
|
||||
|
||||
def clean_mesh(self):
|
||||
obj = None
|
||||
@@ -1158,11 +1161,19 @@ class IfcImporter:
|
||||
if not last_obj:
|
||||
return
|
||||
|
||||
# Temporarily unhide types collection to make sure all objects will be cleaned
|
||||
project_collection = bpy.context.view_layer.layer_collection.children[self.project["blender"].name]
|
||||
if types_collection := project_collection.children.get("IfcTypeProduct"):
|
||||
types_collection.hide_viewport = False
|
||||
bpy.context.view_layer.objects.active = last_obj
|
||||
|
||||
bpy.ops.object.editmode_toggle()
|
||||
bpy.ops.mesh.tris_convert_to_quads()
|
||||
bpy.ops.mesh.normals_make_consistent()
|
||||
bpy.ops.object.editmode_toggle()
|
||||
|
||||
if types_collection:
|
||||
types_collection.hide_viewport = True
|
||||
bpy.context.view_layer.objects.active = last_obj
|
||||
IfcStore.edited_objs.clear()
|
||||
|
||||
@@ -1388,12 +1399,6 @@ class IfcImporter:
|
||||
mesh.polygons.foreach_set("loop_total", loop_total)
|
||||
mesh.polygons.foreach_set("use_smooth", [0] * total_faces)
|
||||
mesh.update()
|
||||
|
||||
rep_str: str = geometry.id
|
||||
if "openings" not in rep_str:
|
||||
rep_id = rep_str.split("-", 1)[0]
|
||||
rep = self.file.by_id(int(rep_id))
|
||||
tool.Loader.load_indexed_colour_map(rep, mesh)
|
||||
else:
|
||||
e = geometry.edges
|
||||
v = verts
|
||||
@@ -1482,7 +1487,6 @@ class IfcImportSettings:
|
||||
self.logger: logging.Logger = None
|
||||
self.input_file = None
|
||||
self.diff_file = None
|
||||
self.geometry_library = "opencascade"
|
||||
self.should_use_cpu_multiprocessing = True
|
||||
self.should_merge_materials_by_colour = False
|
||||
self.should_load_geometry = True
|
||||
@@ -1518,7 +1522,6 @@ class IfcImportSettings:
|
||||
logger = logging.getLogger("ImportIFC")
|
||||
settings.logger = logger
|
||||
settings.diff_file = scene_diff.diff_json_file
|
||||
settings.geometry_library = props.geometry_library
|
||||
settings.should_use_cpu_multiprocessing = props.should_use_cpu_multiprocessing
|
||||
settings.should_merge_materials_by_colour = props.should_merge_materials_by_colour
|
||||
settings.should_load_geometry = props.should_load_geometry
|
||||
|
||||
@@ -76,7 +76,7 @@ class BIM_OT_aggregate_unassign_object(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
for obj in tool.Blender.get_selected_objects():
|
||||
for obj in bpy.context.selected_objects:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element:
|
||||
continue
|
||||
|
||||
@@ -107,9 +107,6 @@ class EditAttributes(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
attributes = bonsai.bim.helper.export_attributes(props.attributes, callback=callback)
|
||||
ifcopenshell.api.run("attribute.edit_attributes", self.file, product=product, attributes=attributes)
|
||||
|
||||
if (name := tool.Loader.get_name(product)) and obj.name != name:
|
||||
obj.name = name
|
||||
bpy.ops.bim.disable_editing_attributes(obj=obj.name)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ classes = (
|
||||
operator.AddBcfViewpoint,
|
||||
operator.BCFFileHandlerOperator,
|
||||
operator.BIM_FH_import_bcf,
|
||||
operator.EditBcfAuthor,
|
||||
operator.EditBcfComment,
|
||||
operator.EditBcfLabels,
|
||||
operator.EditBcfProjectName,
|
||||
|
||||
@@ -46,15 +46,7 @@ class BcfStore:
|
||||
@classmethod
|
||||
def set(cls, bcfxml: Union[bcf.bcfxml.BcfXml, None], filepath: str) -> None:
|
||||
cls.bcfxml = bcfxml
|
||||
props = bpy.context.scene.BCFProperties
|
||||
props.bcf_file = filepath
|
||||
|
||||
# Set bcf_version prop on load.
|
||||
if filepath or bcfxml:
|
||||
bcfxml = cls.get_bcfxml()
|
||||
assert bcfxml
|
||||
bcf_v2 = (bcfxml.version.version_id or "").startswith("2")
|
||||
props.bcf_version = "2" if bcf_v2 else "3"
|
||||
bpy.context.scene.BCFProperties.bcf_file = filepath
|
||||
|
||||
@classmethod
|
||||
def set_by_filepath(cls, filepath: str) -> None:
|
||||
|
||||
@@ -59,6 +59,11 @@ def updateBcfProjectName(self, context):
|
||||
bpy.ops.bim.edit_bcf_project_name()
|
||||
|
||||
|
||||
def updateBcfAuthor(self, context):
|
||||
if bcfstore.BcfStore.get_bcfxml():
|
||||
bpy.ops.bim.edit_bcf_author()
|
||||
|
||||
|
||||
def updateBcfTopicName(self, context):
|
||||
if bcfstore.BcfStore.get_bcfxml():
|
||||
bpy.ops.bim.edit_bcf_topic_name()
|
||||
@@ -97,7 +102,6 @@ def getBcfViewpoints(self, context, force_update=False):
|
||||
bcfviewpoints_enum = []
|
||||
props = context.scene.BCFProperties
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
assert bcfxml
|
||||
topic = props.active_topic
|
||||
viewpoints = bcfxml.topics[topic.name].viewpoints.keys() if topic else []
|
||||
bcfviewpoints_enum.extend([(v, f"Viewpoint {i+1}", "") for i, v in enumerate(viewpoints)])
|
||||
@@ -184,54 +188,26 @@ class BcfTopic(PropertyGroup):
|
||||
is_editable: BoolProperty(name="Edit Topic Attributes", default=False, update=updateBcfTopicIsEditable)
|
||||
|
||||
|
||||
def get_related_topics(self: "BCFProperties", context: bpy.types.Context) -> list[tuple[str, str, str]]:
|
||||
props = self
|
||||
active_topic = props.active_topic
|
||||
active_related_topics = active_topic.related_topics.keys()
|
||||
enum_items = []
|
||||
i = 0
|
||||
for t in props.topics:
|
||||
if t.name == active_topic.name:
|
||||
continue
|
||||
if t.name in active_related_topics:
|
||||
continue
|
||||
enum_items.append((t.name, t.title, t.description))
|
||||
return enum_items
|
||||
|
||||
|
||||
class BCFProperties(PropertyGroup):
|
||||
bcf_file: StringProperty(name="BCF File")
|
||||
bcf_version: EnumProperty(
|
||||
name="BCF Version",
|
||||
description="Currently loaded BCF project version / BCF version to use for created projects",
|
||||
items=[("2", "v2.1", ""), ("3", "v3.0", "")],
|
||||
default="3",
|
||||
)
|
||||
comment_text_width: IntProperty(name="Comment Text Width", default=40)
|
||||
name: StringProperty(default="", name="Project Name", update=updateBcfProjectName)
|
||||
author: StringProperty(
|
||||
default="john@doe.com",
|
||||
name="Author Email",
|
||||
description="Author name that will be used for added comments, topics",
|
||||
)
|
||||
author: StringProperty(default="john@doe.com", name="Author Email", update=updateBcfAuthor)
|
||||
topics: CollectionProperty(name="BCF Topics", type=BcfTopic)
|
||||
active_topic_index: IntProperty(name="Active BCF Topic Index", update=refreshBcfTopic)
|
||||
file_reference: StringProperty(default="", name="Reference")
|
||||
file_ifc_project: StringProperty(default="", name="IFC Project")
|
||||
file_ifc_spatial_structure_element: StringProperty(default="", name="IFC Spatial Structure Element")
|
||||
reference_link: StringProperty(default="", name="Reference Link")
|
||||
label: StringProperty(
|
||||
default="", name="Label", search=lambda s, c, t: get_extensions_items(s, c, t, extensions_attr="topic_labels")
|
||||
)
|
||||
bim_snippet_reference: StringProperty(default="", name="Reference", description="URI or filepath to BimSnippet")
|
||||
label: StringProperty(default="", name="Label")
|
||||
bim_snippet_reference: StringProperty(default="", name="Reference")
|
||||
bim_snippet_type: StringProperty(
|
||||
default="", name="Type", search=lambda s, c, t: get_extensions_items(s, c, t, extensions_attr="snippet_types")
|
||||
)
|
||||
bim_snippet_schema: StringProperty(default="", name="Schema")
|
||||
document_reference: StringProperty(default="", name="Referenced Document")
|
||||
document_reference_description: StringProperty(default="", name="Description")
|
||||
document_description: StringProperty(default="", name="Document Description")
|
||||
related_topic: EnumProperty(name="Related Topic", items=get_related_topics)
|
||||
related_topic: StringProperty(name="Related Topic")
|
||||
comment: StringProperty(default="", name="Comment")
|
||||
has_related_viewpoint: BoolProperty(name="Has Related Viewpoint", default=False)
|
||||
|
||||
@@ -246,6 +222,7 @@ class BCFProperties(PropertyGroup):
|
||||
self.bim_snippet_schema = ""
|
||||
self.document_reference = ""
|
||||
self.document_reference_description = ""
|
||||
self.related_topic = ""
|
||||
self.comment = ""
|
||||
self.has_related_viewpoint = False
|
||||
|
||||
|
||||