mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-28 07:49:59 +00:00
Compare commits
31 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 893fd50b85 | |||
| 7a3e53a9f8 | |||
| 07ee62eb91 | |||
| b41878e960 | |||
| d9f1cb5525 | |||
| 8110951987 | |||
| 6b63b5aae7 | |||
| e814736e68 | |||
| 802e3a0ea1 | |||
| 4e8a962f60 | |||
| e20d4c93b7 | |||
| 1be76c8aaa | |||
| a4566797fe | |||
| 4ee3458ff9 | |||
| f86de533f8 | |||
| 693831dca3 | |||
| 7500919b5d | |||
| e37b6fa8a2 | |||
| 9de9645229 | |||
| 373d664251 | |||
| 6ab912a049 | |||
| 9b9c900340 | |||
| a1ac820050 | |||
| e812a886dc | |||
| b759e22875 | |||
| 93e0babe5a | |||
| 7d021fb7a4 | |||
| b2cb8ffd8f | |||
| 25a5651e50 | |||
| b301eb6520 | |||
| 9f90d37401 |
@@ -1,10 +1,17 @@
|
||||
name: ci-ifcopenshell-conda-daily
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: ["ci"]
|
||||
types:
|
||||
- completed
|
||||
push:
|
||||
paths:
|
||||
- .github/workflows/ci-ifcopenshell-conda-daily.yml
|
||||
schedule:
|
||||
# ┌───────────── minute (0 - 59)
|
||||
# │ ┌───────────── hour (0 - 23)
|
||||
# │ │ ┌───────────── day of the month (1 - 31)
|
||||
# │ │ │ ┌───────────── month (1 - 12 or JAN-DEC)
|
||||
# │ │ │ │ ┌───────────── day of the week (0 - 6 or SUN-SAT)
|
||||
# * * * * *
|
||||
- cron: "49 23 * * *" # 11min before utc midnight every day
|
||||
|
||||
jobs:
|
||||
activate:
|
||||
@@ -26,20 +33,16 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
pyver: [
|
||||
# { name: py39, distver: '3.9' },
|
||||
{ name: py310, distver: '3.10'},
|
||||
{ name: py311, distver: '3.11'}
|
||||
]
|
||||
platform: [
|
||||
{ name: Windows, distver: windows-2022, upload: 'false' },
|
||||
{ name: Windows, distver: windows-2019, upload: 'true' },
|
||||
{ name: Linux, distver: ubuntu-20.04, upload: 'true' },
|
||||
{ name: Linux, distver: ubuntu-22.04, upload: 'false' },
|
||||
{ name: macOS, distver: macos-11, upload: 'true' },
|
||||
{ name: macOS, distver: macos-12, upload: 'false' }
|
||||
{ name: Windows, distver: windows-2022, upload: 'true' },
|
||||
{ name: Linux, distver: ubuntu-22.04, upload: 'true' },
|
||||
{ name: macOS, distver: macos-12, upload: 'true' }
|
||||
]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Download MacOSX SDK
|
||||
@@ -58,8 +61,10 @@ jobs:
|
||||
- name: build, test and upload ifcopenshell
|
||||
if: ${{ matrix.platform.upload == 'true' }}
|
||||
run: |
|
||||
conda-build . --python ${{ matrix.pyver.distver }} -c conda-forge --token ${{ secrets.ANACONDA_TOKEN }} --user ifcopenshell --no-remove-work-dir
|
||||
conda-build . --python ${{ matrix.pyver.distver }} -c conda-forge --token ${{ secrets.ANACONDA_TOKEN }} --user ifcopenshell
|
||||
working-directory: ./conda
|
||||
- name: build & test ifcopenshell
|
||||
if: ${{ matrix.platform.upload == 'false' }}
|
||||
run: |
|
||||
conda-build . --python ${{ matrix.pyver.distver }} -c conda-forge --no-remove-work-dir
|
||||
conda-build . --python ${{ matrix.pyver.distver }} -c conda-forge
|
||||
working-directory: ./conda
|
||||
|
||||
-81
@@ -1,81 +0,0 @@
|
||||
language: cpp
|
||||
compiler: gcc
|
||||
cache: ccache
|
||||
os: linux
|
||||
dist: focal
|
||||
addons:
|
||||
apt:
|
||||
update: true
|
||||
packages:
|
||||
- libboost-date-time-dev
|
||||
- libboost-filesystem-dev
|
||||
- libboost-iostreams-dev
|
||||
- libboost-program-options-dev
|
||||
- libboost-regex-dev
|
||||
- libboost-system-dev
|
||||
- libboost-thread-dev
|
||||
- libocct-data-exchange-dev
|
||||
- libocct-foundation-dev
|
||||
- libocct-modeling-algorithms-dev
|
||||
- libocct-modeling-data-dev
|
||||
- libxml2-dev
|
||||
- nlohmann-json3-dev
|
||||
- opencollada-dev
|
||||
- python3-all-dev
|
||||
- python3-pip
|
||||
- swig
|
||||
- libhdf5-dev
|
||||
|
||||
before_script:
|
||||
- if [ $TRAVIS_OS_NAME == "linux" ]; then ccache -z; fi
|
||||
|
||||
install:
|
||||
# for IDS
|
||||
- python3 -m pip install xmlschema
|
||||
|
||||
script:
|
||||
- pwd
|
||||
- mkdir build && cd build
|
||||
- |
|
||||
cmake \
|
||||
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
|
||||
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
|
||||
-DOCC_INCLUDE_DIR=/usr/include/opencascade \
|
||||
-DOCC_LIBRARY_DIR=/usr/lib/x86_64-linux-gnu \
|
||||
-DPYTHON_EXECUTABLE:FILEPATH=/usr/bin/python3 \
|
||||
-DPYTHON_INCLUDE_DIR:PATH=/usr/include/python3.8 \
|
||||
-DPYTHON_LIBRARY:FILEPATH=/usr/lib/x86_64-linux-gnu/libpython3.8.so \
|
||||
"-DSCHEMA_VERSIONS=2x3;4" \
|
||||
-DGLTF_SUPPORT=On \
|
||||
-DJSON_INCLUDE_DIR=/usr/include \
|
||||
-DHDF5_INCLUDE_DIR=/usr/include/hdf5/serial \
|
||||
../cmake
|
||||
|
||||
# TODO: Drop sudo
|
||||
- sudo make -j $(nproc) install
|
||||
|
||||
- cd ../test
|
||||
- /usr/bin/python tests.py
|
||||
- /usr/bin/python ../src/ifcopenshell-python/ifcopenshell/test_ids.py
|
||||
|
||||
- cd input
|
||||
|
||||
- /usr/local/bin/IfcConvert -yv acad2010_walls.ifc acad2010_walls.glb
|
||||
|
||||
- /usr/bin/python -c "from io import open; import ifcopenshell; f = ifcopenshell.open('encoding.ifc'); assert list(map(ord, f[1][0])) == [39, 97, 39, 32, 49, 109, 179, 32, 8804, 32, 53, 109, 179, 32, 8805, 32, 49, 48, 109, 179]"
|
||||
|
||||
- |
|
||||
(for i in *.ifc; do \
|
||||
echo $i | tee -a log; \
|
||||
timeout 1m /usr/local/bin/IfcConvert -yv "$i" "$i.dae" --validate >> log 2>&1; \
|
||||
echo $i $? >> statuses; \
|
||||
done) || true
|
||||
- echo Failed
|
||||
- grep -v 0$ statuses
|
||||
- grep -v 0$ statuses | wc -l
|
||||
- echo Succeeded
|
||||
- grep 0$ statuses
|
||||
- grep 0$ statuses | wc -l
|
||||
|
||||
after_script:
|
||||
- if [ $TRAVIS_OS_NAME == "linux" ]; then ccache -s; fi
|
||||
+1
-1
@@ -41,4 +41,4 @@ cmake -G Ninja \
|
||||
|
||||
ninja
|
||||
|
||||
ninja install
|
||||
ninja install -j 2
|
||||
|
||||
@@ -3,5 +3,5 @@ mypy
|
||||
pylint
|
||||
isort
|
||||
xsdata
|
||||
tox
|
||||
tox==3.27.1
|
||||
tox-conda
|
||||
|
||||
+32
-12
@@ -1,8 +1,10 @@
|
||||
from typing import Optional
|
||||
|
||||
import numpy as np
|
||||
from numpy.typing import NDArray
|
||||
|
||||
|
||||
def calc_camera_vectors(
|
||||
def camera_vectors_from_element_placement(
|
||||
elem_placement: NDArray[np.float_],
|
||||
) -> tuple[NDArray[np.float_], NDArray[np.float_], NDArray[np.float_]]:
|
||||
"""
|
||||
@@ -15,19 +17,37 @@ def calc_camera_vectors(
|
||||
Camera position, direction and up vectors
|
||||
"""
|
||||
target_position = elem_placement[:3, 3]
|
||||
camera_position = target_position + np.array((5, 5, 5))
|
||||
camera_direction = unit_vector(camera_position - target_position)
|
||||
return camera_vectors_from_target_position(target_position)
|
||||
|
||||
|
||||
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_]]:
|
||||
"""
|
||||
Calculate the vectors of a camera pointing to a target point.
|
||||
|
||||
Args:
|
||||
target_position: point the camera is pointing to.
|
||||
camera_offset: offset of the camera from the target point.
|
||||
|
||||
Returns:
|
||||
Camera position, direction and up vectors
|
||||
"""
|
||||
camera_offset = np.array((5, 5, 5)) if offset is None else offset
|
||||
camera_position = target_position + camera_offset
|
||||
camera_direction = unit_vector(-camera_offset) # pylint: disable=invalid-unary-operand-type
|
||||
camera_right = unit_vector(np.cross(np.array([0.0, 0.0, 1.0]), camera_direction))
|
||||
camera_up = unit_vector(np.cross(camera_direction, camera_right))
|
||||
rotation_transform = np.eye(4)
|
||||
rotation_transform[0, :3] = camera_right
|
||||
rotation_transform[1, :3] = camera_up
|
||||
rotation_transform[2, :3] = camera_direction
|
||||
translation_transform = np.eye(4)
|
||||
translation_transform[:3, -1] = -camera_position
|
||||
look_at_transform = np.matmul(rotation_transform, translation_transform)
|
||||
mat = np.linalg.inv(look_at_transform)
|
||||
return camera_position, -mat[:3, 2], mat[:3, 1]
|
||||
return camera_position, camera_direction, camera_up
|
||||
# rotation_transform = np.eye(4)
|
||||
# rotation_transform[0, :3] = camera_right
|
||||
# rotation_transform[1, :3] = camera_up
|
||||
# rotation_transform[2, :3] = camera_direction
|
||||
# translation_transform = np.eye(4)
|
||||
# translation_transform[:3, -1] = -camera_position
|
||||
# look_at_transform = np.matmul(rotation_transform, translation_transform)
|
||||
# mat = np.linalg.inv(look_at_transform)
|
||||
# return camera_position, -mat[:3, 2], mat[:3, 1]
|
||||
|
||||
|
||||
def unit_vector(v: NDArray[np.float_]) -> NDArray[np.float_]:
|
||||
|
||||
@@ -3,7 +3,7 @@ import uuid
|
||||
import warnings
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
from typing import Any, Optional, TypeVar
|
||||
from typing import Any, NoReturn, Optional, TypeVar
|
||||
|
||||
import bcf.v2.model as mdl
|
||||
from bcf.inmemory_zipfile import InMemoryZipFile, ZipFileInterface
|
||||
@@ -50,7 +50,7 @@ class BcfXml:
|
||||
self._version = (
|
||||
self._xml_handler.parse(self._zip_file.read("bcf.version"), mdl.Version)
|
||||
if self._zip_file
|
||||
else mdl.Version(version_id="3.0")
|
||||
else mdl.Version(version_id="2.1")
|
||||
)
|
||||
return self._version
|
||||
|
||||
@@ -210,10 +210,12 @@ class BcfXml:
|
||||
self.topics[topic_handler.guid] = topic_handler
|
||||
return topic_handler
|
||||
|
||||
def __eq__(self, other: object) -> bool:
|
||||
if not isinstance(other, BcfXml):
|
||||
raise TypeError("Equality needs a BcfXml object.")
|
||||
return self.version == other.version and self.project_info == other.project_info
|
||||
def __eq__(self, other: object) -> bool | NoReturn:
|
||||
return (
|
||||
self.version == other.version and self.project_info == other.project_info
|
||||
if isinstance(other, BcfXml)
|
||||
else NotImplemented
|
||||
)
|
||||
|
||||
# region Deprecated methods
|
||||
def new_project(self) -> "BcfXml":
|
||||
|
||||
@@ -3,9 +3,11 @@ import datetime
|
||||
import uuid
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
from typing import Any, Optional
|
||||
from typing import Any, NoReturn, Optional
|
||||
|
||||
import numpy as np
|
||||
from ifcopenshell import entity_instance
|
||||
from numpy.typing import NDArray
|
||||
from xsdata.models.datatype import XmlDateTime
|
||||
|
||||
import bcf.v2.model as mdl
|
||||
@@ -226,15 +228,29 @@ 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:
|
||||
"""
|
||||
Add a viewpoint tergeting an IFC element to the topic.
|
||||
|
||||
Args:
|
||||
element: The IFC element.
|
||||
"""
|
||||
vi_handler = VisualizationInfoHandler.create_from_point_and_guids(
|
||||
position, *guids, xml_handler=self._xml_handler
|
||||
)
|
||||
self.add_visinfo_handler(vi_handler)
|
||||
|
||||
def add_visinfo_handler(self, new_viewpoint: VisualizationInfoHandler) -> None:
|
||||
self.viewpoints[new_viewpoint.guid] = new_viewpoint
|
||||
self.markup.viewpoints.append(mdl.ViewPoint(viewpoint=new_viewpoint.guid, guid=new_viewpoint.guid))
|
||||
|
||||
def __eq__(self, other: object) -> bool:
|
||||
if not isinstance(other, TopicHandler):
|
||||
raise TypeError("Equality needs a BcfXml object.")
|
||||
def __eq__(self, other: object) -> bool | NoReturn:
|
||||
return (
|
||||
self.markup == other.markup
|
||||
and self.viewpoints == other.viewpoints
|
||||
and self.bim_snippet == other.bim_snippet
|
||||
(
|
||||
self.markup == other.markup
|
||||
and self.viewpoints == other.viewpoints
|
||||
and self.bim_snippet == other.bim_snippet
|
||||
)
|
||||
if isinstance(other, TopicHandler)
|
||||
else NotImplemented
|
||||
)
|
||||
|
||||
@@ -9,7 +9,10 @@ from ifcopenshell.util import placement
|
||||
from numpy.typing import NDArray
|
||||
|
||||
import bcf.v2.model as mdl
|
||||
from bcf.geometry import calc_camera_vectors
|
||||
from bcf.geometry import (
|
||||
camera_vectors_from_element_placement,
|
||||
camera_vectors_from_target_position,
|
||||
)
|
||||
from bcf.inmemory_zipfile import ZipFileInterface
|
||||
from bcf.xml_parser import AbstractXmlParserSerializer, XmlParserSerializer
|
||||
|
||||
@@ -169,6 +172,29 @@ class VisualizationInfoHandler:
|
||||
xml_handler = xml_handler or XmlParserSerializer()
|
||||
return cls(visualization_info=build_viewpoint(element), xml_handler=xml_handler)
|
||||
|
||||
@classmethod
|
||||
def create_from_point_and_guids(
|
||||
cls,
|
||||
position: NDArray[np.float_],
|
||||
*guids: str,
|
||||
xml_handler: Optional[AbstractXmlParserSerializer] = None,
|
||||
) -> "VisualizationInfoHandler":
|
||||
"""
|
||||
Create a new VisualizationInfoHandler object from an IFC element.
|
||||
|
||||
Args:
|
||||
position: target point coordinates.
|
||||
*guids: One or more IFC element GUID.
|
||||
xml_handler: The XML handler to use.
|
||||
|
||||
Returns:
|
||||
The VisualizationInfoHandler object.
|
||||
"""
|
||||
xml_handler = xml_handler or XmlParserSerializer()
|
||||
return cls(
|
||||
visualization_info=build_viewpoint_from_position_and_guids(position, *guids), xml_handler=xml_handler
|
||||
)
|
||||
|
||||
|
||||
@lru_cache(maxsize=None)
|
||||
def build_viewpoint(element: entity_instance) -> mdl.VisualizationInfo:
|
||||
@@ -192,18 +218,39 @@ def build_viewpoint(element: entity_instance) -> mdl.VisualizationInfo:
|
||||
)
|
||||
|
||||
|
||||
def build_components(guid: str) -> mdl.Components:
|
||||
def build_viewpoint_from_position_and_guids(position: NDArray[np.float_], *guids: str) -> mdl.VisualizationInfo:
|
||||
"""
|
||||
Return a BCF viewpoint of an IFC element.
|
||||
|
||||
This function is cached to speedudp the creation of multiple BCF topics regarding the same element.
|
||||
|
||||
Args:
|
||||
position: target point coordinates.
|
||||
*guids: One or more IFC element GUID.
|
||||
|
||||
Returns:
|
||||
The BCF viewpoint definition.
|
||||
"""
|
||||
return mdl.VisualizationInfo(
|
||||
guid=str(uuid.uuid4()),
|
||||
components=build_components(*guids),
|
||||
perspective_camera=build_camera_from_vectors(*camera_vectors_from_target_position(position)),
|
||||
)
|
||||
|
||||
|
||||
def build_components(*guids: str) -> mdl.Components:
|
||||
"""
|
||||
Return the BCF components from an IFC element GUID.
|
||||
|
||||
Args:
|
||||
guid: The IFC element GUID.
|
||||
*guids: One or more IFC element GUID.
|
||||
|
||||
Returns:
|
||||
The BCF components definition.
|
||||
"""
|
||||
components = [mdl.Component(ifc_guid=guid) for guid in guids]
|
||||
return mdl.Components(
|
||||
selection=mdl.ComponentSelection(component=[mdl.Component(ifc_guid=guid)]),
|
||||
selection=mdl.ComponentSelection(component=components),
|
||||
visibility=mdl.ComponentVisibility(default_visibility=True),
|
||||
)
|
||||
|
||||
@@ -218,7 +265,7 @@ def build_camera(elem_placement: NDArray[np.float_]) -> mdl.PerspectiveCamera:
|
||||
Returns:
|
||||
The BCF camera definition.
|
||||
"""
|
||||
return build_camera_from_vectors(*calc_camera_vectors(elem_placement))
|
||||
return build_camera_from_vectors(*camera_vectors_from_element_placement(elem_placement))
|
||||
|
||||
|
||||
def build_camera_from_vectors(
|
||||
|
||||
@@ -3,7 +3,7 @@ import uuid
|
||||
import warnings
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
from typing import Any, Optional, TypeVar
|
||||
from typing import Any, NoReturn, Optional, TypeVar
|
||||
|
||||
import bcf.v3.model as mdl
|
||||
from bcf.inmemory_zipfile import InMemoryZipFile, ZipFileInterface
|
||||
@@ -224,13 +224,13 @@ class BcfXml:
|
||||
self.topics[topic_handler.guid] = topic_handler
|
||||
return topic_handler
|
||||
|
||||
def __eq__(self, other: object) -> bool:
|
||||
if not isinstance(other, BcfXml):
|
||||
raise TypeError("Equality needs a BcfXml object.")
|
||||
def __eq__(self, other: object) -> bool | NoReturn:
|
||||
return (
|
||||
self.version == other.version
|
||||
and self.project_info == other.project_info
|
||||
and self.extensions == other.extensions
|
||||
if isinstance(other, BcfXml)
|
||||
else NotImplemented
|
||||
)
|
||||
|
||||
# region Deprecated methods
|
||||
|
||||
@@ -3,9 +3,11 @@ import datetime
|
||||
import uuid
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
from typing import Any, Optional
|
||||
from typing import Any, NoReturn, Optional
|
||||
|
||||
import numpy as np
|
||||
from ifcopenshell import entity_instance
|
||||
from numpy.typing import NDArray
|
||||
from xsdata.models.datatype import XmlDateTime
|
||||
|
||||
import bcf.v3.model as mdl
|
||||
@@ -73,7 +75,7 @@ class TopicHandler:
|
||||
self._bim_snippet = value
|
||||
|
||||
@property
|
||||
def viewpoints(self) -> Optional[VisualizationInfoHandler]:
|
||||
def viewpoints(self) -> dict[str, "VisualizationInfoHandler"]:
|
||||
if (
|
||||
not self._viewpoints
|
||||
and self._topic_dir
|
||||
@@ -143,7 +145,7 @@ class TopicHandler:
|
||||
self._save_viewpoints(destination_zip, topic_dir)
|
||||
self._save_bim_snippet(destination_zip)
|
||||
|
||||
def _save_viewpoints(self, destination_zip, topic_dir) -> None:
|
||||
def _save_viewpoints(self, destination_zip: ZipFileInterface, topic_dir: str) -> None:
|
||||
if not self.topic.viewpoints or not (viewpoints := self.topic.viewpoints.view_point):
|
||||
return
|
||||
for vpt in viewpoints:
|
||||
@@ -172,17 +174,31 @@ 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:
|
||||
"""
|
||||
Add a viewpoint tergeting an IFC element to the topic.
|
||||
|
||||
Args:
|
||||
element: The IFC element.
|
||||
"""
|
||||
vi_handler = VisualizationInfoHandler.create_from_point_and_guids(
|
||||
position, *guids, xml_handler=self._xml_handler
|
||||
)
|
||||
self.add_visinfo_handler(vi_handler)
|
||||
|
||||
def add_visinfo_handler(self, new_viewpoint: VisualizationInfoHandler) -> None:
|
||||
self.viewpoints[new_viewpoint.guid] = new_viewpoint
|
||||
if self.topic.viewpoints is None:
|
||||
self.topic.viewpoints = mdl.TopicViewpoints()
|
||||
self.topic.viewpoints.view_point.append(mdl.ViewPoint(viewpoint=new_viewpoint.guid, guid=new_viewpoint.guid))
|
||||
|
||||
def __eq__(self, other: object) -> bool:
|
||||
if not isinstance(other, TopicHandler):
|
||||
raise TypeError("Equality needs a BcfXml object.")
|
||||
def __eq__(self, other: object) -> bool | NoReturn:
|
||||
return (
|
||||
self.markup == other.markup
|
||||
and self.viewpoints == other.viewpoints
|
||||
and self.bim_snippet == other.bim_snippet
|
||||
(
|
||||
self.markup == other.markup
|
||||
and self.viewpoints == other.viewpoints
|
||||
and self.bim_snippet == other.bim_snippet
|
||||
)
|
||||
if isinstance(other, TopicHandler)
|
||||
else NotImplemented
|
||||
)
|
||||
|
||||
@@ -9,7 +9,10 @@ from ifcopenshell.util import placement
|
||||
from numpy.typing import NDArray
|
||||
|
||||
import bcf.v3.model as mdl
|
||||
from bcf.geometry import calc_camera_vectors
|
||||
from bcf.geometry import (
|
||||
camera_vectors_from_element_placement,
|
||||
camera_vectors_from_target_position,
|
||||
)
|
||||
from bcf.inmemory_zipfile import ZipFileInterface
|
||||
from bcf.xml_parser import AbstractXmlParserSerializer, XmlParserSerializer
|
||||
|
||||
@@ -169,6 +172,29 @@ class VisualizationInfoHandler:
|
||||
xml_handler = xml_handler or XmlParserSerializer()
|
||||
return cls(visualization_info=build_viewpoint(element), xml_handler=xml_handler)
|
||||
|
||||
@classmethod
|
||||
def create_from_point_and_guids(
|
||||
cls,
|
||||
position: NDArray[np.float_],
|
||||
*guids: str,
|
||||
xml_handler: Optional[AbstractXmlParserSerializer] = None,
|
||||
) -> "VisualizationInfoHandler":
|
||||
"""
|
||||
Create a new VisualizationInfoHandler object from an IFC element.
|
||||
|
||||
Args:
|
||||
position: target point coordinates.
|
||||
*guids: One or more IFC element GUID.
|
||||
xml_handler: The XML handler to use.
|
||||
|
||||
Returns:
|
||||
The VisualizationInfoHandler object.
|
||||
"""
|
||||
xml_handler = xml_handler or XmlParserSerializer()
|
||||
return cls(
|
||||
visualization_info=build_viewpoint_from_position_and_guids(position, *guids), xml_handler=xml_handler
|
||||
)
|
||||
|
||||
|
||||
@lru_cache(maxsize=None)
|
||||
def build_viewpoint(element: entity_instance) -> mdl.VisualizationInfo:
|
||||
@@ -192,18 +218,39 @@ def build_viewpoint(element: entity_instance) -> mdl.VisualizationInfo:
|
||||
)
|
||||
|
||||
|
||||
def build_components(guid: str) -> mdl.Components:
|
||||
def build_viewpoint_from_position_and_guids(position: NDArray[np.float_], *guids: str) -> mdl.VisualizationInfo:
|
||||
"""
|
||||
Return a BCF viewpoint of an IFC element.
|
||||
|
||||
This function is cached to speedudp the creation of multiple BCF topics regarding the same element.
|
||||
|
||||
Args:
|
||||
position: target point coordinates.
|
||||
*guids: One or more IFC element GUID.
|
||||
|
||||
Returns:
|
||||
The BCF viewpoint definition.
|
||||
"""
|
||||
return mdl.VisualizationInfo(
|
||||
guid=str(uuid.uuid4()),
|
||||
components=build_components(*guids),
|
||||
perspective_camera=build_camera_from_vectors(*camera_vectors_from_target_position(position)),
|
||||
)
|
||||
|
||||
|
||||
def build_components(*guids: str) -> mdl.Components:
|
||||
"""
|
||||
Return the BCF components from an IFC element GUID.
|
||||
|
||||
Args:
|
||||
guid: The IFC element GUID.
|
||||
*guids: One or more IFC element GUID.
|
||||
|
||||
Returns:
|
||||
The BCF components definition.
|
||||
"""
|
||||
components = [mdl.Component(ifc_guid=guid) for guid in guids]
|
||||
return mdl.Components(
|
||||
selection=mdl.ComponentSelection(component=[mdl.Component(ifc_guid=guid)]),
|
||||
selection=mdl.ComponentSelection(component=components),
|
||||
visibility=mdl.ComponentVisibility(default_visibility=True),
|
||||
)
|
||||
|
||||
@@ -218,7 +265,7 @@ def build_camera(elem_placement: NDArray[np.float_]) -> mdl.PerspectiveCamera:
|
||||
Returns:
|
||||
The BCF camera definition.
|
||||
"""
|
||||
return build_camera_from_vectors(*calc_camera_vectors(elem_placement))
|
||||
return build_camera_from_vectors(*camera_vectors_from_element_placement(elem_placement))
|
||||
|
||||
|
||||
def build_camera_from_vectors(
|
||||
|
||||
@@ -47,12 +47,16 @@ def test_bcf_edit_saveas(xml_handler, build_sample) -> None:
|
||||
modified_path = Path(tmp_dir) / "edited.bcf"
|
||||
parsed.save(modified_path)
|
||||
with BcfXml.load(modified_path, xml_handler=xml_handler) as modified_parsed:
|
||||
assert modified_parsed == bcf
|
||||
parsed_th = modified_parsed.topics[orig_th.guid]
|
||||
assert parsed_th.markup != orig_th.markup
|
||||
assert parsed_th.markup == parsed.topics[orig_th.guid].markup
|
||||
assert parsed_th.viewpoints == orig_th.viewpoints
|
||||
assert parsed_th.bim_snippet == orig_th.bim_snippet
|
||||
_assert_modified_parsed(modified_parsed, bcf, orig_th, parsed)
|
||||
|
||||
|
||||
def _assert_modified_parsed(modified_parsed, bcf, orig_th, parsed):
|
||||
assert modified_parsed == bcf
|
||||
parsed_th = modified_parsed.topics[orig_th.guid]
|
||||
assert parsed_th.markup != orig_th.markup
|
||||
assert parsed_th.markup == parsed.topics[orig_th.guid].markup
|
||||
assert parsed_th.viewpoints == orig_th.viewpoints
|
||||
assert parsed_th.bim_snippet == orig_th.bim_snippet
|
||||
|
||||
|
||||
def test_bcf_edit(xml_handler, build_sample) -> None:
|
||||
@@ -66,13 +70,8 @@ def test_bcf_edit(xml_handler, build_sample) -> None:
|
||||
th.topic.title = "New Topic Title"
|
||||
parsed.save()
|
||||
with BcfXml.load(file_path, xml_handler=xml_handler) as modified_parsed:
|
||||
assert modified_parsed == bcf
|
||||
_assert_modified_parsed(modified_parsed, bcf, orig_th, parsed)
|
||||
assert len(modified_parsed.topics) == 1
|
||||
parsed_th = modified_parsed.topics[orig_th.guid]
|
||||
assert parsed_th.markup != orig_th.markup
|
||||
assert parsed_th.markup == parsed.topics[orig_th.guid].markup
|
||||
assert parsed_th.viewpoints == orig_th.viewpoints
|
||||
assert parsed_th.bim_snippet == orig_th.bim_snippet
|
||||
|
||||
|
||||
def test_save_no_filename(build_sample) -> None:
|
||||
@@ -112,11 +111,16 @@ def test_massive_bcf(xml_handler) -> None:
|
||||
bcf.save(file_path)
|
||||
|
||||
|
||||
def test_equality_with_wrong_object() -> None:
|
||||
with pytest.raises(TypeError):
|
||||
build_sample[0] == "Wrong object"
|
||||
def test_equality_with_wrong_object(build_sample) -> None:
|
||||
assert build_sample[0] != "Wrong object"
|
||||
|
||||
|
||||
def test_topic_equality_with_wrong_object() -> None:
|
||||
with pytest.raises(TypeError):
|
||||
build_sample[1] == "Wrong object"
|
||||
def test_topic_equality_with_wrong_object(build_sample) -> None:
|
||||
assert build_sample[1] != "Wrong object"
|
||||
|
||||
|
||||
def test_bcf_get_set_version(build_sample) -> None:
|
||||
bcf = build_sample[0]
|
||||
assert bcf.version.version_id == "2.1"
|
||||
bcf.version.version_id = "2.0"
|
||||
assert bcf.version.version_id == "2.0"
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
from bcf.v2.bcfxml import BcfXml
|
||||
|
||||
|
||||
def test_create_clash_set_bcf() -> None:
|
||||
bcfxml = BcfXml.create_new("Clash Test")
|
||||
topic = bcfxml.add_topic("Test", "Test topic", "IfcClash")
|
||||
topic.add_viewpoint_from_point_and_guids(
|
||||
np.array([10, 10, 10]),
|
||||
"firstId",
|
||||
"secondId",
|
||||
)
|
||||
assert len(topic.viewpoints) == 1
|
||||
guid, vi_handler = next((k, v) for k, v in topic.viewpoints.items())
|
||||
v_info = vi_handler.visualization_info
|
||||
assert v_info.guid == guid
|
||||
components = v_info.components.selection.component
|
||||
assert {c.ifc_guid for c in components} == {"firstId", "secondId"}
|
||||
camera = v_info.perspective_camera
|
||||
viewpoint = camera.camera_view_point
|
||||
assert viewpoint.x == 15
|
||||
assert viewpoint.y == 15
|
||||
assert viewpoint.z == 15
|
||||
# default direction is the unit vector of -1, -1, -1
|
||||
direction = camera.camera_direction
|
||||
assert direction.x == pytest.approx(-1 / 3**0.5)
|
||||
assert direction.y == pytest.approx(-1 / 3**0.5)
|
||||
assert direction.z == pytest.approx(-1 / 3**0.5)
|
||||
# default
|
||||
up_vector = camera.camera_up_vector
|
||||
assert up_vector.x == pytest.approx(-1 / 6**0.5)
|
||||
assert up_vector.y == pytest.approx(-1 / 6**0.5)
|
||||
assert up_vector.z == pytest.approx(1 / 1.5**0.5)
|
||||
assert camera.field_of_view == 60
|
||||
@@ -48,12 +48,16 @@ def test_bcf_edit_saveas(xml_handler, build_sample) -> None:
|
||||
modified_path = Path(tmp_dir) / "edited.bcf"
|
||||
parsed.save(modified_path)
|
||||
with BcfXml.load(modified_path, xml_handler=xml_handler) as modified_parsed:
|
||||
assert modified_parsed == bcf
|
||||
parsed_th = modified_parsed.topics[orig_th.guid]
|
||||
assert parsed_th.markup != orig_th.markup
|
||||
assert parsed_th.markup == parsed.topics[orig_th.guid].markup
|
||||
assert parsed_th.viewpoints == orig_th.viewpoints
|
||||
assert parsed_th.bim_snippet == orig_th.bim_snippet
|
||||
_assert_modified_parsed(modified_parsed, bcf, orig_th, parsed)
|
||||
|
||||
|
||||
def _assert_modified_parsed(modified_parsed, bcf, orig_th, parsed):
|
||||
assert modified_parsed == bcf
|
||||
parsed_th = modified_parsed.topics[orig_th.guid]
|
||||
assert parsed_th.markup != orig_th.markup
|
||||
assert parsed_th.markup == parsed.topics[orig_th.guid].markup
|
||||
assert parsed_th.viewpoints == orig_th.viewpoints
|
||||
assert parsed_th.bim_snippet == orig_th.bim_snippet
|
||||
|
||||
|
||||
def test_bcf_edit(xml_handler, build_sample) -> None:
|
||||
@@ -67,13 +71,8 @@ def test_bcf_edit(xml_handler, build_sample) -> None:
|
||||
th.topic.title = "New Topic Title"
|
||||
parsed.save()
|
||||
with BcfXml.load(file_path, xml_handler=xml_handler) as modified_parsed:
|
||||
assert modified_parsed == bcf
|
||||
_assert_modified_parsed(modified_parsed, bcf, orig_th, parsed)
|
||||
assert len(modified_parsed.topics) == 1
|
||||
parsed_th = modified_parsed.topics[orig_th.guid]
|
||||
assert parsed_th.markup != orig_th.markup
|
||||
assert parsed_th.markup == parsed.topics[orig_th.guid].markup
|
||||
assert parsed_th.viewpoints == orig_th.viewpoints
|
||||
assert parsed_th.bim_snippet == orig_th.bim_snippet
|
||||
|
||||
|
||||
def test_save_no_filename(build_sample) -> None:
|
||||
@@ -88,7 +87,7 @@ def test_load_no_filename() -> None:
|
||||
|
||||
|
||||
def test_save_keep_open(build_sample) -> None:
|
||||
bcf, orig_th = build_sample
|
||||
bcf, _ = build_sample
|
||||
with TemporaryDirectory() as tmp_dir:
|
||||
file_path = Path(tmp_dir) / "test.bcf"
|
||||
bcf.save(file_path, keep_open=True)
|
||||
@@ -96,11 +95,6 @@ def test_save_keep_open(build_sample) -> None:
|
||||
bcf._zip_file.close()
|
||||
|
||||
|
||||
# image = PIL.Image.new('RGB', size=(100, 100))
|
||||
# file = BinaryIO()
|
||||
# image.save(file)
|
||||
|
||||
|
||||
def test_massive_bcf(xml_handler) -> None:
|
||||
ext = mdl.Extensions(topic_types=mdl.ExtensionsTopicTypes(topic_type=["Test type"]))
|
||||
bcf = BcfXml.create_new("Test project", extensions=ext, xml_handler=xml_handler)
|
||||
@@ -119,11 +113,9 @@ def test_massive_bcf(xml_handler) -> None:
|
||||
bcf.save(file_path)
|
||||
|
||||
|
||||
def test_equality_with_wrong_object() -> None:
|
||||
with pytest.raises(TypeError):
|
||||
build_sample[0] == "Wrong object"
|
||||
def test_equality_with_wrong_object(build_sample) -> None:
|
||||
assert build_sample[0] != "Wrong object"
|
||||
|
||||
|
||||
def test_topic_equality_with_wrong_object() -> None:
|
||||
with pytest.raises(TypeError):
|
||||
build_sample[1] == "Wrong object"
|
||||
def test_topic_equality_with_wrong_object(build_sample) -> None:
|
||||
assert build_sample[1] != "Wrong object"
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
from bcf.v3.bcfxml import BcfXml
|
||||
|
||||
|
||||
def test_create_clash_set_bcf() -> None:
|
||||
bcfxml = BcfXml.create_new("Clash Test")
|
||||
topic = bcfxml.add_topic("Test", "Test topic", "IfcClash")
|
||||
topic.add_viewpoint_from_point_and_guids(
|
||||
np.array([10, 10, 10]),
|
||||
"firstId",
|
||||
"secondId",
|
||||
)
|
||||
assert len(topic.viewpoints) == 1
|
||||
guid, vi_handler = next((k, v) for k, v in topic.viewpoints.items())
|
||||
v_info = vi_handler.visualization_info
|
||||
assert v_info.guid == guid
|
||||
components = v_info.components.selection.component
|
||||
assert {c.ifc_guid for c in components} == {"firstId", "secondId"}
|
||||
camera = v_info.perspective_camera
|
||||
viewpoint = camera.camera_view_point
|
||||
assert viewpoint.x == 15
|
||||
assert viewpoint.y == 15
|
||||
assert viewpoint.z == 15
|
||||
# default direction is the unit vector of -1, -1, -1
|
||||
direction = camera.camera_direction
|
||||
assert direction.x == pytest.approx(-1 / 3**0.5)
|
||||
assert direction.y == pytest.approx(-1 / 3**0.5)
|
||||
assert direction.z == pytest.approx(-1 / 3**0.5)
|
||||
# default
|
||||
up_vector = camera.camera_up_vector
|
||||
assert up_vector.x == pytest.approx(-1 / 6**0.5)
|
||||
assert up_vector.y == pytest.approx(-1 / 6**0.5)
|
||||
assert up_vector.z == pytest.approx(1 / 1.5**0.5)
|
||||
assert camera.field_of_view == 60
|
||||
@@ -23,6 +23,7 @@ import math
|
||||
import zipfile
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.attribute
|
||||
import ifcopenshell.util.element
|
||||
from ifcopenshell.util.doc import get_attribute_doc, get_predefined_type_doc, get_property_doc
|
||||
from mathutils import geometry
|
||||
from mathutils import Vector
|
||||
@@ -207,6 +208,12 @@ def get_obj_ifc_definition_id(context, obj, obj_type):
|
||||
return bpy.data.objects.get(obj).BIMObjectProperties.ifc_definition_id
|
||||
elif obj_type == "Material":
|
||||
return bpy.data.materials.get(obj).BIMObjectProperties.ifc_definition_id
|
||||
elif obj_type == "MaterialSet":
|
||||
return ifcopenshell.util.element.get_material(
|
||||
tool.Ifc.get_entity(bpy.data.objects.get(obj)), should_skip_usage=True
|
||||
).id()
|
||||
elif obj_type == "MaterialSetItem":
|
||||
return bpy.data.objects.get(obj).BIMObjectMaterialProperties.active_material_set_item_id
|
||||
elif obj_type == "Task":
|
||||
return context.scene.BIMTaskTreeProperties.tasks[
|
||||
context.scene.BIMWorkScheduleProperties.active_task_index
|
||||
|
||||
@@ -63,6 +63,7 @@ classes = (
|
||||
operator.ResizeText,
|
||||
operator.SaveDrawingStyle,
|
||||
operator.SelectDocIfcFile,
|
||||
operator.SelectAssignedProduct,
|
||||
prop.Variable,
|
||||
prop.Drawing,
|
||||
prop.Schedule,
|
||||
|
||||
@@ -414,7 +414,7 @@ class BaseDecorator:
|
||||
pos += Vector((-sin, cos)) * gap
|
||||
|
||||
blf.enable(font_id, blf.ROTATION)
|
||||
blf.position(font_id, pos.x, pos.y, 0)
|
||||
blf.position(font_id, pos.x, pos.y, 0) if hasattr(pos, "x") else blf.position(font_id, 0, 0, 0)
|
||||
|
||||
blf.rotation(font_id, ang)
|
||||
blf.color(font_id, *color)
|
||||
|
||||
@@ -1363,3 +1363,11 @@ class ContractSheet(bpy.types.Operator):
|
||||
sheet.is_expanded = False
|
||||
core.load_sheets(tool.Drawing)
|
||||
return {"FINISHED"}
|
||||
|
||||
class SelectAssignedProduct(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.select_assigned_product"
|
||||
bl_label = "Select Assigned Product"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
core.select_assigned_product(tool.Drawing)
|
||||
@@ -314,6 +314,7 @@ class BIM_PT_product_assignments(Panel):
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=ProductAssignmentsData.data["relating_product"] or "No Relating Product", icon="OBJECT_DATA")
|
||||
row.operator("bim.enable_editing_assigned_product", icon="GREASEPENCIL", text="")
|
||||
row.operator("bim.select_assigned_product", icon="RESTRICT_SELECT_OFF", text="")
|
||||
|
||||
|
||||
class BIM_PT_text(Panel):
|
||||
|
||||
@@ -28,7 +28,6 @@ classes = (
|
||||
operator.EnablePsetEditing,
|
||||
operator.GuessQuantity,
|
||||
operator.CalculateQuantity,
|
||||
operator.GuessAllQuantities,
|
||||
operator.RemovePset,
|
||||
operator.TogglePsetExpansion,
|
||||
operator.BIM_OT_add_property_to_edit,
|
||||
@@ -41,6 +40,8 @@ classes = (
|
||||
prop.IfcProperty,
|
||||
prop.PsetProperties,
|
||||
prop.MaterialPsetProperties,
|
||||
prop.MaterialSetPsetProperties,
|
||||
prop.MaterialSetItemPsetProperties,
|
||||
prop.TaskPsetProperties,
|
||||
prop.ResourcePsetProperties,
|
||||
prop.ProfilePsetProperties,
|
||||
@@ -51,6 +52,8 @@ classes = (
|
||||
ui.BIM_PT_object_psets,
|
||||
ui.BIM_PT_object_qtos,
|
||||
ui.BIM_PT_material_psets,
|
||||
ui.BIM_PT_material_set_psets,
|
||||
ui.BIM_PT_material_set_item_psets,
|
||||
ui.BIM_PT_task_qtos,
|
||||
ui.BIM_PT_resource_qtos,
|
||||
ui.BIM_PT_resource_psets,
|
||||
@@ -65,6 +68,8 @@ classes = (
|
||||
|
||||
def register():
|
||||
bpy.types.Object.PsetProperties = bpy.props.PointerProperty(type=prop.PsetProperties)
|
||||
bpy.types.Object.MaterialSetPsetProperties = bpy.props.PointerProperty(type=prop.MaterialSetPsetProperties)
|
||||
bpy.types.Object.MaterialSetItemPsetProperties = bpy.props.PointerProperty(type=prop.MaterialSetItemPsetProperties)
|
||||
bpy.types.Material.PsetProperties = bpy.props.PointerProperty(type=prop.MaterialPsetProperties)
|
||||
bpy.types.Scene.TaskPsetProperties = bpy.props.PointerProperty(type=prop.TaskPsetProperties)
|
||||
bpy.types.Scene.ResourcePsetProperties = bpy.props.PointerProperty(type=prop.ResourcePsetProperties)
|
||||
|
||||
@@ -29,6 +29,8 @@ def refresh():
|
||||
ObjectPsetsData.is_loaded = False
|
||||
ObjectQtosData.is_loaded = False
|
||||
MaterialPsetsData.is_loaded = False
|
||||
MaterialSetPsetsData.is_loaded = False
|
||||
MaterialSetItemPsetsData.is_loaded = False
|
||||
TaskQtosData.is_loaded = False
|
||||
ResourceQtosData.is_loaded = False
|
||||
ResourcePsetsData.is_loaded = False
|
||||
@@ -98,6 +100,36 @@ class MaterialPsetsData(Data):
|
||||
cls.is_loaded = True
|
||||
|
||||
|
||||
class MaterialSetPsetsData(Data):
|
||||
data = {}
|
||||
is_loaded = False
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
psets = {}
|
||||
element = tool.Ifc.get_entity(bpy.context.active_object)
|
||||
if element:
|
||||
material = ifcopenshell.util.element.get_material(element, should_skip_usage=True)
|
||||
if material and "Set" in material.is_a():
|
||||
psets = cls.psetqtos(material)
|
||||
cls.data = {"psets": psets}
|
||||
cls.is_loaded = True
|
||||
|
||||
|
||||
class MaterialSetItemPsetsData(Data):
|
||||
data = {}
|
||||
is_loaded = False
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
psets = {}
|
||||
ifc_definition_id = bpy.context.active_object.BIMObjectMaterialProperties.active_material_set_item_id
|
||||
if ifc_definition_id:
|
||||
psets = cls.psetqtos(tool.Ifc.get().by_id(ifc_definition_id))
|
||||
cls.data = {"psets": psets}
|
||||
cls.is_loaded = True
|
||||
|
||||
|
||||
class TaskQtosData(Data):
|
||||
data = {}
|
||||
is_loaded = False
|
||||
|
||||
@@ -27,6 +27,7 @@ import blenderbim.bim.helper
|
||||
import blenderbim.bim.handler
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.core.pset as core
|
||||
import blenderbim.core.qto as QtoCore
|
||||
import blenderbim.bim.module.pset.data
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from ifcopenshell.api.pset.data import Data
|
||||
@@ -47,6 +48,10 @@ def get_pset_props(context, obj, obj_type):
|
||||
return bpy.data.objects.get(obj).PsetProperties
|
||||
elif obj_type == "Material":
|
||||
return bpy.data.materials.get(obj).PsetProperties
|
||||
elif obj_type == "MaterialSet":
|
||||
return bpy.data.objects.get(obj).MaterialSetPsetProperties
|
||||
elif obj_type == "MaterialSetItem":
|
||||
return bpy.data.objects.get(obj).MaterialSetItemPsetProperties
|
||||
elif obj_type == "Task":
|
||||
return context.scene.TaskPsetProperties
|
||||
elif obj_type == "Resource":
|
||||
@@ -331,6 +336,7 @@ class AddQto(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.add_qto"
|
||||
bl_label = "Add Qto"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Add Quantity Take Off"
|
||||
obj: bpy.props.StringProperty()
|
||||
obj_type: bpy.props.StringProperty()
|
||||
|
||||
@@ -432,33 +438,6 @@ class GuessQuantity(bpy.types.Operator):
|
||||
return unit_settings.length_unit[0 : -len("METERS")], "METRE"
|
||||
|
||||
|
||||
class GuessAllQuantities(bpy.types.Operator):
|
||||
bl_idname = "bim.guess_all_quantities"
|
||||
bl_label = "Guess All Quantities"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
pset_id: bpy.props.IntProperty()
|
||||
obj_name: bpy.props.StringProperty()
|
||||
obj_type: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
self.qto_calculator = QtoCalculator()
|
||||
obj = context.active_object
|
||||
bpy.ops.bim.enable_pset_editing(pset_id=self.pset_id, obj=self.obj_name, obj_type=self.obj_type)
|
||||
for prop in obj.PsetProperties.properties:
|
||||
if (
|
||||
"length" in prop.name.lower()
|
||||
or "area" in prop.name.lower()
|
||||
or "volume" in prop.name.lower()
|
||||
or "width" in prop.name.lower()
|
||||
or "height" in prop.name.lower()
|
||||
or "depth" in prop.name.lower()
|
||||
or "perimeter" in prop.name.lower()
|
||||
):
|
||||
bpy.ops.bim.guess_quantity(prop=prop.name)
|
||||
bpy.ops.bim.edit_pset(obj=self.obj_name, obj_type=self.obj_type)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class CopyPropertyToSelection(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.copy_property_to_selection"
|
||||
bl_label = "Copy Property To Selection"
|
||||
|
||||
@@ -82,6 +82,33 @@ def get_material_pset_names(self, context):
|
||||
return psetnames[ifc_class]
|
||||
|
||||
|
||||
def get_material_set_pset_names(self, context):
|
||||
global psetnames
|
||||
element = tool.Ifc.get_entity(context.active_object)
|
||||
if not element:
|
||||
return []
|
||||
material = ifcopenshell.util.element.get_material(element, should_skip_usage=True)
|
||||
if not material or "Set" not in material.is_a():
|
||||
return []
|
||||
ifc_class = material.is_a()
|
||||
if ifc_class not in psetnames:
|
||||
psets = blenderbim.bim.schema.ifc.psetqto.get_applicable(ifc_class, pset_only=True)
|
||||
psetnames[ifc_class] = blender_formatted_enum_from_psets(psets)
|
||||
return psetnames[ifc_class]
|
||||
|
||||
|
||||
def get_material_set_item_pset_names(self, context):
|
||||
global psetnames
|
||||
ifc_definition_id = context.active_object.BIMObjectMaterialProperties.active_material_set_item_id
|
||||
if not ifc_definition_id:
|
||||
return []
|
||||
ifc_class = tool.Ifc.get().by_id(ifc_definition_id).is_a()
|
||||
if ifc_class not in psetnames:
|
||||
psets = blenderbim.bim.schema.ifc.psetqto.get_applicable(ifc_class, pset_only=True)
|
||||
psetnames[ifc_class] = blender_formatted_enum_from_psets(psets)
|
||||
return psetnames[ifc_class]
|
||||
|
||||
|
||||
def get_task_qto_names(self, context):
|
||||
global qtonames
|
||||
ifc_class = "IfcTask"
|
||||
@@ -182,6 +209,20 @@ class MaterialPsetProperties(PropertyGroup):
|
||||
pset_name: EnumProperty(items=get_material_pset_names, name="Pset Name")
|
||||
|
||||
|
||||
class MaterialSetPsetProperties(PropertyGroup):
|
||||
active_pset_id: IntProperty(name="Active Pset ID")
|
||||
active_pset_name: StringProperty(name="Pset Name")
|
||||
properties: CollectionProperty(name="Properties", type=IfcProperty)
|
||||
pset_name: EnumProperty(items=get_material_set_pset_names, name="Pset Name")
|
||||
|
||||
|
||||
class MaterialSetItemPsetProperties(PropertyGroup):
|
||||
active_pset_id: IntProperty(name="Active Pset ID")
|
||||
active_pset_name: StringProperty(name="Pset Name")
|
||||
properties: CollectionProperty(name="Properties", type=IfcProperty)
|
||||
pset_name: EnumProperty(items=get_material_set_item_pset_names, name="Pset Name")
|
||||
|
||||
|
||||
class TaskPsetProperties(PropertyGroup):
|
||||
active_pset_id: IntProperty(name="Active Pset ID")
|
||||
active_pset_name: StringProperty(name="Pset Name")
|
||||
|
||||
@@ -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 blenderbim.tool as tool
|
||||
from bpy.types import Panel
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.helper import prop_with_search
|
||||
@@ -23,6 +24,8 @@ from blenderbim.bim.module.pset.data import (
|
||||
ObjectPsetsData,
|
||||
ObjectQtosData,
|
||||
MaterialPsetsData,
|
||||
MaterialSetPsetsData,
|
||||
MaterialSetItemPsetsData,
|
||||
TaskQtosData,
|
||||
ResourceQtosData,
|
||||
ResourcePsetsData,
|
||||
@@ -75,6 +78,10 @@ def get_active_pset_obj_name(context, obj_type):
|
||||
return context.active_object.name
|
||||
elif obj_type == "Material":
|
||||
return context.active_object.active_material.name
|
||||
elif obj_type == "MaterialSet":
|
||||
return context.active_object.name
|
||||
elif obj_type == "MaterialSetItem":
|
||||
return context.active_object.name
|
||||
return ""
|
||||
|
||||
|
||||
@@ -88,11 +95,8 @@ def draw_psetqto_ui(context, pset_id, pset, props, layout, obj_type):
|
||||
obj_name = get_active_pset_obj_name(context, obj_type)
|
||||
if not props.active_pset_id:
|
||||
row.label(text=pset["Name"], icon="COPY_ID")
|
||||
if "Qto" in pset["Name"]:
|
||||
op = row.operator("bim.guess_all_quantities", icon="FILE_REFRESH", text="")
|
||||
op.pset_id = pset_id
|
||||
op.obj_name = obj_name
|
||||
op.obj_type = obj_type
|
||||
if "Qto" in pset["Name"] and "Base" in pset["Name"]:
|
||||
op = row.operator("bim.calculate_all_quantities", icon="MOD_EDGESPLIT", text="")
|
||||
op = row.operator("bim.enable_pset_editing", icon="GREASEPENCIL", text="")
|
||||
op.pset_id = pset_id
|
||||
op.obj = obj_name
|
||||
@@ -270,6 +274,75 @@ class BIM_PT_material_psets(Panel):
|
||||
draw_psetqto_ui(context, pset["id"], pset, props, self.layout, "Material")
|
||||
|
||||
|
||||
class BIM_PT_material_set_psets(Panel):
|
||||
bl_label = "IFC Material Set Property Sets"
|
||||
bl_idname = "BIM_PT_material_set_psets"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "object"
|
||||
bl_parent_id = "BIM_PT_object_material"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
if not context.active_object:
|
||||
return False
|
||||
if not tool.Ifc.get() or tool.Ifc.get().schema == "IFC2X3":
|
||||
return False # We don't support material psets in IFC2X3 because they suck
|
||||
if not tool.Ifc.get_entity(context.active_object):
|
||||
return False
|
||||
return True
|
||||
|
||||
def draw(self, context):
|
||||
if not MaterialSetPsetsData.is_loaded:
|
||||
MaterialSetPsetsData.load()
|
||||
|
||||
props = context.active_object.MaterialSetPsetProperties
|
||||
row = self.layout.row(align=True)
|
||||
prop_with_search(row, props, "pset_name", text="")
|
||||
op = row.operator("bim.add_pset", icon="ADD", text="")
|
||||
op.obj = context.active_object.name
|
||||
op.obj_type = "MaterialSet"
|
||||
|
||||
for pset in MaterialSetPsetsData.data["psets"]:
|
||||
draw_psetqto_ui(context, pset["id"], pset, props, self.layout, "MaterialSet")
|
||||
|
||||
|
||||
class BIM_PT_material_set_item_psets(Panel):
|
||||
bl_label = "IFC Material Set Item Property Sets"
|
||||
bl_idname = "BIM_PT_material_set_item_psets"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "object"
|
||||
bl_parent_id = "BIM_PT_object_material"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
if not context.active_object:
|
||||
return False
|
||||
if not tool.Ifc.get() or tool.Ifc.get().schema == "IFC2X3":
|
||||
return False # We don't support material psets in IFC2X3 because they suck
|
||||
if not tool.Ifc.get_entity(context.active_object):
|
||||
return False
|
||||
return True
|
||||
|
||||
def draw(self, context):
|
||||
if not MaterialSetItemPsetsData.is_loaded:
|
||||
MaterialSetItemPsetsData.load()
|
||||
|
||||
props = context.active_object.MaterialSetItemPsetProperties
|
||||
row = self.layout.row(align=True)
|
||||
prop_with_search(row, props, "pset_name", text="")
|
||||
op = row.operator("bim.add_pset", icon="ADD", text="")
|
||||
op.obj = context.active_object.name
|
||||
op.obj_type = "MaterialSetItem"
|
||||
|
||||
for pset in MaterialSetItemPsetsData.data["psets"]:
|
||||
draw_psetqto_ui(context, pset["id"], pset, props, self.layout, "MaterialSetItem")
|
||||
|
||||
|
||||
|
||||
class BIM_PT_task_qtos(Panel):
|
||||
bl_label = "IFC Task Quantity Sets"
|
||||
bl_idname = "BIM_PT_task_qtos"
|
||||
|
||||
@@ -26,8 +26,8 @@ classes = (
|
||||
operator.CalculateObjectVolumes,
|
||||
operator.ExecuteQtoMethod,
|
||||
operator.QuantifyObjects,
|
||||
operator.AssignPsetQto,
|
||||
operator.CalculateAllQtos,
|
||||
operator.AssignBaseQto,
|
||||
operator.CalculateAllQuantities,
|
||||
prop.BIMQtoProperties,
|
||||
ui.BIM_PT_qto_utilities,
|
||||
)
|
||||
|
||||
@@ -172,30 +172,30 @@ class QuantifyObjects(bpy.types.Operator):
|
||||
PsetData.load(self.file, obj.BIMObjectProperties.ifc_definition_id)
|
||||
return {"FINISHED"}
|
||||
|
||||
class AssignPsetQto(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.assign_pset_qto"
|
||||
class AssignBaseQto(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.assign_objects_base_qto"
|
||||
bl_label = "Assign IFC Object Quantity Set"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Assign IFC quantity set to selected object"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.active_object and context.selected_objects
|
||||
return tool.Ifc.get() and context.selected_objects
|
||||
|
||||
def _execute(self, context):
|
||||
core.assign_pset_qto(tool.Qto, selected_objects = context.selected_objects)
|
||||
core.assign_objects_base_qto(tool.Ifc, tool.Qto, selected_objects = context.selected_objects)
|
||||
return {"FINISHED"}
|
||||
|
||||
class CalculateAllQtos(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.calculate_all_qtos"
|
||||
class CalculateAllQuantities(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.calculate_all_quantities"
|
||||
bl_label = "Calculate all quantities"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Calculate all possible quantities and assign them to selected object"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.active_object and context.selected_objects
|
||||
return tool.Ifc.get() and context.selected_objects
|
||||
|
||||
def _execute(self, context):
|
||||
core.calculate_all_qtos(tool.Qto, selected_objects = context.selected_objects)
|
||||
return {"FINISHED"}
|
||||
core.calculate_objects_base_quantities(tool.Ifc, tool.Qto, QtoCalculator(), selected_objects = context.selected_objects)
|
||||
return {"FINISHED"}
|
||||
@@ -53,7 +53,7 @@ class BIM_PT_qto_utilities(Panel):
|
||||
row.operator("bim.quantify_objects", icon="COPYDOWN", text="")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.operator("bim.assign_pset_qto")
|
||||
row.operator("bim.assign_objects_base_qto")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.operator("bim.calculate_all_qtos")
|
||||
row.operator("bim.calculate_all_quantities", icon="MOD_EDGESPLIT")
|
||||
|
||||
@@ -480,7 +480,7 @@ class FilterModelElements(Operator):
|
||||
self.update_model_view(context, selection)
|
||||
return {"FINISHED"}
|
||||
|
||||
def add_groups(self, selector):
|
||||
def add_groups(self, selector:str) -> str:
|
||||
selection = ""
|
||||
for group_index, group in enumerate(selector.groups):
|
||||
if group_index != 0:
|
||||
@@ -491,7 +491,7 @@ class FilterModelElements(Operator):
|
||||
selection += ")" if len(selector.groups) > 1 else ""
|
||||
return selection
|
||||
|
||||
def add_queries(self, selection, group):
|
||||
def add_queries(self, selection: str, group: str) -> str:
|
||||
for query_index, query in enumerate(group.queries):
|
||||
if query_index != 0:
|
||||
selection += " & " if query.and_or == "and" else " | "
|
||||
@@ -513,8 +513,10 @@ class FilterModelElements(Operator):
|
||||
selection += f"@ #{query.sub_options[index].global_id}"
|
||||
return selection
|
||||
|
||||
def add_filters(self, selection, query):
|
||||
def add_filters(self, selection: str, query: str) -> str:
|
||||
for f_index, f in enumerate(query.filters):
|
||||
|
||||
comparison_operator = f.comparison
|
||||
|
||||
if f_index != 0:
|
||||
selection += " & " if f.and_or == "and" else " | "
|
||||
@@ -523,19 +525,23 @@ class FilterModelElements(Operator):
|
||||
selection += "["
|
||||
|
||||
if f.selector == "IfcPropertySet":
|
||||
selection += f'{f.active_option.split(": ")[1]}.{f.active_sub_option.split(": ")[1]} {"!" if f.negation else ""}="{f.value}"'
|
||||
selection += f'{f.active_option.split(": ")[1]}.{f.active_sub_option.split(": ")[1]} {"!" if f.negation else ""}{comparison_operator} "{f.value}"'
|
||||
elif f.selector == "Attribute":
|
||||
selection += f'{f.attribute} {"!" if f.negation else ""}= "{f.value}"'
|
||||
# we're using the prop_search functionality in blender which returns the index of the option. Sometimes the user can override this and enter a value that doesn't exist in the list. In this case there is no index and we need to handle it. @vulevukusej
|
||||
pattern = re.compile(r'^[0-9]+:')
|
||||
match = pattern.search(f.active_option)
|
||||
|
||||
selection += f'{f.active_option.split(": ")[1] if match else f.active_option} {"!" if f.negation else ""}{comparison_operator} "{f.value}"'
|
||||
|
||||
selection += "]"
|
||||
return selection
|
||||
|
||||
def update_model_view(self, context, selection):
|
||||
def update_model_view(self, context, selection: str) -> None:
|
||||
query = Selector.parse(IfcStore.file, selection)
|
||||
sel_element_ids = [e.id() for e in query]
|
||||
bpy.ops.object.select_all(action="DESELECT")
|
||||
|
||||
for obj in context.visible_objects:
|
||||
for obj in bpy.data.scenes["Scene"].objects:
|
||||
obj.hide_set(False) # reset 3d view
|
||||
|
||||
if self.option == "select":
|
||||
|
||||
@@ -130,6 +130,10 @@ def load_selection_options(self, context):
|
||||
elif self.selector == "IfcPropertySet":
|
||||
psets = Selector.parse(ifc, ".IfcPropertySet")
|
||||
options = set([o.Name for o in psets])
|
||||
elif self.selector == "Attribute":
|
||||
attributes = ["GlobalId", "Name", "Description", "ObjectType", "Tag", "PredefinedType"]
|
||||
for a in attributes:
|
||||
options.append((a, a, ""))
|
||||
|
||||
elif load_option == "sub_options":
|
||||
if self.selector in ["IfcSpatialElement", "IfcElementType"]:
|
||||
@@ -208,11 +212,6 @@ class SearchQueryFilter(PropertyGroup, IfcSelector):
|
||||
update=load_selection_options,
|
||||
default="-",
|
||||
)
|
||||
attribute: EnumProperty(
|
||||
items=[(i, i, i) for i in ["GlobalId", "Name", "Description", "ObjectType", "Tag", "PredefinedType"]],
|
||||
name="Filter selection by",
|
||||
update=load_selection_options,
|
||||
)
|
||||
|
||||
|
||||
class SearchQuery(PropertyGroup, IfcSelector):
|
||||
|
||||
@@ -224,7 +224,7 @@ class IfcSelectorUI:
|
||||
row.prop(f, "selector", text="")
|
||||
|
||||
if f.selector == "Attribute":
|
||||
row.prop(f, "attribute", text="")
|
||||
row.prop_search(f, "active_option", f, "options", text="", results_are_suggestions=True)
|
||||
row.prop(
|
||||
f,
|
||||
"negation",
|
||||
|
||||
@@ -259,3 +259,6 @@ def sync_references(ifc, collector, drawing_tool, drawing=None):
|
||||
|
||||
if reference_obj and ifc.is_edited(reference_obj):
|
||||
drawing_tool.sync_object_representation(reference_obj)
|
||||
|
||||
def select_assigned_product(drawing):
|
||||
drawing.select_assigned_product()
|
||||
@@ -22,30 +22,46 @@ def calculate_circle_radius(qto, obj=None):
|
||||
qto.set_qto_result(result)
|
||||
return result
|
||||
|
||||
def assign_pset_qto(qto, selected_objects):
|
||||
|
||||
def assign_objects_base_qto(ifc, qto, selected_objects):
|
||||
for obj in selected_objects:
|
||||
qto.assign_base_qto_to_object(obj)
|
||||
assign_object_base_qto(ifc, qto, obj)
|
||||
|
||||
def calculate_all_qtos(qto, selected_objects):
|
||||
for obj in selected_objects:
|
||||
if not qto.get_pset_qto_object_ifc_info(obj):
|
||||
print(f"There is no pset qto instance associated to object {obj.name}")
|
||||
continue
|
||||
|
||||
applicable_quantity_names = qto.get_applicable_quantity_names(obj)
|
||||
def assign_object_base_qto(ifc, qto, obj):
|
||||
product = ifc.get_entity(obj)
|
||||
if not product:
|
||||
return
|
||||
base_quantity_name = qto.get_applicable_base_quantity_name(product)
|
||||
if not base_quantity_name:
|
||||
return
|
||||
ifc.run(
|
||||
"pset.add_qto",
|
||||
product=product,
|
||||
name=base_quantity_name,
|
||||
)
|
||||
|
||||
calculated_quantities = qto.get_calculated_quantities(obj, applicable_quantity_names)
|
||||
|
||||
qto.edit_qto(obj, calculated_quantities)
|
||||
def calculate_objects_base_quantities(ifc, qto, calculator, selected_objects):
|
||||
if selected_objects:
|
||||
for obj in selected_objects:
|
||||
calculate_object_base_quantities(ifc, qto, calculator, obj)
|
||||
|
||||
def guess_all_qtos(qto, selected_objects):
|
||||
for obj in selected_objects:
|
||||
if not qto.get_pset_qto_object_ifc_info(obj):
|
||||
print(f"There is no pset qto instance associated to object {obj.name}")
|
||||
continue
|
||||
|
||||
applicable_quantity_names = qto.get_base_quantities_name(obj)
|
||||
|
||||
calculated_quantities = qto.get_calculated_quantities(obj, applicable_quantity_names)
|
||||
|
||||
qto.edit_qto(obj, calculated_quantities)
|
||||
def calculate_object_base_quantities(ifc, qto, calculator, obj):
|
||||
product = ifc.get_entity(obj)
|
||||
if not product:
|
||||
return
|
||||
base_quantity_name = qto.get_applicable_base_quantity_name(product)
|
||||
if not base_quantity_name:
|
||||
print(f"There is no base quantity")
|
||||
return
|
||||
base_qto = qto.get_base_qto(product)
|
||||
if not base_qto:
|
||||
base_qto = ifc.run(
|
||||
"pset.add_qto",
|
||||
product=product,
|
||||
name=base_quantity_name,
|
||||
)
|
||||
calculated_quantities = qto.calculate_object_quantities(calculator, base_quantity_name, obj)
|
||||
ifc.run("pset.edit_qto", qto=base_qto, properties=calculated_quantities)
|
||||
|
||||
@@ -198,6 +198,7 @@ class Drawing:
|
||||
def open_spreadsheet(cls, uri): pass
|
||||
def open_svg(cls, filepath): pass
|
||||
def run_root_assign_class(cls, obj=None, ifc_class=None, predefined_type=None, should_add_representation=True, context=None, ifc_representation_class=None): pass
|
||||
def select_assigned_product(cls, drawing): pass
|
||||
def set_drawing_collection_name(cls, group, collection): pass
|
||||
def show_decorations(cls): pass
|
||||
def sync_object_placement(cls, obj): pass
|
||||
@@ -403,15 +404,16 @@ class Pset:
|
||||
class Qto:
|
||||
def get_radius_of_selected_vertices(cls, obj): pass
|
||||
def set_qto_result(cls, result): pass
|
||||
def assign_base_qto_to_object(cls, object): pass
|
||||
def get_pset_qto_object_ifc_info(cls, object): pass
|
||||
def get_applicable_quantity_names(cls, object): pass
|
||||
def get_qto_applicable_name(cls, object): pass
|
||||
def edit_qto(cls, object, calculated_quantities): pass
|
||||
def get_qto_entity(cls, object): pass
|
||||
def get_new_quantity(cls, object, quantity_name, alternative_prop_names): pass
|
||||
def get_applicable_quantity_names(cls, qto_name): pass
|
||||
def get_applicable_base_quantity_name(cls, object): pass
|
||||
def get_rounded_value(cls, new_quantity): pass
|
||||
def get_calculated_quantities(cls, object, pset_qto_properties): pass
|
||||
def calculate_object_quantities(cls, calculator, baste_qto, object): pass
|
||||
def add_object_base_qto(cls, object): pass
|
||||
def add_product_base_qto(cls, product): pass
|
||||
def get_applicable_base_quantity_names(cls, product): pass
|
||||
def get_new_calculated_quantity(cls, qto_name, quantity_name, object): pass
|
||||
def get_new_guessed_quantity(cls, object, qto_name, quantity_name, ): pass
|
||||
|
||||
|
||||
@interface
|
||||
class Resource:
|
||||
|
||||
@@ -898,3 +898,10 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
if tool.Ifc.get_schema() == "IFC2X3":
|
||||
return reference.ReferenceToDocument[0]
|
||||
return reference.ReferencedDocument
|
||||
@classmethod
|
||||
def select_assigned_product(cls):
|
||||
obj = cls.get_active_object()
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
product = cls.get_assigned_product(element)
|
||||
if product:
|
||||
tool.Ifc.get_object(product).select_set(True)
|
||||
@@ -25,6 +25,7 @@ from mathutils import Vector
|
||||
from ifcopenshell import util
|
||||
from blenderbim.bim.module.pset.qto_calculator import QtoCalculator
|
||||
from blenderbim.bim.module.pset.calc_quantity_function_mapper import mapper
|
||||
import blenderbim.bim.schema
|
||||
|
||||
|
||||
class Qto(blenderbim.core.tool.Qto):
|
||||
@@ -42,92 +43,55 @@ class Qto(blenderbim.core.tool.Qto):
|
||||
bpy.context.scene.BIMQtoProperties.qto_result = str(round(result, 3))
|
||||
|
||||
@classmethod
|
||||
def assign_base_qto_to_object(cls, obj):
|
||||
file = tool.Ifc.get()
|
||||
entity = tool.Ifc.get_entity(obj)
|
||||
pset_qto_name = cls.get_qto_applicable_name(obj)
|
||||
ifcopenshell.api.run(
|
||||
"pset.add_qto",
|
||||
file,
|
||||
**{
|
||||
"product": entity,
|
||||
"name": pset_qto_name,
|
||||
},
|
||||
)
|
||||
def add_object_base_qto(cls, obj):
|
||||
product = tool.Ifc.get_entity(obj)
|
||||
cls.add_product_base_qto(product)
|
||||
|
||||
@classmethod
|
||||
def get_pset_qto_object_ifc_info(cls, obj):
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
pset_qto_ifc_info = ifcopenshell.util.element.get_psets(element, qtos_only = True)
|
||||
return pset_qto_ifc_info
|
||||
|
||||
@classmethod
|
||||
def get_applicable_quantity_names(cls, obj):
|
||||
file = tool.Ifc.get()
|
||||
schema = file.schema
|
||||
pset_qto = util.pset.PsetQto(schema)
|
||||
qto_name = cls.get_qto_applicable_name(obj)
|
||||
properties_templates = pset_qto.get_by_name(qto_name).get_info()['HasPropertyTemplates']
|
||||
applicable_quantity_names = [a.get_info()['Name'] for a in properties_templates]
|
||||
return applicable_quantity_names
|
||||
|
||||
@classmethod
|
||||
def get_qto_applicable_name(cls, obj):
|
||||
entity = tool.Ifc.get_entity(obj)
|
||||
ifc_class = entity.get_info()['type']
|
||||
qto_applicable_names = blenderbim.bim.schema.ifc.psetqto.get_applicable_names(ifc_class, qto_only=True)
|
||||
qto_applicable_name = [q for q in qto_applicable_names if "Qto_" in q and "Base" in q][0]
|
||||
return qto_applicable_name
|
||||
|
||||
@classmethod
|
||||
def edit_qto(cls, obj, calculated_quantities):
|
||||
file = tool.Ifc.get()
|
||||
pset_qto_applicable_name = cls.get_qto_applicable_name(obj)
|
||||
qto_entity = cls.get_qto_entity(obj, pset_qto_applicable_name)
|
||||
|
||||
ifcopenshell.api.run("pset.edit_qto",
|
||||
file,
|
||||
qto = qto_entity, properties = calculated_quantities,
|
||||
def add_product_base_qto(cls, product):
|
||||
base_quantity_name = cls.get_applicable_base_quantity_name(product)
|
||||
if base_quantity_name:
|
||||
return tool.Ifc.run(
|
||||
"pset.add_qto",
|
||||
product=product,
|
||||
name=base_quantity_name,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def get_qto_entity(cls, obj, pset_qto_applicable_name):
|
||||
file = tool.Ifc.get()
|
||||
pset_qto_object_ifc_info = cls.get_pset_qto_object_ifc_info(obj)
|
||||
qto_entity = file.by_id(pset_qto_object_ifc_info[pset_qto_applicable_name]['id'])
|
||||
return qto_entity
|
||||
def get_applicable_quantity_names(cls, qto_name):
|
||||
pset_template = blenderbim.bim.schema.ifc.psetqto.get_by_name(qto_name)
|
||||
return (
|
||||
[property.Name for property in pset_template.HasPropertyTemplates]
|
||||
if hasattr(pset_template, "HasPropertyTemplates")
|
||||
else []
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def get_applicable_base_quantity_name(cls, product=None):
|
||||
if not product:
|
||||
return
|
||||
applicable_qto_names = blenderbim.bim.schema.ifc.psetqto.get_applicable_names(product.is_a(), qto_only=True)
|
||||
return next((qto_name for qto_name in applicable_qto_names if "Qto_" in qto_name and "Base" in qto_name), None)
|
||||
|
||||
@classmethod
|
||||
def get_new_calculated_quantity(cls, qto_name, quantity_name, obj):
|
||||
calculator = QtoCalculator()
|
||||
new_quantity = calculator.calculate_quantity(qto_name, quantity_name, obj)
|
||||
return new_quantity
|
||||
return QtoCalculator().calculate_quantity(qto_name, quantity_name, obj)
|
||||
|
||||
@classmethod
|
||||
def get_new_guessed_quantity(cls, obj, quantity_name, alternative_prop_names):
|
||||
calculator = QtoCalculator()
|
||||
new_quantity = calculator.guess_quantity(quantity_name, alternative_prop_names, obj)
|
||||
return QtoCalculator().guess_quantity(quantity_name, alternative_prop_names, obj)
|
||||
|
||||
@classmethod
|
||||
def get_rounded_value(cls, new_quantity):
|
||||
return round(new_quantity, 3)
|
||||
|
||||
@classmethod
|
||||
def get_calculated_quantities(cls, obj, applicable_quantity_names):
|
||||
calculated_quantities = {}
|
||||
qto_name = cls.get_qto_applicable_name(obj)
|
||||
calculator = QtoCalculator()
|
||||
|
||||
for quantity_name in applicable_quantity_names:
|
||||
if not cls.has_calculator(qto_name, quantity_name):
|
||||
continue
|
||||
else:
|
||||
new_quantity = calculator.calculate_quantity(qto_name, quantity_name, obj)
|
||||
new_quantity = cls.get_rounded_value(new_quantity)
|
||||
|
||||
calculated_quantities[quantity_name] = new_quantity
|
||||
|
||||
return calculated_quantities
|
||||
def calculate_object_quantities(cls, calculator, qto_name, obj):
|
||||
return {
|
||||
quantity_name: cls.get_rounded_value(calculator.calculate_quantity(qto_name, quantity_name, obj))
|
||||
for quantity_name in cls.get_applicable_quantity_names(qto_name) or []
|
||||
if cls.has_calculator(qto_name, quantity_name)
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def has_calculator(cls, qto_name, quantity_name):
|
||||
@@ -137,8 +101,8 @@ class Qto(blenderbim.core.tool.Qto):
|
||||
def get_guessed_quantities(cls, obj, pset_qto_properties):
|
||||
calculated_quantities = {}
|
||||
for pset_qto_property in pset_qto_properties:
|
||||
quantity_name = pset_qto_property.get_info()['Name']
|
||||
alternative_prop_names = [p.get_info()['Name'] for p in pset_qto_properties]
|
||||
quantity_name = pset_qto_property.get_info()["Name"]
|
||||
alternative_prop_names = [p.get_info()["Name"] for p in pset_qto_properties]
|
||||
|
||||
new_quantity = cls.get_new_guessed_quantity(obj, quantity_name, alternative_prop_names)
|
||||
|
||||
@@ -151,3 +115,15 @@ class Qto(blenderbim.core.tool.Qto):
|
||||
|
||||
return calculated_quantities
|
||||
|
||||
@classmethod
|
||||
def get_base_qto(cls, product):
|
||||
if not hasattr(product, "IsDefinedBy"):
|
||||
return
|
||||
for rel in product.IsDefinedBy or []:
|
||||
if not (
|
||||
rel.is_a("IfcRelDefinesByProperties")
|
||||
and "Base" in rel.RelatingPropertyDefinition.Name
|
||||
and "Qto_" in rel.RelatingPropertyDefinition.Name
|
||||
):
|
||||
continue
|
||||
return rel.RelatingPropertyDefinition
|
||||
|
||||
@@ -22,8 +22,6 @@
|
||||
import numpy as np
|
||||
import json
|
||||
import sys
|
||||
import argparse
|
||||
import logging
|
||||
import multiprocessing
|
||||
import ifcopenshell
|
||||
import ifcopenshell.geom
|
||||
@@ -117,71 +115,22 @@ class Clasher:
|
||||
self.export_json()
|
||||
|
||||
def export_bcfxml(self):
|
||||
import bcf
|
||||
import bcf.v2.bcfxml
|
||||
from bcf.v2.bcfxml import BcfXml
|
||||
|
||||
for i, clash_set in enumerate(self.clash_sets):
|
||||
bcfxml = bcf.v2.bcfxml.BcfXml()
|
||||
bcfxml.new_project()
|
||||
bcfxml.project.name = clash_set["name"]
|
||||
bcfxml.edit_project()
|
||||
for key, clash in clash_set["clashes"].items():
|
||||
topic = bcf.v2.data.Topic()
|
||||
topic.title = "{}/{} and {}/{}".format(
|
||||
clash["a_ifc_class"], clash["a_name"], clash["b_ifc_class"], clash["b_name"]
|
||||
bcfxml = BcfXml.create_new(clash_set["name"])
|
||||
for clash in clash_set["clashes"].values():
|
||||
title = f'{clash["a_ifc_class"]}/{clash["a_name"]} and {clash["b_ifc_class"]}/{clash["b_name"]}'
|
||||
topic = bcfxml.add_topic(title, title, "IfcClash")
|
||||
topic.add_viewpoint_from_point_and_guids(
|
||||
np.array(clash["position"]), clash["a_global_id"], clash["b_global_id"],
|
||||
)
|
||||
topic = bcfxml.add_topic(topic)
|
||||
viewpoint = bcf.v2.data.Viewpoint()
|
||||
viewpoint.perspective_camera = bcf.v2.data.PerspectiveCamera()
|
||||
position = np.array(clash["position"])
|
||||
point = position + np.array((5, 5, 5)) # Dumb, but works (for now)!
|
||||
viewpoint.perspective_camera.camera_view_point.x = point[0]
|
||||
viewpoint.perspective_camera.camera_view_point.y = point[1]
|
||||
viewpoint.perspective_camera.camera_view_point.z = point[2]
|
||||
mat = self.get_track_to_matrix(point, position)
|
||||
viewpoint.perspective_camera.camera_direction.x = mat[0][2] * -1
|
||||
viewpoint.perspective_camera.camera_direction.y = mat[1][2] * -1
|
||||
viewpoint.perspective_camera.camera_direction.z = mat[2][2] * -1
|
||||
viewpoint.perspective_camera.camera_up_vector.x = mat[0][1]
|
||||
viewpoint.perspective_camera.camera_up_vector.y = mat[1][1]
|
||||
viewpoint.perspective_camera.camera_up_vector.z = mat[2][1]
|
||||
viewpoint.components = bcf.v2.data.Components()
|
||||
c1 = bcf.v2.data.Component()
|
||||
c1.ifc_guid = clash["a_global_id"]
|
||||
c2 = bcf.v2.data.Component()
|
||||
c2.ifc_guid = clash["b_global_id"]
|
||||
viewpoint.components.selection.append(c1)
|
||||
viewpoint.components.selection.append(c2)
|
||||
viewpoint.components.visibility = bcf.v2.data.ComponentVisibility()
|
||||
viewpoint.components.visibility.default_visibility = True
|
||||
viewpoint.snapshot = self.get_viewpoint_snapshot(viewpoint, mat)
|
||||
bcfxml.add_viewpoint(topic, viewpoint)
|
||||
if i == 0:
|
||||
bcfxml.save_project(self.settings.output)
|
||||
else:
|
||||
bcfxml.save_project(self.settings.output + f".{i}")
|
||||
suffix = f".{i}" if i else ""
|
||||
bcfxml.save_project(f"{self.settings.output}{suffix}")
|
||||
|
||||
def get_viewpoint_snapshot(self, viewpoint, mat):
|
||||
return None # Possible to overload this function in a GUI application if used as a library
|
||||
|
||||
# https://blender.stackexchange.com/questions/68834/recreate-to-track-quat-with-two-vectors-using-python/141706#141706
|
||||
def get_track_to_matrix(self, camera_position, target_position):
|
||||
camera_direction = camera_position - target_position
|
||||
camera_direction = camera_direction / np.linalg.norm(camera_direction)
|
||||
camera_right = np.cross(np.array([0.0, 0.0, 1.0]), camera_direction)
|
||||
camera_right = camera_right / np.linalg.norm(camera_right)
|
||||
camera_up = np.cross(camera_direction, camera_right)
|
||||
camera_up = camera_up / np.linalg.norm(camera_up)
|
||||
rotation_transform = np.zeros((4, 4))
|
||||
rotation_transform[0, :3] = camera_right
|
||||
rotation_transform[1, :3] = camera_up
|
||||
rotation_transform[2, :3] = camera_direction
|
||||
rotation_transform[-1, -1] = 1
|
||||
translation_transform = np.eye(4)
|
||||
translation_transform[:3, -1] = -camera_position
|
||||
look_at_transform = np.matmul(rotation_transform, translation_transform)
|
||||
return np.linalg.inv(look_at_transform)
|
||||
|
||||
def export_json(self):
|
||||
clash_sets = self.clash_sets.copy()
|
||||
for clash_set in clash_sets:
|
||||
|
||||
@@ -136,7 +136,9 @@ bool IfcGeom::Kernel::convert(const IfcSchema::IfcBooleanResult* l, TopoDS_Shape
|
||||
TopoDS_Shape temp;
|
||||
double d;
|
||||
if (util::fit_halfspace(s1, s2, temp, d, getValue(GV_PRECISION))) {
|
||||
if (d < getValue(GV_PRECISION)) {
|
||||
// #2665 we also set a precision-independent treshold, because in the boolean op routine
|
||||
// the working fuzziness might still be increased.
|
||||
if (d < getValue(GV_PRECISION) * 20. || d < 0.00002) {
|
||||
Logger::Message(Logger::LOG_WARNING, "Halfspace subtraction yields unchanged volume:", l);
|
||||
continue;
|
||||
} else {
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
#ifndef BASE_UTILS_H
|
||||
#define BASE_UTILS_H
|
||||
|
||||
#include <gp_Ax3.hxx>
|
||||
#include <gp_Pln.hxx>
|
||||
#include <gp_Pnt.hxx>
|
||||
#include <gp_Ax3.hxx>
|
||||
#include <gp_Ax2d.hxx>
|
||||
#include <gp_Trsf2d.hxx>
|
||||
#include <gp_GTrsf2d.hxx>
|
||||
#include <gp_Trsf.hxx>
|
||||
#include <gp_GTrsf.hxx>
|
||||
|
||||
#include <TopTools_ListOfShape.hxx>
|
||||
#include <TopoDS_Wire.hxx>
|
||||
@@ -68,4 +73,4 @@ namespace IfcGeom {
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -620,18 +620,6 @@ bool IfcGeom::util::boolean_subtraction_2d_using_builder(const TopoDS_Shape & a_
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
TopoDS_Compound C;
|
||||
BRep_Builder BB;
|
||||
BB.MakeCompound(C);
|
||||
|
||||
for (auto& w : wires) {
|
||||
BB.Add(C, w);
|
||||
}
|
||||
|
||||
BRepTools::Write(C, "debug.brep");
|
||||
}
|
||||
|
||||
shape_index = 0;
|
||||
edge_index = 0;
|
||||
|
||||
@@ -1369,4 +1357,4 @@ const TopoDS_Shape& IfcGeom::util::ensure_fit_for_subtraction(const TopoDS_Shape
|
||||
}
|
||||
|
||||
return solid;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,7 +80,11 @@ bool IfcGeom::util::approximate_plane_through_wire(const TopoDS_Wire& wire, gp_P
|
||||
}
|
||||
|
||||
gp_Vec v(x, y, z);
|
||||
if (v.SquareMagnitude() < eps_ * eps_) {
|
||||
// @todo the epsilon passed to us here is the maximum allowed deviation of
|
||||
// the given points to the constructed plane. When doing triangulation and
|
||||
// obtaining a 2d points for the Delaunay, infinity is passed here, so this
|
||||
// can't for assessing degenerativeness.
|
||||
if (v.SquareMagnitude() < 1.e-7) {
|
||||
Logger::Warning("Degenerate face boundary in normal estimation");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -218,6 +218,6 @@ class Usecase:
|
||||
|
||||
def create_2d_origin(self):
|
||||
self.origin = self.file.createIfcAxis2Placement2D(
|
||||
self.file.createIfcCartesianPoint((0.0, 0.0, 0.0)),
|
||||
self.file.createIfcDirection((1.0, 0.0, 0.0)),
|
||||
self.file.createIfcCartesianPoint((0.0, 0.0)),
|
||||
self.file.createIfcDirection((1.0, 0.0)),
|
||||
)
|
||||
|
||||
@@ -76,7 +76,11 @@ class Usecase:
|
||||
ifcopenshell.api.run("grid.remove_grid_axis", self.file, axis=axis)
|
||||
|
||||
# TODO: remove object placement and other relationships
|
||||
for inverse in self.file.get_inverse(self.settings["product"]):
|
||||
|
||||
# Use a while loop so that we don't keep inverses in memory that might
|
||||
# get deleted as a result of the API calls in the loop body (#2697)
|
||||
while inverses := self.file.get_inverse(self.settings["product"]):
|
||||
inverse = next(iter(inverses))
|
||||
if inverse.is_a("IfcRelDefinesByProperties"):
|
||||
ifcopenshell.api.run(
|
||||
"pset.remove_pset",
|
||||
|
||||
@@ -634,8 +634,9 @@ def usedin(inst, ref_name):
|
||||
|
||||
|
||||
class express_set(set):
|
||||
def __rmul__(self, other):
|
||||
def __mul__(self, other):
|
||||
return express_set(set(other) & self)
|
||||
__rmul__ = __mul__
|
||||
def __add__(self, other):
|
||||
def make_list(v):
|
||||
# Comply with 12.6.3 Union operator
|
||||
|
||||
@@ -30,8 +30,9 @@ def usedin(inst, ref_name):
|
||||
|
||||
|
||||
class express_set(set):
|
||||
def __rmul__(self, other):
|
||||
def __mul__(self, other):
|
||||
return express_set(set(other) & self)
|
||||
__rmul__ = __mul__
|
||||
def __add__(self, other):
|
||||
def make_list(v):
|
||||
# Comply with 12.6.3 Union operator
|
||||
|
||||
@@ -30,8 +30,9 @@ def usedin(inst, ref_name):
|
||||
|
||||
|
||||
class express_set(set):
|
||||
def __rmul__(self, other):
|
||||
def __mul__(self, other):
|
||||
return express_set(set(other) & self)
|
||||
__rmul__ = __mul__
|
||||
def __add__(self, other):
|
||||
def make_list(v):
|
||||
# Comply with 12.6.3 Union operator
|
||||
|
||||
@@ -30,8 +30,9 @@ def usedin(inst, ref_name):
|
||||
|
||||
|
||||
class express_set(set):
|
||||
def __rmul__(self, other):
|
||||
def __mul__(self, other):
|
||||
return express_set(set(other) & self)
|
||||
__rmul__ = __mul__
|
||||
def __add__(self, other):
|
||||
def make_list(v):
|
||||
# Comply with 12.6.3 Union operator
|
||||
|
||||
@@ -30,8 +30,9 @@ def usedin(inst, ref_name):
|
||||
|
||||
|
||||
class express_set(set):
|
||||
def __rmul__(self, other):
|
||||
def __mul__(self, other):
|
||||
return express_set(set(other) & self)
|
||||
__rmul__ = __mul__
|
||||
def __add__(self, other):
|
||||
def make_list(v):
|
||||
# Comply with 12.6.3 Union operator
|
||||
|
||||
@@ -30,8 +30,9 @@ def usedin(inst, ref_name):
|
||||
|
||||
|
||||
class express_set(set):
|
||||
def __rmul__(self, other):
|
||||
def __mul__(self, other):
|
||||
return express_set(set(other) & self)
|
||||
__rmul__ = __mul__
|
||||
def __add__(self, other):
|
||||
def make_list(v):
|
||||
# Comply with 12.6.3 Union operator
|
||||
|
||||
@@ -30,8 +30,9 @@ def usedin(inst, ref_name):
|
||||
|
||||
|
||||
class express_set(set):
|
||||
def __rmul__(self, other):
|
||||
def __mul__(self, other):
|
||||
return express_set(set(other) & self)
|
||||
__rmul__ = __mul__
|
||||
def __add__(self, other):
|
||||
def make_list(v):
|
||||
# Comply with 12.6.3 Union operator
|
||||
|
||||
@@ -30,8 +30,9 @@ def usedin(inst, ref_name):
|
||||
|
||||
|
||||
class express_set(set):
|
||||
def __rmul__(self, other):
|
||||
def __mul__(self, other):
|
||||
return express_set(set(other) & self)
|
||||
__rmul__ = __mul__
|
||||
def __add__(self, other):
|
||||
def make_list(v):
|
||||
# Comply with 12.6.3 Union operator
|
||||
|
||||
@@ -30,8 +30,9 @@ def usedin(inst, ref_name):
|
||||
|
||||
|
||||
class express_set(set):
|
||||
def __rmul__(self, other):
|
||||
def __mul__(self, other):
|
||||
return express_set(set(other) & self)
|
||||
__rmul__ = __mul__
|
||||
def __add__(self, other):
|
||||
def make_list(v):
|
||||
# Comply with 12.6.3 Union operator
|
||||
|
||||
@@ -30,8 +30,9 @@ def usedin(inst, ref_name):
|
||||
|
||||
|
||||
class express_set(set):
|
||||
def __rmul__(self, other):
|
||||
def __mul__(self, other):
|
||||
return express_set(set(other) & self)
|
||||
__rmul__ = __mul__
|
||||
def __add__(self, other):
|
||||
def make_list(v):
|
||||
# Comply with 12.6.3 Union operator
|
||||
|
||||
@@ -30,8 +30,9 @@ def usedin(inst, ref_name):
|
||||
|
||||
|
||||
class express_set(set):
|
||||
def __rmul__(self, other):
|
||||
def __mul__(self, other):
|
||||
return express_set(set(other) & self)
|
||||
__rmul__ = __mul__
|
||||
def __add__(self, other):
|
||||
def make_list(v):
|
||||
# Comply with 12.6.3 Union operator
|
||||
|
||||
@@ -30,8 +30,9 @@ def usedin(inst, ref_name):
|
||||
|
||||
|
||||
class express_set(set):
|
||||
def __rmul__(self, other):
|
||||
def __mul__(self, other):
|
||||
return express_set(set(other) & self)
|
||||
__rmul__ = __mul__
|
||||
def __add__(self, other):
|
||||
def make_list(v):
|
||||
# Comply with 12.6.3 Union operator
|
||||
|
||||
@@ -50,7 +50,8 @@ def get_subtypes(entity):
|
||||
|
||||
def reassign_class(ifc_file, element, new_class):
|
||||
try:
|
||||
new_element = ifc_file.create_entity(new_class)
|
||||
new_element = ifcopenshell.create_entity(new_class)
|
||||
new_element.GlobalId = element.GlobalId
|
||||
except:
|
||||
print(f"Class of {element} could not be changed to {new_class}")
|
||||
return element
|
||||
@@ -63,6 +64,7 @@ def reassign_class(ifc_file, element, new_class):
|
||||
for inverse in ifc_file.get_inverse(element):
|
||||
ifcopenshell.util.element.replace_attribute(inverse, element, new_element)
|
||||
ifc_file.remove(element)
|
||||
ifc_file.add(new_element, element.id())
|
||||
return new_element
|
||||
|
||||
|
||||
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
ISO-10303-21;
|
||||
HEADER;
|
||||
FILE_DESCRIPTION(('ViewDefinition [CoordinationView]'),'2;1');
|
||||
FILE_NAME('','2023-01-13T11:08:39',(),(),'IfcOpenShell v0.7.0-8a108f37','IfcOpenShell v0.7.0-8a108f37','');
|
||||
FILE_SCHEMA(('IFC4'));
|
||||
ENDSEC;
|
||||
DATA;
|
||||
#1=IFCTASK('0J288bRSP71RkTGLX57Mso',$,'sleep',$,$,$,$,$,$,.T.,$,$,$);
|
||||
#2=IFCMATERIAL('brick',$,$);
|
||||
#3=IFCRELASSOCIATESMATERIAL('3CndjAA6v4f9K4OHeiDzQJ',$,$,$,(#1),#2);
|
||||
ENDSEC;
|
||||
END-ISO-10303-21;
|
||||
@@ -0,0 +1,10 @@
|
||||
import ifcopenshell
|
||||
|
||||
task = lambda f: f.createIfcTask(ifcopenshell.guid.new(),None,'sleep',IsMilestone=True)
|
||||
wall = lambda f: f.createIfcWall(ifcopenshell.guid.new())
|
||||
|
||||
for i,fn in enumerate((task, wall)):
|
||||
f = ifcopenshell.file(schema="IFC4")
|
||||
elem = fn(f)
|
||||
f.createIfcRelAssociatesMaterial(ifcopenshell.guid.new(), None, None, None, [elem], f.createIfcMaterial('brick'))
|
||||
f.write(f"{'pass' if i else 'fail'}-assoc-material-{elem.is_a()}-{f.schema}.ifc")
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
ISO-10303-21;
|
||||
HEADER;
|
||||
FILE_DESCRIPTION(('ViewDefinition [CoordinationView]'),'2;1');
|
||||
FILE_NAME('','2023-01-13T11:08:39',(),(),'IfcOpenShell v0.7.0-8a108f37','IfcOpenShell v0.7.0-8a108f37','');
|
||||
FILE_SCHEMA(('IFC4'));
|
||||
ENDSEC;
|
||||
DATA;
|
||||
#1=IFCWALL('2AgqCFV5f2XhqZDQAUeko6',$,$,$,$,$,$,$,$);
|
||||
#2=IFCMATERIAL('brick',$,$);
|
||||
#3=IFCRELASSOCIATESMATERIAL('2dtKImsN59_hw0uWDJiJUA',$,$,$,(#1),#2);
|
||||
ENDSEC;
|
||||
END-ISO-10303-21;
|
||||
@@ -9,7 +9,7 @@ IfcSverchok is packaged like a regular Blender add-on, so installation is the sa
|
||||
|
||||
Like all Blender add-ons, they can be installed using `Edit > Preferences > Addons > Install > Choose Downloaded ZIP > Enable Add-on Checkbox`. You can enable add-ons permanently by using `Save User Settings` from the Addons menu.
|
||||
|
||||
Before installing, you will also need to [install the BlenderBIM Add-on](https://blenderbim.org/download.html) and [install Sverchok](https://github.com/nortikin/sverchok/releases/tag/v1.1.0) (v1.1.0).
|
||||
Before installing, you will also need to [install the BlenderBIM Add-on](https://blenderbim.org/download.html) and install the latest version of [Sverchok](https://github.com/nortikin/sverchok) (v1.2).
|
||||
|
||||
## Nodes
|
||||
List of nodes that have been tested to the best of ability (nodes not listed here are not considered ready for use). If you find bugs/unexpected behaviour in any of them, please open an issue or get in touch otherwise.
|
||||
|
||||
@@ -616,19 +616,26 @@ void HdfSerializer::write(const IfcGeom::BRepElement* o) {
|
||||
|
||||
auto element_group = write((const IfcGeom::Element*)o);
|
||||
|
||||
/*
|
||||
// For now we disable softlinks, as it can't be safely used with delete()
|
||||
// @todo we can still cache the serialization or read it from the file probably for a comparible speedup.
|
||||
|
||||
auto it = group_cache_.find(o->geometry().id());
|
||||
if (it != group_cache_.end()) {
|
||||
H5Lcreate_soft(it->second.c_str(), element_group.getLocId(), o->geometry().id().c_str(), H5P_DEFAULT, H5P_DEFAULT);
|
||||
return;
|
||||
}
|
||||
*/
|
||||
|
||||
H5::Group representation_group = createRepresentationGroup(element_group, o->geometry().id());
|
||||
|
||||
/*
|
||||
const size_t len = H5Iget_name(representation_group.getId(), NULL, 0);
|
||||
char* name_buffer = new char[len];
|
||||
H5Iget_name(representation_group.getId(), name_buffer, len + 1);
|
||||
group_cache_.insert(it, { o->geometry().id(), name_buffer });
|
||||
delete[] name_buffer;
|
||||
*/
|
||||
|
||||
std::list<std::string> brep_strings;
|
||||
size_t num_parts = std::distance(o->geometry().begin(), o->geometry().end());
|
||||
|
||||
Reference in New Issue
Block a user