diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 0000000000..fab942429d --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,8 @@ +c14f5eeca042232025797990396232e4feab53e7 +d6881e833da0b6278313870d37d42ca6ece8686a +892be5444c5bd01a601c99243ac8d3e7b2f0e779 +d169a964dd7e9ab5c98b35a82bcbc76dc6229a7a +4a6ec11f6f84a3d1fed2ee9b2f85d783ac3daa9d +2c9d6a47f4d24a923069775206bf734feeb14830 +8f1743ed64a2a52c201a1ef5e312b4d3a7a922cf +ad7f030344c405e10cfa7d5cf06d82c121eeb825 diff --git a/.gitignore b/.gitignore index c7e9cf7aad..47c55fe281 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/README.md b/README.md index 69e777d592..87eba9c96a 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ For more information, see * [http://ifcopenshell.org](http://ifcopenshell.org) * [http://academy.ifcopenshell.org](http://academy.ifcopenshell.org) -[![Build Status](https://api.travis-ci.org/IfcOpenShell/IfcOpenShell.png)](https://api.travis-ci.org/IfcOpenShell/IfcOpenShell) +[![Build Status](https://travis-ci.org/IfcOpenShell/IfcOpenShell.svg?branch=v0.6.0)](https://travis-ci.org/IfcOpenShell/IfcOpenShell) Prerequisites ------------- diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt index b9ff0048af..70d85bb840 100644 --- a/cmake/CMakeLists.txt +++ b/cmake/CMakeLists.txt @@ -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") diff --git a/nix/build-all.py b/nix/build-all.py index b9a100f672..b917327218 100644 --- a/nix/build-all.py +++ b/nix/build-all.py @@ -23,7 +23,7 @@ # This script builds IfcOpenShell and its dependencies # # # # Prerequisites for this script to function correctly: # -# * git * bzip2 * tar * c(++) compilers * yacc * autoconf # +# * cmake * git * bzip2 * tar * c(++) compilers * yacc * autoconf # # # # if building with USE_OCCT additionally: # # * freetype * glx.h # @@ -38,15 +38,15 @@ # * libffi(-dev[el]) # # # # on debian 7.8 these can be obtained with: # -# $ apt-get install git gcc g++ autoconf bison bzip2 # +# $ apt-get install git gcc g++ autoconf bison bzip2 cmake # # libfreetype6-dev mesa-common-dev libffi-dev libfontconfig1-dev # # # # on ubuntu 14.04: # -# $ apt-get install git gcc g++ autoconf bison make # +# $ apt-get install git gcc g++ autoconf bison make cmake # # libfreetype6-dev mesa-common-dev libffi-dev libfontconfig1-dev # # # # on OS X El Capitan with homebrew: # -# $ brew install git bison autoconf automake freetype libffi # +# $ brew install git bison autoconf automake freetype libffi cmake # # # ############################################################################### @@ -79,7 +79,7 @@ ch.setLevel(logging.INFO) logger.addHandler(ch) PROJECT_NAME="IfcOpenShell" -PYTHON_VERSIONS=["2.7.16", "3.2.6", "3.3.6", "3.4.6", "3.5.3", "3.6.2", "3.7.3", "3.8.2"] +PYTHON_VERSIONS=["2.7.16", "3.2.6", "3.3.6", "3.4.6", "3.5.3", "3.6.2", "3.7.3", "3.8.6", "3.9.1"] JSON_VERSION="v3.6.1" OCE_VERSION="0.18" # OCCT_VERSION="7.1.0" @@ -87,13 +87,11 @@ OCE_VERSION="0.18" # OCCT_VERSION="7.2.0" # OCCT_HASH="88af392" OCCT_VERSION="7.3.0p3" -BOOST_VERSION="1.59.0" +BOOST_VERSION="1.71.0" #PCRE_VERSION="8.39" PCRE_VERSION="8.41" #LIBXML2_VERSION="2.9.3" LIBXML2_VERSION="2.9.9" -CMAKE_VERSION="3.4.3" -#CMAKE_VERSION="3.14.5" SWIG_VERSION="3.0.12" #SWIG_VERSION="4.0.0" #OPENCOLLADA_VERSION="v1.6.63" @@ -159,6 +157,13 @@ TOOLSET = None if get_os() == "Darwin": # C++11 features used in OCCT 7+ need a more recent stdlib TOOLSET = "10.9" if USE_OCCT else "10.6" + +# python 3.4 doesn't seem to build anymore on recent versions of clang +if get_os() == "Darwin": + try: + PYTHON_VERSIONS.remove("3.4.6") + except ValueError as e: + pass try: IFCOS_NUM_BUILD_PROCS = os.environ["IFCOS_NUM_BUILD_PROCS"] @@ -259,7 +264,7 @@ print("Building:", *sorted(targets, key=lambda t: len(list(v(t))))) # Check that required tools are in PATH -for cmd in [git, bunzip2, tar, cc, cplusplus, autoconf, automake, yacc, make, "patch"]: +for cmd in [git, bunzip2, tar, cc, cplusplus, autoconf, automake, yacc, make, "patch", "cmake"]: if which(cmd) is None: raise ValueError("Required tool '%s' not installed or not added to PATH" % (cmd,)) @@ -303,10 +308,9 @@ def run(cmds, cwd=None): return stdout.strip() BOOST_VERSION_UNDERSCORE=BOOST_VERSION.replace(".", "_") -CMAKE_VERSION_2=CMAKE_VERSION[:CMAKE_VERSION.rindex('.')] OCE_LOCATION="https://github.com/tpaviot/oce/archive/OCE-%s.tar.gz" % (OCE_VERSION,) -BOOST_LOCATION="http://downloads.sourceforge.net/project/boost/boost/%s/boost_%s.tar.bz2" % (BOOST_VERSION, BOOST_VERSION_UNDERSCORE) +BOOST_LOCATION="https://dl.bintray.com/boostorg/release/%s/source/" % (BOOST_VERSION,) # Helper functions @@ -322,8 +326,7 @@ def run_cmake(arg1, cmake_args, cmake_dir=None, cwd=None): P=".." else: P=cmake_dir - cmake_path= os.path.join(DEPS_DIR, "install", "cmake-%s" % (CMAKE_VERSION,), "bin", "cmake") - run([cmake_path, P]+cmake_args+["-DCMAKE_BUILD_TYPE=%s" % (BUILD_CFG,)], cwd=cwd) + run(["cmake", P]+cmake_args+["-DCMAKE_BUILD_TYPE=%s" % (BUILD_CFG,)], cwd=cwd) def git_clone_or_pull_repository(clone_url, target_dir, revision=None): """Lazily clones the `git` repository denoted by `clone_url` into @@ -424,7 +427,7 @@ def build_dependency(name, mode, build_tool_args, download_url, download_name, d else: raise ValueError() logger.info("\rBuilding %s... " % (name,)) - run([make, "-j%s" % (IFCOS_NUM_BUILD_PROCS,)], cwd=extract_build_dir) + run([make, "-j%s" % (IFCOS_NUM_BUILD_PROCS,), "VERBOSE=1"], cwd=extract_build_dir) logger.info( "\rInstalling %s... " % (name,)) run([make, "install"], cwd=extract_build_dir) logger.info( "\rInstalled %s \n" % (name,)) @@ -445,7 +448,10 @@ cecho("Collecting dependencies:", GREEN) ADDITIONAL_ARGS=[] BOOST_ADDRESS_MODEL=[] if TARGET_ARCH == "i686" and run([uname, "-m"]).strip() == "x86_64": - ADDITIONAL_ARGS=["-m32", "-arch i386"] + if get_os() == "Darwin": + ADDITIONAL_ARGS=["-m32", "-arch i386"] + else: + ADDITIONAL_ARGS=["-m32"] BOOST_ADDRESS_MODEL=["architecture=x86", "address-model=32"] if get_os() == "Darwin": @@ -484,25 +490,8 @@ os.environ["CFLAGS"] = CFLAGS os.environ["LDFLAGS"] = LDFLAGS # Some dependencies need a more recent CMake version than most distros provide -build_dependency(name="cmake-%s" % (CMAKE_VERSION,), mode="autoconf", build_tool_args=[], download_url="https://cmake.org/files/v%s" % (CMAKE_VERSION_2,), download_name="cmake-%s.tar.gz" % (CMAKE_VERSION,)) - -# Extract compiler flags from CMake to harmonize settings with other autoconf dependencies -CMAKE_FLAG_EXTRACT_DIR="ifcopenshell_cmake_test_%s" % (time.time(),) -# was sp.check_output([bash, "-c", "cat /dev/urandom | env LC_CTYPE=C tr -dc 'a-zA-Z0-9' | head -c 32"]), in bash script, unclear what the exact required format is and whether it's needed -if os.path.exists(CMAKE_FLAG_EXTRACT_DIR): - shutil.rmtree(CMAKE_FLAG_EXTRACT_DIR) -os.makedirs(CMAKE_FLAG_EXTRACT_DIR) -BUILD_CFG_UPPER=BUILD_CFG.upper() -for FL in ["C", "CXX"]: - run([bash, "-c", """echo " - message(\"\${CMAKE_%s_FLAGS_%s}\") - " > CMakeLists.txt""" % (FL, BUILD_CFG_UPPER)], cwd=CMAKE_FLAG_EXTRACT_DIR) - FL="%sFLAGS" % (FL,) - FLM="%sFLAGS_MINIMAL" % (FL,) -# @TODO: bash code unclear -# exec("%sFLAGS=%s" % (FL, sp.check_output([os.path.join(DEPS_DIR, "install", "cmake-%s" % (CMAKE_VERSION,), "bin", "cmake"), "." -# declare ${FL}FLAGS_MINIMAL="`$DEPS_DIR/install/cmake-$CMAKE_VERSION/bin/cmake . 2>&1 >/dev/null` ${!FLM}" -shutil.rmtree(CMAKE_FLAG_EXTRACT_DIR) +# @tfk: this is no longer needed +# build_dependency(name="cmake-%s" % (CMAKE_VERSION,), mode="autoconf", build_tool_args=[], download_url="https://cmake.org/files/v%s" % (CMAKE_VERSION_2,), download_name="cmake-%s.tar.gz" % (CMAKE_VERSION,)) if "json" in targets: json_url = "https://github.com/nlohmann/json/releases/download/{JSON_VERSION}/json.hpp".format(**locals()) @@ -527,7 +516,7 @@ if "swig" in targets: build_dependency( name="swig", mode="autoconf", - build_tool_args=["--with-pcre-prefix={DEPS_DIR}/install/pcre-{PCRE_VERSION}".format(**locals())], + build_tool_args=["--disable-ccache", "--with-pcre-prefix={DEPS_DIR}/install/pcre-{PCRE_VERSION}".format(**locals())], download_url="https://github.com/swig/swig.git", download_name="swig", download_tool=download_tool_git, @@ -598,7 +587,7 @@ if "OpenCOLLADA" in targets: download_url="https://github.com/KhronosGroup/OpenCOLLADA.git", download_name="OpenCOLLADA", download_tool=download_tool_git, - patch="./patches/opencollada/pr622.patch", + patch="./patches/opencollada/pr622_and_disable_subdirs.patch", revision=OPENCOLLADA_VERSION ) @@ -662,7 +651,7 @@ if "boost" in targets: list(map(str_concat("cxxflags"), CXXFLAGS.strip().split(' '))) + \ list(map(str_concat("linkflags"), LDFLAGS.strip().split(' '))) + \ ["stage", "-s", "NO_BZIP2=1"], - download_url="http://downloads.sourceforge.net/project/boost/boost/{BOOST_VERSION}/".format(**locals()), + download_url=BOOST_LOCATION, download_name="boost_{BOOST_VERSION_UNDERSCORE}.tar.bz2".format(**locals()) ) @@ -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: diff --git a/nix/patches/opencollada/pr622_and_disable_subdirs.patch b/nix/patches/opencollada/pr622_and_disable_subdirs.patch new file mode 100644 index 0000000000..854b023af1 --- /dev/null +++ b/nix/patches/opencollada/pr622_and_disable_subdirs.patch @@ -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 ++#include + #include + #include + #include diff --git a/nix/patches/pr622.patch b/nix/patches/pr622.patch new file mode 100644 index 0000000000..8a5bca16f6 --- /dev/null +++ b/nix/patches/pr622.patch @@ -0,0 +1,22 @@ +From a0deb4ce8b43cf3c8b8c0a4225c6be5296446dbd Mon Sep 17 00:00:00 2001 +From: Adam Eri +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 ++#include + #include + #include + #include diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000000..e34796ec5f --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,2 @@ +[tool.black] +line-length = 120 \ No newline at end of file diff --git a/src/.gitattributes b/src/.gitattributes new file mode 100644 index 0000000000..1f5bf09722 --- /dev/null +++ b/src/.gitattributes @@ -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 . diff --git a/src/bcf/README.md b/src/bcf/README.md new file mode 100644 index 0000000000..90c3fd71ca --- /dev/null +++ b/src/bcf/README.md @@ -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) +``` diff --git a/src/bcf/bcf/bcfxml.py b/src/bcf/bcf/bcfxml.py new file mode 100644 index 0000000000..c0791f98fd --- /dev/null +++ b/src/bcf/bcf/bcfxml.py @@ -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() diff --git a/src/bcf/bcf/data.py b/src/bcf/bcf/data.py new file mode 100644 index 0000000000..7fd23638be --- /dev/null +++ b/src/bcf/bcf/data.py @@ -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 = [] diff --git a/src/bcf/bcf/xsd/markup.xsd b/src/bcf/bcf/xsd/markup.xsd new file mode 100644 index 0000000000..037464ee8f --- /dev/null +++ b/src/bcf/bcf/xsd/markup.xsd @@ -0,0 +1,154 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/bcf/bcf/xsd/project.xsd b/src/bcf/bcf/xsd/project.xsd new file mode 100644 index 0000000000..8203889b6c --- /dev/null +++ b/src/bcf/bcf/xsd/project.xsd @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/bcf/bcf/xsd/version.xsd b/src/bcf/bcf/xsd/version.xsd new file mode 100644 index 0000000000..55cbd2be90 --- /dev/null +++ b/src/bcf/bcf/xsd/version.xsd @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/bcf/bcf/xsd/visinfo.xsd b/src/bcf/bcf/xsd/visinfo.xsd new file mode 100644 index 0000000000..c54cbf8dd5 --- /dev/null +++ b/src/bcf/bcf/xsd/visinfo.xsd @@ -0,0 +1,191 @@ + + + + + + VisualizationInfo documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + view's visible size in meters + + + + + + + + + + + + + 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. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/examples/ifc_curve_rebar.cpp b/src/examples/ifc_curve_rebar.cpp index befd7bfdf7..2ba269e015 100644 --- a/src/examples/ifc_curve_rebar.cpp +++ b/src/examples/ifc_curve_rebar.cpp @@ -1,143 +1,143 @@ -/******************************************************************************** -* * -* This file is part of IfcOpenShell. * -* * -* IfcOpenShell is free software: you can redistribute it and/or modify * -* it under the terms of the Lesser GNU General Public License as published by * -* the Free Software Foundation, either version 3.0 of the License, or * -* (at your option) any later version. * -* * -* IfcOpenShell is distributed in the hope that it will be useful, * -* but WITHOUT ANY WARRANTY; without even the implied warranty of * -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * -* Lesser GNU General Public License for more details. * -* * -* You should have received a copy of the Lesser GNU General Public License * -* along with this program. If not, see . * -* * -********************************************************************************/ - -/******************************************************************************** -* * -* Example of curve rebar. * -* * -********************************************************************************/ - -#include -#include -#include - -#include "ifcparse\Ifc2x3.h" -#include "ifcparse\IfcUtil.h" -#include "ifcparse\IfcHierarchyHelper.h" -#include "ifcgeom\IfcGeom.h" - -typedef std::string S; -typedef IfcParse::IfcGlobalId guid; -boost::none_t const null = boost::none; - -void create_curve_rebar(IfcHierarchyHelper& file) -{ - int dia = 24; - int R = 3 * dia; - int length = 12 * dia; - - double crossSectionarea = M_PI * (dia / 2) * 2; - IfcSchema::IfcReinforcingBar* rebar = new IfcSchema::IfcReinforcingBar( - guid(), 0, S("test"), null, - null, 0, 0, - null, S("SR24"), //SteelGrade - dia, //diameter - crossSectionarea, //crossSectionarea = math.pi*(12.0/2)**2 - 0, - IfcSchema::IfcReinforcingBarRoleEnum::IfcReinforcingBarRoleEnum::IfcReinforcingBarRole_LIGATURE, - IfcSchema::IfcReinforcingBarSurfaceEnum::IfcReinforcingBarSurfaceEnum::IfcReinforcingBarSurface_PLAIN //PLAIN or TEXTURED - ); - - file.addBuildingProduct(rebar); - rebar->setOwnerHistory(file.getSingle()); - - IfcSchema::IfcCompositeCurveSegment::list::ptr segments(new IfcSchema::IfcCompositeCurveSegment::list()); - - IfcSchema::IfcCartesianPoint* p1 = file.addTriplet(0, 0, 1000.); - IfcSchema::IfcCartesianPoint* p2 = file.addTriplet(0, 0, 0); - IfcSchema::IfcCartesianPoint* p3 = file.addTriplet(0, R, 0); - IfcSchema::IfcCartesianPoint* p4 = file.addTriplet(0, R, -R); - IfcSchema::IfcCartesianPoint* p5 = file.addTriplet(0, R + length, -R); - - /*first segment - line */ - IfcSchema::IfcCartesianPoint::list::ptr points1(new IfcSchema::IfcCartesianPoint::list()); - points1->push(p1); - points1->push(p2); - file.addEntities(points1->generalize()); - IfcSchema::IfcPolyline* poly1 = new IfcSchema::IfcPolyline(points1); - file.addEntity(poly1); - - IfcSchema::IfcCompositeCurveSegment* segment1 = new IfcSchema::IfcCompositeCurveSegment(IfcSchema::IfcTransitionCode::IfcTransitionCode_CONTINUOUS, true, poly1); - file.addEntity(segment1); - segments->push(segment1); - - /*second segment - arc */ - IfcSchema::IfcAxis2Placement3D* axis1 = new IfcSchema::IfcAxis2Placement3D(p3, file.addTriplet(1, 0, 0), file.addTriplet(0, 1, 0)); - file.addEntity(axis1); - IfcSchema::IfcCircle* circle = new IfcSchema::IfcCircle(axis1, R); - file.addEntity(circle); - - IfcEntityList::ptr trim1(new IfcEntityList); - IfcEntityList::ptr trim2(new IfcEntityList); - - trim1->push(new IfcSchema::IfcParameterValue(180)); - trim1->push(p2); - - trim2->push(new IfcSchema::IfcParameterValue(270)); - trim2->push(p4); - IfcSchema::IfcTrimmedCurve* trimmed_curve = new IfcSchema::IfcTrimmedCurve(circle, trim1, trim2, false, IfcSchema::IfcTrimmingPreference::IfcTrimmingPreference_PARAMETER); - file.addEntity(trimmed_curve); - - IfcSchema::IfcCompositeCurveSegment* segment2 = new IfcSchema::IfcCompositeCurveSegment(IfcSchema::IfcTransitionCode::IfcTransitionCode_CONTSAMEGRADIENT, false, trimmed_curve); - file.addEntity(segment2); - segments->push(segment2); - - /*third segment - line */ - IfcSchema::IfcCartesianPoint::list::ptr points2(new IfcSchema::IfcCartesianPoint::list()); - points2->push(p4); - points2->push(p5); - file.addEntities(points2->generalize()); - IfcSchema::IfcPolyline* poly2 = new IfcSchema::IfcPolyline(points2); - file.addEntity(poly2); - - IfcSchema::IfcCompositeCurveSegment* segment3 = new IfcSchema::IfcCompositeCurveSegment(IfcSchema::IfcTransitionCode::IfcTransitionCode_CONTINUOUS, true, poly2); - file.addEntity(segment3); - segments->push(segment3); - - IfcSchema::IfcCompositeCurve* curve = new IfcSchema::IfcCompositeCurve(segments, false); - file.addEntity(curve); - - IfcSchema::IfcSweptDiskSolid* solid = new IfcSchema::IfcSweptDiskSolid(curve, dia / 2, null, 0, 1); - - IfcSchema::IfcRepresentation::list::ptr reps(new IfcSchema::IfcRepresentation::list()); - IfcSchema::IfcRepresentationItem::list::ptr items(new IfcSchema::IfcRepresentationItem::list()); - items->push(solid); - IfcSchema::IfcShapeRepresentation* rep = new IfcSchema::IfcShapeRepresentation( - file.getSingle(), S("Body"), S("AdvancedSweptSolid"), items); - reps->push(rep); - - IfcSchema::IfcProductDefinitionShape* shape = new IfcSchema::IfcProductDefinitionShape(null, null, reps); - file.addEntity(shape); - - rebar->setRepresentation(shape); - - IfcSchema::IfcObjectPlacement* storey_placement = file.getSingle()->ObjectPlacement(); - rebar->setObjectPlacement(file.addLocalPlacement(storey_placement, 0, 0, 0)); -} - -int main() -{ - IfcHierarchyHelper file; - file.header().file_name().name("ifc_curve_rebar.ifc"); - create_curve_rebar(file); - std::ofstream f("ifc_curve_rebar.ifc"); - f << file; - - return 0; -} +/******************************************************************************** +* * +* This file is part of IfcOpenShell. * +* * +* IfcOpenShell is free software: you can redistribute it and/or modify * +* it under the terms of the Lesser GNU General Public License as published by * +* the Free Software Foundation, either version 3.0 of the License, or * +* (at your option) any later version. * +* * +* IfcOpenShell is distributed in the hope that it will be useful, * +* but WITHOUT ANY WARRANTY; without even the implied warranty of * +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +* Lesser GNU General Public License for more details. * +* * +* You should have received a copy of the Lesser GNU General Public License * +* along with this program. If not, see . * +* * +********************************************************************************/ + +/******************************************************************************** +* * +* Example of curve rebar. * +* * +********************************************************************************/ + +#include +#include +#include + +#include "ifcparse\Ifc2x3.h" +#include "ifcparse\IfcUtil.h" +#include "ifcparse\IfcHierarchyHelper.h" +#include "ifcgeom\IfcGeom.h" + +typedef std::string S; +typedef IfcParse::IfcGlobalId guid; +boost::none_t const null = boost::none; + +void create_curve_rebar(IfcHierarchyHelper& file) +{ + int dia = 24; + int R = 3 * dia; + int length = 12 * dia; + + double crossSectionarea = M_PI * (dia / 2) * 2; + IfcSchema::IfcReinforcingBar* rebar = new IfcSchema::IfcReinforcingBar( + guid(), 0, S("test"), null, + null, 0, 0, + null, S("SR24"), //SteelGrade + dia, //diameter + crossSectionarea, //crossSectionarea = math.pi*(12.0/2)**2 + 0, + IfcSchema::IfcReinforcingBarRoleEnum::IfcReinforcingBarRoleEnum::IfcReinforcingBarRole_LIGATURE, + IfcSchema::IfcReinforcingBarSurfaceEnum::IfcReinforcingBarSurfaceEnum::IfcReinforcingBarSurface_PLAIN //PLAIN or TEXTURED + ); + + file.addBuildingProduct(rebar); + rebar->setOwnerHistory(file.getSingle()); + + IfcSchema::IfcCompositeCurveSegment::list::ptr segments(new IfcSchema::IfcCompositeCurveSegment::list()); + + IfcSchema::IfcCartesianPoint* p1 = file.addTriplet(0, 0, 1000.); + IfcSchema::IfcCartesianPoint* p2 = file.addTriplet(0, 0, 0); + IfcSchema::IfcCartesianPoint* p3 = file.addTriplet(0, R, 0); + IfcSchema::IfcCartesianPoint* p4 = file.addTriplet(0, R, -R); + IfcSchema::IfcCartesianPoint* p5 = file.addTriplet(0, R + length, -R); + + /*first segment - line */ + IfcSchema::IfcCartesianPoint::list::ptr points1(new IfcSchema::IfcCartesianPoint::list()); + points1->push(p1); + points1->push(p2); + file.addEntities(points1->generalize()); + IfcSchema::IfcPolyline* poly1 = new IfcSchema::IfcPolyline(points1); + file.addEntity(poly1); + + IfcSchema::IfcCompositeCurveSegment* segment1 = new IfcSchema::IfcCompositeCurveSegment(IfcSchema::IfcTransitionCode::IfcTransitionCode_CONTINUOUS, true, poly1); + file.addEntity(segment1); + segments->push(segment1); + + /*second segment - arc */ + IfcSchema::IfcAxis2Placement3D* axis1 = new IfcSchema::IfcAxis2Placement3D(p3, file.addTriplet(1, 0, 0), file.addTriplet(0, 1, 0)); + file.addEntity(axis1); + IfcSchema::IfcCircle* circle = new IfcSchema::IfcCircle(axis1, R); + file.addEntity(circle); + + IfcEntityList::ptr trim1(new IfcEntityList); + IfcEntityList::ptr trim2(new IfcEntityList); + + trim1->push(new IfcSchema::IfcParameterValue(180)); + trim1->push(p2); + + trim2->push(new IfcSchema::IfcParameterValue(270)); + trim2->push(p4); + IfcSchema::IfcTrimmedCurve* trimmed_curve = new IfcSchema::IfcTrimmedCurve(circle, trim1, trim2, false, IfcSchema::IfcTrimmingPreference::IfcTrimmingPreference_PARAMETER); + file.addEntity(trimmed_curve); + + IfcSchema::IfcCompositeCurveSegment* segment2 = new IfcSchema::IfcCompositeCurveSegment(IfcSchema::IfcTransitionCode::IfcTransitionCode_CONTSAMEGRADIENT, false, trimmed_curve); + file.addEntity(segment2); + segments->push(segment2); + + /*third segment - line */ + IfcSchema::IfcCartesianPoint::list::ptr points2(new IfcSchema::IfcCartesianPoint::list()); + points2->push(p4); + points2->push(p5); + file.addEntities(points2->generalize()); + IfcSchema::IfcPolyline* poly2 = new IfcSchema::IfcPolyline(points2); + file.addEntity(poly2); + + IfcSchema::IfcCompositeCurveSegment* segment3 = new IfcSchema::IfcCompositeCurveSegment(IfcSchema::IfcTransitionCode::IfcTransitionCode_CONTINUOUS, true, poly2); + file.addEntity(segment3); + segments->push(segment3); + + IfcSchema::IfcCompositeCurve* curve = new IfcSchema::IfcCompositeCurve(segments, false); + file.addEntity(curve); + + IfcSchema::IfcSweptDiskSolid* solid = new IfcSchema::IfcSweptDiskSolid(curve, dia / 2, null, 0, 1); + + IfcSchema::IfcRepresentation::list::ptr reps(new IfcSchema::IfcRepresentation::list()); + IfcSchema::IfcRepresentationItem::list::ptr items(new IfcSchema::IfcRepresentationItem::list()); + items->push(solid); + IfcSchema::IfcShapeRepresentation* rep = new IfcSchema::IfcShapeRepresentation( + file.getSingle(), S("Body"), S("AdvancedSweptSolid"), items); + reps->push(rep); + + IfcSchema::IfcProductDefinitionShape* shape = new IfcSchema::IfcProductDefinitionShape(null, null, reps); + file.addEntity(shape); + + rebar->setRepresentation(shape); + + IfcSchema::IfcObjectPlacement* storey_placement = file.getSingle()->ObjectPlacement(); + rebar->setObjectPlacement(file.addLocalPlacement(storey_placement, 0, 0, 0)); +} + +int main() +{ + IfcHierarchyHelper file; + file.header().file_name().name("ifc_curve_rebar.ifc"); + create_curve_rebar(file); + std::ofstream f("ifc_curve_rebar.ifc"); + f << file; + + return 0; +} diff --git a/src/ifc2ca/ca2ifc.py b/src/ifc2ca/ca2ifc.py index c288fe707a..566f446148 100644 --- a/src/ifc2ca/ca2ifc.py +++ b/src/ifc2ca/ca2ifc.py @@ -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]) + 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]) 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): + 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']))] - for i,v in enumerate(verts): + 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() diff --git a/src/ifc2ca/ifc2ca.py b/src/ifc2ca/ifc2ca.py index f625201176..7e20641dc6 100644 --- a/src/ifc2ca/ifc2ca.py +++ b/src/ifc2ca/ifc2ca.py @@ -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,26 +214,26 @@ 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: - xAxis = np.array(axes.RefDirection.DirectionRatios) # this can be not accurate (in the xz plane) + xAxis = np.array(axes.RefDirection.DirectionRatios) # this can be not accurate (in the xz plane) zAxis = np.array(axes.Axis.DirectionRatios) zAxis /= np.linalg.norm(zAxis) yAxis = np.cross(zAxis, xAxis) @@ -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) @@ -304,13 +322,13 @@ class IFC2CA: xAxis /= np.linalg.norm(xAxis) return [xAxis.tolist(), yAxis.tolist(), zAxis.tolist()] - else: # return None and copy the elements orientation + else: # return None and copy the elements orientation return None def get_1D_orientation(self, geometry, zAxis): xAxis = np.array(geometry[1]) - np.array(geometry[0]) xAxis /= np.linalg.norm(xAxis) - zAxis = np.array(zAxis.DirectionRatios) # this can be not strictly perpendicular (in the xz plane) + zAxis = np.array(zAxis.DirectionRatios) # this can be not strictly perpendicular (in the xz plane) yAxis = np.cross(zAxis, xAxis) yAxis /= np.linalg.norm(yAxis) zAxis = np.cross(xAxis, yAxis) @@ -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) @@ -329,17 +347,17 @@ class IFC2CA: return orientation def transform_vectors(self, geometry, trsf, include_translation=True): - if not any(isinstance(el, list) for el in geometry): # single point which contains no list + if not any(isinstance(el, list) for el in geometry): # single point which contains no list geometry = [geometry] 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 + if len(globalGeometry) == 1: # single point globalGeometry = globalGeometry[0] return globalGeometry @@ -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) - } - } for rel in itemList] + "pointOnElement": self.get_coordinate(rel.ConnectionConstraint.PointOnRelatingElement), + }, + } + 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: - f.write(json.dumps(ifc2ca.result, indent = 4)) + with open(BASE_PATH + fileName + ".json", "w") as f: + f.write(json.dumps(ifc2ca.result, indent=4)) diff --git a/src/ifc2ca/scriptCodeAster.py b/src/ifc2ca/scriptCodeAster.py index 64d210195d..815f6f0d76 100644 --- a/src/ifc2ca/scriptCodeAster.py +++ b/src/ifc2ca/scriptCodeAster.py @@ -1,951 +1,903 @@ -import json -import numpy as np -import itertools - -flatten = itertools.chain.from_iterable - -class COMMANDFILE: - def __init__(self, dataFilename, asterFilename): - self.dataFilename = dataFilename - self.asterFilename = asterFilename - 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]) - - def create(self): - - AccelOfGravity = 9.806 # m/sec^2 - - # Read data from input file - with open(self.dataFilename) as dataFile: - data = json.load(dataFile) - - 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'] = [] - self.calculateRestraints(conn) - for el in elements: - for rel in 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_' - rel['springGroupName'] = self.getGroupName(rel['relatingElement']) + '_1DS_' + self.getGroupName(rel['relatedConnection']) - if conn['geometryType'] == 'line': - rel['conn_string'] = '_1DC_' - rel['springGroupName'] = None - if conn['geometryType'] == 'surface': - rel['conn_string'] = '_2DC_' - rel['springGroupName'] = None - - rel['groupName1'] = self.getGroupName(rel['relatingElement']) + rel['conn_string'] + self.getGroupName(rel['relatedConnection']) - if rel['eccentricity']: - rel['groupName2'] = self.getGroupName(rel['relatedConnection']) + '_0DC_' + self.getGroupName(rel['relatingElement']) - rel['index'] = len(conn['relatedElements']) + 1 - rel['unifiedGroupName'] = self.getGroupName(rel['relatedConnection']) + '_0DC_%g' % rel['index'] - else: - rel['groupName2'] = self.getGroupName(rel['relatedConnection']) - self.calculateConstraints(rel) - conn['relatedElements'].append(rel) - # End <-- - - materials = data['db']['materials'] - profiles = data['db']['profiles'] - - edgeGroupNames = tuple([self.getGroupName(el['ifcName']) for el in elements if el['geometryType'] == 'line']) - faceGroupNames = tuple([self.getGroupName(el['ifcName']) for el in elements if el['geometryType'] == 'surface']) - point0DGroupNames = tuple([self.getGroupName(el['ifcName']) + '_0D' for el in connections if el['geometryType'] == 'point']) - spring1DGroupNames = tuple(flatten([[rel['springGroupName'] for rel in el['connections'] if rel['springGroupName']] for el in elements])) - point1DGroupNames = tuple([self.getGroupName(el['ifcName']) + '_0D' for el in connections if el['geometryType'] == 'line']) - - unifiedConnection = False - rigidLinkGroupNames = [] - for conn in connections: - conn['unifiedGroupNames'] = [rel['unifiedGroupName'] for rel in conn['relatedElements'] if rel['eccentricity']] - # if not conn['appliedCondition'] and len(conn['unifiedGroupNames']) == 1: - # conn['appliedCondition'] = { - # 'dx': True, - # 'dy': True, - # 'dz': True - # } - if len(conn['unifiedGroupNames']) >= 1: - conn['unifiedGroupNames'].insert(0, self.getGroupName(conn['ifcName'])) - conn['unifiedGroupNames'] = tuple(conn['unifiedGroupNames']) - unifiedConnection = True - rigidLinkGroupNames.extend([self.getGroupName(rel['relatingElement']) + '_1DR_' + self.getGroupName(conn['ifcName']) for rel in conn['relatedElements'] if rel['eccentricity']]) - rigidLinkGroupNames = tuple(rigidLinkGroupNames) - - # Define file to write command file for code_aster - f = open(self.asterFilename, 'w') - - f.write('# Command file generated by IfcOpenShell/ifc2ca scripts\n') - f.write('\n') - - f.write('# Linear Static Analysis With Self-Weight\n') - - f.write( -''' -# STEP: INITIALIZE STUDY -DEBUT( - PAR_LOT = 'NON' -) -''' - ) - - f.write( -''' -# STEP: READ MED FILE -mesh = LIRE_MAILLAGE( - FORMAT = 'MED', - UNITE = 20 -) -''' - ) - - f.write( -''' -# STEP: DEFINE MODEL -model = AFFE_MODELE( - MAILLAGE = mesh, - AFFE = ( - _F( - TOUT = 'OUI', - PHENOMENE = 'MECANIQUE', - MODELISATION = '3D' - ),''' - ) - - if faceGroupNames: - template = \ - ''' - _F( - GROUP_MA = {groupNames}, - PHENOMENE = 'MECANIQUE', - MODELISATION = 'DKT' - ),''' - - context = { - 'groupNames': faceGroupNames - } - - f.write(template.format(**context)) - - if edgeGroupNames: - template = \ - ''' - _F( - GROUP_MA = {groupNames}, - PHENOMENE = 'MECANIQUE', - MODELISATION = 'POU_D_E' - ),''' - - context = { - 'groupNames': edgeGroupNames - } - - f.write(template.format(**context)) - - if point0DGroupNames: - template = \ - ''' - _F( - GROUP_MA = {groupNames}, - PHENOMENE = 'MECANIQUE', - MODELISATION = 'DIS_TR' - ),''' - - context = { - 'groupNames': tuple(flatten([point0DGroupNames, spring1DGroupNames])) - } - - f.write(template.format(**context)) - - if point1DGroupNames: - template = \ - ''' - _F( - GROUP_MA = {groupNames}, - PHENOMENE = 'MECANIQUE', - MODELISATION = 'DIS_TR' - ),''' - - context = { - 'groupNames': point1DGroupNames - } - - f.write(template.format(**context)) - - if rigidLinkGroupNames: - template = \ - ''' - _F( - GROUP_MA = {groupNames}, - PHENOMENE = 'MECANIQUE', - MODELISATION = 'POU_D_E' - ),''' - - context = { - 'groupNames': rigidLinkGroupNames - } - - f.write(template.format(**context)) - - f.write( -''' - ) -)\n -''' - ) - - - f.write('# STEP: DEFINE MATERIALS') - - for i,material in enumerate(materials): - template = \ -''' -{matNameID} = DEFI_MATERIAU( - ELAS = _F( - E = {youngModulus}, - NU = {poissonRatio}, - RHO = {massDensity} - ) -) -''' - if 'poissonRatio' in material['mechProps']: - poissonRatio = material['mechProps']['poissonRatio'] - else: - if 'shearModulus' in material['mechProps']: - poissonRatio = (material['mechProps']['youngModulus'] / 2.0 / material['mechProps']['shearModulus']) - 1 - else: - poissonRatio = 0.0 - - context = { - 'matNameID': 'mat'+ '_%s' % i, - 'youngModulus': float(material['mechProps']['youngModulus']), - 'poissonRatio': float(poissonRatio), - 'massDensity': float(material['commonProps']['massDensity']) - } - - f.write(template.format(**context)) - - - f.write( -''' -material = AFFE_MATERIAU( - MAILLAGE = mesh, - AFFE = (''' - ) - - for i,material in enumerate(materials): - template = \ - ''' - _F( - GROUP_MA = {groupNames}, - MATER = {matNameID}, - ),''' - - context = { - 'groupNames': tuple([self.getGroupName(rel) for rel in material['relatedElements']]), - 'matNameID': 'mat'+ '_%s' % i - } - - f.write(template.format(**context)) - - if rigidLinkGroupNames: - template = \ - ''' - _F( - GROUP_MA = {groupNames}, - MATER = {matNameID}, - ),''' - - context = { - 'groupNames': rigidLinkGroupNames, - 'matNameID': 'mat_0' - } - - f.write(template.format(**context)) - - f.write( -''' - ) -) -''' - ) - - - f.write( -''' -# STEP: DEFINE ELEMENTS -element = AFFE_CARA_ELEM( - MODELE = model, - POUTRE = (''' - ) - - for profile in profiles: - if profile['profileShape'] == 'rectangular' and profile['profileType'] == 'AREA': - template = \ - ''' - _F( - GROUP_MA = {groupNames}, - SECTION = 'RECTANGLE', - CARA = ('HY', 'HZ'), - VALE = {profileDimensions} - ),''' - - context = { - 'groupNames': tuple([self.getGroupName(rel) for rel in profile['relatedElements']]), - 'profileDimensions': (profile['xDim'], profile['yDim']) - } - - f.write(template.format(**context)) - - elif profile['profileShape'] == 'iSymmetrical' and profile['profileType'] == 'AREA': - template = \ - ''' - _F( - GROUP_MA = {groupNames}, - SECTION = 'GENERALE', - CARA = ('A', 'IY', 'IZ', 'JX'), - VALE = {profileProperties} - ),''' - - context = { - 'groupNames': tuple([self.getGroupName(rel) for rel in profile['relatedElements']]), - 'profileProperties': ( - profile['mechProps']['crossSectionArea'], - profile['mechProps']['momentOfInertiaY'], - profile['mechProps']['momentOfInertiaZ'], - profile['mechProps']['torsionalConstantX'] - ) - } - - f.write(template.format(**context)) - - if rigidLinkGroupNames: - template = \ - ''' - _F( - GROUP_MA = {groupNames}, - SECTION = 'RECTANGLE', - CARA = ('HY', 'HZ'), - VALE = {profileDimensions} - ),''' - - context = { - 'groupNames': rigidLinkGroupNames, - 'profileDimensions': (1, 1) - } - - f.write(template.format(**context)) - - f.write( -''' - ), - COQUE = (''' - ) - - for el in [el for el in elements if el['geometryType'] == 'surface']: - - template = \ - ''' - _F( - GROUP_MA = '{groupName}', - EPAIS = {thickness}, - VECTEUR = {localAxisX} - ),''' - - context = { - 'groupName': self.getGroupName(el['ifcName']), - 'thickness': el['thickness'], - 'localAxisX': tuple(el['orientation'][0]) - } - - f.write(template.format(**context)) - - f.write( -''' - ),''' - ) - f.write( -''' - DISCRET = (''' - ) - - for conn in [conn for conn in connections if conn['geometryType'] == 'point']: - - template = \ - ''' - _F( - GROUP_MA = '{groupName}', - CARA = 'K_TR_D_N', - VALE = {stiffnesses}, - REPERE = 'LOCAL' - ),''' - - context = { - 'groupName': self.getGroupName(conn['ifcName']) + '_0D', - 'stiffnesses': conn['stiffnesses'] - } - - f.write(template.format(**context)) - - for rel in conn['relatedElements']: - - template = \ - ''' - _F( - GROUP_MA = '{groupName}', - CARA = 'K_TR_D_L', - VALE = {stiffnesses}, - REPERE = 'LOCAL' - ),''' - - context = { - 'groupName': rel['springGroupName'], - 'stiffnesses': rel['stiffnesses'] - } - - f.write(template.format(**context)) - - for conn in [conn for conn in connections if conn['geometryType'] == 'line']: - - template = \ - ''' - _F( - GROUP_MA = '{groupName}', - CARA = 'K_TR_D_N', - VALE = {stiffnesses}, - REPERE = 'LOCAL' - ),''' - - context = { - 'groupName': self.getGroupName(conn['ifcName']) + '_0D', - 'stiffnesses': conn['stiffnesses'] - } - - f.write(template.format(**context)) - - f.write( -''' - ),''' - ) - - f.write( -''' - ORIENTATION = (''' - ) - - for el in [el for el in elements if el['geometryType'] == 'line']: - - template = \ - ''' - _F( - GROUP_MA = '{groupName}', - CARA = 'VECT_Y', - VALE = {localAxisY} - ),''' - - context = { - 'groupName': self.getGroupName(el['ifcName']), - 'localAxisY': tuple(el['orientation'][1]) - } - - f.write(template.format(**context)) - - for conn in [conn for conn in connections if conn['geometryType'] == 'point']: - - template = \ - ''' - _F( - GROUP_MA = '{groupName}', - CARA = 'VECT_X_Y', - VALE = {localAxesXY} - ),''' - - context = { - 'groupName': self.getGroupName(conn['ifcName']) + '_0D', - 'localAxesXY': tuple(conn['orientation'][0] + conn['orientation'][1]) - } - - f.write(template.format(**context)) - - for rel in conn['relatedElements']: - - template = \ - ''' - _F( - GROUP_MA = '{groupName}', - CARA = 'VECT_X_Y', - VALE = {localAxesXY} - ),''' - - context = { - 'groupName': rel['springGroupName'], - 'localAxesXY': tuple(rel['orientation'][0] + rel['orientation'][1]) - } - - f.write(template.format(**context)) - - for conn in [conn for conn in connections if conn['geometryType'] == 'line']: - - template = \ - ''' - _F( - GROUP_MA = '{groupName}', - CARA = 'VECT_X_Y', - VALE = {localAxesXY} - ),''' - - context = { - 'groupName': self.getGroupName(conn['ifcName']) + '_0D', - 'localAxesXY': tuple(conn['orientation'][0] + conn['orientation'][1]) - } - - f.write(template.format(**context)) - - f.write( -''' - ),''' - ) - - f.write( -''' -)\n -''' - ) - - - f.write('# STEP: DEFINE SUPPORTS AND CONSTRAINTS') - - f.write( -''' -liaisons = AFFE_CHAR_MECA( - MODELE = model, - LIAISON_DDL = (''' - ) - - for conn in [conn for conn in connections if conn['geometryType'] == 'point']: - if conn['appliedCondition']: - for i in range(len(conn['liaisons']['coeffs'])): - template = \ - ''' - _F( - GROUP_NO = {groupNames}, - DDL = {dofs}, - COEF_MULT = {coeffs}, - COEF_IMPO = 0.0 - ),''' - - context = { - 'groupNames': conn['liaisons']['groupNames'], - 'dofs': conn['liaisons']['dofs'][i], - 'coeffs': conn['liaisons']['coeffs'][i] - } - - f.write(template.format(**context)) - - for rel in conn['relatedElements']: - for i in range(len(rel['liaisons']['coeffs'])): - template = \ - ''' - _F( - GROUP_NO = {groupNames}, - DDL = {dofs}, - COEF_MULT = {coeffs}, - COEF_IMPO = 0.0 - ),''' - - context = { - 'groupNames': rel['liaisons']['groupNames'], - 'dofs': rel['liaisons']['dofs'][i], - 'coeffs': rel['liaisons']['coeffs'][i] - } - - f.write(template.format(**context)) - - f.write( - ''' - ),''' - ) - - f.write( - ''' - LIAISON_GROUP = (''' - ) - - for conn in [conn for conn in connections if conn['geometryType'] == 'line']: - if conn['appliedCondition']: - for i in range(len(conn['liaisons']['coeffs'])): - template = \ - ''' - _F( - GROUP_NO_1 = {groupName_1}, - GROUP_NO_2 = {groupName_1}, - DDL_1 = {dofs}, - DDL_2 = {dofs}, - COEF_MULT_1 = {coeffs}, - COEF_MULT_2 = (0.0, 0.0, 0.0), - COEF_IMPO = 0.0 - ),''' - - context = { - 'groupName_1': tuple([conn['liaisons']['groupNames'][0]]), - 'dofs': conn['liaisons']['dofs'][i], - 'coeffs': conn['liaisons']['coeffs'][i] - } - - f.write(template.format(**context)) - - for rel in conn['relatedElements']: - for i in range(len(rel['liaisons']['coeffs'])): - template = \ - ''' - _F( - GROUP_NO_1 = {groupName_1}, - GROUP_NO_2 = {groupName_2}, - DDL_1 = {dofs}, - DDL_2 = {dofs}, - COEF_MULT_1 = {coeffs_1}, - COEF_MULT_2 = {coeffs_2}, - COEF_IMPO = 0.0 - ),''' - - context = { - 'groupName_1': tuple([rel['liaisons']['groupNames'][0]]), - 'groupName_2': tuple([rel['liaisons']['groupNames'][3]]), - 'dofs': tuple(list(rel['liaisons']['dofs'][i])[:3]), - 'coeffs_1': tuple(list(rel['liaisons']['coeffs'][i])[:3]), - 'coeffs_2': tuple(list(rel['liaisons']['coeffs'][i])[3:]), - } - - f.write(template.format(**context)) - - f.write( - ''' - ),''' - ) - - if unifiedConnection: - f.write( - ''' - LIAISON_UNIF = (''' - ) - - for conn in [conn for conn in connections if len(conn['unifiedGroupNames']) > 1]: - template = \ - ''' - _F( - GROUP_NO = {groupNames}, - DDL = ('DX', 'DY', 'DZ', 'DRX', 'DRY', 'DRZ') - ),''' - - context = { - 'groupNames': conn['unifiedGroupNames'] - } - - f.write(template.format(**context)) - - f.write( - ''' - ),''' - ) - - if rigidLinkGroupNames: - f.write( - ''' - LIAISON_SOLIDE = (''' - ) - - for groupName in rigidLinkGroupNames: - template = \ - ''' - _F( - GROUP_MA = '{groupName}' - ),''' - - context = { - 'groupName': groupName - } - - f.write(template.format(**context)) - - f.write( - ''' - ),''' - ) - - f.write( - ''' -) -''' - ) - - template = \ -''' -# STEP: DEFINE LOAD -gravLoad = AFFE_CHAR_MECA( - MODELE = model, - PESANTEUR = _F( - GRAVITE = {AccelOfGravity}, - DIRECTION = (0.0, 0.0, -1.0) - ) -) -''' - context = { - 'AccelOfGravity': AccelOfGravity, - } - - f.write(template.format(**context)) - - - f.write( -''' -# STEP: RUN ANALYSIS -res_Bld = MECA_STATIQUE( - MODELE = model, - CHAM_MATER = material, - CARA_ELEM = element, - EXCIT = ( - _F( - CHARGE = liaisons - ), - _F( - CHARGE = gravLoad - ) - ) -) -''' - ) - - # f.write( - # ''' - # # STEP: POST-PROCESSING - # res_Bld = CALC_CHAMP( - # reuse = res_Bld, - # RESULTAT = res_Bld, - # # CONTRAINTE = ('SIEF_ELNO', 'SIGM_ELNO', 'EFGE_ELNO',), - # FORCE = ('REAC_NODA', 'FORC_NODA',) - # ) - # ''' - # ) - # - # template = \ - # ''' - # # STEP: MASS EXTRACTION FOR EACH ASSEMBLE - # FaceMass = POST_ELEM( - # TITRE = 'TotMass', - # MODELE = model, - # CARA_ELEM = element, - # CHAM_MATER = material, - # MASS_INER = _F( - # GROUP_MA = {massList}, - # ), - # )\n''' - # - # context = { - # 'massList': massList, - # } - # - # f.write(template.format(**context)) - # - # f.write( - # ''' - # IMPR_TABLE( - # UNITE = 10, - # TABLE = FaceMass, - # SEPARATEUR = ',', - # NOM_PARA = ('LIEU', 'MASSE', 'CDG_X', 'CDG_Y', 'CDG_Z'), - # # FORMAT_R = '1PE15.6', - # ) - # ''' - # ) - # - # template = \ - # ''' - # # STEP: REACTION EXTRACTION AT THE BASE - # Reacs = POST_RELEVE_T( - # ACTION = _F( - # INTITULE = 'sumReac', - # GROUP_NO = {groupNames}, - # RESULTAT = res_Bld, - # NOM_CHAM = 'REAC_NODA', - # RESULTANTE = ('DX','DY','DZ',), - # MOMENT = ('DRX','DRY','DRZ',), - # POINT = (0,0,0,), - # OPERATION = 'EXTRACTION' - # ) - # ) - # ''' - # - # context = { - # 'groupNames': point0DGroupNames, - # } - # - # f.write(template.format(**context)) - # - # f.write( - # ''' - # IMPR_TABLE( - # UNITE = 10, - # TABLE = Reacs, - # SEPARATEUR = ',', - # # NOM_PARA = ('INTITULE', 'RESU', 'NOM_CHAM', 'INST', 'DX','DY','DZ'), - # FORMAT_R = '1PE12.3', - # ) - # ''' - # ) - # - f.write( -''' -# STEP: DEFORMED SHAPE EXTRACTION -IMPR_RESU( - FORMAT = 'MED', - UNITE = 80, - RESU = _F( - RESULTAT = res_Bld, - NOM_CHAM = ('DEPL',), # 'REAC_NODA', 'FORC_NODA', - NOM_CHAM_MED = ('Bld_DISP',), # 'Bld_REAC', 'Bld_FORC' - ) -) -''' - ) - - f.write( -''' -# STEP: CONCLUDE STUDY -FIN() -''' - ) - - f.close() - - - def calculateConstraints(self, rel): - gr1 = rel['groupName1'] - gr2 = rel['groupName2'] - o = np.array(rel['orientation']).transpose().tolist() - liaisons = { - 'groupNames': (gr1, gr1, gr1, gr2, gr2, gr2), - 'coeffs': [], - 'dofs': [] - } - stiffnesses = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0] - if not rel['appliedCondition']: - rel['appliedCondition'] = { - 'dx': True, - 'dy': True, - 'dz': True, - 'drx': True, - 'dry': True, - 'drz': True - } - if isinstance(rel['appliedCondition']['dx'], bool) and rel['appliedCondition']['dx']: - liaisons['coeffs'].append((o[0][0], o[1][0], o[2][0], -o[0][0], -o[1][0], -o[2][0])) - liaisons['dofs'].append(('DX', 'DY', 'DZ', 'DX', 'DY', 'DZ')) - elif isinstance(rel['appliedCondition']['dx'], float) and rel['appliedCondition']['dx'] > 0: - stiffnesses[0] = rel['appliedCondition']['dx'] - - if isinstance(rel['appliedCondition']['dy'], bool) and rel['appliedCondition']['dy']: - liaisons['coeffs'].append((o[0][1], o[1][1], o[2][1], -o[0][1], -o[1][1], -o[2][1])) - liaisons['dofs'].append(('DX', 'DY', 'DZ', 'DX', 'DY', 'DZ')) - elif isinstance(rel['appliedCondition']['dy'], float) and rel['appliedCondition']['dy'] > 0: - stiffnesses[1] = rel['appliedCondition']['dy'] - - if isinstance(rel['appliedCondition']['dz'], bool) and rel['appliedCondition']['dz']: - liaisons['coeffs'].append((o[0][2], o[1][2], o[2][2], -o[0][2], -o[1][2], -o[2][2])) - liaisons['dofs'].append(('DX', 'DY', 'DZ', 'DX', 'DY', 'DZ')) - elif isinstance(rel['appliedCondition']['dz'], float) and rel['appliedCondition']['dz'] > 0: - stiffnesses[2] = rel['appliedCondition']['dz'] - - if isinstance(rel['appliedCondition']['drx'], bool) and rel['appliedCondition']['drx']: - liaisons['coeffs'].append((o[0][0], o[1][0], o[2][0], -o[0][0], -o[1][0], -o[2][0])) - liaisons['dofs'].append(('DRX', 'DRY', 'DRZ', 'DRX', 'DRY', 'DRZ')) - elif isinstance(rel['appliedCondition']['drx'], float) and rel['appliedCondition']['drx'] > 0: - stiffnesses[3] = rel['appliedCondition']['drx'] - - if isinstance(rel['appliedCondition']['dry'], bool) and rel['appliedCondition']['dry']: - liaisons['coeffs'].append((o[0][1], o[1][1], o[2][1], -o[0][1], -o[1][1], -o[2][1])) - liaisons['dofs'].append(('DRX', 'DRY', 'DRZ', 'DRX', 'DRY', 'DRZ')) - elif isinstance(rel['appliedCondition']['dry'], float) and rel['appliedCondition']['dry'] > 0: - stiffnesses[4] = rel['appliedCondition']['dry'] - - if isinstance(rel['appliedCondition']['drz'], bool) and rel['appliedCondition']['drz']: - liaisons['coeffs'].append((o[0][2], o[1][2], o[2][2], -o[0][2], -o[1][2], -o[2][2])) - liaisons['dofs'].append(('DRX', 'DRY', 'DRZ', 'DRX', 'DRY', 'DRZ')) - elif isinstance(rel['appliedCondition']['drz'], float) and rel['appliedCondition']['drz'] > 0: - stiffnesses[5] = rel['appliedCondition']['drz'] - - - rel['liaisons'] = liaisons - rel['stiffnesses'] = tuple(stiffnesses) - - def calculateRestraints(self, conn): - group = self.getGroupName(conn['ifcName']) - o = np.array(conn['orientation']).transpose().tolist() - liaisons = { - 'groupNames': (group, group, group), - 'coeffs': [], - 'dofs': [] - } - stiffnesses = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0] - - if not conn['appliedCondition']: - conn['liaisons'] = liaisons - conn['stiffnesses'] = tuple(stiffnesses) - return - - if isinstance(conn['appliedCondition']['dx'], bool) and conn['appliedCondition']['dx']: - liaisons['coeffs'].append((o[0][0], o[1][0], o[2][0])) - liaisons['dofs'].append(('DX', 'DY', 'DZ')) - elif isinstance(conn['appliedCondition']['dx'], float) and conn['appliedCondition']['dx'] > 0: - stiffnesses[0] = conn['appliedCondition']['dx'] - - if isinstance(conn['appliedCondition']['dy'], bool) and conn['appliedCondition']['dy']: - liaisons['coeffs'].append((o[0][1], o[1][1], o[2][1])) - liaisons['dofs'].append(('DX', 'DY', 'DZ')) - elif isinstance(conn['appliedCondition']['dy'], float) and conn['appliedCondition']['dy'] > 0: - stiffnesses[1] = conn['appliedCondition']['dy'] - - if isinstance(conn['appliedCondition']['dz'], bool) and conn['appliedCondition']['dz']: - liaisons['coeffs'].append((o[0][2], o[1][2], o[2][2])) - liaisons['dofs'].append(('DX', 'DY', 'DZ')) - elif isinstance(conn['appliedCondition']['dz'], float) and conn['appliedCondition']['dz'] > 0: - stiffnesses[2] = conn['appliedCondition']['dz'] - - if isinstance(conn['appliedCondition']['drx'], bool) and conn['appliedCondition']['drx']: - liaisons['coeffs'].append((o[0][0], o[1][0], o[2][0])) - liaisons['dofs'].append(('DRX', 'DRY', 'DRZ')) - elif isinstance(conn['appliedCondition']['drx'], float) and conn['appliedCondition']['drx'] > 0: - stiffnesses[3] = conn['appliedCondition']['drx'] - - if isinstance(conn['appliedCondition']['dry'], bool) and conn['appliedCondition']['dry']: - liaisons['coeffs'].append((o[0][1], o[1][1], o[2][1])) - liaisons['dofs'].append(('DRX', 'DRY', 'DRZ')) - elif isinstance(conn['appliedCondition']['dry'], float) and conn['appliedCondition']['dry'] > 0: - stiffnesses[4] = conn['appliedCondition']['dry'] - - if isinstance(conn['appliedCondition']['drz'], bool) and conn['appliedCondition']['drz']: - liaisons['coeffs'].append((o[0][2], o[1][2], o[2][2])) - liaisons['dofs'].append(('DRX', 'DRY', 'DRZ')) - elif isinstance(conn['appliedCondition']['drz'], float) and conn['appliedCondition']['drz'] > 0: - stiffnesses[5] = conn['appliedCondition']['drz'] - - conn['liaisons'] = liaisons - conn['stiffnesses'] = tuple(stiffnesses) - -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/models/' - DATAFILENAME = BASE_PATH + fileName + '/' + fileName + '.json' - ASTERFILENAME = BASE_PATH + fileName + '/' + fileName + '.comm' - COMMANDFILE(DATAFILENAME, ASTERFILENAME) +import json +import numpy as np +import itertools + +flatten = itertools.chain.from_iterable + + +class COMMANDFILE: + def __init__(self, dataFilename, asterFilename): + self.dataFilename = dataFilename + self.asterFilename = asterFilename + 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]) + + def create(self): + + AccelOfGravity = 9.806 # m/sec^2 + + # Read data from input file + with open(self.dataFilename) as dataFile: + data = json.load(dataFile) + + 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"] = [] + self.calculateRestraints(conn) + for el in elements: + for rel in 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_" + rel["springGroupName"] = ( + self.getGroupName(rel["relatingElement"]) + + "_1DS_" + + self.getGroupName(rel["relatedConnection"]) + ) + if conn["geometryType"] == "line": + rel["conn_string"] = "_1DC_" + rel["springGroupName"] = None + if conn["geometryType"] == "surface": + rel["conn_string"] = "_2DC_" + rel["springGroupName"] = None + + rel["groupName1"] = ( + self.getGroupName(rel["relatingElement"]) + + rel["conn_string"] + + self.getGroupName(rel["relatedConnection"]) + ) + if rel["eccentricity"]: + rel["groupName2"] = ( + self.getGroupName(rel["relatedConnection"]) + + "_0DC_" + + self.getGroupName(rel["relatingElement"]) + ) + rel["index"] = len(conn["relatedElements"]) + 1 + rel["unifiedGroupName"] = self.getGroupName(rel["relatedConnection"]) + "_0DC_%g" % rel["index"] + else: + rel["groupName2"] = self.getGroupName(rel["relatedConnection"]) + self.calculateConstraints(rel) + conn["relatedElements"].append(rel) + # End <-- + + materials = data["db"]["materials"] + profiles = data["db"]["profiles"] + + edgeGroupNames = tuple([self.getGroupName(el["ifcName"]) for el in elements if el["geometryType"] == "line"]) + faceGroupNames = tuple([self.getGroupName(el["ifcName"]) for el in elements if el["geometryType"] == "surface"]) + point0DGroupNames = tuple( + [self.getGroupName(el["ifcName"]) + "_0D" for el in connections if el["geometryType"] == "point"] + ) + spring1DGroupNames = tuple( + flatten( + [[rel["springGroupName"] for rel in el["connections"] if rel["springGroupName"]] for el in elements] + ) + ) + point1DGroupNames = tuple( + [self.getGroupName(el["ifcName"]) + "_0D" for el in connections if el["geometryType"] == "line"] + ) + + unifiedConnection = False + rigidLinkGroupNames = [] + for conn in connections: + conn["unifiedGroupNames"] = [ + rel["unifiedGroupName"] for rel in conn["relatedElements"] if rel["eccentricity"] + ] + # if not conn['appliedCondition'] and len(conn['unifiedGroupNames']) == 1: + # conn['appliedCondition'] = { + # 'dx': True, + # 'dy': True, + # 'dz': True + # } + if len(conn["unifiedGroupNames"]) >= 1: + conn["unifiedGroupNames"].insert(0, self.getGroupName(conn["ifcName"])) + conn["unifiedGroupNames"] = tuple(conn["unifiedGroupNames"]) + unifiedConnection = True + rigidLinkGroupNames.extend( + [ + self.getGroupName(rel["relatingElement"]) + "_1DR_" + self.getGroupName(conn["ifcName"]) + for rel in conn["relatedElements"] + if rel["eccentricity"] + ] + ) + rigidLinkGroupNames = tuple(rigidLinkGroupNames) + + # Define file to write command file for code_aster + f = open(self.asterFilename, "w") + + f.write("# Command file generated by IfcOpenShell/ifc2ca scripts\n") + f.write("\n") + + f.write("# Linear Static Analysis With Self-Weight\n") + + f.write( + """ +# STEP: INITIALIZE STUDY +DEBUT( + PAR_LOT = 'NON' +) +""" + ) + + f.write( + """ +# STEP: READ MED FILE +mesh = LIRE_MAILLAGE( + FORMAT = 'MED', + UNITE = 20 +) +""" + ) + + f.write( + """ +# STEP: DEFINE MODEL +model = AFFE_MODELE( + MAILLAGE = mesh, + AFFE = ( + _F( + TOUT = 'OUI', + PHENOMENE = 'MECANIQUE', + MODELISATION = '3D' + ),""" + ) + + if faceGroupNames: + template = """ + _F( + GROUP_MA = {groupNames}, + PHENOMENE = 'MECANIQUE', + MODELISATION = 'DKT' + ),""" + + context = {"groupNames": faceGroupNames} + + f.write(template.format(**context)) + + if edgeGroupNames: + template = """ + _F( + GROUP_MA = {groupNames}, + PHENOMENE = 'MECANIQUE', + MODELISATION = 'POU_D_E' + ),""" + + context = {"groupNames": edgeGroupNames} + + f.write(template.format(**context)) + + if point0DGroupNames: + template = """ + _F( + GROUP_MA = {groupNames}, + PHENOMENE = 'MECANIQUE', + MODELISATION = 'DIS_TR' + ),""" + + context = {"groupNames": tuple(flatten([point0DGroupNames, spring1DGroupNames]))} + + f.write(template.format(**context)) + + if point1DGroupNames: + template = """ + _F( + GROUP_MA = {groupNames}, + PHENOMENE = 'MECANIQUE', + MODELISATION = 'DIS_TR' + ),""" + + context = {"groupNames": point1DGroupNames} + + f.write(template.format(**context)) + + if rigidLinkGroupNames: + template = """ + _F( + GROUP_MA = {groupNames}, + PHENOMENE = 'MECANIQUE', + MODELISATION = 'POU_D_E' + ),""" + + context = {"groupNames": rigidLinkGroupNames} + + f.write(template.format(**context)) + + f.write( + """ + ) +)\n +""" + ) + + f.write("# STEP: DEFINE MATERIALS") + + for i, material in enumerate(materials): + template = """ +{matNameID} = DEFI_MATERIAU( + ELAS = _F( + E = {youngModulus}, + NU = {poissonRatio}, + RHO = {massDensity} + ) +) +""" + if "poissonRatio" in material["mechProps"]: + poissonRatio = material["mechProps"]["poissonRatio"] + else: + if "shearModulus" in material["mechProps"]: + poissonRatio = ( + material["mechProps"]["youngModulus"] / 2.0 / material["mechProps"]["shearModulus"] + ) - 1 + else: + poissonRatio = 0.0 + + context = { + "matNameID": "mat" + "_%s" % i, + "youngModulus": float(material["mechProps"]["youngModulus"]), + "poissonRatio": float(poissonRatio), + "massDensity": float(material["commonProps"]["massDensity"]), + } + + f.write(template.format(**context)) + + f.write( + """ +material = AFFE_MATERIAU( + MAILLAGE = mesh, + AFFE = (""" + ) + + for i, material in enumerate(materials): + template = """ + _F( + GROUP_MA = {groupNames}, + MATER = {matNameID}, + ),""" + + context = { + "groupNames": tuple([self.getGroupName(rel) for rel in material["relatedElements"]]), + "matNameID": "mat" + "_%s" % i, + } + + f.write(template.format(**context)) + + if rigidLinkGroupNames: + template = """ + _F( + GROUP_MA = {groupNames}, + MATER = {matNameID}, + ),""" + + context = {"groupNames": rigidLinkGroupNames, "matNameID": "mat_0"} + + f.write(template.format(**context)) + + f.write( + """ + ) +) +""" + ) + + f.write( + """ +# STEP: DEFINE ELEMENTS +element = AFFE_CARA_ELEM( + MODELE = model, + POUTRE = (""" + ) + + for profile in profiles: + if profile["profileShape"] == "rectangular" and profile["profileType"] == "AREA": + template = """ + _F( + GROUP_MA = {groupNames}, + SECTION = 'RECTANGLE', + CARA = ('HY', 'HZ'), + VALE = {profileDimensions} + ),""" + + context = { + "groupNames": tuple([self.getGroupName(rel) for rel in profile["relatedElements"]]), + "profileDimensions": (profile["xDim"], profile["yDim"]), + } + + f.write(template.format(**context)) + + elif profile["profileShape"] == "iSymmetrical" and profile["profileType"] == "AREA": + template = """ + _F( + GROUP_MA = {groupNames}, + SECTION = 'GENERALE', + CARA = ('A', 'IY', 'IZ', 'JX'), + VALE = {profileProperties} + ),""" + + context = { + "groupNames": tuple([self.getGroupName(rel) for rel in profile["relatedElements"]]), + "profileProperties": ( + profile["mechProps"]["crossSectionArea"], + profile["mechProps"]["momentOfInertiaY"], + profile["mechProps"]["momentOfInertiaZ"], + profile["mechProps"]["torsionalConstantX"], + ), + } + + f.write(template.format(**context)) + + if rigidLinkGroupNames: + template = """ + _F( + GROUP_MA = {groupNames}, + SECTION = 'RECTANGLE', + CARA = ('HY', 'HZ'), + VALE = {profileDimensions} + ),""" + + context = {"groupNames": rigidLinkGroupNames, "profileDimensions": (1, 1)} + + f.write(template.format(**context)) + + f.write( + """ + ), + COQUE = (""" + ) + + for el in [el for el in elements if el["geometryType"] == "surface"]: + + template = """ + _F( + GROUP_MA = '{groupName}', + EPAIS = {thickness}, + VECTEUR = {localAxisX} + ),""" + + context = { + "groupName": self.getGroupName(el["ifcName"]), + "thickness": el["thickness"], + "localAxisX": tuple(el["orientation"][0]), + } + + f.write(template.format(**context)) + + f.write( + """ + ),""" + ) + f.write( + """ + DISCRET = (""" + ) + + for conn in [conn for conn in connections if conn["geometryType"] == "point"]: + + template = """ + _F( + GROUP_MA = '{groupName}', + CARA = 'K_TR_D_N', + VALE = {stiffnesses}, + REPERE = 'LOCAL' + ),""" + + context = {"groupName": self.getGroupName(conn["ifcName"]) + "_0D", "stiffnesses": conn["stiffnesses"]} + + f.write(template.format(**context)) + + for rel in conn["relatedElements"]: + + template = """ + _F( + GROUP_MA = '{groupName}', + CARA = 'K_TR_D_L', + VALE = {stiffnesses}, + REPERE = 'LOCAL' + ),""" + + context = {"groupName": rel["springGroupName"], "stiffnesses": rel["stiffnesses"]} + + f.write(template.format(**context)) + + for conn in [conn for conn in connections if conn["geometryType"] == "line"]: + + template = """ + _F( + GROUP_MA = '{groupName}', + CARA = 'K_TR_D_N', + VALE = {stiffnesses}, + REPERE = 'LOCAL' + ),""" + + context = {"groupName": self.getGroupName(conn["ifcName"]) + "_0D", "stiffnesses": conn["stiffnesses"]} + + f.write(template.format(**context)) + + f.write( + """ + ),""" + ) + + f.write( + """ + ORIENTATION = (""" + ) + + for el in [el for el in elements if el["geometryType"] == "line"]: + + template = """ + _F( + GROUP_MA = '{groupName}', + CARA = 'VECT_Y', + VALE = {localAxisY} + ),""" + + context = {"groupName": self.getGroupName(el["ifcName"]), "localAxisY": tuple(el["orientation"][1])} + + f.write(template.format(**context)) + + for conn in [conn for conn in connections if conn["geometryType"] == "point"]: + + template = """ + _F( + GROUP_MA = '{groupName}', + CARA = 'VECT_X_Y', + VALE = {localAxesXY} + ),""" + + context = { + "groupName": self.getGroupName(conn["ifcName"]) + "_0D", + "localAxesXY": tuple(conn["orientation"][0] + conn["orientation"][1]), + } + + f.write(template.format(**context)) + + for rel in conn["relatedElements"]: + + template = """ + _F( + GROUP_MA = '{groupName}', + CARA = 'VECT_X_Y', + VALE = {localAxesXY} + ),""" + + context = { + "groupName": rel["springGroupName"], + "localAxesXY": tuple(rel["orientation"][0] + rel["orientation"][1]), + } + + f.write(template.format(**context)) + + for conn in [conn for conn in connections if conn["geometryType"] == "line"]: + + template = """ + _F( + GROUP_MA = '{groupName}', + CARA = 'VECT_X_Y', + VALE = {localAxesXY} + ),""" + + context = { + "groupName": self.getGroupName(conn["ifcName"]) + "_0D", + "localAxesXY": tuple(conn["orientation"][0] + conn["orientation"][1]), + } + + f.write(template.format(**context)) + + f.write( + """ + ),""" + ) + + f.write( + """ +)\n +""" + ) + + f.write("# STEP: DEFINE SUPPORTS AND CONSTRAINTS") + + f.write( + """ +liaisons = AFFE_CHAR_MECA( + MODELE = model, + LIAISON_DDL = (""" + ) + + for conn in [conn for conn in connections if conn["geometryType"] == "point"]: + if conn["appliedCondition"]: + for i in range(len(conn["liaisons"]["coeffs"])): + template = """ + _F( + GROUP_NO = {groupNames}, + DDL = {dofs}, + COEF_MULT = {coeffs}, + COEF_IMPO = 0.0 + ),""" + + context = { + "groupNames": conn["liaisons"]["groupNames"], + "dofs": conn["liaisons"]["dofs"][i], + "coeffs": conn["liaisons"]["coeffs"][i], + } + + f.write(template.format(**context)) + + for rel in conn["relatedElements"]: + for i in range(len(rel["liaisons"]["coeffs"])): + template = """ + _F( + GROUP_NO = {groupNames}, + DDL = {dofs}, + COEF_MULT = {coeffs}, + COEF_IMPO = 0.0 + ),""" + + context = { + "groupNames": rel["liaisons"]["groupNames"], + "dofs": rel["liaisons"]["dofs"][i], + "coeffs": rel["liaisons"]["coeffs"][i], + } + + f.write(template.format(**context)) + + f.write( + """ + ),""" + ) + + f.write( + """ + LIAISON_GROUP = (""" + ) + + for conn in [conn for conn in connections if conn["geometryType"] == "line"]: + if conn["appliedCondition"]: + for i in range(len(conn["liaisons"]["coeffs"])): + template = """ + _F( + GROUP_NO_1 = {groupName_1}, + GROUP_NO_2 = {groupName_1}, + DDL_1 = {dofs}, + DDL_2 = {dofs}, + COEF_MULT_1 = {coeffs}, + COEF_MULT_2 = (0.0, 0.0, 0.0), + COEF_IMPO = 0.0 + ),""" + + context = { + "groupName_1": tuple([conn["liaisons"]["groupNames"][0]]), + "dofs": conn["liaisons"]["dofs"][i], + "coeffs": conn["liaisons"]["coeffs"][i], + } + + f.write(template.format(**context)) + + for rel in conn["relatedElements"]: + for i in range(len(rel["liaisons"]["coeffs"])): + template = """ + _F( + GROUP_NO_1 = {groupName_1}, + GROUP_NO_2 = {groupName_2}, + DDL_1 = {dofs}, + DDL_2 = {dofs}, + COEF_MULT_1 = {coeffs_1}, + COEF_MULT_2 = {coeffs_2}, + COEF_IMPO = 0.0 + ),""" + + context = { + "groupName_1": tuple([rel["liaisons"]["groupNames"][0]]), + "groupName_2": tuple([rel["liaisons"]["groupNames"][3]]), + "dofs": tuple(list(rel["liaisons"]["dofs"][i])[:3]), + "coeffs_1": tuple(list(rel["liaisons"]["coeffs"][i])[:3]), + "coeffs_2": tuple(list(rel["liaisons"]["coeffs"][i])[3:]), + } + + f.write(template.format(**context)) + + f.write( + """ + ),""" + ) + + if unifiedConnection: + f.write( + """ + LIAISON_UNIF = (""" + ) + + for conn in [conn for conn in connections if len(conn["unifiedGroupNames"]) > 1]: + template = """ + _F( + GROUP_NO = {groupNames}, + DDL = ('DX', 'DY', 'DZ', 'DRX', 'DRY', 'DRZ') + ),""" + + context = {"groupNames": conn["unifiedGroupNames"]} + + f.write(template.format(**context)) + + f.write( + """ + ),""" + ) + + if rigidLinkGroupNames: + f.write( + """ + LIAISON_SOLIDE = (""" + ) + + for groupName in rigidLinkGroupNames: + template = """ + _F( + GROUP_MA = '{groupName}' + ),""" + + context = {"groupName": groupName} + + f.write(template.format(**context)) + + f.write( + """ + ),""" + ) + + f.write( + """ +) +""" + ) + + template = """ +# STEP: DEFINE LOAD +gravLoad = AFFE_CHAR_MECA( + MODELE = model, + PESANTEUR = _F( + GRAVITE = {AccelOfGravity}, + DIRECTION = (0.0, 0.0, -1.0) + ) +) +""" + context = { + "AccelOfGravity": AccelOfGravity, + } + + f.write(template.format(**context)) + + f.write( + """ +# STEP: RUN ANALYSIS +res_Bld = MECA_STATIQUE( + MODELE = model, + CHAM_MATER = material, + CARA_ELEM = element, + EXCIT = ( + _F( + CHARGE = liaisons + ), + _F( + CHARGE = gravLoad + ) + ) +) +""" + ) + + # f.write( + # ''' + # # STEP: POST-PROCESSING + # res_Bld = CALC_CHAMP( + # reuse = res_Bld, + # RESULTAT = res_Bld, + # # CONTRAINTE = ('SIEF_ELNO', 'SIGM_ELNO', 'EFGE_ELNO',), + # FORCE = ('REAC_NODA', 'FORC_NODA',) + # ) + # ''' + # ) + # + # template = \ + # ''' + # # STEP: MASS EXTRACTION FOR EACH ASSEMBLE + # FaceMass = POST_ELEM( + # TITRE = 'TotMass', + # MODELE = model, + # CARA_ELEM = element, + # CHAM_MATER = material, + # MASS_INER = _F( + # GROUP_MA = {massList}, + # ), + # )\n''' + # + # context = { + # 'massList': massList, + # } + # + # f.write(template.format(**context)) + # + # f.write( + # ''' + # IMPR_TABLE( + # UNITE = 10, + # TABLE = FaceMass, + # SEPARATEUR = ',', + # NOM_PARA = ('LIEU', 'MASSE', 'CDG_X', 'CDG_Y', 'CDG_Z'), + # # FORMAT_R = '1PE15.6', + # ) + # ''' + # ) + # + # template = \ + # ''' + # # STEP: REACTION EXTRACTION AT THE BASE + # Reacs = POST_RELEVE_T( + # ACTION = _F( + # INTITULE = 'sumReac', + # GROUP_NO = {groupNames}, + # RESULTAT = res_Bld, + # NOM_CHAM = 'REAC_NODA', + # RESULTANTE = ('DX','DY','DZ',), + # MOMENT = ('DRX','DRY','DRZ',), + # POINT = (0,0,0,), + # OPERATION = 'EXTRACTION' + # ) + # ) + # ''' + # + # context = { + # 'groupNames': point0DGroupNames, + # } + # + # f.write(template.format(**context)) + # + # f.write( + # ''' + # IMPR_TABLE( + # UNITE = 10, + # TABLE = Reacs, + # SEPARATEUR = ',', + # # NOM_PARA = ('INTITULE', 'RESU', 'NOM_CHAM', 'INST', 'DX','DY','DZ'), + # FORMAT_R = '1PE12.3', + # ) + # ''' + # ) + # + f.write( + """ +# STEP: DEFORMED SHAPE EXTRACTION +IMPR_RESU( + FORMAT = 'MED', + UNITE = 80, + RESU = _F( + RESULTAT = res_Bld, + NOM_CHAM = ('DEPL',), # 'REAC_NODA', 'FORC_NODA', + NOM_CHAM_MED = ('Bld_DISP',), # 'Bld_REAC', 'Bld_FORC' + ) +) +""" + ) + + f.write( + """ +# STEP: CONCLUDE STUDY +FIN() +""" + ) + + f.close() + + def calculateConstraints(self, rel): + gr1 = rel["groupName1"] + gr2 = rel["groupName2"] + o = np.array(rel["orientation"]).transpose().tolist() + liaisons = {"groupNames": (gr1, gr1, gr1, gr2, gr2, gr2), "coeffs": [], "dofs": []} + stiffnesses = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0] + if not rel["appliedCondition"]: + rel["appliedCondition"] = {"dx": True, "dy": True, "dz": True, "drx": True, "dry": True, "drz": True} + if isinstance(rel["appliedCondition"]["dx"], bool) and rel["appliedCondition"]["dx"]: + liaisons["coeffs"].append((o[0][0], o[1][0], o[2][0], -o[0][0], -o[1][0], -o[2][0])) + liaisons["dofs"].append(("DX", "DY", "DZ", "DX", "DY", "DZ")) + elif isinstance(rel["appliedCondition"]["dx"], float) and rel["appliedCondition"]["dx"] > 0: + stiffnesses[0] = rel["appliedCondition"]["dx"] + + if isinstance(rel["appliedCondition"]["dy"], bool) and rel["appliedCondition"]["dy"]: + liaisons["coeffs"].append((o[0][1], o[1][1], o[2][1], -o[0][1], -o[1][1], -o[2][1])) + liaisons["dofs"].append(("DX", "DY", "DZ", "DX", "DY", "DZ")) + elif isinstance(rel["appliedCondition"]["dy"], float) and rel["appliedCondition"]["dy"] > 0: + stiffnesses[1] = rel["appliedCondition"]["dy"] + + if isinstance(rel["appliedCondition"]["dz"], bool) and rel["appliedCondition"]["dz"]: + liaisons["coeffs"].append((o[0][2], o[1][2], o[2][2], -o[0][2], -o[1][2], -o[2][2])) + liaisons["dofs"].append(("DX", "DY", "DZ", "DX", "DY", "DZ")) + elif isinstance(rel["appliedCondition"]["dz"], float) and rel["appliedCondition"]["dz"] > 0: + stiffnesses[2] = rel["appliedCondition"]["dz"] + + if isinstance(rel["appliedCondition"]["drx"], bool) and rel["appliedCondition"]["drx"]: + liaisons["coeffs"].append((o[0][0], o[1][0], o[2][0], -o[0][0], -o[1][0], -o[2][0])) + liaisons["dofs"].append(("DRX", "DRY", "DRZ", "DRX", "DRY", "DRZ")) + elif isinstance(rel["appliedCondition"]["drx"], float) and rel["appliedCondition"]["drx"] > 0: + stiffnesses[3] = rel["appliedCondition"]["drx"] + + if isinstance(rel["appliedCondition"]["dry"], bool) and rel["appliedCondition"]["dry"]: + liaisons["coeffs"].append((o[0][1], o[1][1], o[2][1], -o[0][1], -o[1][1], -o[2][1])) + liaisons["dofs"].append(("DRX", "DRY", "DRZ", "DRX", "DRY", "DRZ")) + elif isinstance(rel["appliedCondition"]["dry"], float) and rel["appliedCondition"]["dry"] > 0: + stiffnesses[4] = rel["appliedCondition"]["dry"] + + if isinstance(rel["appliedCondition"]["drz"], bool) and rel["appliedCondition"]["drz"]: + liaisons["coeffs"].append((o[0][2], o[1][2], o[2][2], -o[0][2], -o[1][2], -o[2][2])) + liaisons["dofs"].append(("DRX", "DRY", "DRZ", "DRX", "DRY", "DRZ")) + elif isinstance(rel["appliedCondition"]["drz"], float) and rel["appliedCondition"]["drz"] > 0: + stiffnesses[5] = rel["appliedCondition"]["drz"] + + rel["liaisons"] = liaisons + rel["stiffnesses"] = tuple(stiffnesses) + + def calculateRestraints(self, conn): + group = self.getGroupName(conn["ifcName"]) + o = np.array(conn["orientation"]).transpose().tolist() + liaisons = {"groupNames": (group, group, group), "coeffs": [], "dofs": []} + stiffnesses = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0] + + if not conn["appliedCondition"]: + conn["liaisons"] = liaisons + conn["stiffnesses"] = tuple(stiffnesses) + return + + if isinstance(conn["appliedCondition"]["dx"], bool) and conn["appliedCondition"]["dx"]: + liaisons["coeffs"].append((o[0][0], o[1][0], o[2][0])) + liaisons["dofs"].append(("DX", "DY", "DZ")) + elif isinstance(conn["appliedCondition"]["dx"], float) and conn["appliedCondition"]["dx"] > 0: + stiffnesses[0] = conn["appliedCondition"]["dx"] + + if isinstance(conn["appliedCondition"]["dy"], bool) and conn["appliedCondition"]["dy"]: + liaisons["coeffs"].append((o[0][1], o[1][1], o[2][1])) + liaisons["dofs"].append(("DX", "DY", "DZ")) + elif isinstance(conn["appliedCondition"]["dy"], float) and conn["appliedCondition"]["dy"] > 0: + stiffnesses[1] = conn["appliedCondition"]["dy"] + + if isinstance(conn["appliedCondition"]["dz"], bool) and conn["appliedCondition"]["dz"]: + liaisons["coeffs"].append((o[0][2], o[1][2], o[2][2])) + liaisons["dofs"].append(("DX", "DY", "DZ")) + elif isinstance(conn["appliedCondition"]["dz"], float) and conn["appliedCondition"]["dz"] > 0: + stiffnesses[2] = conn["appliedCondition"]["dz"] + + if isinstance(conn["appliedCondition"]["drx"], bool) and conn["appliedCondition"]["drx"]: + liaisons["coeffs"].append((o[0][0], o[1][0], o[2][0])) + liaisons["dofs"].append(("DRX", "DRY", "DRZ")) + elif isinstance(conn["appliedCondition"]["drx"], float) and conn["appliedCondition"]["drx"] > 0: + stiffnesses[3] = conn["appliedCondition"]["drx"] + + if isinstance(conn["appliedCondition"]["dry"], bool) and conn["appliedCondition"]["dry"]: + liaisons["coeffs"].append((o[0][1], o[1][1], o[2][1])) + liaisons["dofs"].append(("DRX", "DRY", "DRZ")) + elif isinstance(conn["appliedCondition"]["dry"], float) and conn["appliedCondition"]["dry"] > 0: + stiffnesses[4] = conn["appliedCondition"]["dry"] + + if isinstance(conn["appliedCondition"]["drz"], bool) and conn["appliedCondition"]["drz"]: + liaisons["coeffs"].append((o[0][2], o[1][2], o[2][2])) + liaisons["dofs"].append(("DRX", "DRY", "DRZ")) + elif isinstance(conn["appliedCondition"]["drz"], float) and conn["appliedCondition"]["drz"] > 0: + stiffnesses[5] = conn["appliedCondition"]["drz"] + + conn["liaisons"] = liaisons + conn["stiffnesses"] = tuple(stiffnesses) + + +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/models/" + DATAFILENAME = BASE_PATH + fileName + "/" + fileName + ".json" + ASTERFILENAME = BASE_PATH + fileName + "/" + fileName + ".comm" + COMMANDFILE(DATAFILENAME, ASTERFILENAME) diff --git a/src/ifc2ca/scriptSalome.py b/src/ifc2ca/scriptSalome.py index 8efa652868..fd8d24c34c 100644 --- a/src/ifc2ca/scriptSalome.py +++ b/src/ifc2ca/scriptSalome.py @@ -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,53 +61,53 @@ 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 dec = 7 # 4 decimals for length in mm - tol = 10**(-dec-3+1) + tol = 10 ** (-dec - 3 + 1) - self.tolLoc = tol*10*2 + self.tolLoc = tol * 10 * 2 tolLoc = self.tolLoc NEW_SALOME = int(salome_version.getVersion()[0]) >= 9 @@ -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,13 +305,13 @@ 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) NETGEN2D_Pars.SetOptimize(1) NETGEN2D_Pars.SetFineness(2) - NETGEN2D_Pars.SetMinSize(meshSize/5.0) + NETGEN2D_Pars.SetMinSize(meshSize / 5.0) NETGEN2D_Pars.SetUseSurfaceCurvature(1) NETGEN2D_Pars.SetQuadAllowed(1) NETGEN2D_Pars.SetSecondOrder(0) @@ -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) diff --git a/src/ifcbimtester/.gitignore b/src/ifcbimtester/.gitignore new file mode 100644 index 0000000000..db03d2ec9b --- /dev/null +++ b/src/ifcbimtester/.gitignore @@ -0,0 +1,3 @@ +# Dependency and build folders created by the build scripts +/build/ +/dist/ \ No newline at end of file diff --git a/src/ifcbimtester/README.md b/src/ifcbimtester/README.md new file mode 100644 index 0000000000..30837779c8 --- /dev/null +++ b/src/ifcbimtester/README.md @@ -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 +``` diff --git a/src/ifcbimtester/bimtester.py b/src/ifcbimtester/bimtester.py deleted file mode 100755 index 892e727720..0000000000 --- a/src/ifcbimtester/bimtester.py +++ /dev/null @@ -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'], '' + a['value'] + '') - 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 :-)') diff --git a/src/ifcbimtester/bimtester/__init__.py b/src/ifcbimtester/bimtester/__init__.py new file mode 100644 index 0000000000..94d43e5dc3 --- /dev/null +++ b/src/ifcbimtester/bimtester/__init__.py @@ -0,0 +1,5 @@ +from os.path import dirname +from os.path import realpath + + +package_path = dirname(realpath(__file__)) diff --git a/src/ifcbimtester/bimtester/clean.py b/src/ifcbimtester/bimtester/clean.py new file mode 100644 index 0000000000..452698500f --- /dev/null +++ b/src/ifcbimtester/bimtester/clean.py @@ -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 diff --git a/src/ifcbimtester/bimtester/features/environment.py b/src/ifcbimtester/bimtester/features/environment.py new file mode 100644 index 0000000000..25f6d040ca --- /dev/null +++ b/src/ifcbimtester/bimtester/features/environment.py @@ -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) diff --git a/src/ifcbimtester/bimtester/features/logfile.py b/src/ifcbimtester/bimtester/features/logfile.py new file mode 100644 index 0000000000..2528b3587b --- /dev/null +++ b/src/ifcbimtester/bimtester/features/logfile.py @@ -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() diff --git a/src/ifcbimtester/bimtester/features/steps/all.py b/src/ifcbimtester/bimtester/features/steps/all.py new file mode 100644 index 0000000000..50eaec41aa --- /dev/null +++ b/src/ifcbimtester/bimtester/features/steps/all.py @@ -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") diff --git a/src/ifcbimtester/bimtester/features/steps/classification/en.py b/src/ifcbimtester/bimtester/features/steps/classification/en.py new file mode 100644 index 0000000000..81e54e6ad7 --- /dev/null +++ b/src/ifcbimtester/bimtester/features/steps/classification/en.py @@ -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) diff --git a/src/ifcbimtester/bimtester/features/steps/element_classes/en.py b/src/ifcbimtester/bimtester/features/steps/element_classes/en.py new file mode 100644 index 0000000000..ab4031f5bc --- /dev/null +++ b/src/ifcbimtester/bimtester/features/steps/element_classes/en.py @@ -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) diff --git a/src/ifcbimtester/bimtester/features/steps/geocoding/en.py b/src/ifcbimtester/bimtester/features/steps/geocoding/en.py new file mode 100644 index 0000000000..ed3c8ae5f8 --- /dev/null +++ b/src/ifcbimtester/bimtester/features/steps/geocoding/en.py @@ -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.*)" has a name of "(?P.*)"') +def step_impl(context, spatial_type, guid, name): + check_geocode_attribute(guid, spatial_type, "Name", name) + + +@step('The (site|building|facility) "(?P.*)" has a description of "(?P.*)"') +def step_impl(context, spatial_type, guid, description): + check_geocode_attribute(guid, spatial_type, "Description", description) + + +@step('The site "(?P.*)" has a land title number of "(?P.*)"') +def step_impl(context, guid, land_title_number): + check_geocode_attribute(guid, "site", "LandTitleNumber", land_title_number) + + +@step('The (site|building) "(?P.*)" has the address "(?P.*)"') +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.*)" has a postal box of "(?P.*)"') +def step_impl(context, spatial_type, guid, postal_box): + check_geocode_address(guid, spatial_type, "PostalBox", postal_box) + + +@step('The (site|building) "(?P.*)" is in the town "(?P.*)"') +def step_impl(context, spatial_type, guid, town): + check_geocode_address(guid, spatial_type, "Town", town) + + +@step('The (site|building) "(?P.*)" is in the region "(?P.*)"') +def step_impl(context, spatial_type, guid, region): + check_geocode_address(guid, spatial_type, "Region", region) + + +@step('The (site|building) "(?P.*)" has a post code of "(?P.*)"') +def step_impl(context, spatial_type, guid, post_code): + check_geocode_address(guid, spatial_type, "PostalCode", post_code) + + +@step('The (site|building) "(?P.*)" is in the country "(?P.*)"') +def step_impl(context, spatial_type, guid, country): + check_geocode_address(guid, spatial_type, "Country", country) + + +@step('The (site|building) "(?P.*)" has an address description of "(?P.*)"') +def step_impl(context, spatial_type, guid, description): + check_geocode_address(guid, spatial_type, "Description", description) diff --git a/src/ifcbimtester/bimtester/features/steps/geolocation/en.py b/src/ifcbimtester/bimtester/features/steps/geolocation/en.py new file mode 100644 index 0000000000..c83813456c --- /dev/null +++ b/src/ifcbimtester/bimtester/features/steps/geolocation/en.py @@ -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) diff --git a/src/ifcbimtester/bimtester/features/steps/geometric_detail/en.py b/src/ifcbimtester/bimtester/features/steps/geometric_detail/en.py new file mode 100644 index 0000000000..c2f7a7d694 --- /dev/null +++ b/src/ifcbimtester/bimtester/features/steps/geometric_detail/en.py @@ -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 diff --git a/src/ifcbimtester/bimtester/features/steps/model_federation/en.py b/src/ifcbimtester/bimtester/features/steps/model_federation/en.py new file mode 100644 index 0000000000..c120825d77 --- /dev/null +++ b/src/ifcbimtester/bimtester/features/steps/model_federation/en.py @@ -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) + ) diff --git a/src/ifcbimtester/bimtester/features/steps/project_setup/de.py b/src/ifcbimtester/bimtester/features/steps/project_setup/de.py new file mode 100644 index 0000000000..8163292264 --- /dev/null +++ b/src/ifcbimtester/bimtester/features/steps/project_setup/de.py @@ -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}"') diff --git a/src/ifcbimtester/bimtester/features/steps/project_setup/en.py b/src/ifcbimtester/bimtester/features/steps/project_setup/en.py new file mode 100644 index 0000000000..eb5e9a0bb4 --- /dev/null +++ b/src/ifcbimtester/bimtester/features/steps/project_setup/en.py @@ -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 diff --git a/src/ifcbimtester/bimtester/features/steps/project_setup/fr.py b/src/ifcbimtester/bimtester/features/steps/project_setup/fr.py new file mode 100644 index 0000000000..0a49367ccf --- /dev/null +++ b/src/ifcbimtester/bimtester/features/steps/project_setup/fr.py @@ -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') diff --git a/src/ifcbimtester/bimtester/features/steps/project_setup/it.py b/src/ifcbimtester/bimtester/features/steps/project_setup/it.py new file mode 100644 index 0000000000..a9b103e7ca --- /dev/null +++ b/src/ifcbimtester/bimtester/features/steps/project_setup/it.py @@ -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}"') diff --git a/src/ifcbimtester/bimtester/features/steps/project_setup/nl.py b/src/ifcbimtester/bimtester/features/steps/project_setup/nl.py new file mode 100644 index 0000000000..84618014ef --- /dev/null +++ b/src/ifcbimtester/bimtester/features/steps/project_setup/nl.py @@ -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}"') diff --git a/src/ifcbimtester/bimtester/features/zoom_smart_view.py b/src/ifcbimtester/bimtester/features/zoom_smart_view.py new file mode 100644 index 0000000000..fa544f8871 --- /dev/null +++ b/src/ifcbimtester/bimtester/features/zoom_smart_view.py @@ -0,0 +1,138 @@ +import fileinput + + +def create_zoom_smartview(sm_file, ifcbasename): + + smf = open(sm_file, "w") + smf.write('\n') + smf.write("\n") + smf.write(" 5\n") + smf.write(" Win - Version: ") + # next line belongs to last, because of line length + smf.write("3.4 (build 3.4.13.559)\n") + smf.write("\n") + smf.write("\n") + smf.write("\n") + smf.write(" \n") + smf.write(" BIMTester {}\n".format(ifcbasename)) + smf.write(" \n") + smf.write(" a2ddfaf7-97f2-4519-aabd-f2d94f6b4d6b\n") + smf.write(" 2020-10-30T13:23:30") + # next line belongs to last, because of line length + smf.write("\n") + smf.write(" \n") + smf.write(" \n") + smf.write(" \n") + smf.write("\n") + smf.close() + + +def append_zoom_smartview(sm_file, step_name, false_elements_guid): + + # build the smartview string + smview_string = " \n" + smview_string += ( + " GUID filter, {}\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 = " " + 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 = """ + Filter GUID + """ + + +each_smartview_string_before = """ bernd@bimstatik.ch + 2020-10-30T13:18:45 + bernd@bimstatik.ch + 2020-10-30T13:23:30 + 15fda94f-b4bf-43be-8ef4-15d3121137e1 + + + Any + + None + None + None + None + None + + + Is + + + + AddSetColored + 187 + 187 + 187 + + + + Any + + None + None + None + None + None + + + Is + + + + SetTransparent + + """ + + +each_smartview_string_after = """ + + None + None + 0 + + """ + + +rule_string_before = """ + Any + + GUID + Summary + Summary + StringValue + None + + + Is + """ + + +rule_string_after = """ + + + SetColored + 255 + 10 + 10 + + """ diff --git a/src/ifcbimtester/bimtester/guiwidget.py b/src/ifcbimtester/bimtester/guiwidget.py new file mode 100644 index 0000000000..e5f24244b9 --- /dev/null +++ b/src/ifcbimtester/bimtester/guiwidget.py @@ -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() diff --git a/src/ifcbimtester/bimtester/ifc.py b/src/ifcbimtester/bimtester/ifc.py new file mode 100644 index 0000000000..5f4321d923 --- /dev/null +++ b/src/ifcbimtester/bimtester/ifc.py @@ -0,0 +1,4 @@ +class IfcStore: + path = "" + file = None + bookmarks = {} diff --git a/src/ifcbimtester/bimtester/lang.py b/src/ifcbimtester/bimtester/lang.py new file mode 100644 index 0000000000..60caf519cc --- /dev/null +++ b/src/ifcbimtester/bimtester/lang.py @@ -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 diff --git a/src/ifcbimtester/bimtester/locale/de/LC_MESSAGES/messages.po b/src/ifcbimtester/bimtester/locale/de/LC_MESSAGES/messages.po new file mode 100644 index 0000000000..d90a17309a --- /dev/null +++ b/src/ifcbimtester/bimtester/locale/de/LC_MESSAGES/messages.po @@ -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 , 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 \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}" + diff --git a/src/ifcbimtester/bimtester/locale/en/LC_MESSAGES/messages.po b/src/ifcbimtester/bimtester/locale/en/LC_MESSAGES/messages.po new file mode 100644 index 0000000000..5ed0f96b3d --- /dev/null +++ b/src/ifcbimtester/bimtester/locale/en/LC_MESSAGES/messages.po @@ -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 , 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 \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 "" + diff --git a/src/ifcbimtester/bimtester/locale/fr/LC_MESSAGES/messages.po b/src/ifcbimtester/bimtester/locale/fr/LC_MESSAGES/messages.po new file mode 100644 index 0000000000..6f0f3d62dc --- /dev/null +++ b/src/ifcbimtester/bimtester/locale/fr/LC_MESSAGES/messages.po @@ -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 , 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 \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 "" + diff --git a/src/ifcbimtester/bimtester/locale/it/LC_MESSAGES/messages.po b/src/ifcbimtester/bimtester/locale/it/LC_MESSAGES/messages.po new file mode 100644 index 0000000000..9dc114a66d --- /dev/null +++ b/src/ifcbimtester/bimtester/locale/it/LC_MESSAGES/messages.po @@ -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 , 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 \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." + diff --git a/src/ifcbimtester/bimtester/locale/messages.pot b/src/ifcbimtester/bimtester/locale/messages.pot new file mode 100644 index 0000000000..a1cebb0676 --- /dev/null +++ b/src/ifcbimtester/bimtester/locale/messages.pot @@ -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 , 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 \n" +"Language-Team: LANGUAGE \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 "" + diff --git a/src/ifcbimtester/bimtester/locale/nl/LC_MESSAGES/messages.po b/src/ifcbimtester/bimtester/locale/nl/LC_MESSAGES/messages.po new file mode 100644 index 0000000000..0171a62b6f --- /dev/null +++ b/src/ifcbimtester/bimtester/locale/nl/LC_MESSAGES/messages.po @@ -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 \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 "" + diff --git a/src/ifcbimtester/bimtester/reports.py b/src/ifcbimtester/bimtester/reports.py new file mode 100644 index 0000000000..21ddc0e016 --- /dev/null +++ b/src/ifcbimtester/bimtester/reports.py @@ -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"], "" + a["value"] + "") + 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"), + } diff --git a/src/ifcbimtester/icon.ico b/src/ifcbimtester/bimtester/resources/icons/bimtester.ico similarity index 100% rename from src/ifcbimtester/icon.ico rename to src/ifcbimtester/bimtester/resources/icons/bimtester.ico diff --git a/src/ifcbimtester/features/template.html b/src/ifcbimtester/bimtester/resources/reports/template.html similarity index 73% rename from src/ifcbimtester/features/template.html rename to src/ifcbimtester/bimtester/resources/reports/template.html index 66a2ebb0ba..98761cc5e1 100644 --- a/src/ifcbimtester/features/template.html +++ b/src/ifcbimtester/bimtester/resources/reports/template.html @@ -1,24 +1,26 @@ - + - BlenderBIM + {{name}}