merge all

This commit is contained in:
admin
2021-03-25 10:04:25 +08:00
parent 33ec80e54d
commit 29b8e67bc8
344 changed files with 152683 additions and 13294 deletions
+7
View File
@@ -12,6 +12,13 @@ __pycache__
.vscode
# PyCharm files
.idea
# Docs
/docs/output
/docs/rst_files
/docs/doxygen
/src/ifcblenderexport/docs/_build
# gettext binary translation files
*.mo
# Vim
+41 -34
View File
@@ -1,56 +1,60 @@
language: cpp
compiler: gcc
cache: ccache
os: linux
dist: bionic
sudo: required
dist: focal
addons:
apt:
update: true
packages:
- libboost-date-time-dev
- libboost-filesystem-dev
- libboost-iostreams-dev
- libboost-program-options-dev
- libboost-regex-dev
- libboost-system-dev
- libboost-thread-dev
- libocct-data-exchange-dev
- libocct-foundation-dev
- libocct-modeling-algorithms-dev
- libocct-modeling-data-dev
- libxml2-dev
- nlohmann-json3-dev
- opencollada-dev
- python3-all-dev
- swig
before_install:
- "sudo add-apt-repository -y ppa:freecad-community/ppa"
- sudo apt-get update -qq
install:
- |
sudo apt-get install -y \
libocct-data-exchange-dev libocct-foundation-dev libocct-modeling-algorithms-dev libocct-modeling-data-dev \
libboost-system-dev libboost-program-options-dev libboost-regex-dev libboost-thread-dev libboost-date-time-dev libboost-iostreams-dev libboost-filesystem-dev \
build-essential cmake python2.7 libpython2.7-dev swig zlib1g liblzma5 opencollada-dev wget apt-transport-https
- sudo mkdir -p /usr/include/json/nlohmann/
- sudo wget https://github.com/nlohmann/json/releases/download/v3.6.1/json.hpp -O /usr/include/json/nlohmann/json.hpp
# - sudo sh -c 'curl https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add -'
# - sudo sh -c 'curl https://storage.googleapis.com/download.dartlang.org/linux/debian/dart_stable.list > /etc/apt/sources.list.d/dart_stable.list'
# - sudo apt-get update -qq && sudo apt-get install -y dart
# - export PATH=$PATH:/usr/lib/dart/bin:$HOME/.pub-cache/bin
# - git clone https://github.com/KhronosGroup/glTF-Validator && pushd glTF-Validator && pub get && pub global activate --source path ./ && popd
before_script:
- if [ $TRAVIS_OS_NAME == "linux" ]; then ccache -z; fi
script:
- pwd
- cd cmake
- mkdir build
- cd build
- mkdir build && cd build
- |
cmake \
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
-DOCC_INCLUDE_DIR=/usr/include/opencascade \
-DOCC_LIBRARY_DIR=/usr/lib/x86_64-linux-gnu \
-DPYTHON_LIBRARY=/usr/lib/python2.7/config-x86_64-linux-gnu/libpython2.7.so \
-DPYTHON_INCLUDE_DIR=/usr/include/python2.7 \
-DPYTHON_EXECUTABLE=/usr/bin/python2.7 \
-DLIBXML2_INCLUDE_DIR=/usr/include/libxml2 \
-DLIBXML2_LIBRARIES="/usr/lib/x86_64-linux-gnu/libxml2.so;/lib/x86_64-linux-gnu/libz.so.1;/lib/x86_64-linux-gnu/liblzma.so.5;/usr/lib/x86_64-linux-gnu/libicuuc.so.60;/usr/lib/x86_64-linux-gnu/libicudata.so.60" \
-DPYTHON_EXECUTABLE:FILEPATH=/usr/bin/python3 \
-DPYTHON_INCLUDE_DIR:PATH=/usr/include/python3.8 \
-DPYTHON_LIBRARY:FILEPATH=/usr/lib/x86_64-linux-gnu/libpython3.8.so \
-DGLTF_SUPPORT=On \
-DJSON_INCLUDE_DIR=/usr/include/json \
..
- sudo make -j2 install
-DJSON_INCLUDE_DIR=/usr/include \
../cmake
# TODO: Drop sudo
- sudo make -j $(nproc) install
- cd ../../test
- /usr/bin/python2.7 tests.py
- cd ../test
- /usr/bin/python tests.py
- cd input
- /usr/local/bin/IfcConvert -yv acad2010_walls.ifc acad2010_walls.glb
# - gltf_validator acad2010_walls.glb
- /usr/bin/python2.7 -c "from __future__ import print_function; from io import open; import ifcopenshell; f = ifcopenshell.open('encoding.ifc'); assert list(map(ord, f[1][0])) == [39, 97, 39, 32, 49, 109, 179, 32, 8804, 32, 53, 109, 179, 32, 8805, 32, 49, 48, 109, 179]"
- /usr/bin/python -c "from io import open; import ifcopenshell; f = ifcopenshell.open('encoding.ifc'); assert list(map(ord, f[1][0])) == [39, 97, 39, 32, 49, 109, 179, 32, 8804, 32, 53, 109, 179, 32, 8805, 32, 49, 48, 109, 179]"
- |
(for i in *.ifc; do \
@@ -64,3 +68,6 @@ script:
- echo Succeeded
- grep 0$ statuses
- grep 0$ statuses | wc -l
after_script:
- if [ $TRAVIS_OS_NAME == "linux" ]; then ccache -s; fi
+98 -34
View File
@@ -21,7 +21,10 @@ cmake_minimum_required(VERSION 3.1.3)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON) # not necessary, but encouraged
project (IfcOpenShell)
project (IfcOpenShell VERSION 0.6.0)
# use extra version to make pre-release using eg semver
set( EXTRA_VERSION "-alpha.3")
foreach(max_year RANGE 2014 2030)
set(max_sdk "$ENV{ADSK_3DSMAX_SDK_${max_year}}")
@@ -35,8 +38,10 @@ OPTION(COLLADA_SUPPORT "Build IfcConvert with COLLADA support (requires OpenCOLL
OPTION(IFCXML_SUPPORT "Build IfcParse with ifcXML support (requires libxml2)." ON)
OPTION(ENABLE_BUILD_OPTIMIZATIONS "Enable certain compiler and linker optimizations on RelWithDebInfo and Release builds." OFF)
OPTION(IFCCONVERT_DOUBLE_PRECISION "IfcConvert: Use double precision floating-point numbers." ON)
OPTION(BUILD_PACKAGE "" OFF)
OPTION(BUILD_IFCGEOM "Build IfcGeom." ON)
OPTION(BUILD_IFCPYTHON "Build IfcPython." OFF)
OPTION(BUILD_DOCUMENTATION "Build IfcOpenShell Documentation." OFF)
OPTION(BUILD_IFCPYTHON "Build IfcPython." ON)
OPTION(BUILD_EXAMPLES "Build example applications." ON)
OPTION(BUILD_GEOMSERVER "Build IfcGeomServer executable." ON)
OPTION(BUILD_CONVERT "Build IfcConvert executable." ON)
@@ -53,7 +58,7 @@ OPTION(USERSPACE_PYTHON_PREFIX "Installs IfcPython for the current user only ins
# TODO QtViewer is deprecated ATM as it uses the 0.4 API
# OPTION(BUILD_QTVIEWER "Build IfcOpenShell Qt GUI Viewer (requires Qt 4 framework)." OFF)
include(GNUInstallDirs)
if((BUILD_CONVERT OR BUILD_GEOMSERVER OR BUILD_IFCPYTHON) AND (NOT BUILD_IFCGEOM))
message(STATUS "'IfcGeom' is required with current outputs")
set(BUILD_IFCGEOM ON)
@@ -64,7 +69,7 @@ IF(NOT BINDIR)
set(BINDIR bin)
ENDIF()
IF(NOT IS_ABSOLUTE ${BINDIR})
set(BINDIR ${CMAKE_INSTALL_PREFIX}/${BINDIR})
set(BINDIR ${CMAKE_INSTALL_BINDIR})
ENDIF()
MESSAGE(STATUS "BINDIR: ${BINDIR}")
@@ -72,7 +77,7 @@ IF(NOT INCLUDEDIR)
set(INCLUDEDIR include)
ENDIF()
IF(NOT IS_ABSOLUTE ${INCLUDEDIR})
set(INCLUDEDIR ${CMAKE_INSTALL_PREFIX}/${INCLUDEDIR})
set(INCLUDEDIR ${CMAKE_INSTALL_INCLUDEDIR})
ENDIF()
MESSAGE(STATUS "INCLUDEDIR: ${INCLUDEDIR}")
@@ -80,11 +85,11 @@ IF(NOT LIBDIR)
set(LIBDIR lib)
ENDIF()
IF(NOT IS_ABSOLUTE ${LIBDIR})
set(LIBDIR ${CMAKE_INSTALL_PREFIX}/${LIBDIR})
set(LIBDIR ${CMAKE_INSTALL_LIBDIR})
ENDIF()
MESSAGE(STATUS "LIBDIR: ${LIBDIR}")
set(IFCOPENSHELL_LIBARY_DIR "") # for *nix rpaths
set(IFCOPENSHELL_LIBRARY_DIR "") # for *nix rpaths
if (BUILD_SHARED_LIBS)
add_definitions(-DIFC_SHARED_BUILD)
@@ -94,7 +99,7 @@ if (BUILD_SHARED_LIBS)
# There will be couple hundreds of these so suppress them away, https://msdn.microsoft.com/en-us/library/esew7y1w.aspx
add_definitions(-wd4251)
endif()
set(IFCOPENSHELL_LIBARY_DIR "${LIBDIR}")
set(IFCOPENSHELL_LIBRARY_DIR "${LIBDIR}")
endif()
# Create cache entries if absent for environment variables
@@ -174,6 +179,7 @@ if(USE_MMAP)
endif()
if (IFCXML_SUPPORT)
find_package(LibXml2 REQUIRED)
add_definitions(-DWITH_IFCXML)
endif()
@@ -218,45 +224,57 @@ endfunction()
if(BUILD_IFCGEOM)
IF(MSVC)
add_debug_variants(LIBXML2_LIBRARIES "${LIBXML2_LIBRARIES}" d)
add_debug_variants(LIBXML2_LIBRARIES "${LIBXML2_LIBRARIES}" d)
ENDIF()
# Find Open CASCADE
# Open CASCADE
IF("${OCC_INCLUDE_DIR}" STREQUAL "")
SET(OCC_INCLUDE_DIR "/usr/include/oce/" CACHE FILEPATH "Open CASCADE header files")
MESSAGE(STATUS "Looking for Open CASCADE include files in: ${OCC_INCLUDE_DIR}")
MESSAGE(STATUS "Use OCC_INCLUDE_DIR to specify another directory")
FIND_PATH(OCC_INCLUDE_DIR Standard_Version.hxx
[PATHS
/usr/include/occt
/usr/include/oce
/usr/include/opencascade
]
REQUIRED
)
IF(OCC_INCLUDE_DIR)
MESSAGE(STATUS "Found Open CASCADE include files in: ${OCC_INCLUDE_DIR}")
ELSE()
MESSAGE(FATAL_ERROR "Unable to find Open CASCADE include directory, specify OCC_INCLUDE_DIR manually.")
ENDIF()
ELSE()
SET(OCC_INCLUDE_DIR ${OCC_INCLUDE_DIR} CACHE FILEPATH "Open CASCADE header files")
MESSAGE(STATUS "Looking for Open CASCADE include files in: ${OCC_INCLUDE_DIR}")
ENDIF()
FIND_FILE(gp_Pnt_hxx "gp_Pnt.hxx" ${OCC_INCLUDE_DIR})
IF(gp_Pnt_hxx)
MESSAGE(STATUS "Header files found")
ELSE()
MESSAGE(FATAL_ERROR "Unable to find header files, aborting")
SET(OCC_INCLUDE_DIR ${OCC_INCLUDE_DIR} CACHE FILEPATH "Open CASCADE header files")
MESSAGE(STATUS "Looking for Open CASCADE include files in: ${OCC_INCLUDE_DIR}")
ENDIF()
SET(OPENCASCADE_LIBRARY_NAMES
TKernel TKMath TKBRep TKGeomBase TKGeomAlgo TKG3d TKG2d TKShHealing TKTopAlgo TKMesh TKPrim TKBool TKBO
TKFillet TKSTEP TKSTEPBase TKSTEPAttr TKXSBase TKSTEP209 TKIGES TKOffset TKHLR
TKernel TKMath TKBRep TKGeomBase TKGeomAlgo TKG3d TKG2d TKShHealing TKTopAlgo TKMesh TKPrim TKBool TKBO
TKFillet TKSTEP TKSTEPBase TKSTEPAttr TKXSBase TKSTEP209 TKIGES TKOffset TKHLR
)
IF("${OCC_LIBRARY_DIR}" STREQUAL "")
SET(OCC_LIBRARY_DIR "/usr/lib/" CACHE FILEPATH "Open CASCADE library files")
MESSAGE(STATUS "Looking for Open CASCADE library files in: ${OCC_LIBRARY_DIR}")
MESSAGE(STATUS "Use OCC_LIBRARY_DIR to specify another directory")
find_library(OCC_LIBRARY TKernel
[PATHS
/usr/lib
]
REQUIRED
)
IF(OCC_LIBRARY)
GET_FILENAME_COMPONENT(OCC_LIBRARY_DIR ${OCC_LIBRARY} PATH)
MESSAGE(STATUS "Found Open CASCADE library files in: ${OCC_LIBRARY_DIR}")
ELSE()
MESSAGE(FATAL_ERROR "Unable find Open CASCADE library directory, specify OCC_LIBRARY_DIR manually.")
ENDIF()
ELSE()
SET(OCC_LIBRARY_DIR ${OCC_LIBRARY_DIR} CACHE FILEPATH "Open CASCADE library files")
MESSAGE(STATUS "Looking for Open CASCADE library files in: ${OCC_LIBRARY_DIR}")
SET(OCC_LIBRARY_DIR ${OCC_LIBRARY_DIR} CACHE FILEPATH "Open CASCADE library files")
MESSAGE(STATUS "Looking for Open CASCADE library files in: ${OCC_LIBRARY_DIR}")
ENDIF()
FIND_LIBRARY(libTKernel NAMES TKernel TKerneld PATHS ${OCC_LIBRARY_DIR} NO_DEFAULT_PATH)
IF(libTKernel)
MESSAGE(STATUS "Library files found")
MESSAGE(STATUS "Required Open Cascade Library files found")
ELSE()
MESSAGE(FATAL_ERROR "Unable to find library files, aborting")
MESSAGE(FATAL_ERROR "Unable to find Open Cascade library files, aborting")
ENDIF()
# Use the found libTKernel as a template for all other OCC libraries
@@ -472,7 +490,7 @@ function(files_for_ifc_version IFC_VERSION RESULT_NAME)
)
endfunction()
set(SCHEMA_VERSIONS "2x3" "4" "4x1" "4x2" "4x3_rc1")
set(SCHEMA_VERSIONS "2x3" "4" "4x1" "4x2" "4x3_rc1" "4x3_rc2")
if(COMPILE_SCHEMA)
# @todo, this appears to be untested at the moment
@@ -623,7 +641,7 @@ if ((NOT WIN32) AND BUILD_SHARED_LIBS)
# Only set RPATHs when building shared libraries (i.e. IfcParse and
# IfcGeom are dynamically linked). Not necessarily a perfect solution
# but probably a good indication of whether RPATHs are necessary.
SET_INSTALL_RPATHS(IfcConvert "${IFCOPENSHELL_LIBARY_DIR};${OCC_LIBRARY_DIR};${Boost_LIBRARY_DIRS};${OPENCOLLADA_LIBRARY_DIR}")
SET_INSTALL_RPATHS(IfcConvert "${IFCOPENSHELL_LIBRARY_DIR};${OCC_LIBRARY_DIR};${Boost_LIBRARY_DIRS};${OPENCOLLADA_LIBRARY_DIR}")
endif()
INSTALL(TARGETS IfcConvert
@@ -644,7 +662,7 @@ ADD_EXECUTABLE(IfcGeomServer ${SOURCE_FILES})
TARGET_LINK_LIBRARIES(IfcGeomServer ${IFCOPENSHELL_LIBRARIES} ${OPENCASCADE_LIBRARIES} ${Boost_LIBRARIES})
if ((NOT WIN32) AND BUILD_SHARED_LIBS)
SET_INSTALL_RPATHS(IfcGeomServer "${IFCOPENSHELL_LIBARY_DIR};${OCC_LIBRARY_DIR};${Boost_LIBRARY_DIRS}")
SET_INSTALL_RPATHS(IfcGeomServer "${IFCOPENSHELL_LIBRARY_DIR};${OCC_LIBRARY_DIR};${Boost_LIBRARY_DIRS}")
endif()
INSTALL(TARGETS IfcGeomServer
@@ -655,6 +673,12 @@ INSTALL(TARGETS IfcGeomServer
endif()
# Documentation
IF(BUILD_DOCUMENTATION)
set(CMAKE_MODULE_PATH "../docs/cmake")
ADD_SUBDIRECTORY(../docs docs)
ENDIF()
IF(BUILD_IFCPYTHON)
ADD_SUBDIRECTORY(../src/ifcwrap ifcwrap)
ENDIF()
@@ -709,3 +733,43 @@ INSTALL(FILES ${SERIALIZERS_S_FILES}
DESTINATION ${INCLUDEDIR}/serializers/schema_dependent
)
endif()
list(APPEND CPACK_SOURCE_IGNORE_FILES
.git
.gitignore
)
set(CPACK_PACKAGE_NAME "${PROJECT_NAME}-${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}${EXTRA_VERSION}")
set(CPACK_SOURCE_PACKAGE_FILE_NAME "${PROJECT_NAME}-${PROJECT_VERSION}${EXTRA_VERSION}")
SET(CPACK_PACKAGE_FILE_NAME "${PROJECT_NAME}-${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}${EXTRA_VERSION}-${CMAKE_SYSTEM_NAME}")
set(CPACK_PACKAGE_DIRECTORY "${PROJECT_BINARY_DIR}/assets")
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "IfcOpenShell")
set(CPACK_PACKAGE_DESCRIPTION "IfcOpenShell.")
set(CPACK_PACKAGE_VENDOR "Cemosis")
set(CPACK_PACKAGE_CONTACT "Christophe Prud'homme <christophe.prudhomme@cemosis.fr>")
set(CPACK_PACKAGE_VERSION_MAJOR "${PROJECT_VERSION_MAJOR}")
set(CPACK_PACKAGE_VERSION_MINOR "${PROJECT_VERSION_MINOR}")
set(CPACK_PACKAGE_VERSION_PATCH "${PROJECT_VERSION_PATCH}")
set(CPACK_GENERATOR "TGZ;DEB")
set(CPACK_SOURCE_GENERATOR "TGZ")
FOREACH(COMPONENT IN ITEMS ${BOOST_COMPONENTS})
string(REPLACE "_" "-" COMP ${COMPONENT})
set(BOOST_DEPS "${BOOST_DEPS}, libboost-${COMP}1.67.0")
endforeach(COMPONENT)
set(CPACK_DEBIAN_PACKAGE_NAME "${PROJECT_NAME}")
set(CPACK_DEBIAN_PACKAGE_MAINTAINER "${CPACK_PACKAGE_CONTACT}")
set(CPACK_DEBIAN_PACKAGE_DEPENDS "python3, libxml2, liboce-foundation11, liboce-modeling11, liboce-ocaf11, liboce-visualization11, liboce-ocaf-lite11, libpython3.8 ${BOOST_DEPS}")
set(CPACK_DEBIAN_PACKAGE_DESCRIPTION_SUMMARY "${CPACK_PACKAGE_DESCRIPTION_SUMMARY}")
set(CPACK_DEBIAN_PACKAGE_DESCRIPTION "${CPACK_PACKAGE_DESCRIPTION}")
set(CPACK_DEBIAN_PACKAGE_PRIORITY "optional")
set(CPACK_DEBIAN_PACKAGE_SECTION "science")
set(CPACK_DEBIAN_PACKAGE_VERSION "${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}${EXTRA_VERSION}")
set(CPACK_DEBIAN_ARCHITECTURE "${CMAKE_SYSTEM_PROCESSOR}")
#set(CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA "${CMAKE_SOURCE_DIR}/cmake/debian/postinst")
include(CPack)
+40
View File
@@ -0,0 +1,40 @@
#Look for an executable called sphinx-build
find_program(SPHINX_EXECUTABLE
NAMES sphinx-build
DOC "Path to sphinx-build executable")
include(FindPackageHandleStandardArgs)
#Handle standard arguments to find_package like REQUIRED and QUIET
find_package_handle_standard_args(Sphinx
"Failed to find sphinx-build executable"
SPHINX_EXECUTABLE)
find_package(Doxygen REQUIRED)
#find_package(Sphinx REQUIRED)
set(SPHINX_SOURCE ${CMAKE_CURRENT_SOURCE_DIR})
set(SPHINX_BUILD ${CMAKE_CURRENT_BINARY_DIR}/docs/sphinx)
MESSAGE(STATUS "SPHINX BUILD ${CMAKE_CURRENT_BINARY_DIR}")
file(MAKE_DIRECTORY ./output/doxygen)
if (DOXYGEN_FOUND)
add_custom_target(Sphinx ALL
COMMAND
${SPHINX_EXECUTABLE} -v -T -b html
${SPHINX_SOURCE} ${CMAKE_CURRENT_SOURCE_DIR}/output
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/output
COMMENT "Generating documentation with Sphinx")
# add_custom_target(ifcopenshell_python_docs ALL
# COMMAND make html
# WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/../src/ifcblenderexport/docs
# OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/../src/ifcblenderexport/docs
# COMMENT "Generating documentation with Sphinx")
else (DOXYGEN_FOUND)
message("Doxygen need to be installed to generate the doxygen documentation")
endif (DOXYGEN_FOUND)
+2531
View File
File diff suppressed because it is too large Load Diff
+11
View File
@@ -0,0 +1,11 @@
#Look for an executable called sphinx-build
find_program(SPHINX_EXECUTABLE
NAMES sphinx-build
DOC "Path to sphinx-build executable")
include(FindPackageHandleStandardArgs)
#Handle standard arguments to find_package like REQUIRED and QUIET
find_package_handle_standard_args(Sphinx
"Failed to find sphinx-build executable"
SPHINX_EXECUTABLE)
+11
View File
@@ -0,0 +1,11 @@
#Look for an executable called sphinx-build
find_program(SPHINX_EXECUTABLE
NAMES sphinx-build
DOC "Path to sphinx-build executable")
include(FindPackageHandleStandardArgs)
#Handle standard arguments to find_package like REQUIRED and QUIET
find_package_handle_standard_args(Sphinx
"Failed to find sphinx-build executable"
SPHINX_EXECUTABLE)
+37
View File
@@ -0,0 +1,37 @@
import textwrap
# The `extensions` list should already be in here from `sphinx-quickstart`
extensions = [
# there may be others here already, e.g. 'sphinx.ext.mathjax'
'breathe',
'exhale',
'sphinx.ext.intersphinx'
]
project = 'IfcOpenShell'
copyright = '2020, IfcOpenShell'
author = ''
# Setup the breathe extension
breathe_projects = {
"My Project": "./output/doxygen/xml"
}
breathe_default_project = "My Project"
# Setup the exhale extension
exhale_args = {
# These arguments are required
"containmentFolder": "./rst_files",
"rootFileName": "library_root.rst",
"rootFileTitle": "IfcOpenShell C++",
"doxygenStripFromPath": "..",
# Suggested optional arguments
"createTreeView": True,
# TIP: if using the sphinx-bootstrap-theme, you need
# "treeViewIsBootstrap": True,
"exhaleExecutesDoxygen": True,
# "exhaleDoxygenStdin": "INPUT = ../src/serializers/ ../src/ifcgeom",
"exhaleUseDoxyfile": True
}
cpp_id_attributes = ['IFC_PARSE_API', 'IFC_GEOM_API']
+10
View File
@@ -0,0 +1,10 @@
IfcOpenShell
============
.. toctree::
:maxdepth: 3
rst_files/library_root
* `ifcopenshell-python documentation <python/html/index.html>`_
+39
View File
@@ -0,0 +1,39 @@
# This program requires doxygen, sphinx, breathe and exhale.
import os
import sys
import shutil
import subprocess
import multiprocessing
# some extra check to see if we can find sphinx in pypy bin dir
sphinx_build = os.path.join(os.path.dirname(sys.executable), 'sphinx-build')
if not os.path.exists(sphinx_build):
sphinx_build = "sphinx-build"
# else:
# os.environ['PATH'] = os.pathsep + os.path.dirname(sys.executable)
# not used, -j caused issues in my sphinx install
j = str(multiprocessing.cpu_count())
if not os.path.isdir("./output/doxygen/xml"):
os.makedirs("output/doxygen/xml")
subprocess.check_call([sphinx_build, "-b", "html", ".", "output"])
if sys.platform == "linux" or sys.platform == "linux2":
if os.path.isdir("output/python"):
shutil.rmtree("output/python")
if not os.path.exists(os.path.join("../src/ifcblenderexport/docs", "contents.rst")):
subprocess.check_call(["ln", "index.rst", "contents.rst"], cwd="../src/ifcblenderexport/docs")
subprocess.check_call(["sed", "-i", "s/html_theme/# html_theme/g", "conf.py"], cwd="../src/ifcblenderexport/docs")
subprocess.check_call(["make", "html"], cwd="../src/ifcblenderexport/docs")
subprocess.check_call(["git", "checkout", "conf.py"], cwd="../src/ifcblenderexport/docs")
shutil.move("../src/ifcblenderexport/docs/_build", "./output")
os.rename("./output/_build", "./output/python")
elif sys.platform == "win32" or sys.platform == "win64":
subprocess.check_call(["make.bat", "html"])
+10
View File
@@ -0,0 +1,10 @@
IfcOpenShell
============
.. toctree::
:maxdepth: 3
rst_files/library_root
* `ifcopenshell-python documentation <python/html/index.html>`_
+1 -1
View File
@@ -79,7 +79,7 @@ ch.setLevel(logging.INFO)
logger.addHandler(ch)
PROJECT_NAME="IfcOpenShell"
PYTHON_VERSIONS=["3.9.1"]
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="master"
+6 -3
View File
@@ -50,9 +50,12 @@ class BcfXml:
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"]
if os.path.isfile(os.path.join(self.filepath, "project.bcfp")):
data = self._read_xml("project.bcfp", "project.xsd")
self.project.extension_schema = data["ExtensionSchema"]
if "Project" in data:
self.project.project_id = data["Project"]["@ProjectId"]
self.project.name = data["Project"].get("Name")
return self.project
def edit_project(self):
+1
View File
@@ -2,6 +2,7 @@ class Project:
def __init__(self):
self.project_id = ""
self.name = ""
self.extension_schema = ""
class BimSnippet:
+1
View File
@@ -0,0 +1 @@
xmlschema
+9 -4
View File
@@ -1,17 +1,22 @@
# ifc2ca
Files and scripts for the use of [`Code_Aster`](https://code-aster.org) in IFC-driven FEM analyses
### File Organisation
#### Scripts:
## File Organisation
## Scripts:
- [`ifc2ca.py`](ifc2ca.py): a python script to extract and create a `json` file from an `ifc` file
- [`scriptSalome.py`](scriptSalome.py): a python script to run in the [`Salome-Meca`](https://www.code-aster.org/spip.php?article303) environment. Creates the geometry and the mesh of the structure
- [`scriptCodeAster.py`](scriptCodeAster.py): a python script to create the input file (`.comm`) for Code_Aster
- [`scriptSalomeBonded.py`](scriptSalomeBonded.py): a python script to run in the [`Salome-Meca`](https://www.code-aster.org/spip.php?article303) environment. Creates the geometry and the mesh of the structure by bonding together all structural elements (no connections are considered)
- [`scriptCodeAsterBonded.py`](scriptCodeAsterBonded.py): a python script to create the input file (`.comm`) for Code_Aster for the "bonded" case
#### Analysis Models
## Analysis Models
A number of analysis models with all the relative input/output and script files are provided in [this repository](https://github.com/IfcOpenShell/analysis-models) within the IfcOpenShell Organization
---
### Current Status
## Current Status
Read [Change Log](changelog.md)
+5 -3
View File
@@ -1,6 +1,7 @@
import json
import ifcopenshell
import os
from datetime import datetime
class CA2IFC:
@@ -317,7 +318,8 @@ class CA2IFC:
)
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)
timestamp = int(datetime.now().timestamp())
ownerHistory = self.f.createIfcOwnerHistory(p_o, application, "READWRITE", None, None, None, None, timestamp)
return ownerHistory
@@ -497,8 +499,8 @@ class CA2IFC:
if __name__ == "__main__":
inputFilename = "structure_01.json"
outputFilename = "structure_01.ifc"
inputFilename = "grid_of_beams.json"
outputFilename = "grid_of_beams.ifc"
ca2ifc = CA2IFC(inputFilename, outputFilename)
ca2ifc.convert()
+17 -6
View File
@@ -1,23 +1,32 @@
## A change log of the ifc2ca files
# A change log of the ifc2ca files
## 01/03/21
- Add salome and code_aster script files for a "bonded" case, meaning all connections between elements are considered fixed. This allows to consider exported ifc files from commercial software that do not include connections between surface elements (see building_02 example)
## 14/10/20
##### 14/10/20
- Add curve connections betweeen elements and as supports according to the defined orientation - stiffness is not yet considered
##### 17/08/20
## 17/08/20
- Add rigid links for point connection to elements
##### 14/07/20
## 14/07/20
- Add internal springs for point connection to elements according to the defined orientation
- Add external springs for point connections according to the defined orientation
##### 27/05/20
## 27/05/20
- Add orientation for point, curve and surface geometries
- Calculate final geometry and orientation based on object placement transformation
- Add warnings in the json file to show any corrections considered while parsing the ifc file from ETABS with a model with rigid links
- Reduce file size by adding material and profile db in the json file and referencing them in the element objects
- Implement internal releases for curve-to-point connections with any arbitrary orientation
##### 22/03/20
## 22/03/20
- Calculation of section properties for I symmetric profile if not provided (assuming fillet radius is zero)
- Added geometry of planar structural surface members
- Refactored the ifc2ca.py script
@@ -25,10 +34,12 @@
- Added advanced meshing script to correctly simulate connections and independent meshing of structural elements (no common nodes)
##### 26/02/20
- Changed section to profile and added I profile
- Modified material and profile schema with mechanical and common properties
- Added geometry identification for point connection from representation
- Added connections along with supports based on the number of elements a connection is applied to
##### 09/02/20
- First implementation for straight structural curve elements and point connections
+123 -41
View File
@@ -17,7 +17,9 @@ class IFC2CA:
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")
connections = self.get_structural_items(
model, item_type="IfcStructuralConnection"
)
materialdb = []
materials = list(dict.fromkeys([e["material"] for e in elements]))
@@ -25,16 +27,24 @@ class IFC2CA:
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
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])
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 = {
@@ -47,11 +57,11 @@ class IFC2CA:
"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(f"Model {model.Name} converted")
print(f"Number of elements: {len(elements)}")
print(f"Number of connections: {len(connections)}")
print(f"Number of materials: {len(materialdb)}")
print(f"Number of profiles: {len(profiledb)}")
print("")
break
@@ -75,12 +85,19 @@ class IFC2CA:
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()))
"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:
@@ -99,21 +116,32 @@ class IFC2CA:
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())))
if (
np.linalg.norm(np.array(c["eccentricity"]["pointOnElement"]))
> length + self.tol
):
print(
f"{np.linalg.norm(np.array(c['eccentricity']['pointOnElement']))} > {length}"
)
self.warnings.append(
f"Eccentricity in {item.is_a()}|{str(item.id())} corrected"
)
c["eccentricity"]["pointOnElement"][0] = length
# End <--
if transformation:
geometry = self.transform_vectors(geometry, transformation)
orientation = self.transform_vectors(orientation, transformation, include_translation=False)
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["eccentricity"]["vector"],
transformation,
include_translation=False,
)
return {
@@ -134,11 +162,15 @@ class IFC2CA:
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()))
"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())
@@ -151,7 +183,9 @@ class IFC2CA:
conn["orientation"] = orientation
if transformation:
geometry = self.transform_vectors(geometry, transformation)
orientation = self.transform_vectors(orientation, transformation, include_translation=False)
orientation = self.transform_vectors(
orientation, transformation, include_translation=False
)
for c in connections:
c["orientation"] = self.transform_vectors(
c["orientation"], transformation, include_translation=False
@@ -174,7 +208,8 @@ class IFC2CA:
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()))
"No representation defined for %s. Connection excluded"
% (item.is_a() + "|" + str(item.id()))
)
return
@@ -184,7 +219,9 @@ class IFC2CA:
orientation = np.eye(3).tolist()
if transformation:
geometry = self.transform_vectors(geometry, transformation)
orientation = self.transform_vectors(orientation, transformation, include_translation=False)
orientation = self.transform_vectors(
orientation, transformation, include_translation=False
)
return {
"ifcName": item.is_a() + "|" + str(item.id()),
@@ -194,14 +231,18 @@ class IFC2CA:
"geometry": geometry,
"orientation": orientation,
"appliedCondition": self.get_connection_input(item, "point"),
"relatedElements": [con.is_a() + "|" + str(con.id()) for con in item.ConnectsStructuralMembers],
"relatedElements": [
con.is_a() + "|" + str(con.id())
for con in item.ConnectsStructuralMembers
],
}
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()))
"No representation defined for %s. Connection excluded"
% (item.is_a() + "|" + str(item.id()))
)
return
@@ -211,7 +252,9 @@ class IFC2CA:
orientation = np.eye(3).tolist()
if transformation:
geometry = self.transform_vectors(geometry, transformation)
orientation = self.transform_vectors(orientation, transformation, include_translation=False)
orientation = self.transform_vectors(
orientation, transformation, include_translation=False
)
return {
"ifcName": item.is_a() + "|" + str(item.id()),
@@ -221,7 +264,10 @@ class IFC2CA:
"geometry": geometry,
"orientation": orientation,
"appliedCondition": self.get_connection_input(item, "line"),
"relatedElements": [con.is_a() + "|" + str(con.id()) for con in item.ConnectsStructuralMembers],
"relatedElements": [
con.is_a() + "|" + str(con.id())
for con in item.ConnectsStructuralMembers
],
}
def get_transformation(self, placement):
@@ -229,11 +275,15 @@ class IFC2CA:
return None
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)
@@ -253,10 +303,13 @@ class IFC2CA:
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()
f"Warning! Object Placement is of type {placement.is_a()}, which is not supported. Default considered"
)
return None
@@ -264,11 +317,13 @@ class IFC2CA:
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:
# print('Trying without rep identifier')
# print("Trying without rep identifier")
for representation in element.Representation.Representations:
rep = self.get_specific_representation(representation, None, rep_type)
if rep:
@@ -281,7 +336,9 @@ class IFC2CA:
return representation
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):
@@ -299,7 +356,9 @@ class IFC2CA:
edges = item.Bounds[0].Bound.EdgeList
coords = []
for edge in edges:
coords.append(self.get_coordinate(edge.EdgeElement.EdgeStart.VertexGeometry))
coords.append(
self.get_coordinate(edge.EdgeElement.EdgeStart.VertexGeometry)
)
return coords
elif item.is_a("IfcVertexPoint"):
@@ -328,7 +387,9 @@ class IFC2CA:
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)
@@ -347,7 +408,9 @@ 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 = []
@@ -453,13 +516,18 @@ class IFC2CA:
{
"ifcName": rel.is_a() + "|" + str(rel.id()),
"id": rel.GlobalId,
"relatingElement": rel.RelatingStructuralMember.is_a() + "|" + str(rel.RelatingStructuralMember.id()),
"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)
rel,
self.get_geometry_type_from_connection(
rel.RelatedStructuralConnection
),
),
"eccentricity": None
if not rel.is_a("IfcRelConnectsWithEccentricity")
@@ -475,7 +543,9 @@ class IFC2CA:
if not rel.ConnectionConstraint.EccentricityInZ
else rel.ConnectionConstraint.EccentricityInZ,
],
"pointOnElement": self.get_coordinate(rel.ConnectionConstraint.PointOnRelatingElement),
"pointOnElement": self.get_coordinate(
rel.ConnectionConstraint.PointOnRelatingElement
),
},
}
for rel in itemList
@@ -532,11 +602,23 @@ 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"]
fileNames = [
"cantilever_01",
"portal_01",
"grid_of_beams",
"slab_01",
"structure_01",
"building_02",
]
files = fileNames
for fileName in files:
+221 -54
View File
@@ -33,7 +33,9 @@ class COMMANDFILE:
self.calculateRestraints(conn)
for el in elements:
for rel in el["connections"]:
conn = [c for c in connections if c["ifcName"] == rel["relatedConnection"]][0]
conn = [
c for c in connections if c["ifcName"] == rel["relatedConnection"]
][0]
rel["conn_string"] = None
if conn["geometryType"] == "point":
rel["conn_string"] = "_0DC_"
@@ -61,7 +63,10 @@ class COMMANDFILE:
+ self.getGroupName(rel["relatingElement"])
)
rel["index"] = len(conn["relatedElements"]) + 1
rel["unifiedGroupName"] = self.getGroupName(rel["relatedConnection"]) + "_0DC_%g" % rel["index"]
rel["unifiedGroupName"] = (
self.getGroupName(rel["relatedConnection"])
+ "_0DC_%g" % rel["index"]
)
else:
rel["groupName2"] = self.getGroupName(rel["relatedConnection"])
self.calculateConstraints(rel)
@@ -71,25 +76,54 @@ class COMMANDFILE:
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"])
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"]
[
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]
[
[
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"]
[
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"]
rel["unifiedGroupName"]
for rel in conn["relatedElements"]
if rel["eccentricity"]
]
# if not conn['appliedCondition'] and len(conn['unifiedGroupNames']) == 1:
# conn['appliedCondition'] = {
@@ -103,7 +137,9 @@ class COMMANDFILE:
unifiedConnection = True
rigidLinkGroupNames.extend(
[
self.getGroupName(rel["relatingElement"]) + "_1DR_" + self.getGroupName(conn["ifcName"])
self.getGroupName(rel["relatingElement"])
+ "_1DR_"
+ self.getGroupName(conn["ifcName"])
for rel in conn["relatedElements"]
if rel["eccentricity"]
]
@@ -182,7 +218,9 @@ model = AFFE_MODELE(
MODELISATION = 'DIS_TR'
),"""
context = {"groupNames": tuple(flatten([point0DGroupNames, spring1DGroupNames]))}
context = {
"groupNames": tuple(flatten([point0DGroupNames, spring1DGroupNames]))
}
f.write(template.format(**context))
@@ -234,7 +272,9 @@ model = AFFE_MODELE(
else:
if "shearModulus" in material["mechProps"]:
poissonRatio = (
material["mechProps"]["youngModulus"] / 2.0 / material["mechProps"]["shearModulus"]
material["mechProps"]["youngModulus"]
/ 2.0
/ material["mechProps"]["shearModulus"]
) - 1
else:
poissonRatio = 0.0
@@ -263,7 +303,9 @@ material = AFFE_MATERIAU(
),"""
context = {
"groupNames": tuple([self.getGroupName(rel) for rel in material["relatedElements"]]),
"groupNames": tuple(
[self.getGroupName(rel) for rel in material["relatedElements"]]
),
"matNameID": "mat" + "_%s" % i,
}
@@ -296,7 +338,10 @@ element = AFFE_CARA_ELEM(
)
for profile in profiles:
if profile["profileShape"] == "rectangular" and profile["profileType"] == "AREA":
if (
profile["profileShape"] == "rectangular"
and profile["profileType"] == "AREA"
):
template = """
_F(
GROUP_MA = {groupNames},
@@ -306,13 +351,18 @@ element = AFFE_CARA_ELEM(
),"""
context = {
"groupNames": tuple([self.getGroupName(rel) for rel in profile["relatedElements"]]),
"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":
elif (
profile["profileShape"] == "iSymmetrical"
and profile["profileType"] == "AREA"
):
template = """
_F(
GROUP_MA = {groupNames},
@@ -322,7 +372,9 @@ element = AFFE_CARA_ELEM(
),"""
context = {
"groupNames": tuple([self.getGroupName(rel) for rel in profile["relatedElements"]]),
"groupNames": tuple(
[self.getGroupName(rel) for rel in profile["relatedElements"]]
),
"profileProperties": (
profile["mechProps"]["crossSectionArea"],
profile["mechProps"]["momentOfInertiaY"],
@@ -388,7 +440,10 @@ element = AFFE_CARA_ELEM(
REPERE = 'LOCAL'
),"""
context = {"groupName": self.getGroupName(conn["ifcName"]) + "_0D", "stiffnesses": conn["stiffnesses"]}
context = {
"groupName": self.getGroupName(conn["ifcName"]) + "_0D",
"stiffnesses": conn["stiffnesses"],
}
f.write(template.format(**context))
@@ -402,7 +457,10 @@ element = AFFE_CARA_ELEM(
REPERE = 'LOCAL'
),"""
context = {"groupName": rel["springGroupName"], "stiffnesses": rel["stiffnesses"]}
context = {
"groupName": rel["springGroupName"],
"stiffnesses": rel["stiffnesses"],
}
f.write(template.format(**context))
@@ -416,7 +474,10 @@ element = AFFE_CARA_ELEM(
REPERE = 'LOCAL'
),"""
context = {"groupName": self.getGroupName(conn["ifcName"]) + "_0D", "stiffnesses": conn["stiffnesses"]}
context = {
"groupName": self.getGroupName(conn["ifcName"]) + "_0D",
"stiffnesses": conn["stiffnesses"],
}
f.write(template.format(**context))
@@ -439,7 +500,10 @@ element = AFFE_CARA_ELEM(
VALE = {localAxisY}
),"""
context = {"groupName": self.getGroupName(el["ifcName"]), "localAxisY": tuple(el["orientation"][1])}
context = {
"groupName": self.getGroupName(el["ifcName"]),
"localAxisY": tuple(el["orientation"][1]),
}
f.write(template.format(**context))
@@ -614,7 +678,9 @@ liaisons = AFFE_CHAR_MECA(
LIAISON_UNIF = ("""
)
for conn in [conn for conn in connections if len(conn["unifiedGroupNames"]) > 1]:
for conn in [
conn for conn in connections if len(conn["unifiedGroupNames"]) > 1
]:
template = """
_F(
GROUP_NO = {groupNames},
@@ -798,44 +864,103 @@ FIN()
gr1 = rel["groupName1"]
gr2 = rel["groupName2"]
o = np.array(rel["orientation"]).transpose().tolist()
liaisons = {"groupNames": (gr1, gr1, gr1, gr2, gr2, gr2), "coeffs": [], "dofs": []}
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]))
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:
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]))
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:
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]))
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:
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]))
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:
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]))
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:
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]))
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:
elif (
isinstance(rel["appliedCondition"]["drz"], float)
and rel["appliedCondition"]["drz"] > 0
):
stiffnesses[5] = rel["appliedCondition"]["drz"]
rel["liaisons"] = liaisons
@@ -852,40 +977,76 @@ FIN()
conn["stiffnesses"] = tuple(stiffnesses)
return
if isinstance(conn["appliedCondition"]["dx"], bool) and conn["appliedCondition"]["dx"]:
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:
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"]:
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:
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"]:
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:
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"]:
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:
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"]:
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:
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"]:
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:
elif (
isinstance(conn["appliedCondition"]["drz"], float)
and conn["appliedCondition"]["drz"] > 0
):
stiffnesses[5] = conn["appliedCondition"]["drz"]
conn["liaisons"] = liaisons
@@ -893,7 +1054,13 @@ FIN()
if __name__ == "__main__":
fileNames = ["cantilever_01", "portal_01", "grid_of_beams", "slab_01", "structure_01"]
fileNames = [
"cantilever_01",
"portal_01",
"grid_of_beams",
"slab_01",
"structure_01",
]
files = fileNames
for fileName in files:
+555
View File
@@ -0,0 +1,555 @@
import json
import numpy as np
import itertools
flatten = itertools.chain.from_iterable
ScaleFactor = 1.0
AccelOfGravity = 9.806 * 1000
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):
# 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"] = []
for el in elements:
for rel in el["connections"]:
conn = [
c for c in connections if c["ifcName"] == rel["relatedConnection"]
][0]
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"
]
)
rigidLinkGroupNames = []
for conn in connections:
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 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"])
* ScaleFactor ** 2,
"poissonRatio": float(poissonRatio),
"massDensity": float(material["commonProps"]["massDensity"])
* ScaleFactor ** 3,
}
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"] / ScaleFactor,
profile["yDim"] / ScaleFactor,
),
}
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"] / ScaleFactor ** 2,
profile["mechProps"]["momentOfInertiaY"] / ScaleFactor ** 4,
profile["mechProps"]["momentOfInertiaZ"] / ScaleFactor ** 4,
profile["mechProps"]["torsionalConstantX"] / ScaleFactor ** 4,
),
}
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"] / ScaleFactor,
"localAxisX": tuple(el["orientation"][0]),
}
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))
f.write(
"""
),"""
)
f.write(
"""
)\n
"""
)
f.write("# STEP: DEFINE SUPPORTS AND CONSTRAINTS")
f.write(
"""
liaisons = AFFE_CHAR_MECA(
MODELE = model,
DDL_IMPO = (
_F(
GROUP_NO = 'grdSupps',
DX = 0.0,
DY = 0.0,
DZ = 0.0,
DRX = 0.0,
DRY = 0.0,
DRZ = 0.0
)
),"""
)
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()
if __name__ == "__main__":
fileNames = ["building_02"]
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)
+127 -45
View File
@@ -75,7 +75,9 @@ class MODEL:
shapeType = "EDGE"
if geometryType == "surface":
shapeType = "FACE"
return self.geompy.MakePartition(objects, [], [], [], self.geompy.ShapeType[shapeType], 0, [], 1)
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"])
@@ -157,17 +159,25 @@ class MODEL:
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]
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"])
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"])
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]
@@ -179,9 +189,14 @@ class MODEL:
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"]):
@@ -193,7 +208,9 @@ class MODEL:
# 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(
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)
@@ -203,9 +220,13 @@ class MODEL:
# Loop 2
for el in elements:
# geompy.addToStudy(el['partObj'], self.getGroupName(el['ifcName']))
geompy.addToStudyInFather(el["partObj"], el["elemObj"], 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]
conn = [
c for c in connections if c["ifcName"] == rel["relatedConnection"]
][0]
rel["conn_string"] = None
if conn["geometryType"] == "point":
rel["conn_string"] = "_0DC_"
@@ -216,7 +237,9 @@ class MODEL:
geompy.addToStudyInFather(
el["partObj"],
el["connObjs"][j],
self.getGroupName(el["ifcName"]) + rel["conn_string"] + self.getGroupName(rel["relatedConnection"]),
self.getGroupName(el["ifcName"])
+ rel["conn_string"]
+ self.getGroupName(rel["relatedConnection"]),
)
if rel["eccentricity"]:
pass
@@ -226,28 +249,29 @@ 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))
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:
# 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")
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")
@@ -255,34 +279,45 @@ class MODEL:
# 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"]),
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"]),
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"]),
self.getGroupName(rel["relatedConnection"])
+ "_0DC_"
+ self.getGroupName(el["ifcName"]),
)
geompy.addToStudyInFather(
bldComp,
el["linkPointObjs"][j][1],
self.getGroupName(rel["relatedConnection"]) + "_0DC_%g" % rel["index"],
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
@@ -339,7 +374,9 @@ class MODEL:
smesh.SetName(tempgroup, "CurveMembers")
if len([e for e in elements if e["geometryType"] == "surface"]) > 0:
tempgroup = bldMesh.GroupOnGeom(surfaceCompound, "SurfaceMembers", SMESH.FACE)
tempgroup = bldMesh.GroupOnGeom(
surfaceCompound, "SurfaceMembers", SMESH.FACE
)
smesh.SetName(tempgroup, "SurfaceMembers")
# Define groups in Mesh
@@ -348,41 +385,59 @@ class MODEL:
shapeType = SMESH.EDGE
if el["geometryType"] == "surface":
shapeType = SMESH.FACE
tempgroup = bldMesh.GroupOnGeom(el["elemObj"], self.getGroupName(el["ifcName"]), shapeType)
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"]),
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"]),
self.getGroupName(el["ifcName"])
+ rel["conn_string"]
+ self.getGroupName(rel["relatedConnection"]),
)
rel["node"] = (bldMesh.GetIDSource(tempgroup.GetNodeIDs(), SMESH.NODE)).GetIDs()[0]
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"]),
self.getGroupName(el["ifcName"])
+ "_1DR_"
+ self.getGroupName(rel["relatedConnection"]),
SMESH.EDGE,
)
smesh.SetName(
tempgroup,
self.getGroupName(el["ifcName"]) + "_1DR_" + self.getGroupName(rel["relatedConnection"]),
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"]),
self.getGroupName(rel["relatedConnection"])
+ "_0DC_"
+ self.getGroupName(el["ifcName"]),
SMESH.NODE,
)
smesh.SetName(
tempgroup,
self.getGroupName(rel["relatedConnection"]) + "_0DC_" + self.getGroupName(el["ifcName"]),
self.getGroupName(rel["relatedConnection"])
+ "_0DC_"
+ self.getGroupName(el["ifcName"]),
)
rel["eccNode"] = (bldMesh.GetIDSource(tempgroup.GetNodeIDs(), SMESH.NODE)).GetIDs()[0]
rel["eccNode"] = (
bldMesh.GetIDSource(tempgroup.GetNodeIDs(), SMESH.NODE)
).GetIDs()[0]
tempgroup = bldMesh.GroupOnGeom(
el["linkPointObjs"][j][1],
@@ -391,38 +446,60 @@ class MODEL:
+ self.getGroupName(rel["relatedConnection"]),
SMESH.NODE,
)
smesh.SetName(tempgroup, self.getGroupName(rel["relatedConnection"]) + "_0DC_%g" % rel["index"])
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)
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"]))
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)
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)
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]
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"]),
self.getGroupName(el["ifcName"])
+ "_1DS_"
+ self.getGroupName(rel["relatedConnection"]),
)
smesh.SetName(
grpName,
self.getGroupName(el["ifcName"]) + "_1DS_" + self.getGroupName(rel["relatedConnection"]),
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]
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"]])])
@@ -437,7 +514,12 @@ class MODEL:
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)
+410
View File
@@ -0,0 +1,410 @@
from __future__ import division
from __future__ import print_function
import os
import time
import json
import salome
import salome_notebook
import salome_version
import numpy as np
import itertools
flatten = itertools.chain.from_iterable
ScaleFactor = 1.0
decimals = 2
class MODEL:
def __init__(self, dataFilename, medFilename, meshSize, zGround):
self.dataFilename = dataFilename
self.medFilename = medFilename
self.meshSize = meshSize
self.zGround = zGround
self.tolLoc = 0
self.mesh = None
self.meshNodes = None
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 makePoint(self, pl):
"""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)"""
(x, y, z) = pl[0]
P1 = self.geompy.MakeVertex(x, y, z)
(x, y, z) = pl[1]
P2 = self.geompy.MakeVertex(x, y, z)
return self.geompy.MakeLineTwoPnt(P1, P2)
def makeFace(self, pl):
"""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):
pointList[ip] = self.geompy.MakeVertex(x, y, z)
LineList = [None for _ in range(len(pl))]
for ip, P2 in enumerate(pointList):
P1 = pointList[ip - 1]
LineList[ip] = self.geompy.MakeLineTwoPnt(P1, P2)
return self.geompy.MakeFaceWires(LineList, 1)
def makeObject(self, geometry, geometryType):
geometry = np.round(np.array(geometry), decimals=decimals) / ScaleFactor
geometry = geometry.tolist()
if geometryType == "point":
return self.makePoint(geometry)
if geometryType == "line":
return self.makeLine(geometry)
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"
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"])
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
def select(self, elements):
zmin = -100
zmax = 126300
for el in elements:
include = True
for p in el["geometry"]:
if p[2] < zmin or p[2] > zmax:
include = False
break
el["include"] = include
return [el for el in elements if el["include"]]
def create(self):
# Read data from input file
with open(self.dataFilename) as dataFile:
data = json.load(dataFile)
# print(len(data['elements']))
# elements = self.select(data['elements'])
# print(len(elements))
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"] = []
# End <--
meshSize = self.meshSize / ScaleFactor
zGround = self.zGround / ScaleFactor
dec = 5 # 4 decimals for length in mm
tol = 10 ** (-dec - 3 + 1)
self.tolLoc = tol * 10 * 2
tolLoc = self.tolLoc
NEW_SALOME = int(salome_version.getVersion()[0]) >= 9
salome.salome_init()
theStudy = salome.myStudy
notebook = salome_notebook.NoteBook(theStudy)
###
### GEOM component
###
import GEOM
from salome.geom import geomBuilder
import math
import SALOMEDS
gg = salome.ImportComponentGUI("GEOM")
if NEW_SALOME:
geompy = geomBuilder.New()
else:
geompy = geomBuilder.New(theStudy)
self.geompy = geompy
O = geompy.MakeVertex(0, 0, 0)
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")
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")
init_time = start_time
# Loop 1
for el in elements:
el["elemObj"] = self.makeObject(el["geometry"], el["geometryType"])
el["linkObjs"] = [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
geometry = self.getLinkGeometry(
rel["eccentricity"], el["orientation"], conn["geometry"]
)
el["linkObjs"][j] = self.makeObject(geometry, "line")
conn["relatedElements"].append(rel)
# Make assemble of Building Object
bldObjs = []
bldObjs.extend([el["elemObj"] for el in elements])
bldObjs.extend(
flatten([[link for link in el["linkObjs"] if link] for el in elements])
)
# bldComp = geompy.MakeCompound(bldObjs)
bldComp = geompy.MakePartition(
bldObjs, [], [], [], self.geompy.ShapeType[buildingShapeType], 0, [], 1
)
geompy.addToStudy(bldComp, "bldComp")
elapsed_time = time.time() - init_time
init_time += elapsed_time
print("Building Geometry Defined in %g sec" % (elapsed_time))
# Define and add groups for all curve, surface and rigid members
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"]
)
# Define group object and add to study
curveCompound = geompy.GetInPlace(bldComp, compoundTemp)
geompy.addToStudyInFather(bldComp, curveCompound, "CurveMembers")
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"]
)
# Define group object and add to study
surfaceCompound = geompy.GetInPlace(bldComp, compoundTemp)
geompy.addToStudyInFather(bldComp, surfaceCompound, "SurfaceMembers")
linkObjs = list(
flatten([[obj for obj in el["linkObjs"] if obj] for el in elements])
)
if len(linkObjs) > 0:
# Make compound of requested group
compoundTemp = geompy.MakeCompound(linkObjs)
# Define group object and add to study
rigidCompound = geompy.GetInPlace(bldComp, compoundTemp)
geompy.addToStudyInFather(bldComp, rigidCompound, "RigidMembers")
for el in elements:
# el['partObj'] = geompy.RestoreGivenSubShapes(bldComp, [el['partObj']], GEOM.FSM_GetInPlace, False, False)[0]
el["elemObj"] = geompy.GetInPlace(bldComp, el["elemObj"])
geompy.addToStudyInFather(
bldComp, el["elemObj"], self.getGroupName(el["ifcName"])
)
for j, rel in enumerate(el["connections"]):
if rel["eccentricity"]: # point geometry
el["linkObjs"][j] = geompy.GetInPlace(bldComp, el["linkObjs"][j])
geompy.addToStudyInFather(
bldComp,
el["linkObjs"][j],
self.getGroupName(el["ifcName"])
+ "_1DR_"
+ self.getGroupName(rel["relatedConnection"]),
)
elapsed_time = time.time() - init_time
init_time += elapsed_time
print("Building Geometry Groups Defined in %g sec" % (elapsed_time))
###
### SMESH component
###
import SMESH
from salome.smesh import smeshBuilder
print("Defining Mesh Components")
if NEW_SALOME:
smesh = smeshBuilder.New()
else:
smesh = smeshBuilder.New(theStudy)
bldMesh = smesh.Mesh(bldComp)
Regular_1D = bldMesh.Segment()
Local_Length_1 = Regular_1D.LocalLength(meshSize, None, tolLoc)
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.SetUseSurfaceCurvature(1)
NETGEN2D_Pars.SetQuadAllowed(1)
NETGEN2D_Pars.SetSecondOrder(0)
NETGEN2D_Pars.SetFuseEdges(254)
isDone = bldMesh.Compute()
coincident_nodes_on_part = bldMesh.FindCoincidentNodesOnPart(
[bldMesh], tolLoc, [], 0
)
if coincident_nodes_on_part:
# bldMesh.MergeNodes(coincident_nodes_on_part, [], 0)
# print(f'{len(coincident_nodes_on_part)} Sets of Coincident Nodes Found and Merged')
print(f"{len(coincident_nodes_on_part)} Sets of Coincident Nodes Found")
print(f"{coincident_nodes_on_part}")
## Set names of Mesh objects
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")
smesh.SetName(bldMesh.GetMesh(), "bldMesh")
elapsed_time = time.time() - init_time
init_time += elapsed_time
print("Meshing Operations Completed in %g sec" % (elapsed_time))
# Define and add groups for all curve, surface and rigid 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"] == "surface"]) > 0:
tempgroup = bldMesh.GroupOnGeom(
surfaceCompound, "SurfaceMembers", SMESH.FACE
)
smesh.SetName(tempgroup, "SurfaceMembers")
if len(linkObjs) > 0:
tempgroup = bldMesh.GroupOnGeom(rigidCompound, "RigidMembers", SMESH.EDGE)
smesh.SetName(tempgroup, "RigidMembers")
# Define groups in Mesh
for el in elements:
if el["geometryType"] == "line":
shapeType = SMESH.EDGE
if el["geometryType"] == "surface":
shapeType = SMESH.FACE
tempgroup = bldMesh.GroupOnGeom(
el["elemObj"], self.getGroupName(el["ifcName"]), shapeType
)
smesh.SetName(tempgroup, self.getGroupName(el["ifcName"]))
for j, rel in enumerate(el["connections"]):
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"]),
)
self.mesh = bldMesh
self.meshNodes = bldMesh.GetNodesId()
# Find ground supports and extract node coordinates
grdSupps = bldMesh.CreateEmptyGroup(SMESH.NODE, "grdSupps")
for node in self.meshNodes:
coords = bldMesh.GetNodeXYZ(node)
if abs(coords[2] - self.zGround) < tolLoc:
grdSupps.Add([node])
smesh.SetName(grdSupps, "grdSupps")
elapsed_time = time.time() - init_time
init_time += 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,
)
else:
bldMesh.ExportMED(self.medFilename, 0, SMESH.MED_V2_2, 1, None, 0)
except:
print("ExportMED() failed. Invalid file name?")
if salome.sg.hasDesktop():
if NEW_SALOME:
salome.sg.updateObjBrowser()
else:
salome.sg.updateObjBrowser(1)
elapsed_time = init_time - start_time
print("ALL Operations Completed in %g sec" % (elapsed_time))
if __name__ == "__main__":
fileNames = ["building_02"]
files = fileNames
meshSize = 500
zGround = 0
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"
model = MODEL(DATAFILENAME, MEDFILENAME, meshSize, zGround)
-53
View File
@@ -1,53 +0,0 @@
# ****************************************************************************
# 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
# standard files
*.feature text
*.html text
*.ifc text
*.md text
*.po text
*.pot text
*.py text
# files which are human readable
# but for which it is not sure if normalize is ok
# svg
# binary files
# ico
# mo
# line endings of the directories commented will be normalized
# bimtester/** -text
# examples/** -text
# line endings of the directories NOT commented will NOT be normalized
# Be carefully changes here could affect a lot of files automatically!
# none ATM
# use git to manually correct the file endings
# git add --renormalize .
+110 -17
View File
@@ -1,29 +1,122 @@
# BIMTester
### Packages to be installed
+ behave
+ pystache
+ ifcopenshell
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 (TODO)
* Zoom Smart View (Someone needs to check if this works)
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:
### Start bimtester Gui from a shell
```
python3 ./bimtester.py -g
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
```
### Create a binary out of the Python package
+ The commands needs to be updated
+ 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`
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.
### Translation files
+ the binary mo translation files are not part of the repo
+ they might be needed to be recreated
+ use the following commands on Linux
```
cd YourIfcopenshellRepository/src/ifcbimtester/bimtester/locale
cd IfcOpenShell/src/ifcbimtester/bimtester/locale
pybabel compile -d .
```
## CLI Usage
BIMTester has a command line application. Check it out:
```
$ python cli.py -h
usage: cli.py [-h] [-a ACTION] [--advanced-arguments ADVANCED_ARGUMENTS] [-c]
-f FEATURE -i IFC [-p PATH] [-r REPORT] [--lang LANG]
Runs unit tests for BIM data
optional arguments:
-h, --help show this help message and exit
-a ACTION, --action ACTION
Action to perform, from run/purge
--advanced-arguments ADVANCED_ARGUMENTS
Specify arguments to Behave
-c, --console Show results in the console
-f FEATURE, --feature FEATURE
Specify a feature file to test
-i IFC, --ifc IFC Specify a ifc file
-p PATH, --path PATH Define a path for use in tests
-r REPORT, --report REPORT
Specify an output file for a HTML report
--lang LANG Specify a language
```
You can turn it into a regular command by symlinking it to your bin folder.
```
$ ln -s /path/to/IfcOpenShell/src/ifcbimtester/cli.py /usr/local/bin/bimtester
$ bimtester -h
```
To run a test, we need an IFC to check and a feature file filled with requirements. The feature file is plaintext. Feel
free to copy the minimal example above.
```
# To see output in the console
$ bimtester -i test.ifc -f test.feature -c
# Or, if you want to generate a HTML report
$ bimtester -i test.ifc -f test.feature -r report.html
```
```
python ./gui.py
```
## Create a binary out of the Python package
TODO: Check if this works
Unix:
```
$ pyinstaller --onefile --clean --icon=icon.ico --add-data "features:features" bimtester.py
```
Windows:
```
$ pyinstaller --onefile --clean --icon=icon.ico --add-data "features;features" bimtester.py
```
@@ -1,3 +0,0 @@
# dummy file to be able to run the command to get a list of all steps
# the command has to be run in the parent directory of this one
# behave --steps-catalog
@@ -1,73 +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):
# get from userdata
userdata = context.config.userdata
context.localedir = userdata.get("localedir")
context.ifcfile = userdata["ifcfile"]
context.ifcbasename = os.path.basename(
os.path.splitext(context.ifcfile)[0]
)
# do not break after a failed scenario
# https://community.osarch.org/discussion/comment/3328/#Comment_3328
continue_after_failed = userdata.getbool(
"runner.continue_after_failed_step", True
)
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
# keep out path
context.outpath = os.path.join(this_path, "..")
# 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]
# )
# since bimtesterfc directory in tmp is removed on every run
# neither log file nor sm file does need to be explicit removed first
# context.outpath = os.path.join(this_path, "..")
# set up log file
context.thelogfile = os.path.join(
context.outpath,
context.ifcbasename + ".log"
)
create_logfile(
context.thelogfile,
context.ifcbasename,
)
# 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.ifcbasename + ".bcsv"
)
create_zoom_smartview(
context.smview_file,
context.ifcbasename,
)
# # 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):
if step.status == "failed":
# append log file
append_logfile(context, step)
# extend smart view
if hasattr(context, "falseguids"):
# print(context.falseguids)
append_zoom_smartview(
context.smview_file,
step.name,
context.falseguids
)
pass
# TODO: refactor smart view support into a decoupled module
#if step.status == "failed":
# # append log file
# append_logfile(context, step)
# # extend smart view
# if hasattr(context, "falseguids"):
# append_zoom_smartview(context.smview_file, step.name, context.falseguids)
@@ -0,0 +1,191 @@
from behave import step, given, when, then, use_step_matcher
from bimtester.ifc import IfcStore
from bimtester.table import TableModel
from bimtester import util
use_step_matcher("parse")
@step('There must be exactly {number} "{ifc_class}" element')
@step('There must be exactly {number} "{ifc_class}" elements')
def step_impl(context, number, ifc_class):
num = len(IfcStore.file.by_type(ifc_class))
assert num == int(
number
), "Could not find {} elements of {}. \
Found {} element(s).".format(
number, ifc_class, num
)
@given('a set of (key,value) called ("{key_name}","{value_name}")')
def step_impl(context, key_name, value_name):
model = getattr(context, "model", None)
if not model:
context.model = TableModel(key_name, value_name)
for row in context.table:
context.model.add_row(row[key_name], row[value_name])
@given('a set of (key,value) called ("{key_name}","{value_name}") taken from the file "{path_file}"')
def step_impl(context, key_name, value_name, path_file):
import csv
import os
model = getattr(context, "model", None)
if not model:
context.model = TableModel(key_name, value_name)
if context.config.userdata.get("path"):
path_file = os.path.join(context.config.userdata.get("path"), path_file)
if not os.path.exists(path_file):
assert False, "File {} not found".format(path_file)
with open(path_file, "r", encoding="utf-8-sig") as csvfile:
reader = csv.DictReader(csvfile)
for row in reader:
context.model.add_row(row[key_name], row[value_name])
@then('there is an element of type key with an attribute "{attribute_name}" equals to value')
def step_impl(context, attribute_name):
if not context.model.key_name and not context.model.value_name:
assert False, "Missing (key,value)"
errors = []
rows = context.model.rows
for key, values in rows.items():
for value in values:
found = check_equals(attribute_name, value, IfcStore.file.by_type(key))
if not found:
errors.append(f"The row ({key}, {value}) was not found.")
assert not errors, "Errors occured:\n{}".format("\n".join(errors))
@then('there is an element of type key with an attribute "{attribute_name}" starting with value')
def step_impl(context, attribute_name):
# import re
if not context.model.key_name and not context.model.value_name:
assert False, "Missing (key,value)"
errors = []
rows = context.model.rows
for key, values in rows.items():
for value in values:
found = check_starts_with(attribute_name, value, IfcStore.file.by_type(key))
if not found:
errors.append(f"The row ({key}, {value}) was not found.")
assert not errors, "Errors occured:\n{}".format("\n".join(errors))
@then('there must be exactly a number of "{ifc_class}" equals to the number of distinct value')
def step_impl(context, ifc_class):
try:
context.execute_steps(
"""
then There must be exactly {number} "{ifc_class}" elements
""".format(
ifc_class=ifc_class, number=context.model.get_count_distinct_values()
)
)
except AssertionError as error:
str_error = str(error)
assert False, str_error[: str_error.find("Traceback")]
assert True
@then('there is a relationship "{ifc_class}" between the two elements of each row')
def step_impl(context, ifc_class):
if not context.model.key_name and not context.model.value_name:
assert False, "Missing (key,value)"
errors = []
elements = IfcStore.file.by_type(ifc_class)
# first, loop on the dataset to check if there are all in the relationships
rows = context.model.rows
for key, values in rows.items():
found = False
# value is actually a list of values
for value in values:
for element in elements:
if (
any(x.Name == key for x in getattr(element, context.model.key_name))
and getattr(element, context.model.value_name).Name == value
):
found = True
if not found:
errors.append(f"The row ({key}, {value}) does not have the relationship.")
# second, loop on the elements to check if there are not too many relationships
for element in elements:
if hasattr(element, context.model.key_name) and hasattr(element, context.model.value_name):
# list here
keys = getattr(element, context.model.key_name)
value = getattr(element, context.model.value_name).Name
for key in keys:
if not key.Name in rows or not value in rows[key.Name]:
errors.append(f"The element ({element}) has a relation that cannot be found in the dataset: {key}")
assert not errors, "Errors occured:\n{}".format("\n".join(errors))
use_step_matcher("re")
@step('all IfcGroup must be linked to a type "(?P<linked_ifc_classes>.*)"')
def step_impl(context, linked_ifc_classes):
groups = IfcStore.file.by_type("IfcGroup")
errors = []
for group in groups:
if not hasattr(group, "IsGroupedBy"):
errors.append(f'The element "{group.Name}" has no "IsGroupedBy" attribute.')
else:
for grouped_by in getattr(group, "IsGroupedBy"):
if not hasattr(grouped_by, "RelatedObjects"):
errors.append(f'The element "{grouped_by.Name}" has no "RelatedObjects" attribute.')
else:
for related_object in getattr(grouped_by, "RelatedObjects"):
found = False
for linked_ifc_class in linked_ifc_classes.split(","):
if related_object.is_a(linked_ifc_class):
found = True
if not found:
errors.append(
f'The element "{related_object.Name}" does not have the right associated type.'
)
assert not errors, "Errors occured:\n{}".format("\n".join(errors))
@then('there is an element of type "(?P<ifc_types>.*)" with an attribute "(?P<attribute_name>.*)" for each key')
def step_impl(context, ifc_types, attribute_name):
check_if_element_exists_by_types_with_attribute_name(ifc_types, attribute_name, context.model.rows.keys())
@then('there is an element of type "(?P<ifc_types>.*)" with an attribute "(?P<attribute_name>.*)" for each value')
def step_impl(context, ifc_types, attribute_name):
values = set(item for sublist in context.model.rows.values() for item in sublist)
check_if_element_exists_by_types_with_attribute_name(ifc_types, attribute_name, values)
def check_if_element_exists_by_types_with_attribute_name(ifc_types, attribute_name, attribute_values):
errors = []
# retrieve all elements of that type
elements = util.by_types(IfcStore.file, ifc_types)
# loop
for attribute_value in attribute_values:
found = False
for element in elements:
if hasattr(element, attribute_name) and getattr(element, attribute_name) == attribute_value:
found = True
if not found:
errors.append(f'An element with attribute "{attribute_name}" equals to "{attribute_value}" was not found.')
assert not errors, "Errors occured:\n{}".format("\n".join(errors))
def check_starts_with(attribute_name, value, elements):
"""Make sure at least an element of the list has a attribute starting with the value"""
if any(hasattr(x, attribute_name) and getattr(x, attribute_name).startswith(value) for x in elements):
return True
return False
def check_equals(attribute_name, value, elements):
"""Make sure at least an element of the list has a attribute equals to the value"""
if any(hasattr(x, attribute_name) and getattr(x, attribute_name) == value for x in elements):
return True
return False
@@ -0,0 +1,23 @@
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")
from bimtester.features.steps.aggregation import en
@@ -1,52 +0,0 @@
from behave import step
import attributes_eleclasses_methods as aem
from utils import switch_locale
the_lang = "de"
@step("Es sind keine {ifc_class} Bauteile vorhanden")
def step_impl(context, ifc_class):
switch_locale(context.localedir, the_lang)
aem.no_eleclass(
context,
ifc_class
)
@step("Aus folgendem Grund gibt es keine {ifc_class} Bauteile: {reason}")
def step_impl(context, ifc_class, reason):
switch_locale(context.localedir, the_lang)
aem.no_eleclass(
context,
ifc_class
)
@step("Alle {ifc_class} Bauteilklassenattribute haben einen Wert")
def step_impl(context, ifc_class):
switch_locale(context.localedir, the_lang)
aem.eleclass_have_class_attributes_with_a_value(
context,
ifc_class
)
@step("Bei allen {ifc_class} Bauteile ist der Name angegeben")
def step_impl(context, ifc_class):
switch_locale(context.localedir, the_lang)
aem.eleclass_has_name_with_a_value(
context,
ifc_class
)
@step("Bei allen {ifc_class} Bauteile ist die Beschreibung angegeben")
def step_impl(context, ifc_class):
switch_locale(context.localedir, the_lang)
aem.eleclass_has_description_with_a_value(
context,
ifc_class
)
@@ -1,59 +0,0 @@
from behave import step
import attributes_eleclasses_methods as aem
from utils import switch_locale
the_lang = "fr"
"""
# TODO the next line needs translation
@step("There are no {ifc_class} elements")
def step_impl(context, ifc_class):
switch_locale(context.localedir, the_lang)
aem.no_eleclass(
context,
ifc_class
)
# TODO the next line needs translation
@step("There are no {ifc_class} elements because {reason}")
def step_impl(context, ifc_class, reason):
switch_locale(context.localedir, the_lang)
aem.no_eleclass(
context,
ifc_class
)
# TODO the next line needs translation
@step("All {ifc_class} elements class attributes have a value")
def step_impl(context, ifc_class):
switch_locale(context.localedir, the_lang)
aem.eleclass_have_class_attributes_with_a_value(
context,
ifc_class
)
# TODO the next line needs translation
@step("All {ifc_class} elements have a name given")
def step_impl(context, ifc_class):
switch_locale(context.localedir, the_lang)
aem.eleclass_has_name_with_a_value(
context,
ifc_class
)
# TODO the next line needs translation
@step("All {ifc_class} elements have a description given")
def step_impl(context, ifc_class):
switch_locale(context.localedir, the_lang)
aem.eleclass_has_description_with_a_value(
context,
ifc_class
)
"""
@@ -1,73 +0,0 @@
import json
from behave import step
from utils import assert_attribute
from utils import assert_type
from utils import IfcFile
def get_classification(name):
classifications = [c for c in IfcFile.get().by_type("IfcClassification") if c.Name == name]
if len(classifications) != 1:
assert False, f'The classification "{name}" was not found'
return classifications[0]
@step("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):
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)
assert_attribute(element, "Edition", edition)
assert_attribute(element, "EditionDate", edition_date)
@step('The classification {name} has the description "{description}"')
def step_impl(context, name, description):
assert_attribute(get_classification(name), "Description", description)
@step("The classification {name} is referenced by the website {location}")
def step_impl(context, name, location):
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, f"Tokens {tokens} are not specified as a JSON list"
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 = IfcFile.by_guid(guid)
if not hasattr(element, "HasAssociations") or not element.HasAssociations:
assert False, f"The element {element} has no associations."
references = [a.RelatingClassification for a in element.HasAssociations if a.is_a("IfcRelAssociatesClassification")]
if not references:
assert False, f"The element {element} has no associated classification references."
is_success = False
for reference in references:
try:
assert_attribute(reference, "Identification", identification)
assert_attribute(reference, "Name", reference_name)
is_success = True
except:
pass
if not is_success:
assert (
False
), "No classification references met the requirement for an identification {} and name {} for the element {}. The references we found were: {}".format(
identification, reference_name, element, references
)
@@ -0,0 +1,70 @@
import json
from behave import step
from bimtester import util
from bimtester.ifc import IfcStore
from bimtester.lang import _
def get_classification(name):
classifications = [c for c in IfcStore.file.by_type("IfcClassification") if c.Name == name]
if len(classifications) != 1:
assert False, f'The classification "{name}" was not found'
return classifications[0]
@step('The classification "{name}" must be used')
def step_impl(context, name):
get_classification(name)
@step('The classification "{name}" is published by "{source}"')
def step_impl(context, name, source):
util.assert_attribute(get_classification(name), "Source", source)
@step('The classification "{name}" is the edition "{edition}" on "{edition_date}"')
def step_impl(context, name, edition, edition_date):
element = get_classification(name)
util.assert_attribute(element, "Edition", edition)
util.assert_attribute(element, "EditionDate", edition_date)
@step('The classification "{name}" has the description "{description}"')
def step_impl(context, name, description):
util.assert_attribute(get_classification(name), "Description", description)
@step('The classification "{name}" is referenced by the website "{location}"')
def step_impl(context, name, location):
util.assert_attribute(get_classification(name), "Location", location)
@step('The classification "{name}" has a hierarchy denoted by the tokens "{tokens}"')
def step_impl(context, name, tokens):
try:
tokens = json.loads(tokens)
except:
assert False, _("Tokens {} are not specified as a JSON list").format(tokens)
util.assert_attribute(get_classification(name), "ReferenceTokens", tokens)
@step('The element "{guid}" is classified as a "{identification}" with name "{reference_name}"')
def step_impl(context, guid, identification, reference_name):
element = util.assert_guid(IfcStore.file, guid)
if not hasattr(element, "HasAssociations") or not element.HasAssociations:
assert False, _("The element {} has no associations.").format(element)
references = [a.RelatingClassification for a in element.HasAssociations if a.is_a("IfcRelAssociatesClassification")]
if not references:
assert False, _("The element {element} has no associated classification references.").format(element)
is_success = False
for reference in references:
try:
util.assert_attribute(reference, "Identification", identification)
util.assert_attribute(reference, "Name", reference_name)
is_success = True
except:
pass
if not is_success:
assert False, _(
"No classification references met the requirement for an identification {} and name {} for the element {}. The references we found were: {}"
).format(identification, reference_name, element, references)
@@ -0,0 +1,40 @@
from behave import step
from bimtester import util
from bimtester.ifc import IfcStore
from bimtester.lang import _
@step('The element "{guid}" is an "{ifc_class}" only')
def step_impl(context, guid, ifc_class):
element = util.assert_guid(IfcStore.file, guid)
util.assert_type(element, ifc_class, is_exact=True)
@step('The element "{guid}" is an "{ifc_class}"')
def step_impl(context, guid, ifc_class):
element = util.assert_guid(IfcStore.file, guid)
util.assert_type(element, ifc_class)
@step('The element "{guid}" is further defined as a "{predefined_type}"')
def step_impl(context, guid, predefined_type):
element = util.assert_guid(IfcStore.file, guid)
if (
hasattr(element, "PredefinedType")
and element.PredefinedType == "USERDEFINED"
and hasattr(element, "ObjectType")
):
util.assert_attribute(element, "ObjectType", predefined_type)
elif hasattr(element, "PredefinedType"):
util.assert_attribute(element, "PredefinedType", predefined_type)
else:
assert False, _("The element {} does not have a PredefinedType or ObjectType attribute").format(element)
@step('The element "{guid}" should not exist because "{reason}"')
def step_impl(context, guid, reason):
try:
element = IfcStore.file.by_id(guid)
except:
return
assert False, _("This element {} should be reevaluated.").format(element)
@@ -1,41 +0,0 @@
from behave import step
from utils import assert_attribute
from utils import assert_type
from utils import IfcFile
@step("The element {guid} is an {ifc_class} only")
def step_impl(context, guid, ifc_class):
element = IfcFile.by_guid(guid)
assert_type(element, ifc_class, is_exact=True)
@step("The element {guid} is an {ifc_class}")
def step_impl(context, guid, ifc_class):
element = IfcFile.by_guid(guid)
assert_type(element, ifc_class)
@step("The element {guid} is further defined as a {predefined_type}")
def step_impl(context, guid, predefined_type):
element = IfcFile.by_guid(guid)
if (
hasattr(element, "PredefinedType")
and element.PredefinedType == "USERDEFINED"
and hasattr(element, "ObjectType")
):
assert_attribute(element, "ObjectType", predefined_type)
elif hasattr(element, "PredefinedType"):
assert_attribute(element, "PredefinedType", predefined_type)
else:
assert False, "The element {} does not have a PredefinedType or ObjectType attribute".format(element)
@step("The element {guid} should not exist because {reason}")
def step_impl(context, guid, reason):
try:
element = IfcFile.get().by_id(guid)
except:
return
assert False, "This element {} should be reevaluated.".format(element)
@@ -1,8 +1,7 @@
from behave import step
from utils import assert_attribute
from utils import assert_type
from utils import IfcFile
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):
@@ -14,71 +13,71 @@ def get_ifc_class_from_spatial_type(spatial_type):
def check_geocode_attribute(guid, spatial_type, name, value):
element = IfcFile.by_guid(guid)
assert_type(element, get_ifc_class_from_spatial_type(spatial_type))
assert_attribute(element, name, value)
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 = IfcFile.by_guid(guid)
element = util.assert_guid(IfcStore.file, guid)
ifc_class = get_ifc_class_from_spatial_type(spatial_type)
assert_type(element, ifc_class)
util.assert_type(element, ifc_class)
if ifc_class == "IfcSite":
address_name = "SiteAddress"
elif ifc_class == "IfcBuilding":
address_name = "BuildingAddress"
assert_attribute(element, address_name)
assert_attribute(getattr(element, address_name), name, value)
util.assert_attribute(element, address_name)
util.assert_attribute(getattr(element, address_name), name, value)
use_step_matcher("re")
@step("The (site|building|facility) (?P<guid>.*) has a name of (?P<name>.*)")
@step('The (site|building|facility) "(?P<guid>.*)" has a name of "(?P<name>.*)"')
def step_impl(context, spatial_type, guid, name):
check_geocode_attribute(guid, spatial_type, "Name", name)
@step('The (site|building|facility) (?P<guid>.*) has a description of "(?P<description>.*)"')
@step('The (site|building|facility) "(?P<guid>.*)" has a description of "(?P<description>.*)"')
def step_impl(context, spatial_type, guid, description):
check_geocode_attribute(guid, spatial_type, "Description", description)
@step("The site (?P<guid>.*) has a land title number of (?P<land_title_number>.*)")
@step('The site "(?P<guid>.*)" has a land title number of "(?P<land_title_number>.*)"')
def step_impl(context, guid, land_title_number):
check_geocode_attribute(guid, "site", "LandTitleNumber", land_title_number)
@step('The (site|building) (?P<guid>.*) has the address "(?P<address_lines>.*)"')
@step('The (site|building) "(?P<guid>.*)" has the address "(?P<address_lines>.*)"')
def step_impl(context, spatial_type, guid, address_lines):
check_geocode_address(guid, spatial_type, "AddressLines", address_lines.split("\\n"))
@step("The (site|building) (?P<guid>.*) has a postal box of (?P<postal_box>.*)")
@step('The (site|building) "(?P<guid>.*)" has a postal box of "(?P<postal_box>.*)"')
def step_impl(context, spatial_type, guid, postal_box):
check_geocode_address(guid, spatial_type, "PostalBox", postal_box)
@step("The (site|building) (?P<guid>.*) is in the town (?P<town>.*)")
@step('The (site|building) "(?P<guid>.*)" is in the town "(?P<town>.*)"')
def step_impl(context, spatial_type, guid, town):
check_geocode_address(guid, spatial_type, "Town", town)
@step("The (site|building) (?P<guid>.*) is in the region (?P<region>.*)")
@step('The (site|building) "(?P<guid>.*)" is in the region "(?P<region>.*)"')
def step_impl(context, spatial_type, guid, region):
check_geocode_address(guid, spatial_type, "Region", region)
@step("The (site|building) (?P<guid>.*) has a post code of (?P<post_code>.*)")
@step('The (site|building) "(?P<guid>.*)" has a post code of "(?P<post_code>.*)"')
def step_impl(context, spatial_type, guid, post_code):
check_geocode_address(guid, spatial_type, "PostalCode", post_code)
@step("The (site|building) (?P<guid>.*) is in the country (?P<country>.*)")
@step('The (site|building) "(?P<guid>.*)" is in the country "(?P<country>.*)"')
def step_impl(context, spatial_type, guid, country):
check_geocode_address(guid, spatial_type, "Country", country)
@step('The (site|building) (?P<guid>.*) has an address description of "(?P<description>.*)"')
@step('The (site|building) "(?P<guid>.*)" has an address description of "(?P<description>.*)"')
def step_impl(context, spatial_type, guid, description):
check_geocode_address(guid, spatial_type, "Description", description)
@@ -1,219 +0,0 @@
import math
import ifcopenshell.util
import ifcopenshell.util.element
import ifcopenshell.util.geolocation
from behave import step
from utils import assert_attribute
from utils import assert_number
from utils import assert_pset
from utils import IfcFile
@step(u"There must be at least one {ifc_class} element")
def step_impl(context, ifc_class):
assert len(IfcFile.get().by_type(ifc_class)) >= 1, "An element of {} could not be found".format(ifc_class)
def check_ifc4_geolocation(entity_name, prop_name=None, value=None, should_assert=True):
if entity_name not in IfcFile.bookmarks:
has_entity = False
project = IfcFile.get().by_type("IfcProject")[0]
for context in project.RepresentationContexts:
if entity_name == "IfcMapConversion":
if (
context.is_a("IfcGeometricRepresentationContext")
and context.ContextType == "Model"
and context.HasCoordinateOperation
):
IfcFile.bookmarks[entity_name] = context.HasCoordinateOperation[0]
has_entity = True
elif entity_name == "IfcProjectedCRS":
if (
context.is_a("IfcGeometricRepresentationContext")
and context.ContextType == "Model"
and context.HasCoordinateOperation
and context.HasCoordinateOperation[0].TargetCRS
):
IfcFile.bookmarks[entity_name] = context.HasCoordinateOperation[0].TargetCRS
has_entity = True
if not has_entity:
assert False, "No model geometric representation contexts refer to an {}".format(entity_name)
if not prop_name:
return
actual_value = getattr(IfcFile.bookmarks[entity_name], prop_name)
if should_assert:
assert actual_value == value, 'We expected a value of "{}" but instead got "{}"'.format(value, actual_value)
else:
return actual_value
@step(u"The project must have coordinate reference system data")
def step_impl(context):
if IfcFile.get().schema == "IFC2X3":
for site in IfcFile.get().by_type("IfcSite"):
assert_pset(site, "EPset_ProjectedCRS")
return
check_ifc4_geolocation("IfcProjectedCRS")
@step(u"The name of the CRS must be {coordinate_reference_name}")
def step_impl(context, coordinate_reference_name):
if IfcFile.get().schema == "IFC2X3":
for site in IfcFile.get().by_type("IfcSite"):
assert_pset(site, "EPset_ProjectedCRS", "Name", coordinate_reference_name)
return
check_ifc4_geolocation("IfcProjectedCRS", "Name", coordinate_reference_name)
@step(u"The description of the CRS must be {value}")
def step_impl(context, value):
if IfcFile.get().schema == "IFC2X3":
for site in IfcFile.get().by_type("IfcSite"):
assert_pset(site, "EPset_ProjectedCRS", "Description", value)
return
check_ifc4_geolocation("IfcProjectedCRS", "Description", value)
@step(u"The geodetic datum must be {coordinate_reference_name}")
def step_impl(context, coordinate_reference_name):
if IfcFile.get().schema == "IFC2X3":
for site in IfcFile.get().by_type("IfcSite"):
assert_pset(site, "EPset_ProjectedCRS", "GeodeticDatum", coordinate_reference_name)
return
check_ifc4_geolocation("IfcProjectedCRS", "GeodeticDatum", coordinate_reference_name)
@step(u"The vertical datum must be {coordinate_reference_name}")
def step_impl(context, coordinate_reference_name):
if IfcFile.get().schema == "IFC2X3":
for site in IfcFile.get().by_type("IfcSite"):
assert_pset(site, "EPset_ProjectedCRS", "VerticalDatum", coordinate_reference_name)
return
check_ifc4_geolocation("IfcProjectedCRS", "VerticalDatum", coordinate_reference_name)
@step(u"The map projection must be {coordinate_reference_name}")
def step_impl(context, coordinate_reference_name):
if IfcFile.get().schema == "IFC2X3":
for site in IfcFile.get().by_type("IfcSite"):
assert_pset(site, "EPset_ProjectedCRS", "MapProjection", coordinate_reference_name)
return
check_ifc4_geolocation("IfcProjectedCRS", "MapProjection", coordinate_reference_name)
@step(u"The map zone must be {coordinate_reference_name}")
def step_impl(context, coordinate_reference_name):
if IfcFile.get().schema == "IFC2X3":
for site in IfcFile.get().by_type("IfcSite"):
assert_pset(site, "EPset_ProjectedCRS", "MapZone", coordinate_reference_name)
return
check_ifc4_geolocation("IfcProjectedCRS", "MapZone", coordinate_reference_name)
@step(u"The map unit must be {unit}")
def step_impl(context, unit):
if IfcFile.get().schema == "IFC2X3":
for site in IfcFile.get().by_type("IfcSite"):
assert_pset(site, "EPset_ProjectedCRS", "MapUnit", unit)
return
actual_value = check_ifc4_geolocation("IfcProjectedCRS", "MapUnit", should_assert=False)
if not actual_value:
assert False, "A unit was not provided in the projected CRS"
if actual_value.is_a("IfcSIUnit"):
prefix = actual_value.Prefix if actual_value.Prefix else ""
actual_value = prefix + actual_value.Name
elif actual_value.is_a("IfcConversionBasedUnit"):
actual_value = actual_value.Name
assert actual_value == unit, 'We expected a value of "{}" but instead got "{}"'.format(unit, actual_value)
@step(u"The project must have coordinate transformations to convert from local to global coordinates")
def step_impl(context):
if IfcFile.get().schema == "IFC2X3":
for site in IfcFile.get().by_type("IfcSite"):
assert_pset(site, "EPset_MapConversion")
check_ifc4_geolocation("IfcMapConversion")
@step(u"The eastings of the model must be offset by {number} to derive its global coordinates")
def step_impl(context, number):
number = assert_number(number)
if IfcFile.get().schema == "IFC2X3":
for site in IfcFile.get().by_type("IfcSite"):
assert_pset(site, "EPset_MapConversion", "Eastings", number)
return
check_ifc4_geolocation("IfcMapConversion", "Eastings", number)
@step(u"The northings of the model must be offset by {number} to derive its global coordinates")
def step_impl(context, number):
number = assert_number(number)
if IfcFile.get().schema == "IFC2X3":
for site in IfcFile.get().by_type("IfcSite"):
assert_pset(site, "EPset_MapConversion", "Northings", number)
return
check_ifc4_geolocation("IfcMapConversion", "Northings", number)
@step(u"The height of the model must be offset by {number} to derive its global coordinates")
def step_impl(context, number):
number = assert_number(number)
if IfcFile.get().schema == "IFC2X3":
for site in IfcFile.get().by_type("IfcSite"):
assert_pset(site, "EPset_MapConversion", "OrthogonalHeight", number)
return
check_ifc4_geolocation("IfcMapConversion", "OrthogonalHeight", number)
@step(u"The model must be rotated clockwise by {number} to derive its global coordinates")
def step_impl(context, number):
number = assert_number(number)
if IfcFile.get().schema == "IFC2X3":
return check_ifc2x3_geolocation("EPset_MapConversion", "Height", number)
abscissa = check_ifc4_geolocation("IfcMapConversion", "XAxisAbscissa", should_assert=False)
ordinate = check_ifc4_geolocation("IfcMapConversion", "XAxisOrdinate", should_assert=False)
actual_value = round(ifcopenshell.util.geolocation.xy2angle(abscissa, ordinate), 3)
value = round(number, 3)
assert actual_value == value, 'We expected a value of "{}" but instead got "{}"'.format(value, actual_value)
@step(u"The model must be scaled along the horizontal axis by {number} to derive its global coordinates")
def step_impl(context, number):
number = assert_number(number)
if IfcFile.get().schema == "IFC2X3":
for site in IfcFile.get().by_type("IfcSite"):
assert_pset(site, "EPset_MapConversion", "Scale", number)
return
check_ifc4_geolocation("IfcMapConversion", "Scale", number)
@step(u"The site {guid} has a longitude of {number}")
def step_impl(context, guid, number):
number = assert_number(number)
site = IfcFile.by_guid(guid)
if not site.is_a("IfcSite"):
assert False, "The element {} is not an IfcSite".format(site)
ref = assert_attribute(site, "RefLongitude")
number = ifcopenshell.util.geolocation.dd2dms(number, use_ms=(len(ref) == 4))
assert_attribute(site, "RefLongitude", number)
@step(u"The site {guid} has a latitude of {number}")
def step_impl(context, guid, number):
number = assert_number(number)
site = IfcFile.by_guid(guid)
if not site.is_a("IfcSite"):
assert False, "The element {} is not an IfcSite".format(site)
ref = assert_attribute(site, "RefLatitude")
number = ifcopenshell.util.geolocation.dd2dms(number, use_ms=(len(ref) == 4))
assert_attribute(site, "RefLatitude", number)
@step(u"The site {guid} has an elevation of {number}")
def step_impl(context, guid, number):
number = assert_number(number)
site = IfcFile.by_guid(guid)
if not site.is_a("IfcSite"):
assert False, "The element {} is not an IfcSite".format(site)
assert_attribute(site, "RefElevation", number)
@@ -0,0 +1,214 @@
import math
import ifcopenshell
import ifcopenshell.util.element
import ifcopenshell.util.geolocation
from behave import step
from bimtester import util
from bimtester.ifc import IfcStore
from bimtester.lang import _
@step(u'There must be at least one "{ifc_class}" element')
def step_impl(context, ifc_class):
assert len(IfcStore.file.by_type(ifc_class)) >= 1, _("An element of {} could not be found").format(ifc_class)
def check_ifc4_geolocation(entity_name, prop_name=None, value=None, should_assert=True):
if entity_name not in IfcStore.bookmarks:
has_entity = False
project = IfcStore.file.by_type("IfcProject")[0]
for context in project.RepresentationContexts:
if entity_name == "IfcMapConversion":
if (
context.is_a("IfcGeometricRepresentationContext")
and context.ContextType == "Model"
and context.HasCoordinateOperation
):
IfcStore.bookmarks[entity_name] = context.HasCoordinateOperation[0]
has_entity = True
elif entity_name == "IfcProjectedCRS":
if (
context.is_a("IfcGeometricRepresentationContext")
and context.ContextType == "Model"
and context.HasCoordinateOperation
and context.HasCoordinateOperation[0].TargetCRS
):
IfcStore.bookmarks[entity_name] = context.HasCoordinateOperation[0].TargetCRS
has_entity = True
if not has_entity:
assert False, _("No model geometric representation contexts refer to an {}").format(entity_name)
if not prop_name:
return
actual_value = getattr(IfcStore.bookmarks[entity_name], prop_name)
if should_assert:
assert actual_value == value, _('We expected a value of "{}" but instead got "{}"').format(value, actual_value)
else:
return actual_value
@step(u"The project must have coordinate reference system data")
def step_impl(context):
if IfcStore.file.schema == "IFC2X3":
for site in IfcStore.file.by_type("IfcSite"):
util.assert_pset(site, "EPset_ProjectedCRS")
return
check_ifc4_geolocation("IfcProjectedCRS")
@step(u'The name of the CRS must be "{coordinate_reference_name}"')
def step_impl(context, coordinate_reference_name):
if IfcStore.file.schema == "IFC2X3":
for site in IfcStore.file.by_type("IfcSite"):
util.assert_pset(site, "EPset_ProjectedCRS", "Name", coordinate_reference_name)
return
check_ifc4_geolocation("IfcProjectedCRS", "Name", coordinate_reference_name)
@step(u'The description of the CRS must be "{value}"')
def step_impl(context, value):
if IfcStore.file.schema == "IFC2X3":
for site in IfcStore.file.by_type("IfcSite"):
util.assert_pset(site, "EPset_ProjectedCRS", "Description", value)
return
check_ifc4_geolocation("IfcProjectedCRS", "Description", value)
@step(u'The geodetic datum must be "{coordinate_reference_name}"')
def step_impl(context, coordinate_reference_name):
if IfcStore.file.schema == "IFC2X3":
for site in IfcStore.file.by_type("IfcSite"):
util.assert_pset(site, "EPset_ProjectedCRS", "GeodeticDatum", coordinate_reference_name)
return
check_ifc4_geolocation("IfcProjectedCRS", "GeodeticDatum", coordinate_reference_name)
@step(u'The vertical datum must be "{coordinate_reference_name}"')
def step_impl(context, coordinate_reference_name):
if IfcStore.file.schema == "IFC2X3":
for site in IfcStore.file.by_type("IfcSite"):
util.assert_pset(site, "EPset_ProjectedCRS", "VerticalDatum", coordinate_reference_name)
return
check_ifc4_geolocation("IfcProjectedCRS", "VerticalDatum", coordinate_reference_name)
@step(u'The map projection must be "{coordinate_reference_name}"')
def step_impl(context, coordinate_reference_name):
if IfcStore.file.schema == "IFC2X3":
for site in IfcStore.file.by_type("IfcSite"):
util.assert_pset(site, "EPset_ProjectedCRS", "MapProjection", coordinate_reference_name)
return
check_ifc4_geolocation("IfcProjectedCRS", "MapProjection", coordinate_reference_name)
@step(u'The map zone must be "{coordinate_reference_name}"')
def step_impl(context, coordinate_reference_name):
if IfcStore.file.schema == "IFC2X3":
for site in IfcStore.file.by_type("IfcSite"):
util.assert_pset(site, "EPset_ProjectedCRS", "MapZone", coordinate_reference_name)
return
check_ifc4_geolocation("IfcProjectedCRS", "MapZone", coordinate_reference_name)
@step(u'The map unit must be "{unit}"')
def step_impl(context, unit):
if IfcStore.file.schema == "IFC2X3":
for site in IfcStore.file.by_type("IfcSite"):
util.assert_pset(site, "EPset_ProjectedCRS", "MapUnit", unit)
return
actual_value = check_ifc4_geolocation("IfcProjectedCRS", "MapUnit", should_assert=False)
if not actual_value:
assert False, _("A unit was not provided in the projected CRS")
if actual_value.is_a("IfcSIUnit"):
prefix = actual_value.Prefix if actual_value.Prefix else ""
actual_value = prefix + actual_value.Name
elif actual_value.is_a("IfcConversionBasedUnit"):
actual_value = actual_value.Name
assert actual_value == unit, _('We expected a value of "{}" but instead got "{}"').format(unit, actual_value)
@step(u"The project must have coordinate transformations to convert from local to global coordinates")
def step_impl(context):
if IfcStore.file.schema == "IFC2X3":
for site in IfcStore.file.by_type("IfcSite"):
util.assert_pset(site, "EPset_MapConversion")
check_ifc4_geolocation("IfcMapConversion")
@step(u'The eastings of the model must be offset by "{number}" to derive its global coordinates')
def step_impl(context, number):
number = util.assert_number(number)
if IfcStore.file.schema == "IFC2X3":
for site in IfcStore.file.by_type("IfcSite"):
util.assert_pset(site, "EPset_MapConversion", "Eastings", number)
return
check_ifc4_geolocation("IfcMapConversion", "Eastings", number)
@step(u'The northings of the model must be offset by "{number}" to derive its global coordinates')
def step_impl(context, number):
number = util.assert_number(number)
if IfcStore.file.schema == "IFC2X3":
for site in IfcStore.file.by_type("IfcSite"):
util.assert_pset(site, "EPset_MapConversion", "Northings", number)
return
check_ifc4_geolocation("IfcMapConversion", "Northings", number)
@step(u'The height of the model must be offset by "{number}" to derive its global coordinates')
def step_impl(context, number):
number = util.assert_number(number)
if IfcStore.file.schema == "IFC2X3":
for site in IfcStore.file.by_type("IfcSite"):
util.assert_pset(site, "EPset_MapConversion", "OrthogonalHeight", number)
return
check_ifc4_geolocation("IfcMapConversion", "OrthogonalHeight", number)
@step(u'The model must be rotated clockwise by "{number}" to derive its global coordinates')
def step_impl(context, number):
number = util.assert_number(number)
if IfcStore.file.schema == "IFC2X3":
return check_ifc2x3_geolocation("EPset_MapConversion", "Height", number)
abscissa = check_ifc4_geolocation("IfcMapConversion", "XAxisAbscissa", should_assert=False)
ordinate = check_ifc4_geolocation("IfcMapConversion", "XAxisOrdinate", should_assert=False)
actual_value = round(ifcopenshell.util.geolocation.xy2angle(abscissa, ordinate), 3)
value = round(number, 3)
assert actual_value == value, _('We expected a value of "{}" but instead got "{}"').format(value, actual_value)
@step(u'The model must be scaled along the horizontal axis by "{number}" to derive its global coordinates')
def step_impl(context, number):
number = util.assert_number(number)
if IfcStore.file.schema == "IFC2X3":
for site in IfcStore.file.by_type("IfcSite"):
util.assert_pset(site, "EPset_MapConversion", "Scale", number)
return
check_ifc4_geolocation("IfcMapConversion", "Scale", number)
@step(u'The site "{guid}" has a longitude of "{number}"')
def step_impl(context, guid, number):
number = util.assert_number(number)
site = util.assert_guid(IfcStore.file, guid)
util.assert_type(site, "IfcSite")
ref = util.assert_attribute(site, "RefLongitude")
number = ifcopenshell.util.geolocation.dd2dms(number, use_ms=(len(ref) == 4))
util.assert_attribute(site, "RefLongitude", number)
@step(u'The site "{guid}" has a latitude of "{number}"')
def step_impl(context, guid, number):
number = util.assert_number(number)
site = util.assert_guid(IfcStore.file, guid)
util.assert_type(site, "IfcSite")
ref = util.assert_attribute(site, "RefLatitude")
number = ifcopenshell.util.geolocation.dd2dms(number, use_ms=(len(ref) == 4))
util.assert_attribute(site, "RefLatitude", number)
@step(u'The site "{guid}" has an elevation of "{number}"')
def step_impl(context, guid, number):
number = util.assert_number(number)
site = util.assert_guid(IfcStore.file, guid)
util.assert_type(site, "IfcSite")
util.assert_attribute(site, "RefElevation", number)
@@ -0,0 +1,29 @@
from behave import step
from bimtester import util
from bimtester.ifc import IfcStore
from bimtester.lang import _
@step('All elements must be under "{number}" polygons')
def step_impl(context, number):
number = int(number)
errors = []
for element in IfcStore.file.by_type("IfcElement"):
if not element.Representation:
continue
total_polygons = 0
tree = IfcStore.file.traverse(element.Representation)
for e in tree:
if e.is_a("IfcFace"):
total_polygons += 1
elif e.is_a("IfcPolygonalFaceSet"):
total_polygons += len(e.Faces)
elif e.is_a("IfcTriangulatedFaceSet"):
total_polygons += len(e.CoordIndex)
if total_polygons > number:
errors.append((total_polygons, element))
if errors:
message = "The following {} elements are over 500 polygons:\n".format(len(errors))
for error in errors:
message += "Polygons: {} - {}\n".format(error[0], error[1])
assert False, message
@@ -1,20 +0,0 @@
from behave import step
import geometric_detail_methods as gdm
from utils import switch_locale
the_lang = "fr"
"""
# TODO the next line needs translation
@step("all {ifc_class} elements have an {representation_class} representation")
def step_impl(context, ifc_class, representation_class):
switch_locale(context.localedir, the_lang)
gdm.eleclass_has_geometric_representation_of_specific_class(
context,
ifc_class,
representation_class
)
"""
@@ -1,101 +0,0 @@
import gettext # noqa
from behave import given
from behave import step
import ifcdata_methods as idm
from utils import IfcFile
from utils import switch_locale
the_lang = "en"
@step('The IFC schema "{schema}" must be provided')
def step_impl(context, schema):
try:
if context.config.userdata.get('path'):
schema = os.path.join(context.config.userdata.get('path'), schema)
IfcFile.load_schema(schema)
except:
assert False, f"The schema {schema} could not be loaded"
@step('The IFC file "{file}" must be provided')
def step_impl(context, file):
try:
IfcFile.load(file)
except:
assert False, f"The file {file} could not be loaded"
@given("The IFC file has been provided through an argument")
def step_impl(context):
switch_locale(context.localedir, the_lang)
idm.provide_ifcfile_by_argument(context)
@given('A file path has been provided through an argument')
def step_impl(context):
try:
assert context.config.userdata.get("path")
except:
assert False, f"The path {context.config.userdata.get('path')} could not be loaded"
@step("IFC data must use the {schema} schema")
def step_impl(context, schema):
switch_locale(context.localedir, the_lang)
idm.has_ifcdata_specific_schema(context, 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 IFC file must be exported by application full name {fullname}")
def step_impl(context, fullname):
real_fullname = IfcFile.get().by_type("IfcApplication")[0].ApplicationFullName
assert real_fullname == fullname , (
"The IFC file was not exported by application full name {} "
"instead it was exported by application full name {}"
.format(fullname, real_fullname)
)
@step("The IFC file must be exported by application identifier {identifier}")
def step_impl(context, identifier):
real_identifier = IfcFile.get().by_type("IfcApplication")[0].ApplicationIdentifier
assert real_identifier == identifier , (
"The IFC file was not exported by application identifier {} "
"instead it was exported by identifier {}"
.format(identifier, real_identifier)
)
@step("The IFC file must be exported by the application version {version}")
def step_impl(context, version):
real_version = IfcFile.get().by_type("IfcApplication")[0].Version
assert real_version == version , (
"The IFC file was not exported by application version {} "
"instead it was exported by version {}"
.format(version, real_version)
)
@step("IFC data header must have a file description of {header_file_description} such as the new Allplan IFC exporter creates it")
def step_impl(context, header_file_description):
is_header_file_description = IfcFile.get().wrapped_data.header.file_description.description
assert str(is_header_file_description) == header_file_description , (
"The file was not exported by the new ifc exporter in Allplan. File description header: {}"
.format(is_header_file_description)
)
@@ -1,19 +0,0 @@
from behave import step
import ifcdata_methods as idm
from utils import switch_locale
the_lang = "de"
@given("Die IFC-Datei wurde durch einen Startparameter zur Verfügung gestellt")
def step_impl(context):
switch_locale(context.localedir, the_lang)
idm.provide_ifcfile_by_argument(context)
@step("Die IFC-Daten müssen das {schema} Schema benutzen")
def step_impl(context, schema):
switch_locale(context.localedir, the_lang)
idm.has_ifcdata_specific_schema(context, schema)
@@ -1,22 +0,0 @@
from behave import step
import ifcdata_methods as idm
from utils import switch_locale
the_lang = "fr"
"""
# TODO the next line needs translation
@given("The IFC file has been provided through an argument")
def step_impl(context):
switch_locale(context.localedir, the_lang)
idm.provide_ifcfile_by_argument(context)
"""
@step("Les données IFC doivent utiliser le schéma {schema}")
def step_impl(context, schema):
switch_locale(context.localedir, the_lang)
idm.has_ifcdata_specific_schema(context, schema)
@@ -1,19 +0,0 @@
from behave import step
import ifcdata_methods as idm
from utils import switch_locale
the_lang = "it"
@given("Il file IFC è stato fornito attraverso un argumento")
def step_impl(context):
switch_locale(context.localedir, the_lang)
idm.provide_ifcfile_by_argument(context)
@step("I dati IFC devono seguire lo schema {schema}")
def step_impl(context, schema):
switch_locale(context.localedir, the_lang)
idm.has_ifcdata_specific_schema(context, schema)
@@ -1,19 +0,0 @@
from utils import IfcFile
def provide_ifcfile_by_argument(context):
try:
IfcFile.load(context.config.userdata.get("ifcfile"))
except:
assert False, (
_("The IFC {} file could not be loaded")
.format(context.config.userdata.get('ifcfile'))
)
def has_ifcdata_specific_schema(context, target_schema):
real_schema = IfcFile.get().schema
assert real_schema == target_schema, (
_("We expected a schema of {} but instead got {}")
.format(target_schema, real_schema)
)
@@ -1,22 +0,0 @@
from behave import step
import ifcdata_methods as idm
from utils import switch_locale
the_lang = "nl"
"""
# TODO the next line needs translation
@given("The IFC file has been provided through an argument")
def step_impl(context):
switch_locale(context.localedir, the_lang)
idm.provide_ifcfile_by_argument(context)
"""
@step("IFC-gegevens moeten het {schema} -schema gebruiken")
def step_impl(context, schema):
switch_locale(context.localedir, the_lang)
idm.has_ifcdata_specific_schema(context, schema)
@@ -1,10 +1,9 @@
import ifcopenshell.util.geolocation
import ifcopenshell.util.placement
from behave import step
from utils import assert_number
from utils import assert_type
from utils import IfcFile
from bimtester import util
from bimtester.ifc import IfcStore
from bimtester.lang import _
def get_decimal_points(value):
@@ -28,17 +27,17 @@ def get_containing_spatial_elements(element):
return results
@step("There is a datum element {guid} as an {ifc_class}")
@step('There is a datum element "{guid}" as an "{ifc_class}"')
def step_impl(context, guid, ifc_class):
element = IfcFile.by_guid(guid)
assert_type(element, ifc_class)
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"
'The element "{guid}" has a global easting, northing, and elevation of "{easting}", "{northing}", and "{elevation}" respectively'
)
def step_impl(context, guid, easting, northing, elevation):
if IfcFile.get().schema == "IFC2X3":
if IfcStore.file.schema == "IFC2X3":
if element.is_a("IfcSite"):
site = element
else:
@@ -46,18 +45,18 @@ def step_impl(context, guid, easting, northing, elevation):
if potential_sites:
site = potential_sites[0]
else:
assert False, "The datum element does not belong to a geolocated site"
assert False, _("The datum element does not belong to a geolocated site")
map_conversion = assert_pset(site, "EPset_MapConversion")
else:
map_conversion = IfcFile.get().by_type("IfcMapConversion")
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"
assert False, _("No map conversion was found in the file")
element = IfcFile.by_guid(guid)
element = utils.assert_guid(IfcStore.file, guid)
if not element.ObjectPlacement:
assert False, "The element does not have an object placement: {}".format(element)
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],
@@ -73,24 +72,24 @@ def step_impl(context, guid, easting, northing, elevation):
element_x = round(e, get_decimal_points(easting))
element_y = round(n, get_decimal_points(northing))
element_z = round(h, get_decimal_points(elevation))
expected_placement = (assert_number(easting), assert_number(northing), assert_number(elevation))
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(
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")
@step('The element "{guid}" has a local X, Y, and Z coordinate of "{x}", "{y}", and "{z}" respectively')
def step_impl(context, guid, x, y, z):
element = IfcFile.by_guid(guid)
element = utils.assert_guid(IfcStore.file, guid)
if not element.ObjectPlacement:
assert False, "The element does not have an object placement: {}".format(element)
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 = (assert_number(x), assert_number(y), assert_number(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(
assert False, _("The element {} is meant to have a location of {} but instead we found {}").format(
element, expected_placement, (element_x, element_y, element_z)
)
@@ -0,0 +1,16 @@
from behave import step
@step('Die IFC-Daten müssen das "{schema}" Schema benutzen')
def step_impl(context, schema):
context.execute_steps(f'* IFC data must use the "{schema}" schema')
@step('Die Globale Identifikationskennung (Globally Unique Identifier = GUID) des Projektes ist "{guid}"')
def step_impl(context, guid):
context.execute_steps(f'* The project must have an identifier of "{guid}"')
@step('Der Name, die Abkürzung oder die Kurzkennung des Projektes ist "{value}"')
def step_impl(context, value):
context.execute_steps(f'* The project name, code, or short identifier must be "{value}"')
@@ -1,37 +1,59 @@
from behave import step
from utils import assert_attribute
from utils import IfcFile
from bimtester import util
from bimtester.ifc import IfcStore
from bimtester.lang import _
@step("The project must have an identifier of {guid}")
@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 must be valid")
def step_impl(context):
errors = util.validate(IfcStore.file)
assert not errors, "Errors occured:\n{}".format("\n".join(errors))
@step('The IFC file "{file}" is exempt from being provided')
def step_impl(context, file):
pass
@step('No further requirements are specified because "{reason}"')
def step_impl(context, reason):
pass
@step('The project must have an identifier of "{guid}"')
def step_impl(context, guid):
assert_attribute(IfcFile.get().by_type("IfcProject")[0], "GlobalId", guid)
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):
assert_attribute(IfcFile.get().by_type("IfcProject")[0], "Name", 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):
assert_attribute(IfcFile.get().by_type("IfcProject")[0], "LongName", 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):
assert_attribute(IfcFile.get().by_type("IfcProject")[0], "Description", 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):
assert_attribute(IfcFile.get().by_type("IfcProject")[0], "ObjectType", 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):
assert_attribute(IfcFile.get().by_type("IfcProject")[0], "Phase", value)
util.assert_attribute(IfcStore.file.by_type("IfcProject")[0], "Phase", value)
@step("The project must contain 3D geometry representing the shape of objects")
@@ -55,7 +77,7 @@ def step_impl(context):
def get_subcontext(identifier, type, target_view):
project = IfcFile.get().by_type("IfcProject")[0]
project = IfcStore.file.by_type("IfcProject")[0]
for rep_context in project.RepresentationContexts:
for subcontext in rep_context.HasSubContexts:
if (
@@ -71,5 +93,5 @@ def get_subcontext(identifier, type, target_view):
@step('the project has a {attribute_name} attribute with a value of "{attribute_value}"')
def step_impl(context, attribute_name, attribute_value):
project = IfcFile.get().by_type("IfcProject")[0]
project = IfcStore.file.by_type("IfcProject")[0]
assert getattr(project, attribute_name) == attribute_value
@@ -0,0 +1,6 @@
from behave import step
@step('Les données IFC doivent utiliser le schéma "{schema}"')
def step_impl(context, schema):
context.execute_steps(f'* IFC data must use the "{schema}" schema')
@@ -0,0 +1,11 @@
from behave import step
@step('I dati IFC devono seguire lo schema "{schema}"')
def step_impl(context, schema):
context.execute_steps(f'* IFC data must use the "{schema}" schema')
@step('Il nome del progetto, codice o identificatore breve deve essere "{value}"')
def step_impl(context, value):
context.execute_steps(f'* "The project name, code, or short identifier must be "{value}"')
@@ -0,0 +1,11 @@
from behave import step
@step('IFC-gegevens moeten het "{schema}" -schema gebruiken')
def step_impl(context, schema):
context.execute_steps(f'* IFC data must use the "{schema}" schema')
@step('De projectnaam, code of korte ID moet "{value}"')
def step_impl(context, value):
context.execute_steps(f'* "The project name, code, or short identifier must be "{value}"')
@@ -1,20 +0,0 @@
from behave import step
from utils import assert_attribute
from utils import IfcFile
from utils import switch_locale
the_lang = "de"
@step("Die Globale Identifikationskennung (Globally Unique Identifier = GUID) des Projektes ist {guid}")
def step_impl(context, guid):
switch_locale(context.localedir, the_lang)
assert_attribute(IfcFile.get().by_type("IfcProject")[0], "GlobalId", guid)
@step('Der Name, die Abkürzung oder die Kurzkennung des Projektes ist "{value}"')
def step_impl(context, value):
switch_locale(context.localedir, the_lang)
assert_attribute(IfcFile.get().by_type("IfcProject")[0], "Name", value)
@@ -1,23 +0,0 @@
from behave import step
from utils import assert_attribute
from utils import IfcFile
from utils import switch_locale
the_lang = "fr"
"""
# TODO the next line needs translation
@step("The project must have an identifier of {guid}")
def step_impl(context, guid):
switch_locale(context.localedir, the_lang)
assert_attribute(IfcFile.get().by_type("IfcProject")[0], "GlobalId", guid)
@step('The project name, code, or short identifier must be "{value}"')
def step_impl(context, value):
switch_locale(context.localedir, the_lang)
assert_attribute(IfcFile.get().by_type("IfcProject")[0], "Name", value)
"""
@@ -1,23 +0,0 @@
from behave import step
from utils import assert_attribute
from utils import IfcFile
from utils import switch_locale
the_lang = "it"
"""
# TODO the next line needs translation
@step("The project must have an identifier of {guid}")
def step_impl(context, guid):
switch_locale(context.localedir, the_lang)
assert_attribute(IfcFile.get().by_type("IfcProject")[0], "GlobalId", guid)
"""
@step('Il nome del progetto, codice o identificatore breve deve essere "{value}"')
def step_impl(context, value):
switch_locale(context.localedir, the_lang)
assert_attribute(IfcFile.get().by_type("IfcProject")[0], "Name", value)
@@ -1,23 +0,0 @@
from behave import step
from utils import assert_attribute
from utils import IfcFile
from utils import switch_locale
the_lang = "nl"
"""
# TODO the next line needs translation
@step("The project must have an identifier of {guid}")
def step_impl(context, guid):
switch_locale(context.localedir, the_lang)
assert_attribute(IfcFile.get().by_type("IfcProject")[0], "GlobalId", guid)
"""
@step('De projectnaam, code of korte ID moet "{value}"')
def step_impl(context, value):
switch_locale(context.localedir, the_lang)
assert_attribute(IfcFile.get().by_type("IfcProject")[0], "Name", value)
@@ -1,167 +0,0 @@
import gettext
import ifcopenshell
import ifcopenshell.express
import ifcopenshell.util
import ifcopenshell.util.element
class IfcFile(object):
file = None
bookmarks = {}
@classmethod
def load(cls, path=None):
cls.file = ifcopenshell.open(path)
if not cls.file:
assert False
@classmethod
def load_schema(cls, path=None):
schema = ifcopenshell.express.parse(path)
ifcopenshell.register_schema(schema)
@classmethod
def get(cls):
if not cls.file:
assert False, "No file was loaded, so this requirement cannot be checked"
return cls.file
@classmethod
def by_guid(cls, guid):
try:
return cls.get().by_guid(guid)
except:
assert False, "An element with the ID {} could not be found.".format(guid)
@classmethod
def by_type(cls, ifc_type):
return cls.get().by_type(ifc_type.strip())
@classmethod
def by_types(cls, ifc_types):
elements = []
for ifc_type in ifc_types.split(","):
elements += cls.by_type(ifc_type.strip())
return elements
def assert_number(number):
try:
return float(number)
except ValueError:
assert False, "A number should be specified, not {}".format(number)
def assert_type(element, ifc_class, is_exact=False):
if is_exact:
assert element.is_a() == ifc_class, "The element {} is an {} instead of {}.".format(
element, element.is_a(), ifc_class
)
else:
assert element.is_a(ifc_class), "The element {} is an {} instead of {}.".format(
element, element.is_a(), ifc_class
)
def assert_attribute(element, name, value=None):
if not hasattr(element, name):
assert False, "The element {} does not have the attribute {}".format(element, name)
if not value:
if getattr(element, name) is None:
assert False, "The element {} does not have a value for the attribute {}".format(element, name)
return getattr(element, name)
if value == "NULL":
value = None
actual_value = getattr(element, name)
if isinstance(value, list) and actual_value:
actual_value = list(actual_value)
assert actual_value == value, 'We expected a value of "{}" but instead got "{}" for the element {}'.format(
value, actual_value, element
)
def assert_pset(element, pset_name, prop_name=None, value=None):
if value == "NULL":
value = None
psets = ifcopenshell.util.element.get_psets(site)
if pset_name not in psets:
assert False, "The element {} does not have a property set named {}".format(element, pset_name)
if prop_name is None:
return psets[pset_name]
if prop_name not in psets[pset_name]:
assert False, 'The element {} does not have a property named "{}" in the pset "{}"'.format(
element, prop_name, pset_name
)
if value is None:
return psets[pset_name][prop_name]
actual_value = psets[pset_name][prop_name]
assert actual_value == value, 'We expected a value of "{}" but instead got "{}" for the element {}'.format(
value, actual_value, element
)
def assert_elements(
ifc_class,
elemcount,
falsecount,
falseelems,
message_all_falseelems,
message_some_falseelems,
message_no_elems,
parameter=None
):
if elemcount > 0 and falsecount == 0:
return # Test OK
elif elemcount == 0:
assert False, (
message_no_elems.format(
ifc_class=ifc_class
)
)
elif falsecount == elemcount:
if parameter is None:
assert False, (
message_all_falseelems.format(
elemcount=elemcount,
ifc_class=ifc_class
)
)
else:
assert False, (
message_all_falseelems.format(
elemcount=elemcount,
ifc_class=ifc_class,
parameter=parameter
)
)
elif falsecount > 0 and falsecount < elemcount:
if parameter is None:
assert False, (
message_some_falseelems.format(
falsecount=falsecount,
elemcount=elemcount,
ifc_class=ifc_class,
falseelems=falseelems,
)
)
else:
assert False, (
message_some_falseelems.format(
falsecount=falsecount,
elemcount=elemcount,
ifc_class=ifc_class,
falseelems=falseelems,
parameter=parameter
)
)
else:
assert False, _("Error in falsecount, something went wrong.")
def switch_locale(locale_dir, locale_id="en"):
newlang = gettext.translation(
"messages",
localedir=locale_dir,
languages=[locale_id]
)
newlang.install()
+23 -138
View File
@@ -1,71 +1,33 @@
# TODO: all args should be passed to the gui,
# either pass them further to behave or make it possible to edit them before
# TODO: if browse widgets will be canceled, last QLineEdit should be restored
# TODO: keep path or file if in browse widget canceled
# TODO: make a frame around features direcory chooser and
# use features path from ifc button
import os
import sys
import bimtester.run
from PySide2 import QtCore
from PySide2 import QtGui
from PySide2 import QtWidgets
from .run import run_all
def run():
app = QtWidgets.QApplication(sys.argv)
form = GuiWidgetBimTester()
form.show()
sys.exit(app.exec_())
class GuiWidgetBimTester(QtWidgets.QWidget):
def __init__(
self,
featurespath="",
ifcfile="",
get_featurepath_from_ifcpath=False,
args=[]
):
def __init__(self, args=[]):
super(GuiWidgetBimTester, self).__init__()
user_path = os.path.expanduser("~")
# get features dir
# print(featurespath)
if not os.path.isdir(featurespath):
featurespath = user_path
# get ifc file
# print(ifcfile)
if not os.path.isfile(ifcfile):
ifcfile = user_path
self.initial_featurespath = featurespath
self.initial_ifcfile = ifcfile
self.get_featurepath_from_ifcpath = get_featurepath_from_ifcpath
self.args = args
# print(self.initial_featurespath)
# print(self.initial_ifcfile)
# print(self.get_featurepath_from_ifcpath)
# init ui
self._setup_ui()
def __del__(self,):
# need as fix for qt event error
# http://forum.freecadweb.org/viewtopic.php?f=18&t=10732&start=10#p86493
# http://forum.freecadweb.org/viewtopic.php?f=18&t=10732&start=10#p86493
def __del__(self):
return
def _setup_ui(self):
# a lot code is taken from FreeCAD FEM solver frame work task panel
# https://forum.freecadweb.org/viewtopic.php?f=10&t=51419
# use a browse button, and a line edit
# the browse button opens a file dialog, which will set the line edit
# icon
# print(__file__)
package_path = os.path.dirname(os.path.realpath(__file__))
iconpath = os.path.join(
package_path, "resources", "icons", "bimtester.ico"
)
iconpath = os.path.join(package_path, "resources", "icons", "bimtester.ico")
"""
# as svg
# https://stackoverflow.com/a/35138314
@@ -78,6 +40,7 @@ class GuiWidgetBimTester(QtWidgets.QWidget):
#)
#theicon.sizeHint()
"""
# as pixmap
theicon = QtWidgets.QLabel(self)
iconpixmap = QtGui.QPixmap(iconpath)
@@ -87,7 +50,6 @@ class GuiWidgetBimTester(QtWidgets.QWidget):
# ifc file
_ifcfile_label = QtWidgets.QLabel("IFC file", self)
self.ifcfile_text = QtWidgets.QLineEdit()
self.set_ifcfile(self.initial_ifcfile)
_ifcfile_browse_btn = QtWidgets.QToolButton()
_ifcfile_browse_btn.setText("...")
_ifcfile_browse_btn.clicked.connect(self.select_ifcfile)
@@ -95,36 +57,20 @@ class GuiWidgetBimTester(QtWidgets.QWidget):
# 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."
)
ffifc_str = "Feature files in a directory 'features' beside the IFC file."
featuredirfromifc_label = QtWidgets.QLabel(ffifc_str, self)
self.featuredirfromifc_cb = QtWidgets.QCheckBox(self)
self.featuredirfromifc_cb.stateChanged.connect(
self.featuredirfromifc_clicked
)
# path browser and line edit
_ffdir_str = (
"Feature files directory. "
"'features' directory has to be in there."
)
_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.set_featurefilesdir(self.initial_featurespath)
self.feafilesdir_browse_btn = QtWidgets.QToolButton()
self.feafilesdir_browse_btn.setText("...")
self.feafilesdir_browse_btn.clicked.connect(
self.select_featurefilesdir
)
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 = 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()
@@ -136,7 +82,6 @@ class GuiWidgetBimTester(QtWidgets.QWidget):
layout.addWidget(theicon, 1, 0, alignment=QtCore.Qt.AlignRight)
layout.addWidget(featuredirfromifc_label, 2, 0)
layout.addWidget(self.featuredirfromifc_cb, 2, 1)
layout.addWidget(_featurefilesdir_label, 3, 0)
layout.addWidget(self.featurefilesdir_text, 4, 0)
@@ -153,17 +98,8 @@ class GuiWidgetBimTester(QtWidgets.QWidget):
layout.setRowStretch(0, 10)
self.setLayout(layout)
# set button state, needs to be here after gui has beed set up
self.featuredirfromifc_cb.setChecked(self.get_featurepath_from_ifcpath)
# **********************************************************
def select_ifcfile(self):
# print(self.get_ifcfile())
# print(os.path.isfile(self.get_ifcfile()))
ifcfile = QtWidgets.QFileDialog.getOpenFileName(
self,
dir=self.get_ifcfile()
)[0]
ifcfile = QtWidgets.QFileDialog.getOpenFileName(self, dir=self.get_ifcfile())[0]
self.set_ifcfile(ifcfile)
def set_ifcfile(self, a_file):
@@ -172,26 +108,13 @@ class GuiWidgetBimTester(QtWidgets.QWidget):
def get_ifcfile(self):
return self.ifcfile_text.text()
def featuredirfromifc_clicked(self):
if self.featuredirfromifc_cb.isChecked() is True:
self.set_featurefilesdir("")
self.featurefilesdir_text.setEnabled(False)
self.feafilesdir_browse_btn.setEnabled(False)
else:
self.set_featurefilesdir(self.initial_featurespath)
self.featurefilesdir_text.setEnabled(True)
self.feafilesdir_browse_btn.setEnabled(True)
def select_featurefilesdir(self):
thedir = self.featurefilesdir_text.text()
# print(thedir)
# print(os.path.isdir(thedir))
# hidden directories are only shown if the option is set
features_path = QtWidgets.QFileDialog.getExistingDirectory(
self,
caption="Choose features directory ...",
dir=thedir,
options=QtWidgets.QFileDialog.HideNameFilterDetails
options=QtWidgets.QFileDialog.HideNameFilterDetails,
)
self.set_featurefilesdir(features_path)
@@ -201,46 +124,11 @@ class GuiWidgetBimTester(QtWidgets.QWidget):
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)
# get features dir
if self.featuredirfromifc_cb.isChecked() is True:
ifcfile = self.get_ifcfile()
print(
"Make sure the feature files are beside "
"the ifc file in a directory named 'features'."
)
if ifcfile == "":
# os.path.realpath("") would return the cmd dir and not ""
print(
"No ifcfile given, "
"thus features files path will be set to ''."
)
the_features_path = ""
elif os.path.isfile(ifcfile) is not True:
# if ifcfile does not exist set features path to ""
print(
"The ifcfile does not exist, "
"thus features files path will be set to ''."
)
the_features_path = ""
else:
ifcfilepath = os.path.dirname(os.path.realpath(ifcfile))
if os.path.isdir(ifcfilepath):
the_features_path = ifcfilepath
else:
print(
"ifcfilepath does not exist, "
"thus features files path will be set to ''."
"this shold never happen, please debug. "
)
the_features_path = ""
else:
the_features_path = self.get_featurefilesdir()
the_features_path = self.get_featurefilesdir()
print(the_features_path)
# get ifc file
@@ -252,14 +140,11 @@ class GuiWidgetBimTester(QtWidgets.QWidget):
patched_args["featuresdir"] = the_features_path
patched_args["ifcfile"] = the_ifcfile
# run bimtester
status = run_all(patched_args)
print(status)
bimtester.run.TestRunner("file.ifc").run({})
QtWidgets.QApplication.restoreOverrideCursor()
def close_widget(self):
print("Close BIMTester Gui")
self.close()
def closeEvent(self, ev):
+4
View File
@@ -0,0 +1,4 @@
class IfcStore:
path = ""
file = None
bookmarks = {}
+15
View File
@@ -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
+95 -138
View File
@@ -1,169 +1,126 @@
import datetime
import gettext # noqa
import json
import os
import pystache
from .features.steps.utils import switch_locale
from bimtester.lang import _
def generate_report(
report_dir=".",
use_report_folder=True,
report_file_name="report.json",
html_template_file_path="",
report_file=""
):
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__))
# TODO use far less parameter
# to be discussed with other devs
def generate(self, report_json, output_file):
print("# Generating HTML reports.")
print("# Generating HTML reports now.")
report = json.loads(open(report_json).read())
for feature in report:
self.generate_feature_report(feature, output_file)
# get locale path
localedir = os.path.join(
os.path.dirname(os.path.realpath(__file__)),
"locale"
)
# get html template path
report_template_path = os.path.join(
os.path.dirname(os.path.realpath(__file__)),
"resources",
"reports"
)
if html_template_file_path:
report_template_path = html_template_file_path
# get report file and report dir
if report_file:
report_file = report_file
report_dir = os.path.dirname(report_file)
else:
if use_report_folder:
report_dir = os.path.join(report_dir, "report")
report_file = os.path.join(report_dir, report_file_name)
# print(report_file)
if not os.path.isdir(report_dir):
return print("Report directory does not exist.")
if not os.path.isfile(report_file):
return print("Report file does not exist.")
# read json report and create html report for each feature
report = json.loads(open(report_file).read())
for feature in report:
file_name = os.path.basename(feature["location"]).split(":")[0]
def generate_feature_report(self, feature, output_file):
# file_name = os.path.basename(feature["location"]).split(":")[0]
data = {
"file_name": file_name,
# "file_name": file_name,
"time": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"name": feature["name"],
"description": feature["description"],
"description": feature.get("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
continue
return
for scenario in feature["elements"]:
steps = []
total_duration = 0
if len(scenario["steps"]) == 0:
print(
"Scenario '{}' in feature '{}' has no steps. "
"Thus skipped in report."
.format(scenario["name"], feature["name"])
)
continue
for step in scenario["steps"]:
if "result" in step:
total_duration += step["result"]["duration"]
name = step["name"]
if "match" in step and "arguments" in step["match"]:
for a in step["match"]["arguments"]:
name = name.replace(a["value"], "<b>" + a["value"] + "</b>")
if "result" not in step:
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."
steps.append(
{
"name": name,
"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"],
}
)
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)
data["scenarios"].append(
{
"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,
}
)
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)
# translate report
# json.dump(mydict, myfile, indent=4)
# workaround for retrieving the feature file language
print(feature["keyword"])
if feature["keyword"] == "Feature":
switch_locale(localedir, "en")
elif feature["keyword"] == "Funktionalität":
switch_locale(localedir, "de")
elif feature["keyword"] == "Fonctionnalité":
switch_locale(localedir, "fr")
elif feature["keyword"] == "Funzionalità":
switch_locale(localedir, "it")
elif feature["keyword"] == "Functionaliteit":
switch_locale(localedir, "nl")
else:
# standard English
switch_locale(localedir, "en")
strings_report = get_html_template_strings()
# print(strings_report)
data.update(strings_report)
# print(data)
data.update(self.get_template_strings())
html_report = os.path.join(report_dir, "{}.html".format(file_name))
html_tmpl = os.path.join(report_template_path, "template.html")
with open(html_report, "w", encoding="utf8") as out:
with open(html_tmpl, encoding="utf8") as template:
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
def get_html_template_strings():
steps = []
total_duration = 0
return {
"tr_lang": _("en"),
"tr_success": _("Success"),
"tr_failure": _("Failure"),
"tr_tests_passed": _("Tests passed"),
"tr_duration": _("Duration"),
"tr_auditing": _("OpenBIM auditing is a feature of"),
"tr_and": _("and")
}
for step in scenario["steps"]:
step_data = self.process_step(step)
total_duration += step_data["time_raw"]
steps.append(step_data)
total_passes = len([s for s in steps if s["is_success"] is True])
total_steps = len(steps)
pass_rate = round((total_passes / total_steps) * 100)
return {
"name": scenario["name"],
# on behave < 1.2.6 there is no 'status' thus report fails
"is_success": scenario["status"] == "passed",
"time": round(total_duration, 2),
"steps": steps,
"total_passes": total_passes,
"total_steps": total_steps,
"pass_rate": pass_rate,
}
def process_step(self, step):
name = step["name"]
if "match" in step and "arguments" in step["match"]:
for a in step["match"]["arguments"]:
name = name.replace(a["value"], "<b>" + a["value"] + "</b>")
if "result" not in step:
step["result"] = {}
step["result"]["status"] = "skipped"
step["result"]["duration"] = 0
step["result"]["error_message"] = "This requirement has been skipped due to a previous failing step."
elif step["result"]["status"] == "undefined":
step["result"] = {}
step["result"]["status"] = "undefined"
step["result"]["duration"] = 0
step["result"]["error_message"] = "This requirement has not yet been specified."
data = {
"name": name,
"time_raw": step["result"]["duration"],
"time": round(step["result"]["duration"], 2),
"is_success": step["result"]["status"] == "passed",
"is_unspecified": step["result"]["status"] == "undefined",
"is_skipped": step["result"]["status"] == "skipped",
"error_message": None if step["result"]["status"] == "passed" else step["result"]["error_message"],
}
# Remove the first "Assertion Failed" message
if isinstance(data["error_message"], list) and data["error_message"]:
data["error_message"].pop(0)
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"),
}
@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang={{tr_lang}}>
<html lang={{_lang}}>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
@@ -32,24 +32,24 @@
<h1>{{name}}</h1>
<p><strong>{{time}} {{file_name}}</strong></p>
<hr>
<span class="{{#is_success}}success{{/is_success}}{{^is_success}}failure{{/is_success}}">{{#is_success}}{{tr_success}}{{/is_success}}{{^is_success}}{{tr_failure}}{{/is_success}}</span>
{{tr_tests_passed}}: <strong>{{total_passes}} / {{total_steps}}</strong> ({{pass_rate}}%)
<span class="{{#is_success}}success{{/is_success}}{{^is_success}}failure{{/is_success}}">{{#is_success}}{{_success}}{{/is_success}}{{^is_success}}{{_failure}}{{/is_success}}</span>
{{_tests_passed}}: <strong>{{total_passes}} / {{total_steps}}</strong> ({{pass_rate}}%)
<br />
{{#description}}
<p class="description">
{{#description}}
{{.}}<br />
{{/description}}
</p>
{{/description}}
<hr>
</header>
{{#scenarios}}
<section>
<h2>{{name}}</h2>
<p>
<span class="{{#is_success}}success{{/is_success}}{{^is_success}}failure{{/is_success}}">{{#is_success}}{{tr_success}}{{/is_success}}{{^is_success}}{{tr_failure}}{{/is_success}}</span>
{{tr_tests_passed}}: <strong>{{total_passes}} / {{total_steps}}</strong> ({{pass_rate}}%)
<span class="{{#is_success}}success{{/is_success}}{{^is_success}}failure{{/is_success}}">{{#is_success}}{{_success}}{{/is_success}}{{^is_success}}{{_failure}}{{/is_success}}</span>
{{_tests_passed}}: <strong>{{total_passes}} / {{total_steps}}</strong> ({{pass_rate}}%)
<span class="time">
{{tr_duration}}: {{time}}s
{{_duration}}: {{time}}s
</span>
</p>
<ol>
@@ -72,7 +72,7 @@
<hr>
<footer>
<p>
{{tr_auditing}} <a href="https://blenderbim.org/">BlenderBIM</a> {{tr_and}} <a href="http://ifcopenshell.org/">IfcOpenShell</a>.
{{_auditing}} <a href="https://blenderbim.org/">BlenderBIM</a> {{_and}} <a href="http://ifcopenshell.org/">IfcOpenShell</a>.
</p>
</footer>
</body>
+107 -322
View File
@@ -1,343 +1,128 @@
import behave.formatter.pretty # Needed for pyinstaller to package it
import os
import shutil
import sys
import json
import shutil
import logging
import tempfile
import webbrowser
# TODO: if the ifc file name or path contains special character
# like German Umlaute behave gives an error
# get bimtester source code module path
bimtester_path = os.path.dirname(os.path.realpath(__file__))
# print(bimtester_path)
locale_path = os.path.join(bimtester_path, "locale")
import ifcopenshell
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__))
import ifcopenshell.express
except:
pass # They are using an old version of IfcOpenShell. Gracefully degrade for now.
import behave.formatter.pretty # Needed for pyinstaller to package it
from bimtester.ifc import IfcStore
from distutils.dir_util import copy_tree
from behave.__main__ import main as behave_main
def get_resource_path(relative_path):
return os.path.join(base_path, relative_path)
# TODO: refactor when this isn't super experimental
from logging import StreamHandler
class IDSHandler(StreamHandler):
def __init__(self):
StreamHandler.__init__(self)
self.results = {
"name": "Specification name",
"status": "passed",
"location": "filename.xml",
"elements": [
{
"keyword": "Scenario",
"name": "Checking IDS specifications",
"status": "passed",
"steps": []
}
]
}
def emit(self, record):
msg = self.format(record)
# Obviously, not a final product
is_fail = "is compliant" not in msg
if is_fail:
self.results["status"] = "failed"
self.results["elements"][0]["status"] = "failed"
self.results["elements"][0]["steps"].append({
"keyword": "*",
"match": {},
"name": msg,
"result": {
"duration": 0.0,
"error_message": "Assertion Failed",
"status": "failed" if is_fail else "passed"
},
"step_type": "given"
})
def run_tests(args):
class TestRunner:
def __init__(self, ifc_path, schema_path=None, ifc=None):
IfcStore.path = ifc_path
print("# Run tests.")
# can't load the IFC file if the schema is not loaded before
if schema_path:
schema = ifcopenshell.express.parse(schema_path)
ifcopenshell.register_schema(schema)
report_file = os.path.join("report", "report.json")
IfcStore.file = ifc if ifc else ifcopenshell.open(ifc_path)
if "copyintemprun" in args and args["copyintemprun"] is True:
print("copyintemprun")
is_copyintemprun = True
args, copy_base_path = copy_intmp_tests(args)
features_path = os.path.join(copy_base_path, "features")
report_file = os.path.join(copy_base_path, report_file)
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__))
else:
print("No copyintemprun")
is_copyintemprun = False
if not get_features(args):
print("No features could be found to check.")
return False
features_path = get_resource_path("features")
self.locale_path = os.path.join(self.base_path, "locale")
# get behave args
behave_args = get_behave_args(args, features_path, report_file)
def run(self, args):
if args["feature"][-4:].lower() == ".xml":
return self.test_ids(args)
return self.test_feature(args)
# run tests
if behave_args != []:
run_behave(behave_args)
else:
print("Error, not able to run behave because of empty behave args.")
return False
def test_ids(self, args):
# Local import whilst this is experimental
import ifcopenshell.ids
if is_copyintemprun is True:
return report_file
else:
return True
logger = logging.getLogger("IDS")
logging.basicConfig(level=logging.INFO, format="%(message)s")
ids_handler = IDSHandler()
logger.addHandler(ids_handler)
ids_file = ifcopenshell.ids.ids(args["feature"])
ids_file.validate(IfcStore.file, logger)
tmpdir = tempfile.mkdtemp()
report_json = os.path.join(tmpdir, "report.json")
json.dump([ids_handler.results], open(report_json, "w"))
return report_json
def get_behave_args(args, features_path, report_file):
def test_feature(self, args):
tmpdir = tempfile.mkdtemp()
features_path = os.path.join(tmpdir, "features")
steps_path = os.path.join(features_path, "steps")
report_json = os.path.join(tmpdir, "report.json")
shutil.copytree(os.path.join(self.base_path, "features"), features_path)
shutil.copy(args["feature"], features_path)
if args["steps"]:
if os.path.isfile(args["steps"]):
shutil.copy(args["steps"], steps_path)
elif os.path.isdir(args["steps"]):
copy_tree(args["steps"], steps_path)
behave_main(self.get_behave_args(args, features_path, report_json))
return report_json
if os.path.isdir(features_path):
def get_behave_args(self, args, features_path, report_json):
behave_args = [features_path]
else:
return []
if os.path.isdir(locale_path):
behave_args.extend([
# path for translation files
# next two lines are one arg
"--define",
"localedir={}".format(locale_path)
])
else:
print(
"Error, translation locals path '{}' does not exist."
.format(locale_path)
)
if args["advanced_arguments"]:
behave_args.extend(args["advanced_arguments"].split())
if args["ifcfile"]:
behave_args.extend([
# next two lines are one arg
"--define",
"ifcfile={}".format(args["ifcfile"])
])
if args["path"]:
behave_args.extend([
# next two lines are one arg
"--define",
"path={}".format(args["path"])
])
if not args["console"]:
behave_args.extend([
# redirect prints in step methods
# if step fails some output is catched, thus might not be printed
behave_args.extend(["--define", "localedir={}".format(self.locale_path)])
if args["advanced_arguments"]:
behave_args.extend(args["advanced_arguments"].split())
if args["ifc"]:
behave_args.extend(["--define", "ifc={}".format(args["ifc"])])
if args["path"]:
behave_args.extend(["--define", "path={}".format(args["path"])])
if args["lang"]:
behave_args.extend(["--lang={}".format(args["lang"])])
if not args["console"]:
# https://github.com/behave/behave/issues/346
"--no-capture",
# next two lines are one arg
"--format",
"json.pretty",
# report file, if relative, than relative to current shell path
# next two lines are one arg
"--outfile",
report_file,
])
return behave_args
def run_behave(behave_args):
from json import dumps
print(dumps(behave_args, indent=4))
from behave.__main__ import main as behave_main
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
"""
# clean logs to be able to run tests
# once again but on another building model and in another directory
# somehow does not work, thus test will be run in the same directory
# on each new run, directory will be deleted before each new run
# https://github.com/behave/behave/issues/871
# run bimtester
# copy manually this code, run bimtester again,
# does not work on two directories
from behave.runner_util import reset_runtime
reset_runtime()
"""
def copy_intmp_tests(args={}):
print("# Copy features and steps to temp.")
from behave import __version__ as behave_version
# https://github.com/behave/behave/issues/871
if behave_version == "1.2.5":
print(
"At least behave version 1.2.6 is needed, but version {} found."
.format(behave_version)
)
return False
# print(args)
# get the features_path, the dir where the feature files to test are in
if ("featuresdir" in args and args["featuresdir"] != ""):
is_features = True
the_features_path = os.path.join(args["featuresdir"], "features")
if not os.path.isdir(the_features_path):
print(
"Error, the features directory '{}' does not exist."
.format(the_features_path)
)
return False
else:
is_features = False
# get ifc path and ifc filename
if ("ifcfile" in args and args["ifcfile"] != ""):
is_ifcfile = True
ifcfile = args["ifcfile"]
if os.path.isfile(ifcfile) is not True:
print("Error, the ifc file '{}' does not exist.".format(ifcfile))
return False
ifc_path = os.path.dirname(os.path.realpath(ifcfile))
if os.path.isdir(ifc_path) is False:
print("ifc path does not exist.")
return False
else:
is_ifcfile = False
# print(is_features)
# print(is_ifcfile)
if is_features is True and is_ifcfile is True:
print("features given, ifcfile given.")
elif is_features is False and is_ifcfile is True:
print("features given, ifcfile NOT given.")
# features = ifcfile directory
the_features_path = os.path.join(ifc_path, "features")
elif is_features is True and is_ifcfile is False:
print("features given, ifcfile NOT given.")
# the ifcfile provided in the feature files is used
# TODO What will be passed as ifcfile arg?
print("Not yet implemented.")
return False
elif is_features is False and is_ifcfile is False:
print("features NOT given, ifcfile NOT given.")
# the current directory = features
# the ifcfile provided in the feature files is used
# TODO What will be passed as ifcfile arg?
print("Not yet implemented.")
return False
else:
print("Error: this should never happen, please debug.")
return False
# set up paths
# a unique temp path should not be used
# behave raises an ambiguous step exception
# copy_base_path = tempfile.mkdtemp()
# thus use the same path on every run
# but delete it if exists
copy_base_path = os.path.join(tempfile.gettempdir(), "bimtesterfc")
if os.path.isdir(copy_base_path):
from shutil import rmtree
rmtree(copy_base_path) # fails on read only files
if os.path.isdir(copy_base_path):
print(
"Delete former beimtester run dir '{}' failed"
.format(copy_base_path)
)
return False
os.mkdir(copy_base_path)
copy_features_path = os.path.join(copy_base_path, "features")
# copy features path from bimtester source code
srccode_features_path = os.path.join(
bimtester_path,
"features",
)
# print(srccode_features_path)
if os.path.exists(srccode_features_path):
shutil.copytree(srccode_features_path, copy_features_path)
else:
print(
"Bimtester source code features directory {} not found."
.format(srccode_features_path)
)
return False
# copy features files
# print(the_features_path)
# print(copy_features_path)
# parameter dirs_exist_ok=True, from py 3.8
# if os.path.exists(the_features_path):
# shutil.copytree(
# the_features_path,
# copy_features_path,
# dirs_exist_ok=True
# )
# copy feature files
feature_files = os.listdir(the_features_path)
# print(feature_files)
for feature_file in feature_files:
cp_feature_file = os.path.join(copy_features_path, feature_file)
# print(feature_file)
# copy file
shutil.copyfile(
os.path.join(the_features_path, feature_file),
cp_feature_file
)
return args, copy_base_path
def run_all(args):
print("# Run all.")
# run bimtester
report_file = run_tests(args)
print(report_file)
# check if it worked out well
if report_file is False:
print("BIMTester behave tests returned False.")
return False
if not os.path.isfile(report_file):
print("Report directory does not exist. This should not happen. Debug")
return False
# create html report and open in webbrowser
from .reports import generate_report
generate_report(report_file=report_file)
# get the feature files
feature_files = os.listdir(
os.path.join(args["featuresdir"], "features")
)
# print(feature_files)
for ff in feature_files:
webbrowser.open(os.path.join(
os.path.dirname(report_file),
ff + ".html"
))
return True
behave_args.extend(["--no-capture", "--format", "json.pretty", "--outfile", report_json])
return behave_args
+19
View File
@@ -0,0 +1,19 @@
from collections import defaultdict
class TableModel:
"""This class represents a table of data"""
def __init__(self, key_name=None, value_name=None):
self.rows = defaultdict(list)
self.key_name = key_name
self.value_name = value_name
def add_row(self, related, relating):
self.rows[related].append(relating)
def get_count(self):
return len(self.rows)
def get_count_distinct_values(self):
return len(set(item for sublist in self.rows.values() for item in sublist))
+126
View File
@@ -0,0 +1,126 @@
import ifcopenshell
import ifcopenshell.util.element
import ifcopenshell.validate
import json
from bimtester.lang import _
def by_types(ifc, ifc_types):
elements = []
for ifc_type in ifc_types.split(","):
elements += ifc.by_type(ifc_type.strip())
return elements
def validate(ifc):
errors = []
logger = ifcopenshell.validate.json_logger()
try:
ifcopenshell.validate.validate(ifc, logger)
if logger.statements:
for statement in logger.statements:
errors.append(f"{json.dumps(statement, default=str)}")
except RuntimeError as error:
assert False, str(error)
def assert_guid(ifc, guid):
try:
return ifc.by_guid(guid)
except:
assert False, _("An element with the ID {} could not be found.").format(guid)
def assert_number(number):
try:
return float(number)
except ValueError:
assert False, _("A number should be specified, not {}").format(number)
def assert_type(element, ifc_class, is_exact=False):
if is_exact:
assert element.is_a() == ifc_class, _("The element {} is an {} instead of {}.").format(
element, element.is_a(), ifc_class
)
else:
assert element.is_a(ifc_class), _("The element {} is an {} instead of {}.").format(
element, element.is_a(), ifc_class
)
def assert_attribute(element, name, value=None):
if not hasattr(element, name):
assert False, _("The element {} does not have the attribute {}").format(element, name)
if not value:
if getattr(element, name) is None:
assert False, _("The element {} does not have a value for the attribute {}").format(element, name)
return getattr(element, name)
if value == "NULL":
value = None
actual_value = getattr(element, name)
if isinstance(value, list) and actual_value:
actual_value = list(actual_value)
assert actual_value == value, _('We expected a value of "{}" but instead got "{}" for the element {}').format(
value, actual_value, element
)
def assert_pset(element, pset_name, prop_name=None, value=None):
if value == "NULL":
value = None
psets = ifcopenshell.util.element.get_psets(site)
if pset_name not in psets:
assert False, _("The element {} does not have a property set named {}").format(element, pset_name)
if prop_name is None:
return psets[pset_name]
if prop_name not in psets[pset_name]:
assert False, _('The element {} does not have a property named "{}" in the pset "{}"').format(
element, prop_name, pset_name
)
if value is None:
return psets[pset_name][prop_name]
actual_value = psets[pset_name][prop_name]
assert actual_value == value, _('We expected a value of "{}" but instead got "{}" for the element {}').format(
value, actual_value, element
)
# TODO: what is this?
def assert_elements(
ifc_class,
elemcount,
falsecount,
falseelems,
message_all_falseelems,
message_some_falseelems,
message_no_elems,
parameter=None,
):
if elemcount > 0 and falsecount == 0:
return # Test OK
elif elemcount == 0:
assert False, message_no_elems.format(ifc_class=ifc_class)
elif falsecount == elemcount:
if parameter is None:
assert False, message_all_falseelems.format(elemcount=elemcount, ifc_class=ifc_class)
else:
assert False, message_all_falseelems.format(elemcount=elemcount, ifc_class=ifc_class, parameter=parameter)
elif falsecount > 0 and falsecount < elemcount:
if parameter is None:
assert False, message_some_falseelems.format(
falsecount=falsecount,
elemcount=elemcount,
ifc_class=ifc_class,
falseelems=falseelems,
)
else:
assert False, message_some_falseelems.format(
falsecount=falsecount,
elemcount=elemcount,
ifc_class=ifc_class,
falseelems=falseelems,
parameter=parameter,
)
else:
assert False, _("Error in falsecount, something went wrong.")
+29
View File
@@ -0,0 +1,29 @@
#!/usr/bin/env python3
import os
import argparse
import bimtester.clean
import bimtester.reports
import bimtester.run
parser = argparse.ArgumentParser(description="Runs unit tests for BIM data")
parser.add_argument("-a", "--action", type=str, help="Action to perform, from run/purge", default="run")
parser.add_argument("--advanced-arguments", type=str, help="Specify arguments to Behave", default="")
parser.add_argument("-c", "--console", action="store_true", help="Show results in the console")
parser.add_argument("-f", "--feature", type=str, help="Specify a feature file or IDS to test", required=True)
parser.add_argument("-i", "--ifc", type=str, help="Specify an IFC file to test", required=True)
parser.add_argument("-p", "--path", type=str, help="Define a path for use in test steps that use relative paths")
parser.add_argument("-r", "--report", type=str, help="Specify an output file for a HTML report")
parser.add_argument("--steps", type=str, help="Specify a custom step definition Python file or directory")
parser.add_argument("--schema-file", type=str, help="Path to a custom IFC schema, used with --schema-name")
parser.add_argument("--schema-name", type=str, help="The name of a custom IFC schema, used with --schema-file")
parser.add_argument("--lang", type=str, help="Specify a language e.g. en/de/fr/it", default="")
args = vars(parser.parse_args())
if args["action"] == "run":
report_json = bimtester.run.TestRunner(args["ifc"], args["schema_file"]).run(args)
if args["report"]:
bimtester.reports.ReportGenerator().generate(report_json, args["report"])
elif args["action"] == "purge":
bimtester.clean.TestPurger().purge()
@@ -1,119 +1,144 @@
from behave import step, given, when, then, use_step_matcher
from utils import IfcFile
use_step_matcher("parse")
@step(u"There must be exactly {number} {ifc_class} element")
@step(u"There must be exactly {number} {ifc_class} elements")
def step_impl(context, number, ifc_class):
num = len(IfcFile.get().by_type(ifc_class))
assert num == int(number), "Could not find {} elements of {}. Found {} element(s).".format(number, ifc_class, num)
@given(u'a set of specific related elements')
def step_impl(context):
model = getattr(context, "model", None)
if not model:
context.model = TableModel()
for row in context.table:
context.model.add_row(row["RelatedObjects"], row["RelatingGroup"])
@given(u'a set of specific related elements taken from the file "{path_file}"')
def step_impl(context, path_file):
import csv
import os
model = getattr(context, "model", None)
if not model:
context.model = TableModel()
if context.config.userdata.get('path'):
path_file = os.path.join(context.config.userdata.get('path'), path_file)
if not os.path.exists(path_file):
assert False, "File {} not found".format(path_file)
with open(path_file, 'r', encoding="utf-8-sig") as csvfile:
reader = csv.DictReader(csvfile)
for row in reader:
context.model.add_row(row["RelatedObjects"], row["RelatingGroup"])
@then(u'there must be exactly a number of {ifc_class} equals to the number of distinct row value')
def step_impl(context, ifc_class):
try:
context.execute_steps(u"""
then There must be exactly {number} {ifc_class} elements
""".format(ifc_class=ifc_class, number=context.model.get_count_distinct_values()))
except AssertionError as error:
str_error = str(error)
assert False, str_error[:str_error.find("Traceback")]
assert True
@then(u'there is a relationship {ifc_class} with {left_attribute} and {right_attribute} between the two elements of each row')
def step_impl(context, ifc_class, left_attribute, right_attribute):
rows = context.model.rows
elements = IfcFile.by_type(ifc_class)
errors = []
for key, value in rows.items():
found = False
for element in elements:
if any(x.Name == key for x in getattr(element, left_attribute))\
and getattr(element, right_attribute).Name == value:
found = True
if not found:
errors.append(f'The row ({key}, {value}) does not have the relationship.')
assert not errors, "Errors occured:\n{}".format("\n".join(errors))
use_step_matcher("re")
@step("all IfcGroup must be linked to a type in the list (?P<linked_ifc_classes>.*)")
def step_impl(context, linked_ifc_classes):
groups = IfcFile.by_type("IfcGroup")
errors = []
for group in groups:
if not hasattr(group, "IsGroupedBy"):
errors.append(f'The element "{group.Name}" has no "IsGroupedBy" attribute.')
else:
for grouped_by in getattr(group, "IsGroupedBy"):
if not hasattr(grouped_by, "RelatedObjects"):
errors.append(f'The element "{grouped_by.Name}" has no "RelatedObjects" attribute.')
else:
for related_object in getattr(grouped_by, "RelatedObjects"):
found = False
for linked_ifc_class in linked_ifc_classes.split(","):
if(related_object.is_a(linked_ifc_class)):
found = True
if not found:
errors.append(f'The element "{related_object.Name}" does not have the right associated type.')
assert not errors, "Errors occured:\n{}".format("\n".join(errors))
@then(u'there is an element of type (?P<ifc_types>.*) with a (?P<attribute_name>.*) attribute for each row key')
def step_impl(context, ifc_types, attribute_name):
check_if_element_exists_by_types_with_attribute_name(ifc_types, attribute_name, context.model.rows.keys())
@then(u'there is an element of type (?P<ifc_types>.*) with a (?P<attribute_name>.*) attribute for each row value')
def step_impl(context, ifc_types, attribute_name):
values = set(context.model.rows.values())
check_if_element_exists_by_types_with_attribute_name(ifc_types, attribute_name, values)
def check_if_element_exists_by_types_with_attribute_name(ifc_types, attribute_name, attribute_values):
errors = []
# retrieve all elements of that type
elements = IfcFile.by_types(ifc_types)
# loop
for attribute_value in attribute_values:
found = False
for element in elements:
if hasattr(element, attribute_name) and getattr(element, attribute_name) == attribute_value:
found = True
if not found:
errors.append(f'An element with {attribute_name} attribute "{attribute_value}" was not found.')
assert not errors, "Errors occured:\n{}".format("\n".join(errors))
class TableModel(object):
"""This class represents a table of data."""
def __init__(self):
self.rows = dict()
def add_row(self, related, relating):
self.rows[related] = relating
def get_count(self):
return len(self.rows)
def get_count_distinct_values(self):
return len(set(self.rows.values()))
from behave import step, given, when, then, use_step_matcher
use_step_matcher("parse")
@step("There must be exactly {number} {ifc_class} element")
@step("There must be exactly {number} {ifc_class} elements")
def step_impl(context, number, ifc_class):
num = len(IfcStore.file.by_type(ifc_class))
assert num == int(number), "Could not find {} elements of {}. Found {} element(s).".format(number, ifc_class, num)
@given("a set of specific related elements")
def step_impl(context):
model = getattr(context, "model", None)
if not model:
context.model = TableModel()
for row in context.table:
context.model.add_row(row["RelatedObjects"], row["RelatingGroup"])
@given('a set of specific related elements taken from the file "{path_file}"')
def step_impl(context, path_file):
import csv
import os
model = getattr(context, "model", None)
if not model:
context.model = TableModel()
if context.config.userdata.get("path"):
path_file = os.path.join(context.config.userdata.get("path"), path_file)
if not os.path.exists(path_file):
assert False, "File {} not found".format(path_file)
with open(path_file, "r", encoding="utf-8-sig") as csvfile:
reader = csv.DictReader(csvfile)
for row in reader:
context.model.add_row(row["RelatedObjects"], row["RelatingGroup"])
@then("there must be exactly a number of {ifc_class} equals to the number of distinct row value")
def step_impl(context, ifc_class):
try:
context.execute_steps(
"""
then There must be exactly {number} {ifc_class} elements
""".format(
ifc_class=ifc_class, number=context.model.get_count_distinct_values()
)
)
except AssertionError as error:
str_error = str(error)
assert False, str_error[: str_error.find("Traceback")]
assert True
@then(
"there is a relationship {ifc_class} with {left_attribute} and {right_attribute} between the two elements of each row"
)
def step_impl(context, ifc_class, left_attribute, right_attribute):
rows = context.model.rows
elements = IfcStore.file.by_type(ifc_class)
errors = []
for key, value in rows.items():
found = False
for element in elements:
if (
any(x.Name == key for x in getattr(element, left_attribute))
and getattr(element, right_attribute).Name == value
):
found = True
if not found:
errors.append(f"The row ({key}, {value}) does not have the relationship.")
assert not errors, "Errors occured:\n{}".format("\n".join(errors))
use_step_matcher("re")
@step("all IfcGroup must be linked to a type in the list (?P<linked_ifc_classes>.*)")
def step_impl(context, linked_ifc_classes):
groups = IfcStore.file.by_type("IfcGroup")
errors = []
for group in groups:
if not hasattr(group, "IsGroupedBy"):
errors.append(f'The element "{group.Name}" has no "IsGroupedBy" attribute.')
else:
for grouped_by in getattr(group, "IsGroupedBy"):
if not hasattr(grouped_by, "RelatedObjects"):
errors.append(f'The element "{grouped_by.Name}" has no "RelatedObjects" attribute.')
else:
for related_object in getattr(grouped_by, "RelatedObjects"):
found = False
for linked_ifc_class in linked_ifc_classes.split(","):
if related_object.is_a(linked_ifc_class):
found = True
if not found:
errors.append(
f'The element "{related_object.Name}" does not have the right associated type.'
)
assert not errors, "Errors occured:\n{}".format("\n".join(errors))
@then("there is an element of type (?P<ifc_types>.*) with a (?P<attribute_name>.*) attribute for each row key")
def step_impl(context, ifc_types, attribute_name):
check_if_element_exists_by_types_with_attribute_name(ifc_types, attribute_name, context.model.rows.keys())
@then("there is an element of type (?P<ifc_types>.*) with a (?P<attribute_name>.*) attribute for each row value")
def step_impl(context, ifc_types, attribute_name):
values = set(context.model.rows.values())
check_if_element_exists_by_types_with_attribute_name(ifc_types, attribute_name, values)
def check_if_element_exists_by_types_with_attribute_name(ifc_types, attribute_name, attribute_values):
errors = []
elements = []
for ifc_type in ifc_types.split(","):
elements += ifc.by_type(ifc_type.strip())
for attribute_value in attribute_values:
found = False
for element in elements:
if hasattr(element, attribute_name) and getattr(element, attribute_name) == attribute_value:
found = True
if not found:
errors.append(f'An element with {attribute_name} attribute "{attribute_value}" was not found.')
assert not errors, "Errors occured:\n{}".format("\n".join(errors))
class TableModel(object):
"""This class represents a table of data."""
def __init__(self):
self.rows = dict()
def add_row(self, related, relating):
self.rows[related] = relating
def get_count(self):
return len(self.rows)
def get_count_distinct_values(self):
return len(set(self.rows.values()))
@@ -0,0 +1,52 @@
import gettext
from behave import given
from behave import step
from utils import IfcFile
from bimtester.ifc import IfcStore
from bimtester.lang import _
@step("The IFC file must be exported by application full name {fullname}")
def step_impl(context, fullname):
real_fullname = IfcStore.file.by_type("IfcApplication")[0].ApplicationFullName
assert real_fullname == fullname, (
"The IFC file was not exported by application full name {} "
"instead it was exported by application full name {}".format(fullname, real_fullname)
)
@step("The IFC file must be exported by application identifier {identifier}")
def step_impl(context, identifier):
real_identifier = IfcStore.file.by_type("IfcApplication")[0].ApplicationIdentifier
assert (
real_identifier == identifier
), "The IFC file was not exported by application identifier {} " "instead it was exported by identifier {}".format(
identifier, real_identifier
)
@step("The IFC file must be exported by the application version {version}")
def step_impl(context, version):
real_version = IfcStore.file.by_type("IfcApplication")[0].Version
assert (
real_version == version
), "The IFC file was not exported by application version {} " "instead it was exported by version {}".format(
version, real_version
)
@step(
"IFC data header must have a file description of {header_file_description} such as the new Allplan IFC exporter creates it"
)
def step_impl(context, header_file_description):
is_header_file_description = IfcStore.file.wrapped_data.header.file_description.description
assert (
str(is_header_file_description) == header_file_description
), "The file was not exported by the new ifc exporter in Allplan. File description header: {}".format(
is_header_file_description
)
@@ -3,55 +3,31 @@ from behave import step
import attributes_eleclasses_methods as aem
from utils import assert_elements
from utils import IfcFile
from utils import switch_locale
the_lang = "en"
@step("There are no {ifc_class} elements")
def step_impl(context, ifc_class):
switch_locale(context.localedir, the_lang)
aem.no_eleclass(
context,
ifc_class
)
aem.no_eleclass(context, ifc_class)
@step("There are no {ifc_class} elements because {reason}")
def step_impl(context, ifc_class, reason):
switch_locale(context.localedir, the_lang)
aem.no_eleclass(
context,
ifc_class
)
aem.no_eleclass(context, ifc_class)
@step("All {ifc_class} elements class attributes have a value")
def step_impl(context, ifc_class):
switch_locale(context.localedir, the_lang)
aem.eleclass_have_class_attributes_with_a_value(
context,
ifc_class
)
aem.eleclass_have_class_attributes_with_a_value(context, ifc_class)
@step("All {ifc_class} elements have a name given")
def step_impl(context, ifc_class):
switch_locale(context.localedir, the_lang)
aem.eleclass_has_name_with_a_value(
context,
ifc_class
)
aem.eleclass_has_name_with_a_value(context, ifc_class)
@step("All {ifc_class} elements have a description given")
def step_impl(context, ifc_class):
switch_locale(context.localedir, the_lang)
aem.eleclass_has_description_with_a_value(
context,
ifc_class
)
aem.eleclass_has_description_with_a_value(context, ifc_class)
@step('all {ifc_class} elements have a name matching the pattern "{pattern}"')
@@ -73,9 +49,6 @@ def step_impl(context, ifc_class, attribute_name, attribute_value):
assert False
# ------------------------------------------------------------------------
# STEPS with Regular Expression Matcher ("re")
# ------------------------------------------------------------------------
use_step_matcher("re")
@@ -0,0 +1,26 @@
from behave import step
@step("Es sind keine {ifc_class} Bauteile vorhanden")
def step_impl(context, ifc_class):
context.execute_steps(f"* There are no {ifc_class} elements")
@step("Aus folgendem Grund gibt es keine {ifc_class} Bauteile: {reason}")
def step_impl(context, ifc_class, reason):
context.execute_steps(f"* There are no {ifc_class} elements because {reason}")
@step("Alle {ifc_class} Bauteilklassenattribute haben einen Wert")
def step_impl(context, ifc_class):
context.execute_steps(f"* All {ifc_class} elements class attributes have a value")
@step("Bei allen {ifc_class} Bauteile ist der Name angegeben")
def step_impl(context, ifc_class):
context.execute_steps(f"* All {ifc_class} elements have a name given")
@step("Bei allen {ifc_class} Bauteile ist die Beschreibung angegeben")
def step_impl(context, ifc_class):
context.execute_steps(f"* All {ifc_class} elements have a description given")
+6
View File
@@ -0,0 +1,6 @@
#!/usr/bin/env python3
from bimtester.guiwidget import run
run()
+14
View File
@@ -0,0 +1,14 @@
--- behave/model_core.py 2018-02-26 02:53:03.000000000 +1100
+++ /home/dion/Projects/IfcOpenShell/src/ifcblenderexport/patch-behave-model_core.py 2021-03-21 11:57:27.625195794 +1100
@@ -269,7 +269,10 @@
class BasicStatement(object):
def __init__(self, filename, line, keyword, name):
filename = filename or '<string>'
- filename = os.path.relpath(filename, os.getcwd()) # -- NEEDS: abspath?
+ try:
+ filename = os.path.relpath(filename, os.getcwd()) # -- NEEDS: abspath?
+ except ValueError:
+ pass
self.location = FileLocation(filename, line)
assert isinstance(keyword, six.text_type)
assert isinstance(name, six.text_type)
+14
View File
@@ -0,0 +1,14 @@
--- behave/runner_util.py 2018-02-26 02:53:03.000000000 +1100
+++ /home/dion/Projects/IfcOpenShell/src/ifcblenderexport/patch-behave-runner_util.py 2021-03-21 11:58:29.777638958 +1100
@@ -381,7 +381,10 @@
locals_["__file__"] = filename
with open(filename, "rb") as f:
# pylint: disable=exec-used
- filename2 = os.path.relpath(filename, os.getcwd())
+ try:
+ filename2 = os.path.relpath(filename, os.getcwd())
+ except ValueError:
+ filename2 = filename
code = compile(f.read(), filename2, "exec", dont_inherit=True)
exec(code, globals_, locals_)
-163
View File
@@ -1,163 +0,0 @@
#!/usr/bin/env python3
import argparse
import os
from bimtester import clean
from bimtester import reports
from bimtester import run
def show_widget(
features="",
ifcfile="",
get_featurepath_from_ifcpath=False,
args=[]
):
import sys
from PySide2 import QtWidgets
from bimtester.guiwidget import GuiWidgetBimTester
# Create the Qt Application
app = QtWidgets.QApplication(sys.argv)
# Create and show the form
form = GuiWidgetBimTester(
features,
ifcfile,
get_featurepath_from_ifcpath,
args
)
form.show()
# Run the main Qt loop
sys.exit(app.exec_())
if __name__ == "__main__":
# TODO make similar to bash commands
# use - not _ in named args
parser = argparse.ArgumentParser(
description="Runs unit tests for BIM data"
)
parser.add_argument(
"-a",
"--advanced-arguments",
type=str,
help="Specify your own arguments to Python's Behave",
default=""
)
parser.add_argument(
"-c",
"--console",
action="store_true",
help="Show results in the console"
)
parser.add_argument(
"-d",
"--featuresdir",
type=str,
help=(
"Specify a features directory. This should contain "
"a directory named 'features' which contains all the "
"feature files."
),
default=""
)
parser.add_argument(
"-f",
"--feature",
type=str,
help="Specify a feature file to test",
default=""
)
parser.add_argument(
"-g",
"--gui",
action="store_true",
help=(
"Start the gui. The option t (copyintemprun) "
"is triggered automaticly."
)
)
parser.add_argument(
"-i",
"--ifcfile",
type=str,
help=(
"Specify a ifc file."
),
default=""
)
parser.add_argument(
"-p",
"--purge",
action="store_true",
help="Purge tests of deleted elements"
)
parser.add_argument(
"-path",
"--path",
type=str,
help=(
"Specify a path to prepend to feature and ifc file"
),
default=""
)
parser.add_argument(
"-r",
"--report",
action="store_true",
help="Generate a HTML report"
)
parser.add_argument(
"-rr",
"--report_after_run",
action="store_true",
help="Generate a HTML report after running the tests"
)
parser.add_argument(
"-t",
"--copyintemprun",
action="store_true",
help=(
"Copy steps and feature files into a temporary directory "
"and run bimtester with them."
)
)
args = vars(parser.parse_args())
from json import dumps
print(dumps(args, indent=4))
if args["path"]:
if args["feature"]:
args["feature"] = os.path.join(args["path"], args["feature"])
if not args["gui"]:
args["ifcfile"] = os.path.join(args["path"], args["ifcfile"])
if args["purge"]:
clean.TestPurger().purge()
elif args["report"]:
reports.generate_report()
elif args["gui"]:
args["copyintemprun"] = True
fea = args["featuresdir"]
ifc = args["ifcfile"]
if fea != "" and ifc != "":
show_widget(fea, ifc, False, args)
elif fea == "" and ifc != "":
show_widget(fea, ifc, True, args)
elif args["copyintemprun"]:
run.run_tests(args)
# TODO merge with else, but do not forget the tmp dir is not known
else:
run.run_tests(args)
if args["report_after_run"]:
reports.generate_report()
print("# All tasks are complete :-)")
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

+5
View File
@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" id="flag-icon-css-de" viewBox="0 0 640 480">
<path fill="#ffce00" d="M0 320h640v160H0z"/>
<path d="M0 0h640v160H0z"/>
<path fill="#d00" d="M0 160h640v160H0z"/>
</svg>

After

Width:  |  Height:  |  Size: 212 B

+7
View File
@@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" id="flag-icon-css-fr" viewBox="0 0 640 480">
<g fill-rule="evenodd" stroke-width="1pt">
<path fill="#fff" d="M0 0h640v480H0z"/>
<path fill="#00267f" d="M0 0h213.3v480H0z"/>
<path fill="#f31830" d="M426.7 0H640v480H426.7z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 291 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

+7
View File
@@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" id="flag-icon-css-gb" viewBox="0 0 640 480">
<path fill="#012169" d="M0 0h640v480H0z"/>
<path fill="#FFF" d="M75 0l244 181L562 0h78v62L400 241l240 178v61h-80L320 301 81 480H0v-60l239-178L0 64V0h75z"/>
<path fill="#C8102E" d="M424 281l216 159v40L369 281h55zm-184 20l6 35L54 480H0l240-179zM640 0v3L391 191l2-44L590 0h50zM0 0l239 176h-60L0 42V0z"/>
<path fill="#FFF" d="M241 0v480h160V0H241zM0 160v160h640V160H0z"/>
<path fill="#C8102E" d="M0 193v96h640v-96H0zM273 0v480h96V0h-96z"/>
</svg>

After

Width:  |  Height:  |  Size: 537 B

+7
View File
@@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" id="flag-icon-css-it" viewBox="0 0 640 480">
<g fill-rule="evenodd" stroke-width="1pt">
<path fill="#fff" d="M0 0h640v480H0z"/>
<path fill="#009246" d="M0 0h213.3v480H0z"/>
<path fill="#ce2b37" d="M426.7 0H640v480H426.7z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 291 B

+5
View File
@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" id="flag-icon-css-nl" viewBox="0 0 640 480">
<path fill="#21468b" d="M0 0h640v480H0z"/>
<path fill="#fff" d="M0 0h640v320H0z"/>
<path fill="#ae1c28" d="M0 0h640v160H0z"/>
</svg>

After

Width:  |  Height:  |  Size: 223 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

+233
View File
@@ -0,0 +1,233 @@
<!doctype html>
<html class="no-js" lang="">
<head>
<meta charset="utf-8">
<title>BIMTester Requirements Generator</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="manifest" href="site.webmanifest">
<link rel="apple-touch-icon" href="icon.png">
<!-- Place favicon.ico in the root directory -->
<link rel="stylesheet" href="normalize.css">
<link rel="stylesheet" href="skeleton.css">
<link rel="stylesheet" href="main.css">
<meta name="theme-color" content="#fafafa">
</head>
<body>
<!--[if IE]>
<p class="browserupgrade">You are using an <strong>outdated</strong> browser. Please <a href="https://browsehappy.com/">upgrade your browser</a> to improve your experience and security.</p>
<![endif]-->
<header{{#has_seamless_header}} class="seamless"{{/has_seamless_header}}>
<h1>
BIMTester Requirements Generator
<span style="font-size: 0.5em; font-style: italic;">
Because your BIM actually sucks but you don't know it yet.
</span>
</h1>
</header>
<div class="row">
<section class="eight columns">
<div class="tcenter">
<img src="images/gb.svg" class="flag active-flag" title="English">
<img src="images/de.svg" class="flag" title="German">
<img src="images/it.svg" class="flag" title="Italian">
<img src="images/fr.svg" class="flag" title="French">
<img src="images/nl.svg" class="flag" title="Dutch">
</div>
<select name="micromvd">
<option>Project setup</option>
</select>
<p class="tcenter">
In order to ensure quality of the digital built environment<br />
As a responsible digital citizen<br />
We expect compliant OpenBIM deliverables<br />
</p>
<div class="tcenter" style="margin-bottom: 20px;">
<h2 style="border-bottom: 0px; display: inline; padding-right: 20px;">
Import
</h2>
<img class="vendor-icon vendor-full" src="images/archicad.png" width="24px">
<img class="vendor-icon vendor-full" src="images/blenderbim.png" width="24px">
<img class="vendor-icon vendor-partial" src="images/freecad.png" width="24px">
<img class="vendor-icon vendor-bad" src="images/revit.png" width="24px">
<img class="vendor-icon vendor-unknown" src="images/tekla.png" width="24px">
<h2 style="border-bottom: 0px; display: inline; padding-right: 20px; padding-left: 20px;">
Export
</h2>
<img class="vendor-icon vendor-partial" src="images/archicad.png" width="24px">
<img class="vendor-icon vendor-full" src="images/blenderbim.png" width="24px">
<img class="vendor-icon vendor-partial" src="images/freecad.png" width="24px">
<img class="vendor-icon vendor-full" src="images/revit.png" width="24px">
<img class="vendor-icon vendor-unknown" src="images/tekla.png" width="24px">
<span style="color: #999; display: block;">Need help meeting these requirements with a vendor? <a href="#">Read more</a></span>
</div>
<h2>
Scenario: Receiving a file
</h2>
<ol>
<li>
<details>
<summary>The data must use the "<code contenteditable>{schema}</code>" schema</summary>
<strong>
Example: <span>The data must use the "IFC4" schema</span>
</strong>
<p>
BIM data may be structured using a particular version of IFC, known as the "schema" version.
Newer versions add lots of capabilities like extra parameters, new data relationships, and
improved data classification. The version you choose will affect what data can be stored,
and which programs have support for reading and writing that version. At the moment, you are
likely to specify either "IFC2X3" or "IFC4".
</p>
<div style="padding-bottom: 20px;">
<div class="onoffswitch">
<input type="checkbox" name="onoffswitch" class="onoffswitch-checkbox" id="myonoffswitch1" tabindex="0" checked>
<label class="onoffswitch-label" for="myonoffswitch1">
<span class="onoffswitch-inner"></span>
<span class="onoffswitch-switch"></span>
</label>
</div>
</div>
</details>
</li>
</ol>
<h2>
Scenario: Exempt files
</h2>
<ol>
<li>
<details>
<summary>The IFC file "<code contenteditable>{file}</code>" is exempt from being provided</summary>
<strong>
Example: <span>The IFC file "PROJECT.ifc" is exempt from being provided</span>
</strong>
<p>
Sometimes, projects may create BIM data that is purely temporary, or as a workaround that is
needed as part of their workflow, but not required from a contractual perspective. In this
case, it is advisable for the requirements to explicitly exclude these BIM files so that the
client knows what is included in the scope of requirements. Simply specify the name of the
file that you know is not required as a deliverable.
</p>
<div style="padding-bottom: 20px;">
<div class="onoffswitch">
<input type="checkbox" name="onoffswitch" class="onoffswitch-checkbox" id="myonoffswitch2" tabindex="0" checked>
<label class="onoffswitch-label" for="myonoffswitch2">
<span class="onoffswitch-inner"></span>
<span class="onoffswitch-switch"></span>
</label>
</div>
</div>
</details>
</li>
<li>
<details>
<summary>
No further requirements are specified because "<code contenteditable>{reason}</code>"
</summary>
<strong>
Example: <span>No further requirements are specified because "it will be superseded in a future project phase"</span>
</strong>
<p>
The recipient may not necessarily wish to specify more BIM requirements to audit. Scenarios
when this is the case may be when the recipient does not have processes in place to use the
BIM data or does not know how to use the BIM data, or when the BIM data is temporary or
irrelevant for any reason. In this scenario, it may be useful to specify the reason why no
further audits will take place. This is the contractual equivalent to "this page is
intentionally left blank".
</p>
<div style="padding-bottom: 20px;">
<div class="onoffswitch">
<input type="checkbox" name="onoffswitch" class="onoffswitch-checkbox" id="myonoffswitch3" tabindex="0" checked>
<label class="onoffswitch-label" for="myonoffswitch3">
<span class="onoffswitch-inner"></span>
<span class="onoffswitch-switch"></span>
</label>
</div>
</div>
</details>
</li>
</ol>
<h2>
Scenario: Project metadata is organised and correct
</h2>
<ol>
<li>The project must have an identifier of "<code contenteditable>{guid}</code>"</li>
<li>The project name, code, or short identifier must be "<code contenteditable>{name}</code>"</li>
<li>The project must have a longer form name of "<code contenteditable>{long_name}</code>"</li>
<li>The project must be described as "<code contenteditable>{description}</code>"</li>
<li>The project must be categorised under "<code contenteditable>{object_type}</code>"</li>
<li>The project must contain information about the "<code contenteditable>{phase}</code>" phase</li>
</ol>
<h2>
Scenario: Project geometry is stored
</h2>
<ol>
<li>The project must contain 3D geometry representing the shape of objects</li>
</ol>
</section>
<aside class="four columns">
<p>
BIMTester is an <strong>open source</strong>, <strong>transparent</strong>,
<strong>standards-based</strong> auditing system for IFC-based projects, using well-established best
practices from the software industry. Requirements can be automatically checked and customised using 100%
free software that puts you in control of your OpenBIM data.
</p>
<h3>
Step 1
</h3>
<p>
Select a requirements template from the dropdown list.
</p>
<h3>
Step 2
</h3>
<p>
Fill out the requirements by editing the highlighted words line by line. Click on a line to toggle whether the requirement is needed for your project.
</p>
<h3>
Step 3
</h3>
<p>
Download the requirements file and include it in contracts.
</p>
<p>
<a href="#" class="button button-primary">Download Requirements</a>
&nbsp;&nbsp;&nbsp;
<a href="#" class="button">Run an automatic Audit</a>
</p>
<h4>
More resources
</h4>
<ul>
<li>Want to write your own requirements? <a href="#">Read more</a>
<li>Are you a poweruser or developer? Learn the ropes. <a href="#">Read more</a>
<li>Need more help? Engage with the OSArch community. <a href="#">Read more</a>
</ul>
</aside>
</div>
<footer>
<p>
BIMTester is built with &hearts; on the awesome <a href="http://www.ifcopenshell.org/">IfcOpenShell</a> project by <a href="https://blenderbim.org/community.html">amazing volunteers</a>. You can be one too!
</p>
</footer>
<!--
<script src="js/vendor/modernizr-3.8.0.min.js"></script>
<script src="https://code.jquery.com/jquery-3.4.1.min.js" integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo=" crossorigin="anonymous"></script>
<script>window.jQuery || document.write('<script src="js/vendor/jquery-3.4.1.min.js"><\/script>')</script>
<script src="js/plugins.js"></script>
<script>
window.ga = function () { ga.q.push(arguments) }; ga.q = []; ga.l = +new Date;
ga('create', 'UA-XXXXX-Y', 'auto'); ga('set','transport','beacon'); ga('send', 'pageview')
</script>
<script src="https://www.google-analytics.com/analytics.js" async></script>
-->
<script src="main.js"></script>
</body>
</html>
+68
View File
@@ -0,0 +1,68 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
sodipodi:docname="info.svg"
inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
id="svg8"
version="1.1"
viewBox="0 0 15 10"
height="10mm"
width="15mm">
<defs
id="defs2" />
<sodipodi:namedview
inkscape:window-maximized="1"
inkscape:window-y="25"
inkscape:window-x="0"
inkscape:window-height="1055"
inkscape:window-width="1920"
showgrid="false"
inkscape:document-rotation="0"
inkscape:current-layer="layer1"
inkscape:document-units="mm"
inkscape:cy="7.4426888"
inkscape:cx="42.12184"
inkscape:zoom="8.6683577"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
borderopacity="1.0"
bordercolor="#666666"
pagecolor="#ffffff"
id="base" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
id="layer1"
inkscape:groupmode="layer"
inkscape:label="Layer 1">
<path
d="M 9.5,5 A 4.5,4.5000019 0 0 1 5,9.5000019 4.5,4.5000019 0 0 1 0.5,5 4.5,4.5000019 0 0 1 5,0.49999809 4.5,4.5000019 0 0 1 9.5,5 Z"
style="fill:none;stroke:#3d3242;stroke-width:0.999996;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;stop-color:#000000"
id="path833" />
<g
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.5833px;line-height:125%;font-family:'Andale Mono';-inkscape-font-specification:'Andale Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#3d3242;fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
id="text837"
aria-label="i">
<path
id="path861"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:Webdings;-inkscape-font-specification:Webdings;fill:#3d3242;fill-opacity:1;stroke-width:0.264583px"
d="m 9.2322863,5.002584 q 0,1.7518255 -1.2402304,2.9920559 Q 6.7518255,9.2297027 5,9.2297027 q -1.7518255,0 -2.9920559,-1.2350628 Q 0.76771367,6.7544095 0.76771367,5.002584 q 0,-1.7518254 1.24023043,-2.9920559 Q 3.2481745,0.77029771 5,0.77029771 q 1.7518255,0 2.9920559,1.24023039 1.2402304,1.2402305 1.2402304,2.9920559 z m -0.692462,0 q 0,-1.467606 -1.0386929,-2.5062989 Q 6.467606,1.4575921 5,1.4575921 q -1.467606,0 -2.506299,1.038693 -1.038693,1.0386929 -1.038693,2.5062989 0,1.467606 1.038693,2.506299 Q 3.532394,8.5424084 5,8.5424084 q 1.467606,0 2.5011314,-1.0335254 Q 8.5398243,6.47019 8.5398243,5.002584 Z M 5.8681613,2.8218455 q 0,0.3358958 -0.2583813,0.5736066 Q 5.3565662,3.6331629 5,3.6331629 q -0.3617339,0 -0.6149476,-0.2377108 -0.2532137,-0.2377108 -0.2532137,-0.5736066 0,-0.3358957 0.2532137,-0.5736065 Q 4.6382661,2.0105281 5,2.0105281 q 0.3565662,0 0.60978,0.2377109 0.2583813,0.2377108 0.2583813,0.5736065 z M 5.7079649,8.1651716 H 4.2868675 V 4.1344227 h 1.4210974 z" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.6 KiB

+271
View File
@@ -0,0 +1,271 @@
@import url('https://fonts.googleapis.com/css2?family=Lato:wght@300;400&display=swap');
* {
scrollbar-width: thin;
scrollbar-color: rgba(.5,.5,.5,.5) rgba(0,0,0,0);
}
/* Works on Chrome/Edge/Safari */
*::-webkit-scrollbar {
width: 5px;
}
*::-webkit-scrollbar-track {
background: rgba(0,0,0,0);
}
*::-webkit-scrollbar-thumb {
background-color: rgba(.5,.5,.5,.5);
border-radius: 20px;
}
.container {
max-width: 1200px;
}
body {
background-color: #e1dcd8;
color: #3d3242;
font-family: 'Lato', sans-serif;
}
header {
background-color: #8daad3;
background-repeat: no-repeat;
background-size: cover;
background-position: center center;
margin-bottom: 40px;
}
footer {
background-color: #c0c0c0;
padding-left: 20px;
line-height: 60px;
}
a {
color: #b85934;
}
a:hover {
color: #e18a42;
}
footer p {
margin: 0px;
}
h1 {
font-size: 1.5em;
margin: 20px;
}
h1, h2, h3, h4 {
letter-spacing: normal;
}
section {
padding: 20px;
background-color: #3d3242;
color: #eee;
}
section select {
width: 100%;
color: #ccc;
}
section h2 {
font-size: 1.2em;
line-height: 50px;
margin: 0px;
font-weight: bold;
border-bottom: 1px solid #756180;
}
section ol {
list-style-type: None;
}
section ol li {
line-height: 40px;
margin: 0px;
background-position: center right;
background-repeat: no-repeat;
border-left: 5px solid #3e7c52;
padding-left: 20px;
}
section ol li.excluded {
border-left: 5px solid #b15a69;
color: #999;
}
section ol li p {
line-height: normal;
}
section ol li:hover {
background-color: #756180;
cursor: pointer;
background-image: url('info.svg');
background-size: 40px;
background-position: right center;
}
section ol li:hover code {
background-color: #3d3242;
}
section ol li code {
background-color: #756180;
border: 2px solid #999;
cursor: auto;
}
section ol li code:focus {
background-color: #b85934;
border: 2px solid #e18a42;
}
section ol li code:hover {
border: 2px solid #e18a42;
}
section p {
background-color: #756180;
border-radius: 10px;
padding: 20px;
}
aside {
margin-left: 0px !important;
padding: 20px;
}
aside h4 {
font-size: 1.2em;
font-weight: bold;
}
input[type="email"], input[type="number"], input[type="search"], input[type="text"], input[type="tel"], input[type="url"], input[type="password"], textarea, select {
border: 2px solid #756180;
border-radius: 10px;
background-color: transparent;
}
input[type="email"]:focus, input[type="number"]:focus, input[type="search"]:focus, input[type="text"]:focus, input[type="tel"]:focus, input[type="url"]:focus, input[type="password"]:focus, textarea:focus, select:focus {
border: 2px solid #e18a42;
}
.tcenter {
text-align: center;
}
.tleft {
text-align: left;
}
.tright {
text-align: right;
}
.fleft {
float: left;
}
.nomargin {
margin: 0px;
}
img.flag {
width: 24px;
padding: 2px;
margin-right: 10px;
margin-bottom: 20px;
margin-top: 10px;
}
img.active-flag {
border: 2px solid #756180;
padding: 0px;
}
.onoffswitch {
position: relative; width: 110px;
-webkit-user-select:none; -moz-user-select:none; -ms-user-select: none;
}
.onoffswitch-checkbox {
position: absolute;
opacity: 0;
pointer-events: none;
}
.onoffswitch-label {
display: block; overflow: hidden; cursor: pointer;
border: 0px solid #999999; border-radius: 17px;
}
.onoffswitch-inner {
display: block; width: 200%; margin-left: -100%;
transition: margin 0.3s ease-in 0s;
}
.onoffswitch-inner:before, .onoffswitch-inner:after {
display: block; float: left; width: 50%; height: 25px; padding: 0; line-height: 25px;
font-size: 11px; color: white; font-family: Trebuchet, Arial, sans-serif; font-weight: bold;
box-sizing: border-box;
}
.onoffswitch-inner:before {
content: "REQUIRED";
padding-left: 10px;
background-color: #3E7C52; color: #FFFFFF;
}
.onoffswitch-inner:after {
content: "EXCLUDED";
padding-right: 10px;
background-color: #B15A69; color: #EEEEEE;
text-align: right;
}
.onoffswitch-switch {
display: block; width: 24px; margin: 0.5px;
background: #FFFFFF;
position: absolute; top: 0; bottom: 0;
right: 86px;
border: 0px solid #999999; border-radius: 17px;
transition: all 0.3s ease-in 0s;
}
.onoffswitch-checkbox:checked + .onoffswitch-label .onoffswitch-inner {
margin-left: 0;
}
.onoffswitch-checkbox:checked + .onoffswitch-label .onoffswitch-switch {
right: 0px;
}
.button-primary {
background-color: #b85934 !important;
border: 0px !important;
}
.button-primary:hover {
background-color: #e18a42 !important;
}
.vendor-icon {
vertical-align: middle;
margin-right: 10px;
margin-top: -1px;
padding-bottom: 5px;
}
.vendor-full {
border-bottom: 5px solid #b6cca1;
}
.vendor-unknown {
border-bottom: 5px solid #555;
}
.vendor-partial {
border-bottom: 5px solid #ffd37f;
}
.vendor-bad {
border-bottom: 5px solid #fbb4a8;
}
+15
View File
@@ -0,0 +1,15 @@
/* Hacky mockup */
let elements = document.getElementsByTagName('input');
for (let i=0; i<elements.length; i++) {
console.log(elements[i]);
elements[i].addEventListener('click', function() {
let input = this;
console.log(input);
let li = input.parentNode.parentNode.parentNode.parentNode;
if (input.checked) {
li.classList.remove('excluded');
} else {
li.classList.add('excluded');
}
});
}
+349
View File
@@ -0,0 +1,349 @@
/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */
/* Document
========================================================================== */
/**
* 1. Correct the line height in all browsers.
* 2. Prevent adjustments of font size after orientation changes in iOS.
*/
html {
line-height: 1.15; /* 1 */
-webkit-text-size-adjust: 100%; /* 2 */
}
/* Sections
========================================================================== */
/**
* Remove the margin in all browsers.
*/
body {
margin: 0;
}
/**
* Render the `main` element consistently in IE.
*/
main {
display: block;
}
/**
* Correct the font size and margin on `h1` elements within `section` and
* `article` contexts in Chrome, Firefox, and Safari.
*/
h1 {
font-size: 2em;
margin: 0.67em 0;
}
/* Grouping content
========================================================================== */
/**
* 1. Add the correct box sizing in Firefox.
* 2. Show the overflow in Edge and IE.
*/
hr {
box-sizing: content-box; /* 1 */
height: 0; /* 1 */
overflow: visible; /* 2 */
}
/**
* 1. Correct the inheritance and scaling of font size in all browsers.
* 2. Correct the odd `em` font sizing in all browsers.
*/
pre {
font-family: monospace, monospace; /* 1 */
font-size: 1em; /* 2 */
}
/* Text-level semantics
========================================================================== */
/**
* Remove the gray background on active links in IE 10.
*/
a {
background-color: transparent;
}
/**
* 1. Remove the bottom border in Chrome 57-
* 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
*/
abbr[title] {
border-bottom: none; /* 1 */
text-decoration: underline; /* 2 */
text-decoration: underline dotted; /* 2 */
}
/**
* Add the correct font weight in Chrome, Edge, and Safari.
*/
b,
strong {
font-weight: bolder;
}
/**
* 1. Correct the inheritance and scaling of font size in all browsers.
* 2. Correct the odd `em` font sizing in all browsers.
*/
code,
kbd,
samp {
font-family: monospace, monospace; /* 1 */
font-size: 1em; /* 2 */
}
/**
* Add the correct font size in all browsers.
*/
small {
font-size: 80%;
}
/**
* Prevent `sub` and `sup` elements from affecting the line height in
* all browsers.
*/
sub,
sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
/* Embedded content
========================================================================== */
/**
* Remove the border on images inside links in IE 10.
*/
img {
border-style: none;
}
/* Forms
========================================================================== */
/**
* 1. Change the font styles in all browsers.
* 2. Remove the margin in Firefox and Safari.
*/
button,
input,
optgroup,
select,
textarea {
font-family: inherit; /* 1 */
font-size: 100%; /* 1 */
line-height: 1.15; /* 1 */
margin: 0; /* 2 */
}
/**
* Show the overflow in IE.
* 1. Show the overflow in Edge.
*/
button,
input { /* 1 */
overflow: visible;
}
/**
* Remove the inheritance of text transform in Edge, Firefox, and IE.
* 1. Remove the inheritance of text transform in Firefox.
*/
button,
select { /* 1 */
text-transform: none;
}
/**
* Correct the inability to style clickable types in iOS and Safari.
*/
button,
[type="button"],
[type="reset"],
[type="submit"] {
-webkit-appearance: button;
}
/**
* Remove the inner border and padding in Firefox.
*/
button::-moz-focus-inner,
[type="button"]::-moz-focus-inner,
[type="reset"]::-moz-focus-inner,
[type="submit"]::-moz-focus-inner {
border-style: none;
padding: 0;
}
/**
* Restore the focus styles unset by the previous rule.
*/
button:-moz-focusring,
[type="button"]:-moz-focusring,
[type="reset"]:-moz-focusring,
[type="submit"]:-moz-focusring {
outline: 1px dotted ButtonText;
}
/**
* Correct the padding in Firefox.
*/
fieldset {
padding: 0.35em 0.75em 0.625em;
}
/**
* 1. Correct the text wrapping in Edge and IE.
* 2. Correct the color inheritance from `fieldset` elements in IE.
* 3. Remove the padding so developers are not caught out when they zero out
* `fieldset` elements in all browsers.
*/
legend {
box-sizing: border-box; /* 1 */
color: inherit; /* 2 */
display: table; /* 1 */
max-width: 100%; /* 1 */
padding: 0; /* 3 */
white-space: normal; /* 1 */
}
/**
* Add the correct vertical alignment in Chrome, Firefox, and Opera.
*/
progress {
vertical-align: baseline;
}
/**
* Remove the default vertical scrollbar in IE 10+.
*/
textarea {
overflow: auto;
}
/**
* 1. Add the correct box sizing in IE 10.
* 2. Remove the padding in IE 10.
*/
[type="checkbox"],
[type="radio"] {
box-sizing: border-box; /* 1 */
padding: 0; /* 2 */
}
/**
* Correct the cursor style of increment and decrement buttons in Chrome.
*/
[type="number"]::-webkit-inner-spin-button,
[type="number"]::-webkit-outer-spin-button {
height: auto;
}
/**
* 1. Correct the odd appearance in Chrome and Safari.
* 2. Correct the outline style in Safari.
*/
[type="search"] {
-webkit-appearance: textfield; /* 1 */
outline-offset: -2px; /* 2 */
}
/**
* Remove the inner padding in Chrome and Safari on macOS.
*/
[type="search"]::-webkit-search-decoration {
-webkit-appearance: none;
}
/**
* 1. Correct the inability to style clickable types in iOS and Safari.
* 2. Change font properties to `inherit` in Safari.
*/
::-webkit-file-upload-button {
-webkit-appearance: button; /* 1 */
font: inherit; /* 2 */
}
/* Interactive
========================================================================== */
/*
* Add the correct display in Edge, IE 10+, and Firefox.
*/
details {
display: block;
}
/*
* Add the correct display in all browsers.
*/
summary {
display: list-item;
}
/* Misc
========================================================================== */
/**
* Add the correct display in IE 10+.
*/
template {
display: none;
}
/**
* Add the correct display in IE 10.
*/
[hidden] {
display: none;
}
+418
View File
@@ -0,0 +1,418 @@
/*
* Skeleton V2.0.4
* Copyright 2014, Dave Gamache
* www.getskeleton.com
* Free to use under the MIT license.
* http://www.opensource.org/licenses/mit-license.php
* 12/29/2014
*/
/* Table of contents
- Grid
- Base Styles
- Typography
- Links
- Buttons
- Forms
- Lists
- Code
- Tables
- Spacing
- Utilities
- Clearing
- Media Queries
*/
/* Grid
*/
.container {
position: relative;
width: 100%;
max-width: 960px;
margin: 0 auto;
padding: 0 20px;
box-sizing: border-box; }
.column,
.columns {
width: 100%;
float: left;
box-sizing: border-box; }
/* For devices larger than 400px */
@media (min-width: 400px) {
.container {
width: 85%;
padding: 0; }
}
/* For devices larger than 550px */
@media (min-width: 550px) {
.container {
width: 80%; }
.column,
.columns {
margin-left: 4%; }
.column:first-child,
.columns:first-child {
margin-left: 0; }
.one.column,
.one.columns { width: 4.66666666667%; }
.two.columns { width: 13.3333333333%; }
.three.columns { width: 22%; }
.four.columns { width: 30.6666666667%; }
.five.columns { width: 39.3333333333%; }
.six.columns { width: 48%; }
.seven.columns { width: 56.6666666667%; }
.eight.columns { width: 65.3333333333%; }
.nine.columns { width: 74.0%; }
.ten.columns { width: 82.6666666667%; }
.eleven.columns { width: 91.3333333333%; }
.twelve.columns { width: 100%; margin-left: 0; }
.one-third.column { width: 30.6666666667%; }
.two-thirds.column { width: 65.3333333333%; }
.one-half.column { width: 48%; }
/* Offsets */
.offset-by-one.column,
.offset-by-one.columns { margin-left: 8.66666666667%; }
.offset-by-two.column,
.offset-by-two.columns { margin-left: 17.3333333333%; }
.offset-by-three.column,
.offset-by-three.columns { margin-left: 26%; }
.offset-by-four.column,
.offset-by-four.columns { margin-left: 34.6666666667%; }
.offset-by-five.column,
.offset-by-five.columns { margin-left: 43.3333333333%; }
.offset-by-six.column,
.offset-by-six.columns { margin-left: 52%; }
.offset-by-seven.column,
.offset-by-seven.columns { margin-left: 60.6666666667%; }
.offset-by-eight.column,
.offset-by-eight.columns { margin-left: 69.3333333333%; }
.offset-by-nine.column,
.offset-by-nine.columns { margin-left: 78.0%; }
.offset-by-ten.column,
.offset-by-ten.columns { margin-left: 86.6666666667%; }
.offset-by-eleven.column,
.offset-by-eleven.columns { margin-left: 95.3333333333%; }
.offset-by-one-third.column,
.offset-by-one-third.columns { margin-left: 34.6666666667%; }
.offset-by-two-thirds.column,
.offset-by-two-thirds.columns { margin-left: 69.3333333333%; }
.offset-by-one-half.column,
.offset-by-one-half.columns { margin-left: 52%; }
}
/* Base Styles
*/
/* NOTE
html is set to 62.5% so that all the REM measurements throughout Skeleton
are based on 10px sizing. So basically 1.5rem = 15px :) */
html {
font-size: 62.5%; }
body {
font-size: 1.5em; /* currently ems cause chrome bug misinterpreting rems on body element */
line-height: 1.6;
font-weight: 400;
font-family: "Raleway", "HelveticaNeue", "Helvetica Neue", Helvetica, Arial, sans-serif;
color: #222; }
/* Typography
*/
h1, h2, h3, h4, h5, h6 {
margin-top: 0;
margin-bottom: 2rem;
font-weight: 300; }
h1 { font-size: 4.0rem; line-height: 1.2; letter-spacing: -.1rem;}
h2 { font-size: 3.6rem; line-height: 1.25; letter-spacing: -.1rem; }
h3 { font-size: 3.0rem; line-height: 1.3; letter-spacing: -.1rem; }
h4 { font-size: 2.4rem; line-height: 1.35; letter-spacing: -.08rem; }
h5 { font-size: 1.8rem; line-height: 1.5; letter-spacing: -.05rem; }
h6 { font-size: 1.5rem; line-height: 1.6; letter-spacing: 0; }
/* Larger than phablet */
@media (min-width: 550px) {
h1 { font-size: 5.0rem; }
h2 { font-size: 4.2rem; }
h3 { font-size: 3.6rem; }
h4 { font-size: 3.0rem; }
h5 { font-size: 2.4rem; }
h6 { font-size: 1.5rem; }
}
p {
margin-top: 0; }
/* Links
*/
a {
color: #1EAEDB; }
a:hover {
color: #0FA0CE; }
/* Buttons
*/
.button,
button,
input[type="submit"],
input[type="reset"],
input[type="button"] {
display: inline-block;
height: 38px;
padding: 0 30px;
color: #555;
text-align: center;
font-size: 11px;
font-weight: 600;
line-height: 38px;
letter-spacing: .1rem;
text-transform: uppercase;
text-decoration: none;
white-space: nowrap;
background-color: transparent;
border-radius: 4px;
border: 1px solid #bbb;
cursor: pointer;
box-sizing: border-box; }
.button:hover,
button:hover,
input[type="submit"]:hover,
input[type="reset"]:hover,
input[type="button"]:hover,
.button:focus,
button:focus,
input[type="submit"]:focus,
input[type="reset"]:focus,
input[type="button"]:focus {
color: #333;
border-color: #888;
outline: 0; }
.button.button-primary,
button.button-primary,
input[type="submit"].button-primary,
input[type="reset"].button-primary,
input[type="button"].button-primary {
color: #FFF;
background-color: #33C3F0;
border-color: #33C3F0; }
.button.button-primary:hover,
button.button-primary:hover,
input[type="submit"].button-primary:hover,
input[type="reset"].button-primary:hover,
input[type="button"].button-primary:hover,
.button.button-primary:focus,
button.button-primary:focus,
input[type="submit"].button-primary:focus,
input[type="reset"].button-primary:focus,
input[type="button"].button-primary:focus {
color: #FFF;
background-color: #1EAEDB;
border-color: #1EAEDB; }
/* Forms
*/
input[type="email"],
input[type="number"],
input[type="search"],
input[type="text"],
input[type="tel"],
input[type="url"],
input[type="password"],
textarea,
select {
height: 38px;
padding: 6px 10px; /* The 6px vertically centers text on FF, ignored by Webkit */
background-color: #fff;
border: 1px solid #D1D1D1;
border-radius: 4px;
box-shadow: none;
box-sizing: border-box; }
/* Removes awkward default styles on some inputs for iOS */
input[type="email"],
input[type="number"],
input[type="search"],
input[type="text"],
input[type="tel"],
input[type="url"],
input[type="password"],
textarea {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none; }
textarea {
min-height: 65px;
padding-top: 6px;
padding-bottom: 6px; }
input[type="email"]:focus,
input[type="number"]:focus,
input[type="search"]:focus,
input[type="text"]:focus,
input[type="tel"]:focus,
input[type="url"]:focus,
input[type="password"]:focus,
textarea:focus,
select:focus {
border: 1px solid #33C3F0;
outline: 0; }
label,
legend {
display: block;
margin-bottom: .5rem;
font-weight: 600; }
fieldset {
padding: 0;
border-width: 0; }
input[type="checkbox"],
input[type="radio"] {
display: inline; }
label > .label-body {
display: inline-block;
margin-left: .5rem;
font-weight: normal; }
/* Lists
*/
ul {
list-style: circle inside; }
ol {
list-style: decimal inside; }
ol, ul {
padding-left: 0;
margin-top: 0; }
ul ul,
ul ol,
ol ol,
ol ul {
margin: 1.5rem 0 1.5rem 3rem;
font-size: 90%; }
li {
margin-bottom: 1rem; }
/* Code
*/
code {
padding: .2rem .5rem;
margin: 0 .2rem;
font-size: 90%;
white-space: nowrap;
background: #F1F1F1;
border: 1px solid #E1E1E1;
border-radius: 4px; }
pre > code {
display: block;
padding: 1rem 1.5rem;
white-space: pre; }
/* Tables
*/
th,
td {
padding: 12px 15px;
text-align: left;
border-bottom: 1px solid #E1E1E1; }
th:first-child,
td:first-child {
padding-left: 0; }
th:last-child,
td:last-child {
padding-right: 0; }
/* Spacing
*/
button,
.button {
margin-bottom: 1rem; }
input,
textarea,
select,
fieldset {
margin-bottom: 1.5rem; }
pre,
blockquote,
dl,
figure,
table,
p,
ul,
ol,
form {
margin-bottom: 2.5rem; }
/* Utilities
*/
.u-full-width {
width: 100%;
box-sizing: border-box; }
.u-max-full-width {
max-width: 100%;
box-sizing: border-box; }
.u-pull-right {
float: right; }
.u-pull-left {
float: left; }
/* Misc
*/
hr {
margin-top: 3rem;
margin-bottom: 3.5rem;
border-width: 0;
border-top: 1px solid #E1E1E1; }
/* Clearing
*/
/* Self Clearing Goodness */
.container:after,
.row:after,
.u-cf {
content: "";
display: table;
clear: both; }
/* Media Queries
*/
/*
Note: The best way to structure the use of media queries is to create the queries
near the relevant code. For example, if you wanted to change the styles for buttons
on small devices, paste the mobile query code up in the buttons section and style it
there.
*/
/* Larger than mobile */
@media (min-width: 400px) {}
/* Larger than phablet (also point when grid becomes active) */
@media (min-width: 550px) {}
/* Larger than tablet */
@media (min-width: 750px) {}
/* Larger than desktop */
@media (min-width: 1000px) {}
/* Larger than Desktop HD */
@media (min-width: 1200px) {}

Some files were not shown because too many files have changed in this diff Show More