Merge branch 'v0.8.0' into tfk-unify-variant-storage

This commit is contained in:
Thomas Krijnen
2024-08-21 11:24:56 +02:00
422 changed files with 7643 additions and 5015 deletions
+12
View File
@@ -0,0 +1,12 @@
name: ci-black-formatting
on:
push:
pull_request:
jobs:
lint-formatting:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: psf/black@stable
+65
View File
@@ -0,0 +1,65 @@
name: ci-ifcconvert
on:
workflow_dispatch:
jobs:
activate:
runs-on: ubuntu-latest
if: |
github.repository == 'IfcOpenShell/IfcOpenShell'
steps:
- name: Set env
run: echo ok go
build:
needs: activate
name: ${{ matrix.config.name }}-${{ matrix.pyver }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
config:
- {
name: "Windows 64bit",
short_name: win64,
}
- {
name: "Windows 32bit",
short_name: win32,
}
- {
name: "Linux 64bit",
short_name: linux64
}
- {
name: "MacOS Intel 64bit",
short_name: macos64
}
- {
name: "MacOS Silicon 64bit",
short_name: macosm164
}
steps:
- uses: actions/checkout@v2
- 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
architecture: 'x64' # optional x64 or x86. Defaults to x64 if not specified
- run: echo ${{ env.DATE }}
- name: Get current version
id: version
run: echo "version=$(cat VERSION)" >> $GITHUB_OUTPUT
- name: Compile
run: |
cd src/ifcopenshell-python &&
make zip-ifcconvert PLATFORM=${{ matrix.config.short_name }}
- name: Upload zip file to release
uses: svenstaro/upload-release-action@v2
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: src/ifcopenshell-python/dist/ifcconvert-${{ steps.version.outputs.version }}-${{ matrix.config.short_name }}.zip
asset_name: ifcconvert-${{ steps.version.outputs.version }}-${{ matrix.config.short_name }}.zip
release_name: "ifcconvert-${{steps.version.outputs.version}}"
tag: "ifcconvert-${{steps.version.outputs.version}}"
overwrite: true
+7 -1
View File
@@ -47,6 +47,7 @@ jobs:
run: |
python -m pip install --upgrade pip
pip install xmlschema xsdata numpy lxml pytest isodate lark networkx tabulate python-dateutil shapely
pip install mathutils
pip install src/bcf --no-deps
pip install https://github.com/Andrej730/aud/archive/refs/heads/master-reduced-size.zip
@@ -125,4 +126,9 @@ jobs:
python tests.py
cd ../src/ifcopenshell-python
mv ifcopenshell ifcopenshell-local # Force testing on installed module
make test-safe
make test
cd ../bcf && make test
pip install requests
cd ../bsdd && make test
cd ../ifcpatch && make test
cd ../ifctester && make test
+2 -2
View File
@@ -46,12 +46,12 @@ Contents
| ifcblender | Historic Blender IFC import add-on | LGPL-3.0-or-later\* |
| ifccityjson | Convert CityJSON to IFC | LGPL-3.0-or-later | [![PyPI](https://img.shields.io/pypi/v/ifccityjson?label=PyPI&color=006dad)](https://pypi.org/project/ifccityjson/) |
| ifcclash | Clash detection library and CLI app | LGPL-3.0-or-later | [![PyPI](https://img.shields.io/pypi/v/ifcclash?label=PyPI&color=006dad)](https://pypi.org/project/ifcclash/) |
| ifcconvert | CLI app to convert IFC to many other formats | LGPL-3.0-or-later\* | [![Official](https://img.shields.io/badge/IfcOpenShell.org-Download-70ba35)](https://docs.ifcopenshell.org/ifcconvert/installation.html)
| ifcconvert | CLI app to convert IFC to many other formats | LGPL-3.0-or-later\* | [![Official](https://img.shields.io/badge/IfcOpenShell.org-Download-70ba35)](https://docs.ifcopenshell.org/ifcconvert/installation.html) [![GitHub](https://img.shields.io/github/v/release/ifcopenshell/ifcopenshell?filter=ifcconvert-*&label=GitHub&color=f6f8fa)](https://github.com/IfcOpenShell/IfcOpenShell/releases?q=ifcconvert&expanded=true)
| ifccsv | Library and CLI app to export and import schedules from IFC | LGPL-3.0-or-later | [![PyPI](https://img.shields.io/pypi/v/ifccsv?label=PyPI&color=006dad)](https://pypi.org/project/ifccsv/) |
| ifcdiff | Compare changes between IFC models | LGPL-3.0-or-later | [![PyPI](https://img.shields.io/pypi/v/ifcdiff?label=PyPI&color=006dad)](https://pypi.org/project/ifcdiff/) |
| ifcfm | Extract IFC data for FM handover requirements | LGPL-3.0-or-later | [![PyPI](https://img.shields.io/pypi/v/ifcfm?label=PyPI&color=006dad)](https://pypi.org/project/ifcfm/) |
| ifcmax | Historic extension for IFC support in 3DS Max | LGPL-3.0-or-later\* | [![Official](https://img.shields.io/badge/IfcOpenShell.org-Download-70ba35)](https://docs.ifcopenshell.org/ifcmax.html)
| ifcopenshell-python | Python library for IFC manipulation | LGPL-3.0-or-later\* | [![GitHub](https://img.shields.io/github/v/release/ifcopenshell/ifcopenshell?filter=ifcopenshell-python-*&label=GitHub&color=f6f8fa)](https://github.com/IfcOpenShell/IfcOpenShell/releases?q=ifcopenshell-python&expanded=true) [![PyPI](https://img.shields.io/pypi/v/ifcopenshell?label=PyPI&color=006dad)](https://pypi.org/project/ifcopenshell/) [![Anaconda](https://img.shields.io/conda/vn/conda-forge/ifcopenshell?label=Anaconda&color=43b02a)](https://anaconda.org/conda-forge/ifcopenshell) [![Anaconda](https://img.shields.io/conda/vn/ifcopenshell/ifcopenshell?label=Anaconda-Unstable&color=43b02a)](https://anaconda.org/ifcopenshell/ifcopenshell) [![Docker](https://img.shields.io/docker/pulls/aecgeeks/ifcopenshell?label=Docker&color=1D63ED)](https://hub.docker.com/r/aecgeeks/ifcopenshell) [![AUR](https://img.shields.io/aur/version/ifcopenshell?label=AUR&color=1793d1)](https://aur.archlinux.org/packages/ifcopenshell) [![AUR Unstable](https://img.shields.io/aur/version/ifcopenshell-git?label=AUR-Unstable&color=1793d1)](https://aur.archlinux.org/packages/ifcopenshell-git) |
| ifcopenshell-python | Python library for IFC manipulation | LGPL-3.0-or-later\* | [![Official](https://img.shields.io/badge/IfcOpenShell.org-Download-70ba35)](https://docs.ifcopenshell.org/ifcopenshell-python/installation.html) [![GitHub](https://img.shields.io/github/v/release/ifcopenshell/ifcopenshell?filter=ifcopenshell-python-*&label=GitHub&color=f6f8fa)](https://github.com/IfcOpenShell/IfcOpenShell/releases?q=ifcopenshell-python&expanded=true) [![PyPI](https://img.shields.io/pypi/v/ifcopenshell?label=PyPI&color=006dad)](https://pypi.org/project/ifcopenshell/) [![Anaconda](https://img.shields.io/conda/vn/conda-forge/ifcopenshell?label=Anaconda&color=43b02a)](https://anaconda.org/conda-forge/ifcopenshell) [![Anaconda](https://img.shields.io/conda/vn/ifcopenshell/ifcopenshell?label=Anaconda-Unstable&color=43b02a)](https://anaconda.org/ifcopenshell/ifcopenshell) [![Docker](https://img.shields.io/docker/pulls/aecgeeks/ifcopenshell?label=Docker&color=1D63ED)](https://hub.docker.com/r/aecgeeks/ifcopenshell) [![AUR](https://img.shields.io/aur/version/ifcopenshell?label=AUR&color=1793d1)](https://aur.archlinux.org/packages/ifcopenshell) [![AUR Unstable](https://img.shields.io/aur/version/ifcopenshell-git?label=AUR-Unstable&color=1793d1)](https://aur.archlinux.org/packages/ifcopenshell-git) |
| ifcpatch | Utility to run pre-packaged scripts to manipulate IFCs | LGPL-3.0-or-later | [![PyPI](https://img.shields.io/pypi/v/ifcpatch?label=PyPI&color=006dad)](https://pypi.org/project/ifcpatch/) |
| ifcsverchok | Blender Add-on for visual node programming with IFC | GPL-3.0-or-later | [![GitHub Unstable](https://img.shields.io/github/v/release/ifcopenshell/ifcopenshell?filter=ifcsverchok-*.*.*.*&label=GitHub-Unstable&color=f6f8fa)](https://github.com/IfcOpenShell/IfcOpenShell/releases?q=ifcsverchok&expanded=true)
| ifctester | Library, CLI and webapp for IDS model auditing | LGPL-3.0-or-later | [![PyPI](https://img.shields.io/pypi/v/ifctester?label=PyPI&color=006dad)](https://pypi.org/project/ifctester/) |
+4 -3
View File
@@ -221,10 +221,11 @@ endif()
if(GLTF_SUPPORT OR CITYJSON_SUPPORT)
UNIFY_ENVVARS_AND_CACHE(JSON_INCLUDE_DIR)
find_file(json_hpp "json.hpp" ${JSON_INCLUDE_DIR}/nlohmann)
find_path(json_header_path "json.hpp" ${JSON_INCLUDE_DIR} PATH_SUFFIXES "nlohmann")
set(JSON_INCLUDE_DIR ${json_header_path})
if(json_hpp)
message(STATUS "JSON for Modern C++ header file found")
if(json_header_path)
message(STATUS "JSON for Modern C++ header file found in ${JSON_INCLUDE_DIR}")
else()
message(FATAL_ERROR "Unable to find JSON for Modern C++ header file, aborting")
endif()
+29
View File
@@ -1,5 +1,34 @@
[tool.black]
line-length = 120
include = '''
src/(
bcf
|bcfserver
|blenderbim
|bsdd
|foundationserver
|ifc2ca
|ifc4d
|ifc5d
|ifcbimtester
|ifcblender
|ifccityjson
|ifcclash
|ifccsv
|ifcdiff
|ifcfm
|ifcpatch
|ifctester
|ifcopenshell-python
|ifcsverchok
|opencdeserver
)/.*.py$
'''
extend-exclude = '''
src/ifcopenshell-python/ifcopenshell/express/*
|src/ifcopenshell-python/ifcopenshell/mvd/*
|src/ifc2ca/templates/*
'''
[tool.pyright]
reportInvalidTypeForm = false
+4
View File
@@ -33,6 +33,10 @@ models:
cd src && xsdata generate -p bcf.v2.model --unnest-classes --kw-only --slots -ds Google bcf/v2/xsd
cd src && xsdata generate -p bcf.v3.model --unnest-classes --kw-only --slots -ds Google bcf/v3/xsd
.PHONY: test
test:
pytest -p no:pytest-blender tests
# .PHONY
# api:
# openapi-python-client generate --url https://api.swaggerhub.com/apis/buildingSMART/BCF/3.0
+1
View File
@@ -18,6 +18,7 @@ GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with BCF. If not, see <http://www.gnu.org/licenses/>.
"""
import zipfile
from pathlib import Path
from typing import Optional, Union
+5 -5
View File
@@ -5,8 +5,8 @@ from numpy.typing import NDArray
def camera_vectors_from_element_placement(
elem_placement: NDArray[np.float_],
) -> tuple[NDArray[np.float_], NDArray[np.float_], NDArray[np.float_]]:
elem_placement: NDArray[np.float64],
) -> tuple[NDArray[np.float64], NDArray[np.float64], NDArray[np.float64]]:
"""
Calculate the vectors of a camera pointing to an element.
@@ -21,8 +21,8 @@ def camera_vectors_from_element_placement(
def camera_vectors_from_target_position(
target_position: NDArray[np.float_], offset: Optional[NDArray[np.float_]] = None
) -> tuple[NDArray[np.float_], NDArray[np.float_], NDArray[np.float_]]:
target_position: NDArray[np.float64], offset: Optional[NDArray[np.float64]] = None
) -> tuple[NDArray[np.float64], NDArray[np.float64], NDArray[np.float64]]:
"""
Calculate the vectors of a camera pointing to a target point.
@@ -50,7 +50,7 @@ def camera_vectors_from_target_position(
# return camera_position, -mat[:3, 2], mat[:3, 1]
def unit_vector(v: NDArray[np.float_]) -> NDArray[np.float_]:
def unit_vector(v: NDArray[np.float64]) -> NDArray[np.float64]:
"""
Return the unit vector of a vector.
+2 -2
View File
@@ -5,6 +5,7 @@ Copyright (c) 2017-2020 Anthon van der Neut, Ruamel bvba
original idea from https://stackoverflow.com/a/19722365/1307905
"""
import zipfile
from io import BytesIO
from os import PathLike
@@ -13,8 +14,7 @@ from typing import Any, Optional, Protocol
class ZipFileInterface(Protocol):
def writestr(self, filename_in_zip: str | zipfile.ZipInfo, file_contents: bytes | str) -> None:
...
def writestr(self, filename_in_zip: str | zipfile.ZipInfo, file_contents: bytes | str) -> None: ...
class InMemoryZipFile:
+1
View File
@@ -1,4 +1,5 @@
"""BCF XML V2 handler."""
import uuid
import warnings
import zipfile
+36 -36
View File
@@ -34,7 +34,7 @@ class BimSnippet:
metadata={
"name": "isExternal",
"type": "Attribute",
}
},
)
@@ -64,7 +64,7 @@ class HeaderFile:
"name": "Filename",
"type": "Element",
"namespace": "",
}
},
)
date: Optional[XmlDateTime] = field(
default=None,
@@ -72,7 +72,7 @@ class HeaderFile:
"name": "Date",
"type": "Element",
"namespace": "",
}
},
)
reference: Optional[str] = field(
default=None,
@@ -80,7 +80,7 @@ class HeaderFile:
"name": "Reference",
"type": "Element",
"namespace": "",
}
},
)
ifc_project: Optional[str] = field(
default=None,
@@ -89,7 +89,7 @@ class HeaderFile:
"type": "Attribute",
"length": 22,
"pattern": r"[0-9,A-Z,a-z,_$]*",
}
},
)
ifc_spatial_structure_element: Optional[str] = field(
default=None,
@@ -98,14 +98,14 @@ class HeaderFile:
"type": "Attribute",
"length": 22,
"pattern": r"[0-9,A-Z,a-z,_$]*",
}
},
)
is_external: bool = field(
default=True,
metadata={
"name": "isExternal",
"type": "Attribute",
}
},
)
@@ -120,7 +120,7 @@ class TopicDocumentReference:
"name": "ReferencedDocument",
"type": "Element",
"namespace": "",
}
},
)
description: Optional[str] = field(
default=None,
@@ -128,7 +128,7 @@ class TopicDocumentReference:
"name": "Description",
"type": "Element",
"namespace": "",
}
},
)
guid: Optional[str] = field(
default=None,
@@ -136,14 +136,14 @@ class TopicDocumentReference:
"name": "Guid",
"type": "Attribute",
"pattern": r"[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}",
}
},
)
is_external: bool = field(
default=False,
metadata={
"name": "isExternal",
"type": "Attribute",
}
},
)
@@ -170,7 +170,7 @@ class ViewPoint:
"name": "Viewpoint",
"type": "Element",
"namespace": "",
}
},
)
snapshot: Optional[str] = field(
default=None,
@@ -178,7 +178,7 @@ class ViewPoint:
"name": "Snapshot",
"type": "Element",
"namespace": "",
}
},
)
index: Optional[int] = field(
default=None,
@@ -186,7 +186,7 @@ class ViewPoint:
"name": "Index",
"type": "Element",
"namespace": "",
}
},
)
guid: str = field(
metadata={
@@ -230,7 +230,7 @@ class Comment:
"name": "Viewpoint",
"type": "Element",
"namespace": "",
}
},
)
modified_date: Optional[XmlDateTime] = field(
default=None,
@@ -238,7 +238,7 @@ class Comment:
"name": "ModifiedDate",
"type": "Element",
"namespace": "",
}
},
)
modified_author: Optional[str] = field(
default=None,
@@ -246,7 +246,7 @@ class Comment:
"name": "ModifiedAuthor",
"type": "Element",
"namespace": "",
}
},
)
guid: str = field(
metadata={
@@ -267,7 +267,7 @@ class Header:
"type": "Element",
"namespace": "",
"min_occurs": 1,
}
},
)
@@ -279,7 +279,7 @@ class Topic:
"name": "ReferenceLink",
"type": "Element",
"namespace": "",
}
},
)
title: str = field(
metadata={
@@ -295,7 +295,7 @@ class Topic:
"name": "Priority",
"type": "Element",
"namespace": "",
}
},
)
index: Optional[int] = field(
default=None,
@@ -303,7 +303,7 @@ class Topic:
"name": "Index",
"type": "Element",
"namespace": "",
}
},
)
labels: List[str] = field(
default_factory=list,
@@ -311,7 +311,7 @@ class Topic:
"name": "Labels",
"type": "Element",
"namespace": "",
}
},
)
creation_date: XmlDateTime = field(
metadata={
@@ -335,7 +335,7 @@ class Topic:
"name": "ModifiedDate",
"type": "Element",
"namespace": "",
}
},
)
modified_author: Optional[str] = field(
default=None,
@@ -343,7 +343,7 @@ class Topic:
"name": "ModifiedAuthor",
"type": "Element",
"namespace": "",
}
},
)
due_date: Optional[XmlDateTime] = field(
default=None,
@@ -351,7 +351,7 @@ class Topic:
"name": "DueDate",
"type": "Element",
"namespace": "",
}
},
)
assigned_to: Optional[str] = field(
default=None,
@@ -359,7 +359,7 @@ class Topic:
"name": "AssignedTo",
"type": "Element",
"namespace": "",
}
},
)
stage: Optional[str] = field(
default=None,
@@ -367,7 +367,7 @@ class Topic:
"name": "Stage",
"type": "Element",
"namespace": "",
}
},
)
description: Optional[str] = field(
default=None,
@@ -375,7 +375,7 @@ class Topic:
"name": "Description",
"type": "Element",
"namespace": "",
}
},
)
bim_snippet: Optional[BimSnippet] = field(
default=None,
@@ -383,7 +383,7 @@ class Topic:
"name": "BimSnippet",
"type": "Element",
"namespace": "",
}
},
)
document_reference: List[TopicDocumentReference] = field(
default_factory=list,
@@ -391,7 +391,7 @@ class Topic:
"name": "DocumentReference",
"type": "Element",
"namespace": "",
}
},
)
related_topic: List[TopicRelatedTopic] = field(
default_factory=list,
@@ -399,7 +399,7 @@ class Topic:
"name": "RelatedTopic",
"type": "Element",
"namespace": "",
}
},
)
guid: str = field(
metadata={
@@ -414,14 +414,14 @@ class Topic:
metadata={
"name": "TopicType",
"type": "Attribute",
}
},
)
topic_status: Optional[str] = field(
default=None,
metadata={
"name": "TopicStatus",
"type": "Attribute",
}
},
)
@@ -433,7 +433,7 @@ class Markup:
"name": "Header",
"type": "Element",
"namespace": "",
}
},
)
topic: Topic = field(
metadata={
@@ -449,7 +449,7 @@ class Markup:
"name": "Comment",
"type": "Element",
"namespace": "",
}
},
)
viewpoints: List[ViewPoint] = field(
default_factory=list,
@@ -457,5 +457,5 @@ class Markup:
"name": "Viewpoints",
"type": "Element",
"namespace": "",
}
},
)
+2 -2
View File
@@ -10,7 +10,7 @@ class Project:
"name": "Name",
"type": "Element",
"namespace": "",
}
},
)
project_id: str = field(
metadata={
@@ -29,7 +29,7 @@ class ProjectExtension:
"name": "Project",
"type": "Element",
"namespace": "",
}
},
)
extension_schema: str = field(
metadata={
+2 -2
View File
@@ -10,12 +10,12 @@ class Version:
"name": "DetailedVersion",
"type": "Element",
"namespace": "",
}
},
)
version_id: Optional[str] = field(
default=None,
metadata={
"name": "VersionId",
"type": "Attribute",
}
},
)
+27 -24
View File
@@ -15,14 +15,14 @@ class Component:
metadata={
"name": "OriginatingSystem",
"type": "Element",
}
},
)
authoring_tool_id: Optional[str] = field(
default=None,
metadata={
"name": "AuthoringToolId",
"type": "Element",
}
},
)
ifc_guid: Optional[str] = field(
default=None,
@@ -31,7 +31,7 @@ class Component:
"type": "Attribute",
"length": 22,
"pattern": r"[0-9,A-Z,a-z,_$]*",
}
},
)
@@ -92,21 +92,21 @@ class ViewSetupHints:
metadata={
"name": "SpacesVisible",
"type": "Attribute",
}
},
)
space_boundaries_visible: Optional[bool] = field(
default=None,
metadata={
"name": "SpaceBoundariesVisible",
"type": "Attribute",
}
},
)
openings_visible: Optional[bool] = field(
default=None,
metadata={
"name": "OpeningsVisible",
"type": "Attribute",
}
},
)
@@ -139,7 +139,7 @@ class ComponentColoringColor:
"name": "Component",
"type": "Element",
"min_occurs": 1,
}
},
)
color: Optional[str] = field(
default=None,
@@ -147,7 +147,7 @@ class ComponentColoringColor:
"name": "Color",
"type": "Attribute",
"pattern": r"[0-9,a-f,A-F]{6}([0-9,a-f,A-F]{2})?",
}
},
)
@@ -159,7 +159,7 @@ class ComponentSelection:
"name": "Component",
"type": "Element",
"min_occurs": 1,
}
},
)
@@ -174,7 +174,7 @@ class ComponentVisibilityExceptions:
"name": "Component",
"type": "Element",
"min_occurs": 1,
}
},
)
@@ -205,6 +205,7 @@ class OrthogonalCamera:
camera_up_vector:
view_to_world_scale: view's visible size in meters
"""
camera_view_point: Point = field(
metadata={
"name": "CameraViewPoint",
@@ -247,6 +248,7 @@ class PerspectiveCamera:
release and viewers should be expect values outside this
range in current implementations.
"""
camera_view_point: Point = field(
metadata={
"name": "CameraViewPoint",
@@ -336,7 +338,7 @@ class ComponentColoring:
"name": "Color",
"type": "Element",
"min_occurs": 1,
}
},
)
@@ -347,14 +349,14 @@ class ComponentVisibility:
metadata={
"name": "Exceptions",
"type": "Element",
}
},
)
default_visibility: Optional[bool] = field(
default=None,
metadata={
"name": "DefaultVisibility",
"type": "Attribute",
}
},
)
@@ -368,7 +370,7 @@ class VisualizationInfoClippingPlanes:
metadata={
"name": "ClippingPlane",
"type": "Element",
}
},
)
@@ -383,7 +385,7 @@ class VisualizationInfoLines:
"name": "Line",
"type": "Element",
"min_occurs": 1,
}
},
)
@@ -394,14 +396,14 @@ class Components:
metadata={
"name": "ViewSetupHints",
"type": "Element",
}
},
)
selection: Optional[ComponentSelection] = field(
default=None,
metadata={
"name": "Selection",
"type": "Element",
}
},
)
visibility: ComponentVisibility = field(
metadata={
@@ -415,7 +417,7 @@ class Components:
metadata={
"name": "Coloring",
"type": "Element",
}
},
)
@@ -424,47 +426,48 @@ class VisualizationInfo:
"""
VisualizationInfo documentation.
"""
components: Optional[Components] = field(
default=None,
metadata={
"name": "Components",
"type": "Element",
}
},
)
orthogonal_camera: Optional[OrthogonalCamera] = field(
default=None,
metadata={
"name": "OrthogonalCamera",
"type": "Element",
}
},
)
perspective_camera: Optional[PerspectiveCamera] = field(
default=None,
metadata={
"name": "PerspectiveCamera",
"type": "Element",
}
},
)
lines: Optional[VisualizationInfoLines] = field(
default=None,
metadata={
"name": "Lines",
"type": "Element",
}
},
)
clipping_planes: Optional[VisualizationInfoClippingPlanes] = field(
default=None,
metadata={
"name": "ClippingPlanes",
"type": "Element",
}
},
)
bitmap: List[VisualizationInfoBitmap] = field(
default_factory=list,
metadata={
"name": "Bitmap",
"type": "Element",
}
},
)
guid: str = field(
metadata={
+2 -1
View File
@@ -1,4 +1,5 @@
"""BCF XML V2 Topic handler."""
import datetime
import tempfile
import uuid
@@ -277,7 +278,7 @@ class TopicHandler:
self.add_visinfo_handler(new_viewpoint)
return new_viewpoint
def add_viewpoint_from_point_and_guids(self, position: NDArray[np.float_], *guids: str) -> None:
def add_viewpoint_from_point_and_guids(self, position: NDArray[np.float64], *guids: str) -> None:
"""Add a viewpoint pointing at an XYZ point in space
Args:
+4 -4
View File
@@ -176,7 +176,7 @@ class VisualizationInfoHandler:
@classmethod
def create_from_point_and_guids(
cls,
position: NDArray[np.float_],
position: NDArray[np.float64],
*guids: str,
xml_handler: Optional[AbstractXmlParserSerializer] = None,
) -> "VisualizationInfoHandler":
@@ -224,7 +224,7 @@ def build_viewpoint(element: entity_instance) -> mdl.VisualizationInfo:
)
def build_viewpoint_from_position_and_guids(position: NDArray[np.float_], *guids: str) -> mdl.VisualizationInfo:
def build_viewpoint_from_position_and_guids(position: NDArray[np.float64], *guids: str) -> mdl.VisualizationInfo:
"""
Return a BCF viewpoint of an IFC element.
@@ -261,7 +261,7 @@ def build_components(*guids: str) -> mdl.Components:
)
def build_camera(elem_placement: NDArray[np.float_]) -> mdl.PerspectiveCamera:
def build_camera(elem_placement: NDArray[np.float64]) -> mdl.PerspectiveCamera:
"""
Return a BCF camera for an IFC element placement matrix.
@@ -275,7 +275,7 @@ def build_camera(elem_placement: NDArray[np.float_]) -> mdl.PerspectiveCamera:
def build_camera_from_vectors(
camera_position: NDArray[np.float_], camera_dir: NDArray[np.float_], camera_up: NDArray[np.float_]
camera_position: NDArray[np.float64], camera_dir: NDArray[np.float64], camera_up: NDArray[np.float64]
) -> mdl.PerspectiveCamera:
"""
Return a BCF camera for an IFC element placement matrix.
+1
View File
@@ -1,4 +1,5 @@
"""BCF XML V3 handlers."""
import uuid
import warnings
import zipfile
+1
View File
@@ -1,4 +1,5 @@
"""BCF XML V3 Documents handler."""
import zipfile
from typing import Any, Optional
+3 -3
View File
@@ -22,7 +22,7 @@ class Document:
"namespace": "",
"min_length": 1,
"white_space": "collapse",
}
},
)
guid: str = field(
metadata={
@@ -45,7 +45,7 @@ class DocumentInfoDocuments:
"name": "Document",
"type": "Element",
"namespace": "",
}
},
)
@@ -57,5 +57,5 @@ class DocumentInfo:
"name": "Documents",
"type": "Element",
"namespace": "",
}
},
)
+14 -14
View File
@@ -15,7 +15,7 @@ class ExtensionsPriorities:
"namespace": "",
"min_length": 1,
"white_space": "collapse",
}
},
)
@@ -32,7 +32,7 @@ class ExtensionsSnippetTypes:
"namespace": "",
"min_length": 1,
"white_space": "collapse",
}
},
)
@@ -49,7 +49,7 @@ class ExtensionsStages:
"namespace": "",
"min_length": 1,
"white_space": "collapse",
}
},
)
@@ -66,7 +66,7 @@ class ExtensionsTopicLabels:
"namespace": "",
"min_length": 1,
"white_space": "collapse",
}
},
)
@@ -83,7 +83,7 @@ class ExtensionsTopicStatuses:
"namespace": "",
"min_length": 1,
"white_space": "collapse",
}
},
)
@@ -100,7 +100,7 @@ class ExtensionsTopicTypes:
"namespace": "",
"min_length": 1,
"white_space": "collapse",
}
},
)
@@ -117,7 +117,7 @@ class ExtensionsUsers:
"namespace": "",
"min_length": 1,
"white_space": "collapse",
}
},
)
@@ -129,7 +129,7 @@ class Extensions:
"name": "TopicTypes",
"type": "Element",
"namespace": "",
}
},
)
topic_statuses: Optional[ExtensionsTopicStatuses] = field(
default=None,
@@ -137,7 +137,7 @@ class Extensions:
"name": "TopicStatuses",
"type": "Element",
"namespace": "",
}
},
)
priorities: Optional[ExtensionsPriorities] = field(
default=None,
@@ -145,7 +145,7 @@ class Extensions:
"name": "Priorities",
"type": "Element",
"namespace": "",
}
},
)
topic_labels: Optional[ExtensionsTopicLabels] = field(
default=None,
@@ -153,7 +153,7 @@ class Extensions:
"name": "TopicLabels",
"type": "Element",
"namespace": "",
}
},
)
users: Optional[ExtensionsUsers] = field(
default=None,
@@ -161,7 +161,7 @@ class Extensions:
"name": "Users",
"type": "Element",
"namespace": "",
}
},
)
snippet_types: Optional[ExtensionsSnippetTypes] = field(
default=None,
@@ -169,7 +169,7 @@ class Extensions:
"name": "SnippetTypes",
"type": "Element",
"namespace": "",
}
},
)
stages: Optional[ExtensionsStages] = field(
default=None,
@@ -177,5 +177,5 @@ class Extensions:
"name": "Stages",
"type": "Element",
"namespace": "",
}
},
)
+42 -42
View File
@@ -40,7 +40,7 @@ class BimSnippet:
metadata={
"name": "IsExternal",
"type": "Attribute",
}
},
)
@@ -68,7 +68,7 @@ class DocumentReference:
"type": "Element",
"namespace": "",
"pattern": r"[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}",
}
},
)
url: Optional[str] = field(
default=None,
@@ -78,7 +78,7 @@ class DocumentReference:
"namespace": "",
"min_length": 1,
"white_space": "collapse",
}
},
)
description: Optional[str] = field(
default=None,
@@ -88,7 +88,7 @@ class DocumentReference:
"namespace": "",
"min_length": 1,
"white_space": "collapse",
}
},
)
guid: str = field(
metadata={
@@ -110,7 +110,7 @@ class File:
"namespace": "",
"min_length": 1,
"white_space": "collapse",
}
},
)
date: Optional[XmlDateTime] = field(
default=None,
@@ -118,7 +118,7 @@ class File:
"name": "Date",
"type": "Element",
"namespace": "",
}
},
)
reference: Optional[str] = field(
default=None,
@@ -128,7 +128,7 @@ class File:
"namespace": "",
"min_length": 1,
"white_space": "collapse",
}
},
)
ifc_project: Optional[str] = field(
default=None,
@@ -137,7 +137,7 @@ class File:
"type": "Attribute",
"length": 22,
"pattern": r"[0-9A-Za-z_$]*",
}
},
)
ifc_spatial_structure_element: Optional[str] = field(
default=None,
@@ -146,14 +146,14 @@ class File:
"type": "Attribute",
"length": 22,
"pattern": r"[0-9A-Za-z_$]*",
}
},
)
is_external: bool = field(
default=True,
metadata={
"name": "IsExternal",
"type": "Attribute",
}
},
)
@@ -170,7 +170,7 @@ class TopicLabels:
"namespace": "",
"min_length": 1,
"white_space": "collapse",
}
},
)
@@ -187,7 +187,7 @@ class TopicReferenceLinks:
"namespace": "",
"min_length": 1,
"white_space": "collapse",
}
},
)
@@ -216,7 +216,7 @@ class ViewPoint:
"namespace": "",
"min_length": 1,
"white_space": "collapse",
}
},
)
snapshot: Optional[str] = field(
default=None,
@@ -226,7 +226,7 @@ class ViewPoint:
"namespace": "",
"min_length": 1,
"white_space": "collapse",
}
},
)
index: Optional[int] = field(
default=None,
@@ -234,7 +234,7 @@ class ViewPoint:
"name": "Index",
"type": "Element",
"namespace": "",
}
},
)
guid: str = field(
metadata={
@@ -274,7 +274,7 @@ class Comment:
"namespace": "",
"min_length": 1,
"white_space": "collapse",
}
},
)
viewpoint: Optional[CommentViewpoint] = field(
default=None,
@@ -282,7 +282,7 @@ class Comment:
"name": "Viewpoint",
"type": "Element",
"namespace": "",
}
},
)
modified_date: Optional[XmlDateTime] = field(
default=None,
@@ -290,7 +290,7 @@ class Comment:
"name": "ModifiedDate",
"type": "Element",
"namespace": "",
}
},
)
modified_author: Optional[str] = field(
default=None,
@@ -300,7 +300,7 @@ class Comment:
"namespace": "",
"min_length": 1,
"white_space": "collapse",
}
},
)
guid: str = field(
metadata={
@@ -323,7 +323,7 @@ class HeaderFiles:
"name": "File",
"type": "Element",
"namespace": "",
}
},
)
@@ -338,7 +338,7 @@ class TopicDocumentReferences:
"name": "DocumentReference",
"type": "Element",
"namespace": "",
}
},
)
@@ -353,7 +353,7 @@ class TopicRelatedTopics:
"name": "RelatedTopic",
"type": "Element",
"namespace": "",
}
},
)
@@ -368,7 +368,7 @@ class TopicViewpoints:
"name": "ViewPoint",
"type": "Element",
"namespace": "",
}
},
)
@@ -380,7 +380,7 @@ class Header:
"name": "Files",
"type": "Element",
"namespace": "",
}
},
)
@@ -395,7 +395,7 @@ class TopicComments:
"name": "Comment",
"type": "Element",
"namespace": "",
}
},
)
@@ -407,7 +407,7 @@ class Topic:
"name": "ReferenceLinks",
"type": "Element",
"namespace": "",
}
},
)
title: str = field(
metadata={
@@ -427,7 +427,7 @@ class Topic:
"namespace": "",
"min_length": 1,
"white_space": "collapse",
}
},
)
index: Optional[int] = field(
default=None,
@@ -435,7 +435,7 @@ class Topic:
"name": "Index",
"type": "Element",
"namespace": "",
}
},
)
labels: Optional[TopicLabels] = field(
default=None,
@@ -443,7 +443,7 @@ class Topic:
"name": "Labels",
"type": "Element",
"namespace": "",
}
},
)
creation_date: XmlDateTime = field(
metadata={
@@ -469,7 +469,7 @@ class Topic:
"name": "ModifiedDate",
"type": "Element",
"namespace": "",
}
},
)
modified_author: Optional[str] = field(
default=None,
@@ -479,7 +479,7 @@ class Topic:
"namespace": "",
"min_length": 1,
"white_space": "collapse",
}
},
)
due_date: Optional[XmlDateTime] = field(
default=None,
@@ -487,7 +487,7 @@ class Topic:
"name": "DueDate",
"type": "Element",
"namespace": "",
}
},
)
assigned_to: Optional[str] = field(
default=None,
@@ -497,7 +497,7 @@ class Topic:
"namespace": "",
"min_length": 1,
"white_space": "collapse",
}
},
)
stage: Optional[str] = field(
default=None,
@@ -507,7 +507,7 @@ class Topic:
"namespace": "",
"min_length": 1,
"white_space": "collapse",
}
},
)
description: Optional[str] = field(
default=None,
@@ -517,7 +517,7 @@ class Topic:
"namespace": "",
"min_length": 1,
"white_space": "collapse",
}
},
)
bim_snippet: Optional[BimSnippet] = field(
default=None,
@@ -525,7 +525,7 @@ class Topic:
"name": "BimSnippet",
"type": "Element",
"namespace": "",
}
},
)
document_references: Optional[TopicDocumentReferences] = field(
default=None,
@@ -533,7 +533,7 @@ class Topic:
"name": "DocumentReferences",
"type": "Element",
"namespace": "",
}
},
)
related_topics: Optional[TopicRelatedTopics] = field(
default=None,
@@ -541,7 +541,7 @@ class Topic:
"name": "RelatedTopics",
"type": "Element",
"namespace": "",
}
},
)
comments: Optional[TopicComments] = field(
default=None,
@@ -549,7 +549,7 @@ class Topic:
"name": "Comments",
"type": "Element",
"namespace": "",
}
},
)
viewpoints: Optional[TopicViewpoints] = field(
default=None,
@@ -557,7 +557,7 @@ class Topic:
"name": "Viewpoints",
"type": "Element",
"namespace": "",
}
},
)
guid: str = field(
metadata={
@@ -574,7 +574,7 @@ class Topic:
"type": "Attribute",
"min_length": 1,
"white_space": "collapse",
}
},
)
topic_type: str = field(
metadata={
@@ -604,7 +604,7 @@ class Markup:
"name": "Header",
"type": "Element",
"namespace": "",
}
},
)
topic: Topic = field(
metadata={
+1 -1
View File
@@ -12,7 +12,7 @@ class Project:
"namespace": "",
"min_length": 1,
"white_space": "collapse",
}
},
)
project_id: str = field(
metadata={
+28 -25
View File
@@ -17,7 +17,7 @@ class Component:
"type": "Element",
"min_length": 1,
"white_space": "collapse",
}
},
)
authoring_tool_id: Optional[str] = field(
default=None,
@@ -26,7 +26,7 @@ class Component:
"type": "Element",
"min_length": 1,
"white_space": "collapse",
}
},
)
ifc_guid: Optional[str] = field(
default=None,
@@ -35,7 +35,7 @@ class Component:
"type": "Attribute",
"length": 22,
"pattern": r"[0-9A-Za-z_$]*",
}
},
)
@@ -96,21 +96,21 @@ class ViewSetupHints:
metadata={
"name": "SpacesVisible",
"type": "Attribute",
}
},
)
space_boundaries_visible: bool = field(
default=False,
metadata={
"name": "SpaceBoundariesVisible",
"type": "Attribute",
}
},
)
openings_visible: bool = field(
default=False,
metadata={
"name": "OpeningsVisible",
"type": "Attribute",
}
},
)
@@ -191,7 +191,7 @@ class ComponentColoringColorComponents:
"name": "Component",
"type": "Element",
"min_occurs": 1,
}
},
)
@@ -202,7 +202,7 @@ class ComponentSelection:
metadata={
"name": "Component",
"type": "Element",
}
},
)
@@ -216,7 +216,7 @@ class ComponentVisibilityExceptions:
metadata={
"name": "Component",
"type": "Element",
}
},
)
@@ -249,6 +249,7 @@ class OrthogonalCamera:
aspect_ratio: Proportional relationship between the width and
the height of the view (w/h).
"""
camera_view_point: Point = field(
metadata={
"name": "CameraViewPoint",
@@ -302,6 +303,7 @@ class PerspectiveCamera:
aspect_ratio: Proportional relationship between the width and
the height of the view (w/h).
"""
camera_view_point: Point = field(
metadata={
"name": "CameraViewPoint",
@@ -371,21 +373,21 @@ class ComponentVisibility:
metadata={
"name": "ViewSetupHints",
"type": "Element",
}
},
)
exceptions: Optional[ComponentVisibilityExceptions] = field(
default=None,
metadata={
"name": "Exceptions",
"type": "Element",
}
},
)
default_visibility: bool = field(
default=False,
metadata={
"name": "DefaultVisibility",
"type": "Attribute",
}
},
)
@@ -399,7 +401,7 @@ class VisualizationInfoBitmaps:
metadata={
"name": "Bitmap",
"type": "Element",
}
},
)
@@ -413,7 +415,7 @@ class VisualizationInfoClippingPlanes:
metadata={
"name": "ClippingPlane",
"type": "Element",
}
},
)
@@ -427,7 +429,7 @@ class VisualizationInfoLines:
metadata={
"name": "Line",
"type": "Element",
}
},
)
@@ -438,7 +440,7 @@ class ComponentColoring:
metadata={
"name": "Color",
"type": "Element",
}
},
)
@@ -449,21 +451,21 @@ class Components:
metadata={
"name": "Selection",
"type": "Element",
}
},
)
visibility: Optional[ComponentVisibility] = field(
default=None,
metadata={
"name": "Visibility",
"type": "Element",
}
},
)
coloring: Optional[ComponentColoring] = field(
default=None,
metadata={
"name": "Coloring",
"type": "Element",
}
},
)
@@ -472,47 +474,48 @@ class VisualizationInfo:
"""
VisualizationInfo documentation.
"""
components: Optional[Components] = field(
default=None,
metadata={
"name": "Components",
"type": "Element",
}
},
)
orthogonal_camera: Optional[OrthogonalCamera] = field(
default=None,
metadata={
"name": "OrthogonalCamera",
"type": "Element",
}
},
)
perspective_camera: Optional[PerspectiveCamera] = field(
default=None,
metadata={
"name": "PerspectiveCamera",
"type": "Element",
}
},
)
lines: Optional[VisualizationInfoLines] = field(
default=None,
metadata={
"name": "Lines",
"type": "Element",
}
},
)
clipping_planes: Optional[VisualizationInfoClippingPlanes] = field(
default=None,
metadata={
"name": "ClippingPlanes",
"type": "Element",
}
},
)
bitmaps: Optional[VisualizationInfoBitmaps] = field(
default=None,
metadata={
"name": "Bitmaps",
"type": "Element",
}
},
)
guid: str = field(
metadata={
+2 -1
View File
@@ -1,4 +1,5 @@
"""BCF XML V3 Topic handler."""
import datetime
import uuid
import zipfile
@@ -174,7 +175,7 @@ class TopicHandler:
new_viewpoint = VisualizationInfoHandler.create_new(element, self._xml_handler)
self.add_visinfo_handler(new_viewpoint)
def add_viewpoint_from_point_and_guids(self, position: NDArray[np.float_], *guids: str) -> None:
def add_viewpoint_from_point_and_guids(self, position: NDArray[np.float64], *guids: str) -> None:
"""
Add a viewpoint tergeting an IFC element to the topic.
+4 -4
View File
@@ -175,7 +175,7 @@ class VisualizationInfoHandler:
@classmethod
def create_from_point_and_guids(
cls,
position: NDArray[np.float_],
position: NDArray[np.float64],
*guids: str,
xml_handler: Optional[AbstractXmlParserSerializer] = None,
) -> "VisualizationInfoHandler":
@@ -218,7 +218,7 @@ def build_viewpoint(element: entity_instance) -> mdl.VisualizationInfo:
)
def build_viewpoint_from_position_and_guids(position: NDArray[np.float_], *guids: str) -> mdl.VisualizationInfo:
def build_viewpoint_from_position_and_guids(position: NDArray[np.float64], *guids: str) -> mdl.VisualizationInfo:
"""
Return a BCF viewpoint of an IFC element.
@@ -255,7 +255,7 @@ def build_components(*guids: str) -> mdl.Components:
)
def build_camera(elem_placement: NDArray[np.float_]) -> mdl.PerspectiveCamera:
def build_camera(elem_placement: NDArray[np.float64]) -> mdl.PerspectiveCamera:
"""
Return a BCF camera for an IFC element placement matrix.
@@ -269,7 +269,7 @@ def build_camera(elem_placement: NDArray[np.float_]) -> mdl.PerspectiveCamera:
def build_camera_from_vectors(
camera_position: NDArray[np.float_], camera_dir: NDArray[np.float_], camera_up: NDArray[np.float_]
camera_position: NDArray[np.float64], camera_dir: NDArray[np.float64], camera_up: NDArray[np.float64]
) -> mdl.PerspectiveCamera:
"""
Return a BCF camera for an IFC element placement matrix.
+1
View File
@@ -1,4 +1,5 @@
"""XML Parser and Serializer factories."""
from typing import Optional, Protocol, Type, TypeVar
from xsdata.formats.dataclass.context import XmlContext
+1
View File
@@ -1,4 +1,5 @@
"""BCF XML tests."""
import uuid
from pathlib import Path
from tempfile import TemporaryDirectory
+1
View File
@@ -1,4 +1,5 @@
"""BCF XML tests."""
import uuid
from pathlib import Path
from tempfile import TemporaryDirectory
+7 -7
View File
@@ -91,12 +91,11 @@ BLENDER_PLATFORM:=windows-x64
endif
# Current build commit hash.
OLD:=c18e4ea
OLD:=d51fa2c
.PHONY: bump
bump:
cd . && $(SED) -b "s/$(OLD)/$(NEW)/" Makefile
cd ../ifcopenshell-python/ && $(SED) -b "s/$(OLD)/$(NEW)/" Makefile
cd ../ifcopenshell-python/docs/ifcconvert/ && $(SED) -b "s/$(OLD)/$(NEW)/" installation.rst
.PHONY: dist
dist:
@@ -196,19 +195,20 @@ endif
cd build/blenderbim/bim/data/gantt/ && wget https://raw.githubusercontent.com/jsGanttImproved/jsgantt-improved/master/dist/jsgantt.css
# Provides IFCJSON functionality
cd build && wget https://github.com/IFCJSON-Team/IFC2JSON_python/archive/master.zip
cd build && unzip master.zip && rm master.zip
# TODO: replace with main repo if https://github.com/IFCJSON-Team/IFC2JSON_python/pull/3 is merged.
cd build && wget -O ifc2json.zip https://github.com/Moult/IFC2JSON_python/archive/refs/heads/feature-ios-v0.8.0.zip
cd build && unzip ifc2json.zip && rm ifc2json.zip
# IFCJSON doesn't have pyproject.toml, so we use python command.
cd build && . env/$(VENV_ACTIVATE) && cd IFC2JSON_python-master/file_converters && \
cd build && . env/$(VENV_ACTIVATE) && cd IFC2JSON_python-*/file_converters && \
$(PYTHON) -c "from setuptools import setup; \
setup( \
name='ifcjson', \
version='0.0.0', \
version='0.0.1', \
author='Jan Brouwer', \
author_email='jan@brewsky.nl', \
packages=['ifcjson'], \
)" bdist_wheel
cp -r build/IFC2JSON_python-master/file_converters/dist/*.whl build/wheels/
cp -r build/IFC2JSON_python-*/file_converters/dist/*.whl build/wheels/
# Brickschema requires pkg_resources which is provided by Blender.
# Provides Brickschema functionality
+2 -1
View File
@@ -98,10 +98,10 @@ classes = [
operator.AddIfcFile,
operator.BIM_OT_add_section_plane,
operator.BIM_OT_delete_object,
operator.BIM_OT_open_webbrowser,
operator.BIM_OT_remove_section_plane,
operator.BIM_OT_select_object,
operator.BIM_OT_show_description,
operator.BIM_OT_multiple_file_selector,
operator.ClippingPlaneCutWithCappings,
operator.CloseError,
operator.EditBlenderCollection,
@@ -121,6 +121,7 @@ classes = [
prop.StrProperty,
operator.BIM_OT_enum_property_search, # /!\ Register AFTER prop.StrProperty
prop.ObjProperty,
prop.MultipleFileSelect,
prop.Attribute,
prop.BIMAreaProperties,
prop.BIMTabProperties,
@@ -2,7 +2,9 @@ import sys
import os
import webbrowser
blenderbim_lib_path = os.environ.get("blenderbim_lib_path")
blenderbim_lib_path = os.environ.get("BLENDERBIM_LIB_PATH")
blenderbim_version = os.environ.get("BLENDERBIM_VERSION")
if blenderbim_lib_path:
sys.path.insert(0, blenderbim_lib_path)
@@ -52,9 +54,16 @@ class WebNamespace(socketio.AsyncNamespace):
"web_operator",
data,
namespace="/blender",
room=data["blenderId"],
room=data.get("blenderId", None),
)
async def on_get_svg(self, sid, data):
print("hello world!")
file_path = data["path"]
with open(file_path, "r") as file:
svg_data = file.read()
await sio.emit("svg_data", svg_data, room=sid, namespace="/web")
async def send_cached_messages(self, sid):
# Send cached messages to the connected web client
for blenderId, messages in blender_messages.items():
@@ -98,6 +107,12 @@ class BlenderNamespace(socketio.AsyncNamespace):
blender_messages[sid]["gantt_data"] = data
await sio.emit("gantt_data", {"blenderId": sid, "data": data}, namespace="/web")
async def on_drawings_data(self, sid, data):
print(f"Drawings directory from Blender client {sid}")
print(data)
blender_messages[sid]["drwings_data"] = data
await sio.emit("drawings_data", {"blenderId": sid, "data": data}, namespace="/web")
# Attach namespaces
sio.register_namespace(WebNamespace("/web"))
@@ -108,26 +123,45 @@ sio.register_namespace(BlenderNamespace("/blender"))
async def index(request):
with open("templates/index.html", "r") as f:
template = f.read()
html_content = pystache.render(template, {"port": sio_port})
html_content = pystache.render(template, {"port": sio_port, "version": blenderbim_version})
return web.Response(text=html_content, content_type="text/html")
async def gantt(request):
with open("templates/gantt.html", "r") as f:
template = f.read()
html_content = pystache.render(template, {"port": sio_port, "version": blenderbim_version})
return web.Response(text=html_content, content_type="text/html")
async def drawings(request):
with open("templates/drawings.html", "r") as f:
template = f.read()
html_content = pystache.render(template, {"port": sio_port})
return web.Response(text=html_content, content_type="text/html")
async def open_web_browser(app):
webbrowser.open(f"http://127.0.0.1:{sio_port}/")
async def on_startup(app):
pid_file = "running_pid.json"
if os.path.exists(pid_file):
with open(pid_file, "r") as f:
pids = json.load(f)
else:
pids = {}
pids[str(os.getpid())] = sio_port
with open(pid_file, "w") as f:
json.dump(pids, f, indent=4)
app.router.add_get("/", index)
app.router.add_get("/drawings", drawings)
app.router.add_get("/gantt", gantt)
app.router.add_static("/jsgantt/", path="../gantt", name="jsgantt")
app.router.add_static("/static/", path="./static", name="static")
app.on_startup.append(open_web_browser)
app.on_startup.append(on_startup)
def main():
@@ -0,0 +1,249 @@
:root {
--font-family: Arial, sans-serif;
--base-font-size: 16px;
--margin-tiny: 0.125rem;
--margin-small: 0.625rem;
--margin-medium: 1.25rem;
--margin-large: 2.5rem;
--padding-tiny: 0.125rem;
--padding-small: 0.625rem;
--padding-medium: 1rem;
--font-size-large: 1.2rem;
--logo-height: 2.5rem;
--nav-height: 2.5rem;
}
:root.dark {
color-scheme: dark;
--bg-color: #252525;
--text-color: #e0e0e0;
--nav-bg-color: #121212;
--nav-border-color: #25682a;
--nav-link-color: #fff;
--nav-link-hover-color: #3fb449;
--warning-color: #FFDB8F;
--border-color: #464444;
}
:root.light {
color-scheme: light;
--bg-color: #ffffff;
--text-color: #000000;
--nav-bg-color: #f8f8f8;
--nav-border-color: #cccccc;
--nav-link-color: #000000;
--nav-link-hover-color: #38a63d;
--warning-color: #FF4500;
--border-color: #222;
}
html {
font-size: var(--base-font-size);
color: var(--text-color);
}
body {
background-color: var(--bg-color);
color: var(--text-color);
margin: 0;
font-family: var(--font-family);
}
#container {
margin-top: var(--margin-medium);
margin-left: var(--margin-small);
margin-right: var(--margin-small);
margin-bottom: var(--margin-medium);
height: calc(100vh - var(--nav-height));
box-sizing: border-box;
display: flex;
}
h3 {
margin-top: 0;
}
nav {
background-color: var(--nav-bg-color);
height: var(--nav-height);
padding: var(--padding-medium) 0;
display: flex;
align-items: center;
position: relative;
border-bottom: 2px solid var(--nav-border-color);
}
nav .logo {
margin-left: var(--margin-large);
height: var(--logo-height);
}
nav ul {
list-style-type: none;
margin: 0;
margin-left: 0.0625rem;
display: flex;
flex-grow: 1;
justify-content: center;
}
nav ul li {
margin-right: 3.125rem;
}
nav ul li a {
text-decoration: none;
color: var(--nav-link-color);
font-size: var(--font-size-large);
}
nav ul li a:hover,
nav ul li a.active {
color: var(--nav-link-hover-color);
}
.warning {
color: var(--warning-color);
margin-bottom: var(--margin-small);
padding: var(--padding-tiny);
display: none;
}
#toggle-theme {
border: none;
background: none;
cursor: pointer;
font-size: var(--font-size-large);
margin-right: var(--margin-medium);
}
#toggle-theme:focus {
outline: none;
}
#client-list {
position: absolute;
top: calc(0 + var(--nav-height));
overflow-y: auto;
overflow: hidden;
transition: opacity 0.3s ease-out, visibility 0.3s ease-out;
border: 1px solid var(--table-border-color);
background-color: var(--nav-bg-color);
z-index: 1;
opacity: 0;
visibility: hidden;
width: auto;
}
#client-list.show {
opacity: 1;
visibility: visible;
}
#connected-list-div {
display: inline-block;
}
.client {
padding: var(--margin-small);
border-bottom: 1px solid var(--table-border-color);
cursor: pointer;
}
.client-details {
max-height: 0;
overflow: hidden;
padding-left: var(--padding-small);
transition: max-height 0.2s ease-out, padding 0.2s ease-out, opacity 0.1s ease-out;
background-color: var(--nav-bg-color);
margin-top: var(--margin-tiny);
opacity: 0;
}
.client-details.show {
max-height: none;
opacity: 1;
}
.client-detail {
border-bottom: 1px solid var(--table-border-color);
padding: var(--padding-small);
}
#show-connected-button {
width: auto;
background-color: var(--bg-color);
border: none;
font-size: var(--base-font-size);
}
#show-connected-button:hover,
.scroll-button:hover {
cursor: pointer;
}
.scroll-button {
font-size: var(--base-font-size);
margin-left: var(--margin-tiny);
margin-top: var(--margin-small);
}
.left {
flex: 1;
background-color: var(--bg-color);
display: flex;
flex-direction: column;
padding: 20px;
}
.right {
flex: 4;
background-color: var(--bg-color);
border: 1px solid var(--border-color);
}
#svg-container {
height: 100%;
}
.panel {
background-color: var(--nav-bg-color);
border: 1px solid var(--border-color);
padding: 10px;
flex-grow: 1;
display: flex;
flex-direction: column;
}
.card-header,
.card-body {
background-color: var(--bg-color);
border: 1px solid var(--border-color);
overflow: auto;
}
.card {
background-color: var(--nav-bg-color);
overflow: auto;
}
li.svg-name {
list-style-type: none;
}
#dropdown-menu:focus {
outline: none;
}
/* remove bootstrap css confilcts */
*,
::after,
::before {
box-sizing: unset;
}
.btn,
.btn:hover {
color: var(--nav-link-hover-color);
}
@@ -1,13 +1,17 @@
:root {
--font-family: Arial, sans-serif;
--base-font-size: 16px;
--margin-tiny: 0.125rem;
--margin-small: 0.625rem;
--margin-medium: 1.25rem;
--margin-large: 2.5rem;
--padding-small: 0.125rem;
--padding-tiny: 0.125rem;
--padding-small: 0.625rem;
--padding-medium: 1rem;
--font-size-small: 1rem;
--font-size-large: 1.2rem;
--logo-height: 2.5rem;
--nav-height: 2.5rem;
--folder-collapse-font-size: 0.75rem;
--folder-collapse-font-family: Courier, "Courier New", monospace;
--box-shadow: 0 0 0.7rem #5f5f5f66;
@@ -31,6 +35,7 @@
--group-item-bg-color: #4b4b4b;
--input-bg-color: #3b3b3b;
--input-border-color: #000;
--details-border-color: #464444;
}
:root.light {
@@ -42,6 +47,7 @@
--nav-link-color: #000000;
--nav-link-hover-color: #38a63d;
--warning-color: #FF4500;
--details-border-color: #222;
}
html {
@@ -53,9 +59,13 @@ body {
color: var(--primary-text-color);
margin: 0;
font-family: var(--font-family);
display: flex;
flex-direction: column;
min-height: 100vh;
}
#container {
flex: 1;
margin-top: var(--margin-medium);
margin-left: var(--margin-small);
margin-right: var(--margin-small);
@@ -107,7 +117,7 @@ nav ul li a.active {
.warning {
color: var(--warning-color);
margin-bottom: var(--margin-small);
padding: var(--padding-small);
padding: var(--padding-tiny);
display: none;
}
@@ -141,6 +151,87 @@ nav ul li a.active {
outline: none;
}
#client-list {
position: absolute;
top: calc(0 + var(--nav-height));
overflow-y: auto;
overflow: hidden;
transition: opacity 0.3s ease-out, visibility 0.3s ease-out;
border: 1px solid var(--table-border-color);
background-color: var(--nav-bg-color);
z-index: 1;
opacity: 0;
visibility: hidden;
width: auto;
}
#client-list.show {
opacity: 1;
visibility: visible;
}
#connected-list-div {
display: inline-block;
}
.client {
padding: var(--margin-small);
border-bottom: 1px solid var(--table-border-color);
cursor: pointer;
}
.client-details {
max-height: 0;
overflow: hidden;
padding-left: var(--padding-small);
transition: max-height 0.2s ease-out, padding 0.2s ease-out, opacity 0.1s ease-out;
background-color: var(--nav-bg-color);
margin-top: var(--margin-tiny);
opacity: 0;
}
.client-details.show {
max-height: none;
opacity: 1;
}
.client-detail {
border-bottom: 1px solid var(--table-border-color);
padding: var(--padding-small);
}
#show-connected-button {
width: auto;
background-color: var(--bg-color);
border: none;
font-size: var(--base-font-size);
}
#show-connected-button:hover,
.scroll-button:hover {
cursor: pointer;
}
.scroll-button {
font-size: var(--base-font-size);
margin-left: var(--margin-tiny);
margin-top: var(--margin-small);
}
footer {
background-color: var(--nav-bg-color);
text-align: right;
padding: var(--padding-tiny);
border-top: 1px solid var(--nav-border-color);
}
footer p {
margin: 0;
color: var(--text-color);
margin-right: var(--margin-small);
font-size: 0.8rem;
}
.gantt-info {
margin: 0.5rem;
font-size: var(--font-size-small);
@@ -1,12 +1,16 @@
:root {
--font-family: Arial, sans-serif;
--base-font-size: 16px;
--margin-tiny: 0.125rem;
--margin-small: 0.625rem;
--margin-medium: 1.25rem;
--margin-large: 2.5rem;
--padding-small: 0.125rem;
--padding-tiny: 0.125rem;
--padding-small: 0.625rem;
--padding-medium: 1rem;
--font-size-large: 1.2rem;
--logo-height: 2.5rem;
--nav-height: 2.5rem;
}
:root.dark {
@@ -18,6 +22,7 @@
--nav-link-color: #fff;
--nav-link-hover-color: #3fb449;
--warning-color: #FFDB8F;
--table-border-color: #464444;
}
:root.light {
@@ -29,6 +34,7 @@
--nav-link-color: #000000;
--nav-link-hover-color: #38a63d;
--warning-color: #FF4500;
--table-border-color: #222;
}
html {
@@ -40,29 +46,26 @@ body {
color: var(--text-color);
margin: 0;
font-family: var(--font-family);
display: flex;
flex-direction: column;
min-height: 100vh;
}
#container {
flex: 1;
margin-top: var(--margin-medium);
margin-left: var(--margin-small);
margin-right: var(--margin-small);
margin-bottom: var(--margin-medium);
}
.table-container {
margin-bottom: var(--margin-large);
}
.csv-table {
margin-top: var(--margin-small);
}
h3 {
margin-top: 0;
}
nav {
background-color: var(--nav-bg-color);
height: var(--nav-height);
padding: var(--padding-medium) 0;
display: flex;
align-items: center;
@@ -102,7 +105,7 @@ nav ul li a.active {
.warning {
color: var(--warning-color);
margin-bottom: var(--margin-small);
padding: var(--padding-small);
padding: var(--padding-tiny);
display: none;
}
@@ -118,6 +121,95 @@ nav ul li a.active {
outline: none;
}
#client-list {
position: absolute;
top: calc(0 + var(--nav-height));
overflow-y: auto;
overflow: hidden;
transition: opacity 0.3s ease-out, visibility 0.3s ease-out;
border: 1px solid var(--table-border-color);
background-color: var(--nav-bg-color);
z-index: 1;
opacity: 0;
visibility: hidden;
width: auto;
}
#client-list.show {
opacity: 1;
visibility: visible;
}
#connected-list-div {
display: inline-block;
}
.client {
padding: var(--margin-small);
border-bottom: 1px solid var(--table-border-color);
cursor: pointer;
}
.client-details {
max-height: 0;
overflow: hidden;
padding-left: var(--padding-small);
transition: max-height 0.2s ease-out, padding 0.2s ease-out, opacity 0.1s ease-out;
background-color: var(--nav-bg-color);
margin-top: var(--margin-tiny);
opacity: 0;
}
.client-details.show {
max-height: none;
opacity: 1;
}
.client-detail {
border-bottom: 1px solid var(--table-border-color);
padding: var(--padding-small);
}
#show-connected-button {
width: auto;
background-color: var(--bg-color);
border: none;
font-size: var(--base-font-size);
}
#show-connected-button:hover,
.scroll-button:hover {
cursor: pointer;
}
.scroll-button {
font-size: var(--base-font-size);
margin-left: var(--margin-tiny);
margin-top: var(--margin-small);
}
footer {
background-color: var(--nav-bg-color);
text-align: right;
padding: var(--padding-tiny);
border-top: 1px solid var(--nav-border-color);
}
footer p {
margin: 0;
color: var(--text-color);
margin-right: var(--margin-small);
font-size: 0.8rem;
}
.table-container {
margin-bottom: var(--margin-large);
}
.csv-table {
margin-top: var(--margin-small);
}
/* ------------ Overwriting Tabulator CSS rules ------------ */
:root.light .tabulator-header,
@@ -127,6 +219,6 @@ nav ul li a.active {
}
:root.light .tabulator {
border-top: 1px solid #222;
border-bottom: 2px solid #222;
border-top: 1px solid var(--table-border-color);
border-bottom: 2px solid var(--table-border-color);
}
@@ -0,0 +1,263 @@
// keeps track of blenders connected
const connectedClients = {};
const allDrawings = {};
let socket;
// Document ready function
$(document).ready(function () {
var systemTheme = window.matchMedia("(prefers-color-scheme: light)").matches
? "light"
: "dark";
var theme = localStorage.getItem("theme") || systemTheme;
setTheme(theme);
$("#dropdown-menu").change(function () {
const blenderId = $(this).attr("id");
const ifcFile = $(this).val();
displayDrawingsNames(blenderId, ifcFile);
});
connectSocket();
});
// Function to connect to Socket.IO server
function connectSocket() {
const url = "ws://localhost:" + SOCKET_PORT + "/web";
socket = io(url);
console.log("socket: ", socket);
// Register socket event handlers
socket.on("blender_connect", handleBlenderConnect);
socket.on("blender_disconnect", handleBlenderDisconnect);
socket.on("connect", handleWebConnect);
socket.on("drawings_data", handleDrawingsData);
socket.on("svg_data", handleSvgData);
// socket.on("default_data", handleDefaultData);
}
// function used to get drawings data from blenderbim
function handleWebConnect() {
const msg = {
sourcePage: "drawings",
operator: {
type: "getDrawings",
},
};
socket.emit("web_operator", msg);
}
// Function to handle 'blender_connect' event
function handleBlenderConnect(blenderId) {
console.log("blender_connect: ", blenderId);
if (!connectedClients.hasOwnProperty(blenderId)) {
connectedClients[blenderId] = {
shown: false,
ifc_file: "",
};
}
}
// Function to handle 'blender_disconnect' event
function handleBlenderDisconnect(blenderId) {
console.log("blender_disconnect: ", blenderId);
if (connectedClients.hasOwnProperty(blenderId)) {
delete connectedClients[blenderId];
// remove(blenderId);
}
$("#blender-count").text(function (i, text) {
return parseInt(text, 10) - 1;
});
}
function handleDrawingsData(data) {
const blenderId = data["blenderId"];
console.log(data);
const filename = data["data"]["ifc_file"];
const drawings = data["data"]["drawings_data"]["drawings"];
const sheets = data["data"]["drawings_data"]["sheets"];
allDrawings[filename] = { drawings: drawings, sheets: sheets };
console.log(connectedClients);
if (connectedClients.hasOwnProperty(blenderId)) {
if (!connectedClients[blenderId].shown) {
connectedClients[blenderId] = {
shown: true,
ifc_file: filename,
};
addSelectOption(blenderId, filename);
} else {
// update(blenderId, data, filename);
}
} else {
connectedClients[blenderId] = {
shown: true,
ifc_file: filename,
};
addSelectOption(blenderId, filename);
}
}
function handleSvgData(data) {
const svgContainer = document.getElementById("svg-container");
svgContainer.innerHTML = "";
svgContainer.style.backgroundColor = "white";
const dom = SVG(svgContainer).svg(data);
const svgElement = dom.node.children[0];
// to make the svg not grow outside the svg container
svgElement.setAttribute("width", "100%");
svgElement.setAttribute("height", "100%");
svgElement.style.maxWidth = "100%";
svgElement.style.maxHeight = "100%";
svgElement.style.width = "100%";
svgElement.style.height = "100%";
svgElement.style.boxSizing = "border-box"; // Ensure padding and borders are included in the element's total width and height
const panZoomControls = svgPanZoom(svgElement, {
zoomEnabled: true,
controlIconsEnabled: true,
fit: true,
center: true,
});
}
function addSelectOption(blenderId, filename) {
const filenameOption = $("<option></option>")
.attr("id", "blender-" + blenderId)
.val(filename)
.text(filename);
$("#dropdown-menu").append(filenameOption);
}
function displayDrawingsNames(blenderId, ifcFile) {
drawings = allDrawings[ifcFile].drawings;
sheets = allDrawings[ifcFile].sheets;
function createSvgNames(text, index, type) {
var label = $("<li></lio>")
.text(text)
.attr("id", type + "-" + index)
.addClass("svg-name")
.click(function () {
const id = $(this).attr("id").split("-");
const index = parseInt(id[1]);
const type = id[0];
const ifcFile = $("#dropdown-menu").val();
var path = "";
if (type === "drawing") {
path = allDrawings[ifcFile].drawings[index].path;
} else if (type === "sheet") {
path = allDrawings[ifcFile].sheets[index].path;
}
const msg = {
path: path,
};
console.log(msg);
socket.emit("get_svg", msg);
});
if (type === "drawing") $("#drawings-sub-panel").append(label);
else if (type === "sheet") $("#sheets-sub-panel").append(label);
}
$("#drawings-sub-panel").empty();
drawings.forEach(function (drawing, index) {
createSvgNames(drawing.name, index, "drawing");
});
$("#sheets-sub-panel").empty();
sheets.forEach(function (sheet, index) {
createSvgNames(sheet.name, index, "sheet");
});
}
function setTheme(theme) {
if (theme === "light") {
$("html").removeClass("dark").addClass("light");
$("#toggle-theme").html('<i class="fas fa-sun"></i>');
} else {
$("html").removeClass("light").addClass("dark");
$("#toggle-theme").html('<i class="fas fa-moon"></i>');
}
localStorage.setItem("theme", theme);
}
function toggleTheme() {
if ($("html").hasClass("dark")) {
setTheme("light");
} else {
setTheme("dark");
}
}
function toggleClientList() {
var clientList = $("#client-list");
if (clientList.hasClass("show")) {
clientList.removeClass("show");
return;
}
clientList.empty();
$.each(connectedClients, function (id, client) {
if (!client.shown) return;
const dropdownIcon = $("<i>")
.addClass("fas fa-chevron-down")
.css("margin-left", "0.5rem");
const clientDiv = $("<div>").addClass("client").text(client.ifc_file);
clientDiv.append(dropdownIcon);
const clientDetailsDiv = $("<div>").addClass("client-details");
if (id) {
const clientId = $("<div>")
.addClass("client-detail")
.text(`Blender ID: ${id}`);
clientDetailsDiv.append(clientId);
}
if (client.headers && client.headers.length) {
const clientHeaders = $("<div>")
.addClass("client-detail")
.text(`Table Headers: ${client.headers.join(", ")}`);
const scrollButton = $("<button>")
.addClass("scroll-button")
.text("Scroll to Table")
.on("click", function () {
$("html, body").animate(
{
scrollTop: $("#table-" + id).offset().top,
},
600
);
clientList.removeClass("show");
});
clientDetailsDiv.append(clientHeaders);
clientDetailsDiv.append(scrollButton);
}
clientDiv.append(clientDetailsDiv);
clientDiv.on("click", function () {
clientDetailsDiv.toggleClass("show");
});
clientList.append(clientDiv);
});
clientList.addClass("show");
}
@@ -60,6 +60,10 @@ function handleBlenderDisconnect(blenderId) {
delete connectedClients[blenderId];
removeGanttElement(blenderId);
}
$("#blender-count").text(function (i, text) {
return parseInt(text, 10) - 1;
});
}
// Function to handle 'gantt_data' event
@@ -77,6 +81,7 @@ function handleGanttData(data) {
if (!connectedClients[blenderId].shown) {
connectedClients[blenderId] = {
shown: true,
ifc_file: filename,
ganttTasks: ganttTasks,
workSchedule: ganttWorkSched,
};
@@ -89,6 +94,7 @@ function handleGanttData(data) {
} else {
connectedClients[blenderId] = {
shown: true,
ifc_file: filename,
ganttTasks: ganttTasks,
workSchedule: ganttWorkSched,
};
@@ -105,6 +111,10 @@ function handleDefaultData(data) {
// Function to add a new gantt with data and filename
function addGanttElement(blenderId, tasks, workSched, filename) {
$("#blender-count").text(function (i, text) {
return parseInt(text, 10) + 1;
});
const ganttContainer = $("<div></div>")
.addClass("gantt-container")
.attr("id", "container-" + blenderId);
@@ -382,3 +392,75 @@ function toggleTheme() {
setTheme("dark");
}
}
function toggleClientList() {
var clientList = $("#client-list");
if (clientList.hasClass("show")) {
clientList.removeClass("show");
return;
}
clientList.empty();
$.each(connectedClients, function (id, client) {
if (!client.shown) return;
const dropdownIcon = $("<i>")
.addClass("fas fa-chevron-down")
.css("margin-left", "0.5rem");
const clientDiv = $("<div>").addClass("client").text(client.ifc_file);
clientDiv.append(dropdownIcon);
const clientDetailsDiv = $("<div>").addClass("client-details");
if (id) {
const clientId = $("<div>")
.addClass("client-detail")
.text(`Blender ID: ${id}`);
clientDetailsDiv.append(clientId);
}
if (client.workSchedule && client.gantt) {
const clientScheduleName = $("<div>")
.addClass("client-detail")
.text(`Schedule Name: ${client.workSchedule.Name}`);
const clientScheduleDate = $("<div>")
.addClass("client-detail")
.text(
`Schedule Date: ${new Date(
client.workSchedule.CreationDate
).toLocaleDateString()}`
);
const scrollButton = $("<button>")
.addClass("scroll-button")
.text("Scroll to Gantt Chart")
.on("click", function () {
$("html, body").animate(
{
scrollTop: $("#gantt-" + id).offset().top,
},
600
);
clientList.removeClass("show");
});
clientDetailsDiv.append(clientScheduleName);
clientDetailsDiv.append(clientScheduleDate);
clientDetailsDiv.append(scrollButton);
}
clientDiv.append(clientDetailsDiv);
clientDiv.on("click", function () {
clientDetailsDiv.toggleClass("show");
});
clientList.append(clientDiv);
});
clientList.addClass("show");
}
@@ -42,6 +42,10 @@ function handleBlenderDisconnect(blenderId) {
delete connectedClients[blenderId];
removeTableElement(blenderId);
}
$("#blender-count").text(function (i, text) {
return parseInt(text, 10) - 1;
});
}
// Function to handle 'csv_data' event
@@ -81,6 +85,10 @@ function handleDefaultData(data) {
// Function to add a new table with data and filename
function addTableElement(blenderId, csvData, filename) {
$("#blender-count").text(function (i, text) {
return parseInt(text, 10) + 1;
});
// store headers of the csv data
const firstLine = csvData.indexOf("\n");
const csvHeaders = csvData.substring(0, firstLine).split(",");
@@ -274,3 +282,66 @@ function toggleTheme() {
setTheme("dark");
}
}
function toggleClientList() {
var clientList = $("#client-list");
if (clientList.hasClass("show")) {
clientList.removeClass("show");
return;
}
clientList.empty();
$.each(connectedClients, function (id, client) {
if (!client.shown) return;
const dropdownIcon = $("<i>")
.addClass("fas fa-chevron-down")
.css("margin-left", "0.5rem");
const clientDiv = $("<div>").addClass("client").text(client.ifc_file);
clientDiv.append(dropdownIcon);
const clientDetailsDiv = $("<div>").addClass("client-details");
if (id) {
const clientId = $("<div>")
.addClass("client-detail")
.text(`Blender ID: ${id}`);
clientDetailsDiv.append(clientId);
}
if (client.headers && client.headers.length) {
const clientHeaders = $("<div>")
.addClass("client-detail")
.text(`Table Headers: ${client.headers.join(", ")}`);
const scrollButton = $("<button>")
.addClass("scroll-button")
.text("Scroll to Table")
.on("click", function () {
$("html, body").animate(
{
scrollTop: $("#table-" + id).offset().top,
},
600
);
clientList.removeClass("show");
});
clientDetailsDiv.append(clientHeaders);
clientDetailsDiv.append(scrollButton);
}
clientDiv.append(clientDetailsDiv);
clientDiv.on("click", function () {
clientDetailsDiv.toggleClass("show");
});
clientList.append(clientDiv);
});
clientList.addClass("show");
}
@@ -0,0 +1,123 @@
<!DOCTYPE html>
<html lang="en" class="dark">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Web Client</title>
<link
rel="stylesheet"
href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css"
/>
<link rel="stylesheet" href="/static/css/drawings.css" />
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.6.0/css/all.min.css"
/>
<script
type="text/javascript"
src="https://code.jquery.com/jquery-3.6.0.min.js"
></script>
<script
type="text/javascript"
src="https://cdnjs.cloudflare.com/ajax/libs/svg.js/3.1.1/svg.min.js"
></script>
<script
type="text/javascript"
src="https://cdn.jsdelivr.net/npm/svg-pan-zoom@3.6.1/dist/svg-pan-zoom.min.js"
></script>
<script
type="text/javascript"
src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"
></script>
<script
type="text/javascript"
src="https://cdn.socket.io/4.0.0/socket.io.min.js"
></script>
<script>
var SOCKET_PORT = {{port}};
</script>
<script defer src="./static/js/drawings.js"></script>
</head>
<body>
<nav>
<img
src="https://blenderbim.org/assets/images/blender/blender-logo.png"
alt="Logo"
class="logo"
/>
<ul>
<li><a href="/">IFC Data</a></li>
<li><a href="/gantt">Gantt Chart</a></li>
<li><a href="/drawings" class="active">Drawings</a></li>
</ul>
<button id="toggle-theme" onclick="toggleTheme()">
<i class="fas fa-moon"></i>
</button>
</nav>
<div id="connected-list-div">
<button id="show-connected-button" onclick="toggleClientList()">
Connected Blenders:
<span id="blender-count">0</span>
<i class="fas fa-chevron-down"></i>
</button>
<div id="client-list"></div>
</div>
<div class="container-fluid d-flex" id="container">
<div class="col-2 left">
<!-- Dropdown menu -->
<div class="dropdown mb-3">
<label for="dropdown-menu">IFC File:</label>
<select class="p-0 pl-2" id="dropdown-menu">
<!-- IFC files will be dynamically populated here -->
<option value="" selected disabled hidden>IFC File</option>
</select>
</div>
<!-- Panel with sub-panels -->
<div class="panel">
<div id="accordion">
<div class="card pb-2">
<div class="card-header overflow-hidden" id="headingOne">
<button
class="btn p-0 w-100 text-left"
data-toggle="collapse"
data-target="#collapseOne"
>
Drawings
</button>
</div>
<div id="collapseOne" class="collapse show">
<div class="card-body">
<ul class="sub-panel pl-2" id="drawings-sub-panel">
<!-- Drawings will be dynamically populated here -->
</ul>
</div>
</div>
</div>
<div class="card pb-2">
<div class="card-header overflow-hidden" id="headingTwo">
<button
class="btn p-0 w-100 text-left"
data-toggle="collapse"
data-target="#collapseTwo"
>
Sheets
</button>
</div>
<div id="collapseTwo" class="collapse show">
<div class="card-body">
<ul class="sub-panel pl-2" id="sheets-sub-panel">
<!-- Sheets will be dynamically populated here -->
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-10 right pl-3">
<div id="svg-container"></div>
</div>
</div>
</body>
</html>
@@ -34,11 +34,23 @@
<ul>
<li><a href="/">IFC Data</a></li>
<li><a href="/gantt" class="active">Gantt Chart</a></li>
<li><a href="/drawings">Drawings</a></li>
</ul>
<button id="toggle-theme" onclick="toggleTheme()">
<i class="fas fa-moon"></i>
</button>
</nav>
<div id="connected-list-div">
<button id="show-connected-button" onclick="toggleClientList()">
Connected Blenders:
<span id="blender-count">0</span>
<i class="fas fa-chevron-down"></i>
</button>
<div id="client-list"></div>
</div>
<div id="container"></div>
<footer>
<p>BlenderBIM Version: {{version}}</p>
</footer>
</body>
</html>
@@ -41,11 +41,23 @@
<ul>
<li><a href="/" class="active">IFC Data</a></li>
<li><a href="/gantt">Gantt Chart</a></li>
<li><a href="/drawings">Drawings</a></li>
</ul>
<button id="toggle-theme" onclick="toggleTheme()">
<i class="fas fa-moon"></i>
</button>
</nav>
<div id="connected-list-div">
<button id="show-connected-button" onclick="toggleClientList()">
Connected Blenders:
<span id="blender-count">0</span>
<i class="fas fa-chevron-down"></i>
</button>
<div id="client-list"></div>
</div>
<div id="container"></div>
<footer>
<p>BlenderBIM Version: {{version}}</p>
</footer>
</body>
</html>
+5
View File
@@ -170,6 +170,11 @@ def subscribe_to(obj, data_path, callback):
def refresh_ui_data():
"""Refresh cached UI data.
Note that calling non-ifc-operators by itself doesn't refresh the UI data
and it need to be refreshed manually if needed.
"""
from blenderbim.bim import modules
for name, value in modules.items():
+2
View File
@@ -50,6 +50,8 @@ def draw_attribute(attribute, layout, copy_operator=None):
return
if value_name == "enum_value":
prop_with_search(layout, attribute, "enum_value", text=attribute.name)
elif value_name == "filepath_value":
attribute.filepath_value.layout_file_select(layout, filter_glob=attribute.filter_glob, text=attribute.name)
elif attribute.name in ["ScheduleDuration", "ActualDuration", "FreeFloat", "TotalFloat"]:
propis = bpy.context.scene.BIMWorkScheduleProperties
for item in propis.durations_attributes:
+3 -3
View File
@@ -551,7 +551,7 @@ class IfcImporter:
for axis in axes:
shape = tool.Loader.create_generic_shape(axis.AxisCurve)
mesh = self.create_mesh(axis, shape)
obj = bpy.data.objects.new(f"IfcGridAxis/{axis.AxisTag}", mesh)
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)
@@ -781,7 +781,7 @@ class IfcImporter:
mesh = bpy.data.meshes.new(mesh_name)
mesh.from_pydata([mathutils.Vector(vertex) * self.unit_scale], [], [])
obj = bpy.data.objects.new("{}/{}".format(product.is_a(), product.Name), mesh)
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)
@@ -847,7 +847,7 @@ class IfcImporter:
mesh.from_pydata(vertex_list, [], [])
tool.Ifc.link(representation, mesh)
obj = bpy.data.objects.new("{}/{}".format(product.is_a(), product.Name), mesh)
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)
return product
@@ -75,7 +75,7 @@ class AggregateData:
return []
if not element:
return []
product_linked_agg_group = [
r
for r in getattr(aggregate, "HasAssignments", []) or []
@@ -85,7 +85,6 @@ class AggregateData:
if not product_linked_agg_group:
return []
total = len(product_linked_agg_group[0].RelatedObjects)
return total
@@ -167,7 +167,7 @@ class BIM_OT_add_aggregate(bpy.types.Operator, tool.Ifc.Operator):
tool.Ifc,
tool.Collector,
tool.Spatial,
structure_obj=tool.Ifc.get_object(current_container),
container=current_container,
element_obj=aggregate,
)
core.assign_object(tool.Ifc, tool.Aggregate, tool.Collector, relating_obj=aggregate, related_obj=obj)
@@ -99,7 +99,7 @@ class BIM_PT_aggregate(Panel):
op = row.operator("bim.select_parts", icon="RESTRICT_SELECT_OFF", text="")
op.obj = context.active_object.name
class BIM_PT_linked_aggregate(Panel):
bl_label = "Linked Aggregates"
bl_idname = "BIM_PT_linked_aggregate"
@@ -133,9 +133,8 @@ class BIM_PT_linked_aggregate(Panel):
props = obj.BIMObjectAggregateProperties
row = layout.row(align=True)
if element.Decomposes:
Number_Linked_Aggregates = AggregateData.data['total_linked_aggregate']
Number_Linked_Aggregates = AggregateData.data["total_linked_aggregate"]
if not Number_Linked_Aggregates:
row.label(text="Not a Linked Aggregate")
else:
@@ -151,6 +150,3 @@ class BIM_PT_linked_aggregate(Panel):
op = row.operator("bim.break_link_to_other_aggregates", text="", icon="X")
else:
row.label(text="Not an Aggregate")
@@ -21,6 +21,7 @@ import json
import ifcopenshell
import ifcopenshell.api
import ifcopenshell.guid
import ifcopenshell.util.element
import blenderbim.bim.helper
import blenderbim.bim.handler
import blenderbim.tool as tool
@@ -36,7 +37,7 @@ class EnableEditingAttributes(bpy.types.Operator):
def execute(self, context):
self.file = IfcStore.get_file()
obj = bpy.data.objects.get(self.obj)
obj = bpy.data.objects[self.obj]
props = obj.BIMAttributeProperties
props.attributes.clear()
@@ -45,8 +45,7 @@ def draw_ui(context, layout, attributes):
row.label(text=attribute["name"])
# row.label(text=attribute["value"])
op = row.operator("bim.select_similar", text=attribute["value"], icon="NONE", emboss=False)
op.key = attribute['name']
op.key = attribute["name"]
# TODO: reimplement, see #1222
# if "IfcSite/" in context.active_object.name or "IfcBuilding/" in context.active_object.name:
@@ -66,10 +66,7 @@ class LoadBcfProject(bpy.types.Operator):
if bcfxml.version.version_id.startswith("2"):
print("No project, we will create one for BBIM.")
bcfxml.project_info = bcf.v2.model.ProjectExtension(
project=bcf.v2.model.Project(
name=nameless,
project_id=str(uuid.uuid4())
), extension_schema=""
project=bcf.v2.model.Project(name=nameless, project_id=str(uuid.uuid4())), extension_schema=""
)
if bcfxml.project.name is None:
bcfxml.project.name = nameless
@@ -971,13 +968,14 @@ class ActivateBcfViewpoint(bpy.types.Operator):
self.set_colours(viewpoint)
def set_exceptions(self, viewpoint, context):
if (
not hasattr(viewpoint.visualization_info.components, "visibility")
or not hasattr(viewpoint.visualization_info.components.visibility.exceptions, "component")
if not hasattr(viewpoint.visualization_info.components, "visibility") or not hasattr(
viewpoint.visualization_info.components.visibility.exceptions, "component"
):
return
exception_global_ids = {v.ifc_guid for v in viewpoint.visualization_info.components.visibility.exceptions.component or []}
exception_global_ids = {
v.ifc_guid for v in viewpoint.visualization_info.components.visibility.exceptions.component or []
}
# print("default_visibility: {}".format(viewpoint.visualization_info.components.visibility.default_visibility))
if viewpoint.visualization_info.components.visibility.default_visibility:
@@ -1034,7 +1032,7 @@ class ActivateBcfViewpoint(bpy.types.Operator):
context.area.type = old
def set_openings_visibility(self, is_visible, context):
pass # We no longer have an openings collection
pass # We no longer have an openings collection
def set_selection(self, viewpoint):
if not viewpoint.visualization_info.components or not viewpoint.visualization_info.components.selection:
@@ -1147,7 +1145,7 @@ class ActivateBcfViewpoint(bpy.types.Operator):
# https://github.com/buildingSMART/BCF-XML/tree/release_3_0/Documentation#coloring
if lv == 8:
t = tuple(int(value[i : i + lv // 4], 16) for i in range(0, lv, lv // 4))
col = [t[1] / 255.0, t[2] / 255.0, t[3] / 255.0, t[0] / 255.0]
col = [t[1] / 255.0, t[2] / 255.0, t[3] / 255.0, t[0] / 255.0]
else:
t = tuple(int(value[i : i + lv // 3], 16) for i in range(0, lv, lv // 3))
col = [t[0] / 255.0, t[1] / 255.0, t[2] / 255.0, 1]
@@ -603,7 +603,7 @@ class AddBoundary(bpy.types.Operator, tool.Ifc.Operator):
objs = context.selected_objects
if not any((element:=tool.Ifc.get_entity(obj)) and element.is_a("IfcSpace") for obj in objs):
if not any((element := tool.Ifc.get_entity(obj)) and element.is_a("IfcSpace") for obj in objs):
self.report({"INFO"}, "No IfcSpace elements selected.")
return {"FINISHED"}
@@ -166,9 +166,9 @@ class BrickschemaReferencesData:
results.append(
{
"id": reference.id(),
"identification": reference.ItemReference
if tool.Ifc.get_schema() == "IFC2X3"
else reference.Identification,
"identification": (
reference.ItemReference if tool.Ifc.get_schema() == "IFC2X3" else reference.Identification
),
"name": reference.Name or "Unnamed",
}
)
@@ -34,6 +34,7 @@ import blenderbim.core.brick as core
import blenderbim.tool.brick as tool
from blenderbim.tool.brick import BrickStore
def update_active_brick_index(self, context):
BrickschemaData.is_loaded = False
@@ -53,7 +54,7 @@ def get_brick_entity_classes(self, context):
return [(uri, uri.split("#")[-1], "") for uri in BrickStore.entity_classes[entity]]
def get_brick_roots(self, context):
def get_brick_roots(self, context):
return [(root, root, "") for root in BrickStore.root_classes]
@@ -64,13 +65,13 @@ def get_brick_relations(self, context):
return relations
relations.append(("http://www.w3.org/2000/01/rdf-schema#label", "label", ""))
return relations
def update_view(self, context):
root = context.scene.BIMBrickProperties.brick_list_root
core.set_brick_list_root(tool.Brick, brick_root=root, split_screen=False)
def split_screen_update_view(self, context):
root = context.scene.BIMBrickProperties.split_screen_brick_list_root
core.set_brick_list_root(tool.Brick, brick_root=root, split_screen=True)
@@ -108,7 +109,11 @@ class BIMBrickProperties(PropertyGroup):
# create relations split screen
split_screen_toggled: BoolProperty(name="Split Screen Toggled", default=False)
split_screen_bricks: CollectionProperty(name="Split Screen Bricks", type=Brick)
split_screen_active_brick_index: IntProperty(name="Split Screen Active Brick Index", update=update_active_brick_index)
split_screen_active_brick_index: IntProperty(
name="Split Screen Active Brick Index", update=update_active_brick_index
)
split_screen_active_brick_class: StringProperty(name="Split Screen Active Brick Class")
split_screen_brick_breadcrumbs: CollectionProperty(name="Split Screen Brick Breadcrumbs", type=StrProperty)
split_screen_brick_list_root: EnumProperty(name="Split Screen Brick List Root", items=get_brick_roots, update=split_screen_update_view)
split_screen_brick_list_root: EnumProperty(
name="Split Screen Brick List Root", items=get_brick_roots, update=split_screen_update_view
)
@@ -46,7 +46,7 @@ class BIM_PT_brickschema_project_info(Panel):
def draw(self, context):
self.props = context.scene.BIMBrickProperties
if not BrickschemaData.data["is_loaded"]:
row = self.layout.row(align=True)
row.operator("bim.new_brick_file", text="Create Project")
@@ -58,7 +58,7 @@ class BIM_PT_brickschema_project_info(Panel):
row.label(text=BrickStore.path, icon="FILEBROWSER")
else:
row.label(text="No file", icon="FILEBROWSER")
row = self.layout.row(align=True)
if BrickStore.last_saved:
row.label(text=BrickStore.last_saved, icon="TIME")
@@ -130,7 +130,7 @@ class BIM_PT_brickschema_create_entity(Panel):
row = self.layout.row(align=True)
prop_with_search(row, self.props, "brick_entity_class", text="Type")
row = self.layout.row(align=True)
active = tool.Ifc.get_entity(context.active_object)
if active and context.selected_objects:
@@ -141,7 +141,7 @@ class BIM_PT_brickschema_create_entity(Panel):
row = self.layout.row(align=True)
row.operator("bim.add_brick", text="Create Entity")
class BIM_PT_brickschema_viewport(Panel):
bl_label = "Viewport"
@@ -194,7 +194,9 @@ class BIM_PT_brickschema_viewport(Panel):
row = grid_right.row()
BIM_UL_bricks.split_screen = True
row.template_list("BIM_UL_bricks", "", self.props, "split_screen_bricks", self.props, "split_screen_active_brick_index")
row.template_list(
"BIM_UL_bricks", "", self.props, "split_screen_bricks", self.props, "split_screen_active_brick_index"
)
if BrickschemaData.data["active_relations"]:
row = self.layout.row(align=True)
@@ -213,7 +215,13 @@ class BIM_PT_brickschema_viewport(Panel):
row.label(text="No selection", icon="INFO")
else:
prop_with_search(row, self.props, "new_brick_relation_type", text="")
row.label(text=split_screen_selection.label if split_screen_selection.label else split_screen_selection.name)
row.label(
text=(
split_screen_selection.label
if split_screen_selection.label
else split_screen_selection.name
)
)
row.operator("bim.add_brick_relation", text="", icon="ADD")
elif self.props.brick_create_relations_toggled:
@@ -234,7 +242,11 @@ class BIM_PT_brickschema_viewport(Panel):
row.label(text=relation["object_name"])
row = split.row(align=True)
row.column().alignment = "RIGHT"
if self.props.brick_edit_relations_toggled and relation["predicate_uri"] and relation["predicate_name"] != "type":
if (
self.props.brick_edit_relations_toggled
and relation["predicate_uri"]
and relation["predicate_name"] != "type"
):
op = row.operator("bim.remove_brick_relation", text="", icon="UNLINKED")
op.predicate = relation["predicate_uri"]
op.object = relation["object_uri"]
@@ -263,6 +275,7 @@ class BIM_UL_bricks(UIList):
row = split.row()
row.label(text=str(item.total_items))
class BIM_PT_ifc_brickschema_references(Panel):
bl_label = "Brickschema References"
bl_idname = "BIM_PT_ifc_brickschema_references"
@@ -291,13 +304,13 @@ class BIM_PT_ifc_brickschema_references(Panel):
row.label(text="No IFC Libraries")
row.operator("bim.convert_brick_project", text="", icon="ADD")
return
elif not BrickschemaReferencesData.data["libraries"]:
row = self.layout.row(align=True)
row.label(text="No IFC Libraries: save the Brick project to create a new library", icon="ERROR")
row = self.layout.row(align=True)
row.label(text="Libraries must have location pointing to a \".ttl\" file", icon="INFO")
row.label(text='Libraries must have location pointing to a ".ttl" file', icon="INFO")
return
row = self.layout.row(align=True)
@@ -318,4 +331,4 @@ class BIM_PT_ifc_brickschema_references(Panel):
row.label(text=reference["identification"], icon="ASSET_MANAGER")
row.label(text=reference["name"])
row.operator("bim.unassign_library_reference", text="", icon="X").reference = reference["id"]
row.operator("bim.view_brick_item", text="", icon="DISCLOSURE_TRI_RIGHT").item = reference["identification"]
row.operator("bim.view_brick_item", text="", icon="DISCLOSURE_TRI_RIGHT").item = reference["identification"]
@@ -47,19 +47,27 @@ class SetActiveBSDDDictionary(bpy.types.Operator):
class SearchBSDDClass(bpy.types.Operator):
bl_idname = "bim.search_bsdd_classifications"
bl_label = "Search bSDD Class"
bl_description = "Search for bSDD classes by the provided keyword"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
keyword = context.scene.BIMBSDDProperties.keyword
core.search_class(keyword, bsdd.Client(), tool.Bsdd)
classes_found = core.search_class(keyword, bsdd.Client(), tool.Bsdd)
self.report({"INFO"}, f"{classes_found} bSDD classes found for '{keyword}'.")
return {"FINISHED"}
class GetBSDDClassificationProperties(bpy.types.Operator):
bl_idname = "bim.get_bsdd_classification_properties"
bl_label = "Search bSDD Classifications"
bl_label = "Search bSDD Class Properties"
bl_description = (
"Search for bSDD class properties for the currently selected class. All non-IFC properties are skipped"
)
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
core.get_class_properties(bsdd.Client(), tool.Bsdd)
pset_data = core.get_class_properties(bsdd.Client(), tool.Bsdd)
psets_found = len(pset_data)
props_found = sum(len(pset) for pset in pset_data.values())
self.report({"INFO"}, f"{psets_found} psets found ({props_found} props) for the active classification.")
return {"FINISHED"}
@@ -43,7 +43,6 @@ class BSDDDomain(PropertyGroup):
class BSDDClassification(PropertyGroup):
name: StringProperty(name="Name")
reference_code: StringProperty(name="Reference Code")
description: StringProperty(name="Description")
uri: StringProperty(name="Namespace URI")
domain_name: StringProperty(name="Domain Name")
domain_namespace_uri: StringProperty(name="Domain Namespace URI")
@@ -62,6 +61,12 @@ class BIMBSDDProperties(PropertyGroup):
classifications: CollectionProperty(name="Classifications", type=BSDDClassification)
active_classification_index: IntProperty(name="Active Classification Index")
keyword: StringProperty(name="Keyword")
should_filter_ifc_class: BoolProperty(name="Filter Active IFC Class", default=True)
load_preview_domains: BoolProperty(name="Load Preview Domains", default=False)
should_filter_ifc_class: BoolProperty(
name="Filter Active IFC Class",
description="Whether to search only for bSDD classes that match active object's IFC class",
default=True,
)
load_preview_domains: BoolProperty(
name="Load Preview Domains", description="Whether it should load preview and inactive domains", default=False
)
classification_psets: CollectionProperty(name="Classification Psets", type=BSDDPset)
@@ -32,6 +32,7 @@ class BIM_PT_bsdd(Panel):
def draw(self, context):
props = context.scene.BIMBSDDProperties
layout = self.layout
row = self.layout.row(align=True)
row.prop(props, "load_preview_domains")
if len(props.domains):
@@ -53,6 +54,22 @@ class BIM_PT_bsdd(Panel):
props,
"active_domain_index",
)
if 0 <= props.active_domain_index < len(props.domains):
selected_domain = props.domains[props.active_domain_index]
else:
selected_domain = None
if selected_domain:
layout.label(text="Selected domain:")
box = layout.box()
row = box.row(align=True)
row.label(text="Language")
row.label(text=selected_domain.default_language_code)
row = box.row(align=True)
row.label(text="Version")
row.label(text=selected_domain.version)
box.operator("bim.open_uri", text="Open bSDD In Browser", icon="URL").uri = selected_domain.uri
else:
row = self.layout.row()
row.operator("bim.load_bsdd_domains")
@@ -61,14 +78,18 @@ class BIM_PT_bsdd(Panel):
class BIM_UL_bsdd_domains(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
if item:
props = context.scene.BIMBSDDProperties
row = layout.row(align=True)
if item.status != "Active":
row.label(text=f"{item.name} ({item.organization_name_owner}) - {item.status}", icon="ERROR")
else:
row.label(text=f"{item.name} ({item.organization_name_owner})")
op = row.operator("bim.set_active_bsdd_domain", text="", icon="RESTRICT_SELECT_OFF")
op.name = item.name
op.uri = item.uri
if item.uri == props.active_uri:
row.label(text="", icon="URL")
else:
op = row.operator("bim.set_active_bsdd_domain", text="", icon="RESTRICT_SELECT_OFF")
op.name = item.name
op.uri = item.uri
class BIM_UL_bsdd_classifications(UIList):
@@ -77,3 +98,4 @@ class BIM_UL_bsdd_classifications(UIList):
row = layout.row(align=True)
row.label(text=item.reference_code)
row.label(text=item.name)
row.operator("bim.open_uri", text="", icon="URL").uri = item.uri
@@ -144,7 +144,7 @@ class CadFillet(bpy.types.Operator):
def execute(self, context):
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
self.radius = self.radius * si_conversion
bpy.ops.object.mode_set(mode="OBJECT")
bpy.ops.object.mode_set(mode="EDIT")
@@ -369,7 +369,7 @@ class CadOffset(bpy.types.Operator):
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
self.distance = self.distance * si_conversion
bpy.ops.object.mode_set(mode="OBJECT")
bpy.ops.object.mode_set(mode="EDIT")
@@ -507,8 +507,6 @@ class CadOffset(bpy.types.Operator):
local_direction = (rotation_i @ direction).normalized()
normals.append(local_direction)
if len(normals) == 2:
# https://stackoverflow.com/a/54042831/9627415
new_normal = (normals[0].lerp(normals[1], 0.5)).normalized()
@@ -638,7 +636,7 @@ class AddIfcArcIndexFillet(bpy.types.Operator):
def execute(self, context):
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
self.radius = self.radius * si_conversion
if self.has_selected_existing_arc(context):
self.change_radius(context)
else:
@@ -814,8 +812,8 @@ class AddRectangle(bpy.types.Operator):
def execute(self, context):
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
self.x = self.x * si_conversion
self.y = self.y * si_conversion
self.y = self.y * si_conversion
obj = context.active_object
bm = bmesh.from_edit_mesh(obj.data)
cursor = obj.matrix_world.inverted() @ context.scene.cursor.location
@@ -70,7 +70,7 @@ class ClashDecorator:
blf.color(font_id, *color)
if coords_2d:
w, h = blf.dimensions(font_id, text)
coords_2d -= Vector((w * .5, 0))
coords_2d -= Vector((w * 0.5, 0))
blf.position(font_id, coords_2d[0], coords_2d[1], 0)
blf.draw(font_id, text) # Set your text here
@@ -496,4 +496,3 @@ class SelectSmartGroup(bpy.types.Operator):
with bpy.context.temp_override(**context_override):
bpy.ops.view3d.view_selected()
return {"FINISHED"}
@@ -117,20 +117,29 @@ class AddClassificationFromBSDD(bpy.types.Operator, tool.Ifc.Operator):
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
is_ifc2x3 = tool.Ifc.get_schema() == "IFC2X3"
props = context.scene.BIMBSDDProperties
domain = [d for d in props.domains if d.name == props.active_domain][0]
domain = next((d for d in props.domains if d.name == props.active_domain and d.uri == props.active_uri), None)
# Maybe user loaded preview domains, set it as active
# and then reloaded them without preview domains.
if not domain:
self.report(
{"INFO"},
f"Couldn't find domain '{props.active_domain}' ({props.active_uri}). Try to reload bSDD dictionaries.",
)
return
for element in tool.Ifc.get().by_type("IfcClassification"):
if element.Name == props.active_domain or (not is_ifc2x3 and element.Location == domain.uri):
if element.Name == props.active_domain or (tool.Classification.get_location(element) == domain.uri):
self.report({"INFO"}, f"Classification '{props.active_domain}' is already added to the project.")
return
classification = ifcopenshell.api.run(
"classification.add_classification", tool.Ifc.get(), classification=props.active_domain
)
classification.Source = domain.organization_name_owner
classification.Edition = domain.version
if not is_ifc2x3:
classification.Location = domain.uri
tool.Classification.set_location(classification, domain.uri)
class EnableAddingManualClassification(bpy.types.Operator):
@@ -400,8 +409,6 @@ class AddClassificationReferenceFromBSDD(bpy.types.Operator, tool.Ifc.Operator):
obj_type: bpy.props.StringProperty()
def _execute(self, context):
is_ifc2x3 = tool.Ifc.get_schema() == "IFC2X3"
if self.obj_type == "Object":
if context.selected_objects:
objects = [o.name for o in context.selected_objects]
@@ -416,9 +423,8 @@ class AddClassificationReferenceFromBSDD(bpy.types.Operator, tool.Ifc.Operator):
classification = None
for element in tool.Ifc.get().by_type("IfcClassification"):
if (
element.Name == bsdd_classification.domain_name
or (not is_ifc2x3 and element.Location == bsdd_classification.domain_namespace_uri)
if element.Name == bsdd_classification.domain_name or (
tool.Classification.get_location(element) == bsdd_classification.domain_namespace_uri
):
classification = element
break
@@ -427,8 +433,7 @@ class AddClassificationReferenceFromBSDD(bpy.types.Operator, tool.Ifc.Operator):
classification = ifcopenshell.api.run(
"classification.add_classification", tool.Ifc.get(), classification=bsdd_classification.domain_name
)
if not is_ifc2x3:
classification.Location = bsdd_classification.domain_namespace_uri
tool.Classification.set_location(classification, bsdd_classification.domain_namespace_uri)
for obj in objects:
ifc_definition_id = tool.Blender.get_obj_ifc_definition_id(obj, self.obj_type, context)
@@ -105,7 +105,7 @@ class BIM_PT_object_constraints(Panel):
if constraint["type"] == "IfcObjective":
icon = "LIGHT"
else:
continue # Metric not implemented
continue # Metric not implemented
row = self.layout.row(align=True)
row.label(text=constraint["name"])
row.operator("bim.unassign_constraint", text="", icon="X").constraint = constraint["id"]
@@ -164,7 +164,14 @@ class CostSchedulesData:
if unit:
data["UnitSymbol"] = ifcopenshell.util.unit.get_unit_symbol(unit)
if quantity.is_a("IfcPhysicalSimpleQuantity"):
measure_class = quantity.wrapped_data.declaration().as_entity().attribute_by_index(3).type_of_attribute().declared_type().name()
measure_class = (
quantity.wrapped_data.declaration()
.as_entity()
.attribute_by_index(3)
.type_of_attribute()
.declared_type()
.name()
)
if "Count" in measure_class:
data["UnitSymbol"] = "U"
@@ -47,14 +47,14 @@ class BIM_PT_cost_schedules(Panel):
row = self.layout.row(align=True)
if CostSchedulesData.data["total_cost_schedules"]:
row.alignment = "RIGHT"
row.operator("bim.export_cost_schedules",icon="EXPORT",text="Export All Schedules")
row.operator("bim.export_cost_schedules", icon="EXPORT", text="Export All Schedules")
row = self.layout.row(align=True)
row.label(text=f"{CostSchedulesData.data['total_cost_schedules']} Cost Schedules Found", icon="TEXT")
else:
row.label(text="No Cost Schedules found.", icon="TEXT")
row.operator("bim.add_cost_schedule", icon="ADD", text="")
row.operator("bim.import_cost_schedule_csv",icon="IMPORT",text="")
row.operator("bim.import_cost_schedule_csv", icon="IMPORT", text="")
for schedule in CostSchedulesData.data["schedules"]:
self.draw_cost_schedule_ui(schedule)
@@ -96,9 +96,9 @@ class BIM_PT_cost_schedules(Panel):
text="{}[{}]".format(cost_schedule["name"], cost_schedule["predefined_type"]), icon="LINENUMBERS_ON"
)
row.operator("bim.enable_editing_cost_items", text="", icon="OUTLINER").cost_schedule = cost_schedule["id"]
row.operator(
"bim.enable_editing_cost_schedule_attributes", text="", icon="GREASEPENCIL"
).cost_schedule = cost_schedule["id"]
row.operator("bim.enable_editing_cost_schedule_attributes", text="", icon="GREASEPENCIL").cost_schedule = (
cost_schedule["id"]
)
row.operator("bim.remove_cost_schedule", text="", icon="X").cost_schedule = cost_schedule["id"]
if self.props.active_cost_schedule_id == cost_schedule["id"]:
if self.props.is_editing == "COST_SCHEDULE_ATTRIBUTES":
@@ -616,9 +616,9 @@ class BIM_UL_cost_items_trait:
def draw_parent_operator(self, row, cost_item_id):
if self.props.active_cost_item_id:
if self.props.active_cost_item_id != cost_item_id:
op = row.operator(
"bim.change_parent_cost_item", text="", icon="LINKED", emboss=False
).new_parent = cost_item_id
op = row.operator("bim.change_parent_cost_item", text="", icon="LINKED", emboss=False).new_parent = (
cost_item_id
)
else:
row.label(text="", icon="BLANK1")
@@ -18,7 +18,8 @@
import bpy
from blenderbim.bim.prop import StrProperty, Attribute
#from blenderbim.bim.module.spatial.data import SpatialData
# from blenderbim.bim.module.spatial.data import SpatialData
from bpy.types import PropertyGroup
from bpy.props import (
PointerProperty,
@@ -30,10 +31,13 @@ from bpy.props import (
FloatVectorProperty,
CollectionProperty,
)
#import blenderbim.tool as tool
#import blenderbim.core.geometry
#import ifcopenshell
# import blenderbim.tool as tool
# import blenderbim.core.geometry
# import ifcopenshell
class BIMCoveringProperties(PropertyGroup):
ceiling_height: bpy.props.FloatProperty(name="ceiling_height", default=2.7, subtype="DISTANCE", description="Ceiling height")
ceiling_height: bpy.props.FloatProperty(
name="ceiling_height", default=2.7, subtype="DISTANCE", description="Ceiling height"
)
@@ -89,7 +89,7 @@ class BIM_PT_ifccsv(Panel):
row = layout.row(align=True)
op = row.operator("bim.search", text="Select", icon="VIEWZOOM")
op.property_group = "CsvProperties"
op.property_group = "CsvProperties"
layout.separator()
row = layout.row(align=True)
@@ -128,6 +128,3 @@ class BIM_PT_ifccsv(Panel):
row = layout.row(align=True)
row.operator("bim.export_ifccsv", icon="EXPORT", text="Export IFC to " + props.format.upper())
row.operator("bim.import_ifccsv", icon="IMPORT")
@@ -90,9 +90,9 @@ class BIM_PT_debug(Panel):
row.operator("bim.select_high_polygon_meshes").threshold = context.scene.BIMDebugProperties.number_of_polygons
row.prop(props, "number_of_polygons", text="")
row = layout.split(factor=0.7, align=True)
row.operator(
"bim.select_highest_polygon_meshes"
).percentile = context.scene.BIMDebugProperties.percentile_of_polygons
row.operator("bim.select_highest_polygon_meshes").percentile = (
context.scene.BIMDebugProperties.percentile_of_polygons
)
row.prop(props, "percentile_of_polygons", text="")
row = layout.split(factor=0.5, align=True)
@@ -140,9 +140,9 @@ class BIM_PT_debug(Panel):
op = row.operator("bim.print_object_placement", icon="OBJECT_ORIGIN", text="")
op.step_id = attribute.int_value
if attribute.int_value:
row.operator(
"bim.inspect_from_step_id", icon="DISCLOSURE_TRI_RIGHT", text=""
).step_id = attribute.int_value
row.operator("bim.inspect_from_step_id", icon="DISCLOSURE_TRI_RIGHT", text="").step_id = (
attribute.int_value
)
if props.inverse_attributes:
layout.label(text="Inverse attributes:")
@@ -152,9 +152,9 @@ class BIM_PT_debug(Panel):
row.prop(attribute, "name", text="")
row.prop(attribute, "string_value", text="")
if attribute.int_value:
row.operator(
"bim.inspect_from_step_id", icon="DISCLOSURE_TRI_RIGHT", text=""
).step_id = attribute.int_value
row.operator("bim.inspect_from_step_id", icon="DISCLOSURE_TRI_RIGHT", text="").step_id = (
attribute.int_value
)
if props.inverse_references:
layout.label(text="Inverse references:")
@@ -163,6 +163,6 @@ class BIM_PT_debug(Panel):
row = layout.row(align=True)
row.prop(attribute, "string_value", text="")
if attribute.int_value:
row.operator(
"bim.inspect_from_step_id", icon="DISCLOSURE_TRI_RIGHT", text=""
).step_id = attribute.int_value
row.operator("bim.inspect_from_step_id", icon="DISCLOSURE_TRI_RIGHT", text="").step_id = (
attribute.int_value
)
@@ -29,6 +29,7 @@
import bpy
from blenderbim.bim.module.demo.data import DemoData
# Every panel in the interface correlates to one of these classes. If you enable
# the "Developer Extras" option in Blender, then you can actually right click on
# any panel in Blender, and click "Edit Source". This will bring you to a ui.py
@@ -159,7 +159,7 @@ class BaseDecorator:
objecttype = "NOTDEFINED"
def __init__(self):
self.font_id = 0 # 0 is the default font
self.font_id = 0 # 0 is the default font
# POLYLINE_UNIFORM_COLOR is good for smoothed lines since `bgl.enable(GL_LINE_SMOOTH)` is deprecated
self.line_shader = gpu.shader.from_builtin("POLYLINE_UNIFORM_COLOR")
@@ -2020,7 +2020,6 @@ class DecorationsHandler:
for decorator in self.decorators.values():
decorator.font_id = font_id
def get_objects_and_decorators(self, collection):
# TODO: do it in data instead of the handler for performance?
results = []
@@ -41,9 +41,8 @@ def set_active_camera_resolution(scene):
props = scene.camera.data.BIMCameraProperties
ortho_scale = max((props.width, props.height))
aspect_ratio = props.width / props.height
if (
(scene.camera.data.ortho_scale != ortho_scale)
or (scene.render.resolution_x / scene.render.resolution_y != aspect_ratio)
if (scene.camera.data.ortho_scale != ortho_scale) or (
scene.render.resolution_x / scene.render.resolution_y != aspect_ratio
):
scene.camera.data.ortho_scale = ortho_scale
@@ -127,7 +127,7 @@ def format_distance(
precision=None,
decimal_places=None,
suppress_zero_inches=False,
in_unit_length = False
in_unit_length=False,
):
s_code = "\u00b2" # Superscript two THIS IS LEGACY (but being kept for when Area Measurements are re-implimented)
@@ -141,9 +141,9 @@ def format_distance(
# Imperial Formatting
if unit_system == "IMPERIAL":
if in_unit_length:
if unit_length == 'INCHES':
if unit_length == "INCHES":
toInches = 1
if unit_length == 'FEET':
if unit_length == "FEET":
toInches = 11.9999
else:
toInches = 39.3700787401574887
@@ -222,11 +222,11 @@ def format_distance(
# METRIC FORMATTING
elif unit_system == "METRIC":
if in_unit_length:
if unit_length == 'CENTIMETERS':
value = value/100
if unit_length == 'MILLIMETERS':
value = value/1000
if unit_length == "CENTIMETERS":
value = value / 100
if unit_length == "MILLIMETERS":
value = value / 1000
if precision:
value = precision * round(float(value) / precision)
@@ -1472,12 +1472,9 @@ class ActivateModel(bpy.types.Operator):
CutDecorator.uninstall()
# save current visibility statuses for Views and Types collections
# save current visibility statuses
visibility_status: dict[bpy.types.Object, bool] = {}
for col in bpy.data.collections["Views"].children:
for obj in col.objects:
visibility_status[obj] = obj.hide_get()
for obj in bpy.data.collections["Types"].objects:
for obj in bpy.data.objects:
visibility_status[obj] = obj.hide_get()
if not bpy.app.background:
@@ -1508,6 +1505,7 @@ class ActivateModel(bpy.types.Operator):
obj.hide_set(hide_status)
tool.Blender.update_viewport()
blenderbim.bim.handler.refresh_ui_data()
return {"FINISHED"}
@@ -560,7 +560,7 @@ class Scheduler:
text_tag = self.svg.text("", **(text_params | {"font-size": "0"} | box_alignment_params))
# TODO: Should be done without using magic number for self.font_width
# TODO: Should be done without using magic number for self.font_width
if wrap_text:
wrapped_lines = []
for line in text_lines:
@@ -123,18 +123,18 @@ class BIM_PT_element_filters(Panel):
self.layout, props.include_filter_groups, ElementFiltersData, "drawing_include"
)
row = self.layout.row(align=True)
row.operator(
"bim.edit_element_filter", icon="CHECKMARK", text="Save Include Filter"
).filter_mode = "INCLUDE"
row.operator("bim.edit_element_filter", icon="CHECKMARK", text="Save Include Filter").filter_mode = (
"INCLUDE"
)
row.operator("bim.enable_editing_element_filter", icon="CANCEL", text="").filter_mode = "NONE"
elif props.filter_mode == "EXCLUDE":
blenderbim.bim.helper.draw_filter(
self.layout, props.exclude_filter_groups, ElementFiltersData, "drawing_exclude"
)
row = self.layout.row(align=True)
row.operator(
"bim.edit_element_filter", icon="CHECKMARK", text="Save Exclude Filter"
).filter_mode = "EXCLUDE"
row.operator("bim.edit_element_filter", icon="CHECKMARK", text="Save Exclude Filter").filter_mode = (
"EXCLUDE"
)
row.operator("bim.enable_editing_element_filter", icon="CANCEL", text="").filter_mode = "NONE"
else:
row = self.layout.row(align=True)
@@ -260,9 +260,9 @@ class BIM_PT_drawings(Panel):
row2 = col.row(align=True)
row2.operator("bim.remove_drawing", icon="X", text="").drawing = active_drawing.ifc_definition_id
row2.operator(
"bim.duplicate_drawing", icon="COPYDOWN", text=""
).drawing = active_drawing.ifc_definition_id
row2.operator("bim.duplicate_drawing", icon="COPYDOWN", text="").drawing = (
active_drawing.ifc_definition_id
)
col = row.column()
col.alignment = "RIGHT"
@@ -339,9 +339,9 @@ class BIM_PT_schedules(Panel):
row = self.layout.row(align=True)
row.alignment = "RIGHT"
row.operator("bim.open_schedule", icon="URL", text="").schedule = active_schedule.ifc_definition_id
row.operator(
"bim.build_schedule", icon="LINENUMBERS_ON", text=""
).schedule = active_schedule.ifc_definition_id
row.operator("bim.build_schedule", icon="LINENUMBERS_ON", text="").schedule = (
active_schedule.ifc_definition_id
)
row.operator("bim.remove_schedule", icon="X", text="").schedule = active_schedule.ifc_definition_id
self.layout.template_list(
@@ -612,13 +612,13 @@ class BIM_UL_sheets(bpy.types.UIList):
row = layout.row(align=True)
if item.is_sheet:
if item.is_expanded:
row.operator(
"bim.contract_sheet", text="", emboss=False, icon="DISCLOSURE_TRI_DOWN"
).sheet = item.ifc_definition_id
row.operator("bim.contract_sheet", text="", emboss=False, icon="DISCLOSURE_TRI_DOWN").sheet = (
item.ifc_definition_id
)
else:
row.operator(
"bim.expand_sheet", text="", emboss=False, icon="DISCLOSURE_TRI_RIGHT"
).sheet = item.ifc_definition_id
row.operator("bim.expand_sheet", text="", emboss=False, icon="DISCLOSURE_TRI_RIGHT").sheet = (
item.ifc_definition_id
)
row.label(text=f"{item.identification} - {item.name}")
else:
@@ -22,7 +22,6 @@ from . import ui, prop, operator
classes = (
operator.ExecuteIfcFM,
operator.ExecuteIfcFMFederate,
operator.SelectFMIfcFile,
operator.SelectFMSpreadsheetFiles,
prop.BIMFMProperties,
ui.BIM_PT_fm,
@@ -24,21 +24,8 @@ import logging
import tempfile
import ifcopenshell
import blenderbim.tool as tool
from blenderbim.bim.operator import MultipleFileSelector
class SelectFMIfcFile(MultipleFileSelector):
bl_idname = "bim.select_fm_ifc_file"
bl_label = "Select FM IFC File"
filename_ext = ".ifc"
filter_glob: bpy.props.StringProperty(default="*.ifc;*.ifczip;*.ifcxml", options={"HIDDEN"})
def execute(self, context):
props = context.scene.BIMFMProperties
self.update_props(props, "ifc_file", props.ifc_files)
return {"FINISHED"}
class ExecuteIfcFM(bpy.types.Operator):
bl_idname = "bim.execute_ifcfm"
bl_label = "Execute IfcFM"
@@ -49,7 +36,7 @@ class ExecuteIfcFM(bpy.types.Operator):
@classmethod
def poll(cls, context):
props = context.scene.BIMFMProperties
return props.should_load_from_memory or props.ifc_file
return props.should_load_from_memory or props.ifc_files.single_file
def invoke(self, context, event):
props = context.scene.BIMFMProperties
@@ -62,14 +49,13 @@ class ExecuteIfcFM(bpy.types.Operator):
props = context.scene.BIMFMProperties
ifc_file = tool.Ifc.get()
filepaths = []
if not (ifc_file and props.should_load_from_memory):
if len(props.ifc_files):
ifc_files = [ifcopenshell.open(f.name) for f in props.ifc_files]
filepaths = [f.name for f in props.ifc_files]
else:
ifc_files = [ifcopenshell.open(props.ifc_file)]
else:
if ifc_file and props.should_load_from_memory:
ifc_files = [ifc_file]
else:
ifc_files = [ifcopenshell.open(f.name) for f in props.ifc_files.file_list]
if len(ifc_files) > 1:
filepaths = [f.name for f in props.ifc_files.file_list]
for i, ifc_file in enumerate(ifc_files):
if filepaths:
@@ -18,7 +18,7 @@
import bpy
from blenderbim.bim.module.fm.data import FMData
from blenderbim.bim.prop import StrProperty
from blenderbim.bim.prop import MultipleFileSelect, StrProperty
from bpy.types import PropertyGroup
from bpy.props import (
PointerProperty,
@@ -39,11 +39,10 @@ def get_engine(self, context):
class BIMFMProperties(PropertyGroup):
ifc_file: StringProperty(default="", name="IFC File")
ifc_files: CollectionProperty(name="IFC Files", type=StrProperty)
ifc_files: PointerProperty(type=MultipleFileSelect)
spreadsheet_files: CollectionProperty(name="Spreadsheets", type=StrProperty)
should_load_from_memory: BoolProperty(default=False, name="Load from Memory")
engine: EnumProperty(items=get_engine, name="Engine")
engine: EnumProperty(items=get_engine, name="Engine", options=set())
format: EnumProperty(
items=[
("csv", "csv", ""),
@@ -52,4 +51,5 @@ class BIMFMProperties(PropertyGroup):
],
name="Format",
default="ods",
options=set(),
)
@@ -45,12 +45,8 @@ class BIM_PT_fm(Panel):
row.prop(props, "should_load_from_memory")
if not IfcStore.get_file() or not props.should_load_from_memory:
row = layout.row(align=True)
if len(props.ifc_files) > 1:
row.label(text=f"{len(props.ifc_files)} Files Selected")
else:
row.prop(props, "ifc_file")
row.operator("bim.select_fm_ifc_file", icon="FILE_FOLDER", text="")
row = layout.row()
props.ifc_files.layout_file_select(row, "*.ifc;*.ifczip;*.ifcxml", "IFC File(s)")
row = layout.row()
row.prop(props, "engine")
@@ -85,15 +85,15 @@ class BIM_MT_object_set_origin(Menu):
bl_label = "IFC Set Origin"
def draw(self, context):
self.layout.operator(
"bim.override_origin_set", icon="PLUGIN", text="IFC Geometry to Origin"
).origin_type = "GEOMETRY_ORIGIN"
self.layout.operator(
"bim.override_origin_set", icon="PLUGIN", text="IFC Origin to Geometry"
).origin_type = "ORIGIN_GEOMETRY"
self.layout.operator(
"bim.override_origin_set", icon="PLUGIN", text="IFC Origin to 3D Cursor"
).origin_type = "ORIGIN_CURSOR"
self.layout.operator("bim.override_origin_set", icon="PLUGIN", text="IFC Geometry to Origin").origin_type = (
"GEOMETRY_ORIGIN"
)
self.layout.operator("bim.override_origin_set", icon="PLUGIN", text="IFC Origin to Geometry").origin_type = (
"ORIGIN_GEOMETRY"
)
self.layout.operator("bim.override_origin_set", icon="PLUGIN", text="IFC Origin to 3D Cursor").origin_type = (
"ORIGIN_CURSOR"
)
self.layout.operator(
"bim.override_origin_set", icon="PLUGIN", text="IFC Origin to Center of Mass (Surface)"
).origin_type = "ORIGIN_CENTER_OF_MASS"
@@ -22,8 +22,6 @@ from . import ui, prop, operator
classes = (
operator.AddGeoreferencing,
operator.ConvertAngleToCoordinates,
operator.ConvertGlobalToLocal,
operator.ConvertLocalToGlobal,
operator.DisableEditingGeoreferencing,
operator.DisableEditingTrueNorth,
operator.DisableEditingWCS,
@@ -21,7 +21,7 @@ import bpy
import numpy as np
import blenderbim.tool as tool
import ifcopenshell.util.geolocation
from mathutils import Matrix
from mathutils import Matrix, Vector
from ifcopenshell.util.doc import get_entity_doc
@@ -44,6 +44,7 @@ class GeoreferenceData:
cls.data["local_unit_symbol"] = cls.local_unit_symbol()
cls.data["map_unit_symbol"] = cls.map_unit_symbol()
cls.data["world_coordinate_system"] = cls.world_coordinate_system()
cls.data["local_origin"] = cls.local_origin()
cls.is_loaded = True
@classmethod
@@ -162,8 +163,40 @@ class GeoreferenceData:
result["has_transformation"] = True
result["rotation"] = str(round(ifcopenshell.util.geolocation.yaxis2angle(*wcs[:, 1][:2]), 3))
result["x"], result["y"], result["z"] = wcs[:, 3][:3]
props = bpy.context.scene.BIMGeoreferenceProperties
if props.has_blender_offset:
blender_xyz = ifcopenshell.util.geolocation.enh2xyz(
result["x"],
result["y"],
result["z"],
float(props.blender_offset_x),
float(props.blender_offset_y),
float(props.blender_offset_z),
float(props.blender_x_axis_abscissa),
float(props.blender_x_axis_ordinate),
)
else:
blender_xyz = wcs[:, 3][:3]
result["blender_x"], result["blender_y"], result["blender_z"] = blender_xyz
result["blender_location"] = Vector([co * unit_scale for co in blender_xyz])
wcs[0][3] *= unit_scale
wcs[1][3] *= unit_scale
wcs[2][3] *= unit_scale
result["matrix"] = Matrix(wcs)
return result
@classmethod
def local_origin(cls):
props = bpy.context.scene.BIMGeoreferenceProperties
if not props.has_blender_offset:
return
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
x = float(props.blender_offset_x) * -1
y = float(props.blender_offset_y) * -1
z = float(props.blender_offset_z) * -1
enh = ifcopenshell.util.geolocation.auto_xyz2enh(tool.Ifc.get(), 0, 0, 0)
xyz_si = Vector([x * unit_scale, y * unit_scale, z * unit_scale])
return {"x": x, "y": y, "z": z, "enh": enh, "location": xyz_si}
@@ -16,6 +16,7 @@
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
import blf
import gpu
import bmesh
@@ -61,10 +62,10 @@ class GeoreferenceDecorator:
self.calculate_angles(context)
props = context.scene.BIMGeoreferenceProperties
e, n, h = [round(float(o), 7) for o in props.model_origin.split(",")]
wcs_matrix = None
if GeoreferenceData.data["world_coordinate_system"]["has_transformation"]:
wcs_matrix = GeoreferenceData.data["world_coordinate_system"]["matrix"]
if (operation := GeoreferenceData.data["coordinate_operation"]) and (scale := operation.get("Scale", None)):
e *= scale
n *= scale
h *= scale
self.addon_prefs = tool.Blender.get_addon_preferences()
@@ -75,10 +76,14 @@ class GeoreferenceDecorator:
blf.enable(self.font_id, blf.SHADOW)
blf.shadow(self.font_id, 6, 0, 0, 0, 1)
text = "Blender Coordinates X: 0, Y: 0, Z: 0"
if props.has_blender_offset:
text = "Blender Origin"
text += f"\nBlender Coordinates ({GeoreferenceData.data['local_unit_symbol']}) X: 0, Y: 0, Z: 0"
else:
text = "IFC Local Origin"
text += f"\nLocal Coordinates ({GeoreferenceData.data['local_unit_symbol']}) X: {props.blender_offset_x}, Y: {props.blender_offset_y}, Z: {props.blender_offset_z}"
if GeoreferenceData.data["projected_crs"]:
text += f"\nMap Coordinates ({GeoreferenceData.data['local_unit_symbol']}) E: {e}, N: {n}, H: {h}"
text += f"\nMap Coordinates ({GeoreferenceData.data['map_unit_symbol']}) E: {e}, N: {n}, H: {h}"
self.draw_text_at_position(context, text, Vector((0, -0.1, 0)))
angle = Matrix.Rotation(radians(self.pn_angle), 4, "Z")
@@ -111,25 +116,42 @@ class GeoreferenceDecorator:
arc_mid = angle_half @ arc_start
self.draw_text_at_position(context, f"{self.tn_angle}deg", arc_mid)
if wcs_matrix:
x, y, z = wcs_matrix.translation
if (wcs := GeoreferenceData.data["world_coordinate_system"]) and wcs["has_transformation"]:
text = "WCS"
if props.has_blender_offset:
text += f"\nBlender Coordinates ({GeoreferenceData.data['local_unit_symbol']}) X: {wcs['blender_x']}, Y: {wcs['blender_y']}, Z: {wcs['blender_z']}"
text += f"\nLocal Coordinates ({GeoreferenceData.data['local_unit_symbol']}) X: {wcs['x']}, Y: {wcs['y']}, Z: {wcs['z']}"
if operation := GeoreferenceData.data["coordinate_operation"]:
e = operation.get("Eastings")
n = operation.get("Northings")
h = operation.get("OrthogonalHeight")
text = f"WCS\n X: {x}, Y: {y}, Z: {z}\nE: {e}, N: {n}, H: {h}"
else:
text = f"WCS\n X: {x}, Y: {y}, Z: {z}"
text += f"\nMap Coordinates ({GeoreferenceData.data['map_unit_symbol']}) E: {e}, N: {n}, H: {h}"
if wcs_matrix.translation.length < 1000:
position = wcs_matrix.translation.copy()
if wcs["blender_location"].length < 1000:
position = wcs["blender_location"].copy()
else:
position = wcs_matrix.translation.normalized() * 3
position = wcs["blender_location"].normalized() * 3
text += "\n(Warning: Actual XYZ Not Shown)"
position -= Vector((0, 0.1, 0))
self.draw_text_at_position(context, text, position)
if props.has_blender_offset:
text = "IFC Local Origin"
x = GeoreferenceData.data["local_origin"]["x"]
y = GeoreferenceData.data["local_origin"]["y"]
z = GeoreferenceData.data["local_origin"]["z"]
location = GeoreferenceData.data["local_origin"]["location"]
text += f"\nBlender Coordinates ({GeoreferenceData.data['local_unit_symbol']}) X: {x}, Y: {y}, Z: {z}"
text += f"\nLocal Coordinates ({GeoreferenceData.data['local_unit_symbol']}) X: 0, Y: 0, Z: 0"
if GeoreferenceData.data["projected_crs"]:
e, n, h = GeoreferenceData.data["local_origin"]["enh"]
text += f"\nMap Coordinates ({GeoreferenceData.data['map_unit_symbol']}) E: {e}, N: {n}, H: {h}"
if location.length > 1000:
location = location.normalized() * 3
text += "\n(Warning: Actual XYZ Not Shown)"
self.draw_text_at_position(context, text, location)
def draw_text_at_position(self, context, text, position):
coords_2d = location_3d_to_region_2d(context.region, context.region_data, position)
if not coords_2d:
@@ -144,10 +166,6 @@ class GeoreferenceDecorator:
def draw_geometry(self, context):
self.calculate_angles(context)
wcs_matrix = None
if GeoreferenceData.data["world_coordinate_system"]["has_transformation"]:
wcs_matrix = GeoreferenceData.data["world_coordinate_system"]["matrix"]
self.addon_prefs = tool.Blender.get_addon_preferences()
decorator_color = self.addon_prefs.decorations_colour
decorator_color_special = self.addon_prefs.decorator_color_special
@@ -263,24 +281,50 @@ class GeoreferenceDecorator:
verts, edges = arc_segments
self.draw_batch("LINES", verts, decorator_color_special, edges)
if wcs_matrix:
if wcs_matrix.translation.length < 1000:
verts = [Vector((0, 0, 0)), wcs_matrix.translation]
if (wcs := GeoreferenceData.data["world_coordinate_system"]) and wcs["has_transformation"]:
if wcs["blender_location"].length < 1000:
verts = [Vector((0, 0, 0)), wcs["blender_location"]]
edges = [[0, 1]]
self.draw_batch("LINES", verts, decorator_color_special, edges)
self.draw_batch("POINTS", verts[1:], decorator_color_special)
else:
location = wcs["blender_location"].normalized()
edges = [[0, 1]]
verts = [Vector((0, 0, 0)), wcs_matrix.translation.normalized() * 3]
verts = [Vector((0, 0, 0)), location * 3]
self.draw_batch("LINES", verts, decorator_color_special, edges)
verts = [wcs_matrix.translation.normalized() * 3, wcs_matrix.translation.normalized() * 3.1]
self.draw_batch("LINES", verts, decorator_color_error, edges)
verts = [wcs_matrix.translation.normalized() * 3.12, wcs_matrix.translation.normalized() * 3.2]
self.draw_batch("LINES", verts, decorator_color_error, edges)
verts = [wcs_matrix.translation.normalized() * 3.22, wcs_matrix.translation.normalized() * 3.3]
self.draw_batch("LINES", verts, decorator_color_error, edges)
verts = [wcs_matrix.translation.normalized() * 3.32, wcs_matrix.translation.normalized() * 3.4]
self.draw_batch("LINES", verts, decorator_color_error, edges)
self.draw_dashed_line(location * 3, location * 6, decorator_color_error)
props = context.scene.BIMGeoreferenceProperties
if props.has_blender_offset:
location = GeoreferenceData.data["local_origin"]["location"]
if location.length < 1000:
verts = [Vector((0, 0, 0)), location]
edges = [[0, 1]]
self.draw_batch("LINES", verts, decorator_color_special, edges)
self.draw_batch("POINTS", verts[1:], decorator_color_special)
else:
location = location.normalized()
edges = [[0, 1]]
verts = [Vector((0, 0, 0)), location * 3]
self.draw_batch("LINES", verts, decorator_color_special, edges)
self.draw_dashed_line(location * 3, location * 6, decorator_color_error)
def draw_dashed_line(self, start, end, colour):
direction = (end - start).normalized()
distance = (end - start).length
current_distance = Vector((0, 0, 0))
gap = 0.05 * direction
dash = 0.1 * direction
points = [start]
while current_distance.length < distance:
points.append(start + current_distance + dash)
points.append(start + current_distance + dash + gap)
current_distance += gap + dash
points.pop()
edges = [[i, i + 1] for i in range(0, len(points), 2)]
verts = points
self.draw_batch("LINES", verts, colour, edges)
def calculate_angles(self, context):
self.pn_angle = 0.0
@@ -87,37 +87,6 @@ class GetCursorLocation(bpy.types.Operator, tool.Ifc.Operator):
core.get_cursor_location(tool.Georeference)
class ConvertLocalToGlobal(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.convert_local_to_global"
bl_label = "Convert Local To Global"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Convert local coordinate to global coordinate"
@classmethod
def poll(cls, context):
file = tool.Ifc.get()
props = context.scene.BIMGeoreferenceProperties
return file and props.local_coordinates.count(",") == 2
def _execute(self, context):
core.convert_local_to_global(tool.Georeference)
class ConvertGlobalToLocal(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.convert_global_to_local"
bl_label = "Convert Global To Local"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Convert global coordinate to local coordinate"
@classmethod
def poll(cls, context):
file = tool.Ifc.get()
props = context.scene.BIMGeoreferenceProperties
return file and file.by_type("IfcUnitAssignment") and props.local_coordinates.count(",") == 2
def _execute(self, context):
core.convert_global_to_local(tool.Georeference)
class ConvertAngleToCoordinates(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.convert_angle_to_coord"
bl_label = "Convert Angle To Y Axis"
@@ -18,6 +18,7 @@
import bpy
import ifcopenshell.util.geolocation
import blenderbim.tool as tool
from blenderbim.bim.prop import Attribute
from bpy.types import PropertyGroup
from bpy.props import (
@@ -101,6 +102,74 @@ def update_should_visualise(self, context):
GeoreferenceDecorator.uninstall()
def update_blender_coordinates(self, context):
props = bpy.context.scene.BIMGeoreferenceProperties
if props.is_updating_coordinates:
return
props.is_updating_coordinates = True
blender_coordinates = tool.Georeference.get_coordinates("blender")
local_coordinates = ifcopenshell.util.geolocation.xyz2enh(
*blender_coordinates,
float(props.blender_offset_x),
float(props.blender_offset_y),
float(props.blender_offset_z),
float(props.blender_x_axis_abscissa),
float(props.blender_x_axis_ordinate),
)
tool.Georeference.set_coordinates("local", local_coordinates)
tool.Georeference.set_coordinates(
"map", ifcopenshell.util.geolocation.auto_xyz2enh(tool.Ifc.get(), *local_coordinates)
)
props.is_updating_coordinates = False
def update_local_coordinates(self, context):
props = bpy.context.scene.BIMGeoreferenceProperties
if props.is_updating_coordinates:
return
props.is_updating_coordinates = True
local_coordinates = tool.Georeference.get_coordinates("local")
tool.Georeference.set_coordinates(
"map", ifcopenshell.util.geolocation.auto_xyz2enh(tool.Ifc.get(), *local_coordinates)
)
if props.has_blender_offset:
tool.Georeference.set_coordinates(
"blender",
ifcopenshell.util.geolocation.enh2xyz(
*local_coordinates,
float(props.blender_offset_x),
float(props.blender_offset_y),
float(props.blender_offset_z),
float(props.blender_x_axis_abscissa),
float(props.blender_x_axis_ordinate),
),
)
props.is_updating_coordinates = False
def update_map_coordinates(self, context):
props = bpy.context.scene.BIMGeoreferenceProperties
if props.is_updating_coordinates:
return
props.is_updating_coordinates = True
map_coordinates = tool.Georeference.get_coordinates("map")
local_coordinates = ifcopenshell.util.geolocation.auto_enh2xyz(tool.Ifc.get(), *map_coordinates)
tool.Georeference.set_coordinates("local", local_coordinates)
if props.has_blender_offset:
tool.Georeference.set_coordinates(
"blender",
ifcopenshell.util.geolocation.enh2xyz(
*local_coordinates,
float(props.blender_offset_x),
float(props.blender_offset_y),
float(props.blender_offset_z),
float(props.blender_x_axis_abscissa),
float(props.blender_x_axis_ordinate),
),
)
props.is_updating_coordinates = False
class BIMGeoreferenceProperties(PropertyGroup):
coordinate_operation_class: bpy.props.EnumProperty(
items=get_coordinate_operation_class, name="Coordinate Operation Class"
@@ -111,11 +180,24 @@ class BIMGeoreferenceProperties(PropertyGroup):
is_editing_true_north: BoolProperty(name="Is Editing True North")
coordinate_operation: CollectionProperty(name="Coordinate Operation", type=Attribute)
projected_crs: CollectionProperty(name="Projected CRS", type=Attribute)
is_updating_coordinates: BoolProperty(name="Is Updating Coordinates", default=False)
blender_coordinates: StringProperty(
name="Blender Coordinates",
description='Formatted "x,y,z" (without quotes)',
default="0,0,0",
update=update_blender_coordinates,
)
local_coordinates: StringProperty(
name="Local Coordinates", description='Formatted "x,y,z" (without quotes)', default="0,0,0"
name="Local Coordinates",
description='Formatted "x,y,z" (without quotes)',
default="0,0,0",
update=update_local_coordinates,
)
map_coordinates: StringProperty(
name="Map Coordinates", description='Formatted "x,y,z" (without quotes)', default="0,0,0"
name="Map Coordinates",
description='Formatted "x,y,z" (without quotes)',
default="0,0,0",
update=update_map_coordinates,
)
should_visualise: BoolProperty(
name="Should Visualise",
@@ -288,12 +288,15 @@ class BIM_PT_gis_calculator(Panel):
props = context.scene.BIMGeoreferenceProperties
row = self.layout.row(align=True)
row.prop(props, "local_coordinates", text=f"Local ({GeoreferenceData.data['local_unit_symbol']})")
row.operator("bim.get_cursor_location", text="", icon="TRACKER")
row = self.layout.row()
row.prop(props, "map_coordinates", text=f"Map ({GeoreferenceData.data['map_unit_symbol']})")
if props.has_blender_offset:
row = self.layout.row(align=True)
row.prop(props, "blender_coordinates", text=f"Blender ({GeoreferenceData.data['local_unit_symbol']})")
row.operator("bim.get_cursor_location", text="", icon="TRACKER")
row = self.layout.row(align=True)
row.operator("bim.convert_local_to_global", text="Local to Global")
row.operator("bim.convert_global_to_local", text="Global to Local")
row.prop(props, "local_coordinates", text=f"Local ({GeoreferenceData.data['local_unit_symbol']})")
if not props.has_blender_offset:
row.operator("bim.get_cursor_location", text="", icon="TRACKER")
row = self.layout.row()
row.prop(props, "map_coordinates", text=f"Map ({GeoreferenceData.data['map_unit_symbol']})")
@@ -101,9 +101,9 @@ class LibraryReferencesData:
results.append(
{
"id": library.id(),
"identification": library.ItemReference
if tool.Ifc.get_schema() == "IFC2X3"
else library.Identification,
"identification": (
library.ItemReference if tool.Ifc.get_schema() == "IFC2X3" else library.Identification
),
"name": library.Name or "Unnamed",
}
)
@@ -34,10 +34,12 @@ classes = (
ui.BIM_PT_solar,
)
def register():
bpy.types.Scene.radiance_exporter_properties = bpy.props.PointerProperty(type=prop.RadianceExporterProperties)
bpy.types.Scene.BIMSolarProperties = bpy.props.PointerProperty(type=prop.BIMSolarProperties)
def unregister():
del bpy.types.Scene.radiance_exporter_properties
del bpy.types.Scene.BIMSolarProperties
@@ -87,7 +87,6 @@ class SolarDecorator:
blf.position(self.font_id, co[0], co[1], 0)
blf.draw(self.font_id, line)
def draw_geometry(self, context):
self.addon_prefs = tool.Blender.get_addon_preferences()
decorator_color_special = self.addon_prefs.decorator_color_special
@@ -153,8 +152,7 @@ class SolarDecorator:
analemma_verts = sun_position.sun_calc.calc_analemma(context, h)
coords.extend([origin @ v for v in analemma_verts])
for i in range(len(analemma_verts) - 1):
indices.append((coord_offset + i,
coord_offset + i+1))
indices.append((coord_offset + i, coord_offset + i + 1))
coord_offset += len(analemma_verts)
self.draw_batch("LINES", coords, decorator_color_special, indices)
@@ -37,12 +37,13 @@ from blenderbim.bim.module.light.data import SolarData
ifc_materials = []
class ExportOBJ(bpy.types.Operator):
"""Exports the IFC File to OBJ"""
bl_idname = "export_scene.radiance"
bl_label = "Export"
bl_description = "Export the IFC to OBJ"
def execute(self, context):
@@ -52,12 +53,11 @@ class ExportOBJ(bpy.types.Operator):
context.scene.radiance_exporter_properties.is_exporting = True
# Conversion from IFC to OBJ
# Settings for obj
settings = ifcopenshell.geom.settings()
serializer_settings = ifcopenshell.geom.serializer_settings()
settings.set("dimensionality", ifcopenshell.ifcopenshell_wrapper.SURFACES_AND_SOLIDS)
settings.set("apply-default-materials", True)
serializer_settings.set("use-element-guids", True)
@@ -65,17 +65,17 @@ class ExportOBJ(bpy.types.Operator):
if should_load_from_memory:
ifc_file = tool.Ifc.get()
else:
ifc_file_path = context.scene.radiance_exporter_properties.ifc_file
ifc_file = ifcopenshell.open(ifc_file_path)
# for material in ifc_file.by_type("IfcMaterial"):
# self.report({'INFO'}, f"Material: {material.Name}, ID: {material.id()}")
obj_file_path = os.path.join(output_dir, "model.obj")
mtl_file_path = os.path.join(output_dir, "model.mtl")
serialiser = ifcopenshell.geom.serializers.obj(obj_file_path, mtl_file_path, settings, serializer_settings)
serialiser.setFile(ifc_file)
serialiser.setUnitNameAndMagnitude("METER", 1.0)
@@ -84,10 +84,10 @@ class ExportOBJ(bpy.types.Operator):
if ifc_file.schema in ("IFC2X3", "IFC4"):
elements = ifc_file.by_type("IfcElement") + ifc_file.by_type("IfcProxy")
else:
elements = ifc_file.by_type("IfcElement")
elements = ifc_file.by_type("IfcElement")
elements = [e for e in elements if not e.is_a("IfcFeatureElement") or e.is_a("IfcSurfaceFeature")]
elements = [e for e in elements if not e.is_a("IfcFeatureElement") or e.is_a("IfcSurfaceFeature")]
iterator = ifcopenshell.geom.iterator(settings, ifc_file, multiprocessing.cpu_count(), include=elements)
if iterator.initialize():
while True:
@@ -102,30 +102,31 @@ class ExportOBJ(bpy.types.Operator):
# print(material, dir(material))
# print(material.name)
ifc_materials.append(material.name)
serialiser.write(shape)
if not iterator.next():
break
serialiser.finalize()
context.scene.radiance_exporter_properties.is_exporting = False
self.report({'INFO'}, "Exported OBJ file to: {}".format(obj_file_path))
self.report({"INFO"}, "Exported OBJ file to: {}".format(obj_file_path))
return {'FINISHED'}
return {"FINISHED"}
class RadianceRender(bpy.types.Operator):
"""Radiance Rendering"""
bl_idname = "render_scene.radiance"
bl_label = "Render"
bl_description = "Renders the scene using Radiance"
def execute(self, context):
if pr is None:
self.report({'ERROR'}, "PyRadiance is not available. Cannot perform rendering.")
return {'CANCELLED'}
self.report({"ERROR"}, "PyRadiance is not available. Cannot perform rendering.")
return {"CANCELLED"}
# Get the resolution from the user input
props = context.scene.radiance_exporter_properties
resolution_x, resolution_y = props.radiance_resolution_x, props.radiance_resolution_y
@@ -138,31 +139,29 @@ class RadianceRender(bpy.types.Operator):
obj_file_path = os.path.join(output_dir, "model.obj")
camera = self.get_active_camera(context)
if camera is None:
self.report({'ERROR'}, "No active camera found in the scene. Please add a camera and set it as active.")
return {'CANCELLED'}
self.report({"ERROR"}, "No active camera found in the scene. Please add a camera and set it as active.")
return {"CANCELLED"}
# Get camera position and direction
camera_position, camera_direction = self.get_camera_data(camera)
# Material processing
# style = []
# with open(obj_file_path, "r") as obj_file:
# for line in obj_file:
# if line.startswith("usemtl"):
# l = line.strip().split(" ")
# style.append(l[1])
# Check if json file is empty or not
props = context.scene.radiance_exporter_properties
if props.use_json_file:
# Load data from JSON file
with open(props.json_file, 'r') as file:
with open(props.json_file, "r") as file:
data = json.load(file)
else:
# Use in-UI material mappings
@@ -170,7 +169,6 @@ class RadianceRender(bpy.types.Operator):
print(data)
# Create materials.rad file
materials_file = os.path.join(output_dir, "materials.rad")
with open(materials_file, "w") as file:
@@ -190,19 +188,19 @@ class RadianceRender(bpy.types.Operator):
# print(i)
# file.write("inherit alias " + i + " " + data.get(i, "white") + "\n")
self.report({'INFO'}, "Exported Materials Rad file to: {}".format(materials_file))
self.report({"INFO"}, "Exported Materials Rad file to: {}".format(materials_file))
# Run obj2mesh
rtm_file_path = os.path.join(output_dir, "model.rtm")
mesh_file_path = save_obj2mesh_output(obj_file_path, rtm_file_path, matfiles=[materials_file])
mesh_file_path = save_obj2mesh_output(obj_file_path, rtm_file_path, matfiles=[materials_file])
# subprocess.run(["obj2mesh", "-a", materials_file, obj_file_path, rtm_file_path])
self.report({'INFO'}, "obj2mesh output: {}".format(mesh_file_path))
self.report({"INFO"}, "obj2mesh output: {}".format(mesh_file_path))
scene_file = os.path.join(output_dir, "scene.rad")
with open(scene_file, "w") as file:
# file.write("void mesh model\n1 " + rtm_file_path + "\n0\n0\n")
file.write('void mesh model\n1 "' + rtm_file_path + '"\n0\n0\n')
self.report({'INFO'}, "Exported Scene file to: {}".format(scene_file))
self.report({"INFO"}, "Exported Scene file to: {}".format(scene_file))
# Py Radiance Rendering code
scene = pr.Scene("ascene")
@@ -216,26 +214,31 @@ class RadianceRender(bpy.types.Operator):
aview = pr.View(position=camera_position, direction=camera_direction)
scene.add_view(aview)
image = pr.render(scene, ambbounce=1, resolution=(resolution_x, resolution_y),
quality=quality, detail=detail, variability=variability)
image = pr.render(
scene,
ambbounce=1,
resolution=(resolution_x, resolution_y),
quality=quality,
detail=detail,
variability=variability,
)
output_path = os.path.join(output_dir, f"{output_file_name}.{output_file_format.lower()}")
if output_file_format == 'HDR':
if output_file_format == "HDR":
with open(output_path, "wb") as wtr:
wtr.write(image)
else:
pass
self.report({'INFO'}, "Radiance rendering completed. Output: {}".format(output_path))
return {'FINISHED'}
self.report({"INFO"}, "Radiance rendering completed. Output: {}".format(output_path))
return {"FINISHED"}
def get_active_camera(self, context):
if context.scene.camera:
return context.scene.camera
for obj in context.scene.objects:
if obj.type == 'CAMERA':
if obj.type == "CAMERA":
return obj
return None
@@ -256,10 +259,11 @@ class RadianceRender(bpy.types.Operator):
resolution_y = props.radiance_resolution_y
return resolution_x, resolution_y
def save_obj2mesh_output(inp: Union[bytes, str, Path], output_file: str, **kwargs):
output_bytes = pr.obj2mesh(inp, **kwargs)
with open(output_file, 'wb') as f:
with open(output_file, "wb") as f:
f.write(output_bytes)
return output_file
@@ -333,6 +337,7 @@ class ViewFromSun(bpy.types.Operator):
props.hour = props.hour # Just to refresh camera position
return {"FINISHED"}
class RefreshIFCMaterials(bpy.types.Operator):
bl_idname = "bim.refresh_ifc_materials"
bl_label = "Refresh IFC Materials"
@@ -341,7 +346,7 @@ class RefreshIFCMaterials(bpy.types.Operator):
def execute(self, context):
props = context.scene.radiance_exporter_properties
ifc_file = tool.Ifc.get() if props.should_load_from_memory else ifcopenshell.open(props.ifc_file)
for style in ifc_file.by_type("IfcSurfaceStyle"):
for render_item in style.Styles:
if render_item.is_a("IfcSurfaceStyleRendering"):
@@ -349,5 +354,5 @@ class RefreshIFCMaterials(bpy.types.Operator):
style_name = style.Name or f"Unnamed Style {render_item.id()}"
if not props.get_material_mapping(style_name):
props.add_material_mapping(style_id, style_name)
return {'FINISHED'}
return {"FINISHED"}
@@ -170,110 +170,88 @@ class RadianceExporterProperties(PropertyGroup):
def get_mappings_dict(self):
return {item["style_id"]: item["radiance_material"] for item in self.material_mappings}
use_json_file: BoolProperty(
name="Upload JSON",
description="Toggle between uploading a JSON file and using in-UI material mapping",
default=False
)
is_exporting: bpy.props.BoolProperty(
name="Is Exporting",
description="Whether the OBJ export is in progress",
default=False
default=False,
)
material_mappings: bpy.props.CollectionProperty(
type= bpy.types.PropertyGroup,
name="Material Mappings"
is_exporting: bpy.props.BoolProperty(
name="Is Exporting", description="Whether the OBJ export is in progress", default=False
)
material_mappings: bpy.props.CollectionProperty(type=bpy.types.PropertyGroup, name="Material Mappings")
should_load_from_memory: BoolProperty(
name="Load from Memory",
default=False,
default=False,
)
radiance_resolution_x: IntProperty(
name="X",
description="Horizontal resolution of the output image",
default=1920,
min=1
name="X", description="Horizontal resolution of the output image", default=1920, min=1
)
radiance_resolution_y: IntProperty(
name="Y",
description="Vertical resolution of the output image",
default=1080,
min=1
name="Y", description="Vertical resolution of the output image", default=1080, min=1
)
output_dir: StringProperty(
name="Output Directory",
description="Directory to output Radiance files",
default="",
subtype="DIR_PATH",
update=lambda self, context: self.update_output_dir(context)
update=lambda self, context: self.update_output_dir(context),
)
ifc_file: StringProperty(
name="IFC File",
description="Path to the IFC file",
default="",
subtype="FILE_PATH",
update=lambda self, context: self.update_ifc_file(context)
update=lambda self, context: self.update_ifc_file(context),
)
json_file: StringProperty(
name="JSON File",
description="Path to the JSON file",
default="",
subtype="FILE_PATH",
update=lambda self, context: self.update_json_file(context)
update=lambda self, context: self.update_json_file(context),
)
radiance_quality: EnumProperty(
name="Quality",
description="Radiance rendering quality",
items=[
('LOW', "Low", "Low quality"),
('MEDIUM', "Medium", "Medium quality"),
('HIGH', "High", "High quality")
],
default='MEDIUM'
items=[("LOW", "Low", "Low quality"), ("MEDIUM", "Medium", "Medium quality"), ("HIGH", "High", "High quality")],
default="MEDIUM",
)
radiance_detail: EnumProperty(
name="Detail",
description="Radiance rendering detail",
items=[
('LOW', "Low", "Low detail"),
('MEDIUM', "Medium", "Medium detail"),
('HIGH', "High", "High detail")
],
default='MEDIUM'
items=[("LOW", "Low", "Low detail"), ("MEDIUM", "Medium", "Medium detail"), ("HIGH", "High", "High detail")],
default="MEDIUM",
)
radiance_variability: EnumProperty(
name="Variability",
description="Radiance rendering variability",
items=[
('LOW', "Low", "Low variability"),
('MEDIUM', "Medium", "Medium variability"),
('HIGH', "High", "High variability")
("LOW", "Low", "Low variability"),
("MEDIUM", "Medium", "Medium variability"),
("HIGH", "High", "High variability"),
],
default="MEDIUM",
)
output_file_name: StringProperty(
name="Output File Name",
description="Name of the output image file (without extension)",
default="render"
name="Output File Name", description="Name of the output image file (without extension)", default="render"
)
output_file_format: EnumProperty(
name="Output File Format",
description="Format of the output image file",
items=[
('HDR', "HDR", "High Dynamic Range"),
("HDR", "HDR", "High Dynamic Range"),
],
default='HDR'
default="HDR",
)
class BIMSolarProperties(PropertyGroup):
sites: EnumProperty(items=get_sites, name="Sites")
latitude: FloatProperty(name="Latitude", min=-90, max=90, update=update_latlong)
@@ -26,11 +26,12 @@ sun_position = tool.Blender.get_sun_position_addon()
class BIM_PT_radiance_exporter(bpy.types.Panel):
"""Creates a Panel in the render properties window"""
bl_label = "Radiance Exporter"
bl_idname = "BIM_PT_radiance_exporter"
bl_options = {'DEFAULT_CLOSED'}
bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW'
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_tab_lighting"
@@ -43,15 +44,13 @@ class BIM_PT_radiance_exporter(bpy.types.Panel):
if tool.Ifc.get():
row = self.layout.row()
row.prop(props, "should_load_from_memory")
if not tool.Ifc.get() or not props.should_load_from_memory:
row = self.layout.row(align=True)
row.prop(props, "ifc_file")
# row.operator("bim.select_ifctester_ifc_file", icon="FILE_FOLDER", text="")
row = layout.row()
row.prop(props, "output_dir")
row = layout.row()
layout.prop(props, "use_json_file")
@@ -69,22 +68,21 @@ class BIM_PT_radiance_exporter(bpy.types.Panel):
layout.operator("bim.refresh_ifc_materials")
layout.separator()
row = layout.row()
row.label(text="Resolution")
row.prop(props, "radiance_resolution_x", text="X")
row = layout.row()
row.label(text="")
row.prop(props, "radiance_resolution_y", text="Y")
row = layout.row()
row.prop(props, "radiance_quality")
row = layout.row()
row.prop(props, "radiance_detail")
row = layout.row()
row.prop(props, "radiance_variability")
@@ -92,7 +90,7 @@ class BIM_PT_radiance_exporter(bpy.types.Panel):
row = layout.row()
row.prop(props, "output_file_name")
row = layout.row()
row.prop(props, "output_file_format")
layout.separator()
@@ -104,12 +102,14 @@ class BIM_PT_radiance_exporter(bpy.types.Panel):
row.operator("render_scene.radiance", text="Radiance Render")
row.enabled = not props.is_exporting
class BIM_PT_solar(bpy.types.Panel):
"""Creates a Panel in the render properties window"""
bl_label = "Solar Access / Shadow"
bl_idname = "BIM_PT_solar"
bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW'
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_tab_solar_analysis"
bl_options = {"HIDE_HEADER"}
@@ -135,7 +135,7 @@ class BIM_PT_solar(bpy.types.Panel):
sun_props = context.scene.sun_pos_properties
row = self.layout.row()
row.prop(sun_props, "coordinates", icon='URL')
row.prop(sun_props, "coordinates", icon="URL")
row = self.layout.row(align=True)
row.prop(props, "latitude")
row.prop(props, "longitude")
@@ -146,20 +146,24 @@ class BIM_PT_solar(bpy.types.Panel):
row.operator("bim.import_true_north", icon="IMPORT", text="")
row = self.layout.row(align=True)
row.prop(props, "month", text={
1: "January",
2: "February",
3: "March",
4: "April",
5: "May",
6: "June",
7: "July",
8: "August",
9: "September",
10: "October",
11: "November",
12: "December",
}[props.month])
row.prop(
props,
"month",
text={
1: "January",
2: "February",
3: "March",
4: "April",
5: "May",
6: "June",
7: "July",
8: "August",
9: "September",
10: "October",
11: "November",
12: "December",
}[props.month],
)
row.prop(props, "day")
row = self.layout.row(align=True)
@@ -258,7 +258,9 @@ class ObjectMaterialData:
precision = bpy.context.scene.DocProperties.imperial_precision
else:
precision = None
formatted_thickness = format_distance(total_thickness, precision=precision, suppress_zero_inches=True, in_unit_length = True)
formatted_thickness = format_distance(
total_thickness, precision=precision, suppress_zero_inches=True, in_unit_length=True
)
data["name"] = f"{formatted_thickness} - {data['name']}"
if item.is_a("IfcMaterial"):
data["material"] = item.Name or "Unnamed"
@@ -120,6 +120,11 @@ def get_contexts(self, context):
def update_material_name(self: "Material", context: bpy.types.Context) -> None:
# No need to update anything if it's either category
# or ifc_definition_id is not yet set
# (which occurs when Material still in the process of creation).
if self.is_category or not self.ifc_definition_id:
return
ifc_file = tool.Ifc.get()
name = self.name
material = ifc_file.by_id(self.ifc_definition_id)
@@ -223,7 +223,7 @@ class BIM_PT_object_material(Panel):
row = self.layout.row(align=True)
row.label(text="No Profiles Available")
row.operator("bim.add_profile_def", icon="ADD", text="")
else:
else:
row = self.layout.row(align=True)
if ObjectMaterialData.data["set_item_name"] == "profile":
prop_with_search(row, self.mprops, "profiles", icon="ITALIC", text="")
@@ -235,7 +235,10 @@ class BIM_PT_object_material(Panel):
row = self.layout.row(align=True)
row.label(text="----- Exterior -----")
for index, set_item in enumerate(ObjectMaterialData.data["set_items"]):
if len(self.props.material_set_item_profile_attributes) and self.props.active_material_set_item_id == set_item["id"]:
if (
len(self.props.material_set_item_profile_attributes)
and self.props.active_material_set_item_id == set_item["id"]
):
self.draw_editable_set_item_profile_ui(set_item)
elif self.props.active_material_set_item_id == set_item["id"]:
self.draw_editable_set_item_ui(set_item)
@@ -341,7 +344,9 @@ class BIM_PT_object_material(Panel):
precision = bpy.context.scene.DocProperties.imperial_precision
else:
precision = None
formatted_thickness = format_distance(total_thickness, precision=precision, suppress_zero_inches=True, in_unit_length = True)
formatted_thickness = format_distance(
total_thickness, precision=precision, suppress_zero_inches=True, in_unit_length=True
)
row = self.layout.row(align=True)
row.label(text=f"Total Thickness: {formatted_thickness}")
@@ -143,7 +143,6 @@ classes = (
stair.EnableEditingStair,
stair.RemoveStair,
pie.OpenPieClass,
pie.PieUpdateContainer,
pie.PieAddOpening,
pie.VIEW3D_MT_PIE_bim,
pie.VIEW3D_MT_PIE_bim_class,
@@ -84,9 +84,9 @@ class EnableEditingArray(bpy.types.Operator, tool.Ifc.Operator):
obj = context.active_object
element = tool.Ifc.get_entity(obj)
props = obj.BIMArrayProperties
relating_obj = props.relating_array_object
if relating_obj:
element = tool.Ifc.get_entity(relating_obj)
parent_globalid = ifcopenshell.util.element.get_pset(element, "BBIM_Array", "Parent")
@@ -102,9 +102,9 @@ class EnableEditingArray(bpy.types.Operator, tool.Ifc.Operator):
props.use_local_space = data.get("use_local_space", False)
props.sync_children = data.get("sync_children", False)
props.method = data.get("method", "OFFSET")
props.is_editing = self.item
return {"FINISHED"}
@@ -149,9 +149,9 @@ class EditArray(bpy.types.Operator, tool.Ifc.Operator):
tool.Blender.Modifier.Array.set_children_lock_state(element, self.item, True)
tool.Blender.Modifier.Array.constrain_children_to_parent(element)
#clears the relating_array_object so it doesn't show again next time
# clears the relating_array_object so it doesn't show again next time
props.relating_array_object = None
return {"FINISHED"}
@@ -285,8 +285,11 @@ class SelectAllArrayObjects(bpy.types.Operator):
array_objects = tool.Blender.Modifier.Array.get_all_objects(parent_element)
tool.Blender.set_objects_selection(
context, active_object=array_objects[0], selected_objects=array_objects, clear_previous_selection=False
)
context,
active_object=array_objects[0],
selected_objects=array_objects,
clear_previous_selection=False,
)
return {"FINISHED"}
@@ -19,6 +19,7 @@
import bpy
import ifcopenshell
import ifcopenshell.util.element
import blenderbim.tool as tool
import blenderbim.core.covering as core
@@ -31,9 +32,7 @@ class AddInstanceFlooringCoveringFromCursor(bpy.types.Operator, tool.Ifc.Operato
@classmethod
def poll(cls, context):
relating_type_id = int(bpy.data.scenes["Scene"].BIMModelProperties.relating_type_id)
relating_type = ifcopenshell.util.element.get_predefined_type(tool.Ifc.get().by_id(relating_type_id))
return relating_type == "FLOORING"
return tool.Covering.covering_poll_relating_type_check(cls, context, "FLOORING")
def _execute(self, context):
try:
@@ -50,9 +49,7 @@ class AddInstanceCeilingCoveringFromCursor(bpy.types.Operator, tool.Ifc.Operator
@classmethod
def poll(cls, context):
relating_type_id = int(bpy.data.scenes["Scene"].BIMModelProperties.relating_type_id)
relating_type = ifcopenshell.util.element.get_predefined_type(tool.Ifc.get().by_id(relating_type_id))
return relating_type == "CEILING"
return tool.Covering.covering_poll_relating_type_check(cls, context, "CEILING")
def _execute(self, context):
try:
@@ -69,8 +66,10 @@ class RegenSelectedCoveringObject(bpy.types.Operator, tool.Ifc.Operator):
@classmethod
def poll(cls, context):
element = tool.Ifc.get_entity(bpy.context.active_object)
return element and element.is_a("IfcCovering")
if (obj := context.active_object) and (element := tool.Ifc.get_entity(obj)) and element.is_a("IfcCovering"):
return True
cls.poll_message_set("IfcCovering must be selected.")
return False
def _execute(self, context):
try:
@@ -89,11 +88,7 @@ class AddInstanceFlooringCoveringsFromWalls(bpy.types.Operator, tool.Ifc.Operato
@classmethod
def poll(cls, context):
element = tool.Ifc.get_entity(bpy.context.active_object)
relating_type_id = int(bpy.data.scenes["Scene"].BIMModelProperties.relating_type_id)
relating_type = ifcopenshell.util.element.get_predefined_type(tool.Ifc.get().by_id(relating_type_id))
if element and element.is_a("IfcWall") and tool.Model.get_usage_type(element) == "LAYER2":
return context.selected_objects and relating_type == "FLOORING"
return tool.Covering.covering_poll_wall_selected(cls, context, "FLOORING")
def _execute(self, context):
# This only works based on a 2D plan only considering the standard
@@ -117,11 +112,7 @@ class AddInstanceCeilingCoveringsFromWalls(bpy.types.Operator, tool.Ifc.Operator
@classmethod
def poll(cls, context):
element = tool.Ifc.get_entity(bpy.context.active_object)
relating_type_id = int(bpy.data.scenes["Scene"].BIMModelProperties.relating_type_id)
relating_type = ifcopenshell.util.element.get_predefined_type(tool.Ifc.get().by_id(relating_type_id))
if element and element.is_a("IfcWall") and tool.Model.get_usage_type(element) == "LAYER2":
return context.selected_objects and relating_type == "CEILING"
return tool.Covering.covering_poll_wall_selected(cls, context, "CEILING")
def _execute(self, context):
# This only works based on a 2D plan only considering the standard
@@ -50,10 +50,11 @@ class AuthoringData:
cls.props = bpy.context.scene.BIMModelProperties
if ifc_element_type:
cls.ifc_element_type = None if ifc_element_type == "all" else ifc_element_type
cls.data["default_container"] = cls.default_container()
cls.data["ifc_element_type"] = cls.ifc_element_type
cls.data["ifc_classes"] = cls.ifc_classes()
cls.data["relating_type_id"] = cls.relating_type_id() # only after .ifc_classes()
cls.data["predefined_type"] = cls.predefined_type() # only after .relating_type_id()
cls.data["predefined_type"] = cls.predefined_type() # only after .relating_type_id()
cls.data["type_class"] = cls.type_class()
# only after .type_class()
@@ -74,6 +75,15 @@ class AuthoringData:
cls.data["boundary_class"] = cls.boundary_class()
cls.data["selected_material_usages"] = cls.selected_material_usages()
@classmethod
def default_container(cls) -> str | None:
props = bpy.context.scene.BIMSpatialDecompositionProperties
if props.default_container:
try:
return tool.Ifc.get().by_id(props.default_container).Name
except:
pass
@classmethod
def boundary_class(cls):
declaration = tool.Ifc.schema().declaration_by_name("IfcRelSpaceBoundary")
@@ -541,7 +541,9 @@ class MEPGenerator:
ifc_representation_class=None,
)
rel = ifcopenshell.api.run("material.assign_material", ifc_file, products=[element], type="IfcMaterialProfileSet")
rel = ifcopenshell.api.run(
"material.assign_material", ifc_file, products=[element], type="IfcMaterialProfileSet"
)
profile_set = rel.RelatingMaterial
material_profile = ifcopenshell.api.run(
"material.add_profile", ifc_file, profile_set=profile_set, material=material
@@ -63,28 +63,6 @@ class PieAddOpening(bpy.types.Operator):
return {"FINISHED"}
class PieUpdateContainer(bpy.types.Operator):
bl_idname = "bim.pie_update_container"
bl_label = "Update Spatial Container"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
for obj in context.selected_objects:
if not obj.BIMObjectProperties.ifc_definition_id:
continue
for collection in obj.users_collection:
spatial_obj = collection.BIMCollectionProperties.obj
if spatial_obj and spatial_obj.BIMObjectProperties.ifc_definition_id:
blenderbim.core.spatial.assign_container(
tool.Ifc, tool.Collector, tool.Spatial, structure_obj=spatial_obj, element_obj=obj
)
break
return {"FINISHED"}
class VIEW3D_MT_PIE_bim(bpy.types.Menu):
bl_label = "Geometry"
@@ -93,7 +71,6 @@ class VIEW3D_MT_PIE_bim(bpy.types.Menu):
pie.operator("bim.edit_object_placement")
pie.operator("bim.update_representation").ifc_representation_class = ""
pie.operator("bim.pie_add_opening")
pie.operator("bim.pie_update_container")
pie.operator("bim.open_pie_class", text="Assign IFC Class")
pie.operator("bim.aggregate_assign_object", text="Assign Aggregation")
pie.operator("bim.aggregate_unassign_object", text="Unassign Aggregation")

Some files were not shown because too many files have changed in this diff Show More