mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-13 02:47:48 +00:00
@@ -12,3 +12,7 @@ __pycache__
|
||||
.vscode
|
||||
# PyCharm files
|
||||
.idea
|
||||
# gettext binary translation files
|
||||
*.mo
|
||||
# Vim
|
||||
*.swp
|
||||
|
||||
+26
-37
@@ -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
@@ -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
@@ -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()
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
Binary file not shown.
@@ -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}"
|
||||
|
||||
Binary file not shown.
@@ -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 ""
|
||||
|
||||
|
||||
Binary file not shown.
@@ -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 ""
|
||||
|
||||
@@ -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 ""
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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)):
|
||||
|
||||
@@ -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."
|
||||
|
||||
|
||||
@@ -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)
|
||||
@@ -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"}
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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>(§ion_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);
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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")}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user