merge all
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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']
|
||||
@@ -0,0 +1,10 @@
|
||||
IfcOpenShell
|
||||
============
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 3
|
||||
|
||||
rst_files/library_root
|
||||
|
||||
|
||||
* `ifcopenshell-python documentation <python/html/index.html>`_
|
||||
@@ -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"])
|
||||
@@ -0,0 +1,10 @@
|
||||
IfcOpenShell
|
||||
============
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 3
|
||||
|
||||
rst_files/library_root
|
||||
|
||||
|
||||
* `ifcopenshell-python documentation <python/html/index.html>`_
|
||||
@@ -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"
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -2,6 +2,7 @@ class Project:
|
||||
def __init__(self):
|
||||
self.project_id = ""
|
||||
self.name = ""
|
||||
self.extension_schema = ""
|
||||
|
||||
|
||||
class BimSnippet:
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
xmlschema
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
@@ -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 .
|
||||
@@ -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()
|
||||
@@ -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):
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
class IfcStore:
|
||||
path = ""
|
||||
file = None
|
||||
bookmarks = {}
|
||||
@@ -0,0 +1,15 @@
|
||||
import gettext
|
||||
|
||||
translation = None
|
||||
|
||||
def _(message):
|
||||
if translation:
|
||||
return translation(message)
|
||||
return message
|
||||
|
||||
|
||||
def switch_locale(locale_dir, locale_id="en"):
|
||||
global translation
|
||||
newlang = gettext.translation("messages", localedir=locale_dir, languages=[locale_id])
|
||||
newlang.install()
|
||||
translation = newlang.gettext
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
@@ -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.")
|
||||
@@ -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")
|
||||
@@ -0,0 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from bimtester.guiwidget import run
|
||||
|
||||
|
||||
run()
|
||||
@@ -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)
|
||||
@@ -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_)
|
||||
|
||||
@@ -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 :-)")
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
@@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="flag-icon-css-de" viewBox="0 0 640 480">
|
||||
<path fill="#ffce00" d="M0 320h640v160H0z"/>
|
||||
<path d="M0 0h640v160H0z"/>
|
||||
<path fill="#d00" d="M0 160h640v160H0z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 212 B |
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="flag-icon-css-fr" viewBox="0 0 640 480">
|
||||
<g fill-rule="evenodd" stroke-width="1pt">
|
||||
<path fill="#fff" d="M0 0h640v480H0z"/>
|
||||
<path fill="#00267f" d="M0 0h213.3v480H0z"/>
|
||||
<path fill="#f31830" d="M426.7 0H640v480H426.7z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 291 B |
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="flag-icon-css-gb" viewBox="0 0 640 480">
|
||||
<path fill="#012169" d="M0 0h640v480H0z"/>
|
||||
<path fill="#FFF" d="M75 0l244 181L562 0h78v62L400 241l240 178v61h-80L320 301 81 480H0v-60l239-178L0 64V0h75z"/>
|
||||
<path fill="#C8102E" d="M424 281l216 159v40L369 281h55zm-184 20l6 35L54 480H0l240-179zM640 0v3L391 191l2-44L590 0h50zM0 0l239 176h-60L0 42V0z"/>
|
||||
<path fill="#FFF" d="M241 0v480h160V0H241zM0 160v160h640V160H0z"/>
|
||||
<path fill="#C8102E" d="M0 193v96h640v-96H0zM273 0v480h96V0h-96z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 537 B |
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="flag-icon-css-it" viewBox="0 0 640 480">
|
||||
<g fill-rule="evenodd" stroke-width="1pt">
|
||||
<path fill="#fff" d="M0 0h640v480H0z"/>
|
||||
<path fill="#009246" d="M0 0h213.3v480H0z"/>
|
||||
<path fill="#ce2b37" d="M426.7 0H640v480H426.7z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 291 B |
@@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="flag-icon-css-nl" viewBox="0 0 640 480">
|
||||
<path fill="#21468b" d="M0 0h640v480H0z"/>
|
||||
<path fill="#fff" d="M0 0h640v320H0z"/>
|
||||
<path fill="#ae1c28" d="M0 0h640v160H0z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 223 B |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1,233 @@
|
||||
<!doctype html>
|
||||
<html class="no-js" lang="">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>BIMTester Requirements Generator</title>
|
||||
<meta name="description" content="">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<link rel="manifest" href="site.webmanifest">
|
||||
<link rel="apple-touch-icon" href="icon.png">
|
||||
<!-- Place favicon.ico in the root directory -->
|
||||
|
||||
<link rel="stylesheet" href="normalize.css">
|
||||
<link rel="stylesheet" href="skeleton.css">
|
||||
<link rel="stylesheet" href="main.css">
|
||||
|
||||
<meta name="theme-color" content="#fafafa">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<!--[if IE]>
|
||||
<p class="browserupgrade">You are using an <strong>outdated</strong> browser. Please <a href="https://browsehappy.com/">upgrade your browser</a> to improve your experience and security.</p>
|
||||
<![endif]-->
|
||||
|
||||
<header{{#has_seamless_header}} class="seamless"{{/has_seamless_header}}>
|
||||
<h1>
|
||||
BIMTester Requirements Generator
|
||||
<span style="font-size: 0.5em; font-style: italic;">
|
||||
Because your BIM actually sucks but you don't know it yet.
|
||||
</span>
|
||||
</h1>
|
||||
</header>
|
||||
<div class="row">
|
||||
<section class="eight columns">
|
||||
<div class="tcenter">
|
||||
<img src="images/gb.svg" class="flag active-flag" title="English">
|
||||
<img src="images/de.svg" class="flag" title="German">
|
||||
<img src="images/it.svg" class="flag" title="Italian">
|
||||
<img src="images/fr.svg" class="flag" title="French">
|
||||
<img src="images/nl.svg" class="flag" title="Dutch">
|
||||
</div>
|
||||
<select name="micromvd">
|
||||
<option>Project setup</option>
|
||||
</select>
|
||||
<p class="tcenter">
|
||||
In order to ensure quality of the digital built environment<br />
|
||||
As a responsible digital citizen<br />
|
||||
We expect compliant OpenBIM deliverables<br />
|
||||
</p>
|
||||
<div class="tcenter" style="margin-bottom: 20px;">
|
||||
<h2 style="border-bottom: 0px; display: inline; padding-right: 20px;">
|
||||
Import
|
||||
</h2>
|
||||
<img class="vendor-icon vendor-full" src="images/archicad.png" width="24px">
|
||||
<img class="vendor-icon vendor-full" src="images/blenderbim.png" width="24px">
|
||||
<img class="vendor-icon vendor-partial" src="images/freecad.png" width="24px">
|
||||
<img class="vendor-icon vendor-bad" src="images/revit.png" width="24px">
|
||||
<img class="vendor-icon vendor-unknown" src="images/tekla.png" width="24px">
|
||||
|
||||
<h2 style="border-bottom: 0px; display: inline; padding-right: 20px; padding-left: 20px;">
|
||||
Export
|
||||
</h2>
|
||||
<img class="vendor-icon vendor-partial" src="images/archicad.png" width="24px">
|
||||
<img class="vendor-icon vendor-full" src="images/blenderbim.png" width="24px">
|
||||
<img class="vendor-icon vendor-partial" src="images/freecad.png" width="24px">
|
||||
<img class="vendor-icon vendor-full" src="images/revit.png" width="24px">
|
||||
<img class="vendor-icon vendor-unknown" src="images/tekla.png" width="24px">
|
||||
|
||||
<span style="color: #999; display: block;">Need help meeting these requirements with a vendor? <a href="#">Read more</a></span>
|
||||
</div>
|
||||
<h2>
|
||||
Scenario: Receiving a file
|
||||
</h2>
|
||||
<ol>
|
||||
<li>
|
||||
<details>
|
||||
<summary>The data must use the "<code contenteditable>{schema}</code>" schema</summary>
|
||||
<strong>
|
||||
Example: <span>The data must use the "IFC4" schema</span>
|
||||
</strong>
|
||||
<p>
|
||||
BIM data may be structured using a particular version of IFC, known as the "schema" version.
|
||||
Newer versions add lots of capabilities like extra parameters, new data relationships, and
|
||||
improved data classification. The version you choose will affect what data can be stored,
|
||||
and which programs have support for reading and writing that version. At the moment, you are
|
||||
likely to specify either "IFC2X3" or "IFC4".
|
||||
</p>
|
||||
<div style="padding-bottom: 20px;">
|
||||
<div class="onoffswitch">
|
||||
<input type="checkbox" name="onoffswitch" class="onoffswitch-checkbox" id="myonoffswitch1" tabindex="0" checked>
|
||||
<label class="onoffswitch-label" for="myonoffswitch1">
|
||||
<span class="onoffswitch-inner"></span>
|
||||
<span class="onoffswitch-switch"></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
</li>
|
||||
</ol>
|
||||
<h2>
|
||||
Scenario: Exempt files
|
||||
</h2>
|
||||
<ol>
|
||||
<li>
|
||||
<details>
|
||||
<summary>The IFC file "<code contenteditable>{file}</code>" is exempt from being provided</summary>
|
||||
<strong>
|
||||
Example: <span>The IFC file "PROJECT.ifc" is exempt from being provided</span>
|
||||
</strong>
|
||||
<p>
|
||||
Sometimes, projects may create BIM data that is purely temporary, or as a workaround that is
|
||||
needed as part of their workflow, but not required from a contractual perspective. In this
|
||||
case, it is advisable for the requirements to explicitly exclude these BIM files so that the
|
||||
client knows what is included in the scope of requirements. Simply specify the name of the
|
||||
file that you know is not required as a deliverable.
|
||||
</p>
|
||||
<div style="padding-bottom: 20px;">
|
||||
<div class="onoffswitch">
|
||||
<input type="checkbox" name="onoffswitch" class="onoffswitch-checkbox" id="myonoffswitch2" tabindex="0" checked>
|
||||
<label class="onoffswitch-label" for="myonoffswitch2">
|
||||
<span class="onoffswitch-inner"></span>
|
||||
<span class="onoffswitch-switch"></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
</li>
|
||||
<li>
|
||||
<details>
|
||||
<summary>
|
||||
No further requirements are specified because "<code contenteditable>{reason}</code>"
|
||||
</summary>
|
||||
<strong>
|
||||
Example: <span>No further requirements are specified because "it will be superseded in a future project phase"</span>
|
||||
</strong>
|
||||
<p>
|
||||
The recipient may not necessarily wish to specify more BIM requirements to audit. Scenarios
|
||||
when this is the case may be when the recipient does not have processes in place to use the
|
||||
BIM data or does not know how to use the BIM data, or when the BIM data is temporary or
|
||||
irrelevant for any reason. In this scenario, it may be useful to specify the reason why no
|
||||
further audits will take place. This is the contractual equivalent to "this page is
|
||||
intentionally left blank".
|
||||
</p>
|
||||
<div style="padding-bottom: 20px;">
|
||||
<div class="onoffswitch">
|
||||
<input type="checkbox" name="onoffswitch" class="onoffswitch-checkbox" id="myonoffswitch3" tabindex="0" checked>
|
||||
<label class="onoffswitch-label" for="myonoffswitch3">
|
||||
<span class="onoffswitch-inner"></span>
|
||||
<span class="onoffswitch-switch"></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
</li>
|
||||
</ol>
|
||||
<h2>
|
||||
Scenario: Project metadata is organised and correct
|
||||
</h2>
|
||||
<ol>
|
||||
<li>The project must have an identifier of "<code contenteditable>{guid}</code>"</li>
|
||||
<li>The project name, code, or short identifier must be "<code contenteditable>{name}</code>"</li>
|
||||
<li>The project must have a longer form name of "<code contenteditable>{long_name}</code>"</li>
|
||||
<li>The project must be described as "<code contenteditable>{description}</code>"</li>
|
||||
<li>The project must be categorised under "<code contenteditable>{object_type}</code>"</li>
|
||||
<li>The project must contain information about the "<code contenteditable>{phase}</code>" phase</li>
|
||||
</ol>
|
||||
<h2>
|
||||
Scenario: Project geometry is stored
|
||||
</h2>
|
||||
<ol>
|
||||
<li>The project must contain 3D geometry representing the shape of objects</li>
|
||||
</ol>
|
||||
</section>
|
||||
<aside class="four columns">
|
||||
<p>
|
||||
BIMTester is an <strong>open source</strong>, <strong>transparent</strong>,
|
||||
<strong>standards-based</strong> auditing system for IFC-based projects, using well-established best
|
||||
practices from the software industry. Requirements can be automatically checked and customised using 100%
|
||||
free software that puts you in control of your OpenBIM data.
|
||||
</p>
|
||||
<h3>
|
||||
Step 1
|
||||
</h3>
|
||||
<p>
|
||||
Select a requirements template from the dropdown list.
|
||||
</p>
|
||||
<h3>
|
||||
Step 2
|
||||
</h3>
|
||||
<p>
|
||||
Fill out the requirements by editing the highlighted words line by line. Click on a line to toggle whether the requirement is needed for your project.
|
||||
</p>
|
||||
<h3>
|
||||
Step 3
|
||||
</h3>
|
||||
<p>
|
||||
Download the requirements file and include it in contracts.
|
||||
</p>
|
||||
<p>
|
||||
<a href="#" class="button button-primary">Download Requirements</a>
|
||||
|
||||
<a href="#" class="button">Run an automatic Audit</a>
|
||||
</p>
|
||||
<h4>
|
||||
More resources
|
||||
</h4>
|
||||
<ul>
|
||||
<li>Want to write your own requirements? <a href="#">Read more</a>
|
||||
<li>Are you a poweruser or developer? Learn the ropes. <a href="#">Read more</a>
|
||||
<li>Need more help? Engage with the OSArch community. <a href="#">Read more</a>
|
||||
</ul>
|
||||
</aside>
|
||||
</div>
|
||||
<footer>
|
||||
<p>
|
||||
BIMTester is built with ♥ on the awesome <a href="http://www.ifcopenshell.org/">IfcOpenShell</a> project by <a href="https://blenderbim.org/community.html">amazing volunteers</a>. You can be one too!
|
||||
</p>
|
||||
</footer>
|
||||
<!--
|
||||
<script src="js/vendor/modernizr-3.8.0.min.js"></script>
|
||||
<script src="https://code.jquery.com/jquery-3.4.1.min.js" integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo=" crossorigin="anonymous"></script>
|
||||
<script>window.jQuery || document.write('<script src="js/vendor/jquery-3.4.1.min.js"><\/script>')</script>
|
||||
<script src="js/plugins.js"></script>
|
||||
<script>
|
||||
window.ga = function () { ga.q.push(arguments) }; ga.q = []; ga.l = +new Date;
|
||||
ga('create', 'UA-XXXXX-Y', 'auto'); ga('set','transport','beacon'); ga('send', 'pageview')
|
||||
</script>
|
||||
<script src="https://www.google-analytics.com/analytics.js" async></script>
|
||||
-->
|
||||
<script src="main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,68 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
sodipodi:docname="info.svg"
|
||||
inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
|
||||
id="svg8"
|
||||
version="1.1"
|
||||
viewBox="0 0 15 10"
|
||||
height="10mm"
|
||||
width="15mm">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<sodipodi:namedview
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:window-y="25"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-height="1055"
|
||||
inkscape:window-width="1920"
|
||||
showgrid="false"
|
||||
inkscape:document-rotation="0"
|
||||
inkscape:current-layer="layer1"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:cy="7.4426888"
|
||||
inkscape:cx="42.12184"
|
||||
inkscape:zoom="8.6683577"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
borderopacity="1.0"
|
||||
bordercolor="#666666"
|
||||
pagecolor="#ffffff"
|
||||
id="base" />
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
id="layer1"
|
||||
inkscape:groupmode="layer"
|
||||
inkscape:label="Layer 1">
|
||||
<path
|
||||
d="M 9.5,5 A 4.5,4.5000019 0 0 1 5,9.5000019 4.5,4.5000019 0 0 1 0.5,5 4.5,4.5000019 0 0 1 5,0.49999809 4.5,4.5000019 0 0 1 9.5,5 Z"
|
||||
style="fill:none;stroke:#3d3242;stroke-width:0.999996;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;stop-color:#000000"
|
||||
id="path833" />
|
||||
<g
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.5833px;line-height:125%;font-family:'Andale Mono';-inkscape-font-specification:'Andale Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#3d3242;fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
id="text837"
|
||||
aria-label="i">
|
||||
<path
|
||||
id="path861"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:Webdings;-inkscape-font-specification:Webdings;fill:#3d3242;fill-opacity:1;stroke-width:0.264583px"
|
||||
d="m 9.2322863,5.002584 q 0,1.7518255 -1.2402304,2.9920559 Q 6.7518255,9.2297027 5,9.2297027 q -1.7518255,0 -2.9920559,-1.2350628 Q 0.76771367,6.7544095 0.76771367,5.002584 q 0,-1.7518254 1.24023043,-2.9920559 Q 3.2481745,0.77029771 5,0.77029771 q 1.7518255,0 2.9920559,1.24023039 1.2402304,1.2402305 1.2402304,2.9920559 z m -0.692462,0 q 0,-1.467606 -1.0386929,-2.5062989 Q 6.467606,1.4575921 5,1.4575921 q -1.467606,0 -2.506299,1.038693 -1.038693,1.0386929 -1.038693,2.5062989 0,1.467606 1.038693,2.506299 Q 3.532394,8.5424084 5,8.5424084 q 1.467606,0 2.5011314,-1.0335254 Q 8.5398243,6.47019 8.5398243,5.002584 Z M 5.8681613,2.8218455 q 0,0.3358958 -0.2583813,0.5736066 Q 5.3565662,3.6331629 5,3.6331629 q -0.3617339,0 -0.6149476,-0.2377108 -0.2532137,-0.2377108 -0.2532137,-0.5736066 0,-0.3358957 0.2532137,-0.5736065 Q 4.6382661,2.0105281 5,2.0105281 q 0.3565662,0 0.60978,0.2377109 0.2583813,0.2377108 0.2583813,0.5736065 z M 5.7079649,8.1651716 H 4.2868675 V 4.1344227 h 1.4210974 z" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.6 KiB |
@@ -0,0 +1,271 @@
|
||||
@import url('https://fonts.googleapis.com/css2?family=Lato:wght@300;400&display=swap');
|
||||
|
||||
* {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgba(.5,.5,.5,.5) rgba(0,0,0,0);
|
||||
}
|
||||
|
||||
/* Works on Chrome/Edge/Safari */
|
||||
*::-webkit-scrollbar {
|
||||
width: 5px;
|
||||
}
|
||||
*::-webkit-scrollbar-track {
|
||||
background: rgba(0,0,0,0);
|
||||
}
|
||||
*::-webkit-scrollbar-thumb {
|
||||
background-color: rgba(.5,.5,.5,.5);
|
||||
border-radius: 20px;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: #e1dcd8;
|
||||
color: #3d3242;
|
||||
font-family: 'Lato', sans-serif;
|
||||
}
|
||||
|
||||
header {
|
||||
background-color: #8daad3;
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
background-position: center center;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
footer {
|
||||
background-color: #c0c0c0;
|
||||
padding-left: 20px;
|
||||
line-height: 60px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #b85934;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #e18a42;
|
||||
}
|
||||
|
||||
footer p {
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 1.5em;
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4 {
|
||||
letter-spacing: normal;
|
||||
}
|
||||
|
||||
section {
|
||||
padding: 20px;
|
||||
background-color: #3d3242;
|
||||
color: #eee;
|
||||
}
|
||||
|
||||
section select {
|
||||
width: 100%;
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
section h2 {
|
||||
font-size: 1.2em;
|
||||
line-height: 50px;
|
||||
margin: 0px;
|
||||
font-weight: bold;
|
||||
border-bottom: 1px solid #756180;
|
||||
}
|
||||
|
||||
section ol {
|
||||
list-style-type: None;
|
||||
}
|
||||
|
||||
section ol li {
|
||||
line-height: 40px;
|
||||
margin: 0px;
|
||||
background-position: center right;
|
||||
background-repeat: no-repeat;
|
||||
border-left: 5px solid #3e7c52;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
section ol li.excluded {
|
||||
border-left: 5px solid #b15a69;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
section ol li p {
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
section ol li:hover {
|
||||
background-color: #756180;
|
||||
cursor: pointer;
|
||||
background-image: url('info.svg');
|
||||
background-size: 40px;
|
||||
background-position: right center;
|
||||
}
|
||||
|
||||
section ol li:hover code {
|
||||
background-color: #3d3242;
|
||||
}
|
||||
|
||||
section ol li code {
|
||||
background-color: #756180;
|
||||
border: 2px solid #999;
|
||||
cursor: auto;
|
||||
}
|
||||
|
||||
section ol li code:focus {
|
||||
background-color: #b85934;
|
||||
border: 2px solid #e18a42;
|
||||
}
|
||||
|
||||
section ol li code:hover {
|
||||
border: 2px solid #e18a42;
|
||||
}
|
||||
|
||||
section p {
|
||||
background-color: #756180;
|
||||
border-radius: 10px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
aside {
|
||||
margin-left: 0px !important;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
aside h4 {
|
||||
font-size: 1.2em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
input[type="email"], input[type="number"], input[type="search"], input[type="text"], input[type="tel"], input[type="url"], input[type="password"], textarea, select {
|
||||
border: 2px solid #756180;
|
||||
border-radius: 10px;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
input[type="email"]:focus, input[type="number"]:focus, input[type="search"]:focus, input[type="text"]:focus, input[type="tel"]:focus, input[type="url"]:focus, input[type="password"]:focus, textarea:focus, select:focus {
|
||||
border: 2px solid #e18a42;
|
||||
}
|
||||
|
||||
.tcenter {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.tleft {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.tright {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.fleft {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.nomargin {
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
img.flag {
|
||||
width: 24px;
|
||||
padding: 2px;
|
||||
margin-right: 10px;
|
||||
margin-bottom: 20px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
img.active-flag {
|
||||
border: 2px solid #756180;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
|
||||
.onoffswitch {
|
||||
position: relative; width: 110px;
|
||||
-webkit-user-select:none; -moz-user-select:none; -ms-user-select: none;
|
||||
}
|
||||
.onoffswitch-checkbox {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
.onoffswitch-label {
|
||||
display: block; overflow: hidden; cursor: pointer;
|
||||
border: 0px solid #999999; border-radius: 17px;
|
||||
}
|
||||
.onoffswitch-inner {
|
||||
display: block; width: 200%; margin-left: -100%;
|
||||
transition: margin 0.3s ease-in 0s;
|
||||
}
|
||||
.onoffswitch-inner:before, .onoffswitch-inner:after {
|
||||
display: block; float: left; width: 50%; height: 25px; padding: 0; line-height: 25px;
|
||||
font-size: 11px; color: white; font-family: Trebuchet, Arial, sans-serif; font-weight: bold;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.onoffswitch-inner:before {
|
||||
content: "REQUIRED";
|
||||
padding-left: 10px;
|
||||
background-color: #3E7C52; color: #FFFFFF;
|
||||
}
|
||||
.onoffswitch-inner:after {
|
||||
content: "EXCLUDED";
|
||||
padding-right: 10px;
|
||||
background-color: #B15A69; color: #EEEEEE;
|
||||
text-align: right;
|
||||
}
|
||||
.onoffswitch-switch {
|
||||
display: block; width: 24px; margin: 0.5px;
|
||||
background: #FFFFFF;
|
||||
position: absolute; top: 0; bottom: 0;
|
||||
right: 86px;
|
||||
border: 0px solid #999999; border-radius: 17px;
|
||||
transition: all 0.3s ease-in 0s;
|
||||
}
|
||||
.onoffswitch-checkbox:checked + .onoffswitch-label .onoffswitch-inner {
|
||||
margin-left: 0;
|
||||
}
|
||||
.onoffswitch-checkbox:checked + .onoffswitch-label .onoffswitch-switch {
|
||||
right: 0px;
|
||||
}
|
||||
|
||||
.button-primary {
|
||||
background-color: #b85934 !important;
|
||||
border: 0px !important;
|
||||
}
|
||||
|
||||
.button-primary:hover {
|
||||
background-color: #e18a42 !important;
|
||||
}
|
||||
|
||||
.vendor-icon {
|
||||
vertical-align: middle;
|
||||
margin-right: 10px;
|
||||
margin-top: -1px;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
.vendor-full {
|
||||
border-bottom: 5px solid #b6cca1;
|
||||
}
|
||||
|
||||
.vendor-unknown {
|
||||
border-bottom: 5px solid #555;
|
||||
}
|
||||
|
||||
.vendor-partial {
|
||||
border-bottom: 5px solid #ffd37f;
|
||||
}
|
||||
|
||||
.vendor-bad {
|
||||
border-bottom: 5px solid #fbb4a8;
|
||||
}
|
||||
@@ -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');
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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) {}
|
||||