Compare commits

..

31 Commits

Author SHA1 Message Date
Sigma Dimensions 893fd50b85 BBIM: Attempt to fix 3D viewport decoration issue 2023-01-20 10:12:42 +01:00
Sigma Dimensions 7a3e53a9f8 update qto core and qto tools:
- when calculating quantities of an object, assign base qto to object if none exists.
- optimise core and tool functions so it's faster for many selected objects
2023-01-19 21:05:47 +01:00
Dion Moult 07ee62eb91 Add support for editing material set item psets. 2023-01-18 16:40:15 +11:00
Thomas Krijnen b41878e960 #2697 take only a single inverse at a time 2023-01-16 21:29:59 +01:00
Vukas Pajic d9f1cb5525 reassign_class now preserves original STEP ID and GLOBALID - #2701 2023-01-16 13:34:57 +01:00
Vukas Pajic 8110951987 Now support custom attributes in IfcSelector #2649 2023-01-16 08:58:14 +01:00
Vukas Pajic 6b63b5aae7 selector now correctly applies comparison operator. #2643 2023-01-16 08:08:26 +01:00
Martyna Jakubowska e814736e68 Update README.md 2023-01-16 06:15:38 +11:00
Thomas Krijnen 802e3a0ea1 #2688 fix non-planar face triangulation 2023-01-15 14:42:24 +01:00
Massimo Fabbro 4e8a962f60 Minor fix 2023-01-15 11:34:54 +11:00
Massimo Fabbro e20d4c93b7 Delete unuseful qto tool function 2023-01-15 11:34:54 +11:00
Massimo Fabbro 1be76c8aaa Fix little bug 2023-01-15 11:34:18 +11:00
Andrea Ghensi a4566797fe fix: ifcclash bcfxml export 2023-01-15 07:23:42 +11:00
Kristoffer Andersen 4ee3458ff9 Run linux/osx builds with fewer cpus 2023-01-14 17:23:44 +01:00
Sigma Dimensions f86de533f8 BBIM: Feature to select an annotation's assigned object 2023-01-13 16:13:57 +01:00
Sigma Dimensions 693831dca3 BBIM: replace "guess_all_quantities" operator in the UI 2023-01-13 15:00:17 +01:00
Kristoffer Andersen 7500919b5d Set to fixed daily release
Remove association to every completed CI workflow. High frequency of releases makes it better to keep this at a fixed interval given it's so compute intensive
2023-01-13 14:59:06 +01:00
Sigma Dimensions e37b6fa8a2 BBIM: Replace "GuessAllQuantities" with "CalculateQuantities in QTO subpanel, and also re-calculate for all selected objects 2023-01-13 14:57:36 +01:00
Thomas Krijnen 9de9645229 #2665 increase halfspace tolerance comparison 2023-01-13 13:56:38 +01:00
Thomas Krijnen 373d664251 #2361 2023-01-13 12:50:18 +01:00
Dion Moult 6ab912a049 Fix invalid IFC context dimensionality 2023-01-13 22:35:23 +11:00
Dion Moult 9b9c900340 You can now manage material set psets from the UI 2023-01-13 22:35:23 +11:00
Thomas Krijnen a1ac820050 #2685 Recompile rules 2023-01-13 12:12:32 +01:00
Thomas Krijnen e812a886dc #2685 also implement __mul__ on express_set 2023-01-13 12:12:32 +01:00
Thomas Krijnen b759e22875 #2685 assoc material test cases 2023-01-13 12:12:32 +01:00
Thomas Krijnen 93e0babe5a #1785 disable hdf5 brep softlinking 2023-01-13 12:10:57 +01:00
Kristoffer Andersen 7d021fb7a4 reduce number of parallel tasks 2023-01-13 08:03:16 +01:00
Kristoffer Andersen b2cb8ffd8f Update ci-ifcopenshell-conda-daily.yml 2023-01-13 07:56:24 +01:00
Kristoffer Andersen 25a5651e50 Update ci-ifcopenshell-conda-daily.yml 2023-01-13 07:53:53 +01:00
Thomas Krijnen b301eb6520 Update boolean_utils.cpp 2023-01-12 13:07:40 +01:00
Thomas Krijnen 9f90d37401 Delete .travis.yml 2023-01-12 12:41:28 +01:00
61 changed files with 743 additions and 440 deletions
@@ -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
View File
@@ -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
View File
@@ -41,4 +41,4 @@ cmake -G Ninja \
ninja
ninja install
ninja install -j 2
+1 -1
View File
@@ -3,5 +3,5 @@ mypy
pylint
isort
xsdata
tox
tox==3.27.1
tox-conda
+32 -12
View File
@@ -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_]:
+8 -6
View File
@@ -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":
+23 -7
View File
@@ -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
)
+52 -5
View File
@@ -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(
+4 -4
View File
@@ -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
+25 -9
View File
@@ -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
)
+52 -5
View File
@@ -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(
+22 -18
View File
@@ -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"
+36
View File
@@ -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
+16 -24
View File
@@ -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"
+36
View File
@@ -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
+7
View File
@@ -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()
+36 -20
View File
@@ -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)
+10 -8
View File
@@ -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)
+47 -71
View File
@@ -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
+9 -60
View File
@@ -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:
+3 -1
View File
@@ -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 {
+7 -2
View File
@@ -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
+1 -13
View File
@@ -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;
}
}
+5 -1
View File
@@ -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
@@ -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")
@@ -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;
+1 -1
View File
@@ -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.
+7
View File
@@ -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());