Merge branch 'v0.6.0' into ifcopenshell_documentation
@@ -0,0 +1,8 @@
|
||||
c14f5eeca042232025797990396232e4feab53e7
|
||||
d6881e833da0b6278313870d37d42ca6ece8686a
|
||||
892be5444c5bd01a601c99243ac8d3e7b2f0e779
|
||||
d169a964dd7e9ab5c98b35a82bcbc76dc6229a7a
|
||||
4a6ec11f6f84a3d1fed2ee9b2f85d783ac3daa9d
|
||||
2c9d6a47f4d24a923069775206bf734feeb14830
|
||||
8f1743ed64a2a52c201a1ef5e312b4d3a7a922cf
|
||||
ad7f030344c405e10cfa7d5cf06d82c121eeb825
|
||||
@@ -12,7 +12,14 @@ __pycache__
|
||||
.vscode
|
||||
# PyCharm files
|
||||
.idea
|
||||
|
||||
# Docs
|
||||
/docs/output
|
||||
/docs/rst_files
|
||||
/docs/doxygen
|
||||
/src/ifcblenderexport/docs/_build
|
||||
|
||||
# gettext binary translation files
|
||||
*.mo
|
||||
# Vim
|
||||
*.swp
|
||||
|
||||
@@ -7,7 +7,7 @@ For more information, see
|
||||
* [http://ifcopenshell.org](http://ifcopenshell.org)
|
||||
* [http://academy.ifcopenshell.org](http://academy.ifcopenshell.org)
|
||||
|
||||
[](https://api.travis-ci.org/IfcOpenShell/IfcOpenShell)
|
||||
[](https://travis-ci.org/IfcOpenShell/IfcOpenShell)
|
||||
|
||||
Prerequisites
|
||||
-------------
|
||||
|
||||
@@ -144,6 +144,14 @@ IF(WIN32 AND ("$ENV{CONDA_BUILD}" STREQUAL ""))
|
||||
SET(Boost_USE_STATIC_LIBS ON)
|
||||
SET(Boost_USE_STATIC_RUNTIME ON)
|
||||
SET(Boost_USE_MULTITHREADED ON)
|
||||
# Disable Boost's autolinking as the libraries to be linked to are supplied
|
||||
# already by CMake, and wrong libraries would be asked for when code is
|
||||
# compiled with a toolset different from default.
|
||||
if(MSVC)
|
||||
ADD_DEFINITIONS(-DBOOST_ALL_NO_LIB)
|
||||
# Necessary for boost version >= 1.67
|
||||
SET(BCRYPT_LIBRARIES "bcrypt.lib")
|
||||
ENDIF()
|
||||
ELSE()
|
||||
# Disable Boost's autolinking as the libraries to be linked to are supplied
|
||||
# already by CMake, and it's going to conflict if there are multiple, as is
|
||||
@@ -210,6 +218,10 @@ endfunction()
|
||||
|
||||
if(BUILD_IFCGEOM)
|
||||
|
||||
IF(MSVC)
|
||||
add_debug_variants(LIBXML2_LIBRARIES "${LIBXML2_LIBRARIES}" d)
|
||||
ENDIF()
|
||||
|
||||
# Find Open CASCADE
|
||||
IF("${OCC_INCLUDE_DIR}" STREQUAL "")
|
||||
SET(OCC_INCLUDE_DIR "/usr/include/oce/" CACHE FILEPATH "Open CASCADE header files")
|
||||
|
||||
@@ -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"
|
||||
@@ -160,6 +158,13 @@ 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"]
|
||||
except KeyError:
|
||||
@@ -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":
|
||||
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())
|
||||
)
|
||||
|
||||
@@ -692,7 +681,8 @@ cmake_args=[
|
||||
"-DCMAKE_INSTALL_PREFIX=" "{DEPS_DIR}/install/ifcopenshell".format(**locals()),
|
||||
"-DBOOST_ROOT=" "{DEPS_DIR}/install/boost-{BOOST_VERSION}".format(**locals()),
|
||||
"-DGLTF_SUPPORT=" "ON",
|
||||
"-DJSON_INCLUDE_DIR=" "{DEPS_DIR}/install/json".format(**locals())
|
||||
"-DJSON_INCLUDE_DIR=" "{DEPS_DIR}/install/json".format(**locals()),
|
||||
"-DBoost_NO_BOOST_CMAKE=" "On"
|
||||
]
|
||||
|
||||
if "occ" in targets and USE_OCCT:
|
||||
|
||||
@@ -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>
|
||||
@@ -0,0 +1,22 @@
|
||||
From a0deb4ce8b43cf3c8b8c0a4225c6be5296446dbd Mon Sep 17 00:00:00 2001
|
||||
From: Adam Eri <adam.eri@blackmirror.media>
|
||||
Date: Tue, 3 Sep 2019 23:30:20 +0200
|
||||
Subject: [PATCH] Resolves compile error on macOS
|
||||
|
||||
Resolves "no member named 'isnan' in namespace 'std'" on macOS
|
||||
---
|
||||
GeneratedSaxParser/src/GeneratedSaxParserUtils.cpp | 1 +
|
||||
1 file changed, 1 insertion(+)
|
||||
|
||||
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>
|
||||
@@ -0,0 +1,2 @@
|
||||
[tool.black]
|
||||
line-length = 120
|
||||
@@ -0,0 +1,64 @@
|
||||
# ****************************************************************************
|
||||
# line endings
|
||||
|
||||
# to suppress line ending changes in github, add ?w=1 at link end of a diff
|
||||
|
||||
# for more information see
|
||||
# FreeCAD source code src/Mod/.gitattributes
|
||||
# FreeCAD forum topic https://forum.freecadweb.org/viewtopic.php?f=17&t=41117
|
||||
# FreeCAD pull request https://github.com/FreeCAD/FreeCAD/pull/2752
|
||||
|
||||
|
||||
# get all used file types
|
||||
|
||||
# in a directory in a bash use
|
||||
# find . -type f -name '*.*' | sed 's|.*\.||' | sort -u
|
||||
|
||||
# search for a specific file ending
|
||||
# find . -type f -name '*.ico'
|
||||
|
||||
|
||||
# normalize the line endings of the following files
|
||||
*.cpp text
|
||||
*.css text
|
||||
*.csv text
|
||||
*.feature text
|
||||
*.gitattributes text
|
||||
*.gitignore text
|
||||
*.gitkeep
|
||||
*.h text
|
||||
*.html text
|
||||
*.ifc text
|
||||
*.json text
|
||||
*.md text
|
||||
*.po text
|
||||
*.pot text
|
||||
*.py text
|
||||
*.txt text
|
||||
|
||||
|
||||
# files not normalized ATM
|
||||
# bat
|
||||
# bnf
|
||||
# blend
|
||||
# i
|
||||
# ico
|
||||
# mo
|
||||
# mpass
|
||||
# png
|
||||
# pth
|
||||
# pyc
|
||||
# rst
|
||||
# svg
|
||||
# ttf
|
||||
# xsd
|
||||
|
||||
|
||||
# line endings of all directories will be normalized
|
||||
# to exclude a directory from being normalized add it here
|
||||
# example: to exclude dirctory ifcbimtester use the following line without #
|
||||
# ifcbimtester/** -text
|
||||
|
||||
|
||||
# use git to manually correct the file endings
|
||||
# git add --renormalize .
|
||||
@@ -0,0 +1,59 @@
|
||||
# bcf
|
||||
|
||||
A simple Python implementation of BCF. The data model is described in `data.py`.
|
||||
Manipulation of BCF-XML is available via `bcfxml.py` and manipulation of BCF-API
|
||||
is available via `bcfapi.py`.
|
||||
|
||||
Currently supports BCF version 2.1.
|
||||
|
||||
## bcfxml
|
||||
|
||||
The `bcfxml` module lets you interact with the BCF-XML standard.
|
||||
|
||||
```
|
||||
from bcf.bcfxml import BcfXml
|
||||
|
||||
bcfxml = BcfXml()
|
||||
|
||||
# Load a project
|
||||
project = bcfxml.get_project("/path/to/file.bcf")
|
||||
|
||||
# The project is also stored in the module
|
||||
# project == bcfxml.project
|
||||
|
||||
print(project.name)
|
||||
|
||||
# To edit a project, just modify the object directly
|
||||
bcfxml.project.name = "New name"
|
||||
bcfxml.edit_project()
|
||||
|
||||
# The BCF file is extracted to this temporary directory
|
||||
print(bcfxml.filepath)
|
||||
|
||||
# Get a dictionary of topics
|
||||
topics = bcfxml.get_topics()
|
||||
|
||||
# Note: topics == bcfxml.topics
|
||||
for guid, topic in bcfxml.topics.items():
|
||||
print("Topic guid is", guid)
|
||||
print("Topic guid is", topic.guid)
|
||||
print("Topic title is", topic.title)
|
||||
|
||||
# Fetch extra data about a topic
|
||||
header = bcfxml.get_header(guid)
|
||||
comments = bcfxml.get_comments(guid)
|
||||
viewpoints = bcfxml.get_viewpoints(guid)
|
||||
|
||||
# Note: comments == topic.comments, and so on
|
||||
for comment_guid, comment in comments.items():
|
||||
print(comment_guid)
|
||||
print(comment.comment)
|
||||
print(comment.author)
|
||||
|
||||
# Get a particular topic
|
||||
topic = bcfxml.get_topic(guid)
|
||||
|
||||
# Modify a topic
|
||||
topic.title = "New title"
|
||||
bcfxml.edit_topic(topic)
|
||||
```
|
||||
@@ -0,0 +1,762 @@
|
||||
import os
|
||||
import uuid
|
||||
import shutil
|
||||
import zipfile
|
||||
import logging
|
||||
import tempfile
|
||||
import bcf.data
|
||||
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__))
|
||||
|
||||
|
||||
@contextmanager
|
||||
def cd(newdir):
|
||||
prevdir = os.getcwd()
|
||||
os.chdir(os.path.expanduser(newdir))
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
os.chdir(prevdir)
|
||||
|
||||
|
||||
class BcfXml:
|
||||
def __init__(self):
|
||||
self.filepath = None
|
||||
self.logger = logging.getLogger("bcfxml")
|
||||
self.author = "john@doe.com"
|
||||
self.project = bcf.data.Project()
|
||||
self.version = "2.1"
|
||||
self.topics = {}
|
||||
|
||||
def new_project(self):
|
||||
self.project.project_id = str(uuid.uuid4())
|
||||
self.project.name = "New Project"
|
||||
self.topics = {}
|
||||
if self.filepath:
|
||||
self.close_project()
|
||||
self.filepath = tempfile.mkdtemp()
|
||||
self.edit_project()
|
||||
self.edit_version()
|
||||
|
||||
def get_project(self, filepath=None):
|
||||
if not filepath:
|
||||
return self.project
|
||||
zip_file = zipfile.ZipFile(filepath)
|
||||
self.filepath = tempfile.mkdtemp()
|
||||
zip_file.extractall(self.filepath)
|
||||
data = self._read_xml("project.bcfp", "project.xsd")
|
||||
self.project.project_id = data["Project"]["@ProjectId"]
|
||||
self.project.name = data["Project"]["Name"]
|
||||
return self.project
|
||||
|
||||
def edit_project(self):
|
||||
self.document = minidom.Document()
|
||||
root = self._create_element(self.document, "ProjectExtension")
|
||||
project = self._create_element(root, "Project", {"ProjectId": self.project.project_id})
|
||||
self._create_element(project, "Name", text=self.project.name)
|
||||
self._create_element(root, "ExtensionSchema", text="extensions.xsd")
|
||||
with open(os.path.join(self.filepath, "project.bcfp"), "wb") as f:
|
||||
f.write(self.document.toprettyxml(encoding="utf-8"))
|
||||
|
||||
def save_project(self, filepath):
|
||||
with cd(self.filepath):
|
||||
zip_file = zipfile.ZipFile(filepath, "w", zipfile.ZIP_DEFLATED)
|
||||
for root, dirs, files in os.walk("./"):
|
||||
for file in files:
|
||||
zip_file.write(os.path.join(root, file))
|
||||
zip_file.close()
|
||||
|
||||
def get_version(self):
|
||||
data = self._read_xml("bcf.version", "version.xsd")
|
||||
self.version = data["@VersionId"]
|
||||
return self.version
|
||||
|
||||
def edit_version(self):
|
||||
self.document = minidom.Document()
|
||||
root = self._create_element(self.document, "Version", {"VersionId": self.version})
|
||||
version = self._create_element(root, "DetailedVersion", text=self.version)
|
||||
with open(os.path.join(self.filepath, "bcf.version"), "wb") as f:
|
||||
f.write(self.document.toprettyxml(encoding="utf-8"))
|
||||
|
||||
def get_topics(self):
|
||||
self.topics = {}
|
||||
topics = []
|
||||
subdirs = []
|
||||
for (dirpath, dirnames, filenames) in os.walk(self.filepath):
|
||||
subdirs = dirnames
|
||||
break
|
||||
for subdir in subdirs:
|
||||
self.topics[subdir] = self.get_topic(subdir)
|
||||
return self.topics
|
||||
|
||||
def get_header(self, guid):
|
||||
data = self._read_xml(os.path.join(guid, "markup.bcf"), "markup.xsd")
|
||||
if "Header" not in data:
|
||||
return
|
||||
header = bcf.data.Header()
|
||||
for item in data["Header"]["File"]:
|
||||
header_file = bcf.data.HeaderFile()
|
||||
optional_keys = {
|
||||
"filename": "Filename",
|
||||
"date": "Date",
|
||||
"reference": "Reference",
|
||||
"ifc_project": "@IfcProject",
|
||||
"ifc_spatial_structure_element": "@IfcSpatialStructureElement",
|
||||
"is_external": "@isExternal",
|
||||
}
|
||||
for key, value in optional_keys.items():
|
||||
if value in item:
|
||||
setattr(header_file, key, item[value])
|
||||
header.files.append(header_file)
|
||||
self.topics[guid].header = header
|
||||
return header
|
||||
|
||||
def get_topic(self, guid):
|
||||
if guid in self.topics:
|
||||
return self.topics[guid]
|
||||
data = self._read_xml(os.path.join(guid, "markup.bcf"), "markup.xsd")
|
||||
topic = bcf.data.Topic()
|
||||
self.topics[guid] = topic
|
||||
|
||||
mandatory_keys = {
|
||||
"guid": "@Guid",
|
||||
"title": "Title",
|
||||
"creation_date": "CreationDate",
|
||||
"creation_author": "CreationAuthor",
|
||||
}
|
||||
for key, value in mandatory_keys.items():
|
||||
setattr(topic, key, data["Topic"][value])
|
||||
|
||||
optional_keys = {
|
||||
"priority": "Priority",
|
||||
"index": "Index",
|
||||
"labels": "Labels",
|
||||
"reference_links": "ReferenceLink",
|
||||
"modified_date": "ModifiedDate",
|
||||
"modified_author": "ModifiedAuthor",
|
||||
"due_date": "DueDate",
|
||||
"assigned_to": "AssignedTo",
|
||||
"stage": "Stage",
|
||||
"description": "Description",
|
||||
"topic_status": "@TopicStatus",
|
||||
"topic_type": "@TopicType",
|
||||
}
|
||||
for key, value in optional_keys.items():
|
||||
if value in data["Topic"]:
|
||||
setattr(topic, key, data["Topic"][value])
|
||||
|
||||
if "BimSnippet" in data["Topic"]:
|
||||
bim_snippet = bcf.data.BimSnippet()
|
||||
keys = {
|
||||
"snippet_type": "@SnippetType",
|
||||
"is_external": "@IsExternal",
|
||||
"reference": "Reference",
|
||||
"reference_schema": "ReferenceSchema",
|
||||
}
|
||||
for key, value in keys.items():
|
||||
if value in data["Topic"]["BimSnippet"]:
|
||||
setattr(bim_snippet, key, data["Topic"]["BimSnippet"][value])
|
||||
topic.bim_snippet = bim_snippet
|
||||
|
||||
if "DocumentReference" in data["Topic"]:
|
||||
for item in data["Topic"]["DocumentReference"]:
|
||||
document_reference = bcf.data.DocumentReference()
|
||||
keys = {
|
||||
"referenced_document": "ReferencedDocument",
|
||||
"is_external": "@IsExternal",
|
||||
"guid": "@Guid",
|
||||
"description": "Description",
|
||||
}
|
||||
for key, value in keys.items():
|
||||
if value in item:
|
||||
setattr(document_reference, key, item[value])
|
||||
topic.document_references.append(document_reference)
|
||||
|
||||
if "RelatedTopic" in data["Topic"]:
|
||||
for item in data["Topic"]["RelatedTopic"]:
|
||||
related_topic = bcf.data.RelatedTopic()
|
||||
related_topic.guid = item["@Guid"]
|
||||
topic.related_topics.append(related_topic)
|
||||
return topic
|
||||
|
||||
def add_topic(self, topic=None):
|
||||
if topic is None:
|
||||
topic = bcf.data.Topic()
|
||||
if not topic.guid:
|
||||
topic.guid = str(uuid.uuid4())
|
||||
if not topic.title:
|
||||
topic.title = "New Topic"
|
||||
os.mkdir(os.path.join(self.filepath, topic.guid))
|
||||
self.edit_topic(topic)
|
||||
return topic
|
||||
|
||||
def edit_topic(self, topic):
|
||||
if not topic.creation_date:
|
||||
topic.creation_date = datetime.utcnow().isoformat()
|
||||
topic.creation_author = self.author
|
||||
else:
|
||||
topic.modified_date = datetime.utcnow().isoformat()
|
||||
topic.modified_author = self.author
|
||||
|
||||
self.document = minidom.Document()
|
||||
root = self._create_element(self.document, "Markup")
|
||||
|
||||
self.write_header(topic.header, root)
|
||||
|
||||
topic_el = self._create_element(
|
||||
root,
|
||||
"Topic",
|
||||
{
|
||||
"Guid": topic.guid,
|
||||
"TopicType": topic.topic_type,
|
||||
"TopicStatus": topic.topic_status,
|
||||
},
|
||||
)
|
||||
|
||||
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,
|
||||
"ModifiedAuthor": topic.modified_author,
|
||||
"DueDate": topic.due_date,
|
||||
"AssignedTo": topic.assigned_to,
|
||||
"Stage": topic.stage,
|
||||
"Description": topic.description,
|
||||
}
|
||||
for key, value in text_map.items():
|
||||
if value:
|
||||
self._create_element(topic_el, key, text=value)
|
||||
|
||||
if topic.bim_snippet:
|
||||
bim_snippet = self._create_element(
|
||||
topic_el,
|
||||
"BimSnippet",
|
||||
{"SnippetType": topic.bim_snippet.snippet_type, "isExternal": topic.bim_snippet.is_external},
|
||||
)
|
||||
self._create_element(bim_snippet, "Reference", text=topic.bim_snippet.reference)
|
||||
self._create_element(bim_snippet, "ReferenceSchema", text=topic.bim_snippet.reference_schema)
|
||||
for reference in topic.document_references:
|
||||
reference_el = self._create_element(
|
||||
topic_el, "DocumentReference", {"Guid": reference.guid, "isExternal": reference.is_external}
|
||||
)
|
||||
self._create_element(reference_el, "ReferencedDocument", text=reference.referenced_document)
|
||||
self._create_element(reference_el, "Description", text=reference.description)
|
||||
for related_topic in topic.related_topics:
|
||||
self._create_element(topic_el, "RelatedTopic", {"Guid": related_topic.guid})
|
||||
|
||||
self.write_comments(topic.comments, root)
|
||||
self.write_viewpoints(topic.viewpoints, root, topic)
|
||||
|
||||
with open(os.path.join(self.filepath, topic.guid, "markup.bcf"), "wb") as f:
|
||||
f.write(self.document.toprettyxml(encoding="utf-8"))
|
||||
|
||||
def write_header(self, header, root):
|
||||
if not header or not header.files:
|
||||
return
|
||||
header_el = self._create_element(root, "Header")
|
||||
for f in header.files:
|
||||
file_el = self._create_element(
|
||||
header_el,
|
||||
"File",
|
||||
{
|
||||
"IfcProject": f.ifc_project,
|
||||
"IfcSpatialStructureElement": f.ifc_spatial_structure_element,
|
||||
"isExternal": f.is_external,
|
||||
},
|
||||
)
|
||||
self._create_element(file_el, "Filename", text=f.filename)
|
||||
self._create_element(file_el, "Date", text=f.date)
|
||||
self._create_element(file_el, "Reference", text=f.reference)
|
||||
|
||||
def write_comments(self, comments, root):
|
||||
for comment in comments.values():
|
||||
comment_el = self._create_element(root, "Comment", {"Guid": comment.guid})
|
||||
text_map = {
|
||||
"Date": comment.date,
|
||||
"Author": comment.author,
|
||||
"Comment": comment.comment,
|
||||
"ModifiedDate": comment.modified_date,
|
||||
"ModifiedAuthor": comment.modified_author,
|
||||
}
|
||||
for key, value in text_map.items():
|
||||
if value:
|
||||
self._create_element(comment_el, key, text=value)
|
||||
if comment.viewpoint:
|
||||
self._create_element(comment_el, "Viewpoint", {"Guid": comment.viewpoint.guid})
|
||||
|
||||
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'."
|
||||
topic.comments[comment.guid] = comment
|
||||
self.edit_comment(comment, topic)
|
||||
|
||||
def edit_comment(self, comment, topic):
|
||||
if not comment.date:
|
||||
comment.date = datetime.utcnow().isoformat()
|
||||
comment.author = self.author
|
||||
else:
|
||||
comment.modified_date = datetime.utcnow().isoformat()
|
||||
comment.modified_author = self.author
|
||||
self.edit_topic(topic)
|
||||
|
||||
def delete_comment(self, guid, topic):
|
||||
if guid in topic.comments:
|
||||
del topic.comments[guid]
|
||||
self.edit_topic(topic)
|
||||
|
||||
def delete_topic(self, guid):
|
||||
if guid in self.topics:
|
||||
del self.topics[guid]
|
||||
shutil.rmtree(os.path.join(self.filepath, guid))
|
||||
|
||||
def write_viewpoints(self, viewpoints, root, topic):
|
||||
for viewpoint in viewpoints.values():
|
||||
viewpoint_el = self._create_element(root, "Viewpoints", {"Guid": viewpoint.guid})
|
||||
text_map = {"Viewpoint": viewpoint.viewpoint, "Snapshot": viewpoint.snapshot, "Index": viewpoint.index}
|
||||
for key, value in text_map.items():
|
||||
if value:
|
||||
self._create_element(viewpoint_el, key, text=value)
|
||||
self.write_viewpoint(viewpoint, topic)
|
||||
|
||||
def write_viewpoint(self, viewpoint, topic):
|
||||
document = minidom.Document()
|
||||
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)
|
||||
self.write_viewpoint_lines(viewpoint, root)
|
||||
self.write_viewpoint_clipping_planes(viewpoint, root)
|
||||
self.write_viewpoint_bitmaps(viewpoint, root)
|
||||
with open(os.path.join(self.filepath, topic.guid, viewpoint.viewpoint), "wb") as f:
|
||||
f.write(document.toprettyxml(encoding="utf-8"))
|
||||
|
||||
def write_viewpoint_components(self, viewpoint, parent):
|
||||
if not viewpoint.components:
|
||||
return
|
||||
components_el = self._create_element(parent, "Components")
|
||||
if viewpoint.components.view_setup_hints:
|
||||
view_setup_hints = self._create_element(
|
||||
components_el,
|
||||
"ViewSetupHints",
|
||||
{
|
||||
"SpacesVisible": viewpoint.components.view_setup_hints.spaces_visible,
|
||||
"SpaceBoundariesVisible": viewpoint.components.view_setup_hints.space_boundaries_visible,
|
||||
"OpeningsVisible": viewpoint.components.view_setup_hints.openings_visible,
|
||||
},
|
||||
)
|
||||
if viewpoint.components.selection:
|
||||
selection_el = self._create_element(components_el, "Selection")
|
||||
for selection in viewpoint.components.selection:
|
||||
self.write_component(selection, selection_el)
|
||||
visibility = self._create_element(
|
||||
components_el, "Visibility", {"DefaultVisibility": viewpoint.components.visibility.default_visibility}
|
||||
)
|
||||
if viewpoint.components.visibility.exceptions:
|
||||
exceptions_el = self._create_element(visibility, "Exceptions")
|
||||
for exception in viewpoint.components.visibility.exceptions:
|
||||
self.write_component(exception, exceptions_el)
|
||||
if viewpoint.components.coloring:
|
||||
coloring_el = self._create_element(components_el, "Coloring")
|
||||
for color in viewpoint.components.coloring:
|
||||
color_el = self._create_element(coloring_el, "Color", {"Color": color.color})
|
||||
for component in color.components:
|
||||
self.write_component(component, color_el)
|
||||
|
||||
def write_viewpoint_orthogonal_camera(self, viewpoint, parent):
|
||||
if not viewpoint.orthogonal_camera:
|
||||
return
|
||||
camera = viewpoint.orthogonal_camera
|
||||
camera_el = self._create_element(parent, "OrthogonalCamera")
|
||||
camera_view_point = self._create_element(camera_el, "CameraViewPoint")
|
||||
self.write_vector(camera_view_point, camera.camera_view_point)
|
||||
camera_direction = self._create_element(camera_el, "CameraDirection")
|
||||
self.write_vector(camera_direction, camera.camera_direction)
|
||||
camera_up_vector = self._create_element(camera_el, "CameraUpVector")
|
||||
self.write_vector(camera_up_vector, camera.camera_up_vector)
|
||||
self._create_element(camera_el, "ViewToWorldScale", text=camera.view_to_world_scale)
|
||||
|
||||
def write_viewpoint_perspective_camera(self, viewpoint, parent):
|
||||
if not viewpoint.perspective_camera:
|
||||
return
|
||||
camera = viewpoint.perspective_camera
|
||||
camera_el = self._create_element(parent, "PerspectiveCamera")
|
||||
camera_view_point = self._create_element(camera_el, "CameraViewPoint")
|
||||
self.write_vector(camera_view_point, camera.camera_view_point)
|
||||
camera_direction = self._create_element(camera_el, "CameraDirection")
|
||||
self.write_vector(camera_direction, camera.camera_direction)
|
||||
camera_up_vector = self._create_element(camera_el, "CameraUpVector")
|
||||
self.write_vector(camera_up_vector, camera.camera_up_vector)
|
||||
self._create_element(camera_el, "FieldOfView", text=camera.field_of_view)
|
||||
|
||||
def write_viewpoint_lines(self, viewpoint, parent):
|
||||
if not viewpoint.lines:
|
||||
return
|
||||
lines_el = self._create_element(parent, "Lines")
|
||||
for line in viewpoint.lines:
|
||||
line_el = self._create_element(lines_el, "Line")
|
||||
start_point_el = self._create_element(line_el, "StartPoint")
|
||||
self.write_vector(start_point_el, line.start_point)
|
||||
end_point_el = self._create_element(line_el, "EndPoint")
|
||||
self.write_vector(end_point_el, line.end_point)
|
||||
|
||||
def write_viewpoint_clipping_planes(self, viewpoint, parent):
|
||||
if not viewpoint.clipping_planes:
|
||||
return
|
||||
planes_el = self._create_element(parent, "ClippingPlanes")
|
||||
for plane in viewpoint.clipping_planes:
|
||||
plane_el = self._create_element(planes_el, "ClippingPlane")
|
||||
location_el = self._create_element(plane_el, "Location")
|
||||
self.write_vector(location_el, plane.location)
|
||||
direction_el = self._create_element(plane_el, "Direction")
|
||||
self.write_vector(direction_el, plane.direction)
|
||||
|
||||
def write_viewpoint_bitmaps(self, viewpoint, parent):
|
||||
if not viewpoint.bitmaps:
|
||||
return
|
||||
for bitmap in viewpoint.bitmaps:
|
||||
bitmap_el = self._create_element(parent, "Bitmap")
|
||||
|
||||
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")
|
||||
self.write_vector(normal_el, bitmap.normal)
|
||||
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)
|
||||
self._create_element(parent, "Z", text=from_obj.z)
|
||||
|
||||
def write_component(self, data, parent):
|
||||
component_el = self._create_element(parent, "Component", {"IfcGuid": data.ifc_guid})
|
||||
text_map = {"OriginatingSystem": data.originating_system, "AuthoringToolId": data.authoring_tool_id}
|
||||
for key, value in text_map.items():
|
||||
if value:
|
||||
self._create_element(component_el, key, text=value)
|
||||
|
||||
def add_viewpoint(self, topic, viewpoint=None):
|
||||
if not viewpoint:
|
||||
viewpoint = bcf.data.Viewpoint()
|
||||
if not viewpoint.guid:
|
||||
viewpoint.guid = str(uuid.uuid4())
|
||||
if not viewpoint.viewpoint:
|
||||
viewpoint.viewpoint = f"{viewpoint.guid}.bcfv"
|
||||
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):
|
||||
filename = viewpoint.guid + os.path.splitext(viewpoint.snapshot)[-1]
|
||||
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):
|
||||
if guid not in topic.viewpoints:
|
||||
return
|
||||
viewpoint = topic.viewpoints[guid]
|
||||
if viewpoint.snapshot:
|
||||
filepath = os.path.join(self.filepath, topic.guid, viewpoint.snapshot)
|
||||
if os.path.exists(filepath):
|
||||
os.remove(filepath)
|
||||
if viewpoint.viewpoint:
|
||||
filepath = os.path.join(self.filepath, topic.guid, viewpoint.viewpoint)
|
||||
if os.path.exists(filepath):
|
||||
os.remove(filepath)
|
||||
for bitmap in viewpoint.bitmaps:
|
||||
if not bitmap.reference:
|
||||
continue
|
||||
filepath = os.path.join(self.filepath, topic.guid, bitmap.reference)
|
||||
if os.path.exists(filepath):
|
||||
os.remove(filepath)
|
||||
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")
|
||||
if "Comment" not in data:
|
||||
return comments
|
||||
for item in data["Comment"]:
|
||||
comment = bcf.data.Comment()
|
||||
mandatory_keys = {"guid": "@Guid", "date": "Date", "author": "Author", "comment": "Comment"}
|
||||
for key, value in mandatory_keys.items():
|
||||
setattr(comment, key, item[value])
|
||||
optional_keys = {"modified_date": "ModifiedDate", "modified_author": "ModifiedAuthor"}
|
||||
for key, value in optional_keys.items():
|
||||
if value in item:
|
||||
setattr(comment, key, item[value])
|
||||
if "Viewpoint" in item:
|
||||
viewpoint = bcf.data.Viewpoint()
|
||||
viewpoint.guid = item["Viewpoint"]["@Guid"]
|
||||
comment.viewpoint = viewpoint
|
||||
comments[comment.guid] = comment
|
||||
self.topics[guid].comments = comments
|
||||
return comments
|
||||
|
||||
def get_viewpoints(self, guid):
|
||||
viewpoints = {}
|
||||
data = self._read_xml(os.path.join(guid, "markup.bcf"), "markup.xsd")
|
||||
if "Viewpoints" not in data:
|
||||
return viewpoints
|
||||
for item in data["Viewpoints"]:
|
||||
viewpoint = self.get_viewpoint(item, guid)
|
||||
viewpoints[viewpoint.guid] = viewpoint
|
||||
self.topics[guid].viewpoints = viewpoints
|
||||
return viewpoints
|
||||
|
||||
def get_viewpoint(self, data, topic_guid):
|
||||
viewpoint = bcf.data.Viewpoint()
|
||||
viewpoint.guid = data["@Guid"]
|
||||
optional_keys = {"viewpoint": "Viewpoint", "snapshot": "Snapshot", "index": "Index"}
|
||||
for key, value in optional_keys.items():
|
||||
if value in data:
|
||||
setattr(viewpoint, key, data[value])
|
||||
visinfo = self._read_xml(os.path.join(topic_guid, viewpoint.viewpoint), "visinfo.xsd")
|
||||
viewpoint.components = self.get_viewpoint_components(visinfo)
|
||||
viewpoint.orthogonal_camera = self.get_viewpoint_orthogonal_camera(visinfo)
|
||||
viewpoint.perspective_camera = self.get_viewpoint_perspective_camera(visinfo)
|
||||
viewpoint.lines = self.get_viewpoint_lines(visinfo)
|
||||
viewpoint.clipping_planes = self.get_viewpoint_clipping_planes(visinfo)
|
||||
viewpoint.bitmaps = self.get_viewpoint_bitmaps(visinfo)
|
||||
return viewpoint
|
||||
|
||||
def get_viewpoint_components(self, visinfo):
|
||||
if "Components" not in visinfo:
|
||||
return None
|
||||
components = bcf.data.Components()
|
||||
data = visinfo["Components"]
|
||||
if "ViewSetupHints" in data:
|
||||
view_setup_hints = bcf.data.ViewSetupHints()
|
||||
optional_keys = {
|
||||
"spaces_visible": "@SpacesVisible",
|
||||
"space_boundaries_visible": "@SpaceBoundariesVisible",
|
||||
"openings_visible": "@OpeningsVisible",
|
||||
}
|
||||
for key, value in optional_keys.items():
|
||||
if value in data["ViewSetupHints"]:
|
||||
setattr(view_setup_hints, key, data["ViewSetupHints"][value])
|
||||
components.view_setup_hints = view_setup_hints
|
||||
if "Selection" in data and "Component" in data["Selection"]:
|
||||
for item in data["Selection"]["Component"]:
|
||||
components.selection.append(self.get_component(item))
|
||||
if "Visibility" in data:
|
||||
component_visibility = bcf.data.ComponentVisibility()
|
||||
if "@DefaultVisibility" in data["Visibility"]:
|
||||
component_visibility.default_visibility = data["Visibility"]["@DefaultVisibility"]
|
||||
if "Exceptions" in data["Visibility"] and "Component" in data["Visibility"]["Exceptions"]:
|
||||
for item in data["Visibility"]["Exceptions"]["Component"]:
|
||||
component_visibility.exceptions.append(self.get_component(item))
|
||||
components.visibility = component_visibility
|
||||
if "Coloring" in data and "Color" in data["Coloring"]:
|
||||
for item in data["Coloring"]["Color"]:
|
||||
color = bcf.data.Color()
|
||||
color.color = item["@Color"]
|
||||
for item2 in item["Component"]:
|
||||
color.components.append(self.get_component(item2))
|
||||
components.coloring.append(color)
|
||||
return components
|
||||
|
||||
def get_viewpoint_orthogonal_camera(self, visinfo):
|
||||
if "OrthogonalCamera" not in visinfo:
|
||||
return None
|
||||
camera = bcf.data.OrthogonalCamera()
|
||||
data = visinfo["OrthogonalCamera"]
|
||||
self.set_vector(camera.camera_view_point, data["CameraViewPoint"])
|
||||
self.set_vector(camera.camera_direction, data["CameraDirection"])
|
||||
self.set_vector(camera.camera_up_vector, data["CameraUpVector"])
|
||||
camera.view_to_world_scale = data["ViewToWorldScale"]
|
||||
return camera
|
||||
|
||||
def get_viewpoint_perspective_camera(self, visinfo):
|
||||
if "PerspectiveCamera" not in visinfo:
|
||||
return None
|
||||
camera = bcf.data.PerspectiveCamera()
|
||||
data = visinfo["PerspectiveCamera"]
|
||||
self.set_vector(camera.camera_view_point, data["CameraViewPoint"])
|
||||
self.set_vector(camera.camera_direction, data["CameraDirection"])
|
||||
self.set_vector(camera.camera_up_vector, data["CameraUpVector"])
|
||||
camera.field_of_view = data["FieldOfView"]
|
||||
return camera
|
||||
|
||||
def get_viewpoint_lines(self, visinfo):
|
||||
if "Lines" not in visinfo:
|
||||
return []
|
||||
lines = []
|
||||
for item in visinfo["Lines"]["Line"]:
|
||||
line = bcf.data.Line()
|
||||
self.set_vector(line.start_point, item["StartPoint"])
|
||||
self.set_vector(line.end_point, item["EndPoint"])
|
||||
lines.append(line)
|
||||
return lines
|
||||
|
||||
def get_viewpoint_clipping_planes(self, visinfo):
|
||||
if "ClippingPlanes" not in visinfo:
|
||||
return []
|
||||
planes = []
|
||||
for item in visinfo["ClippingPlanes"]["ClippingPlane"]:
|
||||
plane = bcf.data.ClippingPlane()
|
||||
self.set_vector(plane.location, item["Location"])
|
||||
self.set_vector(plane.direction, item["Direction"])
|
||||
planes.append(plane)
|
||||
return planes
|
||||
|
||||
def get_viewpoint_bitmaps(self, visinfo):
|
||||
if "Bitmap" not in visinfo:
|
||||
return []
|
||||
bitmaps = []
|
||||
for item in visinfo["Bitmap"]:
|
||||
bitmap = bcf.data.Bitmap()
|
||||
bitmap.reference = item["Reference"]
|
||||
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"])
|
||||
bitmap.height = item["Height"]
|
||||
bitmaps.append(bitmap)
|
||||
return bitmaps
|
||||
|
||||
def set_vector(self, to_obj, from_xml):
|
||||
to_obj.x = from_xml["X"]
|
||||
to_obj.y = from_xml["Y"]
|
||||
to_obj.z = from_xml["Z"]
|
||||
|
||||
def get_component(self, data):
|
||||
component = bcf.data.Component()
|
||||
optional_keys = {
|
||||
"originating_system": "OriginatingSystem",
|
||||
"authoring_tool_id": "AuthoringToolId",
|
||||
"ifc_guid": "@IfcGuid",
|
||||
}
|
||||
for key, value in optional_keys.items():
|
||||
if value in data:
|
||||
setattr(component, key, data[value])
|
||||
return component
|
||||
|
||||
def close_project(self):
|
||||
shutil.rmtree(self.filepath)
|
||||
|
||||
def _read_xml(self, filename, xsd):
|
||||
schema = XMLSchema(os.path.join(cwd, "xsd", xsd))
|
||||
filepath = os.path.join(self.filepath, filename)
|
||||
(data, errors) = schema.to_dict(filepath, validation="lax")
|
||||
for error in errors:
|
||||
self.logger.error(error)
|
||||
return data
|
||||
|
||||
def _create_element(self, parent, name, attributes={}, text=None):
|
||||
element = self.document.createElement(name)
|
||||
for key, value in attributes.items():
|
||||
if isinstance(value, bool):
|
||||
element.setAttribute(key, str(value).lower())
|
||||
elif value:
|
||||
element.setAttribute(key, value)
|
||||
if text is not None:
|
||||
text = self.document.createTextNode(str(text))
|
||||
element.appendChild(text)
|
||||
parent.appendChild(element)
|
||||
return element
|
||||
|
||||
def __del__(self):
|
||||
self.close_project()
|
||||
@@ -0,0 +1,178 @@
|
||||
class Project:
|
||||
def __init__(self):
|
||||
self.project_id = ""
|
||||
self.name = ""
|
||||
|
||||
|
||||
class BimSnippet:
|
||||
def __init__(self):
|
||||
self.snippet_type = None
|
||||
self.is_external = False
|
||||
self.reference = None
|
||||
self.reference_schema = None
|
||||
|
||||
|
||||
class DocumentReference:
|
||||
def __init__(self):
|
||||
self.referenced_document = None
|
||||
self.description = None
|
||||
self.guid = None
|
||||
self.is_external = False
|
||||
|
||||
|
||||
class RelatedTopic:
|
||||
def __init__(self):
|
||||
self.guid = None
|
||||
|
||||
|
||||
class HeaderFile:
|
||||
def __init__(self):
|
||||
self.filename = None
|
||||
self.date = None
|
||||
self.reference = None
|
||||
self.ifc_project = None
|
||||
self.ifc_spatial_structure_element = None
|
||||
self.is_external = True
|
||||
|
||||
|
||||
class Header:
|
||||
def __init__(self):
|
||||
self.files = []
|
||||
|
||||
|
||||
class Topic:
|
||||
def __init__(self):
|
||||
self.reference_links = []
|
||||
self.title = ""
|
||||
self.priority = None
|
||||
self.index = None # Deprecated, stored, but ignored
|
||||
self.labels = []
|
||||
self.creation_date = None
|
||||
self.creation_author = None
|
||||
self.modified_date = None
|
||||
self.modified_author = None
|
||||
self.due_date = None
|
||||
self.assigned_to = None
|
||||
self.stage = None
|
||||
self.description = None
|
||||
self.bim_snippet = None
|
||||
self.document_references = []
|
||||
self.related_topics = []
|
||||
self.topic_status = None
|
||||
self.topic_type = None
|
||||
self.guid = None
|
||||
|
||||
self.header = None
|
||||
self.comments = {}
|
||||
self.viewpoints = {}
|
||||
|
||||
|
||||
class Comment:
|
||||
def __init__(self):
|
||||
self.guid = None
|
||||
self.date = None
|
||||
self.author = None
|
||||
self.comment = None
|
||||
self.viewpoint = None
|
||||
self.modified_date = None
|
||||
self.modified_author = None
|
||||
self.topic_guid = None # Part of BCF-API
|
||||
|
||||
|
||||
class ViewSetupHints:
|
||||
def __init__(self):
|
||||
self.spaces_visible = False
|
||||
self.space_boundaries_visible = False
|
||||
self.openings_visible = False
|
||||
|
||||
|
||||
class Component:
|
||||
def __init__(self):
|
||||
self.originating_system = None
|
||||
self.authoring_tool_id = None
|
||||
self.ifc_guid = None
|
||||
|
||||
|
||||
class ComponentVisibility:
|
||||
def __init__(self):
|
||||
self.exceptions = []
|
||||
self.default_visibility = False
|
||||
|
||||
|
||||
class Color:
|
||||
def __init__(self):
|
||||
self.color = None
|
||||
self.components = []
|
||||
|
||||
|
||||
class Components:
|
||||
def __init__(self):
|
||||
self.view_setup_hints = None
|
||||
self.selection = []
|
||||
self.visibility = None
|
||||
self.coloring = []
|
||||
|
||||
|
||||
class Point:
|
||||
def __init__(self):
|
||||
self.x = 0
|
||||
self.y = 0
|
||||
self.z = 0
|
||||
|
||||
|
||||
class Direction(Point):
|
||||
pass
|
||||
|
||||
|
||||
class OrthogonalCamera:
|
||||
def __init__(self):
|
||||
self.camera_view_point = Point()
|
||||
self.camera_direction = Direction()
|
||||
self.camera_up_vector = Direction()
|
||||
self.view_to_world_scale = 1.0
|
||||
|
||||
|
||||
class PerspectiveCamera:
|
||||
def __init__(self):
|
||||
self.camera_view_point = Point()
|
||||
self.camera_direction = Direction()
|
||||
self.camera_up_vector = Direction()
|
||||
self.field_of_view = 60.0
|
||||
|
||||
|
||||
class Line:
|
||||
def __init__(self):
|
||||
self.start_point = Point()
|
||||
self.end_point = Point()
|
||||
|
||||
|
||||
class ClippingPlane:
|
||||
def __init__(self):
|
||||
self.location = Point()
|
||||
self.direction = Direction()
|
||||
|
||||
|
||||
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.location = Point()
|
||||
self.normal = Direction()
|
||||
self.up = Direction()
|
||||
self.height = 1.0
|
||||
|
||||
|
||||
class Viewpoint:
|
||||
def __init__(self):
|
||||
self.guid = None
|
||||
self.viewpoint = None
|
||||
self.snapshot = None
|
||||
self.index = None
|
||||
|
||||
self.components = None # It's not a list, despite the plural name
|
||||
self.orthogonal_camera = None
|
||||
self.perspective_camera = None
|
||||
self.lines = []
|
||||
self.clipping_planes = []
|
||||
self.bitmaps = []
|
||||
@@ -0,0 +1,154 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Mit XMLSpy v2011 rel. 2 sp1 (http://www.altova.com) von Klaus Linhard (IABI e.V.) bearbeitet -->
|
||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
||||
<xs:element name="Markup">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element name="Header" type="Header" minOccurs="0"/>
|
||||
<xs:element name="Topic" type="Topic"/>
|
||||
<xs:element name="Comment" type="Comment" minOccurs="0" maxOccurs="unbounded"/>
|
||||
<!-- ISG Jira issue BCF-9. Add support for several viewpoints and snapshots per issue -->
|
||||
<xs:element name="Viewpoints" type="ViewPoint" minOccurs="0" maxOccurs="unbounded"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:complexType name="Header">
|
||||
<xs:sequence>
|
||||
<xs:element name="File" maxOccurs="unbounded">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element name="Filename" type="xs:string" minOccurs="0"/>
|
||||
<xs:element name="Date" type="xs:dateTime" minOccurs="0"/>
|
||||
<!-- Reference (URL) of the file -->
|
||||
<xs:element name="Reference" type="xs:string" minOccurs="0"/>
|
||||
</xs:sequence>
|
||||
<xs:attributeGroup ref="FileAttributes"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
<!-- ISG Jira issue BCF-9. Add support for several viewpoints and snapshots per issue -->
|
||||
<xs:complexType name="ViewPoint">
|
||||
<xs:sequence>
|
||||
<!-- viewpoint file (xml) -->
|
||||
<xs:element name="Viewpoint" type="xs:string" minOccurs="0"/>
|
||||
<!-- the snapshot png -->
|
||||
<xs:element name="Snapshot" type="xs:string" minOccurs="0"/>
|
||||
<!-- the viewpoint index (sort order) -->
|
||||
<xs:element name="Index" type="xs:int" minOccurs="0"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="Guid" type="Guid" use="required"/>
|
||||
<!-- Guid of the viewpoint -->
|
||||
</xs:complexType>
|
||||
<!-- BimSnippet -->
|
||||
<xs:complexType name="BimSnippet">
|
||||
<xs:sequence>
|
||||
<!--
|
||||
Name of the file in the topic folder containing the snippet or a URL.
|
||||
E.G.- Expresscode containing p.e Issue, Request
|
||||
// Maybe some header infos ?? // IfcEntites // Geometry
|
||||
-->
|
||||
<!-- Reference (name) to the snippet file -->
|
||||
<xs:element name="Reference" type="xs:string"/>
|
||||
<xs:element name="ReferenceSchema" type="xs:string"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="SnippetType" type="xs:string" use="required"/>
|
||||
<xs:attribute name="isExternal" type="xs:boolean" default="false"/>
|
||||
<!-- This flag is true when the reference is a URL pointing outside of the BCF file-->
|
||||
</xs:complexType>
|
||||
<xs:complexType name="Topic">
|
||||
<xs:sequence>
|
||||
<xs:element name="ReferenceLink" type="xs:string" minOccurs="0" maxOccurs="unbounded"/>
|
||||
<xs:element name="Title" type="xs:string"/>
|
||||
<xs:element name="Priority" type="Priority" minOccurs="0"/>
|
||||
<!-- ISG Jira issue BCF-8 Add a way save order the topics -->
|
||||
<xs:element name="Index" type="xs:int" minOccurs="0"/>
|
||||
<xs:element name="Labels" type="TopicLabel" minOccurs="0" maxOccurs="unbounded"/>
|
||||
<xs:element name="CreationDate" type="xs:dateTime" minOccurs="1"/>
|
||||
<xs:element name="CreationAuthor" type="UserIdType" minOccurs="1"/>
|
||||
<xs:element name="ModifiedDate" type="xs:dateTime" minOccurs="0"/>
|
||||
<xs:element name="ModifiedAuthor" type="UserIdType" minOccurs="0"/>
|
||||
<xs:element name="DueDate" type="xs:dateTime" minOccurs="0"/>
|
||||
<xs:element name="AssignedTo" type="UserIdType" minOccurs="0"/>
|
||||
<xs:element name="Stage" type="Stage" minOccurs="0"/>
|
||||
<xs:element name="Description" type="xs:string" minOccurs="0"/>
|
||||
<xs:element name="BimSnippet" type="BimSnippet" minOccurs="0"/>
|
||||
<!-- Name of the file in the topic folder or url -->
|
||||
<xs:element name="DocumentReference" minOccurs="0" maxOccurs="unbounded">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<!-- Name of the file in the topic folder or url -->
|
||||
<xs:element name="ReferencedDocument" type="xs:string" minOccurs="0"/>
|
||||
<!-- Human readable name of the document -->
|
||||
<xs:element name="Description" type="xs:string" minOccurs="0"/>
|
||||
</xs:sequence>
|
||||
<xs:attributeGroup ref="DocumentReference"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="RelatedTopic" minOccurs="0" maxOccurs="unbounded">
|
||||
<xs:complexType>
|
||||
<xs:attribute name="Guid" type="Guid" use="required"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="Guid" type="Guid" use="required"/>
|
||||
<xs:attribute name="TopicType" type="TopicType"/>
|
||||
<xs:attribute name="TopicStatus" type="TopicStatus"/>
|
||||
</xs:complexType>
|
||||
<!-- Reference to a document inside of the topic folder or a url pointing to the web -->
|
||||
<xs:attributeGroup name="DocumentReference">
|
||||
<!-- Guid of the DocumentReference -->
|
||||
<xs:attribute name="Guid" type="Guid"/>
|
||||
<!-- A flag that is true when the ReferencedDocument points outside of the BCF file (a URL) -->
|
||||
<xs:attribute name="isExternal" type="xs:boolean" default="false"/>
|
||||
</xs:attributeGroup>
|
||||
<xs:complexType name="Comment">
|
||||
<xs:sequence>
|
||||
<xs:element name="Date" type="xs:dateTime"/>
|
||||
<xs:element name="Author" type="UserIdType"/>
|
||||
<xs:element name="Comment" type="xs:string"/>
|
||||
<xs:element name="Viewpoint" minOccurs="0">
|
||||
<xs:complexType>
|
||||
<xs:attribute name="Guid" type="Guid" use="required"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="ModifiedDate" type="xs:dateTime" minOccurs="0"/>
|
||||
<xs:element name="ModifiedAuthor" type="UserIdType" minOccurs="0"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="Guid" type="Guid" use="required"/>
|
||||
</xs:complexType>
|
||||
<xs:simpleType name="TopicStatus">
|
||||
<xs:restriction base="xs:string"/>
|
||||
</xs:simpleType>
|
||||
<xs:simpleType name="TopicType">
|
||||
<xs:restriction base="xs:string"/>
|
||||
</xs:simpleType>
|
||||
<xs:simpleType name="TopicLabel">
|
||||
<xs:restriction base="xs:string"/>
|
||||
</xs:simpleType>
|
||||
<xs:simpleType name="Priority">
|
||||
<xs:restriction base="xs:string"/>
|
||||
</xs:simpleType>
|
||||
<xs:simpleType name="UserIdType">
|
||||
<xs:restriction base="xs:string"/>
|
||||
</xs:simpleType>
|
||||
<xs:simpleType name="Stage">
|
||||
<xs:restriction base="xs:string"/>
|
||||
</xs:simpleType>
|
||||
<xs:simpleType name="Guid">
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:pattern value="[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
<xs:simpleType name="IfcGuid">
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:length value="22"/>
|
||||
<xs:pattern value="[0-9,A-Z,a-z,_$]*"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
<xs:attributeGroup name="FileAttributes">
|
||||
<xs:attribute name="IfcProject" type="IfcGuid"/>
|
||||
<xs:attribute name="IfcSpatialStructureElement" type="IfcGuid"/>
|
||||
<xs:attribute name="isExternal" type="xs:boolean" default="true"/>
|
||||
</xs:attributeGroup>
|
||||
</xs:schema>
|
||||
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Mit XMLSpy v2011 rel. 2 sp1 (http://www.altova.com) von Klaus Linhard (IABI e.V.) bearbeitet -->
|
||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
||||
<xs:element name="ProjectExtension">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element name="Project" type="Project" minOccurs="0"/>
|
||||
<xs:element name="ExtensionSchema" type="xs:anyURI"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:complexType name="Project">
|
||||
<xs:sequence>
|
||||
<xs:element name="Name" type="xs:string" minOccurs="0"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="ProjectId" type="xs:string" use="required"/>
|
||||
</xs:complexType>
|
||||
</xs:schema>
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Mit XMLSpy v2011 rel. 3 (http://www.altova.com) von Klaus Linhard (IABI e.V.) bearbeitet -->
|
||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
||||
<xs:element name="Version">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element name="DetailedVersion" type="xs:string" minOccurs="0"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="VersionId" type="xs:string"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
</xs:schema>
|
||||
@@ -0,0 +1,191 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Mit XMLSpy v2011 rel. 2 sp1 (http://www.altova.com) von Klaus Linhard (IABI e.V.) bearbeitet -->
|
||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" attributeFormDefault="unqualified">
|
||||
<xs:element name="VisualizationInfo">
|
||||
<xs:annotation>
|
||||
<xs:documentation>VisualizationInfo documentation</xs:documentation>
|
||||
</xs:annotation>
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element name="Components" type="Components" minOccurs="0"/>
|
||||
<xs:element name="OrthogonalCamera" type="OrthogonalCamera" minOccurs="0"/>
|
||||
<xs:element name="PerspectiveCamera" type="PerspectiveCamera" minOccurs="0"/>
|
||||
<xs:element name="Lines" minOccurs="0">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element name="Line" type="Line" maxOccurs="unbounded"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="ClippingPlanes" minOccurs="0">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element name="ClippingPlane" type="ClippingPlane" minOccurs="0" maxOccurs="unbounded"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<!-- ISG Jira issue BCF-17: Add support for text in the viewpoints -->
|
||||
<xs:element name="Bitmap" minOccurs="0" maxOccurs="unbounded">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element name="Bitmap" type="BitmapFormat"/>
|
||||
<!-- Name of the bitmap file in the topic folder -->
|
||||
<xs:element name="Reference" type="xs:string"/>
|
||||
<!-- Location of the center of the bitmap -->
|
||||
<xs:element name="Location" type="Point"/>
|
||||
<!-- Normal of the bitmap -->
|
||||
<xs:element name="Normal" type="Direction"/>
|
||||
<!-- Upvector of the bitmap -->
|
||||
<xs:element name="Up" type="Direction"/>
|
||||
<!-- Height of the bitmap -->
|
||||
<xs:element name="Height" type="xs:double"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
</xs:sequence>
|
||||
<!-- Guid of the viewpoint -->
|
||||
<xs:attribute name="Guid" type="Guid" use="required"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:complexType name="OrthogonalCamera">
|
||||
<xs:sequence>
|
||||
<xs:element name="CameraViewPoint" type="Point"/>
|
||||
<xs:element name="CameraDirection" type="Direction"/>
|
||||
<xs:element name="CameraUpVector" type="Direction"/>
|
||||
<xs:element name="ViewToWorldScale" type="xs:double">
|
||||
<xs:annotation>
|
||||
<xs:documentation>view's visible size in meters</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="PerspectiveCamera">
|
||||
<xs:sequence>
|
||||
<xs:element name="CameraViewPoint" type="Point"/>
|
||||
<xs:element name="CameraDirection" type="Direction"/>
|
||||
<xs:element name="CameraUpVector" type="Direction"/>
|
||||
<xs:element name="FieldOfView" type="FieldOfView">
|
||||
<xs:annotation>
|
||||
<xs:documentation>
|
||||
It is currently limited to a value between 45 and 60 degrees.
|
||||
This limitation will be dropped in the next release and viewers
|
||||
should be expect values outside this range in current implementations.
|
||||
</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="Point">
|
||||
<xs:sequence>
|
||||
<xs:element name="X" type="xs:double"/>
|
||||
<xs:element name="Y" type="xs:double"/>
|
||||
<xs:element name="Z" type="xs:double"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="Direction">
|
||||
<xs:sequence>
|
||||
<xs:element name="X" type="xs:double"/>
|
||||
<xs:element name="Y" type="xs:double"/>
|
||||
<xs:element name="Z" type="xs:double"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
<xs:simpleType name="FieldOfView">
|
||||
<xs:restriction base="xs:double">
|
||||
<xs:minInclusive value="1"/>
|
||||
<xs:maxInclusive value="170"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
<xs:complexType name="Components">
|
||||
<xs:sequence>
|
||||
<xs:element name="ViewSetupHints" type="ViewSetupHints" minOccurs="0" />
|
||||
<!-- Components with relevance to the viewpoint. They should be displayed highlighted or selected in a viewer -->
|
||||
<xs:element name="Selection" type="ComponentSelection" minOccurs="0" />
|
||||
<xs:element name="Visibility" type="ComponentVisibility" minOccurs="1" />
|
||||
<xs:element name="Coloring" type="ComponentColoring" minOccurs="0" />
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="ViewSetupHints">
|
||||
<xs:attribute name="SpacesVisible" type="xs:boolean"/>
|
||||
<xs:attribute name="SpaceBoundariesVisible" type="xs:boolean"/>
|
||||
<xs:attribute name="OpeningsVisible" type="xs:boolean"/>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="ComponentSelection">
|
||||
<xs:sequence>
|
||||
<xs:element name="Component" type="Component" maxOccurs="unbounded"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="ComponentVisibility">
|
||||
<xs:sequence>
|
||||
<xs:element name="Exceptions" minOccurs="0">
|
||||
<!-- List Components that are different than the DefaultVisibility. E.g. if DefaultVisibility = false then list
|
||||
Components that should be visible -->
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element name="Component" type="Component" maxOccurs="unbounded"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="DefaultVisibility" type="xs:boolean"/>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="ComponentColoring">
|
||||
<xs:sequence>
|
||||
<xs:element name="Color" maxOccurs="unbounded">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element name="Component" type="Component" maxOccurs="unbounded"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute ref="Color"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="Component">
|
||||
<xs:sequence>
|
||||
<xs:element name="OriginatingSystem" type="xs:string" minOccurs="0"/>
|
||||
<xs:element name="AuthoringToolId" type="xs:string" minOccurs="0"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute ref="IfcGuid"/>
|
||||
<!-- ISG Jira Issue BCF-14 -->
|
||||
</xs:complexType>
|
||||
<xs:attribute name="Color">
|
||||
<xs:simpleType>
|
||||
<xs:restriction base="xs:normalizedString">
|
||||
<!-- Should either match 3 or 4 hex bytes , e.g. "FF00FF" or "FF00FF99" -->
|
||||
<xs:pattern value="[0-9,a-f,A-F]{6}([0-9,a-f,A-F]{2})?"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="IfcGuid">
|
||||
<xs:simpleType>
|
||||
<xs:restriction base="xs:normalizedString">
|
||||
<xs:length value="22"/>
|
||||
<xs:pattern value="[0-9,A-Z,a-z,_$]*"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
</xs:attribute>
|
||||
<xs:complexType name="Line">
|
||||
<xs:sequence>
|
||||
<xs:element name="StartPoint" type="Point"/>
|
||||
<xs:element name="EndPoint" type="Point"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="ClippingPlane">
|
||||
<xs:sequence>
|
||||
<xs:element name="Location" type="Point"/>
|
||||
<xs:element name="Direction" type="Direction"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
<!-- ISG Jira issue BCF-17: Add support for text in the viewpoints -->
|
||||
<xs:simpleType name="BitmapFormat">
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:enumeration value="PNG"/>
|
||||
<xs:enumeration value="JPG"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
<xs:simpleType name="Guid">
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:pattern value="[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
</xs:schema>
|
||||
@@ -2,6 +2,7 @@ import json
|
||||
import ifcopenshell
|
||||
import os
|
||||
|
||||
|
||||
class CA2IFC:
|
||||
def __init__(self, inputFilename, outputFilename):
|
||||
self.inputFilename = inputFilename
|
||||
@@ -30,7 +31,7 @@ class CA2IFC:
|
||||
localPlacement = self.f.createIfcLocalPlacement(None, globalAxes)
|
||||
|
||||
# TODO: create units
|
||||
lengthUnit = self.f.createIfcSIUnit(None, 'LENGTHUNIT', None, 'METRE')
|
||||
lengthUnit = self.f.createIfcSIUnit(None, "LENGTHUNIT", None, "METRE")
|
||||
unitAssignment = self.f.createIfcUnitAssignment((lengthUnit,))
|
||||
|
||||
# create owner history
|
||||
@@ -40,132 +41,246 @@ class CA2IFC:
|
||||
self.reps = self.create_reference_subrep(globalAxes)
|
||||
|
||||
# create project and model
|
||||
project = self.f.createIfcProject(self.guid(), ownerHistory, 'A Project', None, None, None, None, (self.reps['model'],), unitAssignment)
|
||||
model = self.f.createIfcStructuralAnalysisModel(self.guid(), ownerHistory, self.data['name'], None, None, 'NOTDEFINED', globalAxes, None, None, localPlacement)
|
||||
project = self.f.createIfcProject(
|
||||
self.guid(), ownerHistory, "A Project", None, None, None, None, (self.reps["model"],), unitAssignment
|
||||
)
|
||||
model = self.f.createIfcStructuralAnalysisModel(
|
||||
self.guid(),
|
||||
ownerHistory,
|
||||
self.data["name"],
|
||||
None,
|
||||
None,
|
||||
"NOTDEFINED",
|
||||
globalAxes,
|
||||
None,
|
||||
None,
|
||||
localPlacement,
|
||||
)
|
||||
self.f.createIfcRelDeclares(self.guid(), ownerHistory, None, None, project, (model,))
|
||||
|
||||
# create materials
|
||||
ifcMaterials = [None for _ in range(len(self.data['db']['materials']))]
|
||||
for i,material in enumerate(self.data['db']['materials']):
|
||||
ifcMaterials = [None for _ in range(len(self.data["db"]["materials"]))]
|
||||
for i, material in enumerate(self.data["db"]["materials"]):
|
||||
ifcMaterials[i] = self.create_material(material)
|
||||
|
||||
# create profiles
|
||||
ifcProfiles = [None for _ in range(len(self.data['db']['profiles']))]
|
||||
for i,profile in enumerate(self.data['db']['profiles']):
|
||||
ifcProfiles = [None for _ in range(len(self.data["db"]["profiles"]))]
|
||||
for i, profile in enumerate(self.data["db"]["profiles"]):
|
||||
ifcProfiles[i] = self.create_profile(profile)
|
||||
|
||||
# create material-profile sets
|
||||
mpSets = list(set([el['material'] + '-' + el['profile'] for el in self.data['elements'] if el['geometryType'] == 'line']))
|
||||
mpSets = list(
|
||||
set([el["material"] + "-" + el["profile"] for el in self.data["elements"] if el["geometryType"] == "line"])
|
||||
)
|
||||
ifcMaterialProfileSets = [None for _ in range(len(mpSets))]
|
||||
for i, mpSet in enumerate(mpSets):
|
||||
materialIndex = [mat['ifcName'] for mat in self.data['db']['materials']].index(mpSet.split('-')[0])
|
||||
profileIndex = [prof['ifcName'] for prof in self.data['db']['profiles']].index(mpSet.split('-')[1])
|
||||
materialIndex = [mat["ifcName"] for mat in self.data["db"]["materials"]].index(mpSet.split("-")[0])
|
||||
profileIndex = [prof["ifcName"] for prof in self.data["db"]["profiles"]].index(mpSet.split("-")[1])
|
||||
material = ifcMaterials[materialIndex]
|
||||
profile = ifcProfiles[profileIndex]
|
||||
matProf = self.f.createIfcMaterialProfile(self.data['db']['materials'][materialIndex]['name'] + ' | ' + self.data['db']['profiles'][profileIndex]['profileName'], None, material, profile)
|
||||
matProf = self.f.createIfcMaterialProfile(
|
||||
self.data["db"]["materials"][materialIndex]["name"]
|
||||
+ " | "
|
||||
+ self.data["db"]["profiles"][profileIndex]["profileName"],
|
||||
None,
|
||||
material,
|
||||
profile,
|
||||
)
|
||||
ifcMaterialProfileSets[i] = self.f.createIfcMaterialProfileSet(None, None, (matProf,))
|
||||
|
||||
# create structural elements
|
||||
ifcElements = [None for _ in range(len(self.data['elements']))]
|
||||
for i,el in enumerate(self.data['elements']):
|
||||
ifcElements = [None for _ in range(len(self.data["elements"]))]
|
||||
for i, el in enumerate(self.data["elements"]):
|
||||
# geometry - product definition shape
|
||||
prodDefShape = self.create_geometry(el)
|
||||
|
||||
if el['geometryType'] == 'line':
|
||||
if el["geometryType"] == "line":
|
||||
# z axis TODO: group by elements
|
||||
localZAxis = self.f.createIfcDirection(tuple(el['orientation'][2]))
|
||||
localZAxis = self.f.createIfcDirection(tuple(el["orientation"][2]))
|
||||
# element
|
||||
ifcElements[i] = self.f.createIfcStructuralCurveMember(self.guid(), ownerHistory, el['name'], None, None, localPlacement, prodDefShape, el['predefinedType'], localZAxis)
|
||||
ifcElements[i] = self.f.createIfcStructuralCurveMember(
|
||||
self.guid(),
|
||||
ownerHistory,
|
||||
el["name"],
|
||||
None,
|
||||
None,
|
||||
localPlacement,
|
||||
prodDefShape,
|
||||
el["predefinedType"],
|
||||
localZAxis,
|
||||
)
|
||||
|
||||
if el['geometryType'] == 'surface':
|
||||
ifcElements[i] = self.f.createIfcStructuralSurfaceMember(self.guid(), ownerHistory, el['name'], None, None, localPlacement, prodDefShape, el['predefinedType'], el['thickness'])
|
||||
if el["geometryType"] == "surface":
|
||||
ifcElements[i] = self.f.createIfcStructuralSurfaceMember(
|
||||
self.guid(),
|
||||
ownerHistory,
|
||||
el["name"],
|
||||
None,
|
||||
None,
|
||||
localPlacement,
|
||||
prodDefShape,
|
||||
el["predefinedType"],
|
||||
el["thickness"],
|
||||
)
|
||||
|
||||
# create structural point connections
|
||||
ifcConnections = [None for _ in range(len(self.data['connections']))]
|
||||
for i,conn in enumerate(self.data['connections']):
|
||||
ifcConnections = [None for _ in range(len(self.data["connections"]))]
|
||||
for i, conn in enumerate(self.data["connections"]):
|
||||
# geometry - product definition shape
|
||||
prodDefShape = self.create_geometry(conn)
|
||||
|
||||
# boundary conditions
|
||||
if conn['appliedCondition']:
|
||||
bc = self.create_applied_conditions(conn['appliedCondition'], conn['geometryType'])
|
||||
if conn['geometryType'] == 'point':
|
||||
appliedCondition = self.f.createIfcBoundaryNodeCondition(None, bc['dx'], bc['dy'], bc['dz'], bc['drx'], bc['dry'], bc['drz'])
|
||||
if conn['geometryType'] == 'line':
|
||||
appliedCondition = self.f.createIfcBoundaryEdgeCondition(None, bc['dx'], bc['dy'], bc['dz'], bc['drx'], bc['dry'], bc['drz'])
|
||||
if conn['geometryType'] == 'surface':
|
||||
appliedCondition = self.f.createIfcBoundaryFaceCondition(None, bc['dx'], bc['dy'], bc['dz'])
|
||||
if conn["appliedCondition"]:
|
||||
bc = self.create_applied_conditions(conn["appliedCondition"], conn["geometryType"])
|
||||
if conn["geometryType"] == "point":
|
||||
appliedCondition = self.f.createIfcBoundaryNodeCondition(
|
||||
None, bc["dx"], bc["dy"], bc["dz"], bc["drx"], bc["dry"], bc["drz"]
|
||||
)
|
||||
if conn["geometryType"] == "line":
|
||||
appliedCondition = self.f.createIfcBoundaryEdgeCondition(
|
||||
None, bc["dx"], bc["dy"], bc["dz"], bc["drx"], bc["dry"], bc["drz"]
|
||||
)
|
||||
if conn["geometryType"] == "surface":
|
||||
appliedCondition = self.f.createIfcBoundaryFaceCondition(None, bc["dx"], bc["dy"], bc["dz"])
|
||||
else:
|
||||
appliedCondition = None
|
||||
|
||||
if conn['geometryType'] == 'point':
|
||||
if conn["geometryType"] == "point":
|
||||
# local axes
|
||||
localAxes = self.create_orientation(conn['orientation'])
|
||||
localAxes = self.create_orientation(conn["orientation"])
|
||||
# connection
|
||||
ifcConnections[i] = self.f.createIfcStructuralPointConnection(self.guid(), ownerHistory, conn['name'], None, None, localPlacement, prodDefShape, appliedCondition, localAxes)
|
||||
ifcConnections[i] = self.f.createIfcStructuralPointConnection(
|
||||
self.guid(),
|
||||
ownerHistory,
|
||||
conn["name"],
|
||||
None,
|
||||
None,
|
||||
localPlacement,
|
||||
prodDefShape,
|
||||
appliedCondition,
|
||||
localAxes,
|
||||
)
|
||||
|
||||
if conn['geometryType'] == 'line':
|
||||
if conn["geometryType"] == "line":
|
||||
# z axis TODO: group by elements
|
||||
localZAxis = self.f.createIfcDirection(tuple(conn['orientation'][2]))
|
||||
localZAxis = self.f.createIfcDirection(tuple(conn["orientation"][2]))
|
||||
# connection
|
||||
ifcConnections[i] = self.f.createIfcStructuralCurveConnection(self.guid(), ownerHistory, conn['name'], None, None, localPlacement, prodDefShape, appliedCondition, localZAxis)
|
||||
ifcConnections[i] = self.f.createIfcStructuralCurveConnection(
|
||||
self.guid(),
|
||||
ownerHistory,
|
||||
conn["name"],
|
||||
None,
|
||||
None,
|
||||
localPlacement,
|
||||
prodDefShape,
|
||||
appliedCondition,
|
||||
localZAxis,
|
||||
)
|
||||
|
||||
if conn['geometryType'] == 'surface':
|
||||
ifcConnections[i] = self.f.createIfcStructuralSurfaceConnection(self.guid(), ownerHistory, conn['name'], None, None, localPlacement, prodDefShape, appliedCondition)
|
||||
if conn["geometryType"] == "surface":
|
||||
ifcConnections[i] = self.f.createIfcStructuralSurfaceConnection(
|
||||
self.guid(), ownerHistory, conn["name"], None, None, localPlacement, prodDefShape, appliedCondition
|
||||
)
|
||||
|
||||
# assign material-profile-sets
|
||||
for i, mpSet in enumerate(mpSets):
|
||||
groupOfElements = []
|
||||
for j,el in enumerate(self.data['elements']):
|
||||
if el['geometryType'] == 'line' and el['material'] + '-' + el['profile'] == mpSet:
|
||||
for j, el in enumerate(self.data["elements"]):
|
||||
if el["geometryType"] == "line" and el["material"] + "-" + el["profile"] == mpSet:
|
||||
groupOfElements.append(ifcElements[j])
|
||||
|
||||
if groupOfElements:
|
||||
self.f.createIfcRelAssociatesMaterial(self.guid(), ownerHistory, None, None, tuple(groupOfElements), ifcMaterialProfileSets[i])
|
||||
self.f.createIfcRelAssociatesMaterial(
|
||||
self.guid(), ownerHistory, None, None, tuple(groupOfElements), ifcMaterialProfileSets[i]
|
||||
)
|
||||
|
||||
# assign materials
|
||||
for i,mat in enumerate(self.data['db']['materials']):
|
||||
for i, mat in enumerate(self.data["db"]["materials"]):
|
||||
groupOfElements = []
|
||||
for j,el in enumerate(self.data['elements']):
|
||||
if el['geometryType'] == 'surface' and el['material'] == mat['ifcName']:
|
||||
for j, el in enumerate(self.data["elements"]):
|
||||
if el["geometryType"] == "surface" and el["material"] == mat["ifcName"]:
|
||||
groupOfElements.append(ifcElements[j])
|
||||
if groupOfElements:
|
||||
self.f.createIfcRelAssociatesMaterial(self.guid(), ownerHistory, None, None, tuple(groupOfElements), ifcMaterials[i])
|
||||
self.f.createIfcRelAssociatesMaterial(
|
||||
self.guid(), ownerHistory, None, None, tuple(groupOfElements), ifcMaterials[i]
|
||||
)
|
||||
|
||||
# create connections with elements
|
||||
for i,el in enumerate(self.data['elements']):
|
||||
for conn in el['connections']:
|
||||
j = [c['ifcName'] for c in self.data['connections']].index(conn['relatedConnection'])
|
||||
geometryType = self.data['connections'][j]['geometryType']
|
||||
for i, el in enumerate(self.data["elements"]):
|
||||
for conn in el["connections"]:
|
||||
j = [c["ifcName"] for c in self.data["connections"]].index(conn["relatedConnection"])
|
||||
geometryType = self.data["connections"][j]["geometryType"]
|
||||
|
||||
if conn['appliedCondition']:
|
||||
bc = self.create_applied_conditions(conn['appliedCondition'], geometryType)
|
||||
if geometryType == 'point':
|
||||
appliedCondition = self.f.createIfcBoundaryNodeCondition(None, bc['dx'], bc['dy'], bc['dz'], bc['drx'], bc['dry'], bc['drz'])
|
||||
if geometryType == 'line':
|
||||
appliedCondition = self.f.createIfcBoundaryEdgeCondition(None, bc['dx'], bc['dy'], bc['dz'], bc['drx'], bc['dry'], bc['drz'])
|
||||
if geometryType == 'surface':
|
||||
appliedCondition = self.f.createIfcBoundaryFaceCondition(None, bc['dx'], bc['dy'], bc['dz'])
|
||||
if conn["appliedCondition"]:
|
||||
bc = self.create_applied_conditions(conn["appliedCondition"], geometryType)
|
||||
if geometryType == "point":
|
||||
appliedCondition = self.f.createIfcBoundaryNodeCondition(
|
||||
None, bc["dx"], bc["dy"], bc["dz"], bc["drx"], bc["dry"], bc["drz"]
|
||||
)
|
||||
if geometryType == "line":
|
||||
appliedCondition = self.f.createIfcBoundaryEdgeCondition(
|
||||
None, bc["dx"], bc["dy"], bc["dz"], bc["drx"], bc["dry"], bc["drz"]
|
||||
)
|
||||
if geometryType == "surface":
|
||||
appliedCondition = self.f.createIfcBoundaryFaceCondition(None, bc["dx"], bc["dy"], bc["dz"])
|
||||
else:
|
||||
appliedCondition = None
|
||||
|
||||
# local axes
|
||||
localAxes = self.create_orientation(conn['orientation'])
|
||||
localAxes = self.create_orientation(conn["orientation"])
|
||||
|
||||
if geometryType == 'point':
|
||||
if not conn['eccentricity']:
|
||||
self.f.createIfcRelConnectsStructuralMember(self.guid(), ownerHistory, None, None, ifcElements[i], ifcConnections[j], appliedCondition, None, None, localAxes)
|
||||
if geometryType == "point":
|
||||
if not conn["eccentricity"]:
|
||||
self.f.createIfcRelConnectsStructuralMember(
|
||||
self.guid(),
|
||||
ownerHistory,
|
||||
None,
|
||||
None,
|
||||
ifcElements[i],
|
||||
ifcConnections[j],
|
||||
appliedCondition,
|
||||
None,
|
||||
None,
|
||||
localAxes,
|
||||
)
|
||||
else:
|
||||
pointOnElement = self.f.createIfcCartesianPoint(tuple(conn['eccentricity']['pointOnElement']))
|
||||
vector = conn['eccentricity']['vector']
|
||||
connPointEcc = self.f.createIfcConnectionPointEccentricity(pointOnElement, None, vector[0], vector[1], vector[2])
|
||||
self.f.createIfcRelConnectsWithEccentricity(self.guid(), ownerHistory, None, None, ifcElements[i], ifcConnections[j], appliedCondition, None, None, localAxes, connPointEcc)
|
||||
pointOnElement = self.f.createIfcCartesianPoint(tuple(conn["eccentricity"]["pointOnElement"]))
|
||||
vector = conn["eccentricity"]["vector"]
|
||||
connPointEcc = self.f.createIfcConnectionPointEccentricity(
|
||||
pointOnElement, None, vector[0], vector[1], vector[2]
|
||||
)
|
||||
self.f.createIfcRelConnectsWithEccentricity(
|
||||
self.guid(),
|
||||
ownerHistory,
|
||||
None,
|
||||
None,
|
||||
ifcElements[i],
|
||||
ifcConnections[j],
|
||||
appliedCondition,
|
||||
None,
|
||||
None,
|
||||
localAxes,
|
||||
connPointEcc,
|
||||
)
|
||||
|
||||
if geometryType in ['line', 'surface']:
|
||||
self.f.createIfcRelConnectsStructuralMember(self.guid(), ownerHistory, None, None, ifcElements[i], ifcConnections[j], appliedCondition, None, None, localAxes)
|
||||
if geometryType in ["line", "surface"]:
|
||||
self.f.createIfcRelConnectsStructuralMember(
|
||||
self.guid(),
|
||||
ownerHistory,
|
||||
None,
|
||||
None,
|
||||
ifcElements[i],
|
||||
ifcConnections[j],
|
||||
appliedCondition,
|
||||
None,
|
||||
None,
|
||||
localAxes,
|
||||
)
|
||||
|
||||
# assign elements and connections to group
|
||||
self.f.createIfcRelAssignsToGroup(self.guid(), ownerHistory, None, None, tuple(ifcElements + ifcConnections), None, model)
|
||||
self.f.createIfcRelAssignsToGroup(
|
||||
self.guid(), ownerHistory, None, None, tuple(ifcElements + ifcConnections), None, model
|
||||
)
|
||||
|
||||
# finalize ifc file
|
||||
self.f.write(self.outputFilename)
|
||||
@@ -177,10 +292,10 @@ class CA2IFC:
|
||||
self.f.wrapped_data.header.file_name.name = os.path.basename(self.outputFilename)
|
||||
|
||||
def create_global_axes(self):
|
||||
self.xAxis = self.f.createIfcDirection((1., 0., 0.))
|
||||
self.yAxis = self.f.createIfcDirection((0., 1., 0.))
|
||||
self.zAxis = self.f.createIfcDirection((0., 0., 1.))
|
||||
self.origin = self.f.createIfcCartesianPoint((0., 0., 0.))
|
||||
self.xAxis = self.f.createIfcDirection((1.0, 0.0, 0.0))
|
||||
self.yAxis = self.f.createIfcDirection((0.0, 1.0, 0.0))
|
||||
self.zAxis = self.f.createIfcDirection((0.0, 0.0, 1.0))
|
||||
self.origin = self.f.createIfcCartesianPoint((0.0, 0.0, 0.0))
|
||||
axes = self.f.createIfcAxis2Placement3D(self.origin, self.zAxis, self.xAxis)
|
||||
|
||||
return axes
|
||||
@@ -193,156 +308,197 @@ class CA2IFC:
|
||||
return axes
|
||||
|
||||
def create_owner_history(self):
|
||||
actor = self.f.createIfcActorRole('ENGINEER', None, None)
|
||||
person = self.f.createIfcPerson('Christovasilis', None, 'Ioannis', None, None, None, (actor,))
|
||||
organization = self.f.createIfcOrganization(None, 'IfcOpenShell', 'IfcOpenShell, an open source (LGPL) software library that helps users and software developers to work with the IFC file format.')
|
||||
actor = self.f.createIfcActorRole("ENGINEER", None, None)
|
||||
person = self.f.createIfcPerson("Christovasilis", None, "Ioannis", None, None, None, (actor,))
|
||||
organization = self.f.createIfcOrganization(
|
||||
None,
|
||||
"IfcOpenShell",
|
||||
"IfcOpenShell, an open source (LGPL) software library that helps users and software developers to work with the IFC file format.",
|
||||
)
|
||||
p_o = self.f.createIfcPersonAndOrganization(person, organization)
|
||||
application = self.f.createIfcApplication(organization, 'v0.0.x', 'IFC2CA', 'IFC2CA')
|
||||
ownerHistory = self.f.createIfcOwnerHistory(p_o, application, 'READWRITE', None, None, p_o, application)
|
||||
application = self.f.createIfcApplication(organization, "v0.0.x", "IFC2CA", "IFC2CA")
|
||||
ownerHistory = self.f.createIfcOwnerHistory(p_o, application, "READWRITE", None, None, p_o, application)
|
||||
|
||||
return ownerHistory
|
||||
|
||||
def create_reference_subrep(self, globalAxes):
|
||||
modelRep = self.f.createIfcGeometricRepresentationContext(None, 'Model', 3, 1.E-05, globalAxes, None)
|
||||
bodySubRep = self.f.createIfcGeometricRepresentationSubContext('Body', 'Model', None, None, None , None, modelRep, None, 'MODEL_VIEW', None)
|
||||
refSubRep = self.f.createIfcGeometricRepresentationSubContext('Reference', 'Model', None, None, None , None, modelRep, None, 'GRAPH_VIEW', None)
|
||||
modelRep = self.f.createIfcGeometricRepresentationContext(None, "Model", 3, 1.0e-05, globalAxes, None)
|
||||
bodySubRep = self.f.createIfcGeometricRepresentationSubContext(
|
||||
"Body", "Model", None, None, None, None, modelRep, None, "MODEL_VIEW", None
|
||||
)
|
||||
refSubRep = self.f.createIfcGeometricRepresentationSubContext(
|
||||
"Reference", "Model", None, None, None, None, modelRep, None, "GRAPH_VIEW", None
|
||||
)
|
||||
|
||||
return {
|
||||
'model': modelRep,
|
||||
'body': bodySubRep,
|
||||
'reference': refSubRep
|
||||
}
|
||||
return {"model": modelRep, "body": bodySubRep, "reference": refSubRep}
|
||||
|
||||
def create_material(self, material):
|
||||
ifcMaterial = self.f.createIfcMaterial(material['name'], None, material['category'])
|
||||
ifcMaterial = self.f.createIfcMaterial(material["name"], None, material["category"])
|
||||
|
||||
mechProps = []
|
||||
if 'youngModulus' in material['mechProps']:
|
||||
youngModulus = self.f.createIfcPropertySingleValue('YoungModulus', None, self.f.createIfcModulusOfElasticityMeasure(material['mechProps']['youngModulus']))
|
||||
if "youngModulus" in material["mechProps"]:
|
||||
youngModulus = self.f.createIfcPropertySingleValue(
|
||||
"YoungModulus", None, self.f.createIfcModulusOfElasticityMeasure(material["mechProps"]["youngModulus"])
|
||||
)
|
||||
mechProps.append(youngModulus)
|
||||
if 'shearModulus' in material['mechProps']:
|
||||
shearModulus = self.f.createIfcPropertySingleValue('ShearModulus', None, self.f.createIfcModulusOfElasticityMeasure(material['mechProps']['shearModulus']))
|
||||
if "shearModulus" in material["mechProps"]:
|
||||
shearModulus = self.f.createIfcPropertySingleValue(
|
||||
"ShearModulus", None, self.f.createIfcModulusOfElasticityMeasure(material["mechProps"]["shearModulus"])
|
||||
)
|
||||
mechProps.append(shearModulus)
|
||||
if 'poissonRatio' in material['mechProps']:
|
||||
poissonRatio = self.f.createIfcPropertySingleValue('PoissonRatio', None, self.f.createIfcPositiveRatioMeasure(material['mechProps']['poissonRatio']))
|
||||
if "poissonRatio" in material["mechProps"]:
|
||||
poissonRatio = self.f.createIfcPropertySingleValue(
|
||||
"PoissonRatio", None, self.f.createIfcPositiveRatioMeasure(material["mechProps"]["poissonRatio"])
|
||||
)
|
||||
mechProps.append(poissonRatio)
|
||||
if mechProps:
|
||||
self.f.createIfcMaterialProperties('Pset_MaterialMechanical', material['name'], tuple(mechProps), ifcMaterial)
|
||||
self.f.createIfcMaterialProperties(
|
||||
"Pset_MaterialMechanical", material["name"], tuple(mechProps), ifcMaterial
|
||||
)
|
||||
|
||||
commonProps = []
|
||||
if 'massDensity' in material['commonProps']:
|
||||
massDensity = self.f.createIfcPropertySingleValue('MassDensity', None, self.f.createIfcMassDensityMeasure(material['commonProps']['massDensity']))
|
||||
if "massDensity" in material["commonProps"]:
|
||||
massDensity = self.f.createIfcPropertySingleValue(
|
||||
"MassDensity", None, self.f.createIfcMassDensityMeasure(material["commonProps"]["massDensity"])
|
||||
)
|
||||
commonProps.append(massDensity)
|
||||
if commonProps:
|
||||
self.f.createIfcMaterialProperties('Pset_MaterialCommon', material['name'], tuple(commonProps), ifcMaterial)
|
||||
self.f.createIfcMaterialProperties("Pset_MaterialCommon", material["name"], tuple(commonProps), ifcMaterial)
|
||||
|
||||
return ifcMaterial
|
||||
|
||||
def create_profile(self, profile):
|
||||
if profile['profileShape'] == 'rectangular':
|
||||
ifcProfile = self.f.createIfcRectangleProfileDef(profile['profileType'], profile['profileName'], None, profile['xDim'], profile['yDim'])
|
||||
if profile["profileShape"] == "rectangular":
|
||||
ifcProfile = self.f.createIfcRectangleProfileDef(
|
||||
profile["profileType"], profile["profileName"], None, profile["xDim"], profile["yDim"]
|
||||
)
|
||||
|
||||
if profile['profileShape'] == 'iSymmetrical':
|
||||
if profile["profileShape"] == "iSymmetrical":
|
||||
ifcProfile = self.f.createIfcIShapeProfileDef(
|
||||
profile['profileType'], profile['profileName'], None,
|
||||
profile['commonProps']['overallWidth'],
|
||||
profile['commonProps']['overallDepth'],
|
||||
profile['commonProps']['webThickness'],
|
||||
profile['commonProps']['flangeThickness'],
|
||||
profile['commonProps']['filletRadius']
|
||||
profile["profileType"],
|
||||
profile["profileName"],
|
||||
None,
|
||||
profile["commonProps"]["overallWidth"],
|
||||
profile["commonProps"]["overallDepth"],
|
||||
profile["commonProps"]["webThickness"],
|
||||
profile["commonProps"]["flangeThickness"],
|
||||
profile["commonProps"]["filletRadius"],
|
||||
)
|
||||
|
||||
mechProps = []
|
||||
if 'massPerLength' in profile['mechProps']:
|
||||
massPerLength = self.f.createIfcPropertySingleValue('MassPerLength', None, self.f.createIfcMassPerLengthMeasure(profile['mechProps']['massPerLength']))
|
||||
if "massPerLength" in profile["mechProps"]:
|
||||
massPerLength = self.f.createIfcPropertySingleValue(
|
||||
"MassPerLength", None, self.f.createIfcMassPerLengthMeasure(profile["mechProps"]["massPerLength"])
|
||||
)
|
||||
mechProps.append(massPerLength)
|
||||
if 'crossSectionArea' in profile['mechProps']:
|
||||
crossSectionArea = self.f.createIfcPropertySingleValue('CrossSectionArea', None, self.f.createIfcAreaMeasure(profile['mechProps']['crossSectionArea']))
|
||||
if "crossSectionArea" in profile["mechProps"]:
|
||||
crossSectionArea = self.f.createIfcPropertySingleValue(
|
||||
"CrossSectionArea", None, self.f.createIfcAreaMeasure(profile["mechProps"]["crossSectionArea"])
|
||||
)
|
||||
mechProps.append(crossSectionArea)
|
||||
if 'momentOfInertiaY' in profile['mechProps']:
|
||||
momentOfInertiaY = self.f.createIfcPropertySingleValue('MomentOfInertiaY', None, self.f.createIfcMomentOfInertiaMeasure(profile['mechProps']['momentOfInertiaY']))
|
||||
if "momentOfInertiaY" in profile["mechProps"]:
|
||||
momentOfInertiaY = self.f.createIfcPropertySingleValue(
|
||||
"MomentOfInertiaY",
|
||||
None,
|
||||
self.f.createIfcMomentOfInertiaMeasure(profile["mechProps"]["momentOfInertiaY"]),
|
||||
)
|
||||
mechProps.append(momentOfInertiaY)
|
||||
if 'momentOfInertiaZ' in profile['mechProps']:
|
||||
momentOfInertiaZ = self.f.createIfcPropertySingleValue('MomentOfInertiaZ', None, self.f.createIfcMomentOfInertiaMeasure(profile['mechProps']['momentOfInertiaZ']))
|
||||
if "momentOfInertiaZ" in profile["mechProps"]:
|
||||
momentOfInertiaZ = self.f.createIfcPropertySingleValue(
|
||||
"MomentOfInertiaZ",
|
||||
None,
|
||||
self.f.createIfcMomentOfInertiaMeasure(profile["mechProps"]["momentOfInertiaZ"]),
|
||||
)
|
||||
mechProps.append(momentOfInertiaZ)
|
||||
if 'torsionalConstantX' in profile['mechProps']:
|
||||
torsionalConstantX = self.f.createIfcPropertySingleValue('TorsionalConstantX', None, self.f.createIfcMomentOfInertiaMeasure(profile['mechProps']['torsionalConstantX']))
|
||||
if "torsionalConstantX" in profile["mechProps"]:
|
||||
torsionalConstantX = self.f.createIfcPropertySingleValue(
|
||||
"TorsionalConstantX",
|
||||
None,
|
||||
self.f.createIfcMomentOfInertiaMeasure(profile["mechProps"]["torsionalConstantX"]),
|
||||
)
|
||||
mechProps.append(torsionalConstantX)
|
||||
if mechProps:
|
||||
self.f.createIfcProfileProperties('Pset_ProfileMechanical', profile['profileName'], tuple(mechProps), ifcProfile)
|
||||
self.f.createIfcProfileProperties(
|
||||
"Pset_ProfileMechanical", profile["profileName"], tuple(mechProps), ifcProfile
|
||||
)
|
||||
|
||||
return ifcProfile
|
||||
|
||||
def create_geometry(self, object):
|
||||
if object['geometryType'] == 'point':
|
||||
point = self.f.createIfcCartesianPoint(tuple(object['geometry']))
|
||||
if object["geometryType"] == "point":
|
||||
point = self.f.createIfcCartesianPoint(tuple(object["geometry"]))
|
||||
vertex = self.f.createIfcVertexPoint(point)
|
||||
vertexTopologyRep = self.f.createIfcTopologyRepresentation(self.reps['reference'], 'Reference', 'Vertex', (vertex,))
|
||||
vertexTopologyRep = self.f.createIfcTopologyRepresentation(
|
||||
self.reps["reference"], "Reference", "Vertex", (vertex,)
|
||||
)
|
||||
vertexProdDefShape = self.f.createIfcProductDefinitionShape(None, None, (vertexTopologyRep,))
|
||||
|
||||
return vertexProdDefShape
|
||||
|
||||
if object['geometryType'] == 'line':
|
||||
startPoint = self.f.createIfcCartesianPoint(tuple(object['geometry'][0]))
|
||||
if object["geometryType"] == "line":
|
||||
startPoint = self.f.createIfcCartesianPoint(tuple(object["geometry"][0]))
|
||||
startVertex = self.f.createIfcVertexPoint(startPoint)
|
||||
endPoint = self.f.createIfcCartesianPoint(tuple(object['geometry'][1]))
|
||||
endPoint = self.f.createIfcCartesianPoint(tuple(object["geometry"][1]))
|
||||
endVertex = self.f.createIfcVertexPoint(endPoint)
|
||||
edge = self.f.createIfcEdge(startVertex, endVertex)
|
||||
edgeTopologyRep = self.f.createIfcTopologyRepresentation(self.reps['reference'], 'Reference', 'Edge', (edge,))
|
||||
edgeTopologyRep = self.f.createIfcTopologyRepresentation(
|
||||
self.reps["reference"], "Reference", "Edge", (edge,)
|
||||
)
|
||||
edgeProdDefShape = self.f.createIfcProductDefinitionShape(None, None, (edgeTopologyRep,))
|
||||
|
||||
return edgeProdDefShape
|
||||
|
||||
if object['geometryType'] == 'surface':
|
||||
verts = [None for _ in range(len(object['geometry']))]
|
||||
for i,p in enumerate(object['geometry']):
|
||||
if object["geometryType"] == "surface":
|
||||
verts = [None for _ in range(len(object["geometry"]))]
|
||||
for i, p in enumerate(object["geometry"]):
|
||||
point = self.f.createIfcCartesianPoint(tuple(p))
|
||||
verts[i] = self.f.createIfcVertexPoint(point)
|
||||
|
||||
orientedEdges = [None for _ in range(len(object['geometry']))]
|
||||
orientedEdges = [None for _ in range(len(object["geometry"]))]
|
||||
for i, v in enumerate(verts):
|
||||
v2Index = (i + 1) if i < len(verts) - 1 else 0
|
||||
edge = self.f.createIfcEdge(v, verts[v2Index])
|
||||
orientedEdges[i] = self.f.createIfcOrientedEdge(None, None, edge, True)
|
||||
|
||||
edgeLoop = self.f.createIfcEdgeLoop(tuple(orientedEdges))
|
||||
localAxes = self.create_orientation(object['orientation'])
|
||||
localAxes = self.create_orientation(object["orientation"])
|
||||
plane = self.f.createIfcPlane(localAxes)
|
||||
faceBound = self.f.createIfcFaceBound(edgeLoop, True)
|
||||
face = self.f.createIfcFaceSurface((faceBound,), plane, True)
|
||||
faceTopologyRep = self.f.createIfcTopologyRepresentation(self.reps['reference'], 'Reference', 'Face', (face,))
|
||||
faceTopologyRep = self.f.createIfcTopologyRepresentation(
|
||||
self.reps["reference"], "Reference", "Face", (face,)
|
||||
)
|
||||
faceProdDefShape = self.f.createIfcProductDefinitionShape(None, None, (faceTopologyRep,))
|
||||
|
||||
return faceProdDefShape
|
||||
|
||||
def create_applied_conditions(self, bc, geometryType):
|
||||
for dof in ['dx', 'dy', 'dz']:
|
||||
for dof in ["dx", "dy", "dz"]:
|
||||
if isinstance(bc[dof], bool):
|
||||
bc[dof] = self.f.createIfcBoolean(bc[dof])
|
||||
else:
|
||||
if geometryType == 'point':
|
||||
if geometryType == "point":
|
||||
bc[dof] = self.f.createIfcLinearStiffnessMeasure(bc[dof])
|
||||
if geometryType == 'line':
|
||||
if geometryType == "line":
|
||||
bc[dof] = self.f.createIfcModulusOfLinearSubgradeReactionMeasure(bc[dof])
|
||||
if geometryType == 'surface':
|
||||
if geometryType == "surface":
|
||||
bc[dof] = self.f.createIfcModulusOfSubgradeReactionMeasure(bc[dof])
|
||||
|
||||
for dof in ['drx', 'dry', 'drz']:
|
||||
for dof in ["drx", "dry", "drz"]:
|
||||
if isinstance(bc[dof], bool):
|
||||
bc[dof] = self.f.createIfcBoolean(bc[dof])
|
||||
else:
|
||||
if geometryType == 'point':
|
||||
if geometryType == "point":
|
||||
bc[dof] = self.f.createIfcRotationalStiffnessMeasure(bc[dof])
|
||||
if geometryType == 'line':
|
||||
if geometryType == "line":
|
||||
bc[dof] = self.f.createIfcModulusOfRotationalSubgradeReactionMeasure(bc[dof])
|
||||
|
||||
return bc
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
inputFilename = 'structure_01.json'
|
||||
outputFilename = 'structure_01.ifc'
|
||||
if __name__ == "__main__":
|
||||
inputFilename = "structure_01.json"
|
||||
outputFilename = "structure_01.ifc"
|
||||
|
||||
ca2ifc = CA2IFC(inputFilename, outputFilename)
|
||||
ca2ifc.convert()
|
||||
|
||||
@@ -4,59 +4,59 @@ import json
|
||||
import ifcopenshell
|
||||
import numpy as np
|
||||
|
||||
|
||||
class IFC2CA:
|
||||
def __init__(self, filename):
|
||||
self.filename = filename
|
||||
self.file = None
|
||||
self.result = {}
|
||||
self.warnings = []
|
||||
self.tol = 1E-06
|
||||
self.tol = 1e-06
|
||||
|
||||
def convert(self):
|
||||
self.file = ifcopenshell.open(self.filename)
|
||||
for model in self.file.by_type('IfcStructuralAnalysisModel'):
|
||||
elements = self.get_structural_items(model, item_type='IfcStructuralMember')
|
||||
connections = self.get_structural_items(model, item_type='IfcStructuralConnection')
|
||||
for model in self.file.by_type("IfcStructuralAnalysisModel"):
|
||||
elements = self.get_structural_items(model, item_type="IfcStructuralMember")
|
||||
connections = self.get_structural_items(model, item_type="IfcStructuralConnection")
|
||||
|
||||
materialdb = []
|
||||
materials = list(dict.fromkeys([e['material'] for e in elements]))
|
||||
materials = list(dict.fromkeys([e["material"] for e in elements]))
|
||||
for mat in [mat for mat in materials if mat]:
|
||||
id = int(mat.split('|')[1])
|
||||
id = int(mat.split("|")[1])
|
||||
material = self.get_material_properties(self.file.by_id(id))
|
||||
material['relatedElements'] = [e['ifcName'] for e in elements if 'material' in e and e['material'] == mat]
|
||||
material["relatedElements"] = [
|
||||
e["ifcName"] for e in elements if "material" in e and e["material"] == mat
|
||||
]
|
||||
materialdb.append(material)
|
||||
|
||||
profiledb = []
|
||||
profiles = list(dict.fromkeys([e['profile'] for e in elements if 'profile' in e]))
|
||||
profiles = list(dict.fromkeys([e["profile"] for e in elements if "profile" in e]))
|
||||
for prof in [prof for prof in profiles if prof]:
|
||||
id = int(prof.split('|')[1])
|
||||
id = int(prof.split("|")[1])
|
||||
profile = self.get_profile_properties(self.file.by_id(id))
|
||||
profile['relatedElements'] = [e['ifcName'] for e in elements if 'profile' in e and e['profile'] == prof]
|
||||
profile["relatedElements"] = [e["ifcName"] for e in elements if "profile" in e and e["profile"] == prof]
|
||||
profiledb.append(profile)
|
||||
|
||||
self.result = {
|
||||
'ifcName': model.is_a() + '|' + str(model.id()),
|
||||
'name': model.Name,
|
||||
'id': model.GlobalId,
|
||||
'elements': elements,
|
||||
'connections': connections,
|
||||
'db': {
|
||||
'materials': materialdb,
|
||||
'profiles': profiledb
|
||||
},
|
||||
'warnings': self.warnings
|
||||
"ifcName": model.is_a() + "|" + str(model.id()),
|
||||
"name": model.Name,
|
||||
"id": model.GlobalId,
|
||||
"elements": elements,
|
||||
"connections": connections,
|
||||
"db": {"materials": materialdb, "profiles": profiledb},
|
||||
"warnings": self.warnings,
|
||||
}
|
||||
|
||||
print('Model "%s" converted' % model.Name)
|
||||
print('Number of elements: ', len(elements))
|
||||
print('Number of connections: ', len(connections))
|
||||
print('Number of materials: ', len(materialdb))
|
||||
print('Number of profiles: ', len(profiledb))
|
||||
print('')
|
||||
print("Number of elements: ", len(elements))
|
||||
print("Number of connections: ", len(connections))
|
||||
print("Number of materials: ", len(materialdb))
|
||||
print("Number of profiles: ", len(profiledb))
|
||||
print("")
|
||||
|
||||
break
|
||||
|
||||
def get_structural_items(self, model, item_type='IfcStructuralItem'):
|
||||
def get_structural_items(self, model, item_type="IfcStructuralItem"):
|
||||
items = []
|
||||
for group in model.IsGroupedBy:
|
||||
for item in group.RelatedObjects:
|
||||
@@ -70,100 +70,112 @@ class IFC2CA:
|
||||
def get_item_data(self, item):
|
||||
transformation = self.get_transformation(item.ObjectPlacement)
|
||||
|
||||
if item.is_a('IfcStructuralCurveMember'):
|
||||
representation = self.get_representation(item, 'Edge')
|
||||
if item.is_a("IfcStructuralCurveMember"):
|
||||
representation = self.get_representation(item, "Edge")
|
||||
material_profile = self.get_material_profile(item)
|
||||
if not representation:
|
||||
self.warnings.append('No representation defined for %s. Member excluded' % (item.is_a() + '|' + str(item.id())))
|
||||
self.warnings.append(
|
||||
"No representation defined for %s. Member excluded" % (item.is_a() + "|" + str(item.id()))
|
||||
)
|
||||
return
|
||||
if not material_profile:
|
||||
self.warnings.append('No material defined for in %s' % (item.is_a() + '|' + str(item.id())))
|
||||
self.warnings.append('No profile defined for in %s' % (item.is_a() + '|' + str(item.id())))
|
||||
self.warnings.append("No material defined for in %s" % (item.is_a() + "|" + str(item.id())))
|
||||
self.warnings.append("No profile defined for in %s" % (item.is_a() + "|" + str(item.id())))
|
||||
materialId = None
|
||||
profileId = None
|
||||
else:
|
||||
material = material_profile.Material
|
||||
materialId = material.is_a() + '|' + str(material.id())
|
||||
materialId = material.is_a() + "|" + str(material.id())
|
||||
profile = material_profile.Profile
|
||||
profileId = profile.is_a() + '|' + str(profile.id())
|
||||
profileId = profile.is_a() + "|" + str(profile.id())
|
||||
|
||||
geometry = self.get_geometry(representation)
|
||||
orientation = self.get_1D_orientation(geometry, item.Axis)
|
||||
connections = self.get_connection_data(item.ConnectedBy)
|
||||
for conn in connections:
|
||||
if not conn['orientation']:
|
||||
conn['orientation'] = orientation
|
||||
if not conn["orientation"]:
|
||||
conn["orientation"] = orientation
|
||||
# --> Correct pointOnElement for eccentricity connection for ETABS files
|
||||
length = np.linalg.norm(np.array(geometry[1]) - np.array(geometry[0]))
|
||||
for c in connections:
|
||||
if c['eccentricity']:
|
||||
if np.linalg.norm(np.array(c['eccentricity']['pointOnElement'])) > length + self.tol:
|
||||
print(np.linalg.norm(np.array(c['eccentricity']['pointOnElement'])), '>', length)
|
||||
self.warnings.append('Eccentricity in %s corrected' % (item.is_a() + '|' + str(item.id())))
|
||||
c['eccentricity']['pointOnElement'][0] = length
|
||||
if c["eccentricity"]:
|
||||
if np.linalg.norm(np.array(c["eccentricity"]["pointOnElement"])) > length + self.tol:
|
||||
print(np.linalg.norm(np.array(c["eccentricity"]["pointOnElement"])), ">", length)
|
||||
self.warnings.append("Eccentricity in %s corrected" % (item.is_a() + "|" + str(item.id())))
|
||||
c["eccentricity"]["pointOnElement"][0] = length
|
||||
# End <--
|
||||
if transformation:
|
||||
geometry = self.transform_vectors(geometry, transformation)
|
||||
orientation = self.transform_vectors(orientation, transformation, include_translation=False)
|
||||
for c in connections:
|
||||
c['orientation'] = self.transform_vectors(c['orientation'], transformation, include_translation=False)
|
||||
if c['eccentricity']:
|
||||
c['eccentricity']['vector'] = self.transform_vectors(c['eccentricity']['vector'], transformation, include_translation=False)
|
||||
c["orientation"] = self.transform_vectors(
|
||||
c["orientation"], transformation, include_translation=False
|
||||
)
|
||||
if c["eccentricity"]:
|
||||
c["eccentricity"]["vector"] = self.transform_vectors(
|
||||
c["eccentricity"]["vector"], transformation, include_translation=False
|
||||
)
|
||||
|
||||
return {
|
||||
'ifcName': item.is_a() + '|' + str(item.id()),
|
||||
'name': item.Name,
|
||||
'id': item.GlobalId,
|
||||
'geometryType': 'line',
|
||||
'predefinedType': item.PredefinedType,
|
||||
'geometry': geometry,
|
||||
'orientation': orientation,
|
||||
'material': materialId,
|
||||
'profile': profileId,
|
||||
'connections': connections
|
||||
"ifcName": item.is_a() + "|" + str(item.id()),
|
||||
"name": item.Name,
|
||||
"id": item.GlobalId,
|
||||
"geometryType": "line",
|
||||
"predefinedType": item.PredefinedType,
|
||||
"geometry": geometry,
|
||||
"orientation": orientation,
|
||||
"material": materialId,
|
||||
"profile": profileId,
|
||||
"connections": connections,
|
||||
}
|
||||
|
||||
elif item.is_a('IfcStructuralSurfaceMember'):
|
||||
representation = self.get_representation(item, 'Face')
|
||||
elif item.is_a("IfcStructuralSurfaceMember"):
|
||||
representation = self.get_representation(item, "Face")
|
||||
material = self.get_material_profile(item)
|
||||
if not representation:
|
||||
self.warnings.append('No representation defined for %s. Member excluded' % (item.is_a() + '|' + str(item.id())))
|
||||
self.warnings.append(
|
||||
"No representation defined for %s. Member excluded" % (item.is_a() + "|" + str(item.id()))
|
||||
)
|
||||
return
|
||||
if not material:
|
||||
self.warnings.append('No material defined for in %s' % (item.is_a() + '|' + str(item.id())))
|
||||
self.warnings.append("No material defined for in %s" % (item.is_a() + "|" + str(item.id())))
|
||||
materialId = None
|
||||
else:
|
||||
materialId = material.is_a() + '|' + str(material.id())
|
||||
materialId = material.is_a() + "|" + str(material.id())
|
||||
|
||||
geometry = self.get_geometry(representation)
|
||||
orientation = self.get_2D_orientation(representation)
|
||||
connections = self.get_connection_data(item.ConnectedBy)
|
||||
for conn in connections:
|
||||
if not conn['orientation']:
|
||||
conn['orientation'] = orientation
|
||||
if not conn["orientation"]:
|
||||
conn["orientation"] = orientation
|
||||
if transformation:
|
||||
geometry = self.transform_vectors(geometry, transformation)
|
||||
orientation = self.transform_vectors(orientation, transformation, include_translation=False)
|
||||
for c in connections:
|
||||
c['orientation'] = self.transform_vectors(c['orientation'], transformation, include_translation=False)
|
||||
c["orientation"] = self.transform_vectors(
|
||||
c["orientation"], transformation, include_translation=False
|
||||
)
|
||||
|
||||
return {
|
||||
'ifcName': item.is_a() + '|' + str(item.id()),
|
||||
'name': item.Name,
|
||||
'id': item.GlobalId,
|
||||
'geometryType': 'surface',
|
||||
'predefinedType': item.PredefinedType,
|
||||
'thickness': item.Thickness,
|
||||
'geometry': geometry,
|
||||
'orientation': orientation,
|
||||
'material': materialId,
|
||||
'connections': connections
|
||||
"ifcName": item.is_a() + "|" + str(item.id()),
|
||||
"name": item.Name,
|
||||
"id": item.GlobalId,
|
||||
"geometryType": "surface",
|
||||
"predefinedType": item.PredefinedType,
|
||||
"thickness": item.Thickness,
|
||||
"geometry": geometry,
|
||||
"orientation": orientation,
|
||||
"material": materialId,
|
||||
"connections": connections,
|
||||
}
|
||||
|
||||
elif item.is_a('IfcStructuralPointConnection'):
|
||||
representation = self.get_representation(item, 'Vertex')
|
||||
elif item.is_a("IfcStructuralPointConnection"):
|
||||
representation = self.get_representation(item, "Vertex")
|
||||
if not representation:
|
||||
self.warnings.append('No representation defined for %s. Connection excluded' % (item.is_a() + '|' + str(item.id())))
|
||||
self.warnings.append(
|
||||
"No representation defined for %s. Connection excluded" % (item.is_a() + "|" + str(item.id()))
|
||||
)
|
||||
return
|
||||
|
||||
geometry = self.get_geometry(representation)
|
||||
@@ -175,20 +187,22 @@ class IFC2CA:
|
||||
orientation = self.transform_vectors(orientation, transformation, include_translation=False)
|
||||
|
||||
return {
|
||||
'ifcName': item.is_a() + '|' + str(item.id()),
|
||||
'name': item.Name,
|
||||
'id': item.GlobalId,
|
||||
'geometryType': 'point',
|
||||
'geometry': geometry,
|
||||
'orientation': orientation,
|
||||
'appliedCondition': self.get_connection_input(item, 'point'),
|
||||
'relatedElements': [con.is_a() + '|' + str(con.id()) for con in item.ConnectsStructuralMembers]
|
||||
"ifcName": item.is_a() + "|" + str(item.id()),
|
||||
"name": item.Name,
|
||||
"id": item.GlobalId,
|
||||
"geometryType": "point",
|
||||
"geometry": geometry,
|
||||
"orientation": orientation,
|
||||
"appliedCondition": self.get_connection_input(item, "point"),
|
||||
"relatedElements": [con.is_a() + "|" + str(con.id()) for con in item.ConnectsStructuralMembers],
|
||||
}
|
||||
|
||||
elif item.is_a('IfcStructuralCurveConnection'):
|
||||
representation = self.get_representation(item, 'Edge')
|
||||
elif item.is_a("IfcStructuralCurveConnection"):
|
||||
representation = self.get_representation(item, "Edge")
|
||||
if not representation:
|
||||
self.warnings.append('No representation defined for %s. Connection excluded' % (item.is_a() + '|' + str(item.id())))
|
||||
self.warnings.append(
|
||||
"No representation defined for %s. Connection excluded" % (item.is_a() + "|" + str(item.id()))
|
||||
)
|
||||
return
|
||||
|
||||
geometry = self.get_geometry(representation)
|
||||
@@ -200,22 +214,22 @@ class IFC2CA:
|
||||
orientation = self.transform_vectors(orientation, transformation, include_translation=False)
|
||||
|
||||
return {
|
||||
'ifcName': item.is_a() + '|' + str(item.id()),
|
||||
'name': item.Name,
|
||||
'id': item.GlobalId,
|
||||
'geometryType': 'line',
|
||||
'geometry': geometry,
|
||||
'orientation': orientation,
|
||||
'appliedCondition': self.get_connection_input(item, 'line'),
|
||||
'relatedElements': [con.is_a() + '|' + str(con.id()) for con in item.ConnectsStructuralMembers]
|
||||
"ifcName": item.is_a() + "|" + str(item.id()),
|
||||
"name": item.Name,
|
||||
"id": item.GlobalId,
|
||||
"geometryType": "line",
|
||||
"geometry": geometry,
|
||||
"orientation": orientation,
|
||||
"appliedCondition": self.get_connection_input(item, "line"),
|
||||
"relatedElements": [con.is_a() + "|" + str(con.id()) for con in item.ConnectsStructuralMembers],
|
||||
}
|
||||
|
||||
def get_transformation(self, placement):
|
||||
if not placement:
|
||||
return None
|
||||
if placement.is_a('IfcLocalPlacement'):
|
||||
if placement.is_a("IfcLocalPlacement"):
|
||||
if placement.PlacementRelTo:
|
||||
print('Warning! Object Placement with PlacementRelTo attribute is not supported and will be neglected')
|
||||
print("Warning! Object Placement with PlacementRelTo attribute is not supported and will be neglected")
|
||||
axes = placement.RelativePlacement
|
||||
location = np.array(self.get_coordinate(axes.Location))
|
||||
if axes.Axis and axes.RefDirection:
|
||||
@@ -227,29 +241,30 @@ class IFC2CA:
|
||||
xAxis = np.cross(yAxis, zAxis)
|
||||
xAxis /= np.linalg.norm(xAxis)
|
||||
else:
|
||||
if np.allclose(location, np.array([0., 0., 0.])):
|
||||
if np.allclose(location, np.array([0.0, 0.0, 0.0])):
|
||||
return None
|
||||
xAxis = np.array([1., 0., 0.])
|
||||
yAxis = np.array([0., 1., 0.])
|
||||
zAxis = np.array([0., 0., 1.])
|
||||
if (np.allclose(location, np.array([0., 0., 0.])) and
|
||||
np.allclose(xAxis, np.array([1., 0., 0.])) and
|
||||
np.allclose(yAxis, np.array([0., 1., 0.])) and
|
||||
np.allclose(zAxis, np.array([0., 0., 1.]))):
|
||||
xAxis = np.array([1.0, 0.0, 0.0])
|
||||
yAxis = np.array([0.0, 1.0, 0.0])
|
||||
zAxis = np.array([0.0, 0.0, 1.0])
|
||||
if (
|
||||
np.allclose(location, np.array([0.0, 0.0, 0.0]))
|
||||
and np.allclose(xAxis, np.array([1.0, 0.0, 0.0]))
|
||||
and np.allclose(yAxis, np.array([0.0, 1.0, 0.0]))
|
||||
and np.allclose(zAxis, np.array([0.0, 0.0, 1.0]))
|
||||
):
|
||||
return None
|
||||
return {
|
||||
'location': location,
|
||||
'rotationMatrix': np.array([xAxis, yAxis, zAxis]).transpose()
|
||||
}
|
||||
return {"location": location, "rotationMatrix": np.array([xAxis, yAxis, zAxis]).transpose()}
|
||||
else:
|
||||
print('Warning! Object Placement is of type %s, which is not supported. Default considered' % placement.is_a())
|
||||
print(
|
||||
"Warning! Object Placement is of type %s, which is not supported. Default considered" % placement.is_a()
|
||||
)
|
||||
return None
|
||||
|
||||
def get_representation(self, element, rep_type):
|
||||
if not element.Representation:
|
||||
return None
|
||||
for representation in element.Representation.Representations:
|
||||
rep = self.get_specific_representation(representation, 'Reference', rep_type)
|
||||
rep = self.get_specific_representation(representation, "Reference", rep_type)
|
||||
if rep:
|
||||
return rep
|
||||
else:
|
||||
@@ -260,42 +275,45 @@ class IFC2CA:
|
||||
return rep
|
||||
|
||||
def get_specific_representation(self, representation, rep_id, rep_type):
|
||||
if (representation.RepresentationIdentifier == rep_id or rep_id is None) \
|
||||
and representation.RepresentationType == rep_type:
|
||||
if (
|
||||
representation.RepresentationIdentifier == rep_id or rep_id is None
|
||||
) and representation.RepresentationType == rep_type:
|
||||
return representation
|
||||
if representation.RepresentationType == 'MappedRepresentation':
|
||||
if representation.RepresentationType == "MappedRepresentation":
|
||||
return self.get_specific_representation(
|
||||
representation.Items[0].MappingSource.MappedRepresentation,
|
||||
rep_id, rep_type)
|
||||
representation.Items[0].MappingSource.MappedRepresentation, rep_id, rep_type
|
||||
)
|
||||
|
||||
def get_geometry(self, representation):
|
||||
# Maybe IfcOpenShell can use create_shape here to simplify this, but
|
||||
# supposedly structural models are very simple anyway, so perhaps we
|
||||
# can do without it.
|
||||
item = representation.Items[0]
|
||||
if item.is_a('IfcEdge'):
|
||||
if item.is_a("IfcEdge"):
|
||||
return [
|
||||
self.get_coordinate(item.EdgeStart.VertexGeometry),
|
||||
self.get_coordinate(item.EdgeEnd.VertexGeometry)
|
||||
self.get_coordinate(item.EdgeEnd.VertexGeometry),
|
||||
]
|
||||
|
||||
elif item.is_a('IfcFaceSurface'):
|
||||
elif item.is_a("IfcFaceSurface"):
|
||||
edges = item.Bounds[0].Bound.EdgeList
|
||||
coords = []
|
||||
for edge in edges:
|
||||
coords.append(self.get_coordinate(edge.EdgeElement.EdgeStart.VertexGeometry))
|
||||
return coords
|
||||
|
||||
elif item.is_a('IfcVertexPoint'):
|
||||
elif item.is_a("IfcVertexPoint"):
|
||||
return self.get_coordinate(item.VertexGeometry)
|
||||
|
||||
def get_coordinate(self, point):
|
||||
if point.is_a('IfcCartesianPoint'):
|
||||
if point.is_a("IfcCartesianPoint"):
|
||||
return list(point.Coordinates)
|
||||
|
||||
def get_0D_orientation(self, axes):
|
||||
if axes and axes.Axis and axes.RefDirection:
|
||||
xAxis = np.array(axes.RefDirection.DirectionRatios) # this can be not strictly perpendicular (in the xz plane)
|
||||
xAxis = np.array(
|
||||
axes.RefDirection.DirectionRatios
|
||||
) # this can be not strictly perpendicular (in the xz plane)
|
||||
zAxis = np.array(axes.Axis.DirectionRatios)
|
||||
zAxis /= np.linalg.norm(zAxis)
|
||||
yAxis = np.cross(zAxis, xAxis)
|
||||
@@ -320,7 +338,7 @@ class IFC2CA:
|
||||
|
||||
def get_2D_orientation(self, representation):
|
||||
item = representation.Items[0]
|
||||
if item.is_a('IfcFaceSurface'):
|
||||
if item.is_a("IfcFaceSurface"):
|
||||
item.SameSense
|
||||
axes = item.FaceSurface.Position
|
||||
orientation = self.get_0D_orientation(axes)
|
||||
@@ -334,9 +352,9 @@ class IFC2CA:
|
||||
globalGeometry = []
|
||||
|
||||
for p in geometry:
|
||||
gp = trsf['rotationMatrix'].dot(np.array(p))
|
||||
gp = trsf["rotationMatrix"].dot(np.array(p))
|
||||
if include_translation:
|
||||
gp += trsf['location']
|
||||
gp += trsf["location"]
|
||||
globalGeometry.append(gp.tolist())
|
||||
|
||||
if len(globalGeometry) == 1: # single point
|
||||
@@ -348,36 +366,36 @@ class IFC2CA:
|
||||
if not element.HasAssociations:
|
||||
return None
|
||||
for association in element.HasAssociations:
|
||||
if not association.is_a('IfcRelAssociatesMaterial'):
|
||||
if not association.is_a("IfcRelAssociatesMaterial"):
|
||||
continue
|
||||
material = association.RelatingMaterial
|
||||
if material.is_a('IfcMaterialProfileSet'):
|
||||
if material.is_a("IfcMaterialProfileSet"):
|
||||
# For now, we only deal with a single profile
|
||||
return material.MaterialProfiles[0]
|
||||
if material.is_a('IfcMaterialProfileSetUsage'):
|
||||
if material.is_a("IfcMaterialProfileSetUsage"):
|
||||
return material.ForProfileSet.MaterialProfiles[0]
|
||||
if material.is_a('IfcMaterial'):
|
||||
if material.is_a("IfcMaterial"):
|
||||
return material
|
||||
|
||||
def get_material_properties(self, material):
|
||||
psets = material.HasProperties
|
||||
|
||||
if self.get_pset_properties(psets, 'Pset_MaterialMechanical'):
|
||||
mechProps = self.get_pset_properties(psets, 'Pset_MaterialMechanical')
|
||||
if self.get_pset_properties(psets, "Pset_MaterialMechanical"):
|
||||
mechProps = self.get_pset_properties(psets, "Pset_MaterialMechanical")
|
||||
else:
|
||||
mechProps = self.get_pset_properties(psets, None)
|
||||
|
||||
if self.get_pset_properties(psets, 'Pset_MaterialCommon'):
|
||||
commonProps = self.get_pset_properties(psets, 'Pset_MaterialCommon')
|
||||
if self.get_pset_properties(psets, "Pset_MaterialCommon"):
|
||||
commonProps = self.get_pset_properties(psets, "Pset_MaterialCommon")
|
||||
else:
|
||||
commonProps = self.get_pset_properties(psets, None)
|
||||
|
||||
return {
|
||||
'ifcName': material.is_a() + '|' + str(material.id()),
|
||||
'name': material.Name,
|
||||
'category': material.Category,
|
||||
'mechProps': mechProps,
|
||||
'commonProps':commonProps
|
||||
"ifcName": material.is_a() + "|" + str(material.id()),
|
||||
"name": material.Name,
|
||||
"category": material.Category,
|
||||
"mechProps": mechProps,
|
||||
"commonProps": commonProps,
|
||||
}
|
||||
|
||||
def get_pset_property(self, psets, pset_name, prop_name):
|
||||
@@ -397,98 +415,113 @@ class IFC2CA:
|
||||
return d
|
||||
|
||||
def get_profile_properties(self, profile):
|
||||
if profile.is_a('IfcRectangleProfileDef'):
|
||||
if profile.is_a("IfcRectangleProfileDef"):
|
||||
return {
|
||||
'ifcName': profile.is_a() + '|' + str(profile.id()),
|
||||
'profileName': profile.ProfileName,
|
||||
'profileType': profile.ProfileType,
|
||||
'profileShape': 'rectangular',
|
||||
'xDim': profile.XDim,
|
||||
'yDim': profile.YDim
|
||||
"ifcName": profile.is_a() + "|" + str(profile.id()),
|
||||
"profileName": profile.ProfileName,
|
||||
"profileType": profile.ProfileType,
|
||||
"profileShape": "rectangular",
|
||||
"xDim": profile.XDim,
|
||||
"yDim": profile.YDim,
|
||||
}
|
||||
|
||||
if profile.is_a('IfcIShapeProfileDef'):
|
||||
if profile.is_a("IfcIShapeProfileDef"):
|
||||
psets = profile.HasProperties
|
||||
|
||||
if self.get_pset_properties(psets, 'Pset_ProfileMechanical'):
|
||||
mechProps = self.get_pset_properties(psets, 'Pset_ProfileMechanical')
|
||||
if self.get_pset_properties(psets, "Pset_ProfileMechanical"):
|
||||
mechProps = self.get_pset_properties(psets, "Pset_ProfileMechanical")
|
||||
else:
|
||||
mechProps = self.get_i_section_properties(profile, 'iSymmetrical')
|
||||
mechProps = self.get_i_section_properties(profile, "iSymmetrical")
|
||||
|
||||
return {
|
||||
'ifcName': profile.is_a() + '|' + str(profile.id()),
|
||||
'profileName': profile.ProfileName,
|
||||
'profileType': profile.ProfileType,
|
||||
'profileShape': 'iSymmetrical',
|
||||
'mechProps': mechProps,
|
||||
'commonProps': {
|
||||
'flangeThickness': profile.FlangeThickness,
|
||||
'webThickness': profile.WebThickness,
|
||||
'overallDepth': profile.OverallDepth,
|
||||
'overallWidth': profile.OverallWidth,
|
||||
'filletRadius': profile.FilletRadius,
|
||||
}
|
||||
"ifcName": profile.is_a() + "|" + str(profile.id()),
|
||||
"profileName": profile.ProfileName,
|
||||
"profileType": profile.ProfileType,
|
||||
"profileShape": "iSymmetrical",
|
||||
"mechProps": mechProps,
|
||||
"commonProps": {
|
||||
"flangeThickness": profile.FlangeThickness,
|
||||
"webThickness": profile.WebThickness,
|
||||
"overallDepth": profile.OverallDepth,
|
||||
"overallWidth": profile.OverallWidth,
|
||||
"filletRadius": profile.FilletRadius,
|
||||
},
|
||||
}
|
||||
|
||||
def get_connection_data(self, itemList):
|
||||
return [{
|
||||
'ifcName': rel.is_a() + '|' + str(rel.id()),
|
||||
'id': rel.GlobalId,
|
||||
'relatingElement': rel.RelatingStructuralMember.is_a() + '|' + str(rel.RelatingStructuralMember.id()),
|
||||
'relatedConnection': rel.RelatedStructuralConnection.is_a() + '|' + str(rel.RelatedStructuralConnection.id()),
|
||||
'orientation': self.get_0D_orientation(rel.ConditionCoordinateSystem),
|
||||
'appliedCondition': self.get_connection_input(rel, self.get_geometry_type_from_connection(rel.RelatedStructuralConnection)),
|
||||
'eccentricity': None if not rel.is_a('IfcRelConnectsWithEccentricity') else {
|
||||
'vector': [
|
||||
0.0 if not rel.ConnectionConstraint.EccentricityInX else rel.ConnectionConstraint.EccentricityInX,
|
||||
0.0 if not rel.ConnectionConstraint.EccentricityInY else rel.ConnectionConstraint.EccentricityInY,
|
||||
0.0 if not rel.ConnectionConstraint.EccentricityInZ else rel.ConnectionConstraint.EccentricityInZ
|
||||
return [
|
||||
{
|
||||
"ifcName": rel.is_a() + "|" + str(rel.id()),
|
||||
"id": rel.GlobalId,
|
||||
"relatingElement": rel.RelatingStructuralMember.is_a() + "|" + str(rel.RelatingStructuralMember.id()),
|
||||
"relatedConnection": rel.RelatedStructuralConnection.is_a()
|
||||
+ "|"
|
||||
+ str(rel.RelatedStructuralConnection.id()),
|
||||
"orientation": self.get_0D_orientation(rel.ConditionCoordinateSystem),
|
||||
"appliedCondition": self.get_connection_input(
|
||||
rel, self.get_geometry_type_from_connection(rel.RelatedStructuralConnection)
|
||||
),
|
||||
"eccentricity": None
|
||||
if not rel.is_a("IfcRelConnectsWithEccentricity")
|
||||
else {
|
||||
"vector": [
|
||||
0.0
|
||||
if not rel.ConnectionConstraint.EccentricityInX
|
||||
else rel.ConnectionConstraint.EccentricityInX,
|
||||
0.0
|
||||
if not rel.ConnectionConstraint.EccentricityInY
|
||||
else rel.ConnectionConstraint.EccentricityInY,
|
||||
0.0
|
||||
if not rel.ConnectionConstraint.EccentricityInZ
|
||||
else rel.ConnectionConstraint.EccentricityInZ,
|
||||
],
|
||||
'pointOnElement': self.get_coordinate(rel.ConnectionConstraint.PointOnRelatingElement)
|
||||
"pointOnElement": self.get_coordinate(rel.ConnectionConstraint.PointOnRelatingElement),
|
||||
},
|
||||
}
|
||||
} for rel in itemList]
|
||||
for rel in itemList
|
||||
]
|
||||
|
||||
def get_geometry_type_from_connection(self, connection):
|
||||
if connection.is_a('IfcStructuralPointConnection'):
|
||||
return 'point'
|
||||
if connection.is_a('IfcStructuralCurveConnection'):
|
||||
return 'line'
|
||||
if connection.is_a('IfcStructuralSurfaceConnection'):
|
||||
return 'surface'
|
||||
if connection.is_a("IfcStructuralPointConnection"):
|
||||
return "point"
|
||||
if connection.is_a("IfcStructuralCurveConnection"):
|
||||
return "line"
|
||||
if connection.is_a("IfcStructuralSurfaceConnection"):
|
||||
return "surface"
|
||||
|
||||
def get_connection_input(self, connection, geometryType):
|
||||
if connection.AppliedCondition:
|
||||
if geometryType == 'point':
|
||||
if geometryType == "point":
|
||||
return {
|
||||
'dx': connection.AppliedCondition.TranslationalStiffnessX.wrappedValue,
|
||||
'dy': connection.AppliedCondition.TranslationalStiffnessY.wrappedValue,
|
||||
'dz': connection.AppliedCondition.TranslationalStiffnessZ.wrappedValue,
|
||||
'drx': connection.AppliedCondition.RotationalStiffnessX.wrappedValue,
|
||||
'dry': connection.AppliedCondition.RotationalStiffnessY.wrappedValue,
|
||||
'drz': connection.AppliedCondition.RotationalStiffnessZ.wrappedValue
|
||||
"dx": connection.AppliedCondition.TranslationalStiffnessX.wrappedValue,
|
||||
"dy": connection.AppliedCondition.TranslationalStiffnessY.wrappedValue,
|
||||
"dz": connection.AppliedCondition.TranslationalStiffnessZ.wrappedValue,
|
||||
"drx": connection.AppliedCondition.RotationalStiffnessX.wrappedValue,
|
||||
"dry": connection.AppliedCondition.RotationalStiffnessY.wrappedValue,
|
||||
"drz": connection.AppliedCondition.RotationalStiffnessZ.wrappedValue,
|
||||
}
|
||||
|
||||
if geometryType == 'line':
|
||||
if geometryType == "line":
|
||||
return {
|
||||
'dx': connection.AppliedCondition.TranslationalStiffnessByLengthX.wrappedValue,
|
||||
'dy': connection.AppliedCondition.TranslationalStiffnessByLengthY.wrappedValue,
|
||||
'dz': connection.AppliedCondition.TranslationalStiffnessByLengthZ.wrappedValue,
|
||||
'drx': connection.AppliedCondition.RotationalStiffnessByLengthX.wrappedValue,
|
||||
'dry': connection.AppliedCondition.RotationalStiffnessByLengthY.wrappedValue,
|
||||
'drz': connection.AppliedCondition.RotationalStiffnessByLengthZ.wrappedValue
|
||||
"dx": connection.AppliedCondition.TranslationalStiffnessByLengthX.wrappedValue,
|
||||
"dy": connection.AppliedCondition.TranslationalStiffnessByLengthY.wrappedValue,
|
||||
"dz": connection.AppliedCondition.TranslationalStiffnessByLengthZ.wrappedValue,
|
||||
"drx": connection.AppliedCondition.RotationalStiffnessByLengthX.wrappedValue,
|
||||
"dry": connection.AppliedCondition.RotationalStiffnessByLengthY.wrappedValue,
|
||||
"drz": connection.AppliedCondition.RotationalStiffnessByLengthZ.wrappedValue,
|
||||
}
|
||||
|
||||
if geometryType == 'surface':
|
||||
if geometryType == "surface":
|
||||
return {
|
||||
'dx': connection.AppliedCondition.TranslationalStiffnessByAreaX.wrappedValue,
|
||||
'dy': connection.AppliedCondition.TranslationalStiffnessByAreaY.wrappedValue,
|
||||
'dz': connection.AppliedCondition.TranslationalStiffnessByAreaZ.wrappedValue
|
||||
"dx": connection.AppliedCondition.TranslationalStiffnessByAreaX.wrappedValue,
|
||||
"dy": connection.AppliedCondition.TranslationalStiffnessByAreaY.wrappedValue,
|
||||
"dz": connection.AppliedCondition.TranslationalStiffnessByAreaZ.wrappedValue,
|
||||
}
|
||||
|
||||
return connection.AppliedCondition
|
||||
|
||||
def get_i_section_properties(self, profile, profileShape):
|
||||
if profileShape == 'iSymmetrical':
|
||||
if profileShape == "iSymmetrical":
|
||||
tf = profile.FlangeThickness
|
||||
tw = profile.WebThickness
|
||||
h = profile.OverallDepth
|
||||
@@ -499,20 +532,16 @@ class IFC2CA:
|
||||
Iz = (2 * tf) * (b ** 3) / 12 + (h - 2 * tf) * (tw ** 3) / 12
|
||||
Jx = 1 / 3 * ((h - tf) * (tw ** 3) + 2 * b * (tf ** 3))
|
||||
|
||||
return {
|
||||
'crossSectionArea': A,
|
||||
'momentOfInertiaY': Iy,
|
||||
'momentOfInertiaZ': Iz,
|
||||
'torsionalConstantX': Jx
|
||||
}
|
||||
return {"crossSectionArea": A, "momentOfInertiaY": Iy, "momentOfInertiaZ": Iz, "torsionalConstantX": Jx}
|
||||
|
||||
if __name__ == '__main__':
|
||||
fileNames = ['cantilever_01', 'portal_01', 'grid_of_beams', 'slab_01', 'structure_01']
|
||||
|
||||
if __name__ == "__main__":
|
||||
fileNames = ["cantilever_01", "portal_01", "grid_of_beams", "slab_01", "structure_01"]
|
||||
files = fileNames
|
||||
|
||||
for fileName in files:
|
||||
BASE_PATH = '/home/jesusbill/Dev-Projects/github.com/IfcOpenShell/analysis-models/ifcFiles/'
|
||||
ifc2ca = IFC2CA(BASE_PATH + fileName + '.ifc')
|
||||
BASE_PATH = "/home/jesusbill/Dev-Projects/github.com/IfcOpenShell/analysis-models/ifcFiles/"
|
||||
ifc2ca = IFC2CA(BASE_PATH + fileName + ".ifc")
|
||||
ifc2ca.convert()
|
||||
with open(BASE_PATH + fileName + '.json', 'w') as f:
|
||||
with open(BASE_PATH + fileName + ".json", "w") as f:
|
||||
f.write(json.dumps(ifc2ca.result, indent=4))
|
||||
|
||||
@@ -11,6 +11,7 @@ import itertools
|
||||
|
||||
flatten = itertools.chain.from_iterable
|
||||
|
||||
|
||||
class MODEL:
|
||||
def __init__(self, dataFilename, medFilename, meshSize):
|
||||
self.dataFilename = dataFilename
|
||||
@@ -22,20 +23,20 @@ class MODEL:
|
||||
self.create()
|
||||
|
||||
def getGroupName(self, name):
|
||||
info = name.split('|')
|
||||
sortName = ''.join(c for c in info[0] if c.isupper())
|
||||
return str(sortName + '_' + info[1])
|
||||
info = name.split("|")
|
||||
sortName = "".join(c for c in info[0] if c.isupper())
|
||||
return str(sortName + "_" + info[1])
|
||||
|
||||
def makePoint(self, pl):
|
||||
'''Function to define a Point from
|
||||
a polyline (list of 1 point)'''
|
||||
"""Function to define a Point from
|
||||
a polyline (list of 1 point)"""
|
||||
|
||||
(x, y, z) = pl
|
||||
return self.geompy.MakeVertex(x, y, z)
|
||||
|
||||
def makeLine(self, pl):
|
||||
'''Function to define a Line from
|
||||
a polyline (list of 2 points)'''
|
||||
"""Function to define a Line from
|
||||
a polyline (list of 2 points)"""
|
||||
|
||||
(x, y, z) = pl[0]
|
||||
P1 = self.geompy.MakeVertex(x, y, z)
|
||||
@@ -45,8 +46,8 @@ class MODEL:
|
||||
return self.geompy.MakeLineTwoPnt(P1, P2)
|
||||
|
||||
def makeFace(self, pl):
|
||||
'''Function to define a Face from
|
||||
a polyline (list of points)'''
|
||||
"""Function to define a Face from
|
||||
a polyline (list of points)"""
|
||||
|
||||
pointList = [None for _ in range(len(pl))]
|
||||
for ip, (x, y, z) in enumerate(pl):
|
||||
@@ -60,45 +61,45 @@ class MODEL:
|
||||
return self.geompy.MakeFaceWires(LineList, 1)
|
||||
|
||||
def makeObject(self, geometry, geometryType):
|
||||
if geometryType == 'point':
|
||||
if geometryType == "point":
|
||||
return self.makePoint(geometry)
|
||||
if geometryType == 'line':
|
||||
if geometryType == "line":
|
||||
return self.makeLine(geometry)
|
||||
if geometryType == 'surface':
|
||||
if geometryType == "surface":
|
||||
return self.makeFace(geometry)
|
||||
|
||||
def makePartition(self, objects, geometryType):
|
||||
if geometryType == 'point':
|
||||
shapeType = 'VERTEX'
|
||||
if geometryType == 'line':
|
||||
shapeType = 'EDGE'
|
||||
if geometryType == 'surface':
|
||||
shapeType = 'FACE'
|
||||
if geometryType == "point":
|
||||
shapeType = "VERTEX"
|
||||
if geometryType == "line":
|
||||
shapeType = "EDGE"
|
||||
if geometryType == "surface":
|
||||
shapeType = "FACE"
|
||||
return self.geompy.MakePartition(objects, [], [], [], self.geompy.ShapeType[shapeType], 0, [], 1)
|
||||
|
||||
def getLinkGeometry(self, ecc, orientation, finalPoint):
|
||||
vector = np.array(orientation).transpose().dot(ecc['vector'])
|
||||
vector = np.array(orientation).transpose().dot(ecc["vector"])
|
||||
initialPoint = (np.array(finalPoint) - vector).tolist()
|
||||
return [initialPoint, finalPoint]
|
||||
|
||||
def length(self, geometry):
|
||||
return ((
|
||||
(geometry[1][0] - geometry[0][0]) ** 2 + \
|
||||
(geometry[1][1] - geometry[0][1]) ** 2 + \
|
||||
(geometry[1][2] - geometry[0][2]) ** 2 \
|
||||
) ** 0.5)
|
||||
return (
|
||||
(geometry[1][0] - geometry[0][0]) ** 2
|
||||
+ (geometry[1][1] - geometry[0][1]) ** 2
|
||||
+ (geometry[1][2] - geometry[0][2]) ** 2
|
||||
) ** 0.5
|
||||
|
||||
def create(self):
|
||||
# Read data from input file
|
||||
with open(self.dataFilename) as dataFile:
|
||||
data = json.load(dataFile)
|
||||
|
||||
elements = data['elements']
|
||||
connections = data['connections']
|
||||
elements = data["elements"]
|
||||
connections = data["connections"]
|
||||
# --> Delete this reference data and repopulate it with the objects
|
||||
# while going through elements
|
||||
for conn in connections:
|
||||
conn['relatedElements'] = []
|
||||
conn["relatedElements"] = []
|
||||
# End <--
|
||||
|
||||
meshSize = self.meshSize
|
||||
@@ -122,7 +123,7 @@ class MODEL:
|
||||
import math
|
||||
import SALOMEDS
|
||||
|
||||
gg = salome.ImportComponentGUI('GEOM')
|
||||
gg = salome.ImportComponentGUI("GEOM")
|
||||
if NEW_SALOME:
|
||||
geompy = geomBuilder.New()
|
||||
else:
|
||||
@@ -133,81 +134,91 @@ class MODEL:
|
||||
OX = geompy.MakeVectorDXDYDZ(1, 0, 0)
|
||||
OY = geompy.MakeVectorDXDYDZ(0, 1, 0)
|
||||
OZ = geompy.MakeVectorDXDYDZ(0, 0, 1)
|
||||
geompy.addToStudy( O, 'O' )
|
||||
geompy.addToStudy( OX, 'OX' )
|
||||
geompy.addToStudy( OY, 'OY' )
|
||||
geompy.addToStudy( OZ, 'OZ' )
|
||||
geompy.addToStudy(O, "O")
|
||||
geompy.addToStudy(OX, "OX")
|
||||
geompy.addToStudy(OY, "OY")
|
||||
geompy.addToStudy(OZ, "OZ")
|
||||
|
||||
if len([e for e in elements if e['geometryType'] == 'line']) > 0:
|
||||
buildingShapeType = 'EDGE'
|
||||
if len([e for e in elements if e['geometryType'] == 'surface']) > 0:
|
||||
buildingShapeType = 'FACE'
|
||||
if len([e for e in elements if e["geometryType"] == "line"]) > 0:
|
||||
buildingShapeType = "EDGE"
|
||||
if len([e for e in elements if e["geometryType"] == "surface"]) > 0:
|
||||
buildingShapeType = "FACE"
|
||||
|
||||
### Define entities ###
|
||||
start_time = time.time()
|
||||
print('Defining Object Geometry')
|
||||
print("Defining Object Geometry")
|
||||
init_time = start_time
|
||||
|
||||
# Loop 1
|
||||
for el in elements:
|
||||
el['elemObj'] = self.makeObject(el['geometry'], el['geometryType'])
|
||||
el["elemObj"] = self.makeObject(el["geometry"], el["geometryType"])
|
||||
|
||||
el['connObjs'] = [None for _ in el['connections']]
|
||||
el['linkObjs'] = [None for _ in el['connections']]
|
||||
el['linkPointObjs'] = [[None, None] for _ in el['connections']]
|
||||
for j,rel in enumerate(el['connections']):
|
||||
conn = [c for c in connections if c['ifcName'] == rel['relatedConnection']][0]
|
||||
if rel['eccentricity']:
|
||||
rel['index'] = len(conn['relatedElements']) + 1
|
||||
conn['relatedElements'].append(rel)
|
||||
el["connObjs"] = [None for _ in el["connections"]]
|
||||
el["linkObjs"] = [None for _ in el["connections"]]
|
||||
el["linkPointObjs"] = [[None, None] for _ in el["connections"]]
|
||||
for j, rel in enumerate(el["connections"]):
|
||||
conn = [c for c in connections if c["ifcName"] == rel["relatedConnection"]][0]
|
||||
if rel["eccentricity"]:
|
||||
rel["index"] = len(conn["relatedElements"]) + 1
|
||||
conn["relatedElements"].append(rel)
|
||||
|
||||
if not rel['eccentricity']:
|
||||
el['connObjs'][j] = self.makeObject(conn['geometry'], conn['geometryType'])
|
||||
if not rel["eccentricity"]:
|
||||
el["connObjs"][j] = self.makeObject(conn["geometry"], conn["geometryType"])
|
||||
else:
|
||||
if conn['geometryType'] == 'point':
|
||||
geometry = self.getLinkGeometry(rel['eccentricity'], el['orientation'], conn['geometry'])
|
||||
el['connObjs'][j] = self.makeObject(geometry[0], conn['geometryType'])
|
||||
if conn["geometryType"] == "point":
|
||||
geometry = self.getLinkGeometry(rel["eccentricity"], el["orientation"], conn["geometry"])
|
||||
el["connObjs"][j] = self.makeObject(geometry[0], conn["geometryType"])
|
||||
|
||||
el['linkPointObjs'][j][0] = self.geompy.MakeVertex(geometry[0][0], geometry[0][1], geometry[0][2])
|
||||
el['linkPointObjs'][j][1] = self.geompy.MakeVertex(geometry[1][0], geometry[1][1], geometry[1][2])
|
||||
el['linkObjs'][j] = self.geompy.MakeLineTwoPnt(el['linkPointObjs'][j][0], el['linkPointObjs'][j][1])
|
||||
el["linkPointObjs"][j][0] = self.geompy.MakeVertex(
|
||||
geometry[0][0], geometry[0][1], geometry[0][2]
|
||||
)
|
||||
el["linkPointObjs"][j][1] = self.geompy.MakeVertex(
|
||||
geometry[1][0], geometry[1][1], geometry[1][2]
|
||||
)
|
||||
el["linkObjs"][j] = self.geompy.MakeLineTwoPnt(
|
||||
el["linkPointObjs"][j][0], el["linkPointObjs"][j][1]
|
||||
)
|
||||
else:
|
||||
print('Eccentricity defined for a %s geometryType' %conn['geometryType'])
|
||||
print("Eccentricity defined for a %s geometryType" % conn["geometryType"])
|
||||
|
||||
el['partObj'] = self.makePartition([el['elemObj']] + el['connObjs'], el['geometryType'])
|
||||
el["partObj"] = self.makePartition([el["elemObj"]] + el["connObjs"], el["geometryType"])
|
||||
|
||||
el['elemObj'] = geompy.GetInPlace(el['partObj'], el['elemObj'])
|
||||
for j,rel in enumerate(el['connections']):
|
||||
el['connObjs'][j] = geompy.GetInPlace(el['partObj'], el['connObjs'][j])
|
||||
el["elemObj"] = geompy.GetInPlace(el["partObj"], el["elemObj"])
|
||||
for j, rel in enumerate(el["connections"]):
|
||||
el["connObjs"][j] = geompy.GetInPlace(el["partObj"], el["connObjs"][j])
|
||||
|
||||
for conn in connections:
|
||||
conn['connObj'] = self.makeObject(conn['geometry'], conn['geometryType'])
|
||||
conn["connObj"] = self.makeObject(conn["geometry"], conn["geometryType"])
|
||||
|
||||
# Make assemble of Building Object
|
||||
bldObjs = []
|
||||
bldObjs.extend([el['partObj'] for el in elements])
|
||||
bldObjs.extend(flatten([[link for link in el['linkObjs'] if link] for el in elements]))
|
||||
bldObjs.extend([conn['connObj'] for conn in connections])
|
||||
bldObjs.extend([el["partObj"] for el in elements])
|
||||
bldObjs.extend(flatten([[link for link in el["linkObjs"] if link] for el in elements]))
|
||||
bldObjs.extend([conn["connObj"] for conn in connections])
|
||||
|
||||
bldComp = geompy.MakeCompound(bldObjs)
|
||||
# bldComp = geompy.MakePartition(bldObjs, [], [], [], self.geompy.ShapeType[buildingShapeType], 0, [], 1)
|
||||
geompy.addToStudy(bldComp, 'bldComp')
|
||||
geompy.addToStudy(bldComp, "bldComp")
|
||||
|
||||
# Loop 2
|
||||
for el in elements:
|
||||
# geompy.addToStudy(el['partObj'], self.getGroupName(el['ifcName']))
|
||||
geompy.addToStudyInFather(el['partObj'], el['elemObj'], self.getGroupName(el['ifcName']))
|
||||
for j,rel in enumerate(el['connections']):
|
||||
conn = [c for c in connections if c['ifcName'] == rel['relatedConnection']][0]
|
||||
rel['conn_string'] = None
|
||||
if conn['geometryType'] == 'point':
|
||||
rel['conn_string'] = '_0DC_'
|
||||
if conn['geometryType'] == 'line':
|
||||
rel['conn_string'] = '_1DC_'
|
||||
if conn['geometryType'] == 'surface':
|
||||
rel['conn_string'] = '_2DC_'
|
||||
geompy.addToStudyInFather(el['partObj'], el['connObjs'][j], self.getGroupName(el['ifcName']) + rel['conn_string'] + self.getGroupName(rel['relatedConnection']))
|
||||
if rel['eccentricity']:
|
||||
geompy.addToStudyInFather(el["partObj"], el["elemObj"], self.getGroupName(el["ifcName"]))
|
||||
for j, rel in enumerate(el["connections"]):
|
||||
conn = [c for c in connections if c["ifcName"] == rel["relatedConnection"]][0]
|
||||
rel["conn_string"] = None
|
||||
if conn["geometryType"] == "point":
|
||||
rel["conn_string"] = "_0DC_"
|
||||
if conn["geometryType"] == "line":
|
||||
rel["conn_string"] = "_1DC_"
|
||||
if conn["geometryType"] == "surface":
|
||||
rel["conn_string"] = "_2DC_"
|
||||
geompy.addToStudyInFather(
|
||||
el["partObj"],
|
||||
el["connObjs"][j],
|
||||
self.getGroupName(el["ifcName"]) + rel["conn_string"] + self.getGroupName(rel["relatedConnection"]),
|
||||
)
|
||||
if rel["eccentricity"]:
|
||||
pass
|
||||
# geompy.addToStudy(el['linkObjs'][j], self.getGroupName(el['ifcName']) + '_1DR_' + self.getGroupName(rel['relatedConnection']))
|
||||
# geompy.addToStudyInFather(el['linkObjs'][j], el['linkPointObjs'][j][0], self.getGroupName(rel['relatedConnection']) + '_0DC_' + self.getGroupName(el['ifcName']))
|
||||
@@ -215,51 +226,67 @@ class MODEL:
|
||||
|
||||
for conn in connections:
|
||||
# geompy.addToStudy(conn['connObj'], self.getGroupName(conn['ifcName']))
|
||||
geompy.addToStudyInFather(conn['connObj'], conn['connObj'], self.getGroupName(conn['ifcName']))
|
||||
geompy.addToStudyInFather(conn["connObj"], conn["connObj"], self.getGroupName(conn["ifcName"]))
|
||||
|
||||
elapsed_time = time.time() - init_time
|
||||
init_time += elapsed_time
|
||||
print('Building Geometry Defined in %g sec' % (elapsed_time))
|
||||
print("Building Geometry Defined in %g sec" % (elapsed_time))
|
||||
|
||||
if len([e for e in elements if e['geometryType'] == 'line']) > 0:
|
||||
buildingShapeType = 'EDGE'
|
||||
if len([e for e in elements if e['geometryType'] == 'surface']) > 0:
|
||||
buildingShapeType = 'FACE'
|
||||
if len([e for e in elements if e["geometryType"] == "line"]) > 0:
|
||||
buildingShapeType = "EDGE"
|
||||
if len([e for e in elements if e["geometryType"] == "surface"]) > 0:
|
||||
buildingShapeType = "FACE"
|
||||
|
||||
# Define and add groups for all curve and surface members
|
||||
if len([e for e in elements if e['geometryType'] == 'line']) > 0:
|
||||
if len([e for e in elements if e["geometryType"] == "line"]) > 0:
|
||||
# Make compound of requested group
|
||||
compoundTemp = geompy.MakeCompound([e['elemObj'] for e in elements if e['geometryType'] == 'line'])
|
||||
compoundTemp = geompy.MakeCompound([e["elemObj"] for e in elements if e["geometryType"] == "line"])
|
||||
# Define group object and add to study
|
||||
curveCompound = geompy.GetInPlace(bldComp, compoundTemp)
|
||||
geompy.addToStudyInFather(bldComp, curveCompound, 'CurveMembers')
|
||||
geompy.addToStudyInFather(bldComp, curveCompound, "CurveMembers")
|
||||
|
||||
if len([e for e in elements if e['geometryType'] == 'surface']) > 0:
|
||||
if len([e for e in elements if e["geometryType"] == "surface"]) > 0:
|
||||
# Make compound of requested group
|
||||
compoundTemp = geompy.MakeCompound([e['elemObj'] for e in elements if e['geometryType'] == 'surface'])
|
||||
compoundTemp = geompy.MakeCompound([e["elemObj"] for e in elements if e["geometryType"] == "surface"])
|
||||
# Define group object and add to study
|
||||
surfaceCompound = geompy.GetInPlace(bldComp, compoundTemp)
|
||||
geompy.addToStudyInFather(bldComp, surfaceCompound, 'SurfaceMembers')
|
||||
geompy.addToStudyInFather(bldComp, surfaceCompound, "SurfaceMembers")
|
||||
|
||||
# Loop 3
|
||||
for el in elements:
|
||||
# el['partObj'] = geompy.RestoreGivenSubShapes(bldComp, [el['partObj']], GEOM.FSM_GetInPlace, False, False)[0]
|
||||
geompy.addToStudyInFather(bldComp, el['elemObj'], self.getGroupName(el['ifcName']))
|
||||
geompy.addToStudyInFather(bldComp, el["elemObj"], self.getGroupName(el["ifcName"]))
|
||||
|
||||
for j,rel in enumerate(el['connections']):
|
||||
geompy.addToStudyInFather(bldComp, el['connObjs'][j], self.getGroupName(el['ifcName']) + rel['conn_string'] + self.getGroupName(rel['relatedConnection']))
|
||||
if rel['eccentricity']: # point geometry
|
||||
geompy.addToStudyInFather(bldComp, el['linkObjs'][j], self.getGroupName(el['ifcName']) + '_1DR_' + self.getGroupName(rel['relatedConnection']))
|
||||
geompy.addToStudyInFather(bldComp, el['linkPointObjs'][j][0], self.getGroupName(rel['relatedConnection']) + '_0DC_' + self.getGroupName(el['ifcName']))
|
||||
geompy.addToStudyInFather(bldComp, el['linkPointObjs'][j][1], self.getGroupName(rel['relatedConnection']) + '_0DC_%g' % rel['index'])
|
||||
for j, rel in enumerate(el["connections"]):
|
||||
geompy.addToStudyInFather(
|
||||
bldComp,
|
||||
el["connObjs"][j],
|
||||
self.getGroupName(el["ifcName"]) + rel["conn_string"] + self.getGroupName(rel["relatedConnection"]),
|
||||
)
|
||||
if rel["eccentricity"]: # point geometry
|
||||
geompy.addToStudyInFather(
|
||||
bldComp,
|
||||
el["linkObjs"][j],
|
||||
self.getGroupName(el["ifcName"]) + "_1DR_" + self.getGroupName(rel["relatedConnection"]),
|
||||
)
|
||||
geompy.addToStudyInFather(
|
||||
bldComp,
|
||||
el["linkPointObjs"][j][0],
|
||||
self.getGroupName(rel["relatedConnection"]) + "_0DC_" + self.getGroupName(el["ifcName"]),
|
||||
)
|
||||
geompy.addToStudyInFather(
|
||||
bldComp,
|
||||
el["linkPointObjs"][j][1],
|
||||
self.getGroupName(rel["relatedConnection"]) + "_0DC_%g" % rel["index"],
|
||||
)
|
||||
|
||||
for conn in connections:
|
||||
# conn['connObj'] = geompy.RestoreGivenSubShapes(bldComp, [conn['connObj']], GEOM.FSM_GetInPlace, False, False)[0]
|
||||
geompy.addToStudyInFather(bldComp, conn['connObj'], self.getGroupName(conn['ifcName']))
|
||||
geompy.addToStudyInFather(bldComp, conn["connObj"], self.getGroupName(conn["ifcName"]))
|
||||
|
||||
elapsed_time = time.time() - init_time
|
||||
init_time += elapsed_time
|
||||
print('Building Geometry Groups Defined in %g sec' % (elapsed_time))
|
||||
print("Building Geometry Groups Defined in %g sec" % (elapsed_time))
|
||||
|
||||
###
|
||||
### SMESH component
|
||||
@@ -268,7 +295,7 @@ class MODEL:
|
||||
import SMESH
|
||||
from salome.smesh import smeshBuilder
|
||||
|
||||
print('Defining Mesh Components')
|
||||
print("Defining Mesh Components")
|
||||
|
||||
if NEW_SALOME:
|
||||
smesh = smeshBuilder.New()
|
||||
@@ -278,7 +305,7 @@ class MODEL:
|
||||
Regular_1D = bldMesh.Segment()
|
||||
Local_Length_1 = Regular_1D.LocalLength(meshSize, None, tolLoc)
|
||||
|
||||
if buildingShapeType == 'FACE':
|
||||
if buildingShapeType == "FACE":
|
||||
NETGEN2D_ONLY = bldMesh.Triangle(algo=smeshBuilder.NETGEN_2D)
|
||||
NETGEN2D_Pars = NETGEN2D_ONLY.Parameters()
|
||||
NETGEN2D_Pars.SetMaxSize(meshSize)
|
||||
@@ -293,102 +320,129 @@ class MODEL:
|
||||
isDone = bldMesh.Compute()
|
||||
|
||||
## Set names of Mesh objects
|
||||
smesh.SetName(Regular_1D.GetAlgorithm(), 'Regular_1D')
|
||||
smesh.SetName(Local_Length_1, 'Local_Length_1')
|
||||
smesh.SetName(Regular_1D.GetAlgorithm(), "Regular_1D")
|
||||
smesh.SetName(Local_Length_1, "Local_Length_1")
|
||||
|
||||
if buildingShapeType == 'FACE':
|
||||
smesh.SetName(NETGEN2D_ONLY.GetAlgorithm(), 'NETGEN2D_ONLY')
|
||||
smesh.SetName(NETGEN2D_Pars, 'NETGEN2D_Pars')
|
||||
if buildingShapeType == "FACE":
|
||||
smesh.SetName(NETGEN2D_ONLY.GetAlgorithm(), "NETGEN2D_ONLY")
|
||||
smesh.SetName(NETGEN2D_Pars, "NETGEN2D_Pars")
|
||||
|
||||
smesh.SetName(bldMesh.GetMesh(), 'bldMesh')
|
||||
smesh.SetName(bldMesh.GetMesh(), "bldMesh")
|
||||
|
||||
elapsed_time = time.time() - init_time
|
||||
init_time += elapsed_time
|
||||
print('Meshing Operations Completed in %g sec' % (elapsed_time))
|
||||
print("Meshing Operations Completed in %g sec" % (elapsed_time))
|
||||
|
||||
# Define and add groups for all curve and surface members
|
||||
if len([e for e in elements if e['geometryType'] == 'line']) > 0:
|
||||
tempgroup = bldMesh.GroupOnGeom(curveCompound, 'CurveMembers', SMESH.EDGE)
|
||||
smesh.SetName(tempgroup, 'CurveMembers')
|
||||
if len([e for e in elements if e["geometryType"] == "line"]) > 0:
|
||||
tempgroup = bldMesh.GroupOnGeom(curveCompound, "CurveMembers", SMESH.EDGE)
|
||||
smesh.SetName(tempgroup, "CurveMembers")
|
||||
|
||||
if len([e for e in elements if e['geometryType'] == 'surface']) > 0:
|
||||
tempgroup = bldMesh.GroupOnGeom(surfaceCompound, 'SurfaceMembers', SMESH.FACE)
|
||||
smesh.SetName(tempgroup, 'SurfaceMembers')
|
||||
if len([e for e in elements if e["geometryType"] == "surface"]) > 0:
|
||||
tempgroup = bldMesh.GroupOnGeom(surfaceCompound, "SurfaceMembers", SMESH.FACE)
|
||||
smesh.SetName(tempgroup, "SurfaceMembers")
|
||||
|
||||
# Define groups in Mesh
|
||||
for el in elements:
|
||||
if el['geometryType'] == 'line':
|
||||
if el["geometryType"] == "line":
|
||||
shapeType = SMESH.EDGE
|
||||
if el['geometryType'] == 'surface':
|
||||
if el["geometryType"] == "surface":
|
||||
shapeType = SMESH.FACE
|
||||
tempgroup = bldMesh.GroupOnGeom(el['elemObj'], self.getGroupName(el['ifcName']), shapeType)
|
||||
smesh.SetName(tempgroup, self.getGroupName(el['ifcName']))
|
||||
tempgroup = bldMesh.GroupOnGeom(el["elemObj"], self.getGroupName(el["ifcName"]), shapeType)
|
||||
smesh.SetName(tempgroup, self.getGroupName(el["ifcName"]))
|
||||
|
||||
for j,rel in enumerate(el['connections']):
|
||||
tempgroup = bldMesh.GroupOnGeom(el['connObjs'][j], self.getGroupName(el['ifcName']) + rel['conn_string'] + self.getGroupName(rel['relatedConnection']), SMESH.NODE)
|
||||
smesh.SetName(tempgroup, self.getGroupName(el['ifcName']) + rel['conn_string'] + self.getGroupName(rel['relatedConnection']))
|
||||
rel['node'] = (bldMesh.GetIDSource(tempgroup.GetNodeIDs(), SMESH.NODE)).GetIDs()[0]
|
||||
if rel['eccentricity']:
|
||||
tempgroup = bldMesh.GroupOnGeom(el['linkObjs'][j], self.getGroupName(el['ifcName']) + '_1DR_' + self.getGroupName(rel['relatedConnection']), SMESH.EDGE)
|
||||
smesh.SetName(tempgroup, self.getGroupName(el['ifcName']) + '_1DR_' + self.getGroupName(rel['relatedConnection']))
|
||||
for j, rel in enumerate(el["connections"]):
|
||||
tempgroup = bldMesh.GroupOnGeom(
|
||||
el["connObjs"][j],
|
||||
self.getGroupName(el["ifcName"]) + rel["conn_string"] + self.getGroupName(rel["relatedConnection"]),
|
||||
SMESH.NODE,
|
||||
)
|
||||
smesh.SetName(
|
||||
tempgroup,
|
||||
self.getGroupName(el["ifcName"]) + rel["conn_string"] + self.getGroupName(rel["relatedConnection"]),
|
||||
)
|
||||
rel["node"] = (bldMesh.GetIDSource(tempgroup.GetNodeIDs(), SMESH.NODE)).GetIDs()[0]
|
||||
if rel["eccentricity"]:
|
||||
tempgroup = bldMesh.GroupOnGeom(
|
||||
el["linkObjs"][j],
|
||||
self.getGroupName(el["ifcName"]) + "_1DR_" + self.getGroupName(rel["relatedConnection"]),
|
||||
SMESH.EDGE,
|
||||
)
|
||||
smesh.SetName(
|
||||
tempgroup,
|
||||
self.getGroupName(el["ifcName"]) + "_1DR_" + self.getGroupName(rel["relatedConnection"]),
|
||||
)
|
||||
|
||||
tempgroup = bldMesh.GroupOnGeom(el['linkPointObjs'][j][0], self.getGroupName(rel['relatedConnection']) + '_0DC_' + self.getGroupName(el['ifcName']), SMESH.NODE)
|
||||
smesh.SetName(tempgroup, self.getGroupName(rel['relatedConnection']) + '_0DC_' + self.getGroupName(el['ifcName']))
|
||||
rel['eccNode'] = (bldMesh.GetIDSource(tempgroup.GetNodeIDs(), SMESH.NODE)).GetIDs()[0]
|
||||
|
||||
tempgroup = bldMesh.GroupOnGeom(el['linkPointObjs'][j][1], self.getGroupName(rel['relatedConnection']) + '_0DC_' + self.getGroupName(rel['relatedConnection']), SMESH.NODE)
|
||||
smesh.SetName(tempgroup, self.getGroupName(rel['relatedConnection']) + '_0DC_%g' % rel['index'])
|
||||
tempgroup = bldMesh.GroupOnGeom(
|
||||
el["linkPointObjs"][j][0],
|
||||
self.getGroupName(rel["relatedConnection"]) + "_0DC_" + self.getGroupName(el["ifcName"]),
|
||||
SMESH.NODE,
|
||||
)
|
||||
smesh.SetName(
|
||||
tempgroup,
|
||||
self.getGroupName(rel["relatedConnection"]) + "_0DC_" + self.getGroupName(el["ifcName"]),
|
||||
)
|
||||
rel["eccNode"] = (bldMesh.GetIDSource(tempgroup.GetNodeIDs(), SMESH.NODE)).GetIDs()[0]
|
||||
|
||||
tempgroup = bldMesh.GroupOnGeom(
|
||||
el["linkPointObjs"][j][1],
|
||||
self.getGroupName(rel["relatedConnection"])
|
||||
+ "_0DC_"
|
||||
+ self.getGroupName(rel["relatedConnection"]),
|
||||
SMESH.NODE,
|
||||
)
|
||||
smesh.SetName(tempgroup, self.getGroupName(rel["relatedConnection"]) + "_0DC_%g" % rel["index"])
|
||||
|
||||
for conn in connections:
|
||||
tempgroup = bldMesh.GroupOnGeom(conn['connObj'], self.getGroupName(conn['ifcName']), SMESH.NODE)
|
||||
smesh.SetName(tempgroup, self.getGroupName(conn['ifcName']))
|
||||
tempgroup = bldMesh.GroupOnGeom(conn["connObj"], self.getGroupName(conn["ifcName"]), SMESH.NODE)
|
||||
smesh.SetName(tempgroup, self.getGroupName(conn["ifcName"]))
|
||||
nodesId = bldMesh.GetIDSource(tempgroup.GetNodeIDs(), SMESH.NODE)
|
||||
tempgroup = bldMesh.Add0DElementsToAllNodes(nodesId, self.getGroupName(conn['ifcName']))
|
||||
smesh.SetName(tempgroup, self.getGroupName(conn['ifcName'] + '_0D'))
|
||||
if conn['geometryType'] == 'point':
|
||||
conn['node'] = nodesId.GetIDs()[0]
|
||||
if conn['geometryType'] == 'line':
|
||||
tempgroup = bldMesh.GroupOnGeom(conn['connObj'], self.getGroupName(conn['ifcName']), SMESH.EDGE)
|
||||
smesh.SetName(tempgroup, self.getGroupName(conn['ifcName']))
|
||||
if conn['geometryType'] == 'surface':
|
||||
tempgroup = bldMesh.GroupOnGeom(conn['connObj'], self.getGroupName(conn['ifcName']), SMESH.FACE)
|
||||
smesh.SetName(tempgroup, self.getGroupName(conn['ifcName']))
|
||||
tempgroup = bldMesh.Add0DElementsToAllNodes(nodesId, self.getGroupName(conn["ifcName"]))
|
||||
smesh.SetName(tempgroup, self.getGroupName(conn["ifcName"] + "_0D"))
|
||||
if conn["geometryType"] == "point":
|
||||
conn["node"] = nodesId.GetIDs()[0]
|
||||
if conn["geometryType"] == "line":
|
||||
tempgroup = bldMesh.GroupOnGeom(conn["connObj"], self.getGroupName(conn["ifcName"]), SMESH.EDGE)
|
||||
smesh.SetName(tempgroup, self.getGroupName(conn["ifcName"]))
|
||||
if conn["geometryType"] == "surface":
|
||||
tempgroup = bldMesh.GroupOnGeom(conn["connObj"], self.getGroupName(conn["ifcName"]), SMESH.FACE)
|
||||
smesh.SetName(tempgroup, self.getGroupName(conn["ifcName"]))
|
||||
|
||||
# create 1D SEG2 spring elements
|
||||
for el in elements:
|
||||
for j,rel in enumerate(el['connections']):
|
||||
conn = [c for c in connections if c['ifcName'] == rel['relatedConnection']][0]
|
||||
if conn['geometryType'] == 'point':
|
||||
grpName = bldMesh.CreateEmptyGroup(SMESH.EDGE, self.getGroupName(el['ifcName']) + '_1DS_' + self.getGroupName(rel['relatedConnection']))
|
||||
smesh.SetName(grpName, self.getGroupName(el['ifcName']) + '_1DS_' + self.getGroupName(rel['relatedConnection']))
|
||||
if not rel['eccentricity']:
|
||||
conn = [conn for conn in connections if conn['ifcName'] == rel['relatedConnection']][0]
|
||||
grpName.Add([bldMesh.AddEdge([conn['node'], rel['node']])])
|
||||
for j, rel in enumerate(el["connections"]):
|
||||
conn = [c for c in connections if c["ifcName"] == rel["relatedConnection"]][0]
|
||||
if conn["geometryType"] == "point":
|
||||
grpName = bldMesh.CreateEmptyGroup(
|
||||
SMESH.EDGE,
|
||||
self.getGroupName(el["ifcName"]) + "_1DS_" + self.getGroupName(rel["relatedConnection"]),
|
||||
)
|
||||
smesh.SetName(
|
||||
grpName,
|
||||
self.getGroupName(el["ifcName"]) + "_1DS_" + self.getGroupName(rel["relatedConnection"]),
|
||||
)
|
||||
if not rel["eccentricity"]:
|
||||
conn = [conn for conn in connections if conn["ifcName"] == rel["relatedConnection"]][0]
|
||||
grpName.Add([bldMesh.AddEdge([conn["node"], rel["node"]])])
|
||||
else:
|
||||
grpName.Add([bldMesh.AddEdge([rel['eccNode'], rel['node']])])
|
||||
grpName.Add([bldMesh.AddEdge([rel["eccNode"], rel["node"]])])
|
||||
|
||||
self.mesh = bldMesh
|
||||
self.meshNodes = bldMesh.GetNodesId()
|
||||
|
||||
elapsed_time = time.time() - init_time
|
||||
init_time += elapsed_time
|
||||
print('Mesh Groups Defined in %g sec' % (elapsed_time))
|
||||
print("Mesh Groups Defined in %g sec" % (elapsed_time))
|
||||
|
||||
try:
|
||||
if NEW_SALOME:
|
||||
bldMesh.ExportMED(
|
||||
self.medFilename,
|
||||
auto_groups = 0,
|
||||
minor = 40,
|
||||
overwrite = 1,
|
||||
meshPart = None,
|
||||
autoDimension = 0
|
||||
self.medFilename, auto_groups=0, minor=40, overwrite=1, meshPart=None, autoDimension=0
|
||||
)
|
||||
else:
|
||||
bldMesh.ExportMED(self.medFilename, 0, SMESH.MED_V2_2, 1, None, 0)
|
||||
except:
|
||||
print('ExportMED() failed. Invalid file name?')
|
||||
print("ExportMED() failed. Invalid file name?")
|
||||
|
||||
if salome.sg.hasDesktop():
|
||||
if NEW_SALOME:
|
||||
@@ -397,16 +451,17 @@ class MODEL:
|
||||
salome.sg.updateObjBrowser(1)
|
||||
|
||||
elapsed_time = init_time - start_time
|
||||
print('ALL Operations Completed in %g sec' % (elapsed_time))
|
||||
print("ALL Operations Completed in %g sec" % (elapsed_time))
|
||||
|
||||
if __name__ == '__main__':
|
||||
fileNames = ['structure_01']
|
||||
|
||||
if __name__ == "__main__":
|
||||
fileNames = ["structure_01"]
|
||||
files = fileNames
|
||||
|
||||
meshSize = 0.1
|
||||
|
||||
for fileName in files:
|
||||
BASE_PATH = '/home/jesusbill/Dev-Projects/github.com/IfcOpenShell/analysis-models/models/'
|
||||
DATAFILENAME = BASE_PATH + fileName + '/' + fileName + '.json'
|
||||
MEDFILENAME = BASE_PATH + fileName + '/' + fileName + '.med'
|
||||
BASE_PATH = "/home/jesusbill/Dev-Projects/github.com/IfcOpenShell/analysis-models/models/"
|
||||
DATAFILENAME = BASE_PATH + fileName + "/" + fileName + ".json"
|
||||
MEDFILENAME = BASE_PATH + fileName + "/" + fileName + ".med"
|
||||
model = MODEL(DATAFILENAME, MEDFILENAME, meshSize)
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
# Dependency and build folders created by the build scripts
|
||||
/build/
|
||||
/dist/
|
||||
@@ -0,0 +1,122 @@
|
||||
# BIMTester
|
||||
|
||||
BIMTester lets you specify a set of exchange requirements, and automatically check whether or not BIM data, typically an
|
||||
IFC file, complies with the requirements. You can run it automatically from a server, integrate it to your own
|
||||
application, or use a GUI. It can generate reports in various formats including:
|
||||
|
||||
* HTML
|
||||
* JSON
|
||||
* XUnit
|
||||
* BCF
|
||||
* Zoom Smart View
|
||||
|
||||
Your exchange requirements are written in plain language which you can use to communicate to project teams and include
|
||||
in contracts. Multiple languages are supported. A series of exchange requirement templates are provided to get started,
|
||||
but you can, and are encouraged to, write your own tailored to your project. An example of a requirement looks like
|
||||
this:
|
||||
|
||||
```
|
||||
Feature: Project setup
|
||||
|
||||
In order to view the BIM data
|
||||
As any interested stakeholder
|
||||
We need an IFC file
|
||||
|
||||
Scenario: Receiving a file
|
||||
* IFC data must use the IFC4 schema
|
||||
```
|
||||
|
||||
Languages supported include (in alphabetical order):
|
||||
|
||||
* Dutch
|
||||
* English
|
||||
* French
|
||||
* German
|
||||
* Italian
|
||||
|
||||
If you are not a developer, we highly recommend simply installing an integrated version of BIMTester.
|
||||
|
||||
* If you use Blender, install the [BlenderBIM Add-on](https://blenderbim.org)
|
||||
* If you use FreeCAD, install the [FreeCAD BIMTester Workbench](https://github.com/bimtester/bimtesterfc)
|
||||
|
||||
If you are a developer, read on!
|
||||
|
||||
## Installation
|
||||
|
||||
The following packages are required for BIMTester to function:
|
||||
|
||||
* behave
|
||||
* pystache
|
||||
* ifcopenshell
|
||||
* PySide2 (optional: needed for GUI)
|
||||
|
||||
The repository does not contain translation files. You can generate them as shown.
|
||||
|
||||
```
|
||||
cd IfcOpenShell/src/ifcbimtester/bimtester/locale
|
||||
pybabel compile -d .
|
||||
```
|
||||
|
||||
## CLI Usage
|
||||
|
||||
BIMTester has a command line application. Check it out:
|
||||
|
||||
```
|
||||
$ python cli.py -h
|
||||
usage: cli.py [-h] [-a ACTION] [--advanced-arguments ADVANCED_ARGUMENTS] [-c]
|
||||
-f FEATURE -i IFC [-p PATH] [-r REPORT] [--lang LANG]
|
||||
|
||||
Runs unit tests for BIM data
|
||||
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
-a ACTION, --action ACTION
|
||||
Action to perform, from run/purge
|
||||
--advanced-arguments ADVANCED_ARGUMENTS
|
||||
Specify arguments to Behave
|
||||
-c, --console Show results in the console
|
||||
-f FEATURE, --feature FEATURE
|
||||
Specify a feature file to test
|
||||
-i IFC, --ifc IFC Specify a ifc file
|
||||
-p PATH, --path PATH Define a path for use in tests
|
||||
-r REPORT, --report REPORT
|
||||
Specify an output file for a HTML report
|
||||
--lang LANG Specify a language
|
||||
```
|
||||
|
||||
You can turn it into a regular command by symlinking it to your bin folder.
|
||||
|
||||
```
|
||||
$ ln -s /path/to/IfcOpenShell/src/ifcbimtester/cli.py /usr/local/bin/bimtester
|
||||
$ bimtester -h
|
||||
```
|
||||
|
||||
To run a test, we need an IFC to check and a feature file filled with requirements. The feature file is plaintext. Feel
|
||||
free to copy the minimal example above.
|
||||
|
||||
```
|
||||
# To see output in the console
|
||||
$ bimtester -i test.ifc -f test.feature -c
|
||||
# Or, if you want to generate a HTML report
|
||||
$ bimtester -i test.ifc -f test.feature -r report.html
|
||||
```
|
||||
|
||||
```
|
||||
python ./gui.py
|
||||
```
|
||||
|
||||
## Create a binary out of the Python package
|
||||
|
||||
TODO: Check if this works
|
||||
|
||||
Unix:
|
||||
|
||||
```
|
||||
$ 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
|
||||
```
|
||||
@@ -1,217 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# Unix:
|
||||
# $ 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`
|
||||
|
||||
from behave.__main__ import main as behave_main
|
||||
import behave.formatter.pretty # Needed for pyinstaller to package it
|
||||
import ifcopenshell
|
||||
import pystache
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import argparse
|
||||
import csv
|
||||
import re
|
||||
import shutil
|
||||
import webbrowser
|
||||
import datetime
|
||||
from pathlib import Path
|
||||
|
||||
try:
|
||||
# PyInstaller creates a temp folder and stores path in _MEIPASS
|
||||
base_path = sys._MEIPASS
|
||||
except Exception:
|
||||
base_path = os.path.dirname(os.path.realpath(__file__))
|
||||
|
||||
|
||||
def get_resource_path(relative_path):
|
||||
return os.path.join(base_path, relative_path)
|
||||
|
||||
|
||||
def run_tests(args):
|
||||
if not get_features(args):
|
||||
print('No features could be found to check.')
|
||||
return False
|
||||
behave_args = [get_resource_path('features')]
|
||||
if args['advanced_arguments']:
|
||||
behave_args.extend(args['advanced_arguments'].split())
|
||||
elif not args['console']:
|
||||
behave_args.extend(['--format', 'json.pretty', '--outfile', 'report/report.json'])
|
||||
behave_main(behave_args)
|
||||
print('# All tests are finished.')
|
||||
return True
|
||||
|
||||
|
||||
def get_features(args):
|
||||
current_path = os.path.abspath(".")
|
||||
features_dir = get_resource_path('features')
|
||||
for f in os.listdir(features_dir):
|
||||
if f.endswith('.feature'):
|
||||
os.remove(os.path.join(features_dir, f))
|
||||
if args['feature']:
|
||||
shutil.copyfile(args['feature'], os.path.join(
|
||||
get_resource_path('features'),
|
||||
os.path.basename(args['feature'])))
|
||||
return True
|
||||
if os.path.exists('features'):
|
||||
shutil.copytree('features', get_resource_path('features'))
|
||||
return True
|
||||
has_features = False
|
||||
for f in os.listdir('.'):
|
||||
if not f.endswith('.feature'):
|
||||
continue
|
||||
if args['feature'] and args['feature'] != f:
|
||||
continue
|
||||
has_features = True
|
||||
shutil.copyfile(f, os.path.join(
|
||||
get_resource_path('features'),
|
||||
os.path.basename(f)))
|
||||
return has_features
|
||||
|
||||
|
||||
def generate_report():
|
||||
print('# Generating HTML reports now.')
|
||||
if not os.path.exists('report'):
|
||||
os.mkdir('report')
|
||||
report_path = 'report/report.json'
|
||||
if not os.path.exists(report_path):
|
||||
return print('No report data was found.')
|
||||
report = json.loads(open(report_path).read())
|
||||
for feature in report:
|
||||
file_name = os.path.basename(feature['location']).split(':')[0]
|
||||
data = {
|
||||
'file_name': file_name,
|
||||
'time': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
|
||||
'name': feature['name'],
|
||||
'description': feature['description'],
|
||||
'is_success': feature['status'] == 'passed',
|
||||
'scenarios': []
|
||||
}
|
||||
for scenario in feature['elements']:
|
||||
steps = []
|
||||
total_duration = 0
|
||||
for step in scenario['steps']:
|
||||
if 'result' in step:
|
||||
total_duration += step['result']['duration']
|
||||
name = step['name']
|
||||
if 'match' in step and 'arguments' in step['match']:
|
||||
for a in step['match']['arguments']:
|
||||
name = name.replace(a['value'], '<b>' + a['value'] + '</b>')
|
||||
if 'result' not in step or step['result']['status'] == 'undefined':
|
||||
step['result'] = {}
|
||||
step['result']['status'] = 'undefined'
|
||||
step['result']['duration'] = 0
|
||||
step['result']['error_message'] = 'This requirement has not yet been specified.'
|
||||
steps.append({
|
||||
'name': name,
|
||||
'time': round(step['result']['duration'], 2),
|
||||
'is_success': step['result']['status'] == 'passed',
|
||||
'is_unspecified': 'result' not in step or step['result']['status'] == 'undefined',
|
||||
'error_message': None if step['result']['status'] == 'passed' else step['result']['error_message']
|
||||
})
|
||||
total_passes = len([s for s in steps if s['is_success'] == True])
|
||||
total_steps = len(steps)
|
||||
pass_rate = round((total_passes / total_steps) * 100)
|
||||
data['scenarios'].append({
|
||||
'name': scenario['name'],
|
||||
'is_success': scenario['status'] == 'passed',
|
||||
'time': round(total_duration, 2),
|
||||
'steps': steps,
|
||||
'total_passes': total_passes,
|
||||
'total_steps': total_steps,
|
||||
'pass_rate': pass_rate
|
||||
})
|
||||
data['total_passes'] = sum([s['total_passes'] for s in data['scenarios']])
|
||||
data['total_steps'] = sum([s['total_steps'] for s in data['scenarios']])
|
||||
data['pass_rate'] = round((data['total_passes'] / data['total_steps']) * 100)
|
||||
|
||||
with open('report/{}.html'.format(file_name), 'w') as out:
|
||||
with open(get_resource_path('features/template.html')) as template:
|
||||
out.write(pystache.render(template.read(), data))
|
||||
|
||||
|
||||
class TestPurger:
|
||||
def __init__(self):
|
||||
self.file = None
|
||||
|
||||
def purge(self):
|
||||
filenames = []
|
||||
if os.path.exists('features'):
|
||||
for filename in Path('features/').glob('*.feature'):
|
||||
filenames.append(filename)
|
||||
for f in os.listdir('.'):
|
||||
if f.endswith('.feature'):
|
||||
filenames.append(f)
|
||||
|
||||
for filename in filenames:
|
||||
with open(filename, 'r') as feature_file:
|
||||
old_file = feature_file.readlines()
|
||||
with open(filename, 'w') as new_file:
|
||||
for line in old_file:
|
||||
is_purged = False
|
||||
if 'The IFC file "' in line and '" must be provided' in line:
|
||||
filename = line.split('"')[1]
|
||||
print('Loading file {} ...'.format(filename))
|
||||
self.file = ifcopenshell.open(filename)
|
||||
if line.strip()[0:2] == '* ':
|
||||
words = line.strip().split()
|
||||
for word in words:
|
||||
if self.is_a_global_id(word):
|
||||
if not self.does_global_id_exist(word):
|
||||
print('Test for {} purged ...'.format(word))
|
||||
is_purged = True
|
||||
if not is_purged:
|
||||
new_file.write(line)
|
||||
|
||||
def is_a_global_id(self, word):
|
||||
return word[0] in ['0', '1', '2', '3'] and len(word) == 22
|
||||
|
||||
def does_global_id_exist(self, global_id):
|
||||
try:
|
||||
self.file.by_guid(global_id)
|
||||
return True
|
||||
except:
|
||||
return False
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Runs unit tests for BIM data')
|
||||
parser.add_argument(
|
||||
'-p',
|
||||
'--purge',
|
||||
action='store_true',
|
||||
help='Purge tests of deleted elements')
|
||||
parser.add_argument(
|
||||
'-r',
|
||||
'--report',
|
||||
action='store_true',
|
||||
help='Generate a HTML report')
|
||||
parser.add_argument(
|
||||
'-c',
|
||||
'--console',
|
||||
action='store_true',
|
||||
help='Show results in the console')
|
||||
parser.add_argument(
|
||||
'-f',
|
||||
'--feature',
|
||||
type=str,
|
||||
help='Specify a feature file to test',
|
||||
default='')
|
||||
parser.add_argument(
|
||||
'-a',
|
||||
'--advanced-arguments',
|
||||
type=str,
|
||||
help='Specify your own arguments to Python\'s Behave',
|
||||
default='')
|
||||
args = vars(parser.parse_args())
|
||||
|
||||
if args['purge']:
|
||||
TestPurger().purge()
|
||||
elif args['report']:
|
||||
generate_report()
|
||||
else:
|
||||
run_tests(args)
|
||||
print('# All tasks are complete :-)')
|
||||
@@ -0,0 +1,5 @@
|
||||
from os.path import dirname
|
||||
from os.path import realpath
|
||||
|
||||
|
||||
package_path = dirname(realpath(__file__))
|
||||
@@ -0,0 +1,47 @@
|
||||
import ifcopenshell
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
class TestPurger:
|
||||
def __init__(self):
|
||||
self.file = None
|
||||
|
||||
def purge(self):
|
||||
filenames = []
|
||||
if os.path.exists("features"):
|
||||
for filename in Path("features/").glob("*.feature"):
|
||||
filenames.append(filename)
|
||||
for f in os.listdir("."):
|
||||
if f.endswith(".feature"):
|
||||
filenames.append(f)
|
||||
|
||||
for filename in filenames:
|
||||
with open(filename, "r") as feature_file:
|
||||
old_file = feature_file.readlines()
|
||||
with open(filename, "w") as new_file:
|
||||
for line in old_file:
|
||||
is_purged = False
|
||||
if 'The IFC file "' in line and '" must be provided' in line:
|
||||
filename = line.split('"')[1]
|
||||
print("Loading file {} ...".format(filename))
|
||||
self.file = ifcopenshell.open(filename)
|
||||
if line.strip()[0:2] == "* ":
|
||||
words = line.strip().split()
|
||||
for word in words:
|
||||
if self.is_a_global_id(word):
|
||||
if not self.does_global_id_exist(word):
|
||||
print("Test for {} purged ...".format(word))
|
||||
is_purged = True
|
||||
if not is_purged:
|
||||
new_file.write(line)
|
||||
|
||||
def is_a_global_id(self, word):
|
||||
return word[0] in ["0", "1", "2", "3"] and len(word) == 22
|
||||
|
||||
def does_global_id_exist(self, global_id):
|
||||
try:
|
||||
self.file.by_guid(global_id)
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
@@ -0,0 +1,54 @@
|
||||
import os
|
||||
|
||||
from behave.model import Scenario
|
||||
from logfile import create_logfile
|
||||
from logfile import append_logfile
|
||||
from zoom_smart_view import append_zoom_smartview
|
||||
from zoom_smart_view import create_zoom_smartview
|
||||
from bimtester.ifc import IfcStore
|
||||
from bimtester.lang import switch_locale
|
||||
|
||||
|
||||
this_path = os.path.dirname(os.path.realpath(__file__))
|
||||
|
||||
|
||||
def before_all(context):
|
||||
userdata = context.config.userdata
|
||||
|
||||
if context.config.lang:
|
||||
switch_locale(userdata.get("localedir"), context.config.lang)
|
||||
|
||||
continue_after_failed = userdata.getbool("runner.continue_after_failed_step", True)
|
||||
Scenario.continue_after_failed_step = continue_after_failed
|
||||
|
||||
# TODO: refactor smart view support into a decoupled module
|
||||
# context.ifc_path = userdata.get("ifc", "")
|
||||
# context.ifc_basename = os.path.basename(
|
||||
# os.path.splitext(context.ifc_path)[0]
|
||||
# )
|
||||
|
||||
# context.outpath = os.path.join(this_path, "..")
|
||||
|
||||
# context.thelogfile = os.path.join(context.outpath, context.ifc_basename + ".log")
|
||||
# create_logfile(
|
||||
# context.thelogfile,
|
||||
# context.ifc_basename,
|
||||
# )
|
||||
|
||||
# # set up smart view file
|
||||
# context.smview_file = os.path.join(context.outpath, context.ifc_basename + ".bcsv")
|
||||
# create_zoom_smartview(
|
||||
# context.smview_file,
|
||||
# context.ifc_basename,
|
||||
# )
|
||||
|
||||
|
||||
def after_step(context, step):
|
||||
pass
|
||||
# TODO: refactor smart view support into a decoupled module
|
||||
#if step.status == "failed":
|
||||
# # append log file
|
||||
# append_logfile(context, step)
|
||||
# # extend smart view
|
||||
# if hasattr(context, "falseguids"):
|
||||
# append_zoom_smartview(context.smview_file, step.name, context.falseguids)
|
||||
@@ -0,0 +1,30 @@
|
||||
import json
|
||||
|
||||
|
||||
def create_logfile(thelogfile, ifcbasename):
|
||||
|
||||
logfile = open(thelogfile, "w")
|
||||
logfile.write("BIMTester log file\n")
|
||||
logfile.write("------------------\n\n")
|
||||
logfile.write("ifc base file name: {}\n".format(ifcbasename))
|
||||
logfile.close()
|
||||
|
||||
|
||||
def append_logfile(thecontext, step):
|
||||
|
||||
# step attributes (also these set by user) scope is the scenario
|
||||
# https://behave.readthedocs.io/en/latest/thecontext_attributes.html
|
||||
print("Step '{}' failed".format(step.name))
|
||||
|
||||
# log file
|
||||
logfile = open(thecontext.thelogfile, "a")
|
||||
logfile.write("\n\nStep '{}' failed\n".format(step.name))
|
||||
if hasattr(thecontext, "falseelems"):
|
||||
logfile.write("{}\n".format(
|
||||
json.dumps(thecontext.falseelems, indent=4)
|
||||
))
|
||||
if hasattr(thecontext, "falseprops"):
|
||||
logfile.write("{}\n".format(
|
||||
json.dumps(thecontext.falseprops, indent=4)
|
||||
))
|
||||
logfile.close()
|
||||
@@ -0,0 +1,15 @@
|
||||
use_step_matcher("parse")
|
||||
from bimtester.features.steps.classification import en
|
||||
use_step_matcher("parse")
|
||||
from bimtester.features.steps.element_classes import en
|
||||
use_step_matcher("parse")
|
||||
from bimtester.features.steps.geocoding import en
|
||||
use_step_matcher("parse")
|
||||
from bimtester.features.steps.geolocation import en
|
||||
use_step_matcher("parse")
|
||||
from bimtester.features.steps.geometric_detail import en
|
||||
use_step_matcher("parse")
|
||||
from bimtester.features.steps.model_federation import en
|
||||
use_step_matcher("parse")
|
||||
from bimtester.features.steps.project_setup import de, en, fr, it, nl
|
||||
use_step_matcher("parse")
|
||||
@@ -0,0 +1,70 @@
|
||||
import json
|
||||
from behave import step
|
||||
from bimtester import util
|
||||
from bimtester.ifc import IfcStore
|
||||
from bimtester.lang import _
|
||||
|
||||
|
||||
def get_classification(name):
|
||||
classifications = [c for c in IfcStore.file.by_type("IfcClassification") if c.Name == name]
|
||||
if len(classifications) != 1:
|
||||
assert False, f'The classification "{name}" was not found'
|
||||
return classifications[0]
|
||||
|
||||
|
||||
@step('The classification "{name}" must be used')
|
||||
def step_impl(context, name):
|
||||
get_classification(name)
|
||||
|
||||
|
||||
@step('The classification "{name}" is published by "{source}"')
|
||||
def step_impl(context, name, source):
|
||||
util.assert_attribute(get_classification(name), "Source", source)
|
||||
|
||||
|
||||
@step('The classification "{name}" is the edition "{edition}" on "{edition_date}"')
|
||||
def step_impl(context, name, edition, edition_date):
|
||||
element = get_classification(name)
|
||||
util.assert_attribute(element, "Edition", edition)
|
||||
util.assert_attribute(element, "EditionDate", edition_date)
|
||||
|
||||
|
||||
@step('The classification "{name}" has the description "{description}"')
|
||||
def step_impl(context, name, description):
|
||||
util.assert_attribute(get_classification(name), "Description", description)
|
||||
|
||||
|
||||
@step('The classification "{name}" is referenced by the website "{location}"')
|
||||
def step_impl(context, name, location):
|
||||
util.assert_attribute(get_classification(name), "Location", location)
|
||||
|
||||
|
||||
@step('The classification "{name}" has a hierarchy denoted by the tokens "{tokens}"')
|
||||
def step_impl(context, name, tokens):
|
||||
try:
|
||||
tokens = json.loads(tokens)
|
||||
except:
|
||||
assert False, _("Tokens {} are not specified as a JSON list").format(tokens)
|
||||
util.assert_attribute(get_classification(name), "ReferenceTokens", tokens)
|
||||
|
||||
|
||||
@step('The element "{guid}" is classified as a "{identification}" with name "{reference_name}"')
|
||||
def step_impl(context, guid, identification, reference_name):
|
||||
element = util.assert_guid(IfcStore.file, guid)
|
||||
if not hasattr(element, "HasAssociations") or not element.HasAssociations:
|
||||
assert False, _("The element {} has no associations.").format(element)
|
||||
references = [a.RelatingClassification for a in element.HasAssociations if a.is_a("IfcRelAssociatesClassification")]
|
||||
if not references:
|
||||
assert False, _("The element {element} has no associated classification references.").format(element)
|
||||
is_success = False
|
||||
for reference in references:
|
||||
try:
|
||||
util.assert_attribute(reference, "Identification", identification)
|
||||
util.assert_attribute(reference, "Name", reference_name)
|
||||
is_success = True
|
||||
except:
|
||||
pass
|
||||
if not is_success:
|
||||
assert False, _(
|
||||
"No classification references met the requirement for an identification {} and name {} for the element {}. The references we found were: {}"
|
||||
).format(identification, reference_name, element, references)
|
||||
@@ -0,0 +1,40 @@
|
||||
from behave import step
|
||||
from bimtester import util
|
||||
from bimtester.ifc import IfcStore
|
||||
from bimtester.lang import _
|
||||
|
||||
|
||||
@step('The element "{guid}" is an "{ifc_class}" only')
|
||||
def step_impl(context, guid, ifc_class):
|
||||
element = util.assert_guid(IfcStore.file, guid)
|
||||
util.assert_type(element, ifc_class, is_exact=True)
|
||||
|
||||
|
||||
@step('The element "{guid}" is an "{ifc_class}"')
|
||||
def step_impl(context, guid, ifc_class):
|
||||
element = util.assert_guid(IfcStore.file, guid)
|
||||
util.assert_type(element, ifc_class)
|
||||
|
||||
|
||||
@step('The element "{guid}" is further defined as a "{predefined_type}"')
|
||||
def step_impl(context, guid, predefined_type):
|
||||
element = util.assert_guid(IfcStore.file, guid)
|
||||
if (
|
||||
hasattr(element, "PredefinedType")
|
||||
and element.PredefinedType == "USERDEFINED"
|
||||
and hasattr(element, "ObjectType")
|
||||
):
|
||||
util.assert_attribute(element, "ObjectType", predefined_type)
|
||||
elif hasattr(element, "PredefinedType"):
|
||||
util.assert_attribute(element, "PredefinedType", predefined_type)
|
||||
else:
|
||||
assert False, _("The element {} does not have a PredefinedType or ObjectType attribute").format(element)
|
||||
|
||||
|
||||
@step('The element "{guid}" should not exist because "{reason}"')
|
||||
def step_impl(context, guid, reason):
|
||||
try:
|
||||
element = IfcStore.file.by_id(guid)
|
||||
except:
|
||||
return
|
||||
assert False, _("This element {} should be reevaluated.").format(element)
|
||||
@@ -0,0 +1,83 @@
|
||||
from behave import step, use_step_matcher
|
||||
from bimtester import util
|
||||
from bimtester.ifc import IfcStore
|
||||
from bimtester.lang import _
|
||||
|
||||
|
||||
def get_ifc_class_from_spatial_type(spatial_type):
|
||||
if spatial_type == "site":
|
||||
return "IfcSite"
|
||||
elif spatial_type == "building":
|
||||
return "IfcBuilding"
|
||||
return "IfcFacility"
|
||||
|
||||
|
||||
def check_geocode_attribute(guid, spatial_type, name, value):
|
||||
element = util.assert_guid(IfcStore.file, guid)
|
||||
util.assert_type(element, get_ifc_class_from_spatial_type(spatial_type))
|
||||
util.assert_attribute(element, name, value)
|
||||
|
||||
|
||||
def check_geocode_address(guid, spatial_type, name, value):
|
||||
element = util.assert_guid(IfcStore.file, guid)
|
||||
ifc_class = get_ifc_class_from_spatial_type(spatial_type)
|
||||
util.assert_type(element, ifc_class)
|
||||
if ifc_class == "IfcSite":
|
||||
address_name = "SiteAddress"
|
||||
elif ifc_class == "IfcBuilding":
|
||||
address_name = "BuildingAddress"
|
||||
util.assert_attribute(element, address_name)
|
||||
util.assert_attribute(getattr(element, address_name), name, value)
|
||||
|
||||
|
||||
use_step_matcher("re")
|
||||
|
||||
|
||||
@step('The (site|building|facility) "(?P<guid>.*)" has a name of "(?P<name>.*)"')
|
||||
def step_impl(context, spatial_type, guid, name):
|
||||
check_geocode_attribute(guid, spatial_type, "Name", name)
|
||||
|
||||
|
||||
@step('The (site|building|facility) "(?P<guid>.*)" has a description of "(?P<description>.*)"')
|
||||
def step_impl(context, spatial_type, guid, description):
|
||||
check_geocode_attribute(guid, spatial_type, "Description", description)
|
||||
|
||||
|
||||
@step('The site "(?P<guid>.*)" has a land title number of "(?P<land_title_number>.*)"')
|
||||
def step_impl(context, guid, land_title_number):
|
||||
check_geocode_attribute(guid, "site", "LandTitleNumber", land_title_number)
|
||||
|
||||
|
||||
@step('The (site|building) "(?P<guid>.*)" has the address "(?P<address_lines>.*)"')
|
||||
def step_impl(context, spatial_type, guid, address_lines):
|
||||
check_geocode_address(guid, spatial_type, "AddressLines", address_lines.split("\\n"))
|
||||
|
||||
|
||||
@step('The (site|building) "(?P<guid>.*)" has a postal box of "(?P<postal_box>.*)"')
|
||||
def step_impl(context, spatial_type, guid, postal_box):
|
||||
check_geocode_address(guid, spatial_type, "PostalBox", postal_box)
|
||||
|
||||
|
||||
@step('The (site|building) "(?P<guid>.*)" is in the town "(?P<town>.*)"')
|
||||
def step_impl(context, spatial_type, guid, town):
|
||||
check_geocode_address(guid, spatial_type, "Town", town)
|
||||
|
||||
|
||||
@step('The (site|building) "(?P<guid>.*)" is in the region "(?P<region>.*)"')
|
||||
def step_impl(context, spatial_type, guid, region):
|
||||
check_geocode_address(guid, spatial_type, "Region", region)
|
||||
|
||||
|
||||
@step('The (site|building) "(?P<guid>.*)" has a post code of "(?P<post_code>.*)"')
|
||||
def step_impl(context, spatial_type, guid, post_code):
|
||||
check_geocode_address(guid, spatial_type, "PostalCode", post_code)
|
||||
|
||||
|
||||
@step('The (site|building) "(?P<guid>.*)" is in the country "(?P<country>.*)"')
|
||||
def step_impl(context, spatial_type, guid, country):
|
||||
check_geocode_address(guid, spatial_type, "Country", country)
|
||||
|
||||
|
||||
@step('The (site|building) "(?P<guid>.*)" has an address description of "(?P<description>.*)"')
|
||||
def step_impl(context, spatial_type, guid, description):
|
||||
check_geocode_address(guid, spatial_type, "Description", description)
|
||||
@@ -0,0 +1,214 @@
|
||||
import math
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.geolocation
|
||||
from behave import step
|
||||
from bimtester import util
|
||||
from bimtester.ifc import IfcStore
|
||||
from bimtester.lang import _
|
||||
|
||||
|
||||
@step(u'There must be at least one "{ifc_class}" element')
|
||||
def step_impl(context, ifc_class):
|
||||
assert len(IfcStore.file.by_type(ifc_class)) >= 1, _("An element of {} could not be found").format(ifc_class)
|
||||
|
||||
|
||||
def check_ifc4_geolocation(entity_name, prop_name=None, value=None, should_assert=True):
|
||||
if entity_name not in IfcStore.bookmarks:
|
||||
has_entity = False
|
||||
project = IfcStore.file.by_type("IfcProject")[0]
|
||||
for context in project.RepresentationContexts:
|
||||
if entity_name == "IfcMapConversion":
|
||||
if (
|
||||
context.is_a("IfcGeometricRepresentationContext")
|
||||
and context.ContextType == "Model"
|
||||
and context.HasCoordinateOperation
|
||||
):
|
||||
IfcStore.bookmarks[entity_name] = context.HasCoordinateOperation[0]
|
||||
has_entity = True
|
||||
elif entity_name == "IfcProjectedCRS":
|
||||
if (
|
||||
context.is_a("IfcGeometricRepresentationContext")
|
||||
and context.ContextType == "Model"
|
||||
and context.HasCoordinateOperation
|
||||
and context.HasCoordinateOperation[0].TargetCRS
|
||||
):
|
||||
IfcStore.bookmarks[entity_name] = context.HasCoordinateOperation[0].TargetCRS
|
||||
has_entity = True
|
||||
if not has_entity:
|
||||
assert False, _("No model geometric representation contexts refer to an {}").format(entity_name)
|
||||
if not prop_name:
|
||||
return
|
||||
actual_value = getattr(IfcStore.bookmarks[entity_name], prop_name)
|
||||
if should_assert:
|
||||
assert actual_value == value, _('We expected a value of "{}" but instead got "{}"').format(value, actual_value)
|
||||
else:
|
||||
return actual_value
|
||||
|
||||
|
||||
@step(u"The project must have coordinate reference system data")
|
||||
def step_impl(context):
|
||||
if IfcStore.file.schema == "IFC2X3":
|
||||
for site in IfcStore.file.by_type("IfcSite"):
|
||||
util.assert_pset(site, "EPset_ProjectedCRS")
|
||||
return
|
||||
check_ifc4_geolocation("IfcProjectedCRS")
|
||||
|
||||
|
||||
@step(u'The name of the CRS must be "{coordinate_reference_name}"')
|
||||
def step_impl(context, coordinate_reference_name):
|
||||
if IfcStore.file.schema == "IFC2X3":
|
||||
for site in IfcStore.file.by_type("IfcSite"):
|
||||
util.assert_pset(site, "EPset_ProjectedCRS", "Name", coordinate_reference_name)
|
||||
return
|
||||
check_ifc4_geolocation("IfcProjectedCRS", "Name", coordinate_reference_name)
|
||||
|
||||
|
||||
@step(u'The description of the CRS must be "{value}"')
|
||||
def step_impl(context, value):
|
||||
if IfcStore.file.schema == "IFC2X3":
|
||||
for site in IfcStore.file.by_type("IfcSite"):
|
||||
util.assert_pset(site, "EPset_ProjectedCRS", "Description", value)
|
||||
return
|
||||
check_ifc4_geolocation("IfcProjectedCRS", "Description", value)
|
||||
|
||||
|
||||
@step(u'The geodetic datum must be "{coordinate_reference_name}"')
|
||||
def step_impl(context, coordinate_reference_name):
|
||||
if IfcStore.file.schema == "IFC2X3":
|
||||
for site in IfcStore.file.by_type("IfcSite"):
|
||||
util.assert_pset(site, "EPset_ProjectedCRS", "GeodeticDatum", coordinate_reference_name)
|
||||
return
|
||||
check_ifc4_geolocation("IfcProjectedCRS", "GeodeticDatum", coordinate_reference_name)
|
||||
|
||||
|
||||
@step(u'The vertical datum must be "{coordinate_reference_name}"')
|
||||
def step_impl(context, coordinate_reference_name):
|
||||
if IfcStore.file.schema == "IFC2X3":
|
||||
for site in IfcStore.file.by_type("IfcSite"):
|
||||
util.assert_pset(site, "EPset_ProjectedCRS", "VerticalDatum", coordinate_reference_name)
|
||||
return
|
||||
check_ifc4_geolocation("IfcProjectedCRS", "VerticalDatum", coordinate_reference_name)
|
||||
|
||||
|
||||
@step(u'The map projection must be "{coordinate_reference_name}"')
|
||||
def step_impl(context, coordinate_reference_name):
|
||||
if IfcStore.file.schema == "IFC2X3":
|
||||
for site in IfcStore.file.by_type("IfcSite"):
|
||||
util.assert_pset(site, "EPset_ProjectedCRS", "MapProjection", coordinate_reference_name)
|
||||
return
|
||||
check_ifc4_geolocation("IfcProjectedCRS", "MapProjection", coordinate_reference_name)
|
||||
|
||||
|
||||
@step(u'The map zone must be "{coordinate_reference_name}"')
|
||||
def step_impl(context, coordinate_reference_name):
|
||||
if IfcStore.file.schema == "IFC2X3":
|
||||
for site in IfcStore.file.by_type("IfcSite"):
|
||||
util.assert_pset(site, "EPset_ProjectedCRS", "MapZone", coordinate_reference_name)
|
||||
return
|
||||
check_ifc4_geolocation("IfcProjectedCRS", "MapZone", coordinate_reference_name)
|
||||
|
||||
|
||||
@step(u'The map unit must be "{unit}"')
|
||||
def step_impl(context, unit):
|
||||
if IfcStore.file.schema == "IFC2X3":
|
||||
for site in IfcStore.file.by_type("IfcSite"):
|
||||
util.assert_pset(site, "EPset_ProjectedCRS", "MapUnit", unit)
|
||||
return
|
||||
actual_value = check_ifc4_geolocation("IfcProjectedCRS", "MapUnit", should_assert=False)
|
||||
if not actual_value:
|
||||
assert False, _("A unit was not provided in the projected CRS")
|
||||
if actual_value.is_a("IfcSIUnit"):
|
||||
prefix = actual_value.Prefix if actual_value.Prefix else ""
|
||||
actual_value = prefix + actual_value.Name
|
||||
elif actual_value.is_a("IfcConversionBasedUnit"):
|
||||
actual_value = actual_value.Name
|
||||
assert actual_value == unit, _('We expected a value of "{}" but instead got "{}"').format(unit, actual_value)
|
||||
|
||||
|
||||
@step(u"The project must have coordinate transformations to convert from local to global coordinates")
|
||||
def step_impl(context):
|
||||
if IfcStore.file.schema == "IFC2X3":
|
||||
for site in IfcStore.file.by_type("IfcSite"):
|
||||
util.assert_pset(site, "EPset_MapConversion")
|
||||
check_ifc4_geolocation("IfcMapConversion")
|
||||
|
||||
|
||||
@step(u'The eastings of the model must be offset by "{number}" to derive its global coordinates')
|
||||
def step_impl(context, number):
|
||||
number = util.assert_number(number)
|
||||
if IfcStore.file.schema == "IFC2X3":
|
||||
for site in IfcStore.file.by_type("IfcSite"):
|
||||
util.assert_pset(site, "EPset_MapConversion", "Eastings", number)
|
||||
return
|
||||
check_ifc4_geolocation("IfcMapConversion", "Eastings", number)
|
||||
|
||||
|
||||
@step(u'The northings of the model must be offset by "{number}" to derive its global coordinates')
|
||||
def step_impl(context, number):
|
||||
number = util.assert_number(number)
|
||||
if IfcStore.file.schema == "IFC2X3":
|
||||
for site in IfcStore.file.by_type("IfcSite"):
|
||||
util.assert_pset(site, "EPset_MapConversion", "Northings", number)
|
||||
return
|
||||
check_ifc4_geolocation("IfcMapConversion", "Northings", number)
|
||||
|
||||
|
||||
@step(u'The height of the model must be offset by "{number}" to derive its global coordinates')
|
||||
def step_impl(context, number):
|
||||
number = util.assert_number(number)
|
||||
if IfcStore.file.schema == "IFC2X3":
|
||||
for site in IfcStore.file.by_type("IfcSite"):
|
||||
util.assert_pset(site, "EPset_MapConversion", "OrthogonalHeight", number)
|
||||
return
|
||||
check_ifc4_geolocation("IfcMapConversion", "OrthogonalHeight", number)
|
||||
|
||||
|
||||
@step(u'The model must be rotated clockwise by "{number}" to derive its global coordinates')
|
||||
def step_impl(context, number):
|
||||
number = util.assert_number(number)
|
||||
if IfcStore.file.schema == "IFC2X3":
|
||||
return check_ifc2x3_geolocation("EPset_MapConversion", "Height", number)
|
||||
abscissa = check_ifc4_geolocation("IfcMapConversion", "XAxisAbscissa", should_assert=False)
|
||||
ordinate = check_ifc4_geolocation("IfcMapConversion", "XAxisOrdinate", should_assert=False)
|
||||
actual_value = round(ifcopenshell.util.geolocation.xy2angle(abscissa, ordinate), 3)
|
||||
value = round(number, 3)
|
||||
assert actual_value == value, _('We expected a value of "{}" but instead got "{}"').format(value, actual_value)
|
||||
|
||||
|
||||
@step(u'The model must be scaled along the horizontal axis by "{number}" to derive its global coordinates')
|
||||
def step_impl(context, number):
|
||||
number = util.assert_number(number)
|
||||
if IfcStore.file.schema == "IFC2X3":
|
||||
for site in IfcStore.file.by_type("IfcSite"):
|
||||
util.assert_pset(site, "EPset_MapConversion", "Scale", number)
|
||||
return
|
||||
check_ifc4_geolocation("IfcMapConversion", "Scale", number)
|
||||
|
||||
|
||||
@step(u'The site "{guid}" has a longitude of "{number}"')
|
||||
def step_impl(context, guid, number):
|
||||
number = util.assert_number(number)
|
||||
site = util.assert_guid(IfcStore.file, guid)
|
||||
util.assert_type(site, "IfcSite")
|
||||
ref = util.assert_attribute(site, "RefLongitude")
|
||||
number = ifcopenshell.util.geolocation.dd2dms(number, use_ms=(len(ref) == 4))
|
||||
util.assert_attribute(site, "RefLongitude", number)
|
||||
|
||||
|
||||
@step(u'The site "{guid}" has a latitude of "{number}"')
|
||||
def step_impl(context, guid, number):
|
||||
number = util.assert_number(number)
|
||||
site = util.assert_guid(IfcStore.file, guid)
|
||||
util.assert_type(site, "IfcSite")
|
||||
ref = util.assert_attribute(site, "RefLatitude")
|
||||
number = ifcopenshell.util.geolocation.dd2dms(number, use_ms=(len(ref) == 4))
|
||||
util.assert_attribute(site, "RefLatitude", number)
|
||||
|
||||
|
||||
@step(u'The site "{guid}" has an elevation of "{number}"')
|
||||
def step_impl(context, guid, number):
|
||||
number = util.assert_number(number)
|
||||
site = util.assert_guid(IfcStore.file, guid)
|
||||
util.assert_type(site, "IfcSite")
|
||||
util.assert_attribute(site, "RefElevation", number)
|
||||
@@ -0,0 +1,29 @@
|
||||
from behave import step
|
||||
from bimtester import util
|
||||
from bimtester.ifc import IfcStore
|
||||
from bimtester.lang import _
|
||||
|
||||
|
||||
@step('All elements must be under "{number}" polygons')
|
||||
def step_impl(context, number):
|
||||
number = int(number)
|
||||
errors = []
|
||||
for element in IfcStore.file.by_type("IfcElement"):
|
||||
if not element.Representation:
|
||||
continue
|
||||
total_polygons = 0
|
||||
tree = IfcStore.file.traverse(element.Representation)
|
||||
for e in tree:
|
||||
if e.is_a("IfcFace"):
|
||||
total_polygons += 1
|
||||
elif e.is_a("IfcPolygonalFaceSet"):
|
||||
total_polygons += len(e.Faces)
|
||||
elif e.is_a("IfcTriangulatedFaceSet"):
|
||||
total_polygons += len(e.CoordIndex)
|
||||
if total_polygons > number:
|
||||
errors.append((total_polygons, element))
|
||||
if errors:
|
||||
message = "The following {} elements are over 500 polygons:\n".format(len(errors))
|
||||
for error in errors:
|
||||
message += "Polygons: {} - {}\n".format(error[0], error[1])
|
||||
assert False, message
|
||||
@@ -0,0 +1,95 @@
|
||||
import ifcopenshell.util.geolocation
|
||||
import ifcopenshell.util.placement
|
||||
from behave import step
|
||||
from bimtester import util
|
||||
from bimtester.ifc import IfcStore
|
||||
from bimtester.lang import _
|
||||
|
||||
|
||||
def get_decimal_points(value):
|
||||
try:
|
||||
return len(value.split(".")[1])
|
||||
except:
|
||||
return 0
|
||||
|
||||
|
||||
def get_containing_spatial_elements(element):
|
||||
results = []
|
||||
if element.is_a("IfcSpatialElement"):
|
||||
results.append(element)
|
||||
for rel in element.Decomposes:
|
||||
if rel.is_a("IfcRelAggregates"):
|
||||
results.append(get_containing_spatial_elements(rel.RelatingObject))
|
||||
elif element.is_a("IfcElement"):
|
||||
for rel in element.ContainedInStructure:
|
||||
if rel.is_a("ifcRelContainedInSpatialStructure"):
|
||||
results.append(get_containing_spatial_elements(rel.RelatingStructure))
|
||||
return results
|
||||
|
||||
|
||||
@step('There is a datum element "{guid}" as an "{ifc_class}"')
|
||||
def step_impl(context, guid, ifc_class):
|
||||
element = utils.assert_guid(IfcStore.file, guid)
|
||||
util.assert_type(element, ifc_class)
|
||||
|
||||
|
||||
@step(
|
||||
'The element "{guid}" has a global easting, northing, and elevation of "{easting}", "{northing}", and "{elevation}" respectively'
|
||||
)
|
||||
def step_impl(context, guid, easting, northing, elevation):
|
||||
if IfcStore.file.schema == "IFC2X3":
|
||||
if element.is_a("IfcSite"):
|
||||
site = element
|
||||
else:
|
||||
potential_sites = [s for s in get_containing_spatial_elements(element) if s.is_a("IfcSite")]
|
||||
if potential_sites:
|
||||
site = potential_sites[0]
|
||||
else:
|
||||
assert False, _("The datum element does not belong to a geolocated site")
|
||||
map_conversion = assert_pset(site, "EPset_MapConversion")
|
||||
else:
|
||||
map_conversion = IfcStore.file.by_type("IfcMapConversion")
|
||||
if map_conversion:
|
||||
map_conversion = map_conversion[0].get_info()
|
||||
else:
|
||||
assert False, _("No map conversion was found in the file")
|
||||
|
||||
element = utils.assert_guid(IfcStore.file, guid)
|
||||
if not element.ObjectPlacement:
|
||||
assert False, _("The element does not have an object placement: {}").format(element)
|
||||
m = ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement)
|
||||
e, n, h = ifcopenshell.util.geolocation.xyz2enh(
|
||||
m[0][3],
|
||||
m[1][3],
|
||||
m[2][3],
|
||||
float(map_conversion["Eastings"]),
|
||||
float(map_conversion["Northings"]),
|
||||
float(map_conversion["OrthogonalHeight"]),
|
||||
float(map_conversion["XAxisAbscissa"]),
|
||||
float(map_conversion["XAxisOrdinate"]),
|
||||
float(map_conversion["Scale"]),
|
||||
)
|
||||
element_x = round(e, get_decimal_points(easting))
|
||||
element_y = round(n, get_decimal_points(northing))
|
||||
element_z = round(h, get_decimal_points(elevation))
|
||||
expected_placement = (util.assert_number(easting), util.assert_number(northing), util.assert_number(elevation))
|
||||
if (element_x, element_y, element_z) != expected_placement:
|
||||
assert False, _("The element {} is meant to have a location of {} but instead we found {}").format(
|
||||
element, expected_placement, (element_x, element_y, element_z)
|
||||
)
|
||||
|
||||
|
||||
@step('The element "{guid}" has a local X, Y, and Z coordinate of "{x}", "{y}", and "{z}" respectively')
|
||||
def step_impl(context, guid, x, y, z):
|
||||
element = utils.assert_guid(IfcStore.file, guid)
|
||||
if not element.ObjectPlacement:
|
||||
assert False, _("The element does not have an object placement: {}").format(element)
|
||||
m = ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement)
|
||||
element_x = round(m[0][3], get_decimal_points(x))
|
||||
element_y = round(m[1][3], get_decimal_points(y))
|
||||
element_z = round(m[2][3], get_decimal_points(z))
|
||||
expected_placement = (util.assert_number(x), util.assert_number(y), util.assert_number(z))
|
||||
if (element_x, element_y, element_z) != expected_placement:
|
||||
assert False, _("The element {} is meant to have a location of {} but instead we found {}").format(
|
||||
element, expected_placement, (element_x, element_y, element_z)
|
||||
)
|
||||
@@ -0,0 +1,16 @@
|
||||
from behave import step
|
||||
|
||||
|
||||
@step('Die IFC-Daten müssen das "{schema}" Schema benutzen')
|
||||
def step_impl(context, schema):
|
||||
context.execute_steps(f'* IFC data must use the "{schema}" schema')
|
||||
|
||||
|
||||
@step('Die Globale Identifikationskennung (Globally Unique Identifier = GUID) des Projektes ist "{guid}"')
|
||||
def step_impl(context, guid):
|
||||
context.execute_steps(f'* The project must have an identifier of "{guid}"')
|
||||
|
||||
|
||||
@step('Der Name, die Abkürzung oder die Kurzkennung des Projektes ist "{value}"')
|
||||
def step_impl(context, value):
|
||||
context.execute_steps(f'* The project name, code, or short identifier must be "{value}"')
|
||||
@@ -0,0 +1,91 @@
|
||||
from behave import step
|
||||
from bimtester import util
|
||||
from bimtester.ifc import IfcStore
|
||||
from bimtester.lang import _
|
||||
|
||||
|
||||
@step('IFC data must use the "{schema}" schema')
|
||||
def step_impl(context, schema):
|
||||
real_schema = IfcStore.file.schema
|
||||
assert real_schema == schema, _("We expected a schema of {} but instead got {}").format(schema, real_schema)
|
||||
|
||||
|
||||
@step('The IFC file "{file}" is exempt from being provided')
|
||||
def step_impl(context, file):
|
||||
pass
|
||||
|
||||
|
||||
@step('No further requirements are specified because "{reason}"')
|
||||
def step_impl(context, reason):
|
||||
pass
|
||||
|
||||
|
||||
@step('The project must have an identifier of "{guid}"')
|
||||
def step_impl(context, guid):
|
||||
util.assert_attribute(IfcStore.file.by_type("IfcProject")[0], "GlobalId", guid)
|
||||
|
||||
|
||||
@step('The project name, code, or short identifier must be "{value}"')
|
||||
def step_impl(context, value):
|
||||
util.assert_attribute(IfcStore.file.by_type("IfcProject")[0], "Name", value)
|
||||
|
||||
|
||||
@step('The project must have a longer form name of "{value}"')
|
||||
def step_impl(context, value):
|
||||
util.assert_attribute(IfcStore.file.by_type("IfcProject")[0], "LongName", value)
|
||||
|
||||
|
||||
@step('The project must be described as "{value}"')
|
||||
def step_impl(context, value):
|
||||
util.assert_attribute(IfcStore.file.by_type("IfcProject")[0], "Description", value)
|
||||
|
||||
|
||||
@step('The project must be categorised under "{value}"')
|
||||
def step_impl(context, value):
|
||||
util.assert_attribute(IfcStore.file.by_type("IfcProject")[0], "ObjectType", value)
|
||||
|
||||
|
||||
@step('The project must contain information about the "{value}" phase')
|
||||
def step_impl(context, value):
|
||||
util.assert_attribute(IfcStore.file.by_type("IfcProject")[0], "Phase", value)
|
||||
|
||||
|
||||
@step("The project must contain 3D geometry representing the shape of objects")
|
||||
def step_impl(context):
|
||||
assert get_subcontext("Body", "Model", "MODEL_VIEW")
|
||||
|
||||
|
||||
@step("The project must contain 3D geometry representing clearance zones")
|
||||
def step_impl(context):
|
||||
assert get_subcontext("Clearance", "Model", "MODEL_VIEW")
|
||||
|
||||
|
||||
@step("The project must contain 3D geometry representing the center of gravity of objects")
|
||||
def step_impl(context):
|
||||
assert get_subcontext("CoG", "Model", "MODEL_VIEW")
|
||||
|
||||
|
||||
@step("The project must contain 3D geometry representing the object bounding boxes")
|
||||
def step_impl(context):
|
||||
assert get_subcontext("Box", "Model", "MODEL_VIEW")
|
||||
|
||||
|
||||
def get_subcontext(identifier, type, target_view):
|
||||
project = IfcStore.file.by_type("IfcProject")[0]
|
||||
for rep_context in project.RepresentationContexts:
|
||||
for subcontext in rep_context.HasSubContexts:
|
||||
if (
|
||||
subcontext.ContextIdentifier == identifier
|
||||
and subcontext.ContextType == type
|
||||
and subcontext.TargetView == target_view
|
||||
):
|
||||
return True
|
||||
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 = IfcStore.file.by_type("IfcProject")[0]
|
||||
assert getattr(project, attribute_name) == attribute_value
|
||||
@@ -0,0 +1,6 @@
|
||||
from behave import step
|
||||
|
||||
|
||||
@step('Les données IFC doivent utiliser le schéma "{schema}"')
|
||||
def step_impl(context, schema):
|
||||
context.execute_steps(f'* IFC data must use the "{schema}" schema')
|
||||
@@ -0,0 +1,11 @@
|
||||
from behave import step
|
||||
|
||||
|
||||
@step('I dati IFC devono seguire lo schema "{schema}"')
|
||||
def step_impl(context, schema):
|
||||
context.execute_steps(f'* IFC data must use the "{schema}" schema')
|
||||
|
||||
|
||||
@step('Il nome del progetto, codice o identificatore breve deve essere "{value}"')
|
||||
def step_impl(context, value):
|
||||
context.execute_steps(f'* "The project name, code, or short identifier must be "{value}"')
|
||||
@@ -0,0 +1,11 @@
|
||||
from behave import step
|
||||
|
||||
|
||||
@step('IFC-gegevens moeten het "{schema}" -schema gebruiken')
|
||||
def step_impl(context, schema):
|
||||
context.execute_steps(f'* IFC data must use the "{schema}" schema')
|
||||
|
||||
|
||||
@step('De projectnaam, code of korte ID moet "{value}"')
|
||||
def step_impl(context, value):
|
||||
context.execute_steps(f'* "The project name, code, or short identifier must be "{value}"')
|
||||
@@ -0,0 +1,138 @@
|
||||
import fileinput
|
||||
|
||||
|
||||
def create_zoom_smartview(sm_file, ifcbasename):
|
||||
|
||||
smf = open(sm_file, "w")
|
||||
smf.write('<?xml version="1.0"?>\n')
|
||||
smf.write("<bimcollabsmartviewfile>\n")
|
||||
smf.write(" <version>5</version>\n")
|
||||
smf.write(" <applicationversion>Win - Version: ")
|
||||
# next line belongs to last, because of line length
|
||||
smf.write("3.4 (build 3.4.13.559)</applicationversion>\n")
|
||||
smf.write("</bimcollabsmartviewfile>\n")
|
||||
smf.write("\n")
|
||||
smf.write("<SMARTVIEWSETS>\n")
|
||||
smf.write(" <SMARTVIEWSET>\n")
|
||||
smf.write(" <TITLE>BIMTester {}</TITLE>\n".format(ifcbasename))
|
||||
smf.write(" <DESCRIPTION></DESCRIPTION>\n")
|
||||
smf.write(" <GUID>a2ddfaf7-97f2-4519-aabd-f2d94f6b4d6b</GUID>\n")
|
||||
smf.write(" <MODIFICATIONDATE>2020-10-30T13:23:30")
|
||||
# next line belongs to last, because of line length
|
||||
smf.write("</MODIFICATIONDATE>\n")
|
||||
smf.write(" <SMARTVIEWS>\n")
|
||||
smf.write(" </SMARTVIEWS>\n")
|
||||
smf.write(" </SMARTVIEWSET>\n")
|
||||
smf.write("</SMARTVIEWSETS>\n")
|
||||
smf.close()
|
||||
|
||||
|
||||
def append_zoom_smartview(sm_file, step_name, false_elements_guid):
|
||||
|
||||
# build the smartview string
|
||||
smview_string = " <SMARTVIEW>\n"
|
||||
smview_string += (
|
||||
" <TITLE>GUID filter, {}</TITLE>\n"
|
||||
.format(step_name)
|
||||
)
|
||||
smview_string += "{}\n".format(each_smartview_string_before)
|
||||
for guid in false_elements_guid:
|
||||
smview_string += (
|
||||
"{}{}{}\n".format(
|
||||
rule_string_before,
|
||||
guid,
|
||||
rule_string_after)
|
||||
)
|
||||
smview_string += "{}\n".format(each_smartview_string_after)
|
||||
|
||||
# insert smartview string into file
|
||||
theline = " </SMARTVIEWS>"
|
||||
newtext = smview_string + theline
|
||||
for line in fileinput.FileInput(sm_file, inplace=True):
|
||||
# the print replaces the line in the file
|
||||
# and add the line afterwards
|
||||
print(line.replace(theline, newtext), end="")
|
||||
|
||||
|
||||
each_smartview_string_title = """ <SMARTVIEW>
|
||||
<TITLE>Filter GUID</TITLE>
|
||||
<DESCRIPTION></DESCRIPTION>"""
|
||||
|
||||
|
||||
each_smartview_string_before = """ <CREATOR>bernd@bimstatik.ch</CREATOR>
|
||||
<CREATIONDATE>2020-10-30T13:18:45</CREATIONDATE>
|
||||
<MODIFIER>bernd@bimstatik.ch</MODIFIER>
|
||||
<MODIFICATIONDATE>2020-10-30T13:23:30</MODIFICATIONDATE>
|
||||
<GUID>15fda94f-b4bf-43be-8ef4-15d3121137e1</GUID>
|
||||
<RULES>
|
||||
<RULE>
|
||||
<IFCTYPE>Any</IFCTYPE>
|
||||
<PROPERTY>
|
||||
<NAME>None</NAME>
|
||||
<PROPERTYSETNAME>None</PROPERTYSETNAME>
|
||||
<TYPE>None</TYPE>
|
||||
<VALUETYPE>None</VALUETYPE>
|
||||
<UNIT>None</UNIT>
|
||||
</PROPERTY>
|
||||
<CONDITION>
|
||||
<TYPE>Is</TYPE>
|
||||
<VALUE></VALUE>
|
||||
</CONDITION>
|
||||
<ACTION>
|
||||
<TYPE>AddSetColored</TYPE>
|
||||
<R>187</R>
|
||||
<G>187</G>
|
||||
<B>187</B>
|
||||
</ACTION>
|
||||
</RULE>
|
||||
<RULE>
|
||||
<IFCTYPE>Any</IFCTYPE>
|
||||
<PROPERTY>
|
||||
<NAME>None</NAME>
|
||||
<PROPERTYSETNAME>None</PROPERTYSETNAME>
|
||||
<TYPE>None</TYPE>
|
||||
<VALUETYPE>None</VALUETYPE>
|
||||
<UNIT>None</UNIT>
|
||||
</PROPERTY>
|
||||
<CONDITION>
|
||||
<TYPE>Is</TYPE>
|
||||
<VALUE></VALUE>
|
||||
</CONDITION>
|
||||
<ACTION>
|
||||
<TYPE>SetTransparent</TYPE>
|
||||
</ACTION>
|
||||
</RULE>"""
|
||||
|
||||
|
||||
each_smartview_string_after = """ </RULES>
|
||||
<INFORMATIONTAKEOFF>
|
||||
<PROPERTYSETNAME>None</PROPERTYSETNAME>
|
||||
<PROPERTYNAME>None</PROPERTYNAME>
|
||||
<OPERATION>0</OPERATION>
|
||||
</INFORMATIONTAKEOFF>
|
||||
</SMARTVIEW>"""
|
||||
|
||||
|
||||
rule_string_before = """ <RULE>
|
||||
<IFCTYPE>Any</IFCTYPE>
|
||||
<PROPERTY>
|
||||
<NAME>GUID</NAME>
|
||||
<PROPERTYSETNAME>Summary</PROPERTYSETNAME>
|
||||
<TYPE>Summary</TYPE>
|
||||
<VALUETYPE>StringValue</VALUETYPE>
|
||||
<UNIT>None</UNIT>
|
||||
</PROPERTY>
|
||||
<CONDITION>
|
||||
<TYPE>Is</TYPE>
|
||||
<VALUE>"""
|
||||
|
||||
|
||||
rule_string_after = """</VALUE>
|
||||
</CONDITION>
|
||||
<ACTION>
|
||||
<TYPE>SetColored</TYPE>
|
||||
<R>255</R>
|
||||
<G>10</G>
|
||||
<B>10</B>
|
||||
</ACTION>
|
||||
</RULE>"""
|
||||
@@ -0,0 +1,153 @@
|
||||
import os
|
||||
import sys
|
||||
import bimtester.run
|
||||
from PySide2 import QtCore
|
||||
from PySide2 import QtGui
|
||||
from PySide2 import QtWidgets
|
||||
|
||||
|
||||
def run():
|
||||
app = QtWidgets.QApplication(sys.argv)
|
||||
form = GuiWidgetBimTester()
|
||||
form.show()
|
||||
sys.exit(app.exec_())
|
||||
|
||||
|
||||
class GuiWidgetBimTester(QtWidgets.QWidget):
|
||||
def __init__(self, args=[]):
|
||||
super(GuiWidgetBimTester, self).__init__()
|
||||
self.args = args
|
||||
|
||||
self._setup_ui()
|
||||
|
||||
# http://forum.freecadweb.org/viewtopic.php?f=18&t=10732&start=10#p86493
|
||||
def __del__(self):
|
||||
return
|
||||
|
||||
def _setup_ui(self):
|
||||
package_path = os.path.dirname(os.path.realpath(__file__))
|
||||
iconpath = os.path.join(package_path, "resources", "icons", "bimtester.ico")
|
||||
|
||||
"""
|
||||
# as svg
|
||||
# https://stackoverflow.com/a/35138314
|
||||
theicon = QtSvg.QSvgWidget(iconpath)
|
||||
# none works ...
|
||||
#theicon.setGeometry(20,20,200,200)
|
||||
#theicon.setSizePolicy(
|
||||
# QtGui.QSizePolicy.Policy.Maximum,
|
||||
# QtGui.QSizePolicy.Policy.Maximum
|
||||
#)
|
||||
#theicon.sizeHint()
|
||||
"""
|
||||
|
||||
# as pixmap
|
||||
theicon = QtWidgets.QLabel(self)
|
||||
iconpixmap = QtGui.QPixmap(iconpath)
|
||||
iconpixmap = iconpixmap.scaled(100, 100, QtCore.Qt.KeepAspectRatio)
|
||||
theicon.setPixmap(iconpixmap)
|
||||
|
||||
# ifc file
|
||||
_ifcfile_label = QtWidgets.QLabel("IFC file", self)
|
||||
self.ifcfile_text = QtWidgets.QLineEdit()
|
||||
_ifcfile_browse_btn = QtWidgets.QToolButton()
|
||||
_ifcfile_browse_btn.setText("...")
|
||||
_ifcfile_browse_btn.clicked.connect(self.select_ifcfile)
|
||||
|
||||
# feature files path
|
||||
# use a layout with a frame and a title, see solver framework tp
|
||||
# beside button
|
||||
ffifc_str = "Feature files in a directory 'features' beside the IFC file."
|
||||
featuredirfromifc_label = QtWidgets.QLabel(ffifc_str, self)
|
||||
|
||||
# path browser and line edit
|
||||
_ffdir_str = "Feature files directory. " "'features' directory has to be in there."
|
||||
_featurefilesdir_label = QtWidgets.QLabel(_ffdir_str, self)
|
||||
self.featurefilesdir_text = QtWidgets.QLineEdit()
|
||||
self.feafilesdir_browse_btn = QtWidgets.QToolButton()
|
||||
self.feafilesdir_browse_btn.setText("...")
|
||||
self.feafilesdir_browse_btn.clicked.connect(self.select_featurefilesdir)
|
||||
|
||||
# buttons
|
||||
self.run_button = QtWidgets.QPushButton(QtGui.QIcon.fromTheme("document-new"), "Run")
|
||||
self.close_button = QtWidgets.QPushButton(QtGui.QIcon.fromTheme("window-close"), "Close")
|
||||
self.run_button.clicked.connect(self.run_bimtester)
|
||||
self.close_button.clicked.connect(self.close_widget)
|
||||
_buttons = QtWidgets.QHBoxLayout()
|
||||
_buttons.addWidget(self.run_button)
|
||||
_buttons.addWidget(self.close_button)
|
||||
|
||||
# Layout:
|
||||
layout = QtWidgets.QGridLayout()
|
||||
layout.addWidget(theicon, 1, 0, alignment=QtCore.Qt.AlignRight)
|
||||
|
||||
layout.addWidget(featuredirfromifc_label, 2, 0)
|
||||
|
||||
layout.addWidget(_featurefilesdir_label, 3, 0)
|
||||
layout.addWidget(self.featurefilesdir_text, 4, 0)
|
||||
layout.addWidget(self.feafilesdir_browse_btn, 4, 1)
|
||||
|
||||
layout.addWidget(_ifcfile_label, 5, 0)
|
||||
layout.addWidget(self.ifcfile_text, 6, 0)
|
||||
layout.addWidget(_ifcfile_browse_btn, 6, 1)
|
||||
|
||||
layout.addLayout(_buttons, 7, 0)
|
||||
# row stretches by 10 compared to the others, std is 0
|
||||
# first parameter is the row number
|
||||
# second is the stretch factor.
|
||||
layout.setRowStretch(0, 10)
|
||||
self.setLayout(layout)
|
||||
|
||||
def select_ifcfile(self):
|
||||
ifcfile = QtWidgets.QFileDialog.getOpenFileName(self, dir=self.get_ifcfile())[0]
|
||||
self.set_ifcfile(ifcfile)
|
||||
|
||||
def set_ifcfile(self, a_file):
|
||||
self.ifcfile_text.setText(a_file)
|
||||
|
||||
def get_ifcfile(self):
|
||||
return self.ifcfile_text.text()
|
||||
|
||||
def select_featurefilesdir(self):
|
||||
thedir = self.featurefilesdir_text.text()
|
||||
features_path = QtWidgets.QFileDialog.getExistingDirectory(
|
||||
self,
|
||||
caption="Choose features directory ...",
|
||||
dir=thedir,
|
||||
options=QtWidgets.QFileDialog.HideNameFilterDetails,
|
||||
)
|
||||
self.set_featurefilesdir(features_path)
|
||||
|
||||
def set_featurefilesdir(self, a_directory):
|
||||
self.featurefilesdir_text.setText(a_directory)
|
||||
|
||||
def get_featurefilesdir(self):
|
||||
return self.featurefilesdir_text.text()
|
||||
|
||||
def run_bimtester(self):
|
||||
print("Run BIMTester by the GUI")
|
||||
QtWidgets.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor)
|
||||
|
||||
the_features_path = self.get_featurefilesdir()
|
||||
print(the_features_path)
|
||||
|
||||
# get ifc file
|
||||
the_ifcfile = self.get_ifcfile()
|
||||
print(the_ifcfile)
|
||||
|
||||
# overwrite the_features_path and ifcfile in args
|
||||
patched_args = self.args
|
||||
patched_args["featuresdir"] = the_features_path
|
||||
patched_args["ifcfile"] = the_ifcfile
|
||||
|
||||
bimtester.run.TestRunner("file.ifc").run({})
|
||||
|
||||
QtWidgets.QApplication.restoreOverrideCursor()
|
||||
|
||||
def close_widget(self):
|
||||
self.close()
|
||||
|
||||
def closeEvent(self, ev):
|
||||
pw = self.parentWidget()
|
||||
if pw and pw.inherits("QDockWidget"):
|
||||
pw.deleteLater()
|
||||
@@ -0,0 +1,4 @@
|
||||
class IfcStore:
|
||||
path = ""
|
||||
file = None
|
||||
bookmarks = {}
|
||||
@@ -0,0 +1,15 @@
|
||||
import gettext
|
||||
|
||||
translation = None
|
||||
|
||||
def _(message):
|
||||
if translation:
|
||||
return translation(message)
|
||||
return message
|
||||
|
||||
|
||||
def switch_locale(locale_dir, locale_id="en"):
|
||||
global translation
|
||||
newlang = gettext.translation("messages", localedir=locale_dir, languages=[locale_id])
|
||||
newlang.install()
|
||||
translation = newlang.gettext
|
||||
@@ -0,0 +1,200 @@
|
||||
# German translations for PROJECT.
|
||||
# Copyright (C) 2020 ORGANIZATION
|
||||
# This file is distributed under the same license as the PROJECT project.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, 2020.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PROJECT VERSION\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"POT-Creation-Date: 2021-01-17 10:55+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"
|
||||
"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"
|
||||
|
||||
#: reports.py:158
|
||||
msgid "en"
|
||||
msgstr "de"
|
||||
|
||||
#: reports.py:159
|
||||
msgid "Success"
|
||||
msgstr "Bestanden"
|
||||
|
||||
#: reports.py:160
|
||||
msgid "Failure"
|
||||
msgstr "Durchgefallen"
|
||||
|
||||
#: reports.py:161
|
||||
msgid "Tests passed"
|
||||
msgstr "Erfolgreiche Tests"
|
||||
|
||||
#: reports.py:162
|
||||
msgid "Duration"
|
||||
msgstr "Dauer"
|
||||
|
||||
#: reports.py:163
|
||||
msgid "OpenBIM auditing is a feature of"
|
||||
msgstr "OpenBIM auditing ist eine Funktionalität von"
|
||||
|
||||
#: reports.py:164
|
||||
msgid "and"
|
||||
msgstr "und"
|
||||
|
||||
#: features/steps/attributes_eleclasses_methods.py:26
|
||||
msgid "All {elemcount} elements in the file are {ifc_class}."
|
||||
msgstr ""
|
||||
|
||||
#: features/steps/attributes_eleclasses_methods.py:34
|
||||
msgid "{falsecount} of {elemcount} element are {ifc_class} elements: {falseelems}"
|
||||
msgstr ""
|
||||
|
||||
#: features/steps/attributes_eleclasses_methods.py:43
|
||||
#: 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:84
|
||||
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:85
|
||||
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:86
|
||||
#: features/steps/attributes_eleclasses_methods.py:112
|
||||
#: features/steps/attributes_eleclasses_methods.py:139
|
||||
#: features/steps/attributes_psets_methods.py:33
|
||||
#: features/steps/geometric_detail_methods.py:56
|
||||
#: features/steps/geometric_detail_methods.py:134
|
||||
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:110
|
||||
msgid "The name of all {elemcount} {elemcount} elements is not set."
|
||||
msgstr ""
|
||||
|
||||
#: features/steps/attributes_eleclasses_methods.py:111
|
||||
msgid ""
|
||||
"The name of {falsecount} out of {elemcount} {ifc_class} elements is not "
|
||||
"set: {falseelems}"
|
||||
msgstr ""
|
||||
|
||||
#: features/steps/attributes_eleclasses_methods.py:137
|
||||
msgid "The description of all {elemcount} {elemcount} elements is not set."
|
||||
msgstr ""
|
||||
|
||||
#: features/steps/attributes_eleclasses_methods.py:138
|
||||
msgid ""
|
||||
"The description of {falsecount} out of {elemcount} {ifc_class} elements "
|
||||
"is not set: {falseelems}"
|
||||
msgstr ""
|
||||
|
||||
#: features/steps/attributes_psets_methods.py:31
|
||||
msgid ""
|
||||
"All {elemcount} {ifc_class} elements are missing the property {parameter}"
|
||||
" in the pset."
|
||||
msgstr ""
|
||||
|
||||
#: features/steps/attributes_psets_methods.py:32
|
||||
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:54
|
||||
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:55
|
||||
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:132
|
||||
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:133
|
||||
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:9
|
||||
msgid "The IFC {} file could not be loaded"
|
||||
msgstr ""
|
||||
|
||||
#: features/steps/ifcdata_methods.py:17
|
||||
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}"
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
# German translations for PROJECT.
|
||||
# Copyright (C) 2020 ORGANIZATION
|
||||
# This file is distributed under the same license as the PROJECT project.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, 2020.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PROJECT VERSION\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"POT-Creation-Date: 2021-01-17 10:55+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"
|
||||
"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"
|
||||
|
||||
#: reports.py:158
|
||||
msgid "en"
|
||||
msgstr ""
|
||||
|
||||
#: reports.py:159
|
||||
msgid "Success"
|
||||
msgstr ""
|
||||
|
||||
#: reports.py:160
|
||||
msgid "Failure"
|
||||
msgstr ""
|
||||
|
||||
#: reports.py:161
|
||||
msgid "Tests passed"
|
||||
msgstr ""
|
||||
|
||||
#: reports.py:162
|
||||
msgid "Duration"
|
||||
msgstr ""
|
||||
|
||||
#: reports.py:163
|
||||
msgid "OpenBIM auditing is a feature of"
|
||||
msgstr ""
|
||||
|
||||
#: reports.py:164
|
||||
msgid "and"
|
||||
msgstr ""
|
||||
|
||||
#: features/steps/attributes_eleclasses_methods.py:26
|
||||
msgid "All {elemcount} elements in the file are {ifc_class}."
|
||||
msgstr ""
|
||||
|
||||
#: features/steps/attributes_eleclasses_methods.py:34
|
||||
msgid "{falsecount} of {elemcount} element are {ifc_class} elements: {falseelems}"
|
||||
msgstr ""
|
||||
|
||||
#: features/steps/attributes_eleclasses_methods.py:43
|
||||
#: features/steps/utils.py:158
|
||||
msgid "Error in falsecount, something went wrong."
|
||||
msgstr ""
|
||||
|
||||
#: features/steps/attributes_eleclasses_methods.py:84
|
||||
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:85
|
||||
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:86
|
||||
#: features/steps/attributes_eleclasses_methods.py:112
|
||||
#: features/steps/attributes_eleclasses_methods.py:139
|
||||
#: features/steps/attributes_psets_methods.py:33
|
||||
#: features/steps/geometric_detail_methods.py:56
|
||||
#: features/steps/geometric_detail_methods.py:134
|
||||
msgid "There are no {ifc_class} elements in the IFC file."
|
||||
msgstr ""
|
||||
|
||||
#: features/steps/attributes_eleclasses_methods.py:110
|
||||
msgid "The name of all {elemcount} {elemcount} elements is not set."
|
||||
msgstr ""
|
||||
|
||||
#: features/steps/attributes_eleclasses_methods.py:111
|
||||
msgid ""
|
||||
"The name of {falsecount} out of {elemcount} {ifc_class} elements is not "
|
||||
"set: {falseelems}"
|
||||
msgstr ""
|
||||
|
||||
#: features/steps/attributes_eleclasses_methods.py:137
|
||||
msgid "The description of all {elemcount} {elemcount} elements is not set."
|
||||
msgstr ""
|
||||
|
||||
#: features/steps/attributes_eleclasses_methods.py:138
|
||||
msgid ""
|
||||
"The description of {falsecount} out of {elemcount} {ifc_class} elements "
|
||||
"is not set: {falseelems}"
|
||||
msgstr ""
|
||||
|
||||
#: features/steps/attributes_psets_methods.py:31
|
||||
msgid ""
|
||||
"All {elemcount} {ifc_class} elements are missing the property {parameter}"
|
||||
" in the pset."
|
||||
msgstr ""
|
||||
|
||||
#: features/steps/attributes_psets_methods.py:32
|
||||
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:54
|
||||
msgid "All {elemcount} {ifc_class} elements are not a {parameter} representation."
|
||||
msgstr ""
|
||||
|
||||
#: features/steps/geometric_detail_methods.py:55
|
||||
msgid ""
|
||||
"The following {falsecount} of {elemcount} {ifc_class} elements are not a "
|
||||
"{parameter} representation: {falseelems}"
|
||||
msgstr ""
|
||||
|
||||
#: features/steps/geometric_detail_methods.py:132
|
||||
msgid "The geometry of all {elemcount} {ifc_class} elements have errors."
|
||||
msgstr ""
|
||||
|
||||
#: features/steps/geometric_detail_methods.py:133
|
||||
msgid ""
|
||||
"The geometry of {falsecount} out of all {elemcount} {ifc_class} elements "
|
||||
"have errors: {falseelems}"
|
||||
msgstr ""
|
||||
|
||||
#: features/steps/ifcdata_methods.py:9
|
||||
msgid "The IFC {} file could not be loaded"
|
||||
msgstr ""
|
||||
|
||||
#: features/steps/ifcdata_methods.py:17
|
||||
msgid "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 ""
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
# French translations for PROJECT.
|
||||
# Copyright (C) 2020 ORGANIZATION
|
||||
# This file is distributed under the same license as the PROJECT project.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, 2020.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PROJECT VERSION\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"POT-Creation-Date: 2021-01-17 10:55+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"
|
||||
"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"
|
||||
|
||||
#: reports.py:158
|
||||
msgid "en"
|
||||
msgstr "fr"
|
||||
|
||||
#: reports.py:159
|
||||
msgid "Success"
|
||||
msgstr "Succès"
|
||||
|
||||
#: reports.py:160
|
||||
msgid "Failure"
|
||||
msgstr "Échec"
|
||||
|
||||
#: reports.py:161
|
||||
msgid "Tests passed"
|
||||
msgstr "Tests réussis"
|
||||
|
||||
#: reports.py:162
|
||||
msgid "Duration"
|
||||
msgstr "Durée"
|
||||
|
||||
#: reports.py:163
|
||||
msgid "OpenBIM auditing is a feature of"
|
||||
msgstr "L'audit OpenBIM auditing est une fonctionnalité de"
|
||||
|
||||
#: reports.py:164
|
||||
msgid "and"
|
||||
msgstr "et"
|
||||
|
||||
#: features/steps/attributes_eleclasses_methods.py:26
|
||||
msgid "All {elemcount} elements in the file are {ifc_class}."
|
||||
msgstr ""
|
||||
|
||||
#: features/steps/attributes_eleclasses_methods.py:34
|
||||
msgid "{falsecount} of {elemcount} element are {ifc_class} elements: {falseelems}"
|
||||
msgstr ""
|
||||
|
||||
#: features/steps/attributes_eleclasses_methods.py:43
|
||||
#: features/steps/utils.py:158
|
||||
msgid "Error in falsecount, something went wrong."
|
||||
msgstr ""
|
||||
|
||||
#: features/steps/attributes_eleclasses_methods.py:84
|
||||
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:85
|
||||
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:86
|
||||
#: features/steps/attributes_eleclasses_methods.py:112
|
||||
#: features/steps/attributes_eleclasses_methods.py:139
|
||||
#: features/steps/attributes_psets_methods.py:33
|
||||
#: features/steps/geometric_detail_methods.py:56
|
||||
#: features/steps/geometric_detail_methods.py:134
|
||||
msgid "There are no {ifc_class} elements in the IFC file."
|
||||
msgstr ""
|
||||
|
||||
#: features/steps/attributes_eleclasses_methods.py:110
|
||||
msgid "The name of all {elemcount} {elemcount} elements is not set."
|
||||
msgstr ""
|
||||
|
||||
#: features/steps/attributes_eleclasses_methods.py:111
|
||||
msgid ""
|
||||
"The name of {falsecount} out of {elemcount} {ifc_class} elements is not "
|
||||
"set: {falseelems}"
|
||||
msgstr ""
|
||||
|
||||
#: features/steps/attributes_eleclasses_methods.py:137
|
||||
msgid "The description of all {elemcount} {elemcount} elements is not set."
|
||||
msgstr ""
|
||||
|
||||
#: features/steps/attributes_eleclasses_methods.py:138
|
||||
msgid ""
|
||||
"The description of {falsecount} out of {elemcount} {ifc_class} elements "
|
||||
"is not set: {falseelems}"
|
||||
msgstr ""
|
||||
|
||||
#: features/steps/attributes_psets_methods.py:31
|
||||
msgid ""
|
||||
"All {elemcount} {ifc_class} elements are missing the property {parameter}"
|
||||
" in the pset."
|
||||
msgstr ""
|
||||
|
||||
#: features/steps/attributes_psets_methods.py:32
|
||||
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:54
|
||||
msgid "All {elemcount} {ifc_class} elements are not a {parameter} representation."
|
||||
msgstr ""
|
||||
|
||||
#: features/steps/geometric_detail_methods.py:55
|
||||
msgid ""
|
||||
"The following {falsecount} of {elemcount} {ifc_class} elements are not a "
|
||||
"{parameter} representation: {falseelems}"
|
||||
msgstr ""
|
||||
|
||||
#: features/steps/geometric_detail_methods.py:132
|
||||
msgid "The geometry of all {elemcount} {ifc_class} elements have errors."
|
||||
msgstr ""
|
||||
|
||||
#: features/steps/geometric_detail_methods.py:133
|
||||
msgid ""
|
||||
"The geometry of {falsecount} out of all {elemcount} {ifc_class} elements "
|
||||
"have errors: {falseelems}"
|
||||
msgstr ""
|
||||
|
||||
#: features/steps/ifcdata_methods.py:9
|
||||
msgid "The IFC {} file could not be loaded"
|
||||
msgstr ""
|
||||
|
||||
#: features/steps/ifcdata_methods.py:17
|
||||
msgid "We expected a schema of {} but instead got {}"
|
||||
msgstr ""
|
||||
|
||||
@@ -0,0 +1,196 @@
|
||||
# Italian translations for PROJECT.
|
||||
# Copyright (C) 2020 ORGANIZATION
|
||||
# This file is distributed under the same license as the PROJECT project.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, 2020.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PROJECT VERSION\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"POT-Creation-Date: 2021-01-17 10:55+0100\n"
|
||||
"PO-Revision-Date: 2020-12-18 11:41+0100\n"
|
||||
"Last-Translator: \n"
|
||||
"Language: it\n"
|
||||
"Language-Team: it <LL@li.org>\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"
|
||||
|
||||
#: reports.py:158
|
||||
msgid "en"
|
||||
msgstr "it"
|
||||
|
||||
#: reports.py:159
|
||||
msgid "Success"
|
||||
msgstr "Successo"
|
||||
|
||||
#: reports.py:160
|
||||
msgid "Failure"
|
||||
msgstr "Errore"
|
||||
|
||||
#: reports.py:161
|
||||
msgid "Tests passed"
|
||||
msgstr "Test superati"
|
||||
|
||||
#: reports.py:162
|
||||
msgid "Duration"
|
||||
msgstr "Durata"
|
||||
|
||||
#: reports.py:163
|
||||
msgid "OpenBIM auditing is a feature of"
|
||||
msgstr "L'auditing OpenBIM è una caratteristica di"
|
||||
|
||||
#: reports.py:164
|
||||
msgid "and"
|
||||
msgstr "e"
|
||||
|
||||
#: features/steps/attributes_eleclasses_methods.py:26
|
||||
msgid "All {elemcount} elements in the file are {ifc_class}."
|
||||
msgstr "Tutti {elemcount} elementi nel file sono classificati {ifc_class}"
|
||||
|
||||
#: features/steps/attributes_eleclasses_methods.py:34
|
||||
msgid "{falsecount} of {elemcount} element are {ifc_class} elements: {falseelems}"
|
||||
msgstr ""
|
||||
"{falsecount} di {elemcount} elementi sono elementi {ifc_class}: "
|
||||
"{falseelems}"
|
||||
|
||||
#: features/steps/attributes_eleclasses_methods.py:43
|
||||
#: features/steps/utils.py:158
|
||||
msgid "Error in falsecount, something went wrong."
|
||||
msgstr "Errore in falsecount, qualcosa è andato storto."
|
||||
|
||||
#: features/steps/attributes_eleclasses_methods.py:84
|
||||
msgid ""
|
||||
"For all {elemcount} {ifc_class} elements at least one of these class "
|
||||
"attributes {parameter} has no value."
|
||||
msgstr ""
|
||||
"Per tutti {elemcount} elementi {ifc_class} almeno uno di questa "
|
||||
"classedegli attributi {parameter} non contiene alcun valore"
|
||||
|
||||
#: features/steps/attributes_eleclasses_methods.py:85
|
||||
msgid ""
|
||||
"For the following {falsecount} out of {elemcount} {ifc_class} elements at"
|
||||
" least one of these class attributes {parameter} has no value: "
|
||||
"{falseelems}"
|
||||
msgstr ""
|
||||
"Per i seguenti {falsecount} di {elemcount} elementi {ifc_class}almeno uno"
|
||||
" degli attributi {parameter} non contiene alcun valore"
|
||||
|
||||
#: features/steps/attributes_eleclasses_methods.py:86
|
||||
#: features/steps/attributes_eleclasses_methods.py:112
|
||||
#: features/steps/attributes_eleclasses_methods.py:139
|
||||
#: features/steps/attributes_psets_methods.py:33
|
||||
#: features/steps/geometric_detail_methods.py:56
|
||||
#: features/steps/geometric_detail_methods.py:134
|
||||
msgid "There are no {ifc_class} elements in the IFC file."
|
||||
msgstr "Il file IFC non contiene elementi della classe {ifc_class}."
|
||||
|
||||
#: features/steps/attributes_eleclasses_methods.py:110
|
||||
msgid "The name of all {elemcount} {elemcount} elements is not set."
|
||||
msgstr "Il nome di {elemcount} {elemcount} elementi non è impostato"
|
||||
|
||||
#: features/steps/attributes_eleclasses_methods.py:111
|
||||
msgid ""
|
||||
"The name of {falsecount} out of {elemcount} {ifc_class} elements is not "
|
||||
"set: {falseelems}"
|
||||
msgstr "Il nome di {falsecount} su {elemcount} elementi {ifc_class} non èimpostato"
|
||||
|
||||
#: features/steps/attributes_eleclasses_methods.py:137
|
||||
msgid "The description of all {elemcount} {elemcount} elements is not set."
|
||||
msgstr "La descrizione di tutti {elemcount} {elemcount} elementi non èimpostato "
|
||||
|
||||
#: features/steps/attributes_eleclasses_methods.py:138
|
||||
msgid ""
|
||||
"The description of {falsecount} out of {elemcount} {ifc_class} elements "
|
||||
"is not set: {falseelems}"
|
||||
msgstr ""
|
||||
"la descrizione di {falsecount} su {elemcount} elementi {ifc_class} non "
|
||||
"èimpostato"
|
||||
|
||||
#: features/steps/attributes_psets_methods.py:31
|
||||
msgid ""
|
||||
"All {elemcount} {ifc_class} elements are missing the property {parameter}"
|
||||
" in the pset."
|
||||
msgstr ""
|
||||
"A {elemcount} gli elementi {ifc_class} manca la proprietà "
|
||||
"{parameter}nello pset."
|
||||
|
||||
#: features/steps/attributes_psets_methods.py:32
|
||||
msgid ""
|
||||
"The following {falsecount} of {elemcount} {ifc_class} elements are "
|
||||
"missing the property {parameter} in the pset: {falseelems}"
|
||||
msgstr ""
|
||||
"Ai seguenti {falsecount} di {elemcount} elementi {ifc_class} manca la "
|
||||
"proprietà{parameter} nello pset: {falseelems}"
|
||||
|
||||
#: features/steps/geometric_detail_methods.py:54
|
||||
msgid "All {elemcount} {ifc_class} elements are not a {parameter} representation."
|
||||
msgstr "Tutti {elemcount} elementi {ifc_class} non rappresentano {parameter}"
|
||||
|
||||
#: features/steps/geometric_detail_methods.py:55
|
||||
msgid ""
|
||||
"The following {falsecount} of {elemcount} {ifc_class} elements are not a "
|
||||
"{parameter} representation: {falseelems}"
|
||||
msgstr ""
|
||||
"I seguenti {falsecount} di {elemcount} elementi {ifc_class} non "
|
||||
"rappresentano{parameter}: {falseelems}"
|
||||
|
||||
#: features/steps/geometric_detail_methods.py:132
|
||||
msgid "The geometry of all {elemcount} {ifc_class} elements have errors."
|
||||
msgstr "La geometria di tutti {elemcount} elementi {ifc_class} contiene errori"
|
||||
|
||||
#: features/steps/geometric_detail_methods.py:133
|
||||
msgid ""
|
||||
"The geometry of {falsecount} out of all {elemcount} {ifc_class} elements "
|
||||
"have errors: {falseelems}"
|
||||
msgstr ""
|
||||
"La geometria di {falsecount} su {elemcount} elementi {ifc_class} "
|
||||
"contieneerrori: {falseelems}"
|
||||
|
||||
#: features/steps/ifcdata_methods.py:9
|
||||
msgid "The IFC {} file could not be loaded"
|
||||
msgstr ""
|
||||
|
||||
#: features/steps/ifcdata_methods.py:17
|
||||
msgid "We expected a schema of {} but instead got {}"
|
||||
msgstr "Ci aspettavamo uno schema di {} ma invece abbiamo ottenuto {}"
|
||||
|
||||
#~ msgid "The geometry of all {} {} elements have errors."
|
||||
#~ msgstr "La geometria di tutti gli elementi {} {} contiene errori."
|
||||
|
||||
#~ msgid "The geometry of {} out of all {} {} elements have errors: {}"
|
||||
#~ msgstr "La geometria di {} su {} {} elementi contiene errori."
|
||||
|
||||
#~ msgid "There are no {} elements in the IFC file."
|
||||
#~ msgstr "Non ci sono {} elementi nel file IFC."
|
||||
|
||||
#~ msgid "All {} {} elements are not a IfcFacetedBrep representation."
|
||||
#~ msgstr "Tutti gli elementi {} {} non sono una rappresentazioneIfcFacetedBrep."
|
||||
|
||||
#~ msgid ""
|
||||
#~ "The following {} of {} {} elements"
|
||||
#~ " are not a IfcFacetedBrep representation:"
|
||||
#~ " {}"
|
||||
#~ msgstr ""
|
||||
#~ "I seguenti {} di {} {} elementi"
|
||||
#~ " non sono una rappresentazioneIfcFacetedBrep. "
|
||||
#~ "{}"
|
||||
|
||||
#~ msgid ""
|
||||
#~ "All {elemcount} {ifc_class} elements are "
|
||||
#~ "not a IfcFacetedBrep representation."
|
||||
#~ msgstr ""
|
||||
#~ "Tutti {elemcount} elementi {ifc_class} non"
|
||||
#~ " sono una rappresentazioneIfcFacetedBrep."
|
||||
|
||||
#~ msgid ""
|
||||
#~ "The following {falsecount} of {elemcount} "
|
||||
#~ "{ifc_class} elements are not a "
|
||||
#~ "IfcFacetedBrep representation: {falseelems}"
|
||||
#~ msgstr ""
|
||||
#~ "I seguenti {falsecount} su {elemcount} "
|
||||
#~ "elementi {ifc_class} non sonouna "
|
||||
#~ "rappresentazione IfcFacetedBrep."
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
# Translations template for PROJECT.
|
||||
# Copyright (C) 2021 ORGANIZATION
|
||||
# This file is distributed under the same license as the PROJECT project.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, 2021.
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PROJECT VERSION\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"POT-Creation-Date: 2021-01-17 10:55+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"
|
||||
"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"
|
||||
|
||||
#: reports.py:158
|
||||
msgid "en"
|
||||
msgstr ""
|
||||
|
||||
#: reports.py:159
|
||||
msgid "Success"
|
||||
msgstr ""
|
||||
|
||||
#: reports.py:160
|
||||
msgid "Failure"
|
||||
msgstr ""
|
||||
|
||||
#: reports.py:161
|
||||
msgid "Tests passed"
|
||||
msgstr ""
|
||||
|
||||
#: reports.py:162
|
||||
msgid "Duration"
|
||||
msgstr ""
|
||||
|
||||
#: reports.py:163
|
||||
msgid "OpenBIM auditing is a feature of"
|
||||
msgstr ""
|
||||
|
||||
#: reports.py:164
|
||||
msgid "and"
|
||||
msgstr ""
|
||||
|
||||
#: features/steps/attributes_eleclasses_methods.py:26
|
||||
msgid "All {elemcount} elements in the file are {ifc_class}."
|
||||
msgstr ""
|
||||
|
||||
#: features/steps/attributes_eleclasses_methods.py:34
|
||||
msgid "{falsecount} of {elemcount} element are {ifc_class} elements: {falseelems}"
|
||||
msgstr ""
|
||||
|
||||
#: features/steps/attributes_eleclasses_methods.py:43
|
||||
#: features/steps/utils.py:158
|
||||
msgid "Error in falsecount, something went wrong."
|
||||
msgstr ""
|
||||
|
||||
#: features/steps/attributes_eleclasses_methods.py:84
|
||||
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:85
|
||||
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:86
|
||||
#: features/steps/attributes_eleclasses_methods.py:112
|
||||
#: features/steps/attributes_eleclasses_methods.py:139
|
||||
#: features/steps/attributes_psets_methods.py:33
|
||||
#: features/steps/geometric_detail_methods.py:56
|
||||
#: features/steps/geometric_detail_methods.py:134
|
||||
msgid "There are no {ifc_class} elements in the IFC file."
|
||||
msgstr ""
|
||||
|
||||
#: features/steps/attributes_eleclasses_methods.py:110
|
||||
msgid "The name of all {elemcount} {elemcount} elements is not set."
|
||||
msgstr ""
|
||||
|
||||
#: features/steps/attributes_eleclasses_methods.py:111
|
||||
msgid ""
|
||||
"The name of {falsecount} out of {elemcount} {ifc_class} elements is not "
|
||||
"set: {falseelems}"
|
||||
msgstr ""
|
||||
|
||||
#: features/steps/attributes_eleclasses_methods.py:137
|
||||
msgid "The description of all {elemcount} {elemcount} elements is not set."
|
||||
msgstr ""
|
||||
|
||||
#: features/steps/attributes_eleclasses_methods.py:138
|
||||
msgid ""
|
||||
"The description of {falsecount} out of {elemcount} {ifc_class} elements "
|
||||
"is not set: {falseelems}"
|
||||
msgstr ""
|
||||
|
||||
#: features/steps/attributes_psets_methods.py:31
|
||||
msgid ""
|
||||
"All {elemcount} {ifc_class} elements are missing the property {parameter}"
|
||||
" in the pset."
|
||||
msgstr ""
|
||||
|
||||
#: features/steps/attributes_psets_methods.py:32
|
||||
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:54
|
||||
msgid "All {elemcount} {ifc_class} elements are not a {parameter} representation."
|
||||
msgstr ""
|
||||
|
||||
#: features/steps/geometric_detail_methods.py:55
|
||||
msgid ""
|
||||
"The following {falsecount} of {elemcount} {ifc_class} elements are not a "
|
||||
"{parameter} representation: {falseelems}"
|
||||
msgstr ""
|
||||
|
||||
#: features/steps/geometric_detail_methods.py:132
|
||||
msgid "The geometry of all {elemcount} {ifc_class} elements have errors."
|
||||
msgstr ""
|
||||
|
||||
#: features/steps/geometric_detail_methods.py:133
|
||||
msgid ""
|
||||
"The geometry of {falsecount} out of all {elemcount} {ifc_class} elements "
|
||||
"have errors: {falseelems}"
|
||||
msgstr ""
|
||||
|
||||
#: features/steps/ifcdata_methods.py:9
|
||||
msgid "The IFC {} file could not be loaded"
|
||||
msgstr ""
|
||||
|
||||
#: features/steps/ifcdata_methods.py:17
|
||||
msgid "We expected a schema of {} but instead got {}"
|
||||
msgstr ""
|
||||
|
||||
@@ -0,0 +1,196 @@
|
||||
# Dutch translations for Bimtester.
|
||||
# Copyright (C) 2021 ORGANIZATION
|
||||
# This file is distributed under the same license as the PROJECT project.
|
||||
# Marcel Plomp , 2021.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PROJECT VERSIE\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRES\n"
|
||||
"POT-Creation-Date: 2021-01-17 10:55+0100\n"
|
||||
"PO-Revision-Date: 2020-12-18 11:41+0100\n"
|
||||
"Last-Translator: \n"
|
||||
"Language: nl\n"
|
||||
"Language-Team: nl <LL@li.org>\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"
|
||||
|
||||
#: reports.py:158
|
||||
msgid "en"
|
||||
msgstr "nl"
|
||||
|
||||
#: reports.py:159
|
||||
msgid "Success"
|
||||
msgstr "Succes"
|
||||
|
||||
#: reports.py:160
|
||||
msgid "Failure"
|
||||
msgstr "Fout"
|
||||
|
||||
#: reports.py:161
|
||||
msgid "Tests passed"
|
||||
msgstr "Tests geslaagd"
|
||||
|
||||
#: reports.py:162
|
||||
msgid "Duration"
|
||||
msgstr "Tijdsduur"
|
||||
|
||||
#: reports.py:163
|
||||
msgid "OpenBIM auditing is a feature of"
|
||||
msgstr "OpenBIM-auditing is een kenmerk van"
|
||||
|
||||
#: reports.py:164
|
||||
msgid "and"
|
||||
msgstr "en"
|
||||
|
||||
#: features/steps/attributes_eleclasses_methods.py:26
|
||||
msgid "All {elemcount} elements in the file are {ifc_class}."
|
||||
msgstr "Alle {elemcount} de elementen in het bestand zijn {ifc_class}"
|
||||
|
||||
#: features/steps/attributes_eleclasses_methods.py:34
|
||||
msgid "{falsecount} of {elemcount} element are {ifc_class} elements: {falseelems}"
|
||||
msgstr ""
|
||||
"{falsecount} van de {elemcount} elementen zijn {ifc_class} elementen: "
|
||||
"{falseelems}"
|
||||
|
||||
#: features/steps/attributes_eleclasses_methods.py:43
|
||||
#: features/steps/utils.py:158
|
||||
msgid "Error in falsecount, something went wrong."
|
||||
msgstr "Fout in de telling, er is iets misgegaan."
|
||||
|
||||
#: features/steps/attributes_eleclasses_methods.py:84
|
||||
msgid ""
|
||||
"For all {elemcount} {ifc_class} elements at least one of these class "
|
||||
"attributes {parameter} has no value."
|
||||
msgstr ""
|
||||
"Voor alle {elemcount} {ifc_class} elementen heeft ten minste één van deze"
|
||||
" class attributen {parameter} heeft geen waarde."
|
||||
|
||||
#: features/steps/attributes_eleclasses_methods.py:85
|
||||
msgid ""
|
||||
"For the following {falsecount} out of {elemcount} {ifc_class} elements at"
|
||||
" least one of these class attributes {parameter} has no value: "
|
||||
"{falseelems}"
|
||||
msgstr ""
|
||||
"Van de {falsecount} uit {elemcount} {ifc_class} elementen op ten minste "
|
||||
"een van deze class-attributen {parameter} heeft geen waarde:{falseelems}"
|
||||
|
||||
#: features/steps/attributes_eleclasses_methods.py:86
|
||||
#: features/steps/attributes_eleclasses_methods.py:112
|
||||
#: features/steps/attributes_eleclasses_methods.py:139
|
||||
#: features/steps/attributes_psets_methods.py:33
|
||||
#: features/steps/geometric_detail_methods.py:56
|
||||
#: features/steps/geometric_detail_methods.py:134
|
||||
msgid "There are no {ifc_class} elements in the IFC file."
|
||||
msgstr "Er zijn geen {ifc_class} elementen in het IFC-bestand."
|
||||
|
||||
#: features/steps/attributes_eleclasses_methods.py:110
|
||||
msgid "The name of all {elemcount} {elemcount} elements is not set."
|
||||
msgstr "De naam van alle {elemcount} {elemcount} elementen is niet ingesteld."
|
||||
|
||||
#: features/steps/attributes_eleclasses_methods.py:111
|
||||
msgid ""
|
||||
"The name of {falsecount} out of {elemcount} {ifc_class} elements is not "
|
||||
"set: {falseelems}"
|
||||
msgstr ""
|
||||
"De naam van {falsecount} van de {elemcount} {ifc_class} elementen is "
|
||||
"nietset: {falseelems}"
|
||||
|
||||
#: features/steps/attributes_eleclasses_methods.py:137
|
||||
msgid "The description of all {elemcount} {elemcount} elements is not set."
|
||||
msgstr ""
|
||||
"De beschrijving van alle {elemcount} {elemcount} elementen is niet "
|
||||
"ingesteld."
|
||||
|
||||
#: features/steps/attributes_eleclasses_methods.py:138
|
||||
msgid ""
|
||||
"The description of {falsecount} out of {elemcount} {ifc_class} elements "
|
||||
"is not set: {falseelems}"
|
||||
msgstr ""
|
||||
"De beschrijving van {falsecount} uit {elemcount} {ifc_class} elementen is"
|
||||
" niet ingesteld: {falseelems}"
|
||||
|
||||
#: features/steps/attributes_psets_methods.py:31
|
||||
msgid ""
|
||||
"All {elemcount} {ifc_class} elements are missing the property {parameter}"
|
||||
" in the pset."
|
||||
msgstr ""
|
||||
"Alle {elemcount} {ifc_class} elementen missen de eigenschap {parameter} "
|
||||
"in de pset."
|
||||
|
||||
#: features/steps/attributes_psets_methods.py:32
|
||||
msgid ""
|
||||
"The following {falsecount} of {elemcount} {ifc_class} elements are "
|
||||
"missing the property {parameter} in the pset: {falseelems}"
|
||||
msgstr ""
|
||||
"De volgende {falsecount} van {elemcount} {ifc_class} elementen zijn "
|
||||
"ontbreekt de eigenschap {parameter} in de pset: {falseelems}"
|
||||
|
||||
#: features/steps/geometric_detail_methods.py:54
|
||||
msgid "All {elemcount} {ifc_class} elements are not a {parameter} representation."
|
||||
msgstr ""
|
||||
"Alle {elemcount} {ifc_class} elementen zijn geen {parameter} "
|
||||
"representatie."
|
||||
|
||||
#: features/steps/geometric_detail_methods.py:55
|
||||
msgid ""
|
||||
"The following {falsecount} of {elemcount} {ifc_class} elements are not a "
|
||||
"{parameter} representation: {falseelems}"
|
||||
msgstr ""
|
||||
"De volgende {falsecount} van {elemcount} {ifc_class} -elementen zijn geen"
|
||||
" {parameter} representatie: {falseelems}"
|
||||
|
||||
#: features/steps/geometric_detail_methods.py:132
|
||||
msgid "The geometry of all {elemcount} {ifc_class} elements have errors."
|
||||
msgstr ""
|
||||
"De geometrie van alle {elemcount} de {ifc_class} elementen bevatten "
|
||||
"fouten."
|
||||
|
||||
#: features/steps/geometric_detail_methods.py:133
|
||||
msgid ""
|
||||
"The geometry of {falsecount} out of all {elemcount} {ifc_class} elements "
|
||||
"have errors: {falseelems}"
|
||||
msgstr ""
|
||||
"De geometrie van {falsecount} van alle {elemcount} {ifc_class} elementen "
|
||||
"hebben fouten: {falseelems}"
|
||||
|
||||
#: features/steps/ifcdata_methods.py:9
|
||||
msgid "The IFC {} file could not be loaded"
|
||||
msgstr ""
|
||||
|
||||
#: features/steps/ifcdata_methods.py:17
|
||||
msgid "We expected a schema of {} but instead got {}"
|
||||
msgstr "We verwachtten een schema van {} maar kregen in plaats daarvan {}"
|
||||
|
||||
#~ 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 ""
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
import datetime
|
||||
import json
|
||||
import os
|
||||
import pystache
|
||||
from bimtester.lang import _
|
||||
|
||||
|
||||
class ReportGenerator:
|
||||
def __init__(self):
|
||||
try:
|
||||
# PyInstaller creates a temp folder and stores path in _MEIPASS
|
||||
self.base_path = sys._MEIPASS
|
||||
except Exception:
|
||||
self.base_path = os.path.dirname(os.path.realpath(__file__))
|
||||
|
||||
def generate(self, report_json, output_file):
|
||||
print("# Generating HTML reports.")
|
||||
|
||||
report = json.loads(open(report_json).read())
|
||||
for feature in report:
|
||||
self.generate_feature_report(feature, output_file)
|
||||
|
||||
def generate_feature_report(self, feature, output_file):
|
||||
file_name = os.path.basename(feature["location"]).split(":")[0]
|
||||
data = {
|
||||
"file_name": file_name,
|
||||
"time": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
||||
"name": feature["name"],
|
||||
"description": feature["description"],
|
||||
"is_success": feature["status"] == "passed",
|
||||
"scenarios": [],
|
||||
}
|
||||
|
||||
if "elements" not in feature:
|
||||
if "status" in feature and feature["status"] == "skipped":
|
||||
print("Feature was skipped. No html report will be created.")
|
||||
else:
|
||||
print("For a unknown reason no html report well be created.")
|
||||
# happens if the feature file does not consist of any valid Scenario
|
||||
return
|
||||
|
||||
for scenario in feature["elements"]:
|
||||
scenario_data = self.process_scenario(scenario)
|
||||
if scenario_data:
|
||||
data["scenarios"].append(scenario_data)
|
||||
|
||||
data["total_passes"] = sum([s["total_passes"] for s in data["scenarios"]])
|
||||
data["total_steps"] = sum([s["total_steps"] for s in data["scenarios"]])
|
||||
data["pass_rate"] = round((data["total_passes"] / data["total_steps"]) * 100)
|
||||
|
||||
data.update(self.get_template_strings())
|
||||
|
||||
with open(output_file, "w", encoding="utf8") as out:
|
||||
with open(
|
||||
os.path.join(self.base_path, "resources", "reports", "template.html"), encoding="utf8"
|
||||
) as template:
|
||||
out.write(pystache.render(template.read(), data))
|
||||
|
||||
def process_scenario(self, scenario):
|
||||
if len(scenario["steps"]) == 0:
|
||||
print("Scenario '{}' in feature '{}' has no steps.".format(scenario["name"], feature["name"]))
|
||||
return
|
||||
|
||||
steps = []
|
||||
total_duration = 0
|
||||
|
||||
for step in scenario["steps"]:
|
||||
step_data = self.process_step(step)
|
||||
total_duration += step_data["time_raw"]
|
||||
steps.append(step_data)
|
||||
|
||||
total_passes = len([s for s in steps if s["is_success"] is True])
|
||||
total_steps = len(steps)
|
||||
pass_rate = round((total_passes / total_steps) * 100)
|
||||
|
||||
return {
|
||||
"name": scenario["name"],
|
||||
# on behave < 1.2.6 there is no 'status' thus report fails
|
||||
"is_success": scenario["status"] == "passed",
|
||||
"time": round(total_duration, 2),
|
||||
"steps": steps,
|
||||
"total_passes": total_passes,
|
||||
"total_steps": total_steps,
|
||||
"pass_rate": pass_rate,
|
||||
}
|
||||
|
||||
def process_step(self, step):
|
||||
name = step["name"]
|
||||
if "match" in step and "arguments" in step["match"]:
|
||||
for a in step["match"]["arguments"]:
|
||||
name = name.replace(a["value"], "<b>" + a["value"] + "</b>")
|
||||
if "result" not in step:
|
||||
step["result"] = {}
|
||||
step["result"]["status"] = "skipped"
|
||||
step["result"]["duration"] = 0
|
||||
step["result"][
|
||||
"error_message"
|
||||
] = "This requirement has been skipped due to a previous failing step."
|
||||
elif step["result"]["status"] == "undefined":
|
||||
step["result"] = {}
|
||||
step["result"]["status"] = "undefined"
|
||||
step["result"]["duration"] = 0
|
||||
step["result"]["error_message"] = "This requirement has not yet been specified."
|
||||
data = {
|
||||
"name": name,
|
||||
"time_raw": step["result"]["duration"],
|
||||
"time": round(step["result"]["duration"], 2),
|
||||
"is_success": step["result"]["status"] == "passed",
|
||||
"is_unspecified": step["result"]["status"] == "undefined",
|
||||
"is_skipped": step["result"]["status"] == "skipped",
|
||||
"error_message": None
|
||||
if step["result"]["status"] == "passed"
|
||||
else step["result"]["error_message"],
|
||||
}
|
||||
|
||||
# TODO: there is probably a better way of doing this
|
||||
if isinstance(data["error_message"], list):
|
||||
data["error_message"] = data["error_message"][1]
|
||||
return data
|
||||
|
||||
def get_template_strings(self):
|
||||
return {
|
||||
"_lang": _("en"),
|
||||
"_success": _("Success"),
|
||||
"_failure": _("Failure"),
|
||||
"_tests_passed": _("Tests passed"),
|
||||
"_duration": _("Duration"),
|
||||
"_auditing": _("OpenBIM auditing is a feature of"),
|
||||
"_and": _("and"),
|
||||
}
|
||||
|
Before Width: | Height: | Size: 104 KiB After Width: | Height: | Size: 104 KiB |
@@ -1,24 +1,26 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<html lang={{_lang}}>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="foobaro">
|
||||
<title>BlenderBIM</title>
|
||||
<title>{{name}}</title>
|
||||
<link href="https://fonts.googleapis.com/css?family=Comfortaa|Inconsolata|Open+Sans&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
body { font-family: 'Arial', sans-serif; padding: 40px; }
|
||||
body { font-family: 'Arial', sans-serif; padding: 10px 40px; }
|
||||
span.time { color: #999; font-style: italic; float: right; }
|
||||
span.step-time { float: right; color: #555; font-size: 0.8em; font-style: italic; }
|
||||
span.success { background-color: #97cc64; padding: 5px; border-radius: 5px; color: #FFF; font-weight: bold; }
|
||||
span.failure { background-color: #fb5a3e; padding: 5px; border-radius: 5px; color: #FFF; font-weight: bold; }
|
||||
p.failure { background-color: #fb5a3e; padding: 5px; border-radius: 5px; color: #fff; }
|
||||
p.unspecified { background-color: #994f00; padding: 5px; border-radius: 5px; color: #fff; }
|
||||
p.skipped { background-color: #8b8d8f; padding: 5px; border-radius: 5px; color: #fff; }
|
||||
p.description { background-color: #eee; border-radius: 5px; padding: 20px; margin-left: auto; margin-right: auto; display: inline-block; font-weight: bold;}
|
||||
li { padding: 10px; font-family: monospace; }
|
||||
li.success { background-color: #b6cca1; color: #333; }
|
||||
li.failure { background-color: #fbb4a8; color: #900; }
|
||||
li.unspecified { background-color: #ffd37f; color: #a30; }
|
||||
li.skipped { background-color: #f5f5f5; color: #333; }
|
||||
li p { margin-bottom: 0px; }
|
||||
footer { color: #999; font-size: 0.8em; }
|
||||
header { text-align: center; }
|
||||
@@ -30,8 +32,8 @@
|
||||
<h1>{{name}}</h1>
|
||||
<p><strong>{{time}} {{file_name}}</strong></p>
|
||||
<hr>
|
||||
<span class="{{#is_success}}success{{/is_success}}{{^is_success}}failure{{/is_success}}">{{#is_success}}Success{{/is_success}}{{^is_success}}Failure{{/is_success}}</span>
|
||||
Tests passed: <strong>{{total_passes}} / {{total_steps}}</strong> ({{pass_rate}}%)
|
||||
<span class="{{#is_success}}success{{/is_success}}{{^is_success}}failure{{/is_success}}">{{#is_success}}{{_success}}{{/is_success}}{{^is_success}}{{_failure}}{{/is_success}}</span>
|
||||
{{_tests_passed}}: <strong>{{total_passes}} / {{total_steps}}</strong> ({{pass_rate}}%)
|
||||
<br />
|
||||
<p class="description">
|
||||
{{#description}}
|
||||
@@ -44,19 +46,19 @@
|
||||
<section>
|
||||
<h2>{{name}}</h2>
|
||||
<p>
|
||||
<span class="{{#is_success}}success{{/is_success}}{{^is_success}}failure{{/is_success}}">{{#is_success}}Success{{/is_success}}{{^is_success}}Failure{{/is_success}}</span>
|
||||
Tests passed: <strong>{{total_passes}} / {{total_steps}}</strong> ({{pass_rate}}%)
|
||||
<span class="{{#is_success}}success{{/is_success}}{{^is_success}}failure{{/is_success}}">{{#is_success}}{{_success}}{{/is_success}}{{^is_success}}{{_failure}}{{/is_success}}</span>
|
||||
{{_tests_passed}}: <strong>{{total_passes}} / {{total_steps}}</strong> ({{pass_rate}}%)
|
||||
<span class="time">
|
||||
Duration: {{time}}s
|
||||
{{_duration}}: {{time}}s
|
||||
</span>
|
||||
</p>
|
||||
<ol>
|
||||
{{#steps}}
|
||||
<li class="{{#is_success}}success{{/is_success}}{{^is_success}}failure{{/is_success}}{{#is_unspecified}} unspecified{{/is_unspecified}}">
|
||||
<li class="{{#is_success}}success{{/is_success}}{{^is_success}}failure{{/is_success}}{{#is_unspecified}} unspecified{{/is_unspecified}}{{#is_skipped}} skipped{{/is_skipped}}">
|
||||
{{{name}}}
|
||||
<span class="step-time">{{time}}s</span>
|
||||
{{^is_success}}
|
||||
<p class="failure{{#is_unspecified}} unspecified{{/is_unspecified}}">
|
||||
<p class="failure{{#is_unspecified}} unspecified{{/is_unspecified}}{{#is_skipped}} skipped{{/is_skipped}}">
|
||||
{{#error_message}}
|
||||
{{.}}<br />
|
||||
{{/error_message}}
|
||||
@@ -70,7 +72,7 @@
|
||||
<hr>
|
||||
<footer>
|
||||
<p>
|
||||
OpenBIM auditing is a feature of <a href="https://blenderbim.org/">BlenderBIM</a> and <a href="http://ifcopenshell.org/">IfcOpenShell</a>.
|
||||
{{_auditing}} <a href="https://blenderbim.org/">BlenderBIM</a> {{_and}} <a href="http://ifcopenshell.org/">IfcOpenShell</a>.
|
||||
</p>
|
||||
</footer>
|
||||
</body>
|
||||
@@ -0,0 +1,62 @@
|
||||
import os
|
||||
import sys
|
||||
import shutil
|
||||
import tempfile
|
||||
import ifcopenshell
|
||||
try:
|
||||
import ifcopenshell.express
|
||||
except:
|
||||
pass # They are using an old version of IfcOpenShell. Gracefully degrade for now.
|
||||
import behave.formatter.pretty # Needed for pyinstaller to package it
|
||||
from bimtester.ifc import IfcStore
|
||||
from distutils.dir_util import copy_tree
|
||||
from behave.__main__ import main as behave_main
|
||||
|
||||
|
||||
class TestRunner:
|
||||
def __init__(self, ifc_path, ifc=None):
|
||||
IfcStore.path = ifc_path
|
||||
IfcStore.file = ifc if ifc else ifcopenshell.open(ifc_path)
|
||||
|
||||
try:
|
||||
# PyInstaller creates a temp folder and stores path in _MEIPASS
|
||||
self.base_path = sys._MEIPASS
|
||||
except Exception:
|
||||
self.base_path = os.path.dirname(os.path.realpath(__file__))
|
||||
|
||||
self.locale_path = os.path.join(self.base_path, "locale")
|
||||
|
||||
def run(self, args):
|
||||
if args["schema_file"]:
|
||||
schema = ifcopenshell.express.parse(args["schema_file"])
|
||||
ifcopenshell.register_schema(args["schema_name"])
|
||||
|
||||
tmpdir = tempfile.mkdtemp()
|
||||
features_path = os.path.join(tmpdir, "features")
|
||||
steps_path = os.path.join(features_path, "steps")
|
||||
report_json = os.path.join(tmpdir, "report.json")
|
||||
shutil.copytree(os.path.join(self.base_path, "features"), features_path)
|
||||
shutil.copy(args["feature"], features_path)
|
||||
if args["steps"]:
|
||||
if os.path.isfile(args["steps"]):
|
||||
shutil.copy(args["steps"], steps_path)
|
||||
elif os.path.isdir(args["steps"]):
|
||||
copy_tree(args["steps"], steps_path)
|
||||
behave_main(self.get_behave_args(args, features_path, report_json))
|
||||
return report_json
|
||||
|
||||
def get_behave_args(self, args, features_path, report_json):
|
||||
behave_args = [features_path]
|
||||
behave_args.extend(["--define", "localedir={}".format(self.locale_path)])
|
||||
if args["advanced_arguments"]:
|
||||
behave_args.extend(args["advanced_arguments"].split())
|
||||
if args["ifc"]:
|
||||
behave_args.extend(["--define", "ifc={}".format(args["ifc"])])
|
||||
if args["path"]:
|
||||
behave_args.extend(["--define", "path={}".format(args["path"])])
|
||||
if args["lang"]:
|
||||
behave_args.extend(["--lang={}".format(args["lang"])])
|
||||
if not args["console"]:
|
||||
# https://github.com/behave/behave/issues/346
|
||||
behave_args.extend(["--no-capture", "--format", "json.pretty", "--outfile", report_json])
|
||||
return behave_args
|
||||
@@ -0,0 +1,105 @@
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.element
|
||||
from bimtester.lang import _
|
||||
|
||||
|
||||
def assert_guid(ifc, guid):
|
||||
try:
|
||||
return ifc.by_guid(guid)
|
||||
except:
|
||||
assert False, _("An element with the ID {} could not be found.").format(guid)
|
||||
|
||||
|
||||
def assert_number(number):
|
||||
try:
|
||||
return float(number)
|
||||
except ValueError:
|
||||
assert False, _("A number should be specified, not {}").format(number)
|
||||
|
||||
|
||||
def assert_type(element, ifc_class, is_exact=False):
|
||||
if is_exact:
|
||||
assert element.is_a() == ifc_class, _("The element {} is an {} instead of {}.").format(
|
||||
element, element.is_a(), ifc_class
|
||||
)
|
||||
else:
|
||||
assert element.is_a(ifc_class), _("The element {} is an {} instead of {}.").format(
|
||||
element, element.is_a(), ifc_class
|
||||
)
|
||||
|
||||
|
||||
def assert_attribute(element, name, value=None):
|
||||
if not hasattr(element, name):
|
||||
assert False, _("The element {} does not have the attribute {}").format(element, name)
|
||||
if not value:
|
||||
if getattr(element, name) is None:
|
||||
assert False, _("The element {} does not have a value for the attribute {}").format(element, name)
|
||||
return getattr(element, name)
|
||||
if value == "NULL":
|
||||
value = None
|
||||
actual_value = getattr(element, name)
|
||||
if isinstance(value, list) and actual_value:
|
||||
actual_value = list(actual_value)
|
||||
assert actual_value == value, _('We expected a value of "{}" but instead got "{}" for the element {}').format(
|
||||
value, actual_value, element
|
||||
)
|
||||
|
||||
|
||||
def assert_pset(element, pset_name, prop_name=None, value=None):
|
||||
if value == "NULL":
|
||||
value = None
|
||||
psets = ifcopenshell.util.element.get_psets(site)
|
||||
if pset_name not in psets:
|
||||
assert False, _("The element {} does not have a property set named {}").format(element, pset_name)
|
||||
if prop_name is None:
|
||||
return psets[pset_name]
|
||||
if prop_name not in psets[pset_name]:
|
||||
assert False, _('The element {} does not have a property named "{}" in the pset "{}"').format(
|
||||
element, prop_name, pset_name
|
||||
)
|
||||
if value is None:
|
||||
return psets[pset_name][prop_name]
|
||||
actual_value = psets[pset_name][prop_name]
|
||||
assert actual_value == value, _('We expected a value of "{}" but instead got "{}" for the element {}').format(
|
||||
value, actual_value, element
|
||||
)
|
||||
|
||||
|
||||
# TODO: what is this?
|
||||
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.")
|
||||
@@ -0,0 +1,29 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import os
|
||||
import argparse
|
||||
import bimtester.clean
|
||||
import bimtester.reports
|
||||
import bimtester.run
|
||||
|
||||
parser = argparse.ArgumentParser(description="Runs unit tests for BIM data")
|
||||
parser.add_argument("-a", "--action", type=str, help="Action to perform, from run/purge", default="run")
|
||||
parser.add_argument("--advanced-arguments", type=str, help="Specify arguments to Behave", default="")
|
||||
parser.add_argument("-c", "--console", action="store_true", help="Show results in the console")
|
||||
parser.add_argument("-f", "--feature", type=str, help="Specify a feature file to test", required=True)
|
||||
parser.add_argument("-i", "--ifc", type=str, help="Specify an IFC file to test", required=True)
|
||||
parser.add_argument("-p", "--path", type=str, help="Define a path for use in test steps that use relative paths")
|
||||
parser.add_argument("-r", "--report", type=str, help="Specify an output file for a HTML report")
|
||||
parser.add_argument("--steps", type=str, help="Specify a custom step definition Python file or directory")
|
||||
parser.add_argument("--schema-file", type=str, help="Path to a custom IFC schema, used with --schema-name")
|
||||
parser.add_argument("--schema-name", type=str, help="The name of a custom IFC schema, used with --schema-file")
|
||||
parser.add_argument("--lang", type=str, help="Specify a language e.g. en/de/fr/it", default="")
|
||||
|
||||
args = vars(parser.parse_args())
|
||||
|
||||
if args["action"] == "run":
|
||||
report_json = bimtester.run.TestRunner(args["ifc"]).run(args)
|
||||
if args["report"]:
|
||||
bimtester.reports.ReportGenerator().generate(report_json, args["report"])
|
||||
elif args["action"] == "purge":
|
||||
bimtester.clean.TestPurger().purge()
|
||||
@@ -0,0 +1,54 @@
|
||||
ISO-10303-21;
|
||||
HEADER;
|
||||
FILE_DESCRIPTION(('ViewDefinition [CoordinationView]'),'2;1');
|
||||
FILE_NAME('col.ifc','2021-01-11T05:16:43',('',''),(''),'IfcOpenShell 0.6.0b0','IfcOpenShell 0.6.0b0','');
|
||||
FILE_SCHEMA(('IFC2X3'));
|
||||
ENDSEC;
|
||||
DATA;
|
||||
#1=IFCPERSON($,$,'',$,$,$,$,$);
|
||||
#2=IFCORGANIZATION($,'',$,$,$);
|
||||
#3=IFCPERSONANDORGANIZATION(#1,#2,$);
|
||||
#4=IFCAPPLICATION(#2,'0.19 build 23652 (Git)','FreeCAD','118df2cf_ed21_438e_a41');
|
||||
#5=IFCOWNERHISTORY(#3,#4,$,.ADDED.,1610342203,#3,#4,1610342203);
|
||||
#6=IFCDIRECTION((1.,0.,0.));
|
||||
#7=IFCDIRECTION((0.,0.,1.));
|
||||
#8=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#9=IFCAXIS2PLACEMENT3D(#8,#7,#6);
|
||||
#10=IFCDIRECTION((0.,1.,0.));
|
||||
#12=IFCDIMENSIONALEXPONENTS(0,0,0,0,0,0,0);
|
||||
#13=IFCSIUNIT(*,.LENGTHUNIT.,$,.METRE.);
|
||||
#14=IFCSIUNIT(*,.AREAUNIT.,$,.SQUARE_METRE.);
|
||||
#15=IFCSIUNIT(*,.VOLUMEUNIT.,$,.CUBIC_METRE.);
|
||||
#16=IFCSIUNIT(*,.PLANEANGLEUNIT.,$,.RADIAN.);
|
||||
#17=IFCMEASUREWITHUNIT(IFCPLANEANGLEMEASURE(0.017453292519943295),#16);
|
||||
#18=IFCCONVERSIONBASEDUNIT(#12,.PLANEANGLEUNIT.,'DEGREE',#17);
|
||||
#19=IFCUNITASSIGNMENT((#13,#14,#15,#18));
|
||||
#20=IFCDIRECTION((0.,1.));
|
||||
#21=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Model',3,1.E-05,#9,#20);
|
||||
#22=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Body','Model',*,*,*,*,#21,$,.MODEL_VIEW.,$);
|
||||
#23=IFCPROJECT('2iAYrakL9FABNNwZfj$CbO',#5,'BIMTester Example 1 - IFC2X3',$,$,$,$,(#21),#19);
|
||||
#24=IFCDIRECTION((1.,0.));
|
||||
#25=IFCCARTESIANPOINT((0.,0.));
|
||||
#26=IFCAXIS2PLACEMENT2D(#25,#24);
|
||||
#27=IFCCIRCLEPROFILEDEF(.AREA.,$,#26,0.2);
|
||||
#28=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#29=IFCAXIS2PLACEMENT3D(#28,#7,#6);
|
||||
#30=IFCEXTRUDEDAREASOLID(#27,#29,#7,5.);
|
||||
#31=IFCCOLOURRGB($,1.,0.5,1.);
|
||||
#32=IFCSURFACESTYLERENDERING(#31,$,$,$,$,$,$,$,.FLAT.);
|
||||
#33=IFCSURFACESTYLE($,.BOTH.,(#32));
|
||||
#34=IFCPRESENTATIONSTYLEASSIGNMENT((#33));
|
||||
#35=IFCSTYLEDITEM(#30,(#34),$);
|
||||
#36=IFCLOCALPLACEMENT($,#9);
|
||||
#37=IFCSHAPEREPRESENTATION(#22,'Body','SweptSolid',(#30));
|
||||
#38=IFCPRODUCTDEFINITIONSHAPE($,$,(#37));
|
||||
#39=IFCBUILDINGELEMENTPROXY('3JNmm1CUH9H9P6lVsx1y3W',#5,'Structure','',$,#36,#38,$,.ELEMENT.);
|
||||
#40=IFCSITE('2PJ1ax1HL4SgHFFReEEwE$',#5,'Default Site','',$,$,$,$,.ELEMENT.,$,$,$,$,$);
|
||||
#41=IFCRELAGGREGATES('1J6GQExT511x6QRu5FmkD2',#5,'ProjectLink','',#23,(#40));
|
||||
#42=IFCBUILDING('1tIoXRzCXF3vuIMrF6RVcd',#5,'Default Building','',$,$,$,$,.ELEMENT.,$,$,$);
|
||||
#43=IFCRELAGGREGATES('2GkPanCgnAzQY_0xv8dnHH',#5,'SiteLink','',#40,(#42));
|
||||
#44=IFCBUILDINGSTOREY('1L8$GCIw116uw35vpyjSsO',#5,'Default Storey','',$,$,$,$,.ELEMENT.,$);
|
||||
#45=IFCRELAGGREGATES('1lB$$h00nFaPQb2gvlhRX$',#5,'DefaultStoreyLink','',#42,(#44));
|
||||
#46=IFCRELCONTAINEDINSPATIALSTRUCTURE('3dnpjDLD5DvuyUGcyHqRvU',#5,'UnassignedObjectsLink','',(#39),#44);
|
||||
ENDSEC;
|
||||
END-ISO-10303-21;
|
||||
@@ -0,0 +1,54 @@
|
||||
ISO-10303-21;
|
||||
HEADER;
|
||||
FILE_DESCRIPTION(('ViewDefinition [CoordinationView]'),'2;1');
|
||||
FILE_NAME('col.ifc','2021-01-11T05:12:19',('',''),(''),'IfcOpenShell 0.6.0b0','IfcOpenShell 0.6.0b0','');
|
||||
FILE_SCHEMA(('IFC4'));
|
||||
ENDSEC;
|
||||
DATA;
|
||||
#1=IFCPERSON($,$,'',$,$,$,$,$);
|
||||
#2=IFCORGANIZATION($,'',$,$,$);
|
||||
#3=IFCPERSONANDORGANIZATION(#1,#2,$);
|
||||
#4=IFCAPPLICATION(#2,'0.19 build 23652 (Git)','FreeCAD','118df2cf_ed21_438e_a41');
|
||||
#5=IFCOWNERHISTORY(#3,#4,$,.ADDED.,1610341939,#3,#4,1610341939);
|
||||
#6=IFCDIRECTION((1.,0.,0.));
|
||||
#7=IFCDIRECTION((0.,0.,1.));
|
||||
#8=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#9=IFCAXIS2PLACEMENT3D(#8,#7,#6);
|
||||
#10=IFCDIRECTION((0.,1.,0.));
|
||||
#12=IFCDIMENSIONALEXPONENTS(0,0,0,0,0,0,0);
|
||||
#13=IFCSIUNIT(*,.LENGTHUNIT.,$,.METRE.);
|
||||
#14=IFCSIUNIT(*,.AREAUNIT.,$,.SQUARE_METRE.);
|
||||
#15=IFCSIUNIT(*,.VOLUMEUNIT.,$,.CUBIC_METRE.);
|
||||
#16=IFCSIUNIT(*,.PLANEANGLEUNIT.,$,.RADIAN.);
|
||||
#17=IFCMEASUREWITHUNIT(IFCPLANEANGLEMEASURE(0.017453292519943295),#16);
|
||||
#18=IFCCONVERSIONBASEDUNIT(#12,.PLANEANGLEUNIT.,'DEGREE',#17);
|
||||
#19=IFCUNITASSIGNMENT((#13,#14,#15,#18));
|
||||
#20=IFCDIRECTION((0.,1.));
|
||||
#21=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Model',3,1.E-05,#9,#20);
|
||||
#22=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Body','Model',*,*,*,*,#21,$,.MODEL_VIEW.,$);
|
||||
#23=IFCPROJECT('2iAYrakL9FABNNwZfj$CbO',#5,'BIMTester Example 1 - IFC4',$,$,$,$,(#21),#19);
|
||||
#24=IFCDIRECTION((1.,0.));
|
||||
#25=IFCCARTESIANPOINT((0.,0.));
|
||||
#26=IFCAXIS2PLACEMENT2D(#25,#24);
|
||||
#27=IFCCIRCLEPROFILEDEF(.AREA.,$,#26,0.2);
|
||||
#28=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#29=IFCAXIS2PLACEMENT3D(#28,#7,#6);
|
||||
#30=IFCEXTRUDEDAREASOLID(#27,#29,#7,5.);
|
||||
#31=IFCCOLOURRGB($,1.,0.5,1.);
|
||||
#32=IFCSURFACESTYLERENDERING(#31,$,$,$,$,$,$,$,.FLAT.);
|
||||
#33=IFCSURFACESTYLE($,.BOTH.,(#32));
|
||||
#34=IFCPRESENTATIONSTYLEASSIGNMENT((#33));
|
||||
#35=IFCSTYLEDITEM(#30,(#34),$);
|
||||
#36=IFCLOCALPLACEMENT($,#9);
|
||||
#37=IFCSHAPEREPRESENTATION(#22,'Body','SweptSolid',(#30));
|
||||
#38=IFCPRODUCTDEFINITIONSHAPE($,$,(#37));
|
||||
#39=IFCBUILDINGELEMENTPROXY('3JNmm1CUH9H9P6lVsx1y3W',#5,'Structure','',$,#36,#38,$,.COMPLEX.);
|
||||
#40=IFCSITE('2PJ1ax1HL4SgHFFReEEwE$',#5,'Default Site','',$,$,$,$,.ELEMENT.,$,$,$,$,$);
|
||||
#41=IFCRELAGGREGATES('1J6GQExT511x6QRu5FmkD2',#5,'ProjectLink','',#23,(#40));
|
||||
#42=IFCBUILDING('1tIoXRzCXF3vuIMrF6RVcd',#5,'Default Building','',$,$,$,$,.ELEMENT.,$,$,$);
|
||||
#43=IFCRELAGGREGATES('2GkPanCgnAzQY_0xv8dnHH',#5,'SiteLink','',#40,(#42));
|
||||
#44=IFCBUILDINGSTOREY('1L8$GCIw116uw35vpyjSsO',#5,'Default Storey','',$,$,$,$,.ELEMENT.,$);
|
||||
#45=IFCRELAGGREGATES('1lB$$h00nFaPQb2gvlhRX$',#5,'DefaultStoreyLink','',#42,(#44));
|
||||
#46=IFCRELCONTAINEDINSPATIALSTRUCTURE('3dnpjDLD5DvuyUGcyHqRvU',#5,'UnassignedObjectsLink','',(#39),#44);
|
||||
ENDSEC;
|
||||
END-ISO-10303-21;
|
||||
@@ -0,0 +1,18 @@
|
||||
# language: de
|
||||
|
||||
Funktionalität: Basisdaten
|
||||
|
||||
Um BIM-Daten anzusehen
|
||||
Für alle beteiligten Akteure
|
||||
Wir brauchen eine IFC-Datei
|
||||
|
||||
|
||||
Szenario: Bereitstellen von IFC-Daten
|
||||
|
||||
* Die IFC-Datei wurde durch einen Startparameter zur Verfügung gestellt
|
||||
* Die IFC-Daten müssen das IFC2X3 Schema benutzen
|
||||
|
||||
|
||||
Szenario: Projektinformationen
|
||||
|
||||
* Der Name, die Abkürzung oder die Kurzkennung des Projektes ist "BIMTester Example 1 - IFC2X3"
|
||||
@@ -0,0 +1,18 @@
|
||||
# language: en
|
||||
|
||||
Feature: Base setup
|
||||
|
||||
In order to view the BIM data
|
||||
As any interested stakeholder
|
||||
We need an IFC file
|
||||
|
||||
|
||||
Scenario: Receiving a file
|
||||
|
||||
* The IFC file has been provided through an argument
|
||||
* IFC data must use the IFC2X3 schema
|
||||
|
||||
|
||||
Scenario: Project information
|
||||
|
||||
* The project name, code, or short identifier must be "BIMTester Example 1 - IFC2X3"
|
||||
@@ -0,0 +1,18 @@
|
||||
# language: fr
|
||||
|
||||
Fonctionnalité: Base setup
|
||||
|
||||
In order to view the BIM data
|
||||
As any interested stakeholder
|
||||
We need an IFC file
|
||||
|
||||
|
||||
Scénario: Recevoir e fichier
|
||||
|
||||
* The IFC file has been provided through an argument
|
||||
* Les données IFC doivent utiliser le schéma IFC2X3
|
||||
|
||||
|
||||
Scénario: Project information
|
||||
|
||||
* The project name, code, or short identifier must be "BIMTester Example 1 - IFC2X3"
|
||||
@@ -0,0 +1,18 @@
|
||||
# language: it
|
||||
|
||||
Funzionalità: Dati di base
|
||||
|
||||
Per poter consultare dati BIM
|
||||
a tutti gli attori partecipanti
|
||||
serve un file IFC
|
||||
|
||||
|
||||
Scenario: Preparare Dati IFC
|
||||
|
||||
* Il file IFC è stato fornito attraverso un argumento
|
||||
* I dati IFC devono seguire lo schema IFC2X3
|
||||
|
||||
|
||||
Scenario: Project information
|
||||
|
||||
* Il nome del progetto, codice o identificatore breve deve essere "BIMTester Example 1 - IFC2X3"
|
||||
@@ -0,0 +1,18 @@
|
||||
# language: nl
|
||||
|
||||
Functionaliteit: Basisgegevens
|
||||
|
||||
Om BIM-gegevens te bekijken
|
||||
Zoals elke geïnteresseerde stakeholder
|
||||
We hebben een IFC-bestand nodig
|
||||
|
||||
|
||||
Scenario: Bestand ontvangen
|
||||
|
||||
* The IFC file has been provided through an argument
|
||||
* IFC-gegevens moeten het IFC2X3 -schema gebruiken
|
||||
|
||||
|
||||
Scenario: Project informatie
|
||||
|
||||
* De projectnaam, code of korte ID moet "BIMTester Example 1 - IFC2X3"
|
||||
@@ -0,0 +1,144 @@
|
||||
from behave import step, given, when, then, use_step_matcher
|
||||
|
||||
use_step_matcher("parse")
|
||||
|
||||
|
||||
@step("There must be exactly {number} {ifc_class} element")
|
||||
@step("There must be exactly {number} {ifc_class} elements")
|
||||
def step_impl(context, number, ifc_class):
|
||||
num = len(IfcStore.file.by_type(ifc_class))
|
||||
assert num == int(number), "Could not find {} elements of {}. Found {} element(s).".format(number, ifc_class, num)
|
||||
|
||||
|
||||
@given("a set of specific related elements")
|
||||
def step_impl(context):
|
||||
model = getattr(context, "model", None)
|
||||
if not model:
|
||||
context.model = TableModel()
|
||||
for row in context.table:
|
||||
context.model.add_row(row["RelatedObjects"], row["RelatingGroup"])
|
||||
|
||||
|
||||
@given('a set of specific related elements taken from the file "{path_file}"')
|
||||
def step_impl(context, path_file):
|
||||
import csv
|
||||
import os
|
||||
|
||||
model = getattr(context, "model", None)
|
||||
if not model:
|
||||
context.model = TableModel()
|
||||
if context.config.userdata.get("path"):
|
||||
path_file = os.path.join(context.config.userdata.get("path"), path_file)
|
||||
if not os.path.exists(path_file):
|
||||
assert False, "File {} not found".format(path_file)
|
||||
with open(path_file, "r", encoding="utf-8-sig") as csvfile:
|
||||
reader = csv.DictReader(csvfile)
|
||||
for row in reader:
|
||||
context.model.add_row(row["RelatedObjects"], row["RelatingGroup"])
|
||||
|
||||
|
||||
@then("there must be exactly a number of {ifc_class} equals to the number of distinct row value")
|
||||
def step_impl(context, ifc_class):
|
||||
try:
|
||||
context.execute_steps(
|
||||
"""
|
||||
then There must be exactly {number} {ifc_class} elements
|
||||
""".format(
|
||||
ifc_class=ifc_class, number=context.model.get_count_distinct_values()
|
||||
)
|
||||
)
|
||||
except AssertionError as error:
|
||||
str_error = str(error)
|
||||
assert False, str_error[: str_error.find("Traceback")]
|
||||
assert True
|
||||
|
||||
|
||||
@then(
|
||||
"there is a relationship {ifc_class} with {left_attribute} and {right_attribute} between the two elements of each row"
|
||||
)
|
||||
def step_impl(context, ifc_class, left_attribute, right_attribute):
|
||||
rows = context.model.rows
|
||||
elements = IfcStore.file.by_type(ifc_class)
|
||||
errors = []
|
||||
for key, value in rows.items():
|
||||
found = False
|
||||
for element in elements:
|
||||
if (
|
||||
any(x.Name == key for x in getattr(element, left_attribute))
|
||||
and getattr(element, right_attribute).Name == value
|
||||
):
|
||||
found = True
|
||||
if not found:
|
||||
errors.append(f"The row ({key}, {value}) does not have the relationship.")
|
||||
assert not errors, "Errors occured:\n{}".format("\n".join(errors))
|
||||
|
||||
|
||||
use_step_matcher("re")
|
||||
|
||||
|
||||
@step("all IfcGroup must be linked to a type in the list (?P<linked_ifc_classes>.*)")
|
||||
def step_impl(context, linked_ifc_classes):
|
||||
groups = IfcStore.file.by_type("IfcGroup")
|
||||
errors = []
|
||||
for group in groups:
|
||||
if not hasattr(group, "IsGroupedBy"):
|
||||
errors.append(f'The element "{group.Name}" has no "IsGroupedBy" attribute.')
|
||||
else:
|
||||
for grouped_by in getattr(group, "IsGroupedBy"):
|
||||
if not hasattr(grouped_by, "RelatedObjects"):
|
||||
errors.append(f'The element "{grouped_by.Name}" has no "RelatedObjects" attribute.')
|
||||
else:
|
||||
for related_object in getattr(grouped_by, "RelatedObjects"):
|
||||
found = False
|
||||
for linked_ifc_class in linked_ifc_classes.split(","):
|
||||
if related_object.is_a(linked_ifc_class):
|
||||
found = True
|
||||
if not found:
|
||||
errors.append(
|
||||
f'The element "{related_object.Name}" does not have the right associated type.'
|
||||
)
|
||||
assert not errors, "Errors occured:\n{}".format("\n".join(errors))
|
||||
|
||||
|
||||
@then("there is an element of type (?P<ifc_types>.*) with a (?P<attribute_name>.*) attribute for each row key")
|
||||
def step_impl(context, ifc_types, attribute_name):
|
||||
check_if_element_exists_by_types_with_attribute_name(ifc_types, attribute_name, context.model.rows.keys())
|
||||
|
||||
|
||||
@then("there is an element of type (?P<ifc_types>.*) with a (?P<attribute_name>.*) attribute for each row value")
|
||||
def step_impl(context, ifc_types, attribute_name):
|
||||
values = set(context.model.rows.values())
|
||||
check_if_element_exists_by_types_with_attribute_name(ifc_types, attribute_name, values)
|
||||
|
||||
|
||||
def check_if_element_exists_by_types_with_attribute_name(ifc_types, attribute_name, attribute_values):
|
||||
errors = []
|
||||
|
||||
elements = []
|
||||
for ifc_type in ifc_types.split(","):
|
||||
elements += ifc.by_type(ifc_type.strip())
|
||||
|
||||
for attribute_value in attribute_values:
|
||||
found = False
|
||||
for element in elements:
|
||||
if hasattr(element, attribute_name) and getattr(element, attribute_name) == attribute_value:
|
||||
found = True
|
||||
if not found:
|
||||
errors.append(f'An element with {attribute_name} attribute "{attribute_value}" was not found.')
|
||||
assert not errors, "Errors occured:\n{}".format("\n".join(errors))
|
||||
|
||||
|
||||
class TableModel(object):
|
||||
"""This class represents a table of data."""
|
||||
|
||||
def __init__(self):
|
||||
self.rows = dict()
|
||||
|
||||
def add_row(self, related, relating):
|
||||
self.rows[related] = relating
|
||||
|
||||
def get_count(self):
|
||||
return len(self.rows)
|
||||
|
||||
def get_count_distinct_values(self):
|
||||
return len(set(self.rows.values()))
|
||||
@@ -0,0 +1,52 @@
|
||||
import gettext
|
||||
from behave import given
|
||||
from behave import step
|
||||
|
||||
from utils import IfcFile
|
||||
from bimtester.ifc import IfcStore
|
||||
from bimtester.lang import _
|
||||
|
||||
|
||||
@step("The IFC file must be exported by application full name {fullname}")
|
||||
def step_impl(context, fullname):
|
||||
|
||||
real_fullname = IfcStore.file.by_type("IfcApplication")[0].ApplicationFullName
|
||||
assert real_fullname == fullname, (
|
||||
"The IFC file was not exported by application full name {} "
|
||||
"instead it was exported by application full name {}".format(fullname, real_fullname)
|
||||
)
|
||||
|
||||
|
||||
@step("The IFC file must be exported by application identifier {identifier}")
|
||||
def step_impl(context, identifier):
|
||||
|
||||
real_identifier = IfcStore.file.by_type("IfcApplication")[0].ApplicationIdentifier
|
||||
assert (
|
||||
real_identifier == identifier
|
||||
), "The IFC file was not exported by application identifier {} " "instead it was exported by identifier {}".format(
|
||||
identifier, real_identifier
|
||||
)
|
||||
|
||||
|
||||
@step("The IFC file must be exported by the application version {version}")
|
||||
def step_impl(context, version):
|
||||
|
||||
real_version = IfcStore.file.by_type("IfcApplication")[0].Version
|
||||
assert (
|
||||
real_version == version
|
||||
), "The IFC file was not exported by application version {} " "instead it was exported by version {}".format(
|
||||
version, real_version
|
||||
)
|
||||
|
||||
|
||||
@step(
|
||||
"IFC data header must have a file description of {header_file_description} such as the new Allplan IFC exporter creates it"
|
||||
)
|
||||
def step_impl(context, header_file_description):
|
||||
|
||||
is_header_file_description = IfcStore.file.wrapped_data.header.file_description.description
|
||||
assert (
|
||||
str(is_header_file_description) == header_file_description
|
||||
), "The file was not exported by the new ifc exporter in Allplan. File description header: {}".format(
|
||||
is_header_file_description
|
||||
)
|
||||
@@ -0,0 +1,91 @@
|
||||
from behave import step
|
||||
|
||||
import attributes_eleclasses_methods as aem
|
||||
from utils import assert_elements
|
||||
from utils import IfcFile
|
||||
|
||||
|
||||
@step("There are no {ifc_class} elements")
|
||||
def step_impl(context, ifc_class):
|
||||
aem.no_eleclass(context, ifc_class)
|
||||
|
||||
|
||||
@step("There are no {ifc_class} elements because {reason}")
|
||||
def step_impl(context, ifc_class, reason):
|
||||
aem.no_eleclass(context, ifc_class)
|
||||
|
||||
|
||||
@step("All {ifc_class} elements class attributes have a value")
|
||||
def step_impl(context, ifc_class):
|
||||
aem.eleclass_have_class_attributes_with_a_value(context, ifc_class)
|
||||
|
||||
|
||||
@step("All {ifc_class} elements have a name given")
|
||||
def step_impl(context, ifc_class):
|
||||
aem.eleclass_has_name_with_a_value(context, ifc_class)
|
||||
|
||||
|
||||
@step("All {ifc_class} elements have a description given")
|
||||
def step_impl(context, ifc_class):
|
||||
aem.eleclass_has_description_with_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
|
||||
|
||||
|
||||
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,26 @@
|
||||
from behave import step
|
||||
|
||||
|
||||
@step("Es sind keine {ifc_class} Bauteile vorhanden")
|
||||
def step_impl(context, ifc_class):
|
||||
context.execute_steps(f"* There are no {ifc_class} elements")
|
||||
|
||||
|
||||
@step("Aus folgendem Grund gibt es keine {ifc_class} Bauteile: {reason}")
|
||||
def step_impl(context, ifc_class, reason):
|
||||
context.execute_steps(f"* There are no {ifc_class} elements because {reason}")
|
||||
|
||||
|
||||
@step("Alle {ifc_class} Bauteilklassenattribute haben einen Wert")
|
||||
def step_impl(context, ifc_class):
|
||||
context.execute_steps(f"* All {ifc_class} elements class attributes have a value")
|
||||
|
||||
|
||||
@step("Bei allen {ifc_class} Bauteile ist der Name angegeben")
|
||||
def step_impl(context, ifc_class):
|
||||
context.execute_steps(f"* All {ifc_class} elements have a name given")
|
||||
|
||||
|
||||
@step("Bei allen {ifc_class} Bauteile ist die Beschreibung angegeben")
|
||||
def step_impl(context, ifc_class):
|
||||
context.execute_steps(f"* All {ifc_class} elements have a description given")
|
||||
@@ -0,0 +1,140 @@
|
||||
import gettext # noqa
|
||||
|
||||
from utils import assert_elements
|
||||
from utils import IfcFile
|
||||
|
||||
|
||||
def no_eleclass(
|
||||
context, ifc_class
|
||||
):
|
||||
|
||||
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 eleclass_have_class_attributes_with_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 eleclass_has_name_with_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 eleclass_has_description_with_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,53 @@
|
||||
from behave import step
|
||||
|
||||
import attributes_psets_methods as apm
|
||||
from utils import assert_elements
|
||||
from utils import IfcFile
|
||||
from utils import switch_locale
|
||||
|
||||
|
||||
the_lang = "en"
|
||||
|
||||
|
||||
@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, the_lang)
|
||||
apm.eleclass_has_property_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,18 @@
|
||||
from behave import step
|
||||
|
||||
import attributes_psets_methods as apm
|
||||
from utils import switch_locale
|
||||
|
||||
|
||||
the_lang = "de"
|
||||
|
||||
|
||||
@step("An alle {ifc_class} Bauteile ist im PSet {pset} das Attribut {aproperty} angehängt")
|
||||
def step_impl(context, ifc_class, aproperty, pset):
|
||||
switch_locale(context.localedir, the_lang)
|
||||
apm.eleclass_has_property_in_pset(
|
||||
context,
|
||||
ifc_class,
|
||||
aproperty,
|
||||
pset
|
||||
)
|
||||
@@ -0,0 +1,21 @@
|
||||
from behave import step
|
||||
|
||||
import attributes_psets_methods as apm
|
||||
from utils import switch_locale
|
||||
|
||||
|
||||
the_lang = "fr"
|
||||
|
||||
|
||||
"""
|
||||
# TODO the next line needs translation
|
||||
@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, the_lang)
|
||||
apm.eleclass_has_property_in_pset(
|
||||
context,
|
||||
ifc_class,
|
||||
aproperty,
|
||||
pset
|
||||
)
|
||||
"""
|
||||
@@ -0,0 +1,36 @@
|
||||
import gettext # noqa
|
||||
|
||||
from utils import assert_elements
|
||||
from utils import IfcFile
|
||||
|
||||
|
||||
def eleclass_has_property_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
|
||||
@@ -0,0 +1,47 @@
|
||||
from behave import step
|
||||
|
||||
import geometric_detail_methods as gdm
|
||||
from utils import assert_elements
|
||||
from utils import IfcFile
|
||||
from utils import switch_locale
|
||||
|
||||
|
||||
the_lang = "en"
|
||||
|
||||
|
||||
@step("All elements must be under {number} polygons")
|
||||
def step_impl(context, number):
|
||||
number = int(number)
|
||||
errors = []
|
||||
for element in IfcFile.get().by_type("IfcElement"):
|
||||
if not element.Representation:
|
||||
continue
|
||||
total_polygons = 0
|
||||
tree = IfcFile.get().traverse(element.Representation)
|
||||
for e in tree:
|
||||
if e.is_a("IfcFace"):
|
||||
total_polygons += 1
|
||||
elif e.is_a("IfcPolygonalFaceSet"):
|
||||
total_polygons += len(e.Faces)
|
||||
elif e.is_a("IfcTriangulatedFaceSet"):
|
||||
total_polygons += len(e.CoordIndex)
|
||||
if total_polygons > number:
|
||||
errors.append((total_polygons, element))
|
||||
if errors:
|
||||
message = (
|
||||
"The following {} elements are over 500 polygons:\n"
|
||||
.format(len(errors))
|
||||
)
|
||||
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, the_lang)
|
||||
gdm.eleclass_has_geometric_representation_of_specific_class(
|
||||
context,
|
||||
ifc_class,
|
||||
representation_class
|
||||
)
|
||||
@@ -0,0 +1,17 @@
|
||||
from behave import step
|
||||
|
||||
import geometric_detail_methods as gdm
|
||||
from utils import switch_locale
|
||||
|
||||
|
||||
the_lang = "de"
|
||||
|
||||
|
||||
@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, the_lang)
|
||||
gdm.eleclass_has_geometric_representation_of_specific_class(
|
||||
context,
|
||||
ifc_class,
|
||||
representation_class
|
||||
)
|
||||
@@ -0,0 +1,58 @@
|
||||
import gettext # noqa
|
||||
from utils import assert_elements
|
||||
from utils import IfcFile
|
||||
|
||||
|
||||
def eleclass_has_geometric_representation_of_specific_class(
|
||||
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
|
||||
)
|
||||
@@ -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,5 +0,0 @@
|
||||
from behave.model import Scenario
|
||||
def before_all(context):
|
||||
userdata = context.config.userdata
|
||||
continue_after_failed = True
|
||||
Scenario.continue_after_failed_step = continue_after_failed
|
||||
@@ -1,66 +0,0 @@
|
||||
import json
|
||||
from behave import step
|
||||
from utils import IfcFile, assert_attribute, assert_type
|
||||
|
||||
|
||||
def get_classification(name):
|
||||
classifications = [c for c in IfcFile.get().by_type('IfcClassification') if c.Name == name]
|
||||
if len(classifications) != 1:
|
||||
assert False, f'The classification "{name}" was not found'
|
||||
return classifications[0]
|
||||
|
||||
|
||||
@step(u'The classification {name} must be used')
|
||||
def step_impl(context, name):
|
||||
get_classification(name)
|
||||
|
||||
|
||||
@step(u'The classification {name} is published by {source}')
|
||||
def step_impl(context, name, source):
|
||||
assert_attribute(get_classification(name), 'Source', source)
|
||||
|
||||
|
||||
@step(u'The classification {name} is the edition {edition} on {edition_date}')
|
||||
def step_impl(context, name, edition, edition_date):
|
||||
element = get_classification(name)
|
||||
assert_attribute(element, 'Edition', edition)
|
||||
assert_attribute(element, 'EditionDate', edition_date)
|
||||
|
||||
|
||||
@step(u'The classification {name} has the description "{description}"')
|
||||
def step_impl(context, name, description):
|
||||
assert_attribute(get_classification(name), 'Description', description)
|
||||
|
||||
|
||||
@step(u'The classification {name} is referenced by the website {location}')
|
||||
def step_impl(context, name, location):
|
||||
assert_attribute(get_classification(name), 'Location', location)
|
||||
|
||||
|
||||
@step(u'The classification {name} has a hierarchy denoted by the tokens {tokens}')
|
||||
def step_impl(context, name, tokens):
|
||||
try:
|
||||
tokens = json.loads(tokens)
|
||||
except:
|
||||
assert False, f'Tokens {tokens} are not specified as a JSON list'
|
||||
assert_attribute(get_classification(name), 'ReferenceTokens', tokens)
|
||||
|
||||
|
||||
@step(u'The element {guid} is classified as a "{identification}" with name "{reference_name}"')
|
||||
def step_impl(context, guid, identification, reference_name):
|
||||
element = IfcFile.by_guid(guid)
|
||||
if not hasattr(element, 'HasAssociations') or not element.HasAssociations:
|
||||
assert False, f'The element {element} has no associations.'
|
||||
references = [a.RelatingClassification for a in element.HasAssociations if a.is_a('IfcRelAssociatesClassification')]
|
||||
if not references:
|
||||
assert False, f'The element {element} has no associated classification references.'
|
||||
is_success = False
|
||||
for reference in references:
|
||||
try:
|
||||
assert_attribute(reference, 'Identification', identification)
|
||||
assert_attribute(reference, 'Name', reference_name)
|
||||
is_success = True
|
||||
except:
|
||||
pass
|
||||
if not is_success:
|
||||
assert False, 'No classification references met the requirement for an identification {} and name {} for the element {}. The references we found were: {}'.format(identification, reference_name, element, references)
|
||||
@@ -1,35 +0,0 @@
|
||||
from behave import step
|
||||
from utils import IfcFile, assert_attribute, assert_type
|
||||
|
||||
@step('The element {guid} is an {ifc_class} only')
|
||||
def step_impl(context, guid, ifc_class):
|
||||
element = IfcFile.by_guid(guid)
|
||||
assert_type(element, ifc_class, is_exact=True)
|
||||
|
||||
|
||||
@step('The element {guid} is an {ifc_class}')
|
||||
def step_impl(context, guid, ifc_class):
|
||||
element = IfcFile.by_guid(guid)
|
||||
assert_type(element, ifc_class)
|
||||
|
||||
|
||||
@step('The element {guid} is further defined as a {predefined_type}')
|
||||
def step_impl(context, guid, predefined_type):
|
||||
element = IfcFile.by_guid(guid)
|
||||
if hasattr(element, 'PredefinedType') \
|
||||
and element.PredefinedType == 'USERDEFINED' \
|
||||
and hasattr(element,'ObjectType'):
|
||||
assert_attribute(element, 'ObjectType', predefined_type)
|
||||
elif hasattr(element, 'PredefinedType'):
|
||||
assert_attribute(element, 'PredefinedType', predefined_type)
|
||||
else:
|
||||
assert False, 'The element {} does not have a PredefinedType or ObjectType attribute'.format(element)
|
||||
|
||||
|
||||
@step('The element {guid} should not exist because {reason}')
|
||||
def step_impl(context, guid, reason):
|
||||
try:
|
||||
element = IfcFile.get().by_id(guid)
|
||||
except:
|
||||
return
|
||||
assert False, 'This element {} should be reevaluated.'.format(element)
|
||||
@@ -1,77 +0,0 @@
|
||||
from behave import step
|
||||
from utils import IfcFile, assert_attribute, assert_type
|
||||
|
||||
|
||||
def get_ifc_class_from_spatial_type(spatial_type):
|
||||
if spatial_type == 'site':
|
||||
return 'IfcSite'
|
||||
elif spatial_type == 'building':
|
||||
return 'IfcBuilding'
|
||||
return 'IfcFacility'
|
||||
|
||||
|
||||
def check_geocode_attribute(guid, spatial_type, name, value):
|
||||
element = IfcFile.by_guid(guid)
|
||||
assert_type(element, get_ifc_class_from_spatial_type(spatial_type))
|
||||
assert_attribute(element, name, value)
|
||||
|
||||
|
||||
def check_geocode_address(guid, spatial_type, name, value):
|
||||
element = IfcFile.by_guid(guid)
|
||||
ifc_class = get_ifc_class_from_spatial_type(spatial_type)
|
||||
assert_type(element, ifc_class)
|
||||
if ifc_class == 'IfcSite':
|
||||
address_name = 'SiteAddress'
|
||||
elif ifc_class == 'IfcBuilding':
|
||||
address_name = 'BuildingAddress'
|
||||
assert_attribute(element, address_name)
|
||||
assert_attribute(getattr(element, address_name), name, value)
|
||||
|
||||
|
||||
use_step_matcher('re')
|
||||
@step('The (site|building|facility) (?P<guid>.*) has a name of (?P<name>.*)')
|
||||
def step_impl(context, spatial_type, guid, name):
|
||||
check_geocode_attribute(guid, spatial_type, 'Name', name)
|
||||
|
||||
|
||||
@step('The (site|building|facility) (?P<guid>.*) has a description of "(?P<description>.*)"')
|
||||
def step_impl(context, spatial_type, guid, description):
|
||||
check_geocode_attribute(guid, spatial_type, 'Description', description)
|
||||
|
||||
@step('The site (?P<guid>.*) has a land title number of (?P<land_title_number>.*)')
|
||||
def step_impl(context, guid, land_title_number):
|
||||
check_geocode_attribute(guid, 'site', 'LandTitleNumber', land_title_number)
|
||||
|
||||
@step('The (site|building) (?P<guid>.*) has the address "(?P<address_lines>.*)"')
|
||||
def step_impl(context, spatial_type, guid, address_lines):
|
||||
check_geocode_address(guid, spatial_type, 'AddressLines', address_lines.split('\\n'))
|
||||
|
||||
|
||||
@step('The (site|building) (?P<guid>.*) has a postal box of (?P<postal_box>.*)')
|
||||
def step_impl(context, spatial_type, guid, postal_box):
|
||||
check_geocode_address(guid, spatial_type, 'PostalBox', postal_box)
|
||||
|
||||
|
||||
@step('The (site|building) (?P<guid>.*) is in the town (?P<town>.*)')
|
||||
def step_impl(context, spatial_type, guid, town):
|
||||
check_geocode_address(guid, spatial_type, 'Town', town)
|
||||
|
||||
|
||||
@step('The (site|building) (?P<guid>.*) is in the region (?P<region>.*)')
|
||||
def step_impl(context, spatial_type, guid, region):
|
||||
check_geocode_address(guid, spatial_type, 'Region', region)
|
||||
|
||||
|
||||
@step('The (site|building) (?P<guid>.*) has a post code of (?P<post_code>.*)')
|
||||
def step_impl(context, spatial_type, guid, post_code):
|
||||
check_geocode_address(guid, spatial_type, 'PostalCode', post_code)
|
||||
|
||||
|
||||
@step('The (site|building) (?P<guid>.*) is in the country (?P<country>.*)')
|
||||
def step_impl(context, spatial_type, guid, country):
|
||||
check_geocode_address(guid, spatial_type, 'Country', country)
|
||||
|
||||
|
||||
@step('The (site|building) (?P<guid>.*) has an address description of "(?P<description>.*)"')
|
||||
def step_impl(context, spatial_type, guid, description):
|
||||
check_geocode_address(guid, spatial_type, 'Description', description)
|
||||
@@ -1,210 +0,0 @@
|
||||
from behave import step
|
||||
from utils import IfcFile, assert_number, assert_pset, assert_attribute
|
||||
import math
|
||||
import ifcopenshell.util
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.geolocation
|
||||
|
||||
@step(u'There must be at least one {ifc_class} element')
|
||||
def step_impl(context, ifc_class):
|
||||
assert len(IfcFile.get().by_type(ifc_class)) >= 1, 'An element of {} could not be found'.format(ifc_class)
|
||||
|
||||
|
||||
def check_ifc4_geolocation(entity_name, prop_name=None, value=None, should_assert=True):
|
||||
if entity_name not in IfcFile.bookmarks:
|
||||
has_entity = False
|
||||
project = IfcFile.get().by_type('IfcProject')[0]
|
||||
for context in project.RepresentationContexts:
|
||||
if entity_name == 'IfcMapConversion':
|
||||
if context.is_a('IfcGeometricRepresentationContext') \
|
||||
and context.ContextType == 'Model' \
|
||||
and context.HasCoordinateOperation:
|
||||
IfcFile.bookmarks[entity_name] = context.HasCoordinateOperation[0]
|
||||
has_entity = True
|
||||
elif entity_name == 'IfcProjectedCRS':
|
||||
if context.is_a('IfcGeometricRepresentationContext') \
|
||||
and context.ContextType == 'Model' \
|
||||
and context.HasCoordinateOperation \
|
||||
and context.HasCoordinateOperation[0].TargetCRS:
|
||||
IfcFile.bookmarks[entity_name] = context.HasCoordinateOperation[0].TargetCRS
|
||||
has_entity = True
|
||||
if not has_entity:
|
||||
assert False, 'No model geometric representation contexts refer to an {}'.format(entity_name)
|
||||
if not prop_name:
|
||||
return
|
||||
actual_value = getattr(IfcFile.bookmarks[entity_name], prop_name)
|
||||
if should_assert:
|
||||
assert actual_value == value, 'We expected a value of "{}" but instead got "{}"'.format(value, actual_value)
|
||||
else:
|
||||
return actual_value
|
||||
|
||||
|
||||
@step(u'The project must have coordinate reference system data')
|
||||
def step_impl(context):
|
||||
if IfcFile.get().schema == 'IFC2X3':
|
||||
for site in IfcFile.get().by_type('IfcSite'):
|
||||
assert_pset(site, 'EPset_ProjectedCRS')
|
||||
return
|
||||
check_ifc4_geolocation('IfcProjectedCRS')
|
||||
|
||||
|
||||
@step(u'The name of the CRS must be {coordinate_reference_name}')
|
||||
def step_impl(context, coordinate_reference_name):
|
||||
if IfcFile.get().schema == 'IFC2X3':
|
||||
for site in IfcFile.get().by_type('IfcSite'):
|
||||
assert_pset(site, 'EPset_ProjectedCRS', 'Name', coordinate_reference_name)
|
||||
return
|
||||
check_ifc4_geolocation('IfcProjectedCRS', 'Name', coordinate_reference_name)
|
||||
|
||||
|
||||
@step(u'The description of the CRS must be {value}')
|
||||
def step_impl(context, value):
|
||||
if IfcFile.get().schema == 'IFC2X3':
|
||||
for site in IfcFile.get().by_type('IfcSite'):
|
||||
assert_pset(site, 'EPset_ProjectedCRS', 'Description', value)
|
||||
return
|
||||
check_ifc4_geolocation('IfcProjectedCRS', 'Description', value)
|
||||
|
||||
|
||||
@step(u'The geodetic datum must be {coordinate_reference_name}')
|
||||
def step_impl(context, coordinate_reference_name):
|
||||
if IfcFile.get().schema == 'IFC2X3':
|
||||
for site in IfcFile.get().by_type('IfcSite'):
|
||||
assert_pset(site, 'EPset_ProjectedCRS', 'GeodeticDatum', coordinate_reference_name)
|
||||
return
|
||||
check_ifc4_geolocation('IfcProjectedCRS', 'GeodeticDatum', coordinate_reference_name)
|
||||
|
||||
|
||||
@step(u'The vertical datum must be {coordinate_reference_name}')
|
||||
def step_impl(context, coordinate_reference_name):
|
||||
if IfcFile.get().schema == 'IFC2X3':
|
||||
for site in IfcFile.get().by_type('IfcSite'):
|
||||
assert_pset(site, 'EPset_ProjectedCRS', 'VerticalDatum', coordinate_reference_name)
|
||||
return
|
||||
check_ifc4_geolocation('IfcProjectedCRS', 'VerticalDatum', coordinate_reference_name)
|
||||
|
||||
|
||||
@step(u'The map projection must be {coordinate_reference_name}')
|
||||
def step_impl(context, coordinate_reference_name):
|
||||
if IfcFile.get().schema == 'IFC2X3':
|
||||
for site in IfcFile.get().by_type('IfcSite'):
|
||||
assert_pset(site, 'EPset_ProjectedCRS', 'MapProjection', coordinate_reference_name)
|
||||
return
|
||||
check_ifc4_geolocation('IfcProjectedCRS', 'MapProjection', coordinate_reference_name)
|
||||
|
||||
|
||||
@step(u'The map zone must be {coordinate_reference_name}')
|
||||
def step_impl(context, coordinate_reference_name):
|
||||
if IfcFile.get().schema == 'IFC2X3':
|
||||
for site in IfcFile.get().by_type('IfcSite'):
|
||||
assert_pset(site, 'EPset_ProjectedCRS', 'MapZone', coordinate_reference_name)
|
||||
return
|
||||
check_ifc4_geolocation('IfcProjectedCRS', 'MapZone', coordinate_reference_name)
|
||||
|
||||
|
||||
@step(u'The map unit must be {unit}')
|
||||
def step_impl(context, unit):
|
||||
if IfcFile.get().schema == 'IFC2X3':
|
||||
for site in IfcFile.get().by_type('IfcSite'):
|
||||
assert_pset(site, 'EPset_ProjectedCRS', 'MapUnit', unit)
|
||||
return
|
||||
actual_value = check_ifc4_geolocation('IfcProjectedCRS', 'MapUnit', should_assert=False)
|
||||
if not actual_value:
|
||||
assert False, 'A unit was not provided in the projected CRS'
|
||||
if actual_value.is_a('IfcSIUnit'):
|
||||
prefix = actual_value.Prefix if actual_value.Prefix else ''
|
||||
actual_value = prefix + actual_value.Name
|
||||
elif actual_value.is_a('IfcConversionBasedUnit'):
|
||||
actual_value = actual_value.Name
|
||||
assert actual_value == unit, 'We expected a value of "{}" but instead got "{}"'.format(unit, actual_value)
|
||||
|
||||
|
||||
@step(u'The project must have coordinate transformations to convert from local to global coordinates')
|
||||
def step_impl(context):
|
||||
if IfcFile.get().schema == 'IFC2X3':
|
||||
for site in IfcFile.get().by_type('IfcSite'):
|
||||
assert_pset(site, 'EPset_MapConversion')
|
||||
check_ifc4_geolocation('IfcMapConversion')
|
||||
|
||||
|
||||
@step(u'The eastings of the model must be offset by {number} to derive its global coordinates')
|
||||
def step_impl(context, number):
|
||||
number = assert_number(number)
|
||||
if IfcFile.get().schema == 'IFC2X3':
|
||||
for site in IfcFile.get().by_type('IfcSite'):
|
||||
assert_pset(site, 'EPset_MapConversion', 'Eastings', number)
|
||||
return
|
||||
check_ifc4_geolocation('IfcMapConversion', 'Eastings', number)
|
||||
|
||||
|
||||
@step(u'The northings of the model must be offset by {number} to derive its global coordinates')
|
||||
def step_impl(context, number):
|
||||
number = assert_number(number)
|
||||
if IfcFile.get().schema == 'IFC2X3':
|
||||
for site in IfcFile.get().by_type('IfcSite'):
|
||||
assert_pset(site, 'EPset_MapConversion', 'Northings', number)
|
||||
return
|
||||
check_ifc4_geolocation('IfcMapConversion', 'Northings', number)
|
||||
|
||||
|
||||
@step(u'The height of the model must be offset by {number} to derive its global coordinates')
|
||||
def step_impl(context, number):
|
||||
number = assert_number(number)
|
||||
if IfcFile.get().schema == 'IFC2X3':
|
||||
for site in IfcFile.get().by_type('IfcSite'):
|
||||
assert_pset(site, 'EPset_MapConversion', 'OrthogonalHeight', number)
|
||||
return
|
||||
check_ifc4_geolocation('IfcMapConversion', 'OrthogonalHeight', number)
|
||||
|
||||
|
||||
@step(u'The model must be rotated clockwise by {number} to derive its global coordinates')
|
||||
def step_impl(context, number):
|
||||
number = assert_number(number)
|
||||
if IfcFile.get().schema == 'IFC2X3':
|
||||
return check_ifc2x3_geolocation('EPset_MapConversion', 'Height', number)
|
||||
abscissa = check_ifc4_geolocation('IfcMapConversion', 'XAxisAbscissa', should_assert=False)
|
||||
ordinate = check_ifc4_geolocation('IfcMapConversion', 'XAxisOrdinate', should_assert=False)
|
||||
actual_value = round(ifcopenshell.util.geolocation.xy2angle(abscissa, ordinate), 3)
|
||||
value = round(number, 3)
|
||||
assert actual_value == value, 'We expected a value of "{}" but instead got "{}"'.format(value, actual_value)
|
||||
|
||||
|
||||
@step(u'The model must be scaled along the horizontal axis by {number} to derive its global coordinates')
|
||||
def step_impl(context, number):
|
||||
number = assert_number(number)
|
||||
if IfcFile.get().schema == 'IFC2X3':
|
||||
for site in IfcFile.get().by_type('IfcSite'):
|
||||
assert_pset(site, 'EPset_MapConversion', 'Scale', number)
|
||||
return
|
||||
check_ifc4_geolocation('IfcMapConversion', 'Scale', number)
|
||||
|
||||
|
||||
@step(u'The site {guid} has a longitude of {number}')
|
||||
def step_impl(context, guid, number):
|
||||
number = assert_number(number)
|
||||
site = IfcFile.by_guid(guid)
|
||||
if not site.is_a('IfcSite'):
|
||||
assert False, 'The element {} is not an IfcSite'.format(site)
|
||||
ref = assert_attribute(site, 'RefLongitude')
|
||||
number = ifcopenshell.util.geolocation.dd2dms(number, use_ms=(len(ref) == 4))
|
||||
assert_attribute(site, 'RefLongitude', number)
|
||||
|
||||
|
||||
@step(u'The site {guid} has a latitude of {number}')
|
||||
def step_impl(context, guid, number):
|
||||
number = assert_number(number)
|
||||
site = IfcFile.by_guid(guid)
|
||||
if not site.is_a('IfcSite'):
|
||||
assert False, 'The element {} is not an IfcSite'.format(site)
|
||||
ref = assert_attribute(site, 'RefLatitude')
|
||||
number = ifcopenshell.util.geolocation.dd2dms(number, use_ms=(len(ref) == 4))
|
||||
assert_attribute(site, 'RefLatitude', number)
|
||||
|
||||
|
||||
@step(u'The site {guid} has an elevation of {number}')
|
||||
def step_impl(context, guid, number):
|
||||
number = assert_number(number)
|
||||
site = IfcFile.by_guid(guid)
|
||||
if not site.is_a('IfcSite'):
|
||||
assert False, 'The element {} is not an IfcSite'.format(site)
|
||||
assert_attribute(site, 'RefElevation', number)
|
||||
@@ -1,27 +0,0 @@
|
||||
from behave import step
|
||||
from utils import IfcFile
|
||||
from utils import IfcFile, assert_attribute
|
||||
|
||||
@step('All elements must be under {number} polygons')
|
||||
def step_impl(context, number):
|
||||
number = int(number)
|
||||
errors = []
|
||||
for element in IfcFile.get().by_type('IfcElement'):
|
||||
if not element.Representation:
|
||||
continue
|
||||
total_polygons = 0
|
||||
tree = IfcFile.get().traverse(element.Representation)
|
||||
for e in tree:
|
||||
if e.is_a('IfcFace'):
|
||||
total_polygons += 1
|
||||
elif e.is_a('IfcPolygonalFaceSet'):
|
||||
total_polygons += len(e.Faces)
|
||||
elif e.is_a('IfcTriangulatedFaceSet'):
|
||||
total_polygons += len(e.CoordIndex)
|
||||
if total_polygons > number:
|
||||
errors.append((total_polygons, element))
|
||||
if errors:
|
||||
message = 'The following {} elements are over 500 polygons:\n'.format(len(errors))
|
||||
for error in errors:
|
||||
message += 'Polygons: {} - {}\n'.format(error[0], error[1])
|
||||
assert False, message
|
||||
@@ -1,113 +0,0 @@
|
||||
import numpy as np
|
||||
import ifcopenshell.util.geolocation
|
||||
from behave import step
|
||||
from utils import IfcFile
|
||||
from utils import IfcFile, assert_number, assert_type
|
||||
|
||||
|
||||
def a2p(o, z, x):
|
||||
y = np.cross(z, x)
|
||||
r = np.eye(4)
|
||||
r[:-1,:-1] = x,y,z
|
||||
r[-1,:-1] = o
|
||||
return r.T
|
||||
|
||||
|
||||
def get_axis2placement(plc):
|
||||
z = np.array(plc.Axis.DirectionRatios if plc.Axis else (0,0,1))
|
||||
x = np.array(plc.RefDirection.DirectionRatios if plc.RefDirection else (1,0,0))
|
||||
o = plc.Location.Coordinates
|
||||
return a2p(o,z,x)
|
||||
|
||||
|
||||
def get_local_placement(plc):
|
||||
if plc is None:
|
||||
return np.eye(4)
|
||||
if plc.PlacementRelTo is None:
|
||||
parent = np.eye(4)
|
||||
else:
|
||||
parent = get_local_placement(plc.PlacementRelTo)
|
||||
return np.dot(get_axis2placement(plc.RelativePlacement), parent)
|
||||
|
||||
|
||||
def get_decimal_points(value):
|
||||
try:
|
||||
return len(value.split('.')[1])
|
||||
except:
|
||||
return 0
|
||||
|
||||
|
||||
def get_containing_spatial_elements(element):
|
||||
results = []
|
||||
if element.is_a('IfcSpatialElement'):
|
||||
results.append(element)
|
||||
for rel in element.Decomposes:
|
||||
if rel.is_a('IfcRelAggregates'):
|
||||
results.append(get_containing_spatial_elements(rel.RelatingObject))
|
||||
elif element.is_a('IfcElement'):
|
||||
for rel in element.ContainedInStructure:
|
||||
if rel.is_a('ifcRelContainedInSpatialStructure'):
|
||||
results.append(get_containing_spatial_elements(rel.RelatingStructure))
|
||||
return results
|
||||
|
||||
|
||||
@step('There is a datum element {guid} as an {ifc_class}')
|
||||
def step_impl(context, guid, ifc_class):
|
||||
element = IfcFile.by_guid(guid)
|
||||
assert_type(element, ifc_class)
|
||||
|
||||
|
||||
@step('The element {guid} has a global easting, northing, and elevation of {easting}, {northing}, and {elevation} respectively')
|
||||
def step_impl(context, guid, easting, northing, elevation):
|
||||
if IfcFile.get().schema == 'IFC2X3':
|
||||
if element.is_a('IfcSite'):
|
||||
site = element
|
||||
else:
|
||||
potential_sites = [s for s in get_containing_spatial_elements(element) if s.is_a('IfcSite')]
|
||||
if potential_sites:
|
||||
site = potential_sites[0]
|
||||
else:
|
||||
assert False, 'The datum element does not belong to a geolocated site'
|
||||
map_conversion = assert_pset(site, 'EPset_MapConversion')
|
||||
else:
|
||||
map_conversion = IfcFile.get().by_type('IfcMapConversion')
|
||||
if map_conversion:
|
||||
map_conversion = map_conversion[0].get_info()
|
||||
else:
|
||||
assert False, 'No map conversion was found in the file'
|
||||
|
||||
element = IfcFile.by_guid(guid)
|
||||
if not element.ObjectPlacement:
|
||||
assert False, 'The element does not have an object placement: {}'.format(element)
|
||||
m = get_local_placement(element.ObjectPlacement)
|
||||
e, n, h = ifcopenshell.util.geolocation.xyz2enh(
|
||||
m[0][3], m[1][3], m[2][3],
|
||||
float(map_conversion['Eastings']),
|
||||
float(map_conversion['Northings']),
|
||||
float(map_conversion['OrthogonalHeight']),
|
||||
float(map_conversion['XAxisAbscissa']),
|
||||
float(map_conversion['XAxisOrdinate']),
|
||||
float(map_conversion['Scale']),
|
||||
)
|
||||
element_x = round(e, get_decimal_points(easting))
|
||||
element_y = round(n, get_decimal_points(northing))
|
||||
element_z = round(h, get_decimal_points(elevation))
|
||||
expected_placement = (assert_number(easting), assert_number(northing), assert_number(elevation))
|
||||
if (element_x, element_y, element_z) != expected_placement:
|
||||
assert False, 'The element {} is meant to have a location of {} but instead we found {}'.format(
|
||||
element, expected_placement, (element_x, element_y, element_z))
|
||||
|
||||
|
||||
@step('The element {guid} has a local X, Y, and Z coordinate of {x}, {y}, and {z} respectively')
|
||||
def step_impl(context, guid, x, y, z):
|
||||
element = IfcFile.by_guid(guid)
|
||||
if not element.ObjectPlacement:
|
||||
assert False, 'The element does not have an object placement: {}'.format(element)
|
||||
m = get_local_placement(element.ObjectPlacement)
|
||||
element_x = round(m[0][3], get_decimal_points(x))
|
||||
element_y = round(m[1][3], get_decimal_points(y))
|
||||
element_z = round(m[2][3], get_decimal_points(z))
|
||||
expected_placement = (assert_number(x), assert_number(y), assert_number(z))
|
||||
if (element_x, element_y, element_z) != expected_placement:
|
||||
assert False, 'The element {} is meant to have a location of {} but instead we found {}'.format(
|
||||
element, expected_placement, (element_x, element_y, element_z))
|
||||
@@ -1,89 +0,0 @@
|
||||
from behave import step
|
||||
from utils import IfcFile
|
||||
from utils import IfcFile, assert_attribute
|
||||
|
||||
@step('The IFC file "{file}" must be provided')
|
||||
def step_impl(context, file):
|
||||
try:
|
||||
IfcFile.load(file)
|
||||
except:
|
||||
assert False, f'The file {file} could not be loaded'
|
||||
|
||||
|
||||
@step('IFC data must use the {schema} schema')
|
||||
def step_impl(context, schema):
|
||||
assert IfcFile.get().schema == schema, \
|
||||
'We expected a schema of {} but instead got {}'.format(
|
||||
schema, IfcFile.get().schema)
|
||||
|
||||
|
||||
@step('The IFC file "{file}" is exempt from being provided')
|
||||
def step_impl(context, file):
|
||||
pass
|
||||
|
||||
|
||||
@step('No further requirements are specified because {reason}')
|
||||
def step_impl(context, reason):
|
||||
pass
|
||||
|
||||
|
||||
@step('The project must have an identifier of {guid}')
|
||||
def step_impl(context, guid):
|
||||
assert_attribute(IfcFile.get().by_type('IfcProject')[0], 'GlobalId', guid)
|
||||
|
||||
|
||||
@step('The project name, code, or short identifier must be "{value}"')
|
||||
def step_impl(context, value):
|
||||
assert_attribute(IfcFile.get().by_type('IfcProject')[0], 'Name', value)
|
||||
|
||||
|
||||
@step('The project must have a longer form name of "{value}"')
|
||||
def step_impl(context, value):
|
||||
assert_attribute(IfcFile.get().by_type('IfcProject')[0], 'LongName', value)
|
||||
|
||||
|
||||
@step('The project must be described as "{value}"')
|
||||
def step_impl(context, value):
|
||||
assert_attribute(IfcFile.get().by_type('IfcProject')[0], 'Description', value)
|
||||
|
||||
|
||||
@step('The project must be categorised under "{value}"')
|
||||
def step_impl(context, value):
|
||||
assert_attribute(IfcFile.get().by_type('IfcProject')[0], 'ObjectType', value)
|
||||
|
||||
|
||||
@step('The project must contain information about the "{value}" phase')
|
||||
def step_impl(context, value):
|
||||
assert_attribute(IfcFile.get().by_type('IfcProject')[0], 'Phase', value)
|
||||
|
||||
|
||||
@step('The project must contain 3D geometry representing the shape of objects')
|
||||
def step_impl(context):
|
||||
assert get_subcontext('Body', 'Model', 'MODEL_VIEW')
|
||||
|
||||
|
||||
@step('The project must contain 3D geometry representing clearance zones')
|
||||
def step_impl(context):
|
||||
assert get_subcontext('Clearance', 'Model', 'MODEL_VIEW')
|
||||
|
||||
|
||||
@step('The project must contain 3D geometry representing the center of gravity of objects')
|
||||
def step_impl(context):
|
||||
assert get_subcontext('CoG', 'Model', 'MODEL_VIEW')
|
||||
|
||||
|
||||
@step('The project must contain 3D geometry representing the object bounding boxes')
|
||||
def step_impl(context):
|
||||
assert get_subcontext('Box', 'Model', 'MODEL_VIEW')
|
||||
|
||||
|
||||
def get_subcontext(identifier, type, target_view):
|
||||
project = IfcFile.get().by_type('IfcProject')[0]
|
||||
for rep_context in project.RepresentationContexts:
|
||||
for subcontext in rep_context.HasSubContexts:
|
||||
if subcontext.ContextIdentifier == identifier \
|
||||
and subcontext.ContextType == type \
|
||||
and subcontext.TargetView == target_view:
|
||||
return True
|
||||
assert False, 'The subcontext with identifier {}, type {}, and target view {} could not be found'.format(
|
||||
identifier, type, target_view)
|
||||
@@ -1,147 +0,0 @@
|
||||
from behave import step
|
||||
from utils import IfcFile
|
||||
|
||||
@step(u'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(u'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(u'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(u'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,65 +0,0 @@
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util
|
||||
import ifcopenshell.util.element
|
||||
|
||||
class IfcFile(object):
|
||||
file = None
|
||||
bookmarks = {}
|
||||
|
||||
@classmethod
|
||||
def load(cls, path=None):
|
||||
cls.file = ifcopenshell.open(path)
|
||||
|
||||
@classmethod
|
||||
def get(cls):
|
||||
if not cls.file:
|
||||
assert False, 'No file was loaded, so this requirement cannot be checked'
|
||||
return cls.file
|
||||
|
||||
@classmethod
|
||||
def by_guid(cls, guid):
|
||||
try:
|
||||
return cls.get().by_guid(guid)
|
||||
except:
|
||||
assert False, 'An element with the ID {} could not be found.'.format(guid)
|
||||
|
||||
def assert_number(number):
|
||||
try:
|
||||
return float(number)
|
||||
except ValueError:
|
||||
assert False, 'A number should be specified, not {}'.format(number)
|
||||
|
||||
def assert_type(element, ifc_class, is_exact = False):
|
||||
if is_exact:
|
||||
assert element.is_a() == ifc_class, 'The element {} is an {} instead of {}.'.format(element, element.is_a(), ifc_class)
|
||||
else:
|
||||
assert element.is_a(ifc_class), 'The element {} is an {} instead of {}.'.format(element, element.is_a(), ifc_class)
|
||||
|
||||
def assert_attribute(element, name, value=None):
|
||||
if not hasattr(element, name):
|
||||
assert False, 'The element {} does not have the attribute {}'.format(element, name)
|
||||
if not value:
|
||||
if getattr(element, name) is None:
|
||||
assert False, 'The element {} does not have a value for the attribute {}'.format(element, name)
|
||||
return getattr(element, name)
|
||||
if value == 'NULL':
|
||||
value = None
|
||||
actual_value = getattr(element, name)
|
||||
if isinstance(value, list) and actual_value:
|
||||
actual_value = list(actual_value)
|
||||
assert actual_value == value, 'We expected a value of "{}" but instead got "{}" for the element {}'.format(value, actual_value, element)
|
||||
|
||||
def assert_pset(element, pset_name, prop_name=None, value=None):
|
||||
if value == 'NULL':
|
||||
value = None
|
||||
psets = ifcopenshell.util.element.get_psets(site)
|
||||
if pset_name not in psets:
|
||||
assert False, 'The element {} does not have a property set named {}'.format(element, pset_name)
|
||||
if prop_name is None:
|
||||
return psets[pset_name]
|
||||
if prop_name not in psets[pset_name]:
|
||||
assert False, 'The element {} does not have a property named "{}" in the pset "{}"'.format(element, prop_name, pset_name)
|
||||
if value is None:
|
||||
return psets[pset_name][prop_name]
|
||||
actual_value = psets[pset_name][prop_name]
|
||||
assert actual_value == value, 'We expected a value of "{}" but instead got "{}" for the element {}'.format(value, actual_value, element)
|
||||
@@ -0,0 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from bimtester.guiwidget import run
|
||||
|
||||
|
||||
run()
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
@@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="flag-icon-css-de" viewBox="0 0 640 480">
|
||||
<path fill="#ffce00" d="M0 320h640v160H0z"/>
|
||||
<path d="M0 0h640v160H0z"/>
|
||||
<path fill="#d00" d="M0 160h640v160H0z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 212 B |
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="flag-icon-css-fr" viewBox="0 0 640 480">
|
||||
<g fill-rule="evenodd" stroke-width="1pt">
|
||||
<path fill="#fff" d="M0 0h640v480H0z"/>
|
||||
<path fill="#00267f" d="M0 0h213.3v480H0z"/>
|
||||
<path fill="#f31830" d="M426.7 0H640v480H426.7z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 291 B |
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="flag-icon-css-gb" viewBox="0 0 640 480">
|
||||
<path fill="#012169" d="M0 0h640v480H0z"/>
|
||||
<path fill="#FFF" d="M75 0l244 181L562 0h78v62L400 241l240 178v61h-80L320 301 81 480H0v-60l239-178L0 64V0h75z"/>
|
||||
<path fill="#C8102E" d="M424 281l216 159v40L369 281h55zm-184 20l6 35L54 480H0l240-179zM640 0v3L391 191l2-44L590 0h50zM0 0l239 176h-60L0 42V0z"/>
|
||||
<path fill="#FFF" d="M241 0v480h160V0H241zM0 160v160h640V160H0z"/>
|
||||
<path fill="#C8102E" d="M0 193v96h640v-96H0zM273 0v480h96V0h-96z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 537 B |
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="flag-icon-css-it" viewBox="0 0 640 480">
|
||||
<g fill-rule="evenodd" stroke-width="1pt">
|
||||
<path fill="#fff" d="M0 0h640v480H0z"/>
|
||||
<path fill="#009246" d="M0 0h213.3v480H0z"/>
|
||||
<path fill="#ce2b37" d="M426.7 0H640v480H426.7z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 291 B |
@@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="flag-icon-css-nl" viewBox="0 0 640 480">
|
||||
<path fill="#21468b" d="M0 0h640v480H0z"/>
|
||||
<path fill="#fff" d="M0 0h640v320H0z"/>
|
||||
<path fill="#ae1c28" d="M0 0h640v160H0z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 223 B |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1,233 @@
|
||||
<!doctype html>
|
||||
<html class="no-js" lang="">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>BIMTester Requirements Generator</title>
|
||||
<meta name="description" content="">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<link rel="manifest" href="site.webmanifest">
|
||||
<link rel="apple-touch-icon" href="icon.png">
|
||||
<!-- Place favicon.ico in the root directory -->
|
||||
|
||||
<link rel="stylesheet" href="normalize.css">
|
||||
<link rel="stylesheet" href="skeleton.css">
|
||||
<link rel="stylesheet" href="main.css">
|
||||
|
||||
<meta name="theme-color" content="#fafafa">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<!--[if IE]>
|
||||
<p class="browserupgrade">You are using an <strong>outdated</strong> browser. Please <a href="https://browsehappy.com/">upgrade your browser</a> to improve your experience and security.</p>
|
||||
<![endif]-->
|
||||
|
||||
<header{{#has_seamless_header}} class="seamless"{{/has_seamless_header}}>
|
||||
<h1>
|
||||
BIMTester Requirements Generator
|
||||
<span style="font-size: 0.5em; font-style: italic;">
|
||||
Because your BIM actually sucks but you don't know it yet.
|
||||
</span>
|
||||
</h1>
|
||||
</header>
|
||||
<div class="row">
|
||||
<section class="eight columns">
|
||||
<div class="tcenter">
|
||||
<img src="images/gb.svg" class="flag active-flag" title="English">
|
||||
<img src="images/de.svg" class="flag" title="German">
|
||||
<img src="images/it.svg" class="flag" title="Italian">
|
||||
<img src="images/fr.svg" class="flag" title="French">
|
||||
<img src="images/nl.svg" class="flag" title="Dutch">
|
||||
</div>
|
||||
<select name="micromvd">
|
||||
<option>Project setup</option>
|
||||
</select>
|
||||
<p class="tcenter">
|
||||
In order to ensure quality of the digital built environment<br />
|
||||
As a responsible digital citizen<br />
|
||||
We expect compliant OpenBIM deliverables<br />
|
||||
</p>
|
||||
<div class="tcenter" style="margin-bottom: 20px;">
|
||||
<h2 style="border-bottom: 0px; display: inline; padding-right: 20px;">
|
||||
Import
|
||||
</h2>
|
||||
<img class="vendor-icon vendor-full" src="images/archicad.png" width="24px">
|
||||
<img class="vendor-icon vendor-full" src="images/blenderbim.png" width="24px">
|
||||
<img class="vendor-icon vendor-partial" src="images/freecad.png" width="24px">
|
||||
<img class="vendor-icon vendor-bad" src="images/revit.png" width="24px">
|
||||
<img class="vendor-icon vendor-unknown" src="images/tekla.png" width="24px">
|
||||
|
||||
<h2 style="border-bottom: 0px; display: inline; padding-right: 20px; padding-left: 20px;">
|
||||
Export
|
||||
</h2>
|
||||
<img class="vendor-icon vendor-partial" src="images/archicad.png" width="24px">
|
||||
<img class="vendor-icon vendor-full" src="images/blenderbim.png" width="24px">
|
||||
<img class="vendor-icon vendor-partial" src="images/freecad.png" width="24px">
|
||||
<img class="vendor-icon vendor-full" src="images/revit.png" width="24px">
|
||||
<img class="vendor-icon vendor-unknown" src="images/tekla.png" width="24px">
|
||||
|
||||
<span style="color: #999; display: block;">Need help meeting these requirements with a vendor? <a href="#">Read more</a></span>
|
||||
</div>
|
||||
<h2>
|
||||
Scenario: Receiving a file
|
||||
</h2>
|
||||
<ol>
|
||||
<li>
|
||||
<details>
|
||||
<summary>The data must use the "<code contenteditable>{schema}</code>" schema</summary>
|
||||
<strong>
|
||||
Example: <span>The data must use the "IFC4" schema</span>
|
||||
</strong>
|
||||
<p>
|
||||
BIM data may be structured using a particular version of IFC, known as the "schema" version.
|
||||
Newer versions add lots of capabilities like extra parameters, new data relationships, and
|
||||
improved data classification. The version you choose will affect what data can be stored,
|
||||
and which programs have support for reading and writing that version. At the moment, you are
|
||||
likely to specify either "IFC2X3" or "IFC4".
|
||||
</p>
|
||||
<div style="padding-bottom: 20px;">
|
||||
<div class="onoffswitch">
|
||||
<input type="checkbox" name="onoffswitch" class="onoffswitch-checkbox" id="myonoffswitch1" tabindex="0" checked>
|
||||
<label class="onoffswitch-label" for="myonoffswitch1">
|
||||
<span class="onoffswitch-inner"></span>
|
||||
<span class="onoffswitch-switch"></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
</li>
|
||||
</ol>
|
||||
<h2>
|
||||
Scenario: Exempt files
|
||||
</h2>
|
||||
<ol>
|
||||
<li>
|
||||
<details>
|
||||
<summary>The IFC file "<code contenteditable>{file}</code>" is exempt from being provided</summary>
|
||||
<strong>
|
||||
Example: <span>The IFC file "PROJECT.ifc" is exempt from being provided</span>
|
||||
</strong>
|
||||
<p>
|
||||
Sometimes, projects may create BIM data that is purely temporary, or as a workaround that is
|
||||
needed as part of their workflow, but not required from a contractual perspective. In this
|
||||
case, it is advisable for the requirements to explicitly exclude these BIM files so that the
|
||||
client knows what is included in the scope of requirements. Simply specify the name of the
|
||||
file that you know is not required as a deliverable.
|
||||
</p>
|
||||
<div style="padding-bottom: 20px;">
|
||||
<div class="onoffswitch">
|
||||
<input type="checkbox" name="onoffswitch" class="onoffswitch-checkbox" id="myonoffswitch2" tabindex="0" checked>
|
||||
<label class="onoffswitch-label" for="myonoffswitch2">
|
||||
<span class="onoffswitch-inner"></span>
|
||||
<span class="onoffswitch-switch"></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
</li>
|
||||
<li>
|
||||
<details>
|
||||
<summary>
|
||||
No further requirements are specified because "<code contenteditable>{reason}</code>"
|
||||
</summary>
|
||||
<strong>
|
||||
Example: <span>No further requirements are specified because "it will be superseded in a future project phase"</span>
|
||||
</strong>
|
||||
<p>
|
||||
The recipient may not necessarily wish to specify more BIM requirements to audit. Scenarios
|
||||
when this is the case may be when the recipient does not have processes in place to use the
|
||||
BIM data or does not know how to use the BIM data, or when the BIM data is temporary or
|
||||
irrelevant for any reason. In this scenario, it may be useful to specify the reason why no
|
||||
further audits will take place. This is the contractual equivalent to "this page is
|
||||
intentionally left blank".
|
||||
</p>
|
||||
<div style="padding-bottom: 20px;">
|
||||
<div class="onoffswitch">
|
||||
<input type="checkbox" name="onoffswitch" class="onoffswitch-checkbox" id="myonoffswitch3" tabindex="0" checked>
|
||||
<label class="onoffswitch-label" for="myonoffswitch3">
|
||||
<span class="onoffswitch-inner"></span>
|
||||
<span class="onoffswitch-switch"></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
</li>
|
||||
</ol>
|
||||
<h2>
|
||||
Scenario: Project metadata is organised and correct
|
||||
</h2>
|
||||
<ol>
|
||||
<li>The project must have an identifier of "<code contenteditable>{guid}</code>"</li>
|
||||
<li>The project name, code, or short identifier must be "<code contenteditable>{name}</code>"</li>
|
||||
<li>The project must have a longer form name of "<code contenteditable>{long_name}</code>"</li>
|
||||
<li>The project must be described as "<code contenteditable>{description}</code>"</li>
|
||||
<li>The project must be categorised under "<code contenteditable>{object_type}</code>"</li>
|
||||
<li>The project must contain information about the "<code contenteditable>{phase}</code>" phase</li>
|
||||
</ol>
|
||||
<h2>
|
||||
Scenario: Project geometry is stored
|
||||
</h2>
|
||||
<ol>
|
||||
<li>The project must contain 3D geometry representing the shape of objects</li>
|
||||
</ol>
|
||||
</section>
|
||||
<aside class="four columns">
|
||||
<p>
|
||||
BIMTester is an <strong>open source</strong>, <strong>transparent</strong>,
|
||||
<strong>standards-based</strong> auditing system for IFC-based projects, using well-established best
|
||||
practices from the software industry. Requirements can be automatically checked and customised using 100%
|
||||
free software that puts you in control of your OpenBIM data.
|
||||
</p>
|
||||
<h3>
|
||||
Step 1
|
||||
</h3>
|
||||
<p>
|
||||
Select a requirements template from the dropdown list.
|
||||
</p>
|
||||
<h3>
|
||||
Step 2
|
||||
</h3>
|
||||
<p>
|
||||
Fill out the requirements by editing the highlighted words line by line. Click on a line to toggle whether the requirement is needed for your project.
|
||||
</p>
|
||||
<h3>
|
||||
Step 3
|
||||
</h3>
|
||||
<p>
|
||||
Download the requirements file and include it in contracts.
|
||||
</p>
|
||||
<p>
|
||||
<a href="#" class="button button-primary">Download Requirements</a>
|
||||
|
||||
<a href="#" class="button">Run an automatic Audit</a>
|
||||
</p>
|
||||
<h4>
|
||||
More resources
|
||||
</h4>
|
||||
<ul>
|
||||
<li>Want to write your own requirements? <a href="#">Read more</a>
|
||||
<li>Are you a poweruser or developer? Learn the ropes. <a href="#">Read more</a>
|
||||
<li>Need more help? Engage with the OSArch community. <a href="#">Read more</a>
|
||||
</ul>
|
||||
</aside>
|
||||
</div>
|
||||
<footer>
|
||||
<p>
|
||||
BIMTester is built with ♥ on the awesome <a href="http://www.ifcopenshell.org/">IfcOpenShell</a> project by <a href="https://blenderbim.org/community.html">amazing volunteers</a>. You can be one too!
|
||||
</p>
|
||||
</footer>
|
||||
<!--
|
||||
<script src="js/vendor/modernizr-3.8.0.min.js"></script>
|
||||
<script src="https://code.jquery.com/jquery-3.4.1.min.js" integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo=" crossorigin="anonymous"></script>
|
||||
<script>window.jQuery || document.write('<script src="js/vendor/jquery-3.4.1.min.js"><\/script>')</script>
|
||||
<script src="js/plugins.js"></script>
|
||||
<script>
|
||||
window.ga = function () { ga.q.push(arguments) }; ga.q = []; ga.l = +new Date;
|
||||
ga('create', 'UA-XXXXX-Y', 'auto'); ga('set','transport','beacon'); ga('send', 'pageview')
|
||||
</script>
|
||||
<script src="https://www.google-analytics.com/analytics.js" async></script>
|
||||
-->
|
||||
<script src="main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,68 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
sodipodi:docname="info.svg"
|
||||
inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
|
||||
id="svg8"
|
||||
version="1.1"
|
||||
viewBox="0 0 15 10"
|
||||
height="10mm"
|
||||
width="15mm">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<sodipodi:namedview
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:window-y="25"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-height="1055"
|
||||
inkscape:window-width="1920"
|
||||
showgrid="false"
|
||||
inkscape:document-rotation="0"
|
||||
inkscape:current-layer="layer1"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:cy="7.4426888"
|
||||
inkscape:cx="42.12184"
|
||||
inkscape:zoom="8.6683577"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
borderopacity="1.0"
|
||||
bordercolor="#666666"
|
||||
pagecolor="#ffffff"
|
||||
id="base" />
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
id="layer1"
|
||||
inkscape:groupmode="layer"
|
||||
inkscape:label="Layer 1">
|
||||
<path
|
||||
d="M 9.5,5 A 4.5,4.5000019 0 0 1 5,9.5000019 4.5,4.5000019 0 0 1 0.5,5 4.5,4.5000019 0 0 1 5,0.49999809 4.5,4.5000019 0 0 1 9.5,5 Z"
|
||||
style="fill:none;stroke:#3d3242;stroke-width:0.999996;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;stop-color:#000000"
|
||||
id="path833" />
|
||||
<g
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.5833px;line-height:125%;font-family:'Andale Mono';-inkscape-font-specification:'Andale Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#3d3242;fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
id="text837"
|
||||
aria-label="i">
|
||||
<path
|
||||
id="path861"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:Webdings;-inkscape-font-specification:Webdings;fill:#3d3242;fill-opacity:1;stroke-width:0.264583px"
|
||||
d="m 9.2322863,5.002584 q 0,1.7518255 -1.2402304,2.9920559 Q 6.7518255,9.2297027 5,9.2297027 q -1.7518255,0 -2.9920559,-1.2350628 Q 0.76771367,6.7544095 0.76771367,5.002584 q 0,-1.7518254 1.24023043,-2.9920559 Q 3.2481745,0.77029771 5,0.77029771 q 1.7518255,0 2.9920559,1.24023039 1.2402304,1.2402305 1.2402304,2.9920559 z m -0.692462,0 q 0,-1.467606 -1.0386929,-2.5062989 Q 6.467606,1.4575921 5,1.4575921 q -1.467606,0 -2.506299,1.038693 -1.038693,1.0386929 -1.038693,2.5062989 0,1.467606 1.038693,2.506299 Q 3.532394,8.5424084 5,8.5424084 q 1.467606,0 2.5011314,-1.0335254 Q 8.5398243,6.47019 8.5398243,5.002584 Z M 5.8681613,2.8218455 q 0,0.3358958 -0.2583813,0.5736066 Q 5.3565662,3.6331629 5,3.6331629 q -0.3617339,0 -0.6149476,-0.2377108 -0.2532137,-0.2377108 -0.2532137,-0.5736066 0,-0.3358957 0.2532137,-0.5736065 Q 4.6382661,2.0105281 5,2.0105281 q 0.3565662,0 0.60978,0.2377109 0.2583813,0.2377108 0.2583813,0.5736065 z M 5.7079649,8.1651716 H 4.2868675 V 4.1344227 h 1.4210974 z" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.6 KiB |
@@ -0,0 +1,271 @@
|
||||
@import url('https://fonts.googleapis.com/css2?family=Lato:wght@300;400&display=swap');
|
||||
|
||||
* {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgba(.5,.5,.5,.5) rgba(0,0,0,0);
|
||||
}
|
||||
|
||||
/* Works on Chrome/Edge/Safari */
|
||||
*::-webkit-scrollbar {
|
||||
width: 5px;
|
||||
}
|
||||
*::-webkit-scrollbar-track {
|
||||
background: rgba(0,0,0,0);
|
||||
}
|
||||
*::-webkit-scrollbar-thumb {
|
||||
background-color: rgba(.5,.5,.5,.5);
|
||||
border-radius: 20px;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: #e1dcd8;
|
||||
color: #3d3242;
|
||||
font-family: 'Lato', sans-serif;
|
||||
}
|
||||
|
||||
header {
|
||||
background-color: #8daad3;
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
background-position: center center;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
footer {
|
||||
background-color: #c0c0c0;
|
||||
padding-left: 20px;
|
||||
line-height: 60px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #b85934;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #e18a42;
|
||||
}
|
||||
|
||||
footer p {
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 1.5em;
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4 {
|
||||
letter-spacing: normal;
|
||||
}
|
||||
|
||||
section {
|
||||
padding: 20px;
|
||||
background-color: #3d3242;
|
||||
color: #eee;
|
||||
}
|
||||
|
||||
section select {
|
||||
width: 100%;
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
section h2 {
|
||||
font-size: 1.2em;
|
||||
line-height: 50px;
|
||||
margin: 0px;
|
||||
font-weight: bold;
|
||||
border-bottom: 1px solid #756180;
|
||||
}
|
||||
|
||||
section ol {
|
||||
list-style-type: None;
|
||||
}
|
||||
|
||||
section ol li {
|
||||
line-height: 40px;
|
||||
margin: 0px;
|
||||
background-position: center right;
|
||||
background-repeat: no-repeat;
|
||||
border-left: 5px solid #3e7c52;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
section ol li.excluded {
|
||||
border-left: 5px solid #b15a69;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
section ol li p {
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
section ol li:hover {
|
||||
background-color: #756180;
|
||||
cursor: pointer;
|
||||
background-image: url('info.svg');
|
||||
background-size: 40px;
|
||||
background-position: right center;
|
||||
}
|
||||
|
||||
section ol li:hover code {
|
||||
background-color: #3d3242;
|
||||
}
|
||||
|
||||
section ol li code {
|
||||
background-color: #756180;
|
||||
border: 2px solid #999;
|
||||
cursor: auto;
|
||||
}
|
||||
|
||||
section ol li code:focus {
|
||||
background-color: #b85934;
|
||||
border: 2px solid #e18a42;
|
||||
}
|
||||
|
||||
section ol li code:hover {
|
||||
border: 2px solid #e18a42;
|
||||
}
|
||||
|
||||
section p {
|
||||
background-color: #756180;
|
||||
border-radius: 10px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
aside {
|
||||
margin-left: 0px !important;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
aside h4 {
|
||||
font-size: 1.2em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
input[type="email"], input[type="number"], input[type="search"], input[type="text"], input[type="tel"], input[type="url"], input[type="password"], textarea, select {
|
||||
border: 2px solid #756180;
|
||||
border-radius: 10px;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
input[type="email"]:focus, input[type="number"]:focus, input[type="search"]:focus, input[type="text"]:focus, input[type="tel"]:focus, input[type="url"]:focus, input[type="password"]:focus, textarea:focus, select:focus {
|
||||
border: 2px solid #e18a42;
|
||||
}
|
||||
|
||||
.tcenter {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.tleft {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.tright {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.fleft {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.nomargin {
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
img.flag {
|
||||
width: 24px;
|
||||
padding: 2px;
|
||||
margin-right: 10px;
|
||||
margin-bottom: 20px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
img.active-flag {
|
||||
border: 2px solid #756180;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
|
||||
.onoffswitch {
|
||||
position: relative; width: 110px;
|
||||
-webkit-user-select:none; -moz-user-select:none; -ms-user-select: none;
|
||||
}
|
||||
.onoffswitch-checkbox {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
.onoffswitch-label {
|
||||
display: block; overflow: hidden; cursor: pointer;
|
||||
border: 0px solid #999999; border-radius: 17px;
|
||||
}
|
||||
.onoffswitch-inner {
|
||||
display: block; width: 200%; margin-left: -100%;
|
||||
transition: margin 0.3s ease-in 0s;
|
||||
}
|
||||
.onoffswitch-inner:before, .onoffswitch-inner:after {
|
||||
display: block; float: left; width: 50%; height: 25px; padding: 0; line-height: 25px;
|
||||
font-size: 11px; color: white; font-family: Trebuchet, Arial, sans-serif; font-weight: bold;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.onoffswitch-inner:before {
|
||||
content: "REQUIRED";
|
||||
padding-left: 10px;
|
||||
background-color: #3E7C52; color: #FFFFFF;
|
||||
}
|
||||
.onoffswitch-inner:after {
|
||||
content: "EXCLUDED";
|
||||
padding-right: 10px;
|
||||
background-color: #B15A69; color: #EEEEEE;
|
||||
text-align: right;
|
||||
}
|
||||
.onoffswitch-switch {
|
||||
display: block; width: 24px; margin: 0.5px;
|
||||
background: #FFFFFF;
|
||||
position: absolute; top: 0; bottom: 0;
|
||||
right: 86px;
|
||||
border: 0px solid #999999; border-radius: 17px;
|
||||
transition: all 0.3s ease-in 0s;
|
||||
}
|
||||
.onoffswitch-checkbox:checked + .onoffswitch-label .onoffswitch-inner {
|
||||
margin-left: 0;
|
||||
}
|
||||
.onoffswitch-checkbox:checked + .onoffswitch-label .onoffswitch-switch {
|
||||
right: 0px;
|
||||
}
|
||||
|
||||
.button-primary {
|
||||
background-color: #b85934 !important;
|
||||
border: 0px !important;
|
||||
}
|
||||
|
||||
.button-primary:hover {
|
||||
background-color: #e18a42 !important;
|
||||
}
|
||||
|
||||
.vendor-icon {
|
||||
vertical-align: middle;
|
||||
margin-right: 10px;
|
||||
margin-top: -1px;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
.vendor-full {
|
||||
border-bottom: 5px solid #b6cca1;
|
||||
}
|
||||
|
||||
.vendor-unknown {
|
||||
border-bottom: 5px solid #555;
|
||||
}
|
||||
|
||||
.vendor-partial {
|
||||
border-bottom: 5px solid #ffd37f;
|
||||
}
|
||||
|
||||
.vendor-bad {
|
||||
border-bottom: 5px solid #fbb4a8;
|
||||
}
|
||||