Merge pull request #3 from IfcOpenShell/v0.6.0

merge master
This commit is contained in:
GisSpace
2020-12-31 15:44:04 +08:00
committed by GitHub
58 changed files with 4236 additions and 1395 deletions
+4
View File
@@ -12,3 +12,7 @@ __pycache__
.vscode
# PyCharm files
.idea
# gettext binary translation files
*.mo
# Vim
*.swp
+26 -37
View File
@@ -23,7 +23,7 @@
# This script builds IfcOpenShell and its dependencies #
# #
# Prerequisites for this script to function correctly: #
# * git * bzip2 * tar * c(++) compilers * yacc * autoconf #
# * cmake * git * bzip2 * tar * c(++) compilers * yacc * autoconf #
# #
# if building with USE_OCCT additionally: #
# * freetype * glx.h #
@@ -38,15 +38,15 @@
# * libffi(-dev[el]) #
# #
# on debian 7.8 these can be obtained with: #
# $ apt-get install git gcc g++ autoconf bison bzip2 #
# $ apt-get install git gcc g++ autoconf bison bzip2 cmake #
# libfreetype6-dev mesa-common-dev libffi-dev libfontconfig1-dev #
# #
# on ubuntu 14.04: #
# $ apt-get install git gcc g++ autoconf bison make #
# $ apt-get install git gcc g++ autoconf bison make cmake #
# libfreetype6-dev mesa-common-dev libffi-dev libfontconfig1-dev #
# #
# on OS X El Capitan with homebrew: #
# $ brew install git bison autoconf automake freetype libffi #
# $ brew install git bison autoconf automake freetype libffi cmake #
# #
###############################################################################
@@ -79,7 +79,7 @@ ch.setLevel(logging.INFO)
logger.addHandler(ch)
PROJECT_NAME="IfcOpenShell"
PYTHON_VERSIONS=["2.7.16", "3.2.6", "3.3.6", "3.4.6", "3.5.3", "3.6.2", "3.7.3", "3.8.2"]
PYTHON_VERSIONS=["2.7.16", "3.2.6", "3.3.6", "3.4.6", "3.5.3", "3.6.2", "3.7.3", "3.8.6", "3.9.1"]
JSON_VERSION="v3.6.1"
OCE_VERSION="0.18"
# OCCT_VERSION="7.1.0"
@@ -87,13 +87,11 @@ OCE_VERSION="0.18"
# OCCT_VERSION="7.2.0"
# OCCT_HASH="88af392"
OCCT_VERSION="7.3.0p3"
BOOST_VERSION="1.59.0"
BOOST_VERSION="1.71.0"
#PCRE_VERSION="8.39"
PCRE_VERSION="8.41"
#LIBXML2_VERSION="2.9.3"
LIBXML2_VERSION="2.9.9"
CMAKE_VERSION="3.4.3"
#CMAKE_VERSION="3.14.5"
SWIG_VERSION="3.0.12"
#SWIG_VERSION="4.0.0"
#OPENCOLLADA_VERSION="v1.6.63"
@@ -159,6 +157,13 @@ TOOLSET = None
if get_os() == "Darwin":
# C++11 features used in OCCT 7+ need a more recent stdlib
TOOLSET = "10.9" if USE_OCCT else "10.6"
# python 3.4 doesn't seem to build anymore on recent versions of clang
if get_os() == "Darwin":
try:
PYTHON_VERSIONS.remove("3.4.6")
except ValueError as e:
pass
try:
IFCOS_NUM_BUILD_PROCS = os.environ["IFCOS_NUM_BUILD_PROCS"]
@@ -259,7 +264,7 @@ print("Building:", *sorted(targets, key=lambda t: len(list(v(t)))))
# Check that required tools are in PATH
for cmd in [git, bunzip2, tar, cc, cplusplus, autoconf, automake, yacc, make, "patch"]:
for cmd in [git, bunzip2, tar, cc, cplusplus, autoconf, automake, yacc, make, "patch", "cmake"]:
if which(cmd) is None:
raise ValueError("Required tool '%s' not installed or not added to PATH" % (cmd,))
@@ -303,10 +308,9 @@ def run(cmds, cwd=None):
return stdout.strip()
BOOST_VERSION_UNDERSCORE=BOOST_VERSION.replace(".", "_")
CMAKE_VERSION_2=CMAKE_VERSION[:CMAKE_VERSION.rindex('.')]
OCE_LOCATION="https://github.com/tpaviot/oce/archive/OCE-%s.tar.gz" % (OCE_VERSION,)
BOOST_LOCATION="http://downloads.sourceforge.net/project/boost/boost/%s/boost_%s.tar.bz2" % (BOOST_VERSION, BOOST_VERSION_UNDERSCORE)
BOOST_LOCATION="https://dl.bintray.com/boostorg/release/%s/source/" % (BOOST_VERSION,)
# Helper functions
@@ -322,8 +326,7 @@ def run_cmake(arg1, cmake_args, cmake_dir=None, cwd=None):
P=".."
else:
P=cmake_dir
cmake_path= os.path.join(DEPS_DIR, "install", "cmake-%s" % (CMAKE_VERSION,), "bin", "cmake")
run([cmake_path, P]+cmake_args+["-DCMAKE_BUILD_TYPE=%s" % (BUILD_CFG,)], cwd=cwd)
run(["cmake", P]+cmake_args+["-DCMAKE_BUILD_TYPE=%s" % (BUILD_CFG,)], cwd=cwd)
def git_clone_or_pull_repository(clone_url, target_dir, revision=None):
"""Lazily clones the `git` repository denoted by `clone_url` into
@@ -424,7 +427,7 @@ def build_dependency(name, mode, build_tool_args, download_url, download_name, d
else:
raise ValueError()
logger.info("\rBuilding %s... " % (name,))
run([make, "-j%s" % (IFCOS_NUM_BUILD_PROCS,)], cwd=extract_build_dir)
run([make, "-j%s" % (IFCOS_NUM_BUILD_PROCS,), "VERBOSE=1"], cwd=extract_build_dir)
logger.info( "\rInstalling %s... " % (name,))
run([make, "install"], cwd=extract_build_dir)
logger.info( "\rInstalled %s \n" % (name,))
@@ -445,7 +448,10 @@ cecho("Collecting dependencies:", GREEN)
ADDITIONAL_ARGS=[]
BOOST_ADDRESS_MODEL=[]
if TARGET_ARCH == "i686" and run([uname, "-m"]).strip() == "x86_64":
ADDITIONAL_ARGS=["-m32", "-arch i386"]
if get_os() == "Darwin":
ADDITIONAL_ARGS=["-m32", "-arch i386"]
else:
ADDITIONAL_ARGS=["-m32"]
BOOST_ADDRESS_MODEL=["architecture=x86", "address-model=32"]
if get_os() == "Darwin":
@@ -484,25 +490,8 @@ os.environ["CFLAGS"] = CFLAGS
os.environ["LDFLAGS"] = LDFLAGS
# Some dependencies need a more recent CMake version than most distros provide
build_dependency(name="cmake-%s" % (CMAKE_VERSION,), mode="autoconf", build_tool_args=[], download_url="https://cmake.org/files/v%s" % (CMAKE_VERSION_2,), download_name="cmake-%s.tar.gz" % (CMAKE_VERSION,))
# Extract compiler flags from CMake to harmonize settings with other autoconf dependencies
CMAKE_FLAG_EXTRACT_DIR="ifcopenshell_cmake_test_%s" % (time.time(),)
# was sp.check_output([bash, "-c", "cat /dev/urandom | env LC_CTYPE=C tr -dc 'a-zA-Z0-9' | head -c 32"]), in bash script, unclear what the exact required format is and whether it's needed
if os.path.exists(CMAKE_FLAG_EXTRACT_DIR):
shutil.rmtree(CMAKE_FLAG_EXTRACT_DIR)
os.makedirs(CMAKE_FLAG_EXTRACT_DIR)
BUILD_CFG_UPPER=BUILD_CFG.upper()
for FL in ["C", "CXX"]:
run([bash, "-c", """echo "
message(\"\${CMAKE_%s_FLAGS_%s}\")
" > CMakeLists.txt""" % (FL, BUILD_CFG_UPPER)], cwd=CMAKE_FLAG_EXTRACT_DIR)
FL="%sFLAGS" % (FL,)
FLM="%sFLAGS_MINIMAL" % (FL,)
# @TODO: bash code unclear
# exec("%sFLAGS=%s" % (FL, sp.check_output([os.path.join(DEPS_DIR, "install", "cmake-%s" % (CMAKE_VERSION,), "bin", "cmake"), "."
# declare ${FL}FLAGS_MINIMAL="`$DEPS_DIR/install/cmake-$CMAKE_VERSION/bin/cmake . 2>&1 >/dev/null` ${!FLM}"
shutil.rmtree(CMAKE_FLAG_EXTRACT_DIR)
# @tfk: this is no longer needed
# build_dependency(name="cmake-%s" % (CMAKE_VERSION,), mode="autoconf", build_tool_args=[], download_url="https://cmake.org/files/v%s" % (CMAKE_VERSION_2,), download_name="cmake-%s.tar.gz" % (CMAKE_VERSION,))
if "json" in targets:
json_url = "https://github.com/nlohmann/json/releases/download/{JSON_VERSION}/json.hpp".format(**locals())
@@ -527,7 +516,7 @@ if "swig" in targets:
build_dependency(
name="swig",
mode="autoconf",
build_tool_args=["--with-pcre-prefix={DEPS_DIR}/install/pcre-{PCRE_VERSION}".format(**locals())],
build_tool_args=["--disable-ccache", "--with-pcre-prefix={DEPS_DIR}/install/pcre-{PCRE_VERSION}".format(**locals())],
download_url="https://github.com/swig/swig.git",
download_name="swig",
download_tool=download_tool_git,
@@ -598,7 +587,7 @@ if "OpenCOLLADA" in targets:
download_url="https://github.com/KhronosGroup/OpenCOLLADA.git",
download_name="OpenCOLLADA",
download_tool=download_tool_git,
patch="./patches/opencollada/pr622.patch",
patch="./patches/opencollada/pr622_and_disable_subdirs.patch",
revision=OPENCOLLADA_VERSION
)
@@ -662,7 +651,7 @@ if "boost" in targets:
list(map(str_concat("cxxflags"), CXXFLAGS.strip().split(' '))) + \
list(map(str_concat("linkflags"), LDFLAGS.strip().split(' '))) + \
["stage", "-s", "NO_BZIP2=1"],
download_url="http://downloads.sourceforge.net/project/boost/boost/{BOOST_VERSION}/".format(**locals()),
download_url=BOOST_LOCATION,
download_name="boost_{BOOST_VERSION_UNDERSCORE}.tar.bz2".format(**locals())
)
@@ -0,0 +1,29 @@
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 95abbe21..293c951c 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -284,10 +284,10 @@ add_subdirectory(COLLADASaxFrameworkLoader)
add_subdirectory(COLLADAStreamWriter)
# building COLLADAValidator app
-add_subdirectory(COLLADAValidator)
+# add_subdirectory(COLLADAValidator)
# DAE validator app
-add_subdirectory(DAEValidator)
+# add_subdirectory(DAEValidator)
# Library export
install(EXPORT LibraryExport DESTINATION ${OPENCOLLADA_INST_CMAKECONFIG} FILE OpenCOLLADATargets.cmake)
diff --git a/GeneratedSaxParser/src/GeneratedSaxParserUtils.cpp b/GeneratedSaxParser/src/GeneratedSaxParserUtils.cpp
index 1f9a3eef..dd6f5c59 100644
--- a/GeneratedSaxParser/src/GeneratedSaxParserUtils.cpp
+++ b/GeneratedSaxParser/src/GeneratedSaxParserUtils.cpp
@@ -10,6 +10,7 @@
#include "GeneratedSaxParserUtils.h"
#include <math.h>
+#include <cmath>
#include <memory>
#include <string.h>
#include <limits>
+101 -14
View File
@@ -9,6 +9,7 @@ from datetime import datetime
from xml.dom import minidom
from xmlschema import XMLSchema
from contextlib import contextmanager
from shutil import copyfile
cwd = os.path.dirname(os.path.realpath(__file__))
@@ -206,6 +207,8 @@ class BcfXml:
self.document = minidom.Document()
root = self._create_element(self.document, "Markup")
self.write_header(topic.header, root)
topic_el = self._create_element(
root,
"Topic",
@@ -216,10 +219,22 @@ class BcfXml:
},
)
for reference_link in topic.reference_links:
self._create_element(topic_el, "ReferenceLink", text=reference_link)
text_map = {
"Title": topic.title,
"Priority": topic.priority,
"Index": topic.index,
}
for key, value in text_map.items():
if value:
self._create_element(topic_el, key, text=value)
for label in topic.labels:
self._create_element(topic_el, "Labels", text=label)
text_map = {
"CreationDate": topic.creation_date,
"CreationAuthor": topic.creation_author,
"ModifiedDate": topic.modified_date,
@@ -233,10 +248,6 @@ class BcfXml:
if value:
self._create_element(topic_el, key, text=value)
for reference_link in topic.reference_links:
self._create_element(topic_el, "ReferenceLink", text=reference_link)
for label in topic.labels:
self._create_element(topic_el, "Labels", text=label)
if topic.bim_snippet:
bim_snippet = self._create_element(
topic_el,
@@ -254,7 +265,6 @@ class BcfXml:
for related_topic in topic.related_topics:
self._create_element(topic_el, "RelatedTopic", {"Guid": related_topic.guid})
self.write_header(topic.header, root)
self.write_comments(topic.comments, root)
self.write_viewpoints(topic.viewpoints, root, topic)
@@ -262,7 +272,7 @@ class BcfXml:
f.write(self.document.toprettyxml(encoding="utf-8"))
def write_header(self, header, root):
if not header:
if not header or not header.files:
return
header_el = self._create_element(root, "Header")
for f in header.files:
@@ -295,14 +305,15 @@ class BcfXml:
if comment.viewpoint:
self._create_element(comment_el, "Viewpoint", {"Guid": comment.viewpoint.guid})
def add_comment(self, comment=None):
def add_comment(self, topic, comment=None):
if comment is None:
comment = bcf.data.Comment()
if not comment.guid:
comment.guid = str(uuid.uuid4())
if not comment.comment:
comment.comment = "'Free software' is a matter of liberty, not price. To understand the concept, you should think of 'free' as in 'free speech,' not as in 'free beer'."
self.edit_comment(comment)
topic.comments[comment.guid] = comment
self.edit_comment(comment, topic)
def edit_comment(self, comment, topic):
if not comment.date:
@@ -334,7 +345,7 @@ class BcfXml:
def write_viewpoint(self, viewpoint, topic):
document = minidom.Document()
root = self._create_element(document, "VisualizationInfo")
root = self._create_element(document, "VisualizationInfo", {"Guid": viewpoint.guid})
self.write_viewpoint_components(viewpoint, root)
self.write_viewpoint_orthogonal_camera(viewpoint, root)
self.write_viewpoint_perspective_camera(viewpoint, root)
@@ -354,7 +365,7 @@ class BcfXml:
"ViewSetupHints",
{
"SpacesVisible": viewpoint.components.view_setup_hints.spaces_visible,
"SpaceBoundiresVisible": viewpoint.components.view_setup_hints.space_boundaries_visible,
"SpaceBoundariesVisible": viewpoint.components.view_setup_hints.space_boundaries_visible,
"OpeningsVisible": viewpoint.components.view_setup_hints.openings_visible,
},
)
@@ -429,9 +440,11 @@ class BcfXml:
return
for bitmap in viewpoint.bitmaps:
bitmap_el = self._create_element(parent, "Bitmap")
text_map = {"Bitmap": bitmap.bitmap_type, "Reference": bitmap.reference, "Height": bitmap.height}
text_map = {"Bitmap": bitmap.bitmap_type, "Reference": bitmap.reference}
for key, value in text_map.items():
self._create_element(bitmap_el, key, text=value)
location_el = self._create_element(bitmap_el, "Location")
self.write_vector(location_el, bitmap.location)
normal_el = self._create_element(bitmap_el, "Normal")
@@ -439,6 +452,8 @@ class BcfXml:
up_el = self._create_element(bitmap_el, "Up")
self.write_vector(up_el, bitmap.up)
self._create_element(bitmap_el, "Height", text=bitmap.height)
def write_vector(self, parent, from_obj):
self._create_element(parent, "X", text=from_obj.x)
self._create_element(parent, "Y", text=from_obj.y)
@@ -461,8 +476,11 @@ class BcfXml:
if viewpoint.snapshot:
topic_filepath = os.path.join(self.filepath, topic.guid)
filepath = os.path.join(topic_filepath, viewpoint.snapshot)
if not os.path.exists(filepath):
pass # TODO: handle uploading files
if not os.path.exists(filepath) or topic_filepath not in filepath:
filename = viewpoint.guid + "." + viewpoint.snapshot[-3:]
copyfile(viewpoint.snapshot, os.path.join(topic_filepath, filename))
viewpoint.snapshot = filename
topic.viewpoints[viewpoint.guid] = viewpoint
self.edit_topic(topic)
def delete_viewpoint(self, guid, topic):
@@ -486,6 +504,75 @@ class BcfXml:
del topic.viewpoints[guid]
self.edit_topic(topic)
def delete_file(self, topic, index):
if not topic.header:
return
f = topic.header.files.pop(index)
filepath = os.path.join(self.filepath, topic.guid, f.reference)
if not f.is_external and os.path.exists(filepath):
os.remove(filepath)
self.edit_topic(topic)
def delete_bim_snippet(self, topic):
if not topic.bim_snippet:
return
if topic.bim_snippet.reference and not topic.bim_snippet.is_external:
filepath = os.path.join(self.filepath, topic.guid, topic.bim_snippet.reference)
if os.path.exists(filepath):
os.remove(filepath)
topic.bim_snippet = None
self.edit_topic(topic)
def delete_document_reference(self, topic, index):
document_reference = topic.document_references[index]
if document_reference.referenced_document and not document_reference.is_external:
filepath = os.path.join(self.filepath, topic.guid, document_reference.referenced_document)
if os.path.exists(filepath):
os.remove(filepath)
del topic.document_references[index]
self.edit_topic(topic)
def add_document_reference(self, topic, document_reference):
if os.path.exists(document_reference.referenced_document):
topic_filepath = os.path.join(self.filepath, topic.guid)
filename = os.path.basename(document_reference.referenced_document)
copyfile(document_reference.referenced_document, os.path.join(topic_filepath, filename))
document_reference.referenced_document = filename
document_reference.is_external = False
else:
document_reference.is_external = True
if not document_reference.guid:
document_reference.guid = str(uuid.uuid4())
topic.document_references.append(document_reference)
self.edit_topic(topic)
def add_bim_snippet(self, topic, bim_snippet):
if topic.bim_snippet:
self.delete_bim_snippet(topic)
if os.path.exists(bim_snippet.reference):
topic_filepath = os.path.join(self.filepath, topic.guid)
filename = os.path.basename(bim_snippet.reference)
copyfile(bim_snippet.reference, os.path.join(topic_filepath, filename))
bim_snippet.reference = filename
bim_snippet.is_external = False
else:
bim_snippet.is_external = True
topic.bim_snippet = bim_snippet
self.edit_topic(topic)
def add_file(self, topic, header_file):
if os.path.exists(header_file.reference):
topic_filepath = os.path.join(self.filepath, topic.guid)
header_file.filename = os.path.basename(header_file.reference)
copyfile(header_file.reference, os.path.join(topic_filepath, header_file.filename))
header_file.reference = header_file.filename
header_file.is_external = False
header_file.date = datetime.utcnow().isoformat()
if not topic.header:
topic.header = bcf.data.Header()
topic.header.files.append(header_file)
self.edit_topic(topic)
def get_comments(self, guid):
comments = {}
data = self._read_xml(os.path.join(guid, "markup.bcf"), "markup.xsd")
@@ -622,7 +709,7 @@ class BcfXml:
for item in visinfo["Bitmap"]:
bitmap = bcf.data.Bitmap()
bitmap.reference = item["Reference"]
bitmap.bitmap_type = item["Bitmap"].lower()
bitmap.bitmap_type = item["Bitmap"].upper()
self.set_vector(bitmap.location, item["Location"])
self.set_vector(bitmap.normal, item["Normal"])
self.set_vector(bitmap.up, item["Up"])
+1 -1
View File
@@ -156,7 +156,7 @@ class Bitmap:
def __init__(self):
self.reference = "" # Only in BCF-XML
self.bitmap_data = None # Only in BCF-API
self.bitmap_type = "png" # Enum of png or jpg
self.bitmap_type = "PNG" # Enum of png or jpg
self.location = Point()
self.normal = Direction()
self.up = Direction()
+2 -2
View File
@@ -91,8 +91,8 @@
</xs:complexType>
<xs:simpleType name="FieldOfView">
<xs:restriction base="xs:double">
<xs:minInclusive value="45"/>
<xs:maxInclusive value="60"/>
<xs:minInclusive value="1"/>
<xs:maxInclusive value="170"/>
</xs:restriction>
</xs:simpleType>
<xs:complexType name="Components">
+11
View File
@@ -16,3 +16,14 @@ python3 ./bimtester.py -g
`$ pyinstaller --onefile --clean --icon=icon.ico --add-data "features:features" bimtester.py`
+ Windows:
`$ pyinstaller --onefile --clean --icon=icon.ico --add-data "features;features" bimtester.py`
### Translation files
+ the binary mo translation files are not part of the repo
+ they might be needed to be recreated
+ use the following commands on Linux
```
cd YourIfcopenshellRepository/src/ifcbimtester/bimtester/locale
pybabel compile -d .
```
@@ -0,0 +1,3 @@
# dummy file to be able to run the command to get a list of all steps
# the command has to be run in the parent directory of this one
# behave --steps-catalog
@@ -3,6 +3,10 @@ from behave.model import Scenario
def before_all(context):
userdata = context.config.userdata
context.ifcbasename = userdata["ifcbasename"]
context.localedir = userdata.get("localedir")
# do not break after a failed scenario
# https://community.osarch.org/discussion/comment/3328/#Comment_3328
continue_after_failed = userdata.getbool("runner.continue_after_failed_step", True)
Scenario.continue_after_failed_step = continue_after_failed
@@ -0,0 +1,97 @@
from behave import step
from attributes_eleclasses_methods import all_element_attribs_have_a_value
from attributes_eleclasses_methods import name_has_a_value
from attributes_eleclasses_methods import description_has_a_value
from attributes_eleclasses_methods import no_element_class_ele
from utils import assert_elements
from utils import IfcFile
from utils import switch_locale
@step("there are no {ifc_class} elements because {reason}")
def step_impl(context, ifc_class, reason):
switch_locale(context.localedir, "en")
no_element_class_ele(context, ifc_class, reason)
@step('all {ifc_class} elements class attributes have a value')
def step_impl(context, ifc_class):
switch_locale(context.localedir, "en")
all_element_attribs_have_a_value(context, ifc_class)
@step('all {ifc_class} elements have a name given')
def step_impl(context, ifc_class):
switch_locale(context.localedir, "en")
name_has_a_value(context, ifc_class)
@step('all {ifc_class} elements have a description given')
def step_impl(context, ifc_class):
switch_locale(context.localedir, "en")
description_has_a_value(context, ifc_class)
@step('all {ifc_class} elements have a name matching the pattern "{pattern}"')
def step_impl(context, ifc_class, pattern):
import re
elements = IfcFile.get().by_type(ifc_class)
for element in elements:
if not re.search(pattern, element.Name):
assert False
@step('there is an {ifc_class} element with a {attribute_name} attribute with a value of "{attribute_value}"')
def step_impl(context, ifc_class, attribute_name, attribute_value):
elements = IfcFile.get().by_type(ifc_class)
for element in elements:
if hasattr(element, attribute_name) and getattr(element, attribute_name) == attribute_value:
return
assert False
# ------------------------------------------------------------------------
# STEPS with Regular Expression Matcher ("re")
# ------------------------------------------------------------------------
use_step_matcher("re")
@step("all (?P<ifc_class>.*) elements have an? (?P<attribute>.*) attribute")
def step_impl(context, ifc_class, attribute):
elements = IfcFile.get().by_type(ifc_class)
for element in elements:
if not getattr(element, attribute):
assert False
@step('all (?P<ifc_class>.*) elements have an? (?P<attribute>.*) matching the pattern "(?P<pattern>.*)"')
def step_impl(context, ifc_class, attribute, pattern):
import re
elements = IfcFile.get().by_type(ifc_class)
for element in elements:
value = getattr(element, attribute)
print(f'Checking value "{value}" for {element}')
assert re.search(pattern, value)
@step('all (?P<ifc_class>.*) elements have an? (?P<attributes>.*) taken from the list in "(?P<list_file>.*)"')
def step_impl(context, ifc_class, attributes, list_file):
import csv
values = []
with open(list_file) as csvfile:
reader = csv.reader(csvfile)
for row in reader:
values.append(row)
elements = IfcFile.get().by_type(ifc_class)
for element in elements:
attribute_values = []
for attribute in attributes.split(","):
if not hasattr(element, attribute):
assert False, f"Failed at element {element.GlobalId}"
attribute_values.append(getattr(element, attribute))
if attribute_values not in values:
assert False, f"Failed at element {element.GlobalId}"
@@ -0,0 +1,134 @@
import gettext # noqa
from utils import assert_elements
from utils import IfcFile
def no_element_class_ele(context, ifc_class, reason):
context.falseelems = []
context.falseguids = []
elements = IfcFile.get().by_type(ifc_class)
for elem in elements:
context.falseelems.append(str(elem))
context.falseguids.append(elem.GlobalId)
context.elemcount = len(elements)
context.falsecount = len(context.falseelems)
if context.elemcount == 0 and context.falsecount == 0:
return # Test OK, thus we can not use the assert_elements method
elif context.falsecount == context.elemcount:
assert False, (
_("All {elemcount} elements in the file are {ifc_class}.")
.format(
elemcount=context.elemcount,
ifc_class=ifc_class
)
)
elif context.falsecount > 0 and fcontext.alsecount < context.elemcount:
assert False, (
_("{falsecount} of {elemcount} element are {ifc_class} elements: {falseelems}")
.format(
falsecount=context.falsecount,
elemcount=context.elemcount,
ifc_class=ifc_class,
falseelems=context.falseelems,
)
)
else:
assert False, _("Error in falsecount, something went wrong.")
def all_element_attribs_have_a_value(context, ifc_class):
from ifcopenshell.ifcopenshell_wrapper import schema_by_name
# schema = schema_by_name("IFC2X3")
schema = schema_by_name(IfcFile.get().schema)
class_attributes = []
for cl_attrib in schema.declaration_by_name(ifc_class).all_attributes():
class_attributes.append(cl_attrib.name())
# print(class_attributes)
context.falseelems = []
context.falseguids = []
context.falseprops = {}
elements = IfcFile.get().by_type(ifc_class)
for elem in elements:
failed_attribs = []
elem_failed = False
for cl_attrib in class_attributes:
attrib_value = getattr(elem, cl_attrib)
if not attrib_value:
elem_failed = True
failed_attribs.append(cl_attrib)
# print(attrib_value)
if elem_failed is True:
context.falseelems.append(str(elem))
context.falseguids.append(elem.GlobalId)
context.falseprops[elem.id()] = failed_attribs
context.elemcount = len(elements)
context.falsecount = len(context.falseelems)
assert_elements(
ifc_class,
context.elemcount,
context.falsecount,
context.falseelems,
message_all_falseelems=_("For all {elemcount} {ifc_class} elements at least one of these class attributes {parameter} has no value."),
message_some_falseelems=_("For the following {falsecount} out of {elemcount} {ifc_class} elements at least one of these class attributes {parameter} has no value: {falseelems}"),
message_no_elems=_("There are no {ifc_class} elements in the IFC file."),
parameter=failed_attribs
)
def name_has_a_value(context, ifc_class):
context.falseelems = []
context.falseguids = []
elements = IfcFile.get().by_type(ifc_class)
for elem in elements:
# print(elem.Name)
if not elem.Name:
context.falseelems.append(str(elem))
context.falseguids.append(elem.GlobalId)
context.elemcount = len(elements)
context.falsecount = len(context.falseelems)
assert_elements(
ifc_class,
context.elemcount,
context.falsecount,
context.falseelems,
message_all_falseelems=_("The name of all {elemcount} {elemcount} elements is not set."),
message_some_falseelems=_("The name of {falsecount} out of {elemcount} {ifc_class} elements is not set: {falseelems}"),
message_no_elems=_("There are no {ifc_class} elements in the IFC file."),
)
def description_has_a_value(context, ifc_class):
context.falseelems = []
context.falseguids = []
elements = IfcFile.get().by_type(ifc_class)
for elem in elements:
# print(elem.Description)
if not elem.Description:
context.falseelems.append(str(elem))
context.falseguids.append(elem.GlobalId)
context.elemcount = len(elements)
context.falsecount = len(context.falseelems)
assert_elements(
ifc_class,
context.elemcount,
context.falsecount,
context.falseelems,
message_all_falseelems=_("The description of all {elemcount} {elemcount} elements is not set."),
message_some_falseelems=_("The description of {falsecount} out of {elemcount} {ifc_class} elements is not set: {falseelems}"),
message_no_elems=_("There are no {ifc_class} elements in the IFC file."),
)
@@ -0,0 +1,45 @@
from behave import step
from attributes_psets_methods import all_eleclass_elements_have_prop_in_pset
from utils import assert_elements
from utils import IfcFile
from utils import switch_locale
@step("all {ifc_class} elements have an {aproperty} property in the {pset} pset")
def step_impl(context, ifc_class, aproperty, pset):
switch_locale(context.localedir, "en")
all_eleclass_elements_have_prop_in_pset(context, ifc_class, aproperty, pset)
# ------------------------------------------------------------------------
# STEPS with Regular Expression Matcher ("re")
# ------------------------------------------------------------------------
use_step_matcher("re")
@step("all (?P<ifc_class>.*) elements have an? (?P<property_path>.*\..*) property")
def step_impl(context, ifc_class, property_path):
pset_name, property_name = property_path.split(".")
elements = IfcFile.get().by_type(ifc_class)
for element in elements:
if not IfcFile.get_property(element, pset_name, property_name):
assert False
@step(
'all (?P<ifc_class>.*) elements have an? (?P<property_path>.*\..*) property value matching the pattern "(?P<pattern>.*)"'
)
def step_impl(context, ifc_class, property_path, pattern):
import re
pset_name, property_name = property_path.split(".")
elements = IfcFile.get().by_type(ifc_class)
for element in elements:
prop = IfcFile.get_property(element, pset_name, property_name)
if not prop:
assert False
# For now, we only check single values
if prop.is_a("IfcPropertySingleValue"):
if not (prop.NominalValue and re.search(pattern, prop.NominalValue.wrappedValue)):
assert False
@@ -0,0 +1,34 @@
import gettext # noqa
from utils import assert_elements
from utils import IfcFile
def all_eleclass_elements_have_prop_in_pset(context, ifc_class, aproperty, pset):
context.falseelems = []
context.falseguids = []
context.falseprops = {}
from ifcopenshell.util.element import get_psets
elements = IfcFile.get().by_type(ifc_class)
for elem in elements:
psets = get_psets(elem)
if not (pset in psets and aproperty in psets[pset]):
context.falseelems.append(str(elem))
context.falseguids.append(elem.GlobalId)
context.falseprops[elem.id()] = str(psets)
context.elemcount = len(elements)
context.falsecount = len(context.falseelems)
assert_elements(
ifc_class,
context.elemcount,
context.falsecount,
context.falseelems,
message_all_falseelems=_("All {elemcount} {ifc_class} elements are missing the property {parameter} in the pset."),
message_some_falseelems=_("The following {falsecount} of {elemcount} {ifc_class} elements are missing the property {parameter} in the pset: {falseelems}"),
message_no_elems=_("There are no {ifc_class} elements in the IFC file."),
parameter=aproperty
)
# the pset name is missing in the failing message, but it is in the step test name
@@ -0,0 +1,19 @@
from behave import step
from utils import IfcFile
@step("all {ifc_class} elements have a {qto_name}.{quantity_name} quantity")
def step_impl(context, ifc_class, qto_name, quantity_name):
elements = IfcFile.get().by_type(ifc_class)
for element in elements:
is_successful = False
if not element.IsDefinedBy:
assert False
for relationship in element.IsDefinedBy:
if relationship.RelatingPropertyDefinition.Name == qto_name:
for quantity in relationship.RelatingPropertyDefinition.Quantities:
if quantity.Name == quantity_name:
is_successful = True
if not is_successful:
assert False
@@ -1,6 +1,9 @@
from behave import step
from geometric_detail_methods import class_geometric_representation
from utils import assert_elements
from utils import IfcFile
from utils import switch_locale
@step("All elements must be under {number} polygons")
@@ -29,3 +32,9 @@ def step_impl(context, number):
for error in errors:
message += "Polygons: {} - {}\n".format(error[0], error[1])
assert False, message
@step("all {ifc_class} elements have an {representation_class} representation")
def step_impl(context, ifc_class, representation_class):
switch_locale(context.localedir, "en")
class_geometric_representation(context, ifc_class, representation_class)
@@ -0,0 +1,10 @@
from behave import step
from geometric_detail_methods import class_geometric_representation
from utils import switch_locale
@step("Alle {ifc_class} Bauteile müssen eine geometrische Repräsentation der Klasse {representation_class} verwenden")
def step_impl(context, ifc_class, representation_class):
switch_locale(context.localedir, "de")
class_geometric_representation(context, ifc_class, representation_class)
@@ -0,0 +1,54 @@
import gettext # noqa
from utils import assert_elements
from utils import IfcFile
def class_geometric_representation(context, ifc_class, representation_class):
def is_item_a_representation(item, representation):
if "/" in representation:
for cls in representation.split("/"):
if item.is_a(cls):
return True
elif item.is_a(representation):
return True
context.falseelems = []
context.falseguids = []
context.falseprops = {}
rep = None
elements = IfcFile.get().by_type(ifc_class)
for elem in elements:
if not elem.Representation:
continue
has_representation = False
for representation in elem.Representation.Representations:
for item in representation.Items:
if item.is_a("IfcMappedItem"):
# We only check one more level deep.
for item2 in item.MappingSource.MappedRepresentation.Items:
if is_item_a_representation(item2, representation_class):
has_representation = True
rep = item2
else:
if is_item_a_representation(item, representation_class):
has_representation = True
rep = item
if not has_representation:
context.falseelems.append(str(elem))
context.falseguids.append(elem.GlobalId)
context.falseprops[elem.id()] = str(rep)
context.elemcount = len(elements)
context.falsecount = len(context.falseelems)
assert_elements(
ifc_class,
context.elemcount,
context.falsecount,
context.falseelems,
message_all_falseelems=_("All {elemcount} {ifc_class} elements are not a {parameter} representation."),
message_some_falseelems=_("The following {falsecount} of {elemcount} {ifc_class} elements are not a {parameter} representation: {falseelems}"),
message_no_elems=_("There are no {ifc_class} elements in the IFC file."),
parameter=representation_class
)
@@ -1,6 +1,6 @@
import gettext
import os
from behave import step, given
import gettext # noqa
from behave import given
from behave import step
from ifcdata_methods import assert_schema
from utils import IfcFile
@@ -67,3 +67,9 @@ def get_subcontext(identifier, type, target_view):
assert False, "The subcontext with identifier {}, type {}, and target view {} could not be found".format(
identifier, type, target_view
)
@step('the project has a {attribute_name} attribute with a value of "{attribute_value}"')
def step_impl(context, attribute_name, attribute_value):
project = IfcFile.get().by_type("IfcProject")[0]
assert getattr(project, attribute_name) == attribute_value
@@ -0,0 +1,10 @@
from behave import step
from utils import IfcFile
@step("all buildings have an address")
def step_impl(context):
for building in IfcFile.get().by_type("IfcBuilding"):
if not building.BuildingAddress:
assert False, f'The building "{building.Name}" has no address.'
@@ -1,160 +0,0 @@
from behave import step
from utils import IfcFile
@step("there are no {ifc_class} elements because {reason}")
def step_impl(context, ifc_class, reason):
assert len(IfcFile.get().by_type(ifc_class)) == 0
@step('all {ifc_class} elements have a name matching the pattern "{pattern}"')
def step_impl(context, ifc_class, pattern):
import re
elements = IfcFile.get().by_type(ifc_class)
for element in elements:
if not re.search(pattern, element.Name):
assert False
@step("all {ifc_class} elements have an {representation_class} representation")
def step_impl(context, ifc_class, representation_class):
def is_item_a_representation(item, representation):
if "/" in representation:
for cls in representation.split("/"):
if item.is_a(cls):
return True
elif item.is_a(representation):
return True
elements = IfcFile.get().by_type(ifc_class)
for element in elements:
if not element.Representation:
continue
has_representation = False
for representation in element.Representation.Representations:
for item in representation.Items:
if item.is_a("IfcMappedItem"):
# We only check one more level deep.
for item2 in item.MappingSource.MappedRepresentation.Items:
if is_item_a_representation(item2, representation_class):
has_representation = True
else:
if is_item_a_representation(item, representation_class):
has_representation = True
if not has_representation:
assert False
use_step_matcher("re")
@step("all (?P<ifc_class>.*) elements have an? (?P<attribute>.*) attribute")
def step_impl(context, ifc_class, attribute):
elements = IfcFile.get().by_type(ifc_class)
for element in elements:
if not getattr(element, attribute):
assert False
@step("all (?P<ifc_class>.*) elements have an? (?P<property_path>.*\..*) property")
def step_impl(context, ifc_class, property_path):
pset_name, property_name = property_path.split(".")
elements = IfcFile.get().by_type(ifc_class)
for element in elements:
if not IfcFile.get_property(element, pset_name, property_name):
assert False
@step(
'all (?P<ifc_class>.*) elements have an? (?P<property_path>.*\..*) property value matching the pattern "(?P<pattern>.*)"'
)
def step_impl(context, ifc_class, property_path, pattern):
import re
pset_name, property_name = property_path.split(".")
elements = IfcFile.get().by_type(ifc_class)
for element in elements:
prop = IfcFile.get_property(element, pset_name, property_name)
if not prop:
assert False
# For now, we only check single values
if prop.is_a("IfcPropertySingleValue"):
if not (prop.NominalValue and re.search(pattern, prop.NominalValue.wrappedValue)):
assert False
@step('all (?P<ifc_class>.*) elements have an? (?P<attribute>.*) matching the pattern "(?P<pattern>.*)"')
def step_impl(context, ifc_class, attribute, pattern):
import re
elements = IfcFile.get().by_type(ifc_class)
for element in elements:
value = getattr(element, attribute)
print(f'Checking value "{value}" for {element}')
assert re.search(pattern, value)
@step('all (?P<ifc_class>.*) elements have an? (?P<attributes>.*) taken from the list in "(?P<list_file>.*)"')
def step_impl(context, ifc_class, attributes, list_file):
import csv
values = []
with open(list_file) as csvfile:
reader = csv.reader(csvfile)
for row in reader:
values.append(row)
elements = IfcFile.get().by_type(ifc_class)
for element in elements:
attribute_values = []
for attribute in attributes.split(","):
if not hasattr(element, attribute):
assert False, f"Failed at element {element.GlobalId}"
attribute_values.append(getattr(element, attribute))
if attribute_values not in values:
assert False, f"Failed at element {element.GlobalId}"
use_step_matcher("parse")
@step("all {ifc_class} elements have a {qto_name}.{quantity_name} quantity")
def step_impl(context, ifc_class, qto_name, quantity_name):
elements = IfcFile.get().by_type(ifc_class)
for element in elements:
is_successful = False
if not element.IsDefinedBy:
assert False
for relationship in element.IsDefinedBy:
if relationship.RelatingPropertyDefinition.Name == qto_name:
for quantity in relationship.RelatingPropertyDefinition.Quantities:
if quantity.Name == quantity_name:
is_successful = True
if not is_successful:
assert False
use_step_matcher("parse")
@step('the project has a {attribute_name} attribute with a value of "{attribute_value}"')
def step_impl(context, attribute_name, attribute_value):
project = IfcFile.get().by_type("IfcProject")[0]
assert getattr(project, attribute_name) == attribute_value
@step('there is an {ifc_class} element with a {attribute_name} attribute with a value of "{attribute_value}"')
def step_impl(context, ifc_class, attribute_name, attribute_value):
elements = IfcFile.get().by_type(ifc_class)
for element in elements:
if hasattr(element, attribute_name) and getattr(element, attribute_name) == attribute_value:
return
assert False
@step("all buildings have an address")
def step_impl(context):
for building in IfcFile.get().by_type("IfcBuilding"):
if not building.BuildingAddress:
assert False, f'The building "{building.Name}" has no address.'
@@ -1,3 +1,4 @@
import gettext
import ifcopenshell
import ifcopenshell.express
import ifcopenshell.util
@@ -99,9 +100,66 @@ def assert_pset(element, pset_name, prop_name=None, value=None):
)
def assert_elements(
ifc_class,
elemcount,
falsecount,
falseelems,
message_all_falseelems,
message_some_falseelems,
message_no_elems,
parameter=None
):
if elemcount > 0 and falsecount == 0:
return # Test OK
elif elemcount == 0:
assert False, (
message_no_elems.format(
ifc_class=ifc_class
)
)
elif falsecount == elemcount:
if parameter is None:
assert False, (
message_all_falseelems.format(
elemcount=elemcount,
ifc_class=ifc_class
)
)
else:
assert False, (
message_all_falseelems.format(
elemcount=elemcount,
ifc_class=ifc_class,
parameter=parameter
)
)
elif falsecount > 0 and falsecount < elemcount:
if parameter is None:
assert False, (
message_some_falseelems.format(
falsecount=falsecount,
elemcount=elemcount,
ifc_class=ifc_class,
falseelems=falseelems,
)
)
else:
assert False, (
message_some_falseelems.format(
falsecount=falsecount,
elemcount=elemcount,
ifc_class=ifc_class,
falseelems=falseelems,
parameter=parameter
)
)
else:
assert False, _("Error in falsecount, something went wrong.")
def switch_locale(locale_dir, locale_id="en"):
from gettext import translation
newlang = translation(
newlang = gettext.translation(
"messages",
localedir=locale_dir,
languages=[locale_id]
@@ -1,9 +0,0 @@
{
"tr_lang": "de",
"tr_success": "Bestanden",
"tr_failure": "Durchgefallen",
"tr_tests_passed": "Erfolgreiche Tests",
"tr_duration": "Dauer",
"tr_auditing": "OpenBIM auditing ist eine Funktionalität von",
"tr_and": "und"
}
@@ -1,9 +0,0 @@
{
"tr_lang": "en",
"tr_success": "Success",
"tr_failure": "Failure",
"tr_tests_passed": "Tests passed",
"tr_duration": "Duration",
"tr_auditing": "OpenBIM auditing is a feature of",
"tr_and": "and"
}
@@ -1,9 +0,0 @@
{
"tr_lang": "fr",
"tr_success": "Succès",
"tr_failure": "Échec",
"tr_tests_passed": "Tests réussis",
"tr_duration": "Durée",
"tr_auditing": "L'audit OpenBIM auditing est une fonctionnalité de",
"tr_and": "et"
}
@@ -7,8 +7,8 @@ msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2020-12-17 15:14+0100\n"
"PO-Revision-Date: 2020-12-18 06:11+0100\n"
"POT-Creation-Date: 2020-12-23 11:03+0100\n"
"PO-Revision-Date: 2020-12-23 11:20+0100\n"
"Last-Translator: \n"
"Language: de\n"
"Language-Team: de <LL@li.org>\n"
@@ -19,6 +19,165 @@ msgstr ""
"Generated-By: Babel 2.6.0\n"
"X-Generator: Poedit 2.2.1\n"
#: reports.py:153 reports.py:160
msgid "OpenBIM auditing is a feature of"
msgstr "OpenBIM auditing ist eine Funktionalität von"
#: reports.py:155
msgid "en"
msgstr "de"
#: reports.py:156
msgid "Success"
msgstr "Bestanden"
#: reports.py:157
msgid "Failure"
msgstr "Durchgefallen"
#: reports.py:158
msgid "Tests passed"
msgstr "Erfolgreiche Tests"
#: reports.py:159
msgid "Duration"
msgstr "Dauer"
#: reports.py:161
msgid "and"
msgstr "und"
#: features/steps/attributes_eleclasses_methods.py:24
msgid "All {elemcount} elements in the file are {ifc_class}."
msgstr ""
#: features/steps/attributes_eleclasses_methods.py:32
msgid "{falsecount} of {elemcount} element are {ifc_class} elements: {falseelems}"
msgstr ""
#: features/steps/attributes_eleclasses_methods.py:41 features/steps/utils.py:158
msgid "Error in falsecount, something went wrong."
msgstr "Fehler in falsecount, es ist etwas falsch gelaufen."
#: features/steps/attributes_eleclasses_methods.py:80
msgid ""
"For all {elemcount} {ifc_class} elements at least one of these class attributes "
"{parameter} has no value."
msgstr ""
#: features/steps/attributes_eleclasses_methods.py:81
msgid ""
"For the following {falsecount} out of {elemcount} {ifc_class} elements at least "
"one of these class attributes {parameter} has no value: {falseelems}"
msgstr ""
#: features/steps/attributes_eleclasses_methods.py:82
#: features/steps/attributes_eleclasses_methods.py:108
#: features/steps/attributes_eleclasses_methods.py:133
#: features/steps/attributes_psets_methods.py:31
#: features/steps/geometric_detail_methods.py:52
#: features/steps/geometric_detail_methods.py:127
msgid "There are no {ifc_class} elements in the IFC file."
msgstr "Es sind keine {ifc_class} Bauteile in der IFC-Datei."
#: features/steps/attributes_eleclasses_methods.py:106
msgid "The name of all {elemcount} {elemcount} elements is not set."
msgstr ""
#: features/steps/attributes_eleclasses_methods.py:107
msgid ""
"The name of {falsecount} out of {elemcount} {ifc_class} elements is not set: "
"{falseelems}"
msgstr ""
#: features/steps/attributes_eleclasses_methods.py:131
msgid "The description of all {elemcount} {elemcount} elements is not set."
msgstr ""
#: features/steps/attributes_eleclasses_methods.py:132
msgid ""
"The description of {falsecount} out of {elemcount} {ifc_class} elements is not "
"set: {falseelems}"
msgstr ""
#: features/steps/attributes_psets_methods.py:29
msgid ""
"All {elemcount} {ifc_class} elements are missing the property {parameter} in "
"the pset."
msgstr ""
#: features/steps/attributes_psets_methods.py:30
msgid ""
"The following {falsecount} of {elemcount} {ifc_class} elements are missing the "
"property {parameter} in the pset: {falseelems}"
msgstr ""
#: features/steps/geometric_detail_methods.py:50
msgid "All {elemcount} {ifc_class} elements are not a {parameter} representation."
msgstr ""
"Alle {elemcount} {ifc_class} Bauteile haben keine geometrische Repräsentation "
"der Klasse {parameter}."
#: features/steps/geometric_detail_methods.py:51
msgid ""
"The following {falsecount} of {elemcount} {ifc_class} elements are not a "
"{parameter} representation: {falseelems}"
msgstr ""
"Die Anzahl Bauteile {falsecount} von allen {elemcount} {ifc_class} Bauteilen "
"haben keine geometrische Repräsentation der Klasse {parameter}: {falseelems}"
#: features/steps/geometric_detail_methods.py:125
msgid "The geometry of all {elemcount} {ifc_class} elements have errors."
msgstr ""
"Die geometrischen Repräsentationen von allen {elemcount} {ifc_class} Bauteilen "
"haben Fehler."
#: features/steps/geometric_detail_methods.py:126
msgid ""
"The geometry of {falsecount} out of all {elemcount} {ifc_class} elements have "
"errors: {falseelems}"
msgstr ""
"Die geometrischen Repräsentationen von {falsecount} von allen {elemcount} "
"{ifc_class} Bauteilen haben Fehler: {falseelems}"
#: features/steps/ifcdata_methods.py:7
msgid "We expected a schema of {} but instead got {}"
msgstr "Wir haben das Schema {} erwartet, aber die Daten nutzen das Schema {}"
#~ msgid "The geometry of all {} {} elements have errors."
#~ msgstr ""
#~ "Die geometrischen Repräsentationen von allen {} {} Bauteilen haben Fehler."
#~ msgid "The geometry of {} out of all {} {} elements have errors: {}"
#~ msgstr ""
#~ "Die geometrischen Repräsentationen von {} von allen {} {} Bauteilen haben "
#~ "Fehler: {}"
#~ msgid "There are no {} elements in the IFC file."
#~ msgstr "Es sind keine {} Bauteile in der IFC-Datei."
#~ msgid "All {} {} elements are not a IfcFacetedBrep representation."
#~ msgstr ""
#~ "Alle {} {} Bauteile haben keine geometrische Repräsentation der Klasse "
#~ "IfcFacetedBrep."
#~ msgid ""
#~ "The following {} of {} {} elements are not a IfcFacetedBrep representation: "
#~ "{}"
#~ msgstr ""
#~ "Die Anzahl Bauteile {} von allen {} {} Bauteilen haben keine geometrische "
#~ "Repräsentation der Klasse IfcFacetedBrep: {}"
#~ msgid ""
#~ "All {elemcount} {ifc_class} elements are not a IfcFacetedBrep representation."
#~ msgstr ""
#~ "Alle {elemcount} {ifc_class} Bauteile haben keine geometrische "
#~ "Repräsentation der Klasse IfcFacetedBrep."
#~ msgid ""
#~ "The following {falsecount} of {elemcount} {ifc_class} elements are not a "
#~ "IfcFacetedBrep representation: {falseelems}"
#~ msgstr ""
#~ "Die Anzahl Bauteile {falsecount} von allen {elemcount} {ifc_class} Bauteilen "
#~ "haben keine geometrische Repräsentation der Klasse IfcFacetedBrep: "
#~ "{falseelems}"
@@ -7,18 +7,162 @@ msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2020-12-17 15:14+0100\n"
"PO-Revision-Date: 2020-12-18 06:12+0100\n"
"POT-Creation-Date: 2020-12-23 11:03+0100\n"
"PO-Revision-Date: 2020-12-18 11:41+0100\n"
"Last-Translator: \n"
"Language: en\n"
"Language-Team: en <LL@li.org>\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.6.0\n"
"X-Generator: Poedit 2.2.1\n"
#: reports.py:153 reports.py:160
msgid "OpenBIM auditing is a feature of"
msgstr ""
#: reports.py:155
msgid "en"
msgstr ""
#: reports.py:156
msgid "Success"
msgstr ""
#: reports.py:157
msgid "Failure"
msgstr ""
#: reports.py:158
msgid "Tests passed"
msgstr ""
#: reports.py:159
msgid "Duration"
msgstr ""
#: reports.py:161
msgid "and"
msgstr ""
#: features/steps/attributes_eleclasses_methods.py:24
msgid "All {elemcount} elements in the file are {ifc_class}."
msgstr ""
#: features/steps/attributes_eleclasses_methods.py:32
msgid "{falsecount} of {elemcount} element are {ifc_class} elements: {falseelems}"
msgstr ""
#: features/steps/attributes_eleclasses_methods.py:41
#: features/steps/utils.py:158
msgid "Error in falsecount, something went wrong."
msgstr ""
#: features/steps/attributes_eleclasses_methods.py:80
msgid ""
"For all {elemcount} {ifc_class} elements at least one of these class "
"attributes {parameter} has no value."
msgstr ""
#: features/steps/attributes_eleclasses_methods.py:81
msgid ""
"For the following {falsecount} out of {elemcount} {ifc_class} elements at"
" least one of these class attributes {parameter} has no value: "
"{falseelems}"
msgstr ""
#: features/steps/attributes_eleclasses_methods.py:82
#: features/steps/attributes_eleclasses_methods.py:108
#: features/steps/attributes_eleclasses_methods.py:133
#: features/steps/attributes_psets_methods.py:31
#: features/steps/geometric_detail_methods.py:52
#: features/steps/geometric_detail_methods.py:127
msgid "There are no {ifc_class} elements in the IFC file."
msgstr ""
#: features/steps/attributes_eleclasses_methods.py:106
msgid "The name of all {elemcount} {elemcount} elements is not set."
msgstr ""
#: features/steps/attributes_eleclasses_methods.py:107
msgid ""
"The name of {falsecount} out of {elemcount} {ifc_class} elements is not "
"set: {falseelems}"
msgstr ""
#: features/steps/attributes_eleclasses_methods.py:131
msgid "The description of all {elemcount} {elemcount} elements is not set."
msgstr ""
#: features/steps/attributes_eleclasses_methods.py:132
msgid ""
"The description of {falsecount} out of {elemcount} {ifc_class} elements "
"is not set: {falseelems}"
msgstr ""
#: features/steps/attributes_psets_methods.py:29
msgid ""
"All {elemcount} {ifc_class} elements are missing the property {parameter}"
" in the pset."
msgstr ""
#: features/steps/attributes_psets_methods.py:30
msgid ""
"The following {falsecount} of {elemcount} {ifc_class} elements are "
"missing the property {parameter} in the pset: {falseelems}"
msgstr ""
#: features/steps/geometric_detail_methods.py:50
msgid "All {elemcount} {ifc_class} elements are not a {parameter} representation."
msgstr ""
#: features/steps/geometric_detail_methods.py:51
msgid ""
"The following {falsecount} of {elemcount} {ifc_class} elements are not a "
"{parameter} representation: {falseelems}"
msgstr ""
#: features/steps/geometric_detail_methods.py:125
msgid "The geometry of all {elemcount} {ifc_class} elements have errors."
msgstr ""
#: features/steps/geometric_detail_methods.py:126
msgid ""
"The geometry of {falsecount} out of all {elemcount} {ifc_class} elements "
"have errors: {falseelems}"
msgstr ""
#: features/steps/ifcdata_methods.py:7
msgid "We expected a schema of {} but instead got {}"
msgstr "We expected a schema of {} but instead got {}"
msgstr ""
#~ msgid "The geometry of all {} {} elements have errors."
#~ msgstr ""
#~ msgid "The geometry of {} out of all {} {} elements have errors: {}"
#~ msgstr ""
#~ msgid "There are no {} elements in the IFC file."
#~ msgstr ""
#~ msgid "All {} {} elements are not a IfcFacetedBrep representation."
#~ msgstr ""
#~ msgid ""
#~ "The following {} of {} {} elements"
#~ " are not a IfcFacetedBrep representation:"
#~ " {}"
#~ msgstr ""
#~ msgid ""
#~ "All {elemcount} {ifc_class} elements are "
#~ "not a IfcFacetedBrep representation."
#~ msgstr ""
#~ msgid ""
#~ "The following {falsecount} of {elemcount} "
#~ "{ifc_class} elements are not a "
#~ "IfcFacetedBrep representation: {falseelems}"
#~ msgstr ""
@@ -7,8 +7,8 @@ msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2020-12-17 15:14+0100\n"
"PO-Revision-Date: 2020-12-18 06:12+0100\n"
"POT-Creation-Date: 2020-12-23 11:03+0100\n"
"PO-Revision-Date: 2020-12-23 11:21+0100\n"
"Last-Translator: \n"
"Language: fr\n"
"Language-Team: fr <LL@li.org>\n"
@@ -19,6 +19,119 @@ msgstr ""
"Generated-By: Babel 2.6.0\n"
"X-Generator: Poedit 2.2.1\n"
#: reports.py:153 reports.py:160
msgid "OpenBIM auditing is a feature of"
msgstr "L'audit OpenBIM auditing est une fonctionnalité de"
#: reports.py:155
msgid "en"
msgstr "fr"
#: reports.py:156
msgid "Success"
msgstr "Succès"
#: reports.py:157
msgid "Failure"
msgstr "Échec"
#: reports.py:158
msgid "Tests passed"
msgstr "Tests réussis"
#: reports.py:159
msgid "Duration"
msgstr "Durée"
#: reports.py:161
msgid "and"
msgstr "et"
#: features/steps/attributes_eleclasses_methods.py:24
msgid "All {elemcount} elements in the file are {ifc_class}."
msgstr ""
#: features/steps/attributes_eleclasses_methods.py:32
msgid "{falsecount} of {elemcount} element are {ifc_class} elements: {falseelems}"
msgstr ""
#: features/steps/attributes_eleclasses_methods.py:41 features/steps/utils.py:158
msgid "Error in falsecount, something went wrong."
msgstr ""
#: features/steps/attributes_eleclasses_methods.py:80
msgid ""
"For all {elemcount} {ifc_class} elements at least one of these class attributes "
"{parameter} has no value."
msgstr ""
#: features/steps/attributes_eleclasses_methods.py:81
msgid ""
"For the following {falsecount} out of {elemcount} {ifc_class} elements at least "
"one of these class attributes {parameter} has no value: {falseelems}"
msgstr ""
#: features/steps/attributes_eleclasses_methods.py:82
#: features/steps/attributes_eleclasses_methods.py:108
#: features/steps/attributes_eleclasses_methods.py:133
#: features/steps/attributes_psets_methods.py:31
#: features/steps/geometric_detail_methods.py:52
#: features/steps/geometric_detail_methods.py:127
msgid "There are no {ifc_class} elements in the IFC file."
msgstr ""
#: features/steps/attributes_eleclasses_methods.py:106
msgid "The name of all {elemcount} {elemcount} elements is not set."
msgstr ""
#: features/steps/attributes_eleclasses_methods.py:107
msgid ""
"The name of {falsecount} out of {elemcount} {ifc_class} elements is not set: "
"{falseelems}"
msgstr ""
#: features/steps/attributes_eleclasses_methods.py:131
msgid "The description of all {elemcount} {elemcount} elements is not set."
msgstr ""
#: features/steps/attributes_eleclasses_methods.py:132
msgid ""
"The description of {falsecount} out of {elemcount} {ifc_class} elements is not "
"set: {falseelems}"
msgstr ""
#: features/steps/attributes_psets_methods.py:29
msgid ""
"All {elemcount} {ifc_class} elements are missing the property {parameter} in "
"the pset."
msgstr ""
#: features/steps/attributes_psets_methods.py:30
msgid ""
"The following {falsecount} of {elemcount} {ifc_class} elements are missing the "
"property {parameter} in the pset: {falseelems}"
msgstr ""
#: features/steps/geometric_detail_methods.py:50
msgid "All {elemcount} {ifc_class} elements are not a {parameter} representation."
msgstr ""
#: features/steps/geometric_detail_methods.py:51
msgid ""
"The following {falsecount} of {elemcount} {ifc_class} elements are not a "
"{parameter} representation: {falseelems}"
msgstr ""
#: features/steps/geometric_detail_methods.py:125
msgid "The geometry of all {elemcount} {ifc_class} elements have errors."
msgstr ""
#: features/steps/geometric_detail_methods.py:126
msgid ""
"The geometry of {falsecount} out of all {elemcount} {ifc_class} elements have "
"errors: {falseelems}"
msgstr ""
#: features/steps/ifcdata_methods.py:7
msgid "We expected a schema of {} but instead got {}"
msgstr ""
+116 -1
View File
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2020-12-17 15:14+0100\n"
"POT-Creation-Date: 2020-12-23 11:03+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -17,6 +17,121 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.6.0\n"
#: reports.py:153 reports.py:160
msgid "OpenBIM auditing is a feature of"
msgstr ""
#: reports.py:155
msgid "en"
msgstr ""
#: reports.py:156
msgid "Success"
msgstr ""
#: reports.py:157
msgid "Failure"
msgstr ""
#: reports.py:158
msgid "Tests passed"
msgstr ""
#: reports.py:159
msgid "Duration"
msgstr ""
#: reports.py:161
msgid "and"
msgstr ""
#: features/steps/attributes_eleclasses_methods.py:24
msgid "All {elemcount} elements in the file are {ifc_class}."
msgstr ""
#: features/steps/attributes_eleclasses_methods.py:32
msgid "{falsecount} of {elemcount} element are {ifc_class} elements: {falseelems}"
msgstr ""
#: features/steps/attributes_eleclasses_methods.py:41
#: features/steps/utils.py:158
msgid "Error in falsecount, something went wrong."
msgstr ""
#: features/steps/attributes_eleclasses_methods.py:80
msgid ""
"For all {elemcount} {ifc_class} elements at least one of these class "
"attributes {parameter} has no value."
msgstr ""
#: features/steps/attributes_eleclasses_methods.py:81
msgid ""
"For the following {falsecount} out of {elemcount} {ifc_class} elements at"
" least one of these class attributes {parameter} has no value: "
"{falseelems}"
msgstr ""
#: features/steps/attributes_eleclasses_methods.py:82
#: features/steps/attributes_eleclasses_methods.py:108
#: features/steps/attributes_eleclasses_methods.py:133
#: features/steps/attributes_psets_methods.py:31
#: features/steps/geometric_detail_methods.py:52
#: features/steps/geometric_detail_methods.py:127
msgid "There are no {ifc_class} elements in the IFC file."
msgstr ""
#: features/steps/attributes_eleclasses_methods.py:106
msgid "The name of all {elemcount} {elemcount} elements is not set."
msgstr ""
#: features/steps/attributes_eleclasses_methods.py:107
msgid ""
"The name of {falsecount} out of {elemcount} {ifc_class} elements is not "
"set: {falseelems}"
msgstr ""
#: features/steps/attributes_eleclasses_methods.py:131
msgid "The description of all {elemcount} {elemcount} elements is not set."
msgstr ""
#: features/steps/attributes_eleclasses_methods.py:132
msgid ""
"The description of {falsecount} out of {elemcount} {ifc_class} elements "
"is not set: {falseelems}"
msgstr ""
#: features/steps/attributes_psets_methods.py:29
msgid ""
"All {elemcount} {ifc_class} elements are missing the property {parameter}"
" in the pset."
msgstr ""
#: features/steps/attributes_psets_methods.py:30
msgid ""
"The following {falsecount} of {elemcount} {ifc_class} elements are "
"missing the property {parameter} in the pset: {falseelems}"
msgstr ""
#: features/steps/geometric_detail_methods.py:50
msgid "All {elemcount} {ifc_class} elements are not a {parameter} representation."
msgstr ""
#: features/steps/geometric_detail_methods.py:51
msgid ""
"The following {falsecount} of {elemcount} {ifc_class} elements are not a "
"{parameter} representation: {falseelems}"
msgstr ""
#: features/steps/geometric_detail_methods.py:125
msgid "The geometry of all {elemcount} {ifc_class} elements have errors."
msgstr ""
#: features/steps/geometric_detail_methods.py:126
msgid ""
"The geometry of {falsecount} out of all {elemcount} {ifc_class} elements "
"have errors: {falseelems}"
msgstr ""
#: features/steps/ifcdata_methods.py:7
msgid "We expected a schema of {} but instead got {}"
msgstr ""
+39 -24
View File
@@ -1,16 +1,32 @@
import datetime
import gettext # noqa
import json
import os
import pystache
from .features.steps.utils import switch_locale
def generate_report(adir=".", use_report_folder=True, report_file_name="", html_template_file_path=""):
#print("# Generating HTML reports now.")
def generate_report(
adir=".",
use_report_folder=True,
report_file_name="",
html_template_file_path=""
):
# print("# Generating HTML reports now.")
# get locale path
localedir = os.path.join(
os.path.dirname(os.path.realpath(__file__)),
"locale"
)
# get html template path
report_template_path = os.path.join(
os.path.dirname(os.path.realpath(__file__)),
"features/"
"resources",
"reports"
)
if html_template_file_path:
@@ -24,9 +40,9 @@ def generate_report(adir=".", use_report_folder=True, report_file_name="", html_
return print("No report directory was found.")
if report_file_name:
report_path = os.path.join(report_dir, report_file_name)
report_path = os.path.join(report_dir, report_file_name)
else:
report_path = os.path.join(report_dir, "report.json")
report_path = os.path.join(report_dir, "report.json")
# print(report_path)
if not os.path.exists(report_path):
return print("No report data was found.")
@@ -113,29 +129,15 @@ def generate_report(adir=".", use_report_folder=True, report_file_name="", html_
# workaround for retrieving the feature file language
print(feature["keyword"])
if feature["keyword"] == "Feature":
strings_file_report = os.path.join(
report_template_path,
"strings_template_en.json"
)
switch_locale(localedir, "en")
elif feature["keyword"] == "Funktionalität":
strings_file_report = os.path.join(
report_template_path,
"strings_template_de.json"
)
switch_locale(localedir, "de")
elif feature["keyword"] == "Fonctionnalité":
strings_file_report = os.path.join(
report_template_path,
"strings_template_fr.json"
)
switch_locale(localedir, "fr")
else:
# standard English
strings_file_report = os.path.join(
report_template_path,
"strings_template_en.json"
)
strings_report = json.loads(
open(strings_file_report, encoding="utf8").read()
)
switch_locale(localedir, "en")
strings_report = get_html_template_strings()
# print(strings_report)
data.update(strings_report)
# print(data)
@@ -145,3 +147,16 @@ def generate_report(adir=".", use_report_folder=True, report_file_name="", html_
with open(html_report, "w", encoding="utf8") as out:
with open(html_tmpl, encoding="utf8") as template:
out.write(pystache.render(template.read(), data))
def get_html_template_strings():
print(_("OpenBIM auditing is a feature of"))
return {
"tr_lang": _("en"),
"tr_success": _("Success"),
"tr_failure": _("Failure"),
"tr_tests_passed": _("Tests passed"),
"tr_duration": _("Duration"),
"tr_auditing": _("OpenBIM auditing is a feature of"),
"tr_and": _("and")
}
+20 -67
View File
@@ -5,40 +5,18 @@ bpy = sys.modules.get("bpy")
if bpy is not None:
import bpy
import blenderbim.bim.module.covetool as module_covetool
import blenderbim.bim.module.model as module_model
import blenderbim.bim.module.bcf as module_bcf
from . import ui, prop, operator
from .module.covetool import prop as covetool_prop
from .module.covetool import ui as covetool_ui
from .module.covetool import operator as covetool_operator
from .module.model import grid as model_grid
from .module.model import wall as model_wall
from .module.model import stair as model_stair
from .module.model import door as model_door
from .module.model import window as model_window
from .module.model import slab as model_slab
from .module.model import opening as model_opening
classes = (
classes = [
operator.ReassignClass,
operator.AssignClass,
operator.UnassignClass,
operator.SelectClass,
operator.SelectType,
operator.NewBcfProject,
operator.LoadBcfProject,
operator.LoadBcfTopics,
operator.LoadBcfComments,
operator.EditBcfProjectName,
operator.EditBcfAuthor,
operator.EditBcfTopicName,
operator.EditBcfTopic,
operator.SaveBcfProject,
operator.AddBcfTopic,
operator.ViewBcfTopic,
operator.ActivateBcfViewpoint,
operator.OpenUri,
operator.OpenBcfReferenceLink,
operator.SelectFeaturesDir,
operator.SelectDiffJsonFile,
operator.SelectDiffNewFile,
@@ -137,6 +115,7 @@ if bpy is not None:
operator.AddDrawingToSheet,
operator.CreateSheets,
operator.OpenView,
operator.OpenViewCamera,
operator.ActivateView,
operator.ExecuteIfcDiff,
operator.VisualiseDiff,
@@ -238,6 +217,8 @@ if bpy is not None:
operator.ExecuteBlenderClash,
operator.CleanWireframes,
operator.LinkIfc,
operator.SnapSpacesTogether,
operator.CopyGrid,
prop.StrProperty,
prop.Attribute,
prop.MaterialLayer,
@@ -264,15 +245,11 @@ if bpy is not None:
prop.Schedule,
prop.DrawingStyle,
prop.Sheet,
prop.BcfBimSnippet,
prop.BcfDocumentReference,
prop.BcfComment,
prop.BcfTopic,
prop.Subcontext,
prop.Representation,
prop.PresentationLayer,
prop.BIMProperties,
prop.BIMDebugProperties,
prop.BCFProperties,
prop.DocProperties,
prop.BIMLibrary,
prop.MapConversion,
@@ -301,9 +278,6 @@ if bpy is not None:
ui.BIM_PT_search,
ui.BIM_PT_ifccsv,
ui.BIM_PT_ifcclash,
ui.BIM_PT_bcf,
ui.BIM_PT_bcf_metadata,
ui.BIM_PT_bcf_comments,
ui.BIM_PT_owner,
ui.BIM_PT_people,
ui.BIM_PT_organisations,
@@ -337,6 +311,7 @@ if bpy is not None:
ui.BIM_PT_clash_manager,
ui.BIM_PT_misc_utilities,
ui.BIM_UL_generic,
ui.BIM_UL_drawinglist,
ui.BIM_UL_clash_sets,
ui.BIM_UL_smart_groups,
ui.BIM_UL_constraints,
@@ -345,22 +320,11 @@ if bpy is not None:
ui.BIM_UL_topics,
ui.BIM_UL_classifications,
ui.BIM_ADDON_preferences,
covetool_prop.CoveToolProject,
covetool_prop.CoveToolSimpleAnalysis,
covetool_prop.CoveToolProperties,
covetool_ui.BIM_UL_covetool_projects,
covetool_ui.BIM_PT_covetool,
covetool_operator.Login,
covetool_operator.RunSimpleAnalysis,
covetool_operator.RunAnalysis,
model_grid.BIM_OT_add_object,
model_wall.BIM_OT_add_object,
model_stair.BIM_OT_add_object,
model_door.BIM_OT_add_object,
model_window.BIM_OT_add_object,
model_slab.BIM_OT_add_object,
model_opening.BIM_OT_add_object,
)
]
classes.extend(module_bcf.classes)
classes.extend(module_covetool.classes)
classes.extend(module_model.classes)
def menu_func_export(self, context):
self.layout.operator(operator.ExportIFC.bl_idname, text="Industry Foundation Classes (.ifc/.ifczip/.ifcjson)")
@@ -381,7 +345,6 @@ if bpy is not None:
bpy.types.TOPBAR_MT_file_import.append(menu_func_import)
bpy.types.Scene.BIMProperties = bpy.props.PointerProperty(type=prop.BIMProperties)
bpy.types.Scene.BIMDebugProperties = bpy.props.PointerProperty(type=prop.BIMDebugProperties)
bpy.types.Scene.BCFProperties = bpy.props.PointerProperty(type=prop.BCFProperties)
bpy.types.Scene.DocProperties = bpy.props.PointerProperty(type=prop.DocProperties)
bpy.types.Scene.BIMLibrary = bpy.props.PointerProperty(type=prop.BIMLibrary)
bpy.types.Scene.MapConversion = bpy.props.PointerProperty(type=prop.MapConversion)
@@ -392,15 +355,10 @@ if bpy is not None:
bpy.types.Mesh.BIMMeshProperties = bpy.props.PointerProperty(type=prop.BIMMeshProperties)
bpy.types.Camera.BIMCameraProperties = bpy.props.PointerProperty(type=prop.BIMCameraProperties)
bpy.types.TextCurve.BIMTextProperties = bpy.props.PointerProperty(type=prop.BIMTextProperties)
bpy.types.Scene.CoveToolProperties = bpy.props.PointerProperty(type=covetool_prop.CoveToolProperties)
bpy.types.SCENE_PT_unit.append(ui.ifc_units)
bpy.types.VIEW3D_MT_mesh_add.append(model_grid.add_object_button)
bpy.types.VIEW3D_MT_mesh_add.append(model_wall.add_object_button)
bpy.types.VIEW3D_MT_mesh_add.append(model_stair.add_object_button)
bpy.types.VIEW3D_MT_mesh_add.append(model_door.add_object_button)
bpy.types.VIEW3D_MT_mesh_add.append(model_window.add_object_button)
bpy.types.VIEW3D_MT_mesh_add.append(model_slab.add_object_button)
bpy.types.VIEW3D_MT_mesh_add.append(model_opening.add_object_button)
module_bcf.register()
module_covetool.register()
module_model.register()
bpy.app.handlers.depsgraph_update_pre.append(operator.depsgraph_update_pre_handler)
def unregister():
@@ -411,7 +369,6 @@ if bpy is not None:
bpy.types.TOPBAR_MT_file_import.remove(menu_func_import)
del bpy.types.Scene.BIMProperties
del bpy.types.Scene.BIMDebugProperties
del bpy.types.Scene.BCFProperties
del bpy.types.Scene.DocProperties
del bpy.types.Scene.MapConversion
del bpy.types.Scene.TargetCRS
@@ -422,11 +379,7 @@ if bpy is not None:
del bpy.types.Camera.BIMCameraProperties
del bpy.types.TextCurve.BIMTextProperties
bpy.types.SCENE_PT_unit.remove(ui.ifc_units)
bpy.types.VIEW3D_MT_mesh_add.remove(model_grid.add_object_button)
bpy.types.VIEW3D_MT_mesh_add.remove(model_wall.add_object_button)
bpy.types.VIEW3D_MT_mesh_add.remove(model_stair.add_object_button)
bpy.types.VIEW3D_MT_mesh_add.remove(model_door.add_object_button)
bpy.types.VIEW3D_MT_mesh_add.remove(model_window.add_object_button)
bpy.types.VIEW3D_MT_mesh_add.remove(model_slab.add_object_button)
bpy.types.VIEW3D_MT_mesh_add.remove(model_opening.add_object_button)
module_model.unregister()
module_covetool.unregister()
module_bcf.unregister()
bpy.app.handlers.depsgraph_update_pre.remove(operator.depsgraph_update_pre_handler)
@@ -22,7 +22,7 @@ class Annotator:
obj = bpy.data.objects.new("IfcAnnotation/Text", curve)
obj.matrix_world = bpy.context.scene.camera.matrix_world
if related_element is None:
location, co2 = Annotator.get_placeholder_coords()
location, _, _, _ = Annotator.get_placeholder_coords()
else:
obj.data.BIMTextProperties.related_element = related_element
location = related_element.location
@@ -66,7 +66,7 @@ class Annotator:
@staticmethod
def add_line_to_annotation(obj, co1=None, co2=None):
if co1 is None:
co1, co2 = Annotator.get_placeholder_coords()
co1, co2, _, _ = Annotator.get_placeholder_coords()
co1 = obj.matrix_world.inverted() @ co1
co2 = obj.matrix_world.inverted() @ co2
if isinstance(obj.data, bpy.types.Mesh):
@@ -82,6 +82,55 @@ class Annotator:
polyline.points[-1].co = list(co2) + [1]
return obj
@staticmethod
def add_plane_to_annotation(obj):
co1, co2, co3, co4 = Annotator.get_placeholder_coords()
co1 = obj.matrix_world.inverted() @ co1 # bot left
co2 = obj.matrix_world.inverted() @ co2 # top left
co3 = obj.matrix_world.inverted() @ co3 # bot right
co4 = obj.matrix_world.inverted() @ co4 # top right
obj.data.vertices.add(4)
v1 = obj.data.vertices[-4]
v2 = obj.data.vertices[-3]
v3 = obj.data.vertices[-2]
v4 = obj.data.vertices[-1]
v1.co = co4
v2.co = co2
v3.co = co1
v4.co = co3
obj.data.edges.add(4)
e1 = obj.data.edges[-4]
e2 = obj.data.edges[-3]
e3 = obj.data.edges[-2]
e4 = obj.data.edges[-1]
e1.vertices = (v1.index, v2.index)
e2.vertices = (v2.index, v3.index)
e3.vertices = (v3.index, v4.index)
e4.vertices = (v4.index, v1.index)
obj.data.loops.add(4)
l1 = obj.data.loops[-4]
l2 = obj.data.loops[-3]
l3 = obj.data.loops[-2]
l4 = obj.data.loops[-1]
l1.vertex_index = v1.index
l1.edge_index = e1.index
l2.vertex_index = v2.index
l2.edge_index = e2.index
l3.vertex_index = v3.index
l3.edge_index = e3.index
l4.vertex_index = v4.index
l4.edge_index = e4.index
obj.data.polygons.add(1)
p1 = obj.data.polygons[-1]
p1.vertices = (v1.index, v2.index, v3.index, v4.index)
p1.loop_start = l1.index
p1.loop_total = 4
return obj
@staticmethod
def get_annotation_obj(name, data_type):
collection = bpy.context.scene.camera.users_collection[0]
@@ -111,4 +160,8 @@ class Annotator:
else:
y = camera.data.ortho_scale / 4
y_offset = camera.matrix_world.to_quaternion() @ Vector((0, y, 0))
return (camera.location + z_offset, camera.location + z_offset + y_offset)
x_offset = camera.matrix_world.to_quaternion() @ Vector((y/2, 0, 0))
return (camera.location + z_offset,
camera.location + z_offset + y_offset,
camera.location + z_offset + x_offset,
camera.location + z_offset + x_offset + y_offset)
File diff suppressed because it is too large Load Diff
@@ -1345,6 +1345,9 @@ class IfcExporter:
self.roundtrip_id_new_to_old = {}
def export(self, selected_objects):
self.file = ifc.IfcStore.get_file()
if self.file and self.ifc_export_settings.should_export_from_memory:
return self.write_ifc_file()
self.schema_version = self.ifc_export_settings.schema
self.schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(self.schema_version)
self.file = ifcopenshell.file(schema=self.schema_version)
@@ -3526,6 +3529,7 @@ class IfcExportSettings:
]
self.should_use_presentation_style_assignment = False
self.should_guess_quantities = False
self.should_export_from_memory = False
self.context_tree = []
@staticmethod
@@ -3545,6 +3549,7 @@ class IfcExportSettings:
settings.should_force_faceted_brep = scene_bim.export_should_force_faceted_brep
settings.should_force_triangulation = scene_bim.export_should_force_triangulation
settings.should_roundtrip_native = scene_bim.import_export_should_roundtrip_native
settings.should_export_from_memory = scene_bim.export_should_export_from_memory
settings.context_tree = []
for ifc_context in ["model", "plan"]:
if getattr(scene_bim, "has_{}_context".format(ifc_context)):
+13 -20
View File
@@ -215,27 +215,20 @@ def format_distance(value, isArea=False, hide_units=True):
feet += 1
inches = 0
if inches != 0:
inchesString = str(inches)
if frac != 0:
inchesString += "-"
else:
inchesString += '"'
else:
inchesString = ""
if feet != 0:
feetString = str(feet) + "' "
else:
feetString = ""
if frac != 0:
fracString = str(frac) + "/" + str(base) + '"'
else:
fracString = ""
if not isArea:
tx_dist = feetString + inchesString + fracString
tx_dist = ""
if feet:
tx_dist += str(feet) + "'"
if feet and inches:
tx_dist += " - "
if inches:
tx_dist += str(inches)
if inches and frac:
tx_dist += " "
if frac:
tx_dist += str(frac) + "/" + str(base)
if inches or frac:
tx_dist += '"'
else:
tx_dist = str("%1.3f" % (value * toInches / inPerFoot)) + " sq. ft."
+2 -1
View File
@@ -12,5 +12,6 @@ class IfcStore:
def get_file():
if IfcStore.file is None:
IfcStore.path = bpy.context.scene.BIMProperties.ifc_file
IfcStore.file = ifcopenshell.open(IfcStore.path)
if IfcStore.path:
IfcStore.file = ifcopenshell.open(IfcStore.path)
return IfcStore.file
@@ -848,7 +848,7 @@ class IfcImporter:
self.add_element_classifications(element, obj)
self.add_element_document_relations(element, obj)
self.add_type_product_psets(element, obj)
self.add_product_representation_contexts(element, obj)
self.add_product_representations(element, obj)
self.type_collection.objects.link(obj)
self.type_products[element.GlobalId] = obj
@@ -974,7 +974,7 @@ class IfcImporter:
self.add_defines_by_type_relation(element, obj)
self.add_opening_relation(element, obj)
self.add_product_definitions(element, obj)
self.add_product_representation_contexts(element, obj)
self.add_product_representations(element, obj)
self.added_data[element.GlobalId] = obj
if element.is_a("IfcOpeningElement"):
@@ -1355,57 +1355,23 @@ class IfcImporter:
bpy.ops.mesh.normals_make_consistent(context_override)
bpy.ops.object.editmode_toggle(context_override)
def add_product_representation_contexts(self, element, obj):
subcontexts = []
ifc_definition_ids = []
def add_product_representations(self, element, obj):
if element.is_a("IfcProduct"):
if not element.Representation:
return
for r in element.Representation.Representations:
ifc_definition_ids.append(r.id())
if r.ContextOfItems.is_a("IfcGeometricRepresentationSubContext"):
subcontexts.append(
"{}/{}/{}".format(
r.ContextOfItems.ContextType or "",
r.ContextOfItems.ContextIdentifier or "",
r.ContextOfItems.TargetView or "",
)
)
else:
subcontexts.append(
"{}/{}/{}".format(
r.ContextOfItems.ContextType or "", r.ContextOfItems.ContextIdentifier or "", ""
)
)
new = obj.BIMObjectProperties.representations.add()
new.name = r.RepresentationIdentifier
new.type = r.RepresentationType
new.ifc_definition_id = r.id()
elif element.is_a("IfcTypeProduct"):
if not element.RepresentationMaps:
return
for r in element.RepresentationMaps:
ifc_definition_ids.append(r.id())
if r.MappedRepresentation.ContextOfItems.is_a("IfcGeometricRepresentationSubContext"):
subcontexts.append(
"{}/{}/{}".format(
r.MappedRepresentation.ContextOfItems.ContextType or "",
r.MappedRepresentation.ContextOfItems.ContextIdentifier or "",
r.MappedRepresentation.ContextOfItems.TargetView or "",
)
)
else:
subcontexts.append(
"{}/{}/{}".format(
r.MappedRepresentation.ContextOfItems.ContextType or "",
r.MappedRepresentation.ContextOfItems.ContextIdentifier or "",
"",
)
)
for i, subcontext in enumerate(subcontexts):
representation_context = obj.BIMObjectProperties.representation_contexts.add()
(
representation_context.context,
representation_context.name,
representation_context.target_view,
) = subcontext.split("/")
representation_context.ifc_definition_id = ifc_definition_ids[i]
new = obj.BIMObjectProperties.representations.add()
new.name = r.MappedRepresentation.RepresentationIdentifier
new.type = r.MappedRepresentation.RepresentationType
new.ifc_definition_id = r.MappedRepresentation.id()
def add_product_definitions(self, element, obj):
if not hasattr(element, "IsDefinedBy") or not element.IsDefinedBy:
@@ -1580,6 +1546,7 @@ class IfcImporter:
subcontext = subcontexts.add()
subcontext.name = context.ContextIdentifier
subcontext.target_view = context.TargetView
subcontext.ifc_definition_id = context.id()
elif context.ContextType == "Model":
bpy.context.scene.BIMProperties.has_model_context = True
elif context.ContextType == "Plan":
@@ -0,0 +1,59 @@
import bpy
from . import ui, prop, operator
classes = (
operator.NewBcfProject,
operator.LoadBcfProject,
operator.LoadBcfTopics,
operator.LoadBcfTopic,
operator.LoadBcfComments,
operator.EditBcfProjectName,
operator.EditBcfAuthor,
operator.EditBcfTopicName,
operator.EditBcfTopic,
operator.SaveBcfProject,
operator.AddBcfTopic,
operator.AddBcfHeaderFile,
operator.AddBcfBimSnippet,
operator.AddBcfReferenceLink,
operator.AddBcfDocumentReference,
operator.AddBcfLabel,
operator.AddBcfRelatedTopic,
operator.ViewBcfTopic,
operator.RemoveBcfComment,
operator.RemoveBcfBimSnippet,
operator.RemoveBcfReferenceLink,
operator.RemoveBcfDocumentReference,
operator.RemoveBcfRelatedTopic,
operator.EditBcfReferenceLinks,
operator.EditBcfLabels,
operator.RemoveBcfLabel,
operator.EditBcfComment,
operator.AddBcfComment,
operator.ActivateBcfViewpoint,
operator.AddBcfViewpoint,
operator.RemoveBcfViewpoint,
operator.RemoveBcfFile,
operator.OpenBcfReferenceLink,
operator.SelectBcfHeaderFile,
operator.SelectBcfBimSnippetReference,
operator.SelectBcfDocumentReference,
prop.BcfReferenceLink,
prop.BcfLabel,
prop.BcfBimSnippet,
prop.BcfDocumentReference,
prop.BcfComment,
prop.BcfTopic,
prop.BCFProperties,
ui.BIM_PT_bcf,
ui.BIM_PT_bcf_metadata,
ui.BIM_PT_bcf_comments,
)
def register():
bpy.types.Scene.BCFProperties = bpy.props.PointerProperty(type=prop.BCFProperties)
def unregister():
del bpy.types.Scene.BCFProperties
@@ -0,0 +1,873 @@
import os
import bpy
import bcf
from . import bcfstore
from math import radians, degrees, atan, tan, cos, sin
from mathutils import Vector, Matrix, Euler, geometry
class NewBcfProject(bpy.types.Operator):
bl_idname = "bim.new_bcf_project"
bl_label = "New BCF Project"
def execute(self, context):
bpy.context.scene.BCFProperties.is_loaded = False
bcfxml = bcfstore.BcfStore.get_bcfxml()
bcfxml.new_project()
bpy.ops.bim.load_bcf_project()
bpy.context.scene.BCFProperties.is_loaded = True
return {"FINISHED"}
class LoadBcfProject(bpy.types.Operator):
bl_idname = "bim.load_bcf_project"
bl_label = "Load BCF Project"
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def execute(self, context):
bpy.context.scene.BCFProperties.is_loaded = False
bcfxml = bcfstore.BcfStore.get_bcfxml()
if self.filepath:
bcfxml.get_project(self.filepath)
bpy.context.scene.BCFProperties.name = bcfxml.project.name
bpy.ops.bim.load_bcf_topics()
bpy.context.scene.BCFProperties.is_loaded = True
return {"FINISHED"}
def invoke(self, context, event):
context.window_manager.fileselect_add(self)
return {"RUNNING_MODAL"}
class LoadBcfTopics(bpy.types.Operator):
bl_idname = "bim.load_bcf_topics"
bl_label = "Load BCF Topics"
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
bcfxml.get_topics()
while len(bpy.context.scene.BCFProperties.topics) > 0:
bpy.context.scene.BCFProperties.topics.remove(0)
index = 0
for topic_guid in bcfxml.topics.keys():
new = bpy.context.scene.BCFProperties.topics.add()
bpy.ops.bim.load_bcf_topic(topic_guid = topic_guid, topic_index = index)
index += 1
return {"FINISHED"}
class LoadBcfTopic(bpy.types.Operator):
bl_idname = "bim.load_bcf_topic"
bl_label = "Load BCF Topics"
topic_guid: bpy.props.StringProperty()
topic_index: bpy.props.IntProperty()
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
topic = bcfxml.get_topic(self.topic_guid)
new = bpy.context.scene.BCFProperties.topics[self.topic_index]
data_map = {
"name": topic.guid,
"title": topic.title,
"type": topic.topic_type,
"status": topic.topic_status,
"priority": topic.priority,
"stage": topic.stage,
"creation_date": topic.creation_date,
"creation_author": topic.creation_author,
"modified_date": topic.modified_date,
"modified_author": topic.modified_author,
"assigned_to": topic.assigned_to,
"due_date": topic.due_date,
"description": topic.description
}
for key, value in data_map.items():
if value is not None:
setattr(new, key, str(value))
while len(new.reference_links) > 0:
new.reference_links.remove(0)
for reference_link in topic.reference_links:
new2 = new.reference_links.add()
new2.name = reference_link
while len(new.labels) > 0:
new.labels.remove(0)
for label in topic.labels:
new2 = new.labels.add()
new2.name = label
if topic.bim_snippet:
data_map = {
"type": topic.bim_snippet.snippet_type,
"is_external": topic.bim_snippet.is_external,
"reference": topic.bim_snippet.reference,
"schema": topic.bim_snippet.reference_schema
}
for key, value in data_map.items():
if value is not None:
setattr(new.bim_snippet, key, value)
while len(new.document_references) > 0:
new.document_references.remove(0)
for doc in topic.document_references:
new2 = new.document_references.add()
data_map = {
"reference": doc.referenced_document,
"description": doc.description,
"guid": doc.guid,
"is_external": doc.is_external
}
for key, value in data_map.items():
if value is not None:
setattr(new2, key, value)
while len(new.related_topics) > 0:
new.related_topics.remove(0)
for related_topic in topic.related_topics:
new2 = new.related_topics.add()
new2.name = related_topic.guid
bpy.ops.bim.load_bcf_comments(topic_guid = topic.guid)
return {"FINISHED"}
class LoadBcfComments(bpy.types.Operator):
bl_idname = "bim.load_bcf_comments"
bl_label = "Load BCF Comments"
topic_guid: bpy.props.StringProperty()
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
bcfxml.get_comments(self.topic_guid)
blender_topic = bpy.context.scene.BCFProperties.topics.get(self.topic_guid)
while len(blender_topic.comments) > 0:
blender_topic.comments.remove(0)
for comment in bcfxml.topics[self.topic_guid].comments.values():
new = blender_topic.comments.add()
data_map = {
"name": comment.guid,
"comment": comment.comment,
"viewpoint": comment.viewpoint.guid if comment.viewpoint else None,
"date": comment.date,
"author": comment.author,
"modified_date": comment.modified_date,
"modified_author": comment.modified_author,
}
for key, value in data_map.items():
if value is not None:
setattr(new, key, str(value))
return {"FINISHED"}
class EditBcfProjectName(bpy.types.Operator):
bl_idname = "bim.edit_bcf_project_name"
bl_label = "Edit BCF Project Name"
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
bcfxml.project.name = bpy.context.scene.BCFProperties.name
bcfxml.edit_project()
return {"FINISHED"}
class EditBcfAuthor(bpy.types.Operator):
bl_idname = "bim.edit_bcf_author"
bl_label = "Edit BCF Author"
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
bcfxml.author = bpy.context.scene.BCFProperties.author
return {"FINISHED"}
class EditBcfTopicName(bpy.types.Operator):
bl_idname = "bim.edit_bcf_topic_name"
bl_label = "Edit BCF Topic Name"
def execute(self, context):
props = bpy.context.scene.BCFProperties
blender_topic = props.topics[props.active_topic_index]
bcfxml = bcfstore.BcfStore.get_bcfxml()
topic = bcfxml.topics[blender_topic.name]
topic.title = blender_topic.title
bcfxml.edit_topic(topic)
return {"FINISHED"}
class EditBcfTopic(bpy.types.Operator):
bl_idname = "bim.edit_bcf_topic"
bl_label = "Edit BCF Topic"
def execute(self, context):
props = bpy.context.scene.BCFProperties
blender_topic = props.topics[props.active_topic_index]
bcfxml = bcfstore.BcfStore.get_bcfxml()
topic = bcfxml.topics[blender_topic.name]
topic.title = blender_topic.title or None
topic.priority = blender_topic.priority or None
topic.due_date = blender_topic.due_date or None
topic.assigned_to = blender_topic.assigned_to or None
topic.stage = blender_topic.stage or None
topic.description = blender_topic.description or None
topic.topic_status = blender_topic.status or None
topic.topic_type = blender_topic.type or None
bcfxml.edit_topic(topic)
props.active_topic_index = props.active_topic_index # Refreshes the BCF Topic
return {"FINISHED"}
class SaveBcfProject(bpy.types.Operator):
bl_idname = "bim.save_bcf_project"
bl_label = "Save BCF Project"
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
bcfxml.save_project(self.filepath)
return {"FINISHED"}
def invoke(self, context, event):
context.window_manager.fileselect_add(self)
return {"RUNNING_MODAL"}
class AddBcfTopic(bpy.types.Operator):
bl_idname = "bim.add_bcf_topic"
bl_label = "Add BCF Topic"
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
bcfxml.add_topic()
new = bpy.context.scene.BCFProperties.topics.add()
new.name = "New Topic"
bpy.ops.bim.load_bcf_topics()
return {"FINISHED"}
class AddBcfBimSnippet(bpy.types.Operator):
bl_idname = "bim.add_bcf_bim_snippet"
bl_label = "Add BCF BIM Snippet"
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
props = bpy.context.scene.BCFProperties
blender_topic = props.topics[props.active_topic_index]
topic = bcfxml.topics[blender_topic.name]
bim_snippet = bcf.data.BimSnippet()
bim_snippet.reference = blender_topic.bim_snippet_reference
bim_snippet.reference_schema = blender_topic.bim_snippet_schema
bim_snippet.snippet_type = blender_topic.bim_snippet_type
bcfxml.add_bim_snippet(topic, bim_snippet)
bpy.ops.bim.load_bcf_topic(topic_guid = topic.guid, topic_index = props.active_topic_index)
return {"FINISHED"}
class AddBcfRelatedTopic(bpy.types.Operator):
bl_idname = "bim.add_bcf_related_topic"
bl_label = "Add BCF Related Topic"
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
props = bpy.context.scene.BCFProperties
blender_topic = props.topics[props.active_topic_index]
related_topic = None
for topic in bcfxml.topics.values():
if topic.title == blender_topic.related_topic:
related_topic = bcf.data.RelatedTopic()
related_topic.guid = topic.guid
break
if not related_topic:
return {"FINISHED"}
topic = bcfxml.topics[blender_topic.name]
topic.related_topics.append(related_topic)
bcfxml.edit_topic(topic)
bpy.ops.bim.load_bcf_topic(topic_guid = topic.guid, topic_index = props.active_topic_index)
return {"FINISHED"}
class AddBcfHeaderFile(bpy.types.Operator):
bl_idname = "bim.add_bcf_header_file"
bl_label = "Add BCF Header File"
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
props = bpy.context.scene.BCFProperties
blender_topic = props.topics[props.active_topic_index]
topic = bcfxml.topics[blender_topic.name]
header_file = bcf.data.HeaderFile()
header_file.reference = blender_topic.file_reference
if not os.path.exists(header_file.reference):
header_file.filename = header_file.reference
if len(blender_topic.file_ifc_project) == 22:
header_file.ifc_project = blender_topic.file_ifc_project
if len(blender_topic.file_ifc_spatial_structure_element) == 22:
header_file.ifc_spatial_structure_element = blender_topic.file_ifc_spatial_structure_element
bcfxml.add_file(topic, header_file)
props.active_topic_index = props.active_topic_index # refreshes the BCF Topic
return {"FINISHED"}
class ViewBcfTopic(bpy.types.Operator):
bl_idname = "bim.view_bcf_topic"
bl_label = "Get BCF Topic"
topic_guid: bpy.props.StringProperty()
def execute(self, context):
for index, topic in enumerate(bpy.context.scene.BCFProperties.topics):
if topic.guid.lower() == self.topic_guid.lower():
bpy.context.scene.BCFProperties.active_topic_index = index
return {"FINISHED"}
class AddBcfViewpoint(bpy.types.Operator):
bl_idname = "bim.add_bcf_viewpoint"
bl_label = "Add BCF Viewpoint"
def execute(self, context):
if not bpy.context.scene.camera:
return {"FINISHED"}
bcfxml = bcfstore.BcfStore.get_bcfxml()
props = bpy.context.scene.BCFProperties
blender_topic = props.topics[props.active_topic_index]
topic = bcfxml.topics[blender_topic.name]
viewpoint = bcf.data.Viewpoint()
if bpy.context.scene.camera.data.type == "ORTHO":
camera = bcf.data.OrthogonalCamera()
camera.view_to_world_scale = bpy.context.scene.camera.data.ortho_scale
viewpoint.orthogonal_camera = camera
elif bpy.context.scene.camera.data.type == "PERSP":
camera = bcf.data.PerspectiveCamera()
camera.field_of_view = degrees(bpy.context.scene.camera.data.angle)
viewpoint.perspective_camera = camera
camera.camera_view_point.x = bpy.context.scene.camera.location.x
camera.camera_view_point.y = bpy.context.scene.camera.location.y
camera.camera_view_point.z = bpy.context.scene.camera.location.z
direction = bpy.context.scene.camera.matrix_world.to_quaternion() @ Vector((0.0, 0.0, -1.0))
camera.camera_direction.x = direction.x
camera.camera_direction.y = direction.y
camera.camera_direction.z = direction.z
up = bpy.context.scene.camera.matrix_world.to_quaternion() @ Vector((0.0, 1.0, 0.0))
camera.camera_up_vector.x = up.x
camera.camera_up_vector.y = up.y
camera.camera_up_vector.z = up.z
old_file_format = bpy.context.scene.render.image_settings.file_format
bpy.context.scene.render.image_settings.file_format = "PNG"
old_filepath = bpy.context.scene.render.filepath
bpy.context.scene.render.filepath = os.path.join(bpy.context.scene.BIMProperties.data_dir, "snapshot.png")
bpy.ops.render.opengl(write_still=True)
viewpoint.snapshot = bpy.context.scene.render.filepath
bcfxml.add_viewpoint(topic, viewpoint)
bpy.context.scene.render.filepath = old_filepath
bpy.context.scene.render.image_settings.file_format = old_file_format
props.active_topic_index = props.active_topic_index # refreshes the BCF Topic
return {"FINISHED"}
class RemoveBcfViewpoint(bpy.types.Operator):
bl_idname = "bim.remove_bcf_viewpoint"
bl_label = "Remove BCF Viewpoint"
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
props = bpy.context.scene.BCFProperties
blender_topic = props.topics[props.active_topic_index]
viewpoint_guid = blender_topic.viewpoints
topic = bcfxml.topics[blender_topic.name]
bcfxml.delete_viewpoint(viewpoint_guid, topic)
props.active_topic_index = props.active_topic_index # Refreshes the BCF Topic
return {"FINISHED"}
class RemoveBcfFile(bpy.types.Operator):
bl_idname = "bim.remove_bcf_file"
bl_label = "Remove BCF File"
index: bpy.props.IntProperty()
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
props = bpy.context.scene.BCFProperties
blender_topic = props.topics[props.active_topic_index]
topic = bcfxml.topics[blender_topic.name]
bcfxml.delete_file(topic, self.index)
props.active_topic_index = props.active_topic_index # Refreshes the BCF Topic
return {"FINISHED"}
class AddBcfReferenceLink(bpy.types.Operator):
bl_idname = "bim.add_bcf_reference_link"
bl_label = "Add BCF Reference Link"
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
props = bpy.context.scene.BCFProperties
blender_topic = props.topics[props.active_topic_index]
topic = bcfxml.topics[blender_topic.name]
if not blender_topic.reference_link:
return {"FINISHED"}
topic.reference_links.append(blender_topic.reference_link)
bcfxml.edit_topic(topic)
bpy.ops.bim.load_bcf_topic(topic_guid = topic.guid, topic_index = props.active_topic_index)
blender_topic.reference_link = ""
return {"FINISHED"}
class AddBcfDocumentReference(bpy.types.Operator):
bl_idname = "bim.add_bcf_document_reference"
bl_label = "Add BCF Document Reference"
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
props = bpy.context.scene.BCFProperties
blender_topic = props.topics[props.active_topic_index]
topic = bcfxml.topics[blender_topic.name]
if not blender_topic.document_reference:
return {"FINISHED"}
document_reference = bcf.data.DocumentReference()
document_reference.referenced_document = blender_topic.document_reference
document_reference.description = blender_topic.document_reference_description or None
bcfxml.add_document_reference(topic, document_reference)
bpy.ops.bim.load_bcf_topic(topic_guid = topic.guid, topic_index = props.active_topic_index)
blender_topic.document_reference = ""
blender_topic.document_reference_description = ""
return {"FINISHED"}
class AddBcfLabel(bpy.types.Operator):
bl_idname = "bim.add_bcf_label"
bl_label = "Add BCF Label"
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
props = bpy.context.scene.BCFProperties
blender_topic = props.topics[props.active_topic_index]
topic = bcfxml.topics[blender_topic.name]
if not blender_topic.label:
return {"FINISHED"}
new = blender_topic.labels.add()
new.name = blender_topic.label
topic.labels.append(blender_topic.label)
bcfxml.edit_topic(topic)
blender_topic.label = ""
return {"FINISHED"}
class EditBcfReferenceLinks(bpy.types.Operator):
bl_idname = "bim.edit_bcf_reference_links"
bl_label = "Edit BCF Reference Link"
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
props = bpy.context.scene.BCFProperties
blender_topic = props.topics[props.active_topic_index]
topic = bcfxml.topics[blender_topic.name]
for index, reference_link in enumerate(topic.reference_links):
if reference_link == blender_topic.reference_links[index].name:
continue
topic.reference_links[index] = blender_topic.reference_links[index].name
bcfxml.edit_topic(topic)
return {"FINISHED"}
class EditBcfLabels(bpy.types.Operator):
bl_idname = "bim.edit_bcf_labels"
bl_label = "Edit BCF Labels"
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
props = bpy.context.scene.BCFProperties
blender_topic = props.topics[props.active_topic_index]
topic = bcfxml.topics[blender_topic.name]
for index, label in enumerate(blender_topic.labels):
if label.name == topic.labels[index]:
continue
topic.labels[index] = blender_topic.labels[index].name
bcfxml.edit_topic(topic)
return {"FINISHED"}
class RemoveBcfReferenceLink(bpy.types.Operator):
bl_idname = "bim.remove_bcf_reference_link"
bl_label = "Remove BCF Reference Link"
index: bpy.props.IntProperty()
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
props = bpy.context.scene.BCFProperties
blender_topic = props.topics[props.active_topic_index]
topic = bcfxml.topics[blender_topic.name]
del topic.reference_links[self.index]
bcfxml.edit_topic(topic)
blender_topic.reference_links.remove(self.index)
return {"FINISHED"}
class RemoveBcfLabel(bpy.types.Operator):
bl_idname = "bim.remove_bcf_label"
bl_label = "Remove BCF Label"
index: bpy.props.IntProperty()
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
props = bpy.context.scene.BCFProperties
blender_topic = props.topics[props.active_topic_index]
topic = bcfxml.topics[blender_topic.name]
del topic.labels[self.index]
bcfxml.edit_topic(topic)
blender_topic.labels.remove(self.index)
return {"FINISHED"}
class RemoveBcfBimSnippet(bpy.types.Operator):
bl_idname = "bim.remove_bcf_bim_snippet"
bl_label = "Remove BCF BIM Snippet"
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
props = bpy.context.scene.BCFProperties
blender_topic = props.topics[props.active_topic_index]
topic = bcfxml.topics[blender_topic.name]
bcfxml.delete_bim_snippet(topic)
blender_topic.bim_snippet.schema = ""
blender_topic.bim_snippet.reference = ""
blender_topic.bim_snippet.type = ""
return {"FINISHED"}
class RemoveBcfDocumentReference(bpy.types.Operator):
bl_idname = "bim.remove_bcf_document_reference"
bl_label = "Remove BCF Document Reference"
index: bpy.props.IntProperty()
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
props = bpy.context.scene.BCFProperties
blender_topic = props.topics[props.active_topic_index]
topic = bcfxml.topics[blender_topic.name]
bcfxml.delete_document_reference(topic, self.index)
bpy.ops.bim.load_bcf_topic(topic_guid = topic.guid, topic_index = props.active_topic_index)
return {"FINISHED"}
class RemoveBcfRelatedTopic(bpy.types.Operator):
bl_idname = "bim.remove_bcf_related_topic"
bl_label = "Remove BCF Related Topic"
index: bpy.props.IntProperty()
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
props = bpy.context.scene.BCFProperties
blender_topic = props.topics[props.active_topic_index]
topic = bcfxml.topics[blender_topic.name]
del topic.related_topics[self.index]
bcfxml.edit_topic(topic)
bpy.ops.bim.load_bcf_topic(topic_guid = topic.guid, topic_index = props.active_topic_index)
return {"FINISHED"}
class RemoveBcfComment(bpy.types.Operator):
bl_idname = "bim.remove_bcf_comment"
bl_label = "Remove BCF Comment"
comment_guid: bpy.props.StringProperty()
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
props = bpy.context.scene.BCFProperties
blender_topic = props.topics[props.active_topic_index]
topic = bcfxml.topics[blender_topic.name]
bcfxml.delete_comment(self.comment_guid, topic)
bpy.ops.bim.load_bcf_comments(topic_guid = topic.guid)
return {"FINISHED"}
class EditBcfComment(bpy.types.Operator):
bl_idname = "bim.edit_bcf_comment"
bl_label = "Edit BCF Comment"
comment_guid: bpy.props.StringProperty()
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
props = bpy.context.scene.BCFProperties
blender_topic = props.topics[props.active_topic_index]
blender_comment = blender_topic.comments.get(self.comment_guid)
topic = bcfxml.topics[blender_topic.name]
comment = topic.comments[self.comment_guid]
comment.comment = blender_comment.comment
bcfxml.edit_comment(comment, topic)
bpy.ops.bim.load_bcf_comments(topic_guid = topic.guid)
return {"FINISHED"}
class AddBcfComment(bpy.types.Operator):
bl_idname = "bim.add_bcf_comment"
bl_label = "Add BCF Comment"
comment_guid: bpy.props.StringProperty()
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
props = bpy.context.scene.BCFProperties
blender_topic = props.topics[props.active_topic_index]
topic = bcfxml.topics[blender_topic.name]
if not blender_topic.comment:
return {"FINISHED"}
comment = bcf.data.Comment()
comment.comment = blender_topic.comment
if blender_topic.has_related_viewpoint and blender_topic.viewpoints:
comment.viewpoint = bcf.data.Viewpoint()
comment.viewpoint.guid = blender_topic.viewpoints
bcfxml.add_comment(topic, comment)
bpy.ops.bim.load_bcf_comments(topic_guid = topic.guid)
print(bcfxml.filepath)
return {"FINISHED"}
class ActivateBcfViewpoint(bpy.types.Operator):
bl_idname = "bim.activate_bcf_viewpoint"
bl_label = "Activate BCF Viewpoint"
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
props = bpy.context.scene.BCFProperties
blender_topic = props.topics[props.active_topic_index]
topic = bcfxml.topics[blender_topic.name]
if not topic.viewpoints:
return {"FINISHED"}
viewpoint_guid = blender_topic.viewpoints
viewpoint = topic.viewpoints[viewpoint_guid]
obj = bpy.data.objects.get("Viewpoint")
if not obj:
obj = bpy.data.objects.new("Viewpoint", bpy.data.cameras.new("Viewpoint"))
bpy.context.scene.collection.objects.link(obj)
bpy.context.scene.camera = obj
cam_width = bpy.context.scene.render.resolution_x
cam_height = bpy.context.scene.render.resolution_y
cam_aspect = cam_width / cam_height
if viewpoint.snapshot:
obj.data.show_background_images = True
while len(obj.data.background_images) > 0:
obj.data.background_images.remove(obj.data.background_images[0])
background = obj.data.background_images.new()
background.image = bpy.data.images.load(
os.path.join(bcfxml.filepath, topic.guid, viewpoint.snapshot)
)
src_width = background.image.size[0]
src_height = background.image.size[1]
src_aspect = src_width / src_height
if src_aspect > cam_aspect:
background.frame_method = "FIT"
else:
background.frame_method = "CROP"
background.display_depth = "FRONT"
area = next(area for area in bpy.context.screen.areas if area.type == "VIEW_3D")
area.spaces[0].region_3d.view_perspective = "CAMERA"
if viewpoint.orthogonal_camera:
camera = viewpoint.orthogonal_camera
obj.data.type = "ORTHO"
obj.data.ortho_scale = viewpoint.orthogonal_camera.view_to_world_scale
elif viewpoint.perspective_camera:
camera = viewpoint.perspective_camera
obj.data.type = "PERSP"
if cam_aspect >= 1:
obj.data.angle = radians(camera.field_of_view)
else:
# https://blender.stackexchange.com/questions/23431/how-to-set-camera-horizontal-and-vertical-fov
obj.data.angle = 2 * atan((0.5 * cam_height) / (0.5 * cam_width / tan(radians(camera.field_of_view) / 2)))
self.set_viewpoint_components(viewpoint)
gp = bpy.data.grease_pencils.get("BCF")
if gp:
bpy.data.grease_pencils.remove(gp)
if viewpoint.lines:
self.draw_lines(viewpoint)
self.delete_clipping_planes()
if viewpoint.clipping_planes:
self.create_clipping_planes(viewpoint)
self.delete_bitmaps()
if viewpoint.bitmaps:
self.create_bitmaps(bcfxml, viewpoint, topic)
z_axis = Vector((-camera.camera_direction.x, -camera.camera_direction.y, -camera.camera_direction.z)).normalized()
y_axis = Vector((camera.camera_up_vector.x, camera.camera_up_vector.y, camera.camera_up_vector.z)).normalized()
x_axis = y_axis.cross(z_axis).normalized()
rotation = Matrix((x_axis, y_axis, z_axis))
rotation.invert()
location = Vector((camera.camera_view_point.x, camera.camera_view_point.y, camera.camera_view_point.z))
obj.matrix_world = rotation.to_4x4()
obj.location = location
return {"FINISHED"}
def set_viewpoint_components(self, viewpoint):
if not viewpoint.components:
return
selected_global_ids = [s.ifc_guid for s in viewpoint.components.selection]
exception_global_ids = [v.ifc_guid for v in viewpoint.components.visibility.exceptions]
global_id_colours = {}
for coloring in viewpoint.components.coloring:
for component in coloring.components:
global_id_colours.setdefault(component.ifc_guid, coloring.color)
for obj in bpy.data.objects:
global_id = obj.BIMObjectProperties.attributes.get("GlobalId")
if not global_id:
continue
global_id = global_id.string_value
is_visible = viewpoint.components.visibility.default_visibility
if global_id in exception_global_ids:
is_visible = not is_visible
if not is_visible:
obj.hide_set(True)
continue
if "IfcSpace" in obj.name:
is_visible = viewpoint.components.viewSetuphints.spacesVisible
elif "IfcOpeningElement" in obj.name:
is_visible = viewpoint.components.viewSetuphints.openingsVisible
obj.hide_set(not is_visible)
if not is_visible:
continue
obj.select_set(global_id in selected_global_ids)
if global_id in global_id_colours:
obj.color = self.hex_to_rgb(global_id_colours[global_id])
def draw_lines(self, viewpoint):
gp = bpy.data.grease_pencils.new("BCF")
scene = bpy.context.scene
scene.grease_pencil = gp
scene.frame_set(1)
layer = gp.layers.new("BCF Annotation", set_active=True)
layer.thickness = 3
layer.color = (1, 0, 0)
frame = layer.frames.new(1)
stroke = frame.strokes.new()
stroke.display_mode = "3DSPACE"
stroke.points.add(len(viewpoint.lines) * 2)
coords = []
for l in viewpoint.lines:
coords.extend([l.start_point.x, l.start_point.y, l.start_point.z, l.end_point.x, l.end_point.y, l.end_point.z])
stroke.points.foreach_set("co", coords)
def create_clipping_planes(self, viewpoint):
n = 0
for plane in viewpoint.clipping_planes:
bpy.ops.bim.add_section_plane()
if n == 0:
obj = bpy.data.objects["Section"]
else:
obj = bpy.data.objects["Section.{:03d}".format(n)]
obj.location = (plane.location.x, plane.location.y, plane.location.z)
obj.rotation_mode = "QUATERNION"
obj.rotation_quaternion = Vector((plane.direction.x, plane.direction.y, plane.direction.z)).to_track_quat(
"Z", "Y"
)
n += 1
def delete_clipping_planes(self):
collection = bpy.data.collections.get("Sections")
if not collection:
return
for section in collection.objects:
bpy.context.view_layer.objects.active = section
bpy.ops.bim.remove_section_plane()
def delete_bitmaps(self):
collection = bpy.data.collections.get("Bitmaps")
if not collection:
collection = bpy.data.collections.new("Bitmaps")
bpy.context.scene.collection.children.link(collection)
for bitmap in collection.objects:
bpy.data.objects.remove(bitmap)
def create_bitmaps(self, bcfxml, viewpoint, topic):
collection = bpy.data.collections.get("Bitmaps")
if not collection:
collection = bpy.data.collections.new("Bitmaps")
for bitmap in viewpoint.bitmaps:
obj = bpy.data.objects.new("Bitmap", None)
obj.empty_display_type = "IMAGE"
image = bpy.data.images.load(os.path.join(bcfxml.filepath, topic.guid, bitmap.reference))
src_width = image.size[0]
src_height = image.size[1]
if src_height > src_width:
obj.empty_display_size = bitmap.height
else:
obj.empty_display_size = bitmap.height * (src_width / src_height)
obj.data = image
y = Vector((bitmap.up.x, bitmap.up.y, bitmap.up.z))
z = Vector((bitmap.normal.x, bitmap.normal.y, bitmap.normal.z))
x = y.cross(z)
obj.matrix_world = Matrix(
[[x[0], y[0], z[0], 0], [x[1], y[1], z[1], 0], [x[2], y[2], z[2], 0], [0, 0, 0, 1]]
)
obj.location = (bitmap.location.x, bitmap.location.y, bitmap.location.z)
collection.objects.link(obj)
def hex_to_rgb(self, value):
value = value.lstrip("#")
lv = len(value)
t = tuple(int(value[i : i + lv // 3], 16) for i in range(0, lv, lv // 3))
return [t[0] / 255.0, t[1] / 255.0, t[2] / 255.0, 1]
class OpenBcfReferenceLink(bpy.types.Operator):
bl_idname = "bim.open_bcf_reference_link"
bl_label = "Open BCF Reference Link"
index: bpy.props.IntProperty()
def execute(self, context):
webbrowser.open(bpy.context.scene.BCFProperties.topic_links[self.index].name)
return {"FINISHED"}
class SelectBcfHeaderFile(bpy.types.Operator):
bl_idname = "bim.select_bcf_header_file"
bl_label = "Select BCF Header File"
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def execute(self, context):
if self.filepath:
props = bpy.context.scene.BCFProperties
topic = props.topics[props.active_topic_index]
topic.file_reference = self.filepath
return {"FINISHED"}
def invoke(self, context, event):
context.window_manager.fileselect_add(self)
return {"RUNNING_MODAL"}
class SelectBcfBimSnippetReference(bpy.types.Operator):
bl_idname = "bim.select_bcf_bim_snippet_reference"
bl_label = "Select BCF BIM Snippet Reference"
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def execute(self, context):
if self.filepath:
props = bpy.context.scene.BCFProperties
topic = props.topics[props.active_topic_index]
topic.bim_snippet_reference = self.filepath
return {"FINISHED"}
def invoke(self, context, event):
context.window_manager.fileselect_add(self)
return {"RUNNING_MODAL"}
class SelectBcfDocumentReference(bpy.types.Operator):
bl_idname = "bim.select_bcf_document_reference"
bl_label = "Select BCF Document Reference"
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def execute(self, context):
if self.filepath:
props = bpy.context.scene.BCFProperties
topic = props.topics[props.active_topic_index]
topic.document_reference = self.filepath
return {"FINISHED"}
def invoke(self, context, event):
context.window_manager.fileselect_add(self)
return {"RUNNING_MODAL"}
@@ -0,0 +1,155 @@
import bpy
from . import bcfstore
from blenderbim.bim.prop import StrProperty
from bpy.types import PropertyGroup
from bpy.props import (
PointerProperty,
StringProperty,
EnumProperty,
BoolProperty,
IntProperty,
FloatProperty,
FloatVectorProperty,
CollectionProperty,
)
bcfviewpoints_enum = None
def updateBcfReferenceLink(self, context):
if bpy.context.scene.BCFProperties.is_loaded:
bpy.ops.bim.edit_bcf_reference_links()
def updateBcfLabel(self, context):
if bpy.context.scene.BCFProperties.is_loaded:
bpy.ops.bim.edit_bcf_labels()
def updateBcfProjectName(self, context):
if bpy.context.scene.BCFProperties.is_loaded:
bpy.ops.bim.edit_bcf_project_name()
def updateBcfAuthor(self, context):
if bpy.context.scene.BCFProperties.is_loaded:
bpy.ops.bim.edit_bcf_author()
def updateBcfTopicName(self, context):
if bpy.context.scene.BCFProperties.is_loaded:
bpy.ops.bim.edit_bcf_topic_name()
def updateBcfTopicIsEditable(self, context):
if bpy.context.scene.BCFProperties.is_loaded and not self.is_editable:
bpy.ops.bim.edit_bcf_topic()
def updateBcfCommentIsEditable(self, context):
if bpy.context.scene.BCFProperties.is_loaded and not self.is_editable:
bpy.ops.bim.edit_bcf_comment(comment_guid = self.name)
def refreshBcfTopic(self, context):
global bcfviewpoints_enum
bcfviewpoints_enum = None
props = bpy.context.scene.BCFProperties
bcfxml = bcfstore.BcfStore.get_bcfxml()
topic = props.topics[props.active_topic_index]
header = bcfxml.get_header(topic.name)
getBcfViewpoints(self, context)
class BcfReferenceLink(PropertyGroup):
name: StringProperty(name="Name", update=updateBcfReferenceLink)
class BcfLabel(PropertyGroup):
name: StringProperty(name="Name", update=updateBcfLabel)
def getBcfViewpoints(self, context):
global bcfviewpoints_enum
if bcfviewpoints_enum is None:
bcfviewpoints_enum = []
props = bpy.context.scene.BCFProperties
bcfxml = bcfstore.BcfStore.get_bcfxml()
topic = props.topics[props.active_topic_index]
viewpoints = bcfxml.get_viewpoints(topic.name)
bcfviewpoints_enum.extend([(v, f"Viewpoint {i+1}", "") for i, v in enumerate(viewpoints.keys())])
return bcfviewpoints_enum
class BcfBimSnippet(PropertyGroup):
schema: StringProperty(name="Schema")
reference: StringProperty(name="Reference")
type: StringProperty(name="Type")
is_external: BoolProperty(name="Is External")
class BcfDocumentReference(PropertyGroup):
reference: StringProperty(name="Reference")
description: StringProperty(name="Description")
guid: StringProperty(name="GUID")
is_external: BoolProperty(name="Is External")
class BcfComment(PropertyGroup):
name: StringProperty(name="GUID")
date: StringProperty(name="Date")
author: StringProperty(name="Author")
comment: StringProperty(name="Comment")
viewpoint: StringProperty(name="Viewpoint")
modified_date: StringProperty(name="Modified Date")
modified_author: StringProperty(name="Modified Author")
is_editable: BoolProperty(name="Is Editable", default=False, update=updateBcfCommentIsEditable)
class BcfTopic(PropertyGroup):
name: StringProperty(name="GUID")
title: StringProperty(default="", name="Title", update=updateBcfTopicName)
type: StringProperty(default="", name="Type")
status: StringProperty(default="", name="Status")
priority: StringProperty(default="", name="Priority")
stage: StringProperty(default="", name="Stage")
creation_date: StringProperty(default="", name="Date")
creation_author: StringProperty(default="", name="Author")
modified_date: StringProperty(default="", name="Modified Date")
modified_author: StringProperty(default="", name="Modified By")
assigned_to: StringProperty(default="", name="Assigned To")
due_date: StringProperty(default="", name="Due Date")
description: StringProperty(default="", name="Description")
viewpoints: EnumProperty(items=getBcfViewpoints, name="BCF Viewpoints")
files: CollectionProperty(name="Files", type=StrProperty)
file_reference: StringProperty(default="", name="Reference")
file_ifc_project: StringProperty(default="", name="IFC Project")
file_ifc_spatial_structure_element: StringProperty(default="", name="IFC Spatial Structure Element")
reference_links: CollectionProperty(name="Reference Links", type=BcfReferenceLink)
reference_link: StringProperty(default="", name="Reference Link")
labels: CollectionProperty(name="Labels", type=BcfLabel)
label: StringProperty(default="", name="Label")
bim_snippet: PointerProperty(type=BcfBimSnippet)
bim_snippet_type: StringProperty(default="", name="Type")
bim_snippet_reference: StringProperty(default="", name="Reference")
bim_snippet_schema: StringProperty(default="", name="Schema")
document_references: CollectionProperty(name="Document References", type=BcfDocumentReference)
document_reference: StringProperty(default="", name="Referenced Document")
document_reference_description: StringProperty(default="", name="Description")
related_topics: CollectionProperty(name="Related Topics", type=StrProperty)
related_topic: StringProperty(default="", name="Related Topic")
comments: CollectionProperty(name="Comments", type=BcfComment)
is_editable: BoolProperty(name="Is Editable", default=False, update=updateBcfTopicIsEditable)
comment: StringProperty(default="", name="Comment")
has_related_viewpoint: BoolProperty(name="Has Related Viewpoint", default=False)
class BCFProperties(PropertyGroup):
is_loaded: BoolProperty(name="Is Loaded", default=False)
comment_text_width: IntProperty(name="Comment Text Width", default=40)
name: StringProperty(default="", name="Project Name", update=updateBcfProjectName)
author: StringProperty(default="john@doe.com", name="Author Email", update=updateBcfAuthor)
topics: CollectionProperty(name="BCF Topics", type=BcfTopic)
active_topic_index: IntProperty(name="Active BCF Topic Index", update=refreshBcfTopic)
@@ -0,0 +1,283 @@
import os
import bpy
from . import bcfstore
from bpy.types import Panel
class BIM_PT_bcf(Panel):
bl_label = "BIM Collaboration Format (BCF)"
bl_idname = "BIM_PT_bcf"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
scene = context.scene
props = bpy.context.scene.BCFProperties
row = layout.row(align=True)
row.operator("bim.new_bcf_project", text="New Project")
row.operator("bim.load_bcf_project", text="Load Project")
if not props.is_loaded:
return
row.operator("bim.save_bcf_project", text="Save Project")
row = layout.row()
row.prop(props, "name")
row = layout.row()
row.prop(props, "author")
props = bpy.context.scene.BCFProperties
row = layout.row()
row.template_list("BIM_UL_topics", "", props, "topics", props, "active_topic_index")
col = row.column(align=True)
col.operator("bim.add_bcf_topic", icon="ADD", text="")
if props.active_topic_index < len(props.topics):
topic = props.topics[props.active_topic_index]
col.prop(topic, "is_editable", icon="CHECKMARK" if topic.is_editable else "GREASEPENCIL", icon_only=True)
if props.active_topic_index < len(props.topics):
topic = props.topics[props.active_topic_index]
row = layout.row()
row.enabled = topic.is_editable
row.prop(topic, "description", text="")
row = layout.row()
row.prop(topic, "viewpoints")
row.operator("bim.activate_bcf_viewpoint", icon="SCENE", text="")
row.operator("bim.add_bcf_viewpoint", icon="ADD", text="")
row.operator("bim.remove_bcf_viewpoint", icon="X", text="")
col = layout.column(align=True)
if topic.type:
col.prop(topic, "type", emboss=topic.is_editable)
if topic.status:
col.prop(topic, "status", emboss=topic.is_editable)
if topic.priority:
col.prop(topic, "priority", emboss=topic.is_editable)
if topic.stage:
col.prop(topic, "stage", emboss=topic.is_editable)
if topic.assigned_to:
col.prop(topic, "assigned_to", emboss=topic.is_editable)
if topic.due_date:
col.prop(topic, "due_date", emboss=topic.is_editable)
col = layout.column(align=True)
if topic.modified_date:
col.prop(topic, "modified_date", emboss=False)
col.prop(topic, "modified_author", emboss=False)
else:
col.prop(topic, "creation_date", emboss=False)
col.prop(topic, "creation_author", emboss=False)
class BIM_PT_bcf_metadata(Panel):
bl_label = "BCF Metadata"
bl_idname = "BIM_PT_bcf_metadata"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_bcf"
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
scene = context.scene
props = bpy.context.scene.BCFProperties
if props.active_topic_index >= len(props.topics):
layout.label(text="No BCF project is loaded")
return
topic = props.topics[props.active_topic_index]
bcfxml = bcfstore.BcfStore.get_bcfxml()
bcf_topic = bcfxml.topics[topic.name]
layout.label(text="Header Files:")
if bcf_topic.header:
for index, f in enumerate(bcf_topic.header.files):
box = self.layout.box()
row = box.row(align=True)
row.label(text=f.filename, icon="FILE_BLANK")
if f.is_external:
row.operator("bim.open_uri", icon="URL", text="").uri = f.reference
else:
op = row.operator("bim.open_uri", icon="FILE_FOLDER", text="")
op.uri = os.path.join(bcfxml.filepath, topic.name, f.reference)
row.operator("bim.remove_bcf_file", icon="X", text="").index = index
row = box.row()
row.label(text="Date")
row.label(text=f.date)
if f.ifc_project:
row = box.row()
row.label(text="IFC Project")
row.label(text=f.ifc_project)
if f.ifc_spatial_structure_element:
row = box.row()
row.label(text="IFC Spatial Structure Element")
row.label(text=f.ifc_spatial_structure_element)
row = layout.row(align=True)
row.prop(topic, "file_reference")
row.operator("bim.select_bcf_header_file", icon="FILE_FOLDER", text="")
row = layout.row()
row.prop(topic, "file_ifc_project")
row = layout.row()
row.prop(topic, "file_ifc_spatial_structure_element")
row = layout.row()
row.operator("bim.add_bcf_header_file")
layout.label(text="Reference Links:")
for index, link in enumerate(topic.reference_links):
row = layout.row(align=True)
row.prop(link, "name")
row.operator("bim.open_uri", icon="URL", text="").uri = link.name
row.operator("bim.remove_bcf_reference_link", icon="X", text="").index = index
row = layout.row()
row.prop(topic, "reference_link")
row = layout.row()
row.operator("bim.add_bcf_reference_link")
layout.label(text="Labels:")
for index, label in enumerate(topic.labels):
row = layout.row(align=True)
row.prop(label, "name", text="")
row.operator("bim.remove_bcf_label", icon="X", text="").index = index
row = layout.row()
row.prop(topic, "label")
row = layout.row()
row.operator("bim.add_bcf_label")
layout.label(text="BIM Snippet:")
if topic.bim_snippet.schema:
row = layout.row(align=True)
row.prop(topic.bim_snippet, "type", emboss=False)
if topic.bim_snippet.schema:
row.operator("bim.open_uri", icon="URL", text="").uri = topic.bim_snippet.schema
row = layout.row(align=True)
row.prop(topic.bim_snippet, "reference", emboss=False)
if topic.bim_snippet.is_external:
row.operator("bim.open_uri", icon="URL", text="").uri = topic.bim_snippet.reference
else:
op = row.operator("bim.open_uri", icon="FILE_FOLDER", text="")
op.uri = os.path.join(bcfxml.filepath, topic.name, topic.bim_snippet.reference)
row.operator("bim.remove_bcf_bim_snippet", icon="X", text="")
else:
row = layout.row(align=True)
row.prop(topic, "bim_snippet_reference")
row.operator("bim.select_bcf_bim_snippet_reference", icon="FILE_FOLDER", text="")
row = layout.row()
row.prop(topic, "bim_snippet_type")
row = layout.row()
row.prop(topic, "bim_snippet_schema")
row = layout.row()
row.operator("bim.add_bcf_bim_snippet")
layout.label(text="Document References:")
for index, doc in enumerate(topic.document_references):
box = self.layout.box()
row = box.row(align=True)
row.prop(doc, "reference")
if doc.is_external:
row.operator("bim.open_uri", icon="URL", text="").uri = doc.reference
else:
op = row.operator("bim.open_uri", icon="FILE_FOLDER", text="")
op.uri = os.path.join(bcfxml.filepath, topic.name, doc.reference)
row.operator("bim.remove_bcf_document_reference", icon="X", text="").index = index
row = box.row(align=True)
row.prop(doc, "description")
row = layout.row(align=True)
row.prop(topic, "document_reference")
row.operator("bim.select_bcf_document_reference", icon="FILE_FOLDER", text="")
row = layout.row()
row.prop(topic, "document_reference_description")
row = layout.row()
row.operator("bim.add_bcf_document_reference")
layout.label(text="Related Topics:")
for index, related_topic in enumerate(topic.related_topics):
row = layout.row(align=True)
row.operator("bim.view_bcf_topic", text=bcfxml.topics[related_topic.name.lower()].title).topic_guid = related_topic.name
row.operator("bim.remove_bcf_related_topic", icon="X", text="").index = index
row = layout.row()
row.prop(topic, "related_topic")
row = layout.row()
row.operator("bim.add_bcf_related_topic")
class BIM_PT_bcf_comments(Panel):
bl_label = "BCF Comments"
bl_idname = "BIM_PT_bcf_comments"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_bcf"
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
scene = context.scene
props = bpy.context.scene.BCFProperties
if props.active_topic_index >= len(props.topics):
layout.label(text="No BCF project is loaded")
return
row = layout.row()
row.prop(props, "comment_text_width")
topic = props.topics[props.active_topic_index]
for comment in topic.comments:
box = self.layout.box()
author_text = "{} ({})".format(comment.author, comment.date)
if comment.modified_author:
author_text = "*{} ({})".format(comment.modified_author, comment.modified_date)
row = box.row(align=True)
row.label(text=author_text, icon="WORDWRAP_ON")
row.prop(
comment, "is_editable", icon="CHECKMARK" if comment.is_editable else "GREASEPENCIL", icon_only=True
)
row.operator("bim.remove_bcf_comment", icon="X", text="").comment_guid = comment.name
if comment.is_editable:
row = box.row()
row.prop(comment, "comment", text="")
else:
col = box.column(align=True)
col.scale_y = 0.8
words = comment.comment.split()
while words:
total_line_chars = 0
line_words = []
while words and total_line_chars < props.comment_text_width:
word = words.pop(0)
line_words.append(word)
total_line_chars += len(word) + 1 # 1 is for the space
col.label(text=" ".join(line_words))
row = layout.row()
row.prop(topic, "comment")
row = layout.row()
row.prop(topic, "has_related_viewpoint")
row = layout.row()
row.operator("bim.add_bcf_comment")
@@ -0,0 +1,21 @@
import bpy
from . import ui, prop, operator
classes = (
prop.CoveToolProject,
prop.CoveToolSimpleAnalysis,
prop.CoveToolProperties,
ui.BIM_UL_covetool_projects,
ui.BIM_PT_covetool,
operator.Login,
operator.RunSimpleAnalysis,
operator.RunAnalysis,
)
def register():
bpy.types.Scene.CoveToolProperties = bpy.props.PointerProperty(type=prop.CoveToolProperties)
def unregister():
del bpy.types.Scene.CoveToolProperties
@@ -0,0 +1,32 @@
import bpy
from . import grid, wall, stair, door, window, slab, opening
classes = (
grid.BIM_OT_add_object,
wall.BIM_OT_add_object,
stair.BIM_OT_add_object,
door.BIM_OT_add_object,
window.BIM_OT_add_object,
slab.BIM_OT_add_object,
opening.BIM_OT_add_object,
)
def register():
bpy.types.VIEW3D_MT_mesh_add.append(grid.add_object_button)
bpy.types.VIEW3D_MT_mesh_add.append(wall.add_object_button)
bpy.types.VIEW3D_MT_mesh_add.append(stair.add_object_button)
bpy.types.VIEW3D_MT_mesh_add.append(door.add_object_button)
bpy.types.VIEW3D_MT_mesh_add.append(window.add_object_button)
bpy.types.VIEW3D_MT_mesh_add.append(slab.add_object_button)
bpy.types.VIEW3D_MT_mesh_add.append(opening.add_object_button)
def unregister():
bpy.types.VIEW3D_MT_mesh_add.remove(grid.add_object_button)
bpy.types.VIEW3D_MT_mesh_add.remove(wall.add_object_button)
bpy.types.VIEW3D_MT_mesh_add.remove(stair.add_object_button)
bpy.types.VIEW3D_MT_mesh_add.remove(door.add_object_button)
bpy.types.VIEW3D_MT_mesh_add.remove(window.add_object_button)
bpy.types.VIEW3D_MT_mesh_add.remove(slab.add_object_button)
bpy.types.VIEW3D_MT_mesh_add.remove(opening.add_object_button)
+130 -425
View File
@@ -1,3 +1,4 @@
from itertools import chain
import os
import bpy
import uuid
@@ -20,14 +21,13 @@ from . import svgwriter
from . import sheeter
from . import scheduler
from . import schema
from . import bcfstore
from . import ifc
from . import annotation
from . import helper
from bpy_extras.io_utils import ImportHelper
from itertools import cycle
from mathutils import Vector, Matrix, Euler, geometry
from math import radians, atan, tan, cos, sin
from math import radians, degrees, atan, tan, cos, sin
from pathlib import Path
from bpy.app.handlers import persistent
@@ -520,415 +520,6 @@ class RejectElement(bpy.types.Operator):
return {"FINISHED"}
class NewBcfProject(bpy.types.Operator):
bl_idname = "bim.new_bcf_project"
bl_label = "New BCF Project"
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
bcfxml.new_project()
bpy.ops.bim.load_bcf_project()
return {"FINISHED"}
class LoadBcfProject(bpy.types.Operator):
bl_idname = "bim.load_bcf_project"
bl_label = "Load BCF Project"
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
if self.filepath:
bcfxml.get_project(self.filepath)
bpy.context.scene.BCFProperties.name = bcfxml.project.name
bpy.ops.bim.load_bcf_topics()
bpy.context.scene.BCFProperties.is_loaded = True
return {"FINISHED"}
def invoke(self, context, event):
context.window_manager.fileselect_add(self)
return {"RUNNING_MODAL"}
class LoadBcfTopics(bpy.types.Operator):
bl_idname = "bim.load_bcf_topics"
bl_label = "Load BCF Topics"
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
bcfxml.get_topics()
while len(bpy.context.scene.BCFProperties.topics) > 0:
bpy.context.scene.BCFProperties.topics.remove(0)
for topic in bcfxml.topics.values():
new = bpy.context.scene.BCFProperties.topics.add()
data_map = {
"name": topic.guid,
"title": topic.title,
"type": topic.topic_type,
"status": topic.topic_status,
"priority": topic.priority,
"stage": topic.stage,
"creation_date": topic.creation_date,
"creation_author": topic.creation_author,
"modified_date": topic.modified_date,
"modified_author": topic.modified_author,
"assigned_to": topic.assigned_to,
"due_date": topic.due_date,
"description": topic.description
}
for key, value in data_map.items():
if value is not None:
setattr(new, key, str(value))
for reference_link in topic.reference_links:
new2 = new.reference_links.add()
new2.name = reference_link
for label in topic.labels:
new2 = new.labels.add()
new2.name = label
if topic.bim_snippet:
data_map = {
"type": topic.bim_snippet.snippet_type,
"is_external": topic.bim_snippet.is_external,
"reference": topic.bim_snippet.reference,
"schema": topic.bim_snippet.reference_schema
}
for key, value in data_map.items():
if value is not None:
setattr(new.bim_snippet, key, value)
for doc in topic.document_references:
new2 = new.document_references.add()
data_map = {
"reference": doc.referenced_document,
"description": doc.description,
"guid": doc.guid,
"is_external": doc.is_external
}
for key, value in data_map.items():
if value is not None:
setattr(new2, key, value)
for related_topic in topic.related_topics:
new2 = new.related_topics.add()
new2.name = related_topic.guid
bpy.ops.bim.load_bcf_comments(topic_guid = topic.guid)
return {"FINISHED"}
class LoadBcfComments(bpy.types.Operator):
bl_idname = "bim.load_bcf_comments"
bl_label = "Load BCF Comments"
topic_guid: bpy.props.StringProperty()
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
bcfxml.get_comments(self.topic_guid)
blender_topic = bpy.context.scene.BCFProperties.topics.get(self.topic_guid)
for comment in bcfxml.topics[self.topic_guid].comments.values():
new = blender_topic.comments.add()
data_map = {
"name": comment.guid,
"comment": comment.comment,
"viewpoint": comment.viewpoint.guid if comment.viewpoint else None,
"date": comment.date,
"author": comment.author,
"modified_date": comment.modified_date,
"modified_author": comment.modified_author,
}
for key, value in data_map.items():
if value is not None:
setattr(new, key, str(value))
return {"FINISHED"}
class EditBcfProjectName(bpy.types.Operator):
bl_idname = "bim.edit_bcf_project_name"
bl_label = "Edit BCF Project Name"
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
bcfxml.project.name = bpy.context.scene.BCFProperties.name
bcfxml.edit_project()
return {"FINISHED"}
class EditBcfAuthor(bpy.types.Operator):
bl_idname = "bim.edit_bcf_author"
bl_label = "Edit BCF Author"
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
bcfxml.author = bpy.context.scene.BCFProperties.author
return {"FINISHED"}
class EditBcfTopicName(bpy.types.Operator):
bl_idname = "bim.edit_bcf_topic_name"
bl_label = "Edit BCF Topic Name"
def execute(self, context):
props = bpy.context.scene.BCFProperties
blender_topic = props.topics[props.active_topic_index]
bcfxml = bcfstore.BcfStore.get_bcfxml()
topic = bcfxml.topics[blender_topic.name]
topic.title = blender_topic.title
bcfxml.edit_topic(topic)
return {"FINISHED"}
class EditBcfTopic(bpy.types.Operator):
bl_idname = "bim.edit_bcf_topic"
bl_label = "Edit BCF Topic"
def execute(self, context):
props = bpy.context.scene.BCFProperties
blender_topic = props.topics[props.active_topic_index]
bcfxml = bcfstore.BcfStore.get_bcfxml()
topic = bcfxml.topics[blender_topic.name]
topic.title = blender_topic.title or None
topic.priority = blender_topic.priority or None
topic.due_date = blender_topic.due_date or None
topic.assigned_to = blender_topic.assigned_to or None
topic.stage = blender_topic.stage or None
topic.description = blender_topic.description or None
topic.topic_status = blender_topic.status or None
topic.topic_type = blender_topic.type or None
bcfxml.edit_topic(topic)
return {"FINISHED"}
class SaveBcfProject(bpy.types.Operator):
bl_idname = "bim.save_bcf_project"
bl_label = "Save BCF Project"
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
bcfxml.save_project(self.filepath)
return {"FINISHED"}
def invoke(self, context, event):
context.window_manager.fileselect_add(self)
return {"RUNNING_MODAL"}
class AddBcfTopic(bpy.types.Operator):
bl_idname = "bim.add_bcf_topic"
bl_label = "Add BCF Topic"
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
bcfxml.add_topic()
new = bpy.context.scene.BCFProperties.topics.add()
new.name = "New Topic"
bpy.ops.bim.load_bcf_topics()
return {"FINISHED"}
class ViewBcfTopic(bpy.types.Operator):
bl_idname = "bim.view_bcf_topic"
bl_label = "Get BCF Topic"
topic_guid: bpy.props.StringProperty()
def execute(self, context):
for index, topic in enumerate(bpy.context.scene.BCFProperties.topics):
if topic.guid.lower() == self.topic_guid.lower():
bpy.context.scene.BCFProperties.active_topic_index = index
return {"FINISHED"}
class ActivateBcfViewpoint(bpy.types.Operator):
bl_idname = "bim.activate_bcf_viewpoint"
bl_label = "Activate BCF Viewpoint"
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
props = bpy.context.scene.BCFProperties
blender_topic = props.topics[props.active_topic_index]
topic = bcfxml.topics[blender_topic.guid]
if not topic.viewpoints:
return {"FINISHED"}
viewpoint_guid = blender_topic.viewpoints
viewpoint = topic.viewpoints[viewpoint_guid]
obj = bpy.data.objects.get("Viewpoint")
if not obj:
obj = bpy.data.objects.new("Viewpoint", bpy.data.cameras.new("Viewpoint"))
bpy.context.scene.collection.objects.link(obj)
bpy.context.scene.camera = obj
cam_width = bpy.context.scene.render.resolution_x
cam_height = bpy.context.scene.render.resolution_y
cam_aspect = cam_width / cam_height
if viewpoint.snapshot:
obj.data.show_background_images = True
while len(obj.data.background_images) > 0:
obj.data.background_images.remove(obj.data.background_images[0])
background = obj.data.background_images.new()
background.image = bpy.data.images.load(
os.path.join(bcfxml.filepath, topic.guid, viewpoint.snapshot)
)
src_width = background.image.size[0]
src_height = background.image.size[1]
src_aspect = src_width / src_height
if src_aspect > cam_aspect:
background.frame_method = "FIT"
else:
background.frame_method = "CROP"
background.display_depth = "FRONT"
area = next(area for area in bpy.context.screen.areas if area.type == "VIEW_3D")
area.spaces[0].region_3d.view_perspective = "CAMERA"
if viewpoint.orthogonal_camera:
camera = viewpoint.orthogonal_camera
obj.data.type = "ORTHO"
obj.data.ortho_scale = viewpoint.orthogonal_camera.view_to_world_scale
elif viewpoint.perspective_camera:
camera = viewpoint.perspective_camera
obj.data.type = "PERSP"
if cam_aspect >= 1:
obj.data.angle = radians(camera.field_of_view)
else:
# https://blender.stackexchange.com/questions/23431/how-to-set-camera-horizontal-and-vertical-fov
obj.data.angle = 2 * atan((0.5 * cam_height) / (0.5 * cam_width / tan(radians(camera.field_of_view) / 2)))
self.set_viewpoint_components(viewpoint)
gp = bpy.data.grease_pencils.get("BCF")
if gp:
bpy.data.grease_pencils.remove(gp)
if viewpoint.lines:
self.draw_lines(viewpoint)
self.delete_clipping_planes()
if viewpoint.clipping_planes:
self.create_clipping_planes(viewpoint)
self.delete_bitmaps()
if viewpoint.bitmaps:
self.create_bitmaps(bcfxml, viewpoint, topic)
z_axis = Vector((-camera.camera_direction.x, -camera.camera_direction.y, -camera.camera_direction.z)).normalized()
y_axis = Vector((camera.camera_up_vector.x, camera.camera_up_vector.y, camera.camera_up_vector.z)).normalized()
x_axis = y_axis.cross(z_axis).normalized()
rotation = Matrix((x_axis, y_axis, z_axis))
rotation.invert()
location = Vector((camera.camera_view_point.x, camera.camera_view_point.y, camera.camera_view_point.z))
obj.matrix_world = rotation.to_4x4()
obj.location = location
return {"FINISHED"}
def set_viewpoint_components(self, viewpoint):
selected_global_ids = [s.ifc_guid for s in viewpoint.components.selection]
exception_global_ids = [v.ifc_guid for v in viewpoint.components.visibility.exceptions]
global_id_colours = {}
for coloring in viewpoint.components.coloring:
for component in coloring.components:
global_id_colours.setdefault(component.ifc_guid, coloring.color)
for obj in bpy.data.objects:
global_id = obj.BIMObjectProperties.attributes.get("GlobalId")
if not global_id:
continue
global_id = global_id.string_value
is_visible = viewpoint.components.visibility.default_visibility
if global_id in exception_global_ids:
is_visible = not is_visible
if not is_visible:
obj.hide_set(True)
continue
if "IfcSpace" in obj.name:
is_visible = viewpoint.components.viewSetuphints.spacesVisible
elif "IfcOpeningElement" in obj.name:
is_visible = viewpoint.components.viewSetuphints.openingsVisible
obj.hide_set(not is_visible)
if not is_visible:
continue
obj.select_set(global_id in selected_global_ids)
if global_id in global_id_colours:
obj.color = self.hex_to_rgb(global_id_colours[global_id])
def draw_lines(self, viewpoint):
gp = bpy.data.grease_pencils.new("BCF")
scene = bpy.context.scene
scene.grease_pencil = gp
scene.frame_set(1)
layer = gp.layers.new("BCF Annotation", set_active=True)
layer.thickness = 3
layer.color = (1, 0, 0)
frame = layer.frames.new(1)
stroke = frame.strokes.new()
stroke.display_mode = "3DSPACE"
stroke.points.add(len(viewpoint.lines) * 2)
coords = []
for l in viewpoint.lines:
coords.extend([l.start_point.x, l.start_point.y, l.start_point.z, l.end_point.x, l.end_point.y, l.end_point.z])
stroke.points.foreach_set("co", coords)
def create_clipping_planes(self, viewpoint):
n = 0
for plane in viewpoint.clipping_planes:
bpy.ops.bim.add_section_plane()
if n == 0:
obj = bpy.data.objects["Section"]
else:
obj = bpy.data.objects["Section.{:03d}".format(n)]
obj.location = (plane.location.x, plane.location.y, plane.location.z)
obj.rotation_mode = "QUATERNION"
obj.rotation_quaternion = Vector((plane.direction.x, plane.direction.y, plane.direction.z)).to_track_quat(
"Z", "Y"
)
n += 1
def delete_clipping_planes(self):
collection = bpy.data.collections.get("Sections")
if not collection:
return
for section in collection.objects:
bpy.context.view_layer.objects.active = section
bpy.ops.bim.remove_section_plane()
def delete_bitmaps(self):
collection = bpy.data.collections.get("Bitmaps")
if not collection:
collection = bpy.data.collections.new("Bitmaps")
bpy.context.scene.collection.children.link(collection)
for bitmap in collection.objects:
bpy.data.objects.remove(bitmap)
def create_bitmaps(self, bcfxml, viewpoint, topic):
collection = bpy.data.collections.get("Bitmaps")
if not collection:
collection = bpy.data.collections.new("Bitmaps")
for bitmap in viewpoint.bitmaps:
obj = bpy.data.objects.new("Bitmap", None)
obj.empty_display_type = "IMAGE"
image = bpy.data.images.load(os.path.join(bcfxml.filepath, topic.guid, bitmap.reference))
src_width = image.size[0]
src_height = image.size[1]
if src_height > src_width:
obj.empty_display_size = bitmap.height
else:
obj.empty_display_size = bitmap.height * (src_width / src_height)
obj.data = image
y = Vector((bitmap.up.x, bitmap.up.y, bitmap.up.z))
z = Vector((bitmap.normal.x, bitmap.normal.y, bitmap.normal.z))
x = y.cross(z)
obj.matrix_world = Matrix(
[[x[0], y[0], z[0], 0], [x[1], y[1], z[1], 0], [x[2], y[2], z[2], 0], [0, 0, 0, 1]]
)
obj.location = (bitmap.location.x, bitmap.location.y, bitmap.location.z)
collection.objects.link(obj)
def hex_to_rgb(self, value):
value = value.lstrip("#")
lv = len(value)
t = tuple(int(value[i : i + lv // 3], 16) for i in range(0, lv, lv // 3))
return [t[0] / 255.0, t[1] / 255.0, t[2] / 255.0, 1]
class OpenUri(bpy.types.Operator):
bl_idname = "bim.open_uri"
bl_label = "Open URI"
@@ -939,16 +530,6 @@ class OpenUri(bpy.types.Operator):
return {"FINISHED"}
class OpenBcfReferenceLink(bpy.types.Operator):
bl_idname = "bim.open_bcf_reference_link"
bl_label = "Open BCF Reference Link"
index: bpy.props.IntProperty()
def execute(self, context):
webbrowser.open(bpy.context.scene.BCFProperties.topic_links[self.index].name)
return {"FINISHED"}
class SelectAudited(bpy.types.Operator):
bl_idname = "bim.select_audited"
bl_label = "Select Audited"
@@ -1987,11 +1568,12 @@ class SelectSmartGroupedClashesPath(bpy.types.Operator):
class ExecuteIfcClash(bpy.types.Operator):
bl_idname = "bim.execute_ifc_clash"
bl_label = "Execute IFC Clash"
filename_ext = ".json"
filename_ext = ".bcf"
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def invoke(self, context, event):
self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ".json")
if ".json" not in bpy.data.filepath:
self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ".bcf")
WindowManager = context.window_manager
WindowManager.fileselect_add(self)
return {"RUNNING_MODAL"}
@@ -2000,11 +1582,34 @@ class ExecuteIfcClash(bpy.types.Operator):
import ifcclash
settings = ifcclash.IfcClashSettings()
self.filepath = bpy.path.ensure_ext(self.filepath, ".json")
if ".json" not in self.filepath:
self.filepath = bpy.path.ensure_ext(self.filepath, ".bcf")
settings.output = self.filepath
settings.logger = logging.getLogger("Clash")
settings.logger.setLevel(logging.DEBUG)
ifc_clasher = ifcclash.IfcClasher(settings)
if bpy.context.scene.BIMProperties.should_create_clash_snapshots:
def get_viewpoint_snapshot(self, viewpoint, mat):
camera = bpy.data.objects.get('IFC Clash Camera')
if not camera:
camera = bpy.data.objects.new("IFC Clash Camera", bpy.data.cameras.new("IFC Clash Camera"))
bpy.context.scene.collection.objects.link(camera)
camera.matrix_world = Matrix(mat)
bpy.context.scene.camera = camera
camera.data.angle = radians(60)
area = next(area for area in bpy.context.screen.areas if area.type == "VIEW_3D")
area.spaces[0].region_3d.view_perspective = "CAMERA"
area.spaces[0].shading.show_xray = True
bpy.context.scene.render.resolution_x = 480
bpy.context.scene.render.resolution_y = 270
bpy.context.scene.render.image_settings.file_format = "PNG"
bpy.context.scene.render.filepath = os.path.join(bpy.context.scene.BIMProperties.data_dir, "snapshot.png")
bpy.ops.render.opengl(write_still=True)
return bpy.context.scene.render.filepath
ifcclash.IfcClasher.get_viewpoint_snapshot = get_viewpoint_snapshot
ifc_clasher.clash_sets = []
for clash_set in bpy.context.scene.BIMProperties.clash_sets:
self.a = []
@@ -2532,6 +2137,25 @@ class OpenView(bpy.types.Operator):
return {"FINISHED"}
class OpenViewCamera(bpy.types.Operator):
bl_idname = "bim.open_view_camera"
bl_label = "Open View Camera"
view_name: bpy.props.StringProperty()
def execute(self, context):
new_drawing_index = bpy.context.scene.DocProperties.drawings.find(self.view_name)
bpy.context.scene.DocProperties.active_drawing_index = new_drawing_index
drawing = bpy.context.scene.DocProperties.drawings[new_drawing_index]
bpy.context.view_layer.objects.active = drawing.camera
bpy.ops.object.select_all(action="DESELECT")
drawing.camera.select_set(True)
for area in bpy.context.screen.areas:
if area.ui_type == "PROPERTIES":
for space in area.spaces:
space.context = "DATA"
return {"FINISHED"}
class CutSection(bpy.types.Operator):
bl_idname = "bim.cut_section"
bl_label = "Cut Section"
@@ -3478,6 +3102,10 @@ class ExportIfcCsv(bpy.types.Operator):
ifc_csv.output = self.filepath
ifc_csv.attributes = [a.name for a in bpy.context.scene.BIMProperties.csv_attributes]
ifc_csv.selector = selector
if bpy.context.scene.BIMProperties.csv_delimiter == "CUSTOM":
ifc_csv.delimiter = bpy.context.scene.BIMProperties.csv_custom_delimiter
else:
ifc_csv.delimiter = bpy.context.scene.BIMProperties.csv_delimiter
ifc_csv.export(ifc_file, results)
return {"FINISHED"}
@@ -3617,7 +3245,10 @@ class AddAnnotation(bpy.types.Operator):
obj = annotation.Annotator.add_text()
else:
obj = annotation.Annotator.get_annotation_obj(self.obj_name, self.data_type)
obj = annotation.Annotator.add_line_to_annotation(obj)
if self.obj_name == 'Break':
obj = annotation.Annotator.add_plane_to_annotation(obj)
else:
obj = annotation.Annotator.add_line_to_annotation(obj)
bpy.ops.object.select_all(action="DESELECT")
bpy.context.view_layer.objects.active = obj
bpy.ops.object.mode_set(mode="EDIT")
@@ -5010,3 +4641,77 @@ class LinkIfc(bpy.types.Operator):
def invoke(self, context, event):
context.window_manager.fileselect_add(self)
return {"RUNNING_MODAL"}
class SnapSpacesTogether(bpy.types.Operator):
bl_idname = "bim.snap_spaces_together"
bl_label = "Snap Spaces Together"
def execute(self, context):
threshold = 0.5
processed_polygons = set()
for obj in bpy.context.selected_objects:
if obj.type != "MESH":
continue
for polygon in obj.data.polygons:
center = obj.matrix_world @ polygon.center
distance = None
for obj2 in bpy.context.selected_objects:
if obj2 == obj or obj.type != "MESH":
continue
result = obj2.ray_cast(
obj2.matrix_world.inverted() @ center,
polygon.normal,
distance=threshold
)
if not result[0]:
continue
hit = obj2.matrix_world @ result[1]
distance = (hit - center).length / 2
if distance < 0.01:
distance = None
break
if (obj2.name, result[3]) in processed_polygons:
distance *= 2
continue
offset = polygon.normal * distance * -1
processed_polygons.add((obj2.name, result[3]))
for v in obj2.data.polygons[result[3]].vertices:
obj2.data.vertices[v].co += offset
break
if distance:
offset = polygon.normal * distance
processed_polygons.add((obj.name, polygon.index))
for v in polygon.vertices:
obj.data.vertices[v].co += offset
return {"FINISHED"}
class CopyGrid(bpy.types.Operator):
bl_idname = "bim.add_grid"
bl_label = "Add Grid"
def execute(self, context):
props = context.scene.DocProperties
if props.active_drawing_index is None or len(props.drawings) == 0:
return {"CANCELLED"}
drawing = props.drawings[props.active_drawing_index]
collection = bpy.data.collections.get("IfcGroup/" + drawing.name)
existing = [obj for obj in collection.objects if obj.name.startswith('IfcGridAxis')]
for obj in existing:
collection.objects.unlink(obj)
source = [obj
for coll in bpy.data.collections
if coll.name.startswith('IfcGrid')
for obj in coll.all_objects
if obj.type == 'MESH']
for obj in source:
dstobj = obj.copy()
dstobj.data = dstobj.data.copy()
collection.objects.link(dstobj)
return {"FINISHED"}
+22 -104
View File
@@ -5,7 +5,6 @@ import ifcopenshell.util.pset
from pathlib import Path
from . import export_ifc
from . import schema
from . import bcfstore
from . import ifc
from . import annotation
from . import decoration
@@ -52,7 +51,6 @@ persons_enum = []
organisations_enum = []
sheets_enum = []
vector_styles_enum = []
bcfviewpoints_enum = None
@persistent
@@ -361,9 +359,11 @@ def refreshTitleblocks(self, context):
def toggleDecorations(self, context):
toggle = self.should_draw_decorations
if toggle:
decoration.DimensionDecorator.install(self, context)
for dec in decoration.all_decorators:
dec.install(self, context)
else:
decoration.DimensionDecorator.uninstall()
for dec in decoration.all_decorators:
dec.uninstall()
def getScenarios(self, context):
@@ -540,35 +540,6 @@ def refreshFontSize(self, context):
annotation.Annotator.resize_text(context.active_object)
def updateBcfProjectName(self, context):
bpy.ops.bim.edit_bcf_project_name()
def updateBcfAuthor(self, context):
bpy.ops.bim.edit_bcf_author()
def updateBcfTopicName(self, context):
bpy.ops.bim.edit_bcf_topic_name()
def updateBcfTopicIsEditable(self, context):
if not self.is_editable:
print("EDITING!")
bpy.ops.bim.edit_bcf_topic()
def refreshBcfTopic(self, context):
global bcfviewpoints_enum
bcfviewpoints_enum = None
props = bpy.context.scene.BCFProperties
bcfxml = bcfstore.BcfStore.get_bcfxml()
topic = props.topics[props.active_topic_index]
header = bcfxml.get_header(topic.name)
getBcfViewpoints(self, context)
class StrProperty(PropertyGroup):
pass
@@ -594,6 +565,12 @@ class Subcontext(PropertyGroup):
ifc_definition_id: IntProperty(name="IFC Definition ID")
class Representation(PropertyGroup):
name: StringProperty(name="Name")
type: StringProperty(name="Type")
ifc_definition_id: IntProperty(name="IFC Definition ID")
class MaterialLayer(PropertyGroup):
name: StringProperty(name="Name")
material: PointerProperty(name="Material", type=bpy.types.Material)
@@ -707,6 +684,9 @@ class DocProperties(PropertyGroup):
ifc_files: CollectionProperty(name="IFCs", type=StrProperty)
drawing_styles: CollectionProperty(name="Drawing Styles", type=DrawingStyle)
should_draw_decorations: BoolProperty(name="Should Draw Decorations", update=toggleDecorations)
decorations_colour: FloatVectorProperty(name="Decorations Colour", subtype="COLOR", default=(1, 0, 0, 1),
min=0.0, max=1.0, size=4,
update=toggleDecorations)
class BIMCameraProperties(PropertyGroup):
@@ -939,67 +919,6 @@ class Constraint(PropertyGroup):
user_defined_qualifier: StringProperty(name="Custom Qualifier")
def getBcfViewpoints(self, context):
global bcfviewpoints_enum
if bcfviewpoints_enum is None:
bcfviewpoints_enum = []
props = bpy.context.scene.BCFProperties
bcfxml = bcfstore.BcfStore.get_bcfxml()
topic = props.topics[props.active_topic_index]
viewpoints = bcfxml.get_viewpoints(topic.name)
bcfviewpoints_enum.extend([(v, f"Viewpoint {i+1}", "") for i, v in enumerate(viewpoints.keys())])
return bcfviewpoints_enum
class BcfBimSnippet(PropertyGroup):
schema: StringProperty(name="Schema")
reference: StringProperty(name="Reference")
type: StringProperty(name="Type")
is_external: BoolProperty(name="Is External")
class BcfDocumentReference(PropertyGroup):
reference: StringProperty(name="Reference")
description: StringProperty(name="Description")
guid: StringProperty(name="GUID")
is_external: BoolProperty(name="Is External")
class BcfComment(PropertyGroup):
name: StringProperty(name="GUID")
date: StringProperty(name="Date")
author: StringProperty(name="Author")
comment: StringProperty(name="Comment")
viewpoint: StringProperty(name="Viewpoint")
modified_date: StringProperty(name="Modified Date")
modified_author: StringProperty(name="Modified Author")
class BcfTopic(PropertyGroup):
name: StringProperty(name="GUID")
title: StringProperty(default="", name="Title", update=updateBcfTopicName)
type: StringProperty(default="", name="Type")
status: StringProperty(default="", name="Status")
priority: StringProperty(default="", name="Priority")
stage: StringProperty(default="", name="Stage")
creation_date: StringProperty(default="", name="Date")
creation_author: StringProperty(default="", name="Author")
modified_date: StringProperty(default="", name="Modified Date")
modified_author: StringProperty(default="", name="Modified By")
assigned_to: StringProperty(default="", name="Assigned To")
due_date: StringProperty(default="", name="Due Date")
description: StringProperty(default="", name="Description")
viewpoints: EnumProperty(items=getBcfViewpoints, name="BCF Viewpoints")
files: CollectionProperty(name="Files", type=StrProperty)
reference_links: CollectionProperty(name="Reference Links", type=StrProperty)
labels: CollectionProperty(name="Labels", type=StrProperty)
bim_snippet: PointerProperty(type=BcfBimSnippet)
document_references: CollectionProperty(name="Document References", type=BcfDocumentReference)
related_topics: CollectionProperty(name="Related Topics", type=StrProperty)
comments: CollectionProperty(name="Comments", type=BcfComment)
is_editable: BoolProperty(name="Is Editable", default=False, update=updateBcfTopicIsEditable)
class PropertySetTemplate(PropertyGroup):
global_id: StringProperty(name="Global ID")
name: StringProperty(name="Name")
@@ -1350,6 +1269,7 @@ class BIMProperties(PropertyGroup):
)
export_should_force_faceted_brep: BoolProperty(name="Export with Faceted Breps", default=False)
export_should_force_triangulation: BoolProperty(name="Export with Triangulation", default=False)
export_should_export_from_memory: BoolProperty(name="Export from Memory", default=False)
import_should_ignore_site_coordinates: BoolProperty(name="Import Ignoring Site Coordinates", default=False)
import_should_ignore_building_coordinates: BoolProperty(name="Import Ignoring Building Coordinates", default=False)
import_should_reset_absolute_coordinates: BoolProperty(name="Import Resetting Absolute Coordinates", default=False)
@@ -1432,6 +1352,12 @@ class BIMProperties(PropertyGroup):
)
ifc_selector: StringProperty(default="", name="IFC Selector")
csv_attributes: CollectionProperty(name="CSV Attributes", type=StrProperty)
csv_delimiter: EnumProperty(
items=[(";", ";", ""), (",", ",", ""), (".", ".", ""), ("CUSTOM", "Custom", ""),],
name="IFC CSV Delimiter",
default=";",
)
csv_custom_delimiter: StringProperty(default="", name="Custom Delimiter")
document_information: CollectionProperty(name="Document Information", type=DocumentInformation)
active_document_information_index: IntProperty(name="Active Document Information Index")
document_references: CollectionProperty(name="Document References", type=DocumentReference)
@@ -1439,6 +1365,7 @@ class BIMProperties(PropertyGroup):
blender_clash_set_a: CollectionProperty(name="Blender Clash Set A", type=StrProperty)
blender_clash_set_b: CollectionProperty(name="Blender Clash Set B", type=StrProperty)
clash_sets: CollectionProperty(name="Clash Sets", type=ClashSet)
should_create_clash_snapshots: BoolProperty(name="Create Snapshots", default=True)
clash_results_path: StringProperty(name="Clash Results Path")
smart_grouped_clashes_path: StringProperty(name="Smart Grouped Clashes Path")
active_clash_set_index: IntProperty(name="Active Clash Set Index")
@@ -1505,15 +1432,6 @@ class BIMProperties(PropertyGroup):
presentation_layers: CollectionProperty(name="Presentation Layers", type=PresentationLayer)
class BCFProperties(PropertyGroup):
is_loaded: BoolProperty(name="Is Loaded", default=False)
comment_text_width: IntProperty(name="Comment Text Width", default=40)
name: StringProperty(default="", name="Project Name", update=updateBcfProjectName)
author: StringProperty(default="john@doe.com", name="Author Email", update=updateBcfAuthor)
topics: CollectionProperty(name="BCF Topics", type=BcfTopic)
active_topic_index: IntProperty(name="Active BCF Topic Index", update=refreshBcfTopic)
class MapConversion(PropertyGroup):
eastings: StringProperty(name="Eastings")
northings: StringProperty(name="Northings")
@@ -1590,7 +1508,7 @@ class BIMObjectProperties(PropertyGroup):
has_boundary_condition: BoolProperty(name="Has Boundary Condition")
boundary_condition: PointerProperty(name="Boundary Condition", type=BoundaryCondition)
structural_member_connection: PointerProperty(name="Structural Member Connection", type=bpy.types.Object)
representation_contexts: CollectionProperty(name="Representation Contexts", type=Subcontext)
representations: CollectionProperty(name="Representations", type=Representation)
# Address applies to IfcSite's SiteAddress and IfcBuilding's BuildingAddress
address: PointerProperty(name="Address", type=Address)
+62 -229
View File
@@ -1,6 +1,6 @@
import os
import bpy
from . import bcfstore
from . import ifc
from bpy.types import Panel
from bpy.props import StringProperty
@@ -638,27 +638,34 @@ class BIM_PT_representations(Panel):
layout = self.layout
props = context.active_object.BIMObjectProperties
if not props.representation_contexts:
if not props.representations:
layout.label(text="No representations found")
row = layout.row(align=True)
row.prop(bpy.context.scene.BIMProperties, "available_contexts", text="")
row.prop(bpy.context.scene.BIMProperties, "available_subcontexts", text="")
row.prop(bpy.context.scene.BIMProperties, "available_target_views", text="")
op = row.operator("bim.switch_context", icon="ADD", text="")
op.has_target_context = False
# TODO: reimplement with incremental editing
# op = row.operator("bim.switch_context", icon="ADD", text="")
# op.has_target_context = False
for index, subcontext in enumerate(props.representation_contexts):
self.file = ifc.IfcStore.get_file()
for index, representation in enumerate(props.representations):
row = layout.row(align=True)
row.prop(subcontext, "context", text="")
row.prop(subcontext, "name", text="")
row.prop(subcontext, "target_view", text="")
op = row.operator("bim.switch_context", icon="OUTLINER_DATA_MESH", text="")
op.has_target_context = True
op.context_name = subcontext.context
op.subcontext_name = subcontext.name
op.target_view_name = subcontext.target_view
row.operator("bim.remove_context", icon="X", text="").index = index
row.prop(representation, "name", text="")
row.prop(representation, "type", text="")
if not representation.ifc_definition_id:
continue
subcontext = self.file.by_id(representation.ifc_definition_id).ContextOfItems
if subcontext.is_a() == "IfcGeometricRepresentationContext":
continue
# TODO: reimplement with incremental editing
# op = row.operator("bim.switch_context", icon="OUTLINER_DATA_MESH", text="")
# op.has_target_context = True
# op.context_name = subcontext.ContextType
# op.subcontext_name = subcontext.ContextIdentifier
# op.target_view_name = subcontext.TargetView
# row.operator("bim.remove_context", icon="X", text="").index = index
class BIM_PT_classification_references(Panel):
@@ -1643,216 +1650,13 @@ class BIM_PT_ifccsv(Panel):
row.operator("bim.remove_csv_attribute", icon="X", text="").index = index
row = layout.row(align=True)
row.operator("bim.export_ifccsv", icon="EXPORT")
row.operator("bim.import_ifccsv", icon="IMPORT")
class BIM_PT_bcf(Panel):
bl_label = "BIM Collaboration Format (BCF)"
bl_idname = "BIM_PT_bcf"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
scene = context.scene
props = bpy.context.scene.BCFProperties
row.prop(props, "csv_delimiter")
row = layout.row(align=True)
row.prop(props, "csv_custom_delimiter")
row = layout.row(align=True)
row.operator("bim.new_bcf_project", text="New Project")
row.operator("bim.load_bcf_project", text="Load Project")
if not props.is_loaded:
return
row.operator("bim.save_bcf_project", text="Save Project")
row = layout.row()
row.prop(props, "name")
row = layout.row()
row.prop(props, "author")
props = bpy.context.scene.BCFProperties
row = layout.row()
row.template_list("BIM_UL_topics", "", props, "topics", props, "active_topic_index")
col = row.column(align=True)
col.operator("bim.add_bcf_topic", icon="ADD", text="")
if props.active_topic_index < len(props.topics):
topic = props.topics[props.active_topic_index]
col.prop(topic, "is_editable", icon="CHECKMARK" if topic.is_editable else "GREASEPENCIL", icon_only=True)
if props.active_topic_index < len(props.topics):
topic = props.topics[props.active_topic_index]
row = layout.row()
row.enabled = topic.is_editable
row.prop(topic, "description", text="")
row = layout.row()
row.prop(topic, "viewpoints")
row.operator("bim.activate_bcf_viewpoint", icon="SCENE", text="")
col = layout.column(align=True)
if topic.type:
col.prop(topic, "type", emboss=topic.is_editable)
if topic.status:
col.prop(topic, "status", emboss=topic.is_editable)
if topic.priority:
col.prop(topic, "priority", emboss=topic.is_editable)
if topic.stage:
col.prop(topic, "stage", emboss=topic.is_editable)
if topic.assigned_to:
col.prop(topic, "assigned_to", emboss=topic.is_editable)
if topic.due_date:
col.prop(topic, "due_date", emboss=topic.is_editable)
col = layout.column(align=True)
if topic.modified_date:
col.prop(topic, "modified_date", emboss=False)
col.prop(topic, "modified_author", emboss=False)
else:
col.prop(topic, "creation_date", emboss=False)
col.prop(topic, "creation_author", emboss=False)
class BIM_PT_bcf_metadata(Panel):
bl_label = "BCF Metadata"
bl_idname = "BIM_PT_bcf_metadata"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_bcf"
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
scene = context.scene
props = bpy.context.scene.BCFProperties
if props.active_topic_index >= len(props.topics):
layout.label(text="No BCF project is loaded")
return
topic = props.topics[props.active_topic_index]
bcfxml = bcfstore.BcfStore.get_bcfxml()
bcf_topic = bcfxml.topics[topic.name]
if bcf_topic.header:
layout.label(text="Header Files:")
for index, f in enumerate(bcf_topic.header.files):
box = self.layout.box()
row = box.row(align=True)
row.label(text=f.filename, icon="FILE_BLANK")
if f.is_external:
row.operator("bim.open_uri", icon="URL", text="").uri = f.reference
else:
op = row.operator("bim.open_uri", icon="FILE_FOLDER", text="")
op.uri = os.path.join(bcfxml.filepath, topic.name, f.reference)
box.label(text=f.date)
#box.label(text=f.ifc_project)
#box.label(text=f.ifc_spatial_structure_element)
if topic.reference_links:
layout.label(text="Reference Links:")
for index, link in enumerate(topic.reference_links):
row = layout.row(align=True)
row.prop(link, "name")
row.operator("bim.open_uri", icon="URL", text="").uri = link.name
if topic.labels:
layout.label(text="Labels:")
for index, label in enumerate(topic.labels):
row = layout.row(align=True)
row.prop(label, "name", text="")
if topic.bim_snippet.schema:
layout.label(text="BIM Snippet:")
row = layout.row(align=True)
row.prop(topic.bim_snippet, "type")
if topic.bim_snippet.schema:
row.operator("bim.open_uri", icon="URL", text="").uri = topic.bim_snippet.schema
row = layout.row(align=True)
row.prop(topic.bim_snippet, "reference")
if topic.bim_snippet.is_external:
row.operator("bim.open_uri", icon="URL", text="").uri = topic.bim_snippet.reference
else:
op = row.operator("bim.open_uri", icon="FILE_FOLDER", text="")
op.uri = os.path.join(bcfxml.filepath, topic.name, topic.bim_snippet.reference)
if topic.document_references:
layout.label(text="Document References:")
for index, doc in enumerate(topic.document_references):
box = self.layout.box()
row = box.row(align=True)
row.prop(doc, "reference")
if doc.is_external:
row.operator("bim.open_uri", icon="URL", text="").uri = doc.reference
else:
op = row.operator("bim.open_uri", icon="FILE_FOLDER", text="")
op.uri = os.path.join(bcfxml.filepath, topic.name, doc.reference)
row = box.row(align=True)
row.prop(doc, "description")
if topic.related_topics:
layout.label(text="Related Topics:")
for related_topic in topic.related_topics:
row = layout.row(align=True)
row.operator("bim.view_bcf_topic", text=related_topic.name).topic_guid = related_topic.name
class BIM_PT_bcf_comments(Panel):
bl_label = "BCF Comments"
bl_idname = "BIM_PT_bcf_comments"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_bcf"
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
scene = context.scene
props = bpy.context.scene.BCFProperties
if props.active_topic_index >= len(props.topics):
layout.label(text="No BCF project is loaded")
return
row = layout.row()
row.prop(props, "comment_text_width")
topic = props.topics[props.active_topic_index]
for comment in topic.comments:
box = self.layout.box()
box.separator()
author_text = "{} ({})".format(comment.author, comment.date)
if comment.modified_author:
author_text = "*{} ({})".format(comment.modified_author, comment.modified_date)
box.label(text=author_text, icon="WORDWRAP_ON")
box.separator()
box.scale_y = 0.5
words = comment.comment.split()
while words:
total_line_chars = 0
line_words = []
while words and total_line_chars < props.comment_text_width:
word = words.pop(0)
line_words.append(word)
total_line_chars += len(word) + 1 # 1 is for the space
box.label(text=" ".join(line_words))
box.separator()
row.operator("bim.export_ifccsv", icon="EXPORT")
row.operator("bim.import_ifccsv", icon="IMPORT")
class BIM_PT_qa(Panel):
@@ -2092,6 +1896,8 @@ class BIM_PT_mvd(Panel):
row = layout.row()
row.prop(bim_properties, "import_export_should_roundtrip_native")
row = layout.row()
row.prop(bim_properties, "export_should_export_from_memory")
row = layout.row()
row.prop(bim_properties, "import_should_use_cpu_multiprocessing")
row = layout.row()
row.prop(bim_properties, "import_should_import_with_profiling")
@@ -2182,6 +1988,17 @@ class BIM_UL_generic(bpy.types.UIList):
layout.label(text="", translate=False)
class BIM_UL_drawinglist(bpy.types.UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
if item:
row = layout.row(align=True)
row.prop(item, "name", text="", emboss=False)
op = row.operator("bim.open_view_camera", icon="OUTLINER_OB_CAMERA", text="")
op.view_name = item.name
else:
layout.label(text="", translate=False)
class BIM_UL_topics(bpy.types.UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
ob = data
@@ -2199,14 +2016,16 @@ class BIM_UL_clash_sets(bpy.types.UIList):
else:
layout.label(text="", translate=False)
class BIM_UL_smart_groups(bpy.types.UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
ob = data
if item:
layout.label(text=str(item.number), translate=False, icon='NONE', icon_value=0)
layout.label(text=str(item.number), translate=False, icon="NONE", icon_value=0)
else:
layout.label(text="", translate=False)
class BIM_UL_constraints(bpy.types.UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
ob = data
@@ -2363,9 +2182,9 @@ class BIM_PT_ifcclash(Panel):
row.prop(source, "selector", text="")
row = layout.row()
row.prop(props, "should_create_clash_snapshots")
row = layout.row(align=True)
row.operator("bim.execute_ifc_clash")
row = layout.row()
row.operator("bim.select_ifc_clash_results")
@@ -2397,6 +2216,8 @@ class BIM_PT_annotation_utilities(Panel):
row.operator("bim.clean_wireframes")
row = layout.row(align=True)
row.operator("bim.link_ifc")
row = layout.row(align=True)
row.operator("bim.add_grid")
row = layout.row(align=True)
op = row.operator("bim.add_annotation", text="Dim", icon="ARROW_LEFTRIGHT")
@@ -2429,6 +2250,14 @@ class BIM_PT_annotation_utilities(Panel):
op.obj_name = "Section Level"
op.data_type = "curve"
row = layout.row(align=True)
op = row.operator("bim.add_annotation", text="Breakline", icon="FCURVE")
op.obj_name = "Break"
op.data_type = "mesh"
op = row.operator("bim.add_annotation", text="Misc", icon="MESH_MONKEY")
op.obj_name = "Misc"
op.data_type = "mesh"
props = bpy.context.scene.DocProperties
row = layout.row(align=True)
@@ -2440,10 +2269,10 @@ class BIM_PT_annotation_utilities(Panel):
op = row.operator("bim.open_view", icon="URL", text="")
op.view = props.drawings[props.active_drawing_index].name
row.operator("bim.remove_drawing", icon="X", text="").index = props.active_drawing_index
layout.template_list("BIM_UL_generic", "", props, "drawings", props, "active_drawing_index")
layout.template_list("BIM_UL_drawinglist", "", props, "drawings", props, "active_drawing_index")
row = layout.row()
row.prop(props, "should_draw_decorations")
layout.prop(props, "should_draw_decorations")
layout.prop(props, "decorations_colour")
class BIM_PT_qto_utilities(Panel):
@@ -2469,6 +2298,7 @@ class BIM_PT_qto_utilities(Panel):
row = layout.row(align=True)
row.operator("bim.calculate_object_volumes")
class BIM_PT_clash_manager(Panel):
bl_idname = "BIM_PT_clash_manager"
bl_label = "Clash Manager"
@@ -2503,11 +2333,12 @@ class BIM_PT_clash_manager(Panel):
row = layout.row(align=True)
row.operator("bim.load_smart_groups_for_active_clash_set")
layout.template_list('BIM_UL_smart_groups', '', props, 'smart_clash_groups', props, 'active_smart_group_index')
layout.template_list("BIM_UL_smart_groups", "", props, "smart_clash_groups", props, "active_smart_group_index")
row = layout.row(align=True)
row.operator("bim.select_smart_group")
class BIM_PT_misc_utilities(Panel):
bl_idname = "BIM_PT_misc_utilities"
bl_label = "Miscellaneous"
@@ -2525,6 +2356,8 @@ class BIM_PT_misc_utilities(Panel):
row.operator("bim.set_override_colour")
row = layout.row(align=True)
row.operator("bim.set_viewport_shadow_from_sun")
row = layout.row(align=True)
row.operator("bim.snap_spaces_together")
class BIM_PT_debug(Panel):
+83 -9
View File
@@ -11,6 +11,7 @@ import sys
import argparse
import logging
class Mesh:
faces: []
vertices: []
@@ -153,6 +154,77 @@ class IfcClasher:
return None
def export(self):
if len(self.settings.output) > 4 and self.settings.output[-4:] == ".bcf":
return self.export_bcfxml()
self.export_json()
def export_bcfxml(self):
import bcf
import bcf.bcfxml
for i, clash_set in enumerate(self.clash_sets):
bcfxml = bcf.bcfxml.BcfXml()
bcfxml.new_project()
bcfxml.project.name = clash_set["name"]
bcfxml.edit_project()
for key, clash in clash_set["clashes"].items():
topic = bcf.data.Topic()
topic.title = "{}/{} and {}/{}".format(
clash["a_ifc_class"], clash["a_name"], clash["b_ifc_class"], clash["b_name"]
)
topic = bcfxml.add_topic(topic)
viewpoint = bcf.data.Viewpoint()
viewpoint.perspective_camera = bcf.data.PerspectiveCamera()
position = np.array(clash["position"])
point = position + np.array((5, 5, 5)) # Dumb, but works!
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.data.Components()
c1 = bcf.data.Component()
c1.ifc_guid = clash["a_global_id"]
c2 = bcf.data.Component()
c2.ifc_guid = clash["b_global_id"]
viewpoint.components.selection.append(c1)
viewpoint.components.selection.append(c2)
viewpoint.components.visibility = bcf.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}")
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):
results = self.clash_sets.copy()
for result in results:
del result["a_cm"]
@@ -269,14 +341,14 @@ class IfcClasher:
def smart_group_clashes(self, clash_sets, max_clustering_distance):
from sklearn.cluster import OPTICS
from collections import defaultdict
count_of_input_clashes = 0
count_of_clash_sets = 0
count_of_smart_groups = 0
count_of_final_clash_sets = 0
count_of_clash_sets = len(clash_sets)
for clash_set in clash_sets:
if not "clashes" in clash_set.keys():
print(f"Skipping clash set [{clash_set['name']}] since it contains no clash results.")
@@ -324,14 +396,14 @@ class IfcClasher:
if not "clashes" in clash_set.keys():
continue
smart_groups = defaultdict(list)
for clash_id, content in clash_set['clashes'].items():
for clash_id, content in clash_set["clashes"].items():
if "smart_group" in content:
object_id_list = list()
# Clash has been grouped, let's extract it.
object_id_list.append(content['a_global_id'])
object_id_list.append(content['b_global_id'])
smart_groups[content['smart_group']].append(object_id_list)
count_of_smart_groups += len(smart_groups)
object_id_list.append(content["a_global_id"])
object_id_list.append(content["b_global_id"])
smart_groups[content["smart_group"]].append(object_id_list)
count_of_smart_groups += len(smart_groups)
output_clash_sets[clash_set["name"]].append(smart_groups)
# Rename the clash groups to something more sensible
@@ -346,8 +418,10 @@ class IfcClasher:
i += 1
count_of_final_clash_sets = len(output_clash_sets)
print(f"Took {count_of_input_clashes} clashes in {count_of_clash_sets} clash sets and turned",
f"them into {count_of_smart_groups} smart groups in {count_of_final_clash_sets} clash sets")
print(
f"Took {count_of_input_clashes} clashes in {count_of_clash_sets} clash sets and turned",
f"them into {count_of_smart_groups} smart groups in {count_of_final_clash_sets} clash sets",
)
return output_clash_sets
+13
View File
@@ -370,6 +370,12 @@ int main(int argc, char** argv) {
"Element at which vertical cross sections should be created")
("elevation-ref", po::value<std::string>(&elevation_ref),
"Element at which vertical elevations should be created")
("auto-section",
"Creates SVG cross section drawings automatically based on model extents")
("auto-elevation",
"Creates SVG elevation drawings automatically based on model extents")
("svg-xmlns",
"Stores name and guid in a separate namespace as opposed to data-name, data-guid")
("door-arcs", "Draw door openings arcs for IfcDoor elements")
("section-height", po::value<double>(&section_height),
"Specifies the cut section height for SVG 2D geometry.")
@@ -949,6 +955,13 @@ int main(int argc, char** argv) {
if (vmap.count("elevation-ref")) {
static_cast<SvgSerializer*>(serializer.get())->setElevationRef(elevation_ref);
}
if (vmap.count("auto-section")) {
static_cast<SvgSerializer*>(serializer.get())->setAutoSection(true);
}
if (vmap.count("auto-elevation")) {
static_cast<SvgSerializer*>(serializer.get())->setAutoElevation(true);
}
static_cast<SvgSerializer*>(serializer.get())->setUseNamespace(vmap.count("svg-xmlns") > 0);
if (relative_center_x && relative_center_y) {
static_cast<SvgSerializer*>(serializer.get())->setDrawingCenter(*relative_center_x, *relative_center_y);
}
+2 -1
View File
@@ -109,6 +109,7 @@ class IfcCsv:
self.attributes = []
self.output = ""
self.selector = None
self.delimiter = ";"
def export(self, ifc_file, elements):
self.ifc_file = ifc_file
@@ -151,7 +152,7 @@ class IfcCsv:
def Import(self, ifc):
ifc_file = ifcopenshell.open(ifc)
with open(self.output, newline="", encoding="utf-8") as f:
reader = csv.reader(f)
reader = csv.reader(f, delimiter=self.delimiter)
headers = []
for row in reader:
if not headers:
+8 -2
View File
@@ -19,9 +19,15 @@ class SvIfcGetAttribute(bpy.types.Node, SverchCustomTreeNode, ifcsverchok.helper
self.outputs.new("SvStringsSocket", "value")
def process(self):
self.sv_input_names = ["entity", "attribute_name"]
self.value_out = []
super().process()
entity_nested_inputs = self.inputs["entity"].sv_get()
attribute_name = self.inputs["attribute_name"].sv_get()[0][0]
for entities in entity_nested_inputs:
if hasattr(entities, "__iter__"):
for entity in entities:
self.process_ifc(entity, attribute_name)
else:
self.process_ifc(entities, attribute_name)
self.outputs["value"].sv_set(self.value_out)
def process_ifc(self, entity, attribute_name):
+149 -36
View File
@@ -397,7 +397,7 @@ void SvgSerializer::write(const IfcGeom::BRepElement<real_t>* brep_obj) {
double elev = p ? p->second : std::numeric_limits<double>::quiet_NaN();
geometry_data data{ compound_local, trsf, brep_obj->product(), storey, elev, brep_obj->name(), nameElement(storey, brep_obj) };
if (buffer_elements_) {
if (auto_section_ || auto_elevation_ || section_ref_ || elevation_ref_) {
element_buffer_.push_back(data);
}
@@ -533,6 +533,7 @@ void SvgSerializer::write(const geometry_data& data) {
std::pair<double, double> range;
gp_Vec projection_direction;
gp_Pln projection_plane;
IfcUtil::IfcBaseEntity* storey = nullptr;
std::string drawing_name;
@@ -555,6 +556,7 @@ void SvgSerializer::write(const geometry_data& data) {
} else if (variant.which() == 2) {
const auto& section = boost::get<vertical_section>(variant);
projection_direction = section.plane.Axis().Direction();
projection_plane = section.plane;
drawing_name = section.name;
use_hlr = section.with_projection;
}
@@ -562,7 +564,40 @@ void SvgSerializer::write(const geometry_data& data) {
auto& compound_to_use = is_floor_plan_ ? compound : compound_unmirrored;
if (use_hlr && hlr) {
hlr->Add(compound_to_use);
// Check if any of the bounding box points is on the correct side of the plane
Bnd_Box bb;
try {
BRepBndLib::Add(compound_to_use, bb);
}
catch (const Standard_Failure&) {}
if (bb.IsVoid()) {
continue;
}
double xs[2], ys[2], zs[2];
bb.Get(xs[0], ys[0], zs[0], xs[1], ys[1], zs[1]);
bool any = false;
for (int i = 0; i < 8; ++i) {
gp_Pnt p(xs[(i & 1) == 1], ys[(i & 2) == 2], zs[(i & 4) == 4]);
auto d = (p.XYZ() - projection_plane.Location().XYZ()).Dot(projection_plane.Axis().Direction().XYZ());
int state;
if (std::abs(d) < 1.e-5) {
state = 0;
} else {
state = d < 0. ? -1 : 1;
}
if (state == 1) {
any = true;
}
}
if (any) {
hlr->Add(compound_to_use);
}
}
TopoDS_Iterator it(compound_to_use);
@@ -865,13 +900,16 @@ void SvgSerializer::resize() {
sc = (*scale_) * 1000;
cx = (xmax + xmin) / 2. * sc - width * center_x_.get_value_or(0.5);
cy = (ymax + ymin) / 2. * sc - height * center_y_.get_value_or(0.5);
} else {
}
else {
if (calculated_scale_) {
sc = *calculated_scale_;
} else {
}
else {
if (dx / width > dy / height) {
sc = width / dx;
} else {
}
else {
sc = height / dy;
}
calculated_scale_ = sc;
@@ -893,8 +931,12 @@ void SvgSerializer::resize() {
(*it)->value() *= sc;
}
}
}
void SvgSerializer::resetScale() {
// reset the bounding box, as a subsequent drawing (elevation, section) will be centered, but use the same scale.
// this is a separate call now as we first need to read drawing extents for automatically positioning sections and
// elevations
xmin = +std::numeric_limits<double>::infinity();
ymin = +std::numeric_limits<double>::infinity();
xmax = -std::numeric_limits<double>::infinity();
@@ -904,6 +946,63 @@ void SvgSerializer::resize() {
void SvgSerializer::finalize() {
resize();
if (!deferred_section_data_.is_initialized() && (auto_section_ || auto_elevation_)) {
deferred_section_data_.emplace();
}
// @nb keep in mind Y-axis is negated in these 6 definitions to account
// for coordinate system differences.
if (auto_section_) {
{
gp_Pln pln(gp_Ax3(
gp_Pnt((xmin + xmax) / 2., (ymin + ymax) / 2., 0.),
gp_Dir(-1, 0, 0),
gp_Dir(0, -1, 0)));
deferred_section_data_->push_back(vertical_section{ pln , "Section North South", false });
}
{
gp_Pln pln(gp_Ax3(
gp_Pnt((xmin + xmax) / 2., (ymin + ymax) / -2., 0.),
gp_Dir(0, -1, 0),
gp_Dir(-1, 0, 0)));
deferred_section_data_->push_back(vertical_section{ pln , "Section East West", true });
}
}
if (auto_elevation_) {
{
gp_Pln pln(gp_Ax3(
gp_Pnt((xmin + xmax) / 2., -(ymin - 1.e-1), 0.),
gp_Dir(0, -1, 0),
gp_Dir(-1, 0, 0)));
deferred_section_data_->push_back(vertical_section{ pln , "Elevation South", true });
}
{
gp_Pln pln(gp_Ax3(
gp_Pnt(xmax + 1.e-1, (ymin + ymax) / -2., 0.),
gp_Dir(-1, 0, 0),
gp_Dir(0, -1, 0)));
deferred_section_data_->push_back(vertical_section{ pln , "Elevation East", true });
}
{
gp_Pln pln(gp_Ax3(
gp_Pnt((xmin + xmax) / 2., -(ymax + 1.e-1), 0.),
gp_Dir(0, -1, 0),
gp_Dir(1, 0, 0)));
deferred_section_data_->push_back(vertical_section{ pln , "Elevation North", true });
}
{
gp_Pln pln(gp_Ax3(
gp_Pnt(xmin - 1.e-1, (ymin + ymax) / -2., 0.),
gp_Dir(-1, 0, 0),
gp_Dir(0, -1, 0)));
deferred_section_data_->push_back(vertical_section{ pln , "Elevation West", true });
}
}
resetScale();
if (deferred_section_data_ && deferred_section_data_->size() && element_buffer_.size()) {
// Draw door arcs only on floor plans.
@@ -922,7 +1021,7 @@ void SvgSerializer::finalize() {
hlr = new HLRBRep_Algo;
}
*section_data_ = { sd };
section_data_ = std::vector<section_data>{ sd };
for (auto& e : element_buffer_) {
write(e);
}
@@ -939,35 +1038,40 @@ void SvgSerializer::finalize() {
HLRBRep_HLRToShape hlr_shapes(hlr);
auto hlr_compound_unmirrored = hlr_shapes.VCompound();
// Compound 3D curves for mirroring to work
ShapeFix_Edge sfe;
TopExp_Explorer exp(hlr_compound_unmirrored, TopAbs_EDGE);
for (; exp.More(); exp.Next()) {
sfe.FixAddCurve3d(TopoDS::Edge(exp.Current()));
if (!hlr_compound_unmirrored.IsNull()) {
// Compound 3D curves for mirroring to work
ShapeFix_Edge sfe;
TopExp_Explorer exp(hlr_compound_unmirrored, TopAbs_EDGE);
for (; exp.More(); exp.Next()) {
sfe.FixAddCurve3d(TopoDS::Edge(exp.Current()));
}
// Mirror to match SVG coord system.
// @todo this is very wasteful. We better do the Y-mirror in the SVG writing and
// not on the TopoDS_Shape input.
gp_Trsf trsf_mirror;
trsf_mirror.SetMirror(gp_Ax2(gp::Origin(), gp::DY()));
BRepBuilderAPI_Transform make_transform_mirror(hlr_compound_unmirrored, trsf_mirror, true);
make_transform_mirror.Build();
auto hlr_compound = make_transform_mirror.Shape();
exp.Init(hlr_compound, TopAbs_EDGE);
BRep_Builder B;
auto& po = start_path(drawing_name, "class=\"projection\"");
for (; exp.More(); exp.Next()) {
TopoDS_Wire w;
B.MakeWire(w);
B.Add(w, exp.Current());
write(po, w);
}
}
// Mirror to match SVG coord system.
// @todo this is very wasteful. We better do the Y-mirror in the SVG writing and
// not on the TopoDS_Shape input.
gp_Trsf trsf_mirror;
trsf_mirror.SetMirror(gp_Ax2(gp::Origin(), gp::DY()));
BRepBuilderAPI_Transform make_transform_mirror(hlr_compound_unmirrored, trsf_mirror, true);
make_transform_mirror.Build();
auto hlr_compound = make_transform_mirror.Shape();
exp.Init(hlr_compound, TopAbs_EDGE);
BRep_Builder B;
auto& po = start_path(drawing_name, "class=\"projection\"");
for (; exp.More(); exp.Next()) {
TopoDS_Wire w;
B.MakeWire(w);
B.Add(w, exp.Current());
write(po, w);
}
}
resize();
resetScale();
if (use_hlr) {
hlr.Nullify();
@@ -987,7 +1091,9 @@ void SvgSerializer::finalize() {
if (it->first.first) {
svg_file << " <g " << nameElement(it->first.first) << ">\n";
} else {
svg_file << " <g data-name=\"" << it->first.second << "\" class=\"section\">\n";
auto n = it->first.second;
IfcUtil::escape_xml(n);
svg_file << " <g " << namespace_prefix_ << "name=\"" << n << "\" class=\"section\">\n";
}
}
svg_file << " <g " << it->second.first << ">\n";
@@ -1007,6 +1113,9 @@ void SvgSerializer::finalize() {
void SvgSerializer::writeHeader() {
svg_file << "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\"";
if (use_namespace_) {
svg_file << " xmlns:ifc=\"http://www.ifcopenshell.org/ns\"";
}
if (scale_) {
svg_file <<
" width=\"" << width << "mm\""
@@ -1071,11 +1180,14 @@ return oss.str();
}
std::string SvgSerializer::nameElement(const IfcUtil::IfcBaseEntity* storey, const IfcGeom::Element<real_t>* elem) {
auto n = elem->name();
IfcUtil::escape_xml(n);
return nameElement_({
{"id", with_section_heights_from_storey_ ? object_id(storey, elem) : GeometrySerializer::object_id(elem)},
{"class", elem->type()},
{"data-name", elem->name()},
{"data-guid", elem->guid()}
{namespace_prefix_ + "name", n},
{namespace_prefix_ + "guid", elem->guid()}
});
}
@@ -1099,13 +1211,14 @@ std::string SvgSerializer::nameElement(const IfcUtil::IfcBaseEntity* elem) {
std::string ifc_name;
if (!elem->get("Name")->isNull()) {
ifc_name = (std::string) *elem->get("Name");
IfcUtil::escape_xml(ifc_name);
}
return nameElement_({
{"id", idElement(elem)},
{"class", entity},
{"data-name", ifc_name},
{"data-guid", *elem->get("GlobalId")}
{namespace_prefix_ + "name", ifc_name},
{namespace_prefix_ + "guid", *elem->get("GlobalId")}
});
}
+26 -4
View File
@@ -119,7 +119,9 @@ protected:
boost::optional<double> scale_, calculated_scale_, center_x_, center_y_;
bool with_section_heights_from_storey_, rescale, print_space_names_, print_space_areas_;
bool draw_door_arcs_, buffer_elements_, is_floor_plan_;
bool draw_door_arcs_, is_floor_plan_;
bool auto_section_, auto_elevation_;
bool use_namespace_;
IfcParse::IfcFile* file;
IfcUtil::IfcBaseEntity* storey_;
@@ -133,6 +135,8 @@ protected:
std::list<geometry_data> element_buffer_;
Handle(HLRBRep_Algo) hlr;
std::string namespace_prefix_;
public:
SvgSerializer(const std::string& out_filename, const SerializerSettings& settings)
: GeometrySerializer(settings)
@@ -146,13 +150,16 @@ public:
, print_space_names_(false)
, print_space_areas_(false)
, draw_door_arcs_(false)
, buffer_elements_(false)
, is_floor_plan_(true)
, auto_section_(false)
, auto_elevation_(false)
, use_namespace_(false)
, file(0)
, storey_(0)
, xcoords_begin(0)
, ycoords_begin(0)
, radii_begin(0)
, namespace_prefix_("data-")
{}
void addXCoordinate(const boost::shared_ptr<util::string_buffer::float_item>& fi) { xcoords.push_back(fi); }
void addYCoordinate(const boost::shared_ptr<util::string_buffer::float_item>& fi) { ycoords.push_back(fi); }
@@ -176,15 +183,30 @@ public:
void setPrintSpaceNames(bool b) { print_space_names_ = b; }
void setPrintSpaceAreas(bool b) { print_space_areas_ = b; }
void setDrawDoorArcs(bool b) { draw_door_arcs_ = b; }
void resize();
void resetScale();
void setSectionRef(const boost::optional<std::string>& s) {
section_ref_ = s;
buffer_elements_ = true;
}
void setElevationRef(const boost::optional<std::string>& s) {
elevation_ref_ = s;
buffer_elements_ = true;
}
void setAutoSection(bool b) {
auto_section_ = b;
}
void setAutoElevation(bool b) {
auto_elevation_ = b;
}
void setUseNamespace(bool b) {
use_namespace_ = b;
namespace_prefix_ = use_namespace_ ? "ifc:" : "data-";
}
void setScale(double s) { scale_ = s; }
void setDrawingCenter(double x, double y) {
center_x_ = x; center_y_ = y;