diff --git a/.github/workflows/ci-bcf-pypi.yml b/.github/workflows/ci-bcf-pypi.yml index ae0a3a8900..03bff9ab75 100644 --- a/.github/workflows/ci-bcf-pypi.yml +++ b/.github/workflows/ci-bcf-pypi.yml @@ -48,10 +48,10 @@ jobs: run: | pip install build cd src/bcf && - python -m build + make dist - name: Publish a Python distribution to PyPI uses: ortega2247/pypi-upload-action@master with: user: __token__ password: ${{ secrets.PYPI_API_TOKEN }} - packages_dir: src/bcf/dist + packages_dir: src/bcf/dist \ No newline at end of file diff --git a/.github/workflows/ci-py-only.yml b/.github/workflows/ci-py-only.yml index 8591d7b20c..6b13686022 100644 --- a/.github/workflows/ci-py-only.yml +++ b/.github/workflows/ci-py-only.yml @@ -88,6 +88,7 @@ jobs: sudo /usr/bin/python -m pip install networkx sudo /usr/bin/python -m pip install tabulate sudo /usr/bin/python -m pip install python-dateutil + sudo /usr/bin/python -m pip install mathutils - name: Test run: | diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e5c4e4c58e..06c247f325 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -114,6 +114,7 @@ jobs: sudo /usr/bin/python -m pip install https://github.com/Andrej730/aud/archive/refs/heads/master-reduced-size.zip sudo /usr/bin/python -m pip install tabulate sudo /usr/bin/python -m pip install python-dateutil + sudo /usr/bin/python -m pip install mathutils - name: Test run: | diff --git a/.gitignore b/.gitignore index 247d590e6f..d89b81bfaa 100644 --- a/.gitignore +++ b/.gitignore @@ -96,5 +96,8 @@ src/ifcopenshell-python/ifcopenshell/ifcopenshell_wrapper.py # apple .DS_Store +# clangd cache +.cache + # Brickschema -src/blenderbim/blenderbim/bim/schema/Brick.ttl \ No newline at end of file +src/blenderbim/blenderbim/bim/schema/Brick.ttl diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt index 39cdf8bf07..0f428738bf 100644 --- a/cmake/CMakeLists.txt +++ b/cmake/CMakeLists.txt @@ -19,7 +19,7 @@ cmake_minimum_required(VERSION 3.1.3) set(CMAKE_CXX_STANDARD 14) -set(CMAKE_CXX_STANDARD_REQUIRED ON) # not necessary, but encouraged +set(CMAKE_CXX_STANDARD_REQUIRED ON) # not necessary, but encouraged project(IfcOpenShell VERSION 0.7.0) @@ -28,50 +28,46 @@ cmake_policy(SET CMP0074 NEW) cmake_policy(SET CMP0078 OLD) cmake_policy(SET CMP0086 NEW) +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE "Release") +endif() + # use extra version to make pre-release using eg semver -set( EXTRA_VERSION "-alpha.3") +set(EXTRA_VERSION "-alpha.3") -foreach(max_year RANGE 2014 2030) -set(max_sdk "$ENV{ADSK_3DSMAX_SDK_${max_year}}") -if (NOT "${max_sdk}" STREQUAL "") -MESSAGE(STATUS "Autodesk 3ds Max SDK found at ${max_sdk}") -set(HAS_MAX TRUE) -endif() -endforeach() -OPTION(MINIMAL_BUILD "The build is to make a minimal version of IFC converter from OCCT into IFC." OFF) -OPTION(COLLADA_SUPPORT "Build IfcConvert with COLLADA support (requires OpenCOLLADA)." ON) -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(BUILD_PACKAGE "" OFF) -OPTION(BUILD_IFCGEOM "Build IfcGeom." ON) -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) -OPTION(HDF5_SUPPORT "Enable HDF5 support (requires HDF5, zlib)" ON) -OPTION(USE_VLD "Use Visual Leak Detector for debugging memory leaks, MSVC-only." OFF) -OPTION(USE_MMAP "Adds a command line options to parse IFC files from memory mapped files using Boost.Iostreams" OFF) -OPTION(BUILD_SHARED_LIBS "Build IfcParse and IfcGeom as shared libs (SO/DLL)." OFF) -OPTION(MSVC_PARALLEL_BUILD "Multi-threaded compilation in Microsoft Visual Studio (/MP)" OFF) -OPTION(NO_WARN "Disable all warnings" OFF) -OPTION(WASM_BUILD OFF) -if (${HAS_MAX}) -OPTION(BUILD_IFCMAX "Build IfcMax, a 3ds Max plug-in, Windows-only." OFF) -endif() -if (${BUILD_CONVERT}) -OPTION(GLTF_SUPPORT "Build IfcConvert with glTF support (requires json.hpp)." OFF) -OPTION(USD_SUPPORT "Build IfcConvert with USD support (requires pixar's USD library)." OFF) -endif() -OPTION(USERSPACE_PYTHON_PREFIX "Installs IfcPython for the current user only instead of system-wide." OFF) -OPTION(ADD_COMMIT_SHA "Add commit sha and branch in version number, warning results in many rebuilds, requires git" OFF) +option(MINIMAL_BUILD "The build is to make a minimal version of IFC converter from OCCT into IFC." OFF) +option(COLLADA_SUPPORT "Build IfcConvert with COLLADA support (requires OpenCOLLADA)." ON) +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(BUILD_PACKAGE "" OFF) +option(BUILD_IFCGEOM "Build IfcGeom." ON) +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) +option(HDF5_SUPPORT "Enable HDF5 support (requires HDF5, zlib)" ON) +option(USE_VLD "Use Visual Leak Detector for debugging memory leaks, MSVC-only." OFF) +option(USE_MMAP "Adds a command line options to parse IFC files from memory mapped files using Boost.Iostreams" OFF) +option(BUILD_SHARED_LIBS "Build IfcParse and IfcGeom as shared libs (SO/DLL)." OFF) +option(MSVC_PARALLEL_BUILD "Multi-threaded compilation in Microsoft Visual Studio (/MP)" OFF) -IF(NOT CMAKE_BUILD_TYPE) - SET(CMAKE_BUILD_TYPE "Release") -ENDIF() +option(NO_WARN "Disable all warnings" OFF) +option(WASM_BUILD OFF) + +if(${BUILD_CONVERT}) + option(GLTF_SUPPORT "Build IfcConvert with glTF support (requires json.hpp)." OFF) + option(USD_SUPPORT "Build IfcConvert with USD support (requires pixar's USD library)." OFF) +endif() + +option(USERSPACE_PYTHON_PREFIX "Installs IfcPython for the current user only instead of system-wide." OFF) +option(ADD_COMMIT_SHA "Add commit sha and branch in version number, warning results in many rebuilds, requires git" OFF) +option(BUILD_IFCMAX "Build IfcMax, a 3ds Max plug-in, Windows-only." OFF) +# QtViewer requires Qt6 +option(BUILD_QTVIEWER "Build IfcOpenShell Qt GUI Viewer" OFF) if(MSVC AND MSVC_PARALLEL_BUILD) - add_definitions("/MP") + add_definitions("/MP") endif() if(NO_WARN) @@ -82,81 +78,90 @@ if(NO_WARN) endif() endif() -# QtViewer requires Qt5 -OPTION(BUILD_QTVIEWER "Build IfcOpenShell Qt GUI Viewer" OFF) include(GNUInstallDirs) -if((BUILD_CONVERT OR BUILD_GEOMSERVER OR BUILD_IFCPYTHON) AND (NOT BUILD_IFCGEOM)) + +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) endif() # Specify where to install files -IF(NOT BINDIR) +if(NOT BINDIR) set(BINDIR bin) -ENDIF() -IF(NOT IS_ABSOLUTE ${BINDIR}) +endif() + +if(NOT IS_ABSOLUTE ${BINDIR}) set(BINDIR ${CMAKE_INSTALL_BINDIR}) -ENDIF() -MESSAGE(STATUS "BINDIR: ${BINDIR}") +endif() -IF(NOT INCLUDEDIR) +message(STATUS "BINDIR: ${BINDIR}") + +if(NOT INCLUDEDIR) set(INCLUDEDIR include) -ENDIF() -IF(NOT IS_ABSOLUTE ${INCLUDEDIR}) - set(INCLUDEDIR ${CMAKE_INSTALL_INCLUDEDIR}) -ENDIF() -MESSAGE(STATUS "INCLUDEDIR: ${INCLUDEDIR}") +endif() -IF(NOT LIBDIR) +if(NOT IS_ABSOLUTE ${INCLUDEDIR}) + set(INCLUDEDIR ${CMAKE_INSTALL_INCLUDEDIR}) +endif() + +message(STATUS "INCLUDEDIR: ${INCLUDEDIR}") + +if(NOT LIBDIR) set(LIBDIR lib) -ENDIF() -IF(NOT IS_ABSOLUTE ${LIBDIR}) +endif() + +if(NOT IS_ABSOLUTE ${LIBDIR}) set(LIBDIR ${CMAKE_INSTALL_LIBDIR}) -ENDIF() -MESSAGE(STATUS "LIBDIR: ${LIBDIR}") +endif() + +message(STATUS "LIBDIR: ${LIBDIR}") set(IFCOPENSHELL_LIBRARY_DIR "") # for *nix rpaths -if (BUILD_SHARED_LIBS) +if(BUILD_SHARED_LIBS) add_definitions(-DIFC_SHARED_BUILD) - if (MSVC) + + if(MSVC) message(WARNING "Building DLLs against the static VC run-time. This is not recommended if the DLLs are to be redistributed.") + # C4521: 'identifier' : class 'type' needs to have dll-interface to be used by clients of class 'type2' # 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_LIBRARY_DIR "${LIBDIR}") endif() # Create cache entries if absent for environment variables -MACRO(UNIFY_ENVVARS_AND_CACHE VAR) - IF ((NOT DEFINED ${VAR}) AND (NOT "$ENV{${VAR}}" STREQUAL "")) - SET(${VAR} "$ENV{${VAR}}" CACHE STRING "${VAR}" FORCE) - ENDIF() -ENDMACRO() +macro(UNIFY_ENVVARS_AND_CACHE VAR) + if((NOT DEFINED ${VAR}) AND(NOT "$ENV{${VAR}}" STREQUAL "")) + set(${VAR} "$ENV{${VAR}}" CACHE STRING "${VAR}" FORCE) + endif() +endmacro() UNIFY_ENVVARS_AND_CACHE(OCC_INCLUDE_DIR) UNIFY_ENVVARS_AND_CACHE(OCC_LIBRARY_DIR) -if (NOT MINIMAL_BUILD) -UNIFY_ENVVARS_AND_CACHE(OPENCOLLADA_INCLUDE_DIR) -UNIFY_ENVVARS_AND_CACHE(OPENCOLLADA_LIBRARY_DIR) -UNIFY_ENVVARS_AND_CACHE(LIBXML2_INCLUDE_DIR) -UNIFY_ENVVARS_AND_CACHE(LIBXML2_LIBRARIES) -UNIFY_ENVVARS_AND_CACHE(PCRE_LIBRARY_DIR) -UNIFY_ENVVARS_AND_CACHE(PYTHON_EXECUTABLE) -UNIFY_ENVVARS_AND_CACHE(HDF5_INCLUDE_DIR) -UNIFY_ENVVARS_AND_CACHE(HDF5_LIBRARY_DIR) -UNIFY_ENVVARS_AND_CACHE(HDF5_LIBRARIES) -UNIFY_ENVVARS_AND_CACHE(CGAL_INCLUDE_DIR) -UNIFY_ENVVARS_AND_CACHE(CGAL_LIBRARY_DIR) -UNIFY_ENVVARS_AND_CACHE(GMP_INCLUDE_DIR) -UNIFY_ENVVARS_AND_CACHE(GMP_LIBRARY_DIR) -UNIFY_ENVVARS_AND_CACHE(MPFR_INCLUDE_DIR) -UNIFY_ENVVARS_AND_CACHE(MPFR_LIBRARY_DIR) -endif() UNIFY_ENVVARS_AND_CACHE(BOOST_ROOT) UNIFY_ENVVARS_AND_CACHE(BOOST_LIBRARYDIR) +if(NOT MINIMAL_BUILD) + UNIFY_ENVVARS_AND_CACHE(OPENCOLLADA_INCLUDE_DIR) + UNIFY_ENVVARS_AND_CACHE(OPENCOLLADA_LIBRARY_DIR) + UNIFY_ENVVARS_AND_CACHE(LIBXML2_INCLUDE_DIR) + UNIFY_ENVVARS_AND_CACHE(LIBXML2_LIBRARIES) + UNIFY_ENVVARS_AND_CACHE(PCRE_LIBRARY_DIR) + UNIFY_ENVVARS_AND_CACHE(PYTHON_EXECUTABLE) + UNIFY_ENVVARS_AND_CACHE(HDF5_INCLUDE_DIR) + UNIFY_ENVVARS_AND_CACHE(HDF5_LIBRARY_DIR) + UNIFY_ENVVARS_AND_CACHE(HDF5_LIBRARIES) + UNIFY_ENVVARS_AND_CACHE(CGAL_INCLUDE_DIR) + UNIFY_ENVVARS_AND_CACHE(CGAL_LIBRARY_DIR) + UNIFY_ENVVARS_AND_CACHE(GMP_INCLUDE_DIR) + UNIFY_ENVVARS_AND_CACHE(GMP_LIBRARY_DIR) + UNIFY_ENVVARS_AND_CACHE(MPFR_INCLUDE_DIR) + UNIFY_ENVVARS_AND_CACHE(MPFR_LIBRARY_DIR) +endif() + if(WASM_BUILD) # when using the nix/build-all.py build script we should not # look into the sysroot for most of the dependencies but rather @@ -165,16 +170,18 @@ if(WASM_BUILD) set(CMAKE_FIND_ROOT_PATH "") endif() -if (NOT MINIMAL_BUILD AND GLTF_SUPPORT AND BUILD_CONVERT) -UNIFY_ENVVARS_AND_CACHE(JSON_INCLUDE_DIR) -FIND_FILE(json_hpp "json.hpp" ${JSON_INCLUDE_DIR}/nlohmann) -IF(json_hpp) - MESSAGE(STATUS "JSON for Modern C++ header file found") -ELSE() - MESSAGE(FATAL_ERROR "Unable to find JSON for Modern C++ header file, aborting") -ENDIF() -add_definitions(-DWITH_GLTF) -set(SWIG_DEFINES ${SWIG_DEFINES} -DWITH_GLTF) +if(NOT MINIMAL_BUILD AND GLTF_SUPPORT AND BUILD_CONVERT) + UNIFY_ENVVARS_AND_CACHE(JSON_INCLUDE_DIR) + find_file(json_hpp "json.hpp" ${JSON_INCLUDE_DIR}/nlohmann) + + if(json_hpp) + message(STATUS "JSON for Modern C++ header file found") + else() + message(FATAL_ERROR "Unable to find JSON for Modern C++ header file, aborting") + endif() + + add_definitions(-DWITH_GLTF) + set(SWIG_DEFINES ${SWIG_DEFINES} -DWITH_GLTF) endif() # Add USD support to serializers @@ -184,10 +191,9 @@ if(NOT MINIMAL_BUILD AND USD_SUPPORT) if("${USD_INCLUDE_DIR}" STREQUAL "") find_path(USD_INCLUDE_DIR pxr.h - [PATHS + PATHS /usr/include/pxr /usr/local/include/pxr - ] REQUIRED ) if(USD_INCLUDE_DIR) @@ -226,42 +232,48 @@ if(NOT MINIMAL_BUILD AND USD_SUPPORT) endif() # Set INSTALL_RPATH for target -MACRO(SET_INSTALL_RPATHS _target _paths) - SET(${_target}_rpaths "") - FOREACH(_path ${_paths}) - LIST(FIND CMAKE_PLATFORM_IMPLICIT_LINK_DIRECTORIES "${_path}" isSystemDir) - IF("${isSystemDir}" STREQUAL "-1") - LIST(APPEND ${_target}_rpaths ${_path}) - ENDIF() - ENDFOREACH() - MESSAGE(STATUS "Set INSTALL_RPATH for ${_target}: ${${_target}_rpaths}") - SET_TARGET_PROPERTIES(${_target} PROPERTIES INSTALL_RPATH "${${_target}_rpaths}") -ENDMACRO() +macro(SET_INSTALL_RPATHS _target _paths) + set(${_target}_rpaths "") + + foreach(_path ${_paths}) + list(FIND CMAKE_PLATFORM_IMPLICIT_LINK_DIRECTORIES "${_path}" isSystemDir) + + if("${isSystemDir}" STREQUAL "-1") + list(APPEND ${_target}_rpaths ${_path}) + endif() + endforeach() + + message(STATUS "Set INSTALL_RPATH for ${_target}: ${${_target}_rpaths}") + set_target_properties(${_target} PROPERTIES INSTALL_RPATH "${${_target}_rpaths}") +endmacro() # Find Boost: On win32 the (hardcoded) default is to use static libraries and # runtime, when doing running conda-build we pick what conda prepared for us. -IF(WIN32 AND ("$ENV{CONDA_BUILD}" STREQUAL "")) - SET(Boost_USE_STATIC_LIBS ON) - SET(Boost_USE_STATIC_RUNTIME OFF) - SET(Boost_USE_MULTITHREADED ON) +if(WIN32 AND("$ENV{CONDA_BUILD}" STREQUAL "")) + set(Boost_USE_STATIC_LIBS ON) + set(Boost_USE_STATIC_RUNTIME OFF) + set(Boost_USE_MULTITHREADED ON) + # Disable Boost's autolinking as the libraries to be linked to are supplied # already by CMake, and wrong libraries would be asked for when code is # compiled with a toolset different from default. if(MSVC) - ADD_DEFINITIONS(-DBOOST_ALL_NO_LIB) + add_definitions(-DBOOST_ALL_NO_LIB) + # Necessary for boost version >= 1.67 - SET(BCRYPT_LIBRARIES "bcrypt.lib") - ENDIF() -ELSE() + set(BCRYPT_LIBRARIES "bcrypt.lib") + endif() +else() # Disable Boost's autolinking as the libraries to be linked to are supplied # already by CMake, and it's going to conflict if there are multiple, as is # the case in conda-forge's libboost feedstock. - ADD_DEFINITIONS(-DBOOST_ALL_NO_LIB) - IF(WIN32) + add_definitions(-DBOOST_ALL_NO_LIB) + + if(WIN32) # Necessary for boost version >= 1.67 - SET(BCRYPT_LIBRARIES "bcrypt.lib") - ENDIF() -ENDIF() + set(BCRYPT_LIBRARIES "bcrypt.lib") + endif() +endif() if (WASM_BUILD) set(BOOST_COMPONENTS) @@ -278,21 +290,22 @@ if(USE_MMAP) else() set(BOOST_COMPONENTS ${BOOST_COMPONENTS} iostreams) endif() + add_definitions(-DUSE_MMAP) endif() -FIND_PACKAGE(Boost REQUIRED COMPONENTS ${BOOST_COMPONENTS}) -MESSAGE(STATUS "Boost include files found in ${Boost_INCLUDE_DIRS}") -MESSAGE(STATUS "Boost libraries found in ${Boost_LIBRARY_DIRS}") +find_package(Boost REQUIRED COMPONENTS ${BOOST_COMPONENTS}) +message(STATUS "Boost include files found in ${Boost_INCLUDE_DIRS}") +message(STATUS "Boost libraries found in ${Boost_LIBRARY_DIRS}") -if (NOT MINIMAL_BUILD) -# libxml2 is required for IFCXML (optional) and SVGFILL (mandatory) - find_package(LibXml2 REQUIRED) +if(NOT MINIMAL_BUILD) + # libxml2 is required for IFCXML (optional) and SVGFILL (mandatory) + find_package(LibXml2 REQUIRED) endif() -if (NOT MINIMAL_BUILD AND IFCXML_SUPPORT) +if(NOT MINIMAL_BUILD AND IFCXML_SUPPORT) add_definitions(-DWITH_IFCXML) - set(SWIG_DEFINES ${SWIG_DEFINES} -DWITH_IFCXML) + set(SWIG_DEFINES ${SWIG_DEFINES} -DWITH_IFCXML) endif() # Usage: @@ -308,10 +321,12 @@ endif() function(add_debug_variants NAME LIBRARIES POSTFIX) set(LIBRARIES_STR "${LIBRARIES}") set(LIBRARIES "") + # the result, "optimized debug ", needs to be a list instead of a string foreach(lib ${LIBRARIES_STR}) list(APPEND LIBRARIES optimized) - if ("${lib}" MATCHES ".lib") + + if("${lib}" MATCHES ".lib") string(REPLACE ".lib" "" lib ${lib}) list(APPEND LIBRARIES ${lib}.lib) else() @@ -319,186 +334,189 @@ function(add_debug_variants NAME LIBRARIES POSTFIX) endif() list(APPEND LIBRARIES debug) - if ("${lib}" MATCHES ".lib") + + if("${lib}" MATCHES ".lib") string(REPLACE ".lib" "" lib ${lib}) list(APPEND LIBRARIES ${lib}${POSTFIX}.lib) else() list(APPEND LIBRARIES ${lib}${POSTFIX}) endif() endforeach() + set(${NAME} ${LIBRARIES} PARENT_SCOPE) endfunction() if(BUILD_IFCGEOM) + if(MSVC) + add_debug_variants(LIBXML2_LIBRARIES "${LIBXML2_LIBRARIES}" d) + endif() -IF(MSVC) - add_debug_variants(LIBXML2_LIBRARIES "${LIBXML2_LIBRARIES}" d) -ENDIF() + # Open CASCADE + if("${OCC_INCLUDE_DIR}" STREQUAL "") + find_path(OCC_INCLUDE_DIR Standard_Version.hxx + PATHS + /usr/include/occt + /usr/include/oce + /usr/include/opencascade + REQUIRED + ) -# Open CASCADE -IF("${OCC_INCLUDE_DIR}" STREQUAL "") - 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() - -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 - - # @todo investigate the exact conditions when this is necessary - TKBin -) - -IF("${OCC_LIBRARY_DIR}" STREQUAL "") - 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}") -ENDIF() - -FIND_LIBRARY(libTKernel NAMES TKernel TKerneld PATHS ${OCC_LIBRARY_DIR} NO_DEFAULT_PATH) -IF(libTKernel) - MESSAGE(STATUS "Required Open Cascade Library files found") -ELSE() - MESSAGE(FATAL_ERROR "Unable to find Open Cascade library files, aborting") -ENDIF() - -# Use the found libTKernel as a template for all other OCC libraries -# TODO Extract this into macro/function -foreach(lib ${OPENCASCADE_LIBRARY_NAMES}) - # Make sure we'll handle the Windows/MSVC debug postfix convention too. - string(REPLACE TKerneld "${lib}" lib_path "${libTKernel}") - string(REPLACE TKernel "${lib}" lib_path "${lib_path}") - list(APPEND OPENCASCADE_LIBRARIES "${lib_path}") -endforeach() - -if(MSVC) - add_definitions(-DHAVE_NO_DLL) - add_debug_variants(OPENCASCADE_LIBRARIES "${OPENCASCADE_LIBRARIES}" d) -endif() -if (WIN32) - # OCC might require linking to Winsock depending on the version and build configuration - list(APPEND OPENCASCADE_LIBRARIES ws2_32.lib) -endif() - -# Make sure cross-referenced symbols between static OCC libraries get -# resolved. Also add thread and rt libraries. -get_filename_component(libTKernelExt ${libTKernel} EXT) -if("${libTKernelExt}" STREQUAL ".a") - set(OCCT_STATIC ON) -endif() - -if(WASM_BUILD) - set(CMAKE_FIND_ROOT_PATH "${CMAKE_FIND_ROOT_PATH_BACKUP}") -endif() - -if(OCCT_STATIC) - find_package(Threads) - - if(WASM_BUILD) - set(OPENCASCADE_LIBRARIES ${OPENCASCADE_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT}) + 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() - # OPENCASCADE_LIBRARIES repeated N times below in order to fix cyclic dependencies - use --start-group ... --end-group instead? - # tfk: --start-group ... --end-group didn't work on the apple linker when last tested - set(OPENCASCADE_LIBRARIES ${OPENCASCADE_LIBRARIES} ${OPENCASCADE_LIBRARIES} ${OPENCASCADE_LIBRARIES} ${OPENCASCADE_LIBRARIES} ${OPENCASCADE_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT}) + 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() - - if (NOT APPLE AND NOT WIN32) - set(OPENCASCADE_LIBRARIES ${OPENCASCADE_LIBRARIES} "rt") + + 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 + + # @todo investigate the exact conditions when this is necessary + TKBin + ) + + if("${OCC_LIBRARY_DIR}" STREQUAL "") + 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}") endif() - if (NOT WIN32) - set(OPENCASCADE_LIBRARIES ${OPENCASCADE_LIBRARIES} "dl") + + find_library(libTKernel NAMES TKernel TKerneld PATHS ${OCC_LIBRARY_DIR} NO_DEFAULT_PATH) + + if(libTKernel) + message(STATUS "Required Open Cascade Library files found") + else() + message(FATAL_ERROR "Unable to find Open Cascade library files, aborting") + endif() + + # Use the found libTKernel as a template for all other OCC libraries + # TODO Extract this into macro/function + foreach(lib ${OPENCASCADE_LIBRARY_NAMES}) + # Make sure we'll handle the Windows/MSVC debug postfix convention too. + string(REPLACE TKerneld "${lib}" lib_path "${libTKernel}") + string(REPLACE TKernel "${lib}" lib_path "${lib_path}") + list(APPEND OPENCASCADE_LIBRARIES "${lib_path}") + endforeach() + + if(MSVC) + add_definitions(-DHAVE_NO_DLL) + add_debug_variants(OPENCASCADE_LIBRARIES "${OPENCASCADE_LIBRARIES}" d) + endif() + + if(WIN32) + # OCC might require linking to Winsock depending on the version and build configuration + list(APPEND OPENCASCADE_LIBRARIES ws2_32.lib) + endif() + + # Make sure cross-referenced symbols between static OCC libraries get + # resolved. Also add thread and rt libraries. + get_filename_component(libTKernelExt ${libTKernel} EXT) + if("${libTKernelExt}" STREQUAL ".a") + set(OCCT_STATIC ON) + endif() + + if(OCCT_STATIC) + find_package(Threads) + + if(WASM_BUILD) + set(OPENCASCADE_LIBRARIES ${OPENCASCADE_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT}) + else() + # OPENCASCADE_LIBRARIES repeated N times below in order to fix cyclic dependencies - use --start-group ... --end-group instead? + # tfk: --start-group ... --end-group didn't work on the apple linker when last tested + set(OPENCASCADE_LIBRARIES ${OPENCASCADE_LIBRARIES} ${OPENCASCADE_LIBRARIES} ${OPENCASCADE_LIBRARIES} ${OPENCASCADE_LIBRARIES} ${OPENCASCADE_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT}) + endif() + + if (NOT APPLE AND NOT WIN32) + set(OPENCASCADE_LIBRARIES ${OPENCASCADE_LIBRARIES} "rt") + endif() + if (NOT WIN32) + set(OPENCASCADE_LIBRARIES ${OPENCASCADE_LIBRARIES} "dl") + endif() endif() -endif() endif(BUILD_IFCGEOM) -IF(NOT MINIMAL_BUILD AND COLLADA_SUPPORT) - # Find OpenCOLLADA - IF("${OPENCOLLADA_INCLUDE_DIR}" STREQUAL "") - MESSAGE(STATUS "No OpenCOLLADA include directory specified") - SET(OPENCOLLADA_INCLUDE_DIR "/usr/include/opencollada" CACHE FILEPATH "OpenCOLLADA header files") - ELSE() - SET(OPENCOLLADA_INCLUDE_DIR "${OPENCOLLADA_INCLUDE_DIR}" CACHE FILEPATH "OpenCOLLADA header files") - ENDIF() +if(NOT MINIMAL_BUILD AND COLLADA_SUPPORT) + # Find OpenCOLLADA + if("${OPENCOLLADA_INCLUDE_DIR}" STREQUAL "") + message(STATUS "No OpenCOLLADA include directory specified") + set(OPENCOLLADA_INCLUDE_DIR "/usr/include/opencollada" CACHE FILEPATH "OpenCOLLADA header files") + else() + set(OPENCOLLADA_INCLUDE_DIR "${OPENCOLLADA_INCLUDE_DIR}" CACHE FILEPATH "OpenCOLLADA header files") + endif() - IF("${OPENCOLLADA_LIBRARY_DIR}" STREQUAL "") - MESSAGE(STATUS "No OpenCOLLADA library directory specified") - FIND_LIBRARY(OPENCOLLADA_FRAMEWORK_LIB NAMES OpenCOLLADAFramework - PATHS /usr/lib64/opencollada /usr/lib/opencollada /usr/lib64 /usr/lib /usr/local/lib64 /usr/local/lib) - GET_FILENAME_COMPONENT(OPENCOLLADA_LIBRARY_DIR ${OPENCOLLADA_FRAMEWORK_LIB} PATH) - ENDIF() + if("${OPENCOLLADA_LIBRARY_DIR}" STREQUAL "") + message(STATUS "No OpenCOLLADA library directory specified") + find_library(OPENCOLLADA_FRAMEWORK_LIB NAMES OpenCOLLADAFramework + PATHS /usr/lib64/opencollada /usr/lib/opencollada /usr/lib64 /usr/lib /usr/local/lib64 /usr/local/lib) + get_filename_component(OPENCOLLADA_LIBRARY_DIR ${OPENCOLLADA_FRAMEWORK_LIB} PATH) + endif() - FIND_LIBRARY(OpenCOLLADAFramework NAMES OpenCOLLADAFramework OpenCOLLADAFrameworkd PATHS ${OPENCOLLADA_LIBRARY_DIR} NO_DEFAULT_PATH) - if (OpenCOLLADAFramework) + find_library(OpenCOLLADAFramework NAMES OpenCOLLADAFramework OpenCOLLADAFrameworkd PATHS ${OPENCOLLADA_LIBRARY_DIR} NO_DEFAULT_PATH) + + if(OpenCOLLADAFramework) message(STATUS "OpenCOLLADA library files found") else() message(FATAL_ERROR "COLLADA_SUPPORT enabled, but unable to find OpenCOLLADA libraries. " "Disable COLLADA_SUPPORT or fix OpenCOLLADA paths to proceed.") endif() - SET(OPENCOLLADA_LIBRARY_DIR "${OPENCOLLADA_LIBRARY_DIR}" CACHE FILEPATH "OpenCOLLADA library files") + set(OPENCOLLADA_LIBRARY_DIR "${OPENCOLLADA_LIBRARY_DIR}" CACHE FILEPATH "OpenCOLLADA library files") - SET(OPENCOLLADA_INCLUDE_DIRS "${OPENCOLLADA_INCLUDE_DIR}/COLLADABaseUtils" "${OPENCOLLADA_INCLUDE_DIR}/COLLADAStreamWriter") + set(OPENCOLLADA_INCLUDE_DIRS "${OPENCOLLADA_INCLUDE_DIR}/COLLADABaseUtils" "${OPENCOLLADA_INCLUDE_DIR}/COLLADAStreamWriter") - FIND_FILE(COLLADASWStreamWriter_h "COLLADASWStreamWriter.h" ${OPENCOLLADA_INCLUDE_DIRS}) - IF(COLLADASWStreamWriter_h) - MESSAGE(STATUS "OpenCOLLADA header files found") - ADD_DEFINITIONS(-DWITH_OPENCOLLADA) - set(SWIG_DEFINES ${SWIG_DEFINES} -DWITH_OPENCOLLADA) + find_file(COLLADASWStreamWriter_h "COLLADASWStreamWriter.h" ${OPENCOLLADA_INCLUDE_DIRS}) - SET(OPENCOLLADA_LIBRARY_NAMES - GeneratedSaxParser MathMLSolver OpenCOLLADABaseUtils OpenCOLLADAFramework OpenCOLLADASaxFrameworkLoader - OpenCOLLADAStreamWriter UTF buffer ftoa - ) + if(COLLADASWStreamWriter_h) + message(STATUS "OpenCOLLADA header files found") + add_definitions(-DWITH_OPENCOLLADA) + set(SWIG_DEFINES ${SWIG_DEFINES} -DWITH_OPENCOLLADA) - # Use the found OpenCOLLADAFramework as a template for all other OpenCOLLADA libraries - foreach(lib ${OPENCOLLADA_LIBRARY_NAMES}) + set(OPENCOLLADA_LIBRARY_NAMES + GeneratedSaxParser MathMLSolver OpenCOLLADABaseUtils OpenCOLLADAFramework OpenCOLLADASaxFrameworkLoader + OpenCOLLADAStreamWriter UTF buffer ftoa + ) + + # Use the found OpenCOLLADAFramework as a template for all other OpenCOLLADA libraries + foreach(lib ${OPENCOLLADA_LIBRARY_NAMES}) # Make sure we'll handle the Windows/MSVC debug postfix convention too. string(REPLACE OpenCOLLADAFrameworkd "${lib}" lib_path "${OpenCOLLADAFramework}") string(REPLACE OpenCOLLADAFramework "${lib}" lib_path "${lib_path}") - list(APPEND OPENCOLLADA_LIBRARIES "${lib_path}") - endforeach() + list(APPEND OPENCOLLADA_LIBRARIES "${lib_path}") + endforeach() - if("${PCRE_LIBRARY_DIR}" STREQUAL "") + if("${PCRE_LIBRARY_DIR}" STREQUAL "") if(WIN32) find_library(pcre_library NAMES pcre pcred PATHS ${OPENCOLLADA_LIBRARY_DIR} NO_DEFAULT_PATH) else() find_library(pcre_library NAMES pcre PATHS ${OPENCOLLADA_LIBRARY_DIR}) endif() - GET_FILENAME_COMPONENT(PCRE_LIBRARY_DIR ${pcre_library} PATH) - else() - find_library(pcre_library NAMES pcre pcred PATHS ${PCRE_LIBRARY_DIR} NO_DEFAULT_PATH) - endif() - if (pcre_library) - SET(OPENCOLLADA_LIBRARY_DIR ${OPENCOLLADA_LIBRARY_DIR} ${PCRE_LIBRARY_DIR}) - if (MSVC) + get_filename_component(PCRE_LIBRARY_DIR ${pcre_library} PATH) + else() + find_library(pcre_library NAMES pcre pcred PATHS ${PCRE_LIBRARY_DIR} NO_DEFAULT_PATH) + endif() + + if(pcre_library) + set(OPENCOLLADA_LIBRARY_DIR ${OPENCOLLADA_LIBRARY_DIR} ${PCRE_LIBRARY_DIR}) + + if(MSVC) # Add release lib regardless whether release or debug found. Debug version will be appended below. list(APPEND OPENCOLLADA_LIBRARIES "${PCRE_LIBRARY_DIR}/pcre.lib") else() @@ -507,61 +525,59 @@ IF(NOT MINIMAL_BUILD AND COLLADA_SUPPORT) else() message(FATAL_ERROR "COLLADA_SUPPORT enabled, but unable to find PCRE. " "Disable COLLADA_SUPPORT or fix PCRE_LIBRARY_DIR path to proceed.") - endif() + endif() - IF(MSVC) - add_debug_variants(OPENCOLLADA_LIBRARIES "${OPENCOLLADA_LIBRARIES}" d) - ENDIF() - ELSE() + if(MSVC) + add_debug_variants(OPENCOLLADA_LIBRARIES "${OPENCOLLADA_LIBRARIES}" d) + endif() + else() message(FATAL_ERROR "COLLADA_SUPPORT enabled, but unable to find OpenCOLLADA headers. " "Disable COLLADA_SUPPORT or fix OpenCOLLADA paths to proceed.") - ENDIF() -ENDIF() + endif() +endif() if(NOT MINIMAL_BUILD AND HDF5_SUPPORT) - IF("${HDF5_INCLUDE_DIR}" STREQUAL "") - MESSAGE(STATUS "No HDF5 include directory specified") - ElSE() - SET(HDF5_INCLUDE_DIR "${HDF5_INCLUDE_DIR}" CACHE FILEPATH "HDF5 header files") - ENDIF() - - IF("${HDF5_LIBRARY_DIR}" STREQUAL "") - MESSAGE(STATUS "No HDF5 library directory specified") - ElSE() - SET(HDF5_LIBRARY_DIR "${HDF5_LIBRARY_DIR}" CACHE FILEPATH "HDF5 library files") - ENDIF() + if("${HDF5_INCLUDE_DIR}" STREQUAL "") + message(STATUS "No HDF5 include directory specified") + else() + set(HDF5_INCLUDE_DIR "${HDF5_INCLUDE_DIR}" CACHE FILEPATH "HDF5 header files") + endif() - if (HDF5_LIBRARY_DIR) - # result of the HDF5 ctest package + if("${HDF5_LIBRARY_DIR}" STREQUAL "") + message(STATUS "No HDF5 library directory specified") + else() + set(HDF5_LIBRARY_DIR "${HDF5_LIBRARY_DIR}" CACHE FILEPATH "HDF5 library files") + endif() + + if(HDF5_LIBRARY_DIR) + # result of the HDF5 ctest package # Find zlib using cmake find_library. How should this be implemented? # FIND_LIBRARY(NAMES z libz libz_debug PATHS ... NO_DEFAULT_PATH) - - - if ("$ENV{CONDA_BUILD}" STREQUAL "") + if("$ENV{CONDA_BUILD}" STREQUAL "") # result of the HDF5 ctest package - - if (WIN32) + if(WIN32) set(zlib_post lib) set(lib_ext lib) else() set(lib_ext a) endif() - if ("${CMAKE_BUILD_TYPE}" STREQUAL "Debug") + if("${CMAKE_BUILD_TYPE}" STREQUAL "Debug") set(debug_postfix "_debug") endif() - SET(HDF5_LIBRARIES - "${HDF5_LIBRARY_DIR}/libhdf5_cpp${debug_postfix}.${lib_ext}" - "${HDF5_LIBRARY_DIR}/libhdf5${debug_postfix}.${lib_ext}" - "${HDF5_LIBRARY_DIR}/libz${zlib_post}${debug_postfix}.${lib_ext}" - "${HDF5_LIBRARY_DIR}/libsz${debug_postfix}.${lib_ext}" - "${HDF5_LIBRARY_DIR}/libaec${debug_postfix}.${lib_ext}" - ) + set(HDF5_LIBRARIES + "${HDF5_LIBRARY_DIR}/libhdf5_cpp${debug_postfix}.${lib_ext}" + "${HDF5_LIBRARY_DIR}/libhdf5${debug_postfix}.${lib_ext}" + "${HDF5_LIBRARY_DIR}/libz${zlib_post}${debug_postfix}.${lib_ext}" + "${HDF5_LIBRARY_DIR}/libsz${debug_postfix}.${lib_ext}" + "${HDF5_LIBRARY_DIR}/libaec${debug_postfix}.${lib_ext}" + ) - ELSE() - MESSAGE(STATUS "Packaging hdf5 and zlib for conda distribution") - if (WIN32) + else() + message(STATUS "Packaging hdf5 and zlib for conda distribution") + + if(WIN32) # Windows set(zlib_post zlib) set(lib_ext lib) @@ -575,19 +591,17 @@ if(NOT MINIMAL_BUILD AND HDF5_SUPPORT) set(lib_ext so) endif() - SET(HDF5_LIBRARIES - "${HDF5_LIBRARY_DIR}/libhdf5_cpp.${lib_ext}" - "${HDF5_LIBRARY_DIR}/libhdf5.${lib_ext}" - "${HDF5_LIBRARY_DIR}/${zlib_post}.${lib_ext}" - ) + set(HDF5_LIBRARIES + "${HDF5_LIBRARY_DIR}/libhdf5_cpp.${lib_ext}" + "${HDF5_LIBRARY_DIR}/libhdf5.${lib_ext}" + "${HDF5_LIBRARY_DIR}/${zlib_post}.${lib_ext}" + ) endif() - endif() - if (NOT HDF5_LIBRARIES) - # debian default - - SET(HDF5_LIBRARIES + if(NOT HDF5_LIBRARIES) + # debian default + set(HDF5_LIBRARIES /usr/lib/x86_64-linux-gnu/hdf5/serial/libhdf5_cpp.so /usr/lib/x86_64-linux-gnu/hdf5/serial/libhdf5.so /usr/lib/x86_64-linux-gnu/libsz.so @@ -595,98 +609,115 @@ if(NOT MINIMAL_BUILD AND HDF5_SUPPORT) z dl ) endif() - + add_definitions(-DWITH_HDF5) - set(SWIG_DEFINES ${SWIG_DEFINES} -DWITH_HDF5) + set(SWIG_DEFINES ${SWIG_DEFINES} -DWITH_HDF5) +endif() + +if(WASM_BUILD) + # reset to use sysroot + set(CMAKE_FIND_ROOT_PATH "${CMAKE_FIND_ROOT_PATH_BACKUP}") endif() if(ENABLE_BUILD_OPTIMIZATIONS) - if(MSVC) + if(MSVC) # NOTE: RelWithDebInfo and Release use O2 (= /Ox /Gl /Gy/ = Og /Oi /Ot /Oy /Ob2 /Gs /GF /Gy) by default, # with the exception with RelWithDebInfo has /Ob1 instead. /Ob2 has been observed to improve the performance # of IfcConvert significantly. # TODO Setting of /GL and /LTCG don't seem to apply for static libraries (IfcGeom, IfcParse) - # C++ - set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /Ob2 /GL") - set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELEASE} /Zi") - # Linker - # /OPT:REF enables also /OPT:ICF and disables INCREMENTAL - set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} /LTCG /OPT:REF") - # /OPT:NOICF is recommended when /DEBUG is used (http://msdn.microsoft.com/en-us/library/xe4t6fc1.aspx) - set(CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} /DEBUG /OPT:NOICF") - set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} /LTCG /OPT:REF") - set(CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO "${CMAKE_EXE_LINKER_FLAGS_RELEASE} /DEBUG /OPT:NOICF") - else() + # C++ + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /Ob2 /GL") + set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELEASE} /Zi") + + # Linker + # /OPT:REF enables also /OPT:ICF and disables INCREMENTAL + set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} /LTCG /OPT:REF") + + # /OPT:NOICF is recommended when /DEBUG is used (http://msdn.microsoft.com/en-us/library/xe4t6fc1.aspx) + set(CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} /DEBUG /OPT:NOICF") + set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} /LTCG /OPT:REF") + set(CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO "${CMAKE_EXE_LINKER_FLAGS_RELEASE} /DEBUG /OPT:NOICF") + else() # GCC-like: Release should use O3 but RelWithDebInfo 02 so enforce 03. Anything other useful that could be added here? set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3") set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELEASE} -O3") - endif() + endif() endif() -IF(MSVC) - # warning due to virtual inheritance - ADD_DEFINITIONS(-wd4250) - # warning due to select definitions in the schema being redundant - ADD_DEFINITIONS(-wd4584) - - # didn't work well on ifcopenbot, @todo make configurable - # add_definitions(/MP) +if(MSVC) + # warning due to virtual inheritance + add_definitions(-wd4250) + + # warning due to select definitions in the schema being redundant + add_definitions(-wd4584) + + # didn't work well on ifcopenbot, @todo make configurable + # add_definitions(/MP) # Enable solution folders (free VS versions prior to 2012 don't support solution folders) - if (MSVC_VERSION GREATER 1600) + if(MSVC_VERSION GREATER 1600) set_property(GLOBAL PROPERTY USE_FOLDERS ON) endif() - IF(USE_VLD) - ADD_DEFINITIONS(-DUSE_VLD) - ENDIF() - # Enforce Unicode for CRT and Win32 API calls - ADD_DEFINITIONS(-D_UNICODE -DUNICODE) - # Disable warnings about unsafe C functions; we could use the safe C99 & C11 versions if we have no need for supporting old compilers. - ADD_DEFINITIONS(-D_SCL_SECURE_NO_WARNINGS -D_CRT_SECURE_NO_WARNINGS) - ADD_DEFINITIONS(-bigobj) # required for building the big ifcXXX.objs, https://msdn.microsoft.com/en-us/library/ms173499.aspx - # Bump up the warning level from the default 3 to 4. - ADD_DEFINITIONS(-W4) - IF(MSVC_VERSION GREATER 1800) # > 2013 - # Disable overeager and false positives causing C4458 ("declaration of 'indentifier' hides class member"), at least for now. - ADD_DEFINITIONS(-wd4458) - ENDIF() + if(USE_VLD) + add_definitions(-DUSE_VLD) + endif() + + # Enforce Unicode for CRT and Win32 API calls + add_definitions(-D_UNICODE -DUNICODE) + + # Disable warnings about unsafe C functions; we could use the safe C99 & C11 versions if we have no need for supporting old compilers. + add_definitions(-D_SCL_SECURE_NO_WARNINGS -D_CRT_SECURE_NO_WARNINGS) + add_definitions(-bigobj) # required for building the big ifcXXX.objs, https://msdn.microsoft.com/en-us/library/ms173499.aspx + + # Bump up the warning level from the default 3 to 4. + add_definitions(-W4) + + if(MSVC_VERSION GREATER 1800) # > 2013 + # Disable overeager and false positives causing C4458 ("declaration of 'indentifier' hides class member"), at least for now. + add_definitions(-wd4458) + endif() + # Enforce standards-conformance on VS > 2015, older Boost versions fail to compile with this - if (MSVC_VERSION GREATER 1900 AND (Boost_MAJOR_VERSION GREATER 1 OR Boost_MINOR_VERSION GREATER 66)) + if(MSVC_VERSION GREATER 1900 AND(Boost_MAJOR_VERSION GREATER 1 OR Boost_MINOR_VERSION GREATER 66)) add_definitions(-permissive-) endif() - # Link against the static VC runtime - # TODO Make this configurable - # IF("$ENV{CONDA_BUILD}" STREQUAL "") - # FOREACH(flag CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE CMAKE_CXX_FLAGS_MINSIZEREL - # CMAKE_CXX_FLAGS_RELWITHDEBINFO CMAKE_C_FLAGS CMAKE_C_FLAGS_DEBUG CMAKE_C_FLAGS_RELEASE - # CMAKE_C_FLAGS_MINSIZEREL CMAKE_C_FLAGS_RELWITHDEBINFO) - # IF(${flag} MATCHES "/MD") - # STRING(REGEX REPLACE "/MD" "/MT" ${flag} "${${flag}}") - # ENDIF() - # IF(${flag} MATCHES "/MDd") - # STRING(REGEX REPLACE "/MDd" "/MTd" ${flag} "${${flag}}") - # ENDIF() - # ENDFOREACH() - # ENDIF() -ElSE() + +# Link against the static VC runtime +# TODO Make this configurable +# IF("$ENV{CONDA_BUILD}" STREQUAL "") +# FOREACH(flag CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE CMAKE_CXX_FLAGS_MINSIZEREL +# CMAKE_CXX_FLAGS_RELWITHDEBINFO CMAKE_C_FLAGS CMAKE_C_FLAGS_DEBUG CMAKE_C_FLAGS_RELEASE +# CMAKE_C_FLAGS_MINSIZEREL CMAKE_C_FLAGS_RELWITHDEBINFO) +# IF(${flag} MATCHES "/MD") +# STRING(REGEX REPLACE "/MD" "/MT" ${flag} "${${flag}}") +# ENDIF() +# IF(${flag} MATCHES "/MDd") +# STRING(REGEX REPLACE "/MDd" "/MTd" ${flag} "${${flag}}") +# ENDIF() +# ENDFOREACH() +# ENDIF() +else() add_definitions(-Wall -Wextra) - if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") + + if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") add_definitions(-Wno-tautological-constant-out-of-range-compare) else() add_definitions(-Wno-maybe-uninitialized) endif() - if (CMAKE_CXX_COMPILER_ID MATCHES "GNU" AND (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 9.0 OR CMAKE_CXX_COMPILER_VERSION VERSION_EQUAL 9.0)) + + if(CMAKE_CXX_COMPILER_ID MATCHES "GNU" AND(CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 9.0 OR CMAKE_CXX_COMPILER_VERSION VERSION_EQUAL 9.0)) # OpenCascade spews a lot of deprecated-copy warnings add_definitions(-Wno-deprecated-copy) endif() + # -fPIC is not relevant on Windows and creates pointless warnings - if (UNIX) + if(UNIX) add_definitions(-fPIC) endif() -ENDIF() +endif() -INCLUDE_DIRECTORIES(${INCLUDE_DIRECTORIES} ${OCC_INCLUDE_DIR} ${OPENCOLLADA_INCLUDE_DIRS} +include_directories(${INCLUDE_DIRECTORIES} ${OCC_INCLUDE_DIR} ${OPENCOLLADA_INCLUDE_DIRS} ${Boost_INCLUDE_DIRS} ${LIBXML2_INCLUDE_DIR} ${JSON_INCLUDE_DIR} ${HDF5_INCLUDE_DIR} ${USD_INCLUDE_DIR} ) @@ -697,7 +728,7 @@ function(files_for_ifc_version IFC_VERSION RESULT_NAME) ${IFC_PARSE_DIR}/Ifc${IFC_VERSION}.h ${IFC_PARSE_DIR}/Ifc${IFC_VERSION}enum.h ${IFC_PARSE_DIR}/Ifc${IFC_VERSION}.cpp - PARENT_SCOPE + PARENT_SCOPE ) endfunction() @@ -710,39 +741,44 @@ if(NOT SCHEMA_VERSIONS) endif() endif() -foreach(s ${SCHEMA_VERSIONS}) - add_definitions(-DHAS_SCHEMA_${s}) +foreach(schema ${SCHEMA_VERSIONS}) + add_definitions(-DHAS_SCHEMA_${schema}) endforeach() string(REPLACE ";" ")(" schema_version_seq "(${SCHEMA_VERSIONS})") -ADD_DEFINITIONS(-DSCHEMA_SEQ=${schema_version_seq}) +add_definitions(-DSCHEMA_SEQ=${schema_version_seq}) if(COMPILE_SCHEMA) - # @todo, this appears to be untested at the moment - + # @todo, this appears to be untested at the moment find_package(PythonInterp) - IF(NOT PYTHONINTERP_FOUND) - MESSAGE(FATAL_ERROR "A Python interpreter is necessary when COMPILE_SCHEMA is enabled. Disable COMPILE_SCHEMA or fix Python paths to proceed.") - ENDIF() + if(NOT PYTHONINTERP_FOUND) + message(FATAL_ERROR "A Python interpreter is necessary when COMPILE_SCHEMA is enabled. Disable COMPILE_SCHEMA or fix Python paths to proceed.") + endif() set(IFC_RELEASE_NOT_USED ${SCHEMA_VERSIONS}) # Install pyparsing if necessary execute_process(COMMAND ${PYTHON_EXECUTABLE} -m pip freeze OUTPUT_VARIABLE PYTHON_PACKAGE_LIST) - if ("${PYTHON_PACKAGE_LIST}" STREQUAL "") + + if("${PYTHON_PACKAGE_LIST}" STREQUAL "") execute_process(COMMAND pip freeze OUTPUT_VARIABLE PYTHON_PACKAGE_LIST) - if ("${PYTHON_PACKAGE_LIST}" STREQUAL "") + + if("${PYTHON_PACKAGE_LIST}" STREQUAL "") message(WARNING "Failed to find pip. Pip is required to automatically install pyparsing") endif() endif() + string(FIND "${PYTHON_PACKAGE_LIST}" pyparsing PYPARSING_FOUND) - if ("${PYPARSING_FOUND}" STREQUAL "-1") + + if("${PYPARSING_FOUND}" STREQUAL "-1") message(STATUS "Installing pyparsing") execute_process(COMMAND ${PYTHON_EXECUTABLE} -m pip "install" --user pyparsing RESULT_VARIABLE SUCCESS) - if (NOT "${SUCCESS}" STREQUAL "0") + + if(NOT "${SUCCESS}" STREQUAL "0") execute_process(COMMAND pip "install" --user pyparsing RESULT_VARIABLE SUCCESS) - if (NOT "${SUCCESS}" STREQUAL "0") + + if(NOT "${SUCCESS}" STREQUAL "0") message(WARNING "Failed to automatically install pyparsing. Please install manually") endif() endif() @@ -757,8 +793,8 @@ if(COMPILE_SCHEMA) OUTPUT_FILE express_parser.py RESULT_VARIABLE SUCCESS) - if (NOT "${SUCCESS}" STREQUAL "0") - MESSAGE(FATAL_ERROR "Failed to bootstrap parser. Make sure pyparsing is installed") + if(NOT "${SUCCESS}" STREQUAL "0") + message(FATAL_ERROR "Failed to bootstrap parser. Make sure pyparsing is installed") endif() # Generate code @@ -767,11 +803,11 @@ if(COMPILE_SCHEMA) OUTPUT_VARIABLE COMPILED_SCHEMA_NAME) # Prevent the schema that had just been compiled from being excluded - foreach(s ${SCHEMA_VERSIONS}) - if("${COMPILED_SCHEMA_NAME}" STREQUAL "${s}") - list(REMOVE_ITEM IFC_RELEASE_NOT_USED "${s}") - endif() - endforeach() + foreach(schema ${SCHEMA_VERSIONS}) + if("${COMPILED_SCHEMA_NAME}" STREQUAL "${schema}") + list(REMOVE_ITEM IFC_RELEASE_NOT_USED "${schema}") + endif() + endforeach() endif() # Boost >= 1.58 requires BOOST_OPTIONAL_USE_OLD_DEFINITION_OF_NONE to build on some Linux distros. @@ -781,8 +817,8 @@ endif() set(IFCOPENSHELL_LIBRARIES IfcParse) if (BUILD_IFCGEOM) - foreach(s ${SCHEMA_VERSIONS}) - set(IFCGEOM_SCHEMA_LIBRARIES ${IFCGEOM_SCHEMA_LIBRARIES} IfcGeom_ifc${s}) + foreach(schema ${SCHEMA_VERSIONS}) + set(IFCGEOM_SCHEMA_LIBRARIES ${IFCGEOM_SCHEMA_LIBRARIES} IfcGeom_ifc${schema}) endforeach() if (WASM_BUILD) set(IFCOPENSHELL_LIBRARIES ${IFCOPENSHELL_LIBRARIES} IfcGeom ${IFCGEOM_SCHEMA_LIBRARIES}) @@ -790,9 +826,10 @@ if (BUILD_IFCGEOM) set(IFCOPENSHELL_LIBRARIES ${IFCOPENSHELL_LIBRARIES} IfcGeom ${IFCGEOM_SCHEMA_LIBRARIES} IfcGeom ${IFCGEOM_SCHEMA_LIBRARIES}) endif() endif() + if (BUILD_CONVERT OR BUILD_IFCPYTHON) - foreach(s ${SCHEMA_VERSIONS}) - set(SERIALIZER_SCHEMA_LIBRARIES ${SERIALIZER_SCHEMA_LIBRARIES} Serializers_ifc${s}) + foreach(schema ${SCHEMA_VERSIONS}) + set(SERIALIZER_SCHEMA_LIBRARIES ${SERIALIZER_SCHEMA_LIBRARIES} Serializers_ifc${schema}) endforeach() set(IFCOPENSHELL_LIBRARIES ${IFCOPENSHELL_LIBRARIES} Serializers ${SERIALIZER_SCHEMA_LIBRARIES}) endif() @@ -801,256 +838,279 @@ endif() file(GLOB IFCPARSE_H_FILES_ALL ../src/ifcparse/*.h) file(GLOB IFCPARSE_CPP_FILES_ALL ../src/ifcparse/*.cpp) -foreach(s ${IFCPARSE_H_FILES_ALL}) -get_filename_component(p "${s}" NAME) -if (NOT "${p}" MATCHES "[0-9]") -list(APPEND IFCPARSE_H_FILES "${s}") -endif() +foreach(file ${IFCPARSE_H_FILES_ALL}) + get_filename_component(filename "${file}" NAME) + + if(NOT "${filename}" MATCHES "[0-9]") + list(APPEND IFCPARSE_H_FILES "${file}") + endif() endforeach() -foreach(s ${IFCPARSE_CPP_FILES_ALL}) -get_filename_component(p "${s}" NAME) -if (NOT "${p}" MATCHES "[0-9]") -list(APPEND IFCPARSE_CPP_FILES "${s}") -endif() +foreach(file ${IFCPARSE_CPP_FILES_ALL}) + get_filename_component(filename "${file}" NAME) + + if(NOT "${filename}" MATCHES "[0-9]") + list(APPEND IFCPARSE_CPP_FILES "${file}") + endif() endforeach() -foreach(s ${SCHEMA_VERSIONS}) - list(APPEND IFCPARSE_H_FILES - ../src/ifcparse/Ifc${s}.h - ../src/ifcparse/Ifc${s}-definitions.h - ) - list(APPEND IFCPARSE_CPP_FILES - ../src/ifcparse/Ifc${s}.cpp - ../src/ifcparse/Ifc${s}-schema.cpp - ) +foreach(schema ${SCHEMA_VERSIONS}) + list(APPEND IFCPARSE_H_FILES + ../src/ifcparse/Ifc${schema}.h + ../src/ifcparse/Ifc${schema}-definitions.h + ) + list(APPEND IFCPARSE_CPP_FILES + ../src/ifcparse/Ifc${schema}.cpp + ../src/ifcparse/Ifc${schema}-schema.cpp + ) endforeach() - set(IFCPARSE_FILES ${IFCPARSE_CPP_FILES} ${IFCPARSE_H_FILES}) add_library(IfcParse ${IFCPARSE_FILES}) -set_target_properties(IfcParse PROPERTIES COMPILE_FLAGS -DIFC_PARSE_EXPORTS VERSION "0.6.0" SOVERSION "0.6") +set_target_properties(IfcParse PROPERTIES COMPILE_FLAGS -DIFC_PARSE_EXPORTS VERSION "${PROJECT_VERSION}" SOVERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}") if (WASM_BUILD) - TARGET_LINK_LIBRARIES(IfcParse ${BCRYPT_LIBRARIES} ${LIBXML2_LIBRARIES}) + target_link_libraries(IfcParse ${BCRYPT_LIBRARIES} ${LIBXML2_LIBRARIES}) else() - TARGET_LINK_LIBRARIES(IfcParse ${Boost_LIBRARIES} ${BCRYPT_LIBRARIES} ${LIBXML2_LIBRARIES}) + target_link_libraries(IfcParse ${Boost_LIBRARIES} ${BCRYPT_LIBRARIES} ${LIBXML2_LIBRARIES}) endif() -if (BUILD_IFCGEOM) -# IfcGeom -file(GLOB IFCGEOM_H_FILES ../src/ifcgeom/*.h ../src/ifcgeom/*.i) -file(GLOB IFCGEOM_CPP_FILES ../src/ifcgeom/*.cpp) -set(IFCGEOM_FILES ${IFCGEOM_CPP_FILES} ${IFCGEOM_H_FILES}) +if(BUILD_IFCGEOM) + # IfcGeom + file(GLOB IFCGEOM_H_FILES ../src/ifcgeom/*.h ../src/ifcgeom/*.i) + file(GLOB IFCGEOM_CPP_FILES ../src/ifcgeom/*.cpp) + set(IFCGEOM_FILES ${IFCGEOM_CPP_FILES} ${IFCGEOM_H_FILES}) -foreach(s ${SCHEMA_VERSIONS}) - add_library(IfcGeom_ifc${s} STATIC ${IFCGEOM_FILES}) - set_target_properties(IfcGeom_ifc${s} PROPERTIES COMPILE_FLAGS "-DIFC_GEOM_EXPORTS -DIfcSchema=Ifc${s}") +foreach(schema ${SCHEMA_VERSIONS}) + add_library(IfcGeom_ifc${schema} STATIC ${IFCGEOM_FILES}) + set_target_properties(IfcGeom_ifc${schema} PROPERTIES COMPILE_FLAGS "-DIFC_GEOM_EXPORTS -DIfcSchema=Ifc${schema}") if (NOT WASM_BUILD) - TARGET_LINK_LIBRARIES(IfcGeom_ifc${s} IfcParse ${OPENCASCADE_LIBRARIES}) + target_link_libraries(IfcGeom_ifc${schema} IfcParse ${OPENCASCADE_LIBRARIES}) endif() endforeach() -# IfcGeom (schema agnostic) -file(GLOB SCHEMA_AGNOSTIC_H_FILES ../src/ifcgeom_schema_agnostic/*.h) -file(GLOB SCHEMA_AGNOSTIC_CPP_FILES ../src/ifcgeom_schema_agnostic/*.cpp) -set(SCHEMA_AGNOSTIC_FILES ${SCHEMA_AGNOSTIC_H_FILES} ${SCHEMA_AGNOSTIC_CPP_FILES}) + # IfcGeom (schema agnostic) + file(GLOB SCHEMA_AGNOSTIC_H_FILES ../src/ifcgeom_schema_agnostic/*.h) + file(GLOB SCHEMA_AGNOSTIC_CPP_FILES ../src/ifcgeom_schema_agnostic/*.cpp) + set(SCHEMA_AGNOSTIC_FILES ${SCHEMA_AGNOSTIC_H_FILES} ${SCHEMA_AGNOSTIC_CPP_FILES}) -add_library(IfcGeom ${SCHEMA_AGNOSTIC_FILES}) -set_target_properties(IfcGeom PROPERTIES COMPILE_FLAGS -DIFC_GEOM_EXPORTS VERSION "0.6.0" SOVERSION "0.6") + add_library(IfcGeom ${SCHEMA_AGNOSTIC_FILES}) + set_target_properties(IfcGeom PROPERTIES COMPILE_FLAGS -DIFC_GEOM_EXPORTS VERSION "${PROJECT_VERSION}" SOVERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}") -if (UNIX) -find_package(Threads) -endif() + if(UNIX) + find_package(Threads) + endif() if (WASM_BUILD) - TARGET_LINK_LIBRARIES(IfcGeom ${IFCGEOM_SCHEMA_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT}) + target_link_libraries(IfcGeom ${IFCGEOM_SCHEMA_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT}) else() - TARGET_LINK_LIBRARIES(IfcGeom IfcParse ${IFCGEOM_SCHEMA_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT}) + target_link_libraries(IfcGeom IfcParse ${IFCGEOM_SCHEMA_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT}) endif() endif(BUILD_IFCGEOM) -if (BUILD_CONVERT OR BUILD_IFCPYTHON) +if(BUILD_CONVERT OR BUILD_IFCPYTHON) + # Serializers + file(GLOB SERIALIZERS_H_FILES ../src/serializers/*.h) + file(GLOB SERIALIZERS_CPP_FILES ../src/serializers/*.cpp) + set(SERIALIZERS_FILES ${SERIALIZERS_H_FILES} ${SERIALIZERS_CPP_FILES}) + file(GLOB SERIALIZERS_S_H_FILES ../src/serializers/schema_dependent/*.h) + file(GLOB SERIALIZERS_S_CPP_FILES ../src/serializers/schema_dependent/*.cpp) + set(SERIALIZERS_S_FILES ${SERIALIZERS_S_H_FILES} ${SERIALIZERS_S_CPP_FILES}) -# Serializers -file(GLOB SERIALIZERS_H_FILES ../src/serializers/*.h) -file(GLOB SERIALIZERS_CPP_FILES ../src/serializers/*.cpp) -set(SERIALIZERS_FILES ${SERIALIZERS_H_FILES} ${SERIALIZERS_CPP_FILES}) -file(GLOB SERIALIZERS_S_H_FILES ../src/serializers/schema_dependent/*.h) -file(GLOB SERIALIZERS_S_CPP_FILES ../src/serializers/schema_dependent/*.cpp) -set(SERIALIZERS_S_FILES ${SERIALIZERS_S_H_FILES} ${SERIALIZERS_S_CPP_FILES}) - -foreach(s ${SCHEMA_VERSIONS}) - add_library(Serializers_ifc${s} STATIC ${SERIALIZERS_S_FILES}) - set_target_properties(Serializers_ifc${s} PROPERTIES COMPILE_FLAGS "-DIFC_GEOM_EXPORTS -DIfcSchema=Ifc${s} ${CONVERT_PRECISION}") +foreach(schema ${SCHEMA_VERSIONS}) + add_library(Serializers_ifc${schema} STATIC ${SERIALIZERS_S_FILES}) + set_target_properties(Serializers_ifc${schema} PROPERTIES COMPILE_FLAGS "-DIFC_GEOM_EXPORTS -DIfcSchema=Ifc${schema}") if (WASM_BUILD) - TARGET_LINK_LIBRARIES(Serializers_ifc${s} ${HDF5_LIBRARIES}) + target_link_libraries(Serializers_ifc${schema} ${HDF5_LIBRARIES}) else() - TARGET_LINK_LIBRARIES(Serializers_ifc${s} IfcGeom ${OPENCASCADE_LIBRARIES} ${HDF5_LIBRARIES}) + target_link_libraries(Serializers_ifc${schema} IfcGeom ${OPENCASCADE_LIBRARIES} ${HDF5_LIBRARIES}) endif() endforeach() -add_library(Serializers ${SERIALIZERS_FILES}) -set_target_properties(Serializers PROPERTIES COMPILE_FLAGS "-DIFC_GEOM_EXPORTS ${CONVERT_PRECISION}" VERSION "0.6.0" SOVERSION "0.6") + add_library(Serializers ${SERIALIZERS_FILES}) + set_target_properties(Serializers PROPERTIES COMPILE_FLAGS "-DIFC_GEOM_EXPORTS" VERSION "${PROJECT_VERSION}" SOVERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}") -TARGET_LINK_LIBRARIES(Serializers ${SERIALIZER_SCHEMA_LIBRARIES} ${OPENCOLLADA_LIBRARIES} ${USD_LIBRARIES}) + target_link_libraries(Serializers ${SERIALIZER_SCHEMA_LIBRARIES} ${OPENCOLLADA_LIBRARIES} ${USD_LIBRARIES}) endif(BUILD_CONVERT OR BUILD_IFCPYTHON) -if (BUILD_CONVERT) +if(BUILD_CONVERT) + # IfcConvert + file(GLOB IFCCONVERT_CPP_FILES ../src/ifcconvert/*.cpp) + file(GLOB IFCCONVERT_H_FILES ../src/ifcconvert/*.h) + set(IFCCONVERT_FILES ${IFCCONVERT_CPP_FILES} ${IFCCONVERT_H_FILES}) + add_executable(IfcConvert ${IFCCONVERT_FILES}) -# IfcConvert -file(GLOB IFCCONVERT_CPP_FILES ../src/ifcconvert/*.cpp) -file(GLOB IFCCONVERT_H_FILES ../src/ifcconvert/*.h) -set(IFCCONVERT_FILES ${IFCCONVERT_CPP_FILES} ${IFCCONVERT_H_FILES}) -ADD_EXECUTABLE(IfcConvert ${IFCCONVERT_FILES}) -set_target_properties(IfcConvert PROPERTIES COMPILE_FLAGS "${CONVERT_PRECISION}") + target_link_libraries(IfcConvert ${IFCOPENSHELL_LIBRARIES} ${OPENCASCADE_LIBRARIES} ${Boost_LIBRARIES} ${HDF5_LIBRARIES} ${USD_LIBRARIES}) -TARGET_LINK_LIBRARIES(IfcConvert ${IFCOPENSHELL_LIBRARIES} ${OPENCASCADE_LIBRARIES} ${Boost_LIBRARIES} ${HDF5_LIBRARIES} ${USD_LIBRARIES}) - -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_LIBRARY_DIR};${OCC_LIBRARY_DIR};${Boost_LIBRARY_DIRS};${OPENCOLLADA_LIBRARY_DIR}") -endif() - -INSTALL(TARGETS IfcConvert - ARCHIVE DESTINATION ${LIBDIR} - LIBRARY DESTINATION ${LIBDIR} - RUNTIME DESTINATION ${BINDIR} -) + 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_LIBRARY_DIR};${OCC_LIBRARY_DIR};${Boost_LIBRARY_DIRS};${OPENCOLLADA_LIBRARY_DIR}") + endif() + install(TARGETS IfcConvert + ARCHIVE DESTINATION ${LIBDIR} + LIBRARY DESTINATION ${LIBDIR} + RUNTIME DESTINATION ${BINDIR} + ) endif(BUILD_CONVERT) # IfcGeomServer if(NOT MINIMAL_BUILD AND BUILD_GEOMSERVER) + file(GLOB CPP_FILES ../src/ifcgeomserver/*.cpp) + file(GLOB H_FILES ../src/ifcgeomserver/*.h) + set(SOURCE_FILES ${CPP_FILES} ${H_FILES}) + add_executable(IfcGeomServer ${SOURCE_FILES}) + target_link_libraries(IfcGeomServer ${IFCOPENSHELL_LIBRARIES} ${OPENCASCADE_LIBRARIES} ${Boost_LIBRARIES}) -file(GLOB CPP_FILES ../src/ifcgeomserver/*.cpp) -file(GLOB H_FILES ../src/ifcgeomserver/*.h) -set(SOURCE_FILES ${CPP_FILES} ${H_FILES}) -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_LIBRARY_DIR};${OCC_LIBRARY_DIR};${Boost_LIBRARY_DIRS}") + endif() -if ((NOT WIN32) AND BUILD_SHARED_LIBS) - SET_INSTALL_RPATHS(IfcGeomServer "${IFCOPENSHELL_LIBRARY_DIR};${OCC_LIBRARY_DIR};${Boost_LIBRARY_DIRS}") + install(TARGETS IfcGeomServer + ARCHIVE DESTINATION ${LIBDIR} + LIBRARY DESTINATION ${LIBDIR} + RUNTIME DESTINATION ${BINDIR} + ) endif() -INSTALL(TARGETS IfcGeomServer - ARCHIVE DESTINATION ${LIBDIR} - LIBRARY DESTINATION ${LIBDIR} - RUNTIME DESTINATION ${BINDIR} -) +if(ADD_COMMIT_SHA) + find_package(Git) -endif() + if(GIT_FOUND) + message("git found: ${GIT_EXECUTABLE} with version ${GIT_VERSION_STRING}") + execute_process( + COMMAND ${GIT_EXECUTABLE} branch -a --contains HEAD + OUTPUT_VARIABLE git_branches + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + string(REPLACE "\n" ";" git_branch_list "${git_branches}") -if (ADD_COMMIT_SHA) -find_package (Git) -if (GIT_FOUND) - message("git found: ${GIT_EXECUTABLE} with version ${GIT_VERSION_STRING}") - execute_process( - COMMAND ${GIT_EXECUTABLE} branch -a --contains HEAD - OUTPUT_VARIABLE git_branches - OUTPUT_STRIP_TRAILING_WHITESPACE - ) - string(REPLACE "\n" ";" git_branch_list "${git_branches}") - foreach(git_branch_candidate IN ITEMS ${git_branch_list}) - string(REPLACE "*" "" git_branch_candidate_temp "${git_branch_candidate}") - string(STRIP "${git_branch_candidate_temp}" git_branch_candidate_2) - if (NOT git_branch_candidate_2 MATCHES "^HEAD$") - string(REPLACE "/" ";" git_branch_candidate_2_list "${git_branch_candidate_2}") - list(GET git_branch_candidate_2_list -1 git_branch) - endif() - endforeach() - execute_process( - COMMAND ${GIT_EXECUTABLE} rev-parse --short HEAD - OUTPUT_VARIABLE git_sha - OUTPUT_STRIP_TRAILING_WHITESPACE - ) - message(STATUS "IfcOpenShell branch: \"${git_branch}\"") - message(STATUS "IfcOpenShell commit: \"${git_sha}\"") - add_definitions(-DIFCOPENSHELL_BRANCH=${git_branch}) - add_definitions(-DIFCOPENSHELL_COMMIT=${git_sha}) -endif() + foreach(git_branch_candidate IN ITEMS ${git_branch_list}) + string(REPLACE "*" "" git_branch_candidate_temp "${git_branch_candidate}") + string(STRIP "${git_branch_candidate_temp}" git_branch_candidate_2) + if (NOT git_branch_candidate_2 MATCHES "^HEAD$") + string(REPLACE "/" ";" git_branch_candidate_2_list "${git_branch_candidate_2}") + list(GET git_branch_candidate_2_list -1 git_branch) + endif() + endforeach() + + execute_process( + COMMAND ${GIT_EXECUTABLE} rev-parse --short HEAD + OUTPUT_VARIABLE git_sha + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + message(STATUS "IfcOpenShell branch: \"${git_branch}\"") + message(STATUS "IfcOpenShell commit: \"${git_sha}\"") + add_definitions(-DIFCOPENSHELL_BRANCH=${git_branch}) + add_definitions(-DIFCOPENSHELL_COMMIT=${git_sha}) + endif() endif() # Documentation -IF(BUILD_DOCUMENTATION) - set(CMAKE_MODULE_PATH "../docs/cmake") - ADD_SUBDIRECTORY(../docs docs) -ENDIF() +if(BUILD_DOCUMENTATION) + set(CMAKE_MODULE_PATH "../docs/cmake") + add_subdirectory(../docs docs) +endif() -IF(NOT MINIMAL_BUILD AND BUILD_IFCPYTHON) - ADD_SUBDIRECTORY(../src/ifcwrap ifcwrap) -ENDIF() +if(NOT MINIMAL_BUILD AND BUILD_IFCPYTHON) + add_subdirectory(../src/ifcwrap ifcwrap) +endif() -IF(BUILD_EXAMPLES) - ADD_SUBDIRECTORY(../src/examples examples) -ENDIF() +if(BUILD_EXAMPLES) + add_subdirectory(../src/examples examples) +endif() -IF(NOT MINIMAL_BUILD AND BUILD_IFCMAX) - ADD_SUBDIRECTORY(../src/ifcmax ifcmax) -ENDIF() -if (NOT MINIMAL_BUILD) -ADD_SUBDIRECTORY(../src/svgfill svgfill) +if(NOT MINIMAL_BUILD AND BUILD_IFCMAX) + foreach(max_year RANGE 2014 2030) + set(max_sdk "$ENV{ADSK_3DSMAX_SDK_${max_year}}") + + if(NOT "${max_sdk}" STREQUAL "") + message(STATUS "Autodesk 3ds Max SDK found at ${max_sdk}") + set(HAS_MAX TRUE) + endif() + endforeach() + + if(HAS_MAX) + add_subdirectory(../src/ifcmax ifcmax) + else() + message(STATUS "Autodesk 3ds Max SDK not found, is required to build IFCMax.") + endif() +endif() + +if(NOT MINIMAL_BUILD) + add_subdirectory(../src/svgfill svgfill) +endif() + +if(BUILD_QTVIEWER) + add_subdirectory(../src/qtviewer qtviewer) endif() # CMake installation targets -INSTALL(FILES ${IFCPARSE_H_FILES} +install(FILES ${IFCPARSE_H_FILES} DESTINATION ${INCLUDEDIR}/ifcparse ) -INSTALL(TARGETS IfcParse +install(TARGETS IfcParse ARCHIVE DESTINATION ${LIBDIR} LIBRARY DESTINATION ${LIBDIR} RUNTIME DESTINATION ${BINDIR} ) if(BUILD_IFCGEOM) -INSTALL(FILES ${IFCGEOM_H_FILES} - DESTINATION ${INCLUDEDIR}/ifcgeom -) + install(FILES ${IFCGEOM_H_FILES} + DESTINATION ${INCLUDEDIR}/ifcgeom + ) -INSTALL(FILES ${SCHEMA_AGNOSTIC_H_FILES} - DESTINATION ${INCLUDEDIR}/ifcgeom_schema_agnostic -) + install(FILES ${SCHEMA_AGNOSTIC_H_FILES} + DESTINATION ${INCLUDEDIR}/ifcgeom_schema_agnostic + ) -INSTALL(TARGETS ${IFCGEOM_SCHEMA_LIBRARIES} IfcGeom - ARCHIVE DESTINATION ${LIBDIR} - LIBRARY DESTINATION ${LIBDIR} - RUNTIME DESTINATION ${BINDIR} -) + install(TARGETS ${IFCGEOM_SCHEMA_LIBRARIES} IfcGeom + ARCHIVE DESTINATION ${LIBDIR} + LIBRARY DESTINATION ${LIBDIR} + RUNTIME DESTINATION ${BINDIR} + ) endif() if(BUILD_CONVERT) -INSTALL(TARGETS Serializers ${SERIALIZER_SCHEMA_LIBRARIES} - ARCHIVE DESTINATION ${LIBDIR} - LIBRARY DESTINATION ${LIBDIR} - RUNTIME DESTINATION ${BINDIR} -) + install(TARGETS Serializers ${SERIALIZER_SCHEMA_LIBRARIES} + ARCHIVE DESTINATION ${LIBDIR} + LIBRARY DESTINATION ${LIBDIR} + RUNTIME DESTINATION ${BINDIR} + ) -INSTALL(FILES ${SERIALIZERS_FILES} - DESTINATION ${INCLUDEDIR}/serializers/ -) + install(FILES ${SERIALIZERS_FILES} + DESTINATION ${INCLUDEDIR}/serializers/ + ) -INSTALL(FILES ${SERIALIZERS_S_FILES} - DESTINATION ${INCLUDEDIR}/serializers/schema_dependent -) + install(FILES ${SERIALIZERS_S_FILES} + DESTINATION ${INCLUDEDIR}/serializers/schema_dependent + ) endif() -IF(BUILD_QTVIEWER) - ADD_SUBDIRECTORY(../src/qtviewer qtviewer) +# Cmake uninstall target +if(NOT TARGET uninstall) + configure_file( + "${CMAKE_CURRENT_SOURCE_DIR}/cmake_uninstall.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake" + IMMEDIATE @ONLY) + + add_custom_target(uninstall + COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake) endif() +# Packaging list(APPEND CPACK_SOURCE_IGNORE_FILES - .git - .gitignore - ) - + .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}") @@ -1063,11 +1123,10 @@ 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}) +foreach(COMPONENT IN ITEMS ${BOOST_COMPONENTS}) string(REPLACE "_" "-" COMP ${COMPONENT}) set(BOOST_DEPS "${BOOST_DEPS}, libboost-${COMP}-dev") endforeach(COMPONENT) @@ -1081,6 +1140,6 @@ 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") +# set(CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA "${CMAKE_SOURCE_DIR}/cmake/debian/postinst") include(CPack) diff --git a/cmake/cmake_uninstall.cmake.in b/cmake/cmake_uninstall.cmake.in new file mode 100644 index 0000000000..04da07edd6 --- /dev/null +++ b/cmake/cmake_uninstall.cmake.in @@ -0,0 +1,43 @@ +################################################################################ +# # +# This file is part of IfcOpenShell. # +# # +# IfcOpenShell is free software: you can redistribute it and/or modify # +# it under the terms of the Lesser GNU General Public License as published by # +# the Free Software Foundation, either version 3.0 of the License, or # +# (at your option) any later version. # +# # +# IfcOpenShell is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# Lesser GNU General Public License for more details. # +# # +# You should have received a copy of the Lesser GNU General Public License # +# along with this program. If not, see . # +# # +################################################################################ + +if(NOT EXISTS "@CMAKE_BINARY_DIR@/install_manifest.txt") + message(FATAL_ERROR "Cannot find install manifest: @CMAKE_BINARY_DIR@/install_manifest.txt") +endif() + +file(READ "@CMAKE_BINARY_DIR@/install_manifest.txt" files) +string(REGEX REPLACE "\n" ";" files "${files}") + +foreach(file ${files}) + message(STATUS "Uninstalling $ENV{DESTDIR}${file}") + + if(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}") + exec_program( + "@CMAKE_COMMAND@" ARGS "-E remove \"$ENV{DESTDIR}${file}\"" + OUTPUT_VARIABLE rm_out + RETURN_VALUE rm_retval + ) + + if(NOT "${rm_retval}" STREQUAL 0) + message(FATAL_ERROR "Problem when removing $ENV{DESTDIR}${file}") + endif() + else(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}") + message(STATUS "File $ENV{DESTDIR}${file} does not exist.") + endif() +endforeach() diff --git a/src/bcf/Makefile b/src/bcf/Makefile index 3a3913b971..e310fd08c1 100644 --- a/src/bcf/Makefile +++ b/src/bcf/Makefile @@ -1,3 +1,9 @@ +VERSION:=`date '+%y%m%d'` +SED:=sed -i +ifeq ($(UNAME_S),Darwin) +SED:=sed -i '' -e +endif + .PHONY: ci ci: tox @@ -12,6 +18,13 @@ models: cd src && xsdata generate -p bcf.v2.model --unnest-classes --kw-only --slots -ds Google bcf/v2/xsd cd src && xsdata generate -p bcf.v3.model --unnest-classes --kw-only --slots -ds Google bcf/v3/xsd +.PHONY: dist +dist: + rm -rf dist + $(SED) "s/999999/$(VERSION)/" pyproject.toml + python -m build + $(SED) "s/$(VERSION)/999999/" pyproject.toml + # .PHONY # api: -# openapi-python-client generate --url https://api.swaggerhub.com/apis/buildingSMART/BCF/3.0 +# openapi-python-client generate --url https://api.swaggerhub.com/apis/buildingSMART/BCF/3.0 \ No newline at end of file diff --git a/src/bcf/pyproject.toml b/src/bcf/pyproject.toml index 7dc6d22176..4d7f1b401b 100644 --- a/src/bcf/pyproject.toml +++ b/src/bcf/pyproject.toml @@ -17,7 +17,7 @@ dependencies = [ "numpy", "ifcopenshell", ] -version = "0.0.1" +version = "0.0.999999" classifiers = [ "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", "Operating System :: OS Independent", @@ -76,7 +76,7 @@ deps = black isort pylint -commands = +commands = black {posargs:.} isort {posargs:.} pylint {posargs:.} --output-format=colorized @@ -138,4 +138,4 @@ max-attributes = 10 [tool.pylint.format] expected-line-ending-format = "LF" -max-line-length = 120 +max-line-length = 120 \ No newline at end of file diff --git a/src/blenderbim/Makefile b/src/blenderbim/Makefile index ac0e7b3c88..736a2277a1 100644 --- a/src/blenderbim/Makefile +++ b/src/blenderbim/Makefile @@ -57,7 +57,6 @@ ifeq ($(PYVERSION), py39) HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/2.3.4/download/linux-64/hpp-fcl-2.3.4-py39h40a70d0_0.conda EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/3.1.0/download/linux-64/eigenpy-3.1.0-py39hdfdd6bb_0.conda BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/linux-64/boost-1.78.0-py39h7c9e3ff_4.tar.bz2 -QHULL_URL:=https://anaconda.org/conda-forge/qhull/2020.2/download/linux-64/qhull-2020.2-h4bd325d_2.tar.bz2 LXML_URL:=https://files.pythonhosted.org/packages/19/d9/a69c6aff5673554df48120565a14a50eaa41d29ae03b02faa0b023666318/lxml-4.6.3-cp39-cp39-manylinux2014_x86_64.whl SHAPELY_URL:=https://files.pythonhosted.org/packages/2d/f2/8ec281d357e8bb7d08dc8d727f0e4c8ef3dae7d3fa75c69c8e452bb82d50/shapely-2.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl PILLOW_URL:=https://files.pythonhosted.org/packages/01/61/3ff85fb4bb596ce3d223c8fcf93c8df5c12bc8899dfb4fb3cb1c5b20dd5f/Pillow-9.2.0-cp39-cp39-manylinux_2_28_x86_64.whl @@ -66,12 +65,12 @@ ifeq ($(PYVERSION), py310) HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/2.3.4/download/linux-64/hpp-fcl-2.3.4-py310h995690b_0.conda EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/3.1.0/download/linux-64/eigenpy-3.1.0-py310hf02b7e0_0.conda BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/linux-64/boost-1.78.0-py310hc4a4660_4.tar.bz2 -QHULL_URL:=https://anaconda.org/conda-forge/qhull/2020.2/download/linux-64/qhull-2020.2-h4bd325d_2.tar.bz2 LXML_URL:=https://files.pythonhosted.org/packages/25/1e/19b46d8e8881fe0df2e20945d51919eeb1817836d62a90efa8506530e45c/lxml-4.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl SHAPELY_URL:=https://files.pythonhosted.org/packages/a8/a5/403728b5614b28083f6424dfdefec5fcf58068495fb03bb08532671c642f/shapely-2.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl PILLOW_URL:=https://files.pythonhosted.org/packages/f6/51/320986ebd6d46a0e95c2240468ced73153b691ce07617078bcdf30c609ec/Pillow-9.2.0-cp310-cp310-manylinux_2_28_x86_64.whl endif ASSIMP_URL:=https://anaconda.org/conda-forge/assimp/5.0.1/download/linux-64/assimp-5.0.1-hedfc422_6.tar.bz2 +QHULL_URL:=https://anaconda.org/conda-forge/qhull/2020.2/download/linux-64/qhull-2020.2-h4bd325d_2.tar.bz2 OCTOMAP_URL:=https://anaconda.org/conda-forge/octomap/1.9.8/download/linux-64/octomap-1.9.8-h924138e_0.tar.bz2 BOOSTCPP_URL:=https://anaconda.org/conda-forge/boost-cpp/1.78.0/download/linux-64/boost-cpp-1.78.0-h6582d0a_3.conda ZLIB_URL:=https://anaconda.org/conda-forge/zlib/1.2.11/download/linux-64/zlib-1.2.11-h516909a_1010.tar.bz2 @@ -83,7 +82,6 @@ ifeq ($(PYVERSION), py39) HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/2.3.4/download/osx-64/hpp-fcl-2.3.4-py39hd85b194_0.conda EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/3.1.0/download/osx-64/eigenpy-3.1.0-py39hc4d6e28_0.conda BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/osx-64/boost-1.78.0-py39h953a6b8_4.tar.bz2 -QHULL_URL:=https://anaconda.org/conda-forge/qhull/2020.2/download/osx-64/qhull-2020.2-h940c156_2.tar.bz2 LXML_URL:=https://files.pythonhosted.org/packages/b8/74/a71f7ad72e8db54ce899efab84507b801660750cbbfa6a39e6717557d36a/lxml-4.6.3-cp39-cp39-macosx_10_9_x86_64.whl SHAPELY_URL:=https://files.pythonhosted.org/packages/36/a4/7e542a209f862f967d7cb8e939eff155f4294a27d17e16441fb8bdd51a2c/shapely-2.0.1-cp39-cp39-macosx_10_9_x86_64.whl PILLOW_URL:=https://files.pythonhosted.org/packages/88/7a/ddfe28b485b623361457d4783007c1f9ba83a87f93e7fec32f64793efb6c/Pillow-9.2.0-cp39-cp39-macosx_10_10_x86_64.whl @@ -92,12 +90,12 @@ ifeq ($(PYVERSION), py310) HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/2.3.4/download/osx-64/hpp-fcl-2.3.4-py310h1db6f5f_0.conda EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/3.1.0/download/osx-64/eigenpy-3.1.0-py310h43da829_0.conda BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/osx-64/boost-1.78.0-py310h3e792ce_4.tar.bz2 -QHULL_URL:=https://anaconda.org/conda-forge/qhull/2020.2/download/osx-64/qhull-2020.2-h940c156_2.tar.bz2 LXML_URL:=https://files.pythonhosted.org/packages/a1/44/17b7dac7a18807d30e2fe10c3328c152808f5464565e230bfd0e77f178c6/lxml-4.8.0-cp310-cp310-macosx_10_15_x86_64.whl SHAPELY_URL:=https://files.pythonhosted.org/packages/1f/2a/dc3353c2431cf53e8d04bb8fba27e584410ca3435c9c85f76d71bf0c0e80/shapely-2.0.1-cp310-cp310-macosx_10_9_x86_64.whl PILLOW_URL:=https://files.pythonhosted.org/packages/d8/60/b13c00d403f34110e96c1b5c0afa73ce461efe3fe960c3a7e3e7fe190d82/Pillow-9.2.0-cp310-cp310-macosx_10_10_x86_64.whl endif ASSIMP_URL:=https://anaconda.org/conda-forge/assimp/5.0.1/download/osx-64/assimp-5.0.1-h1224e73_6.tar.bz2 +QHULL_URL:=https://anaconda.org/conda-forge/qhull/2020.2/download/osx-64/qhull-2020.2-h940c156_2.tar.bz2 OCTOMAP_URL:=https://anaconda.org/conda-forge/octomap/1.9.8/download/osx-64/octomap-1.9.8-hb8565cd_0.tar.bz2 BOOSTCPP_URL:=https://anaconda.org/conda-forge/boost-cpp/1.78.0/download/osx-64/boost-cpp-1.78.0-hf5ba120_3.conda ZLIB_URL:=https://anaconda.org/conda-forge/zlib/1.2.11/download/osx-64/zlib-1.2.11-h7795811_1010.tar.bz2 @@ -109,7 +107,6 @@ ifeq ($(PYVERSION), py39) HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/2.3.4/download/osx-arm64/hpp-fcl-2.3.4-py39hc34188a_0.conda EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/3.1.0/download/osx-arm64/eigenpy-3.1.0-py39h13cfc01_0.conda BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/osx-arm64/boost-1.78.0-py39h99de9ae_4.tar.bz2 -QHULL_URL:=https://anaconda.org/conda-forge/qhull/2020.2/download/osx-arm64/qhull-2020.2-hc021e02_2.tar.bz2 LXML_URL:=https://anaconda.org/conda-forge/lxml/4.9.1/download/osx-arm64/lxml-4.9.1-py39h9eb174b_0.tar.bz2 SHAPELY_URL:=https://files.pythonhosted.org/packages/ea/aa/45fbd031edf3149cb767d8b9f9db45d5faf0324d743c6b8fb0298cc022d0/shapely-2.0.1-cp39-cp39-macosx_11_0_arm64.whl PILLOW_URL:=https://files.pythonhosted.org/packages/aa/bc/21097cd891dd2fa02f2b3d767e02e883e026482e59d29975d1bc30024aa3/Pillow-9.2.0-cp39-cp39-macosx_11_0_arm64.whl @@ -118,12 +115,12 @@ ifeq ($(PYVERSION), py310) HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/2.3.4/download/osx-arm64/hpp-fcl-2.3.4-py310h46fc4cd_0.conda EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/3.1.0/download/osx-arm64/eigenpy-3.1.0-py310ha2643af_0.conda BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/osx-arm64/boost-1.78.0-py310h629746b_4.tar.bz2 -QHULL_URL:=https://anaconda.org/conda-forge/qhull/2020.2/download/osx-arm64/qhull-2020.2-hc021e02_2.tar.bz2 LXML_URL:=https://anaconda.org/conda-forge/lxml/4.9.1/download/osx-arm64/lxml-4.9.1-py310h02f21da_0.tar.bz2 SHAPELY_URL:=https://files.pythonhosted.org/packages/ec/41/d59208743e737184e1b403e95a937aebb022b8459e99efbcd5208fc8be46/shapely-2.0.1-cp310-cp310-macosx_11_0_arm64.whl PILLOW_URL:=https://files.pythonhosted.org/packages/0c/5f/117b653cad585f3aedfe0de996c292e67d4b020ed77f652e5a6c8c24f908/Pillow-9.2.0-cp310-cp310-macosx_11_0_arm64.whl endif ASSIMP_URL:=https://anaconda.org/conda-forge/assimp/5.0.1/download/osx-arm64/assimp-5.0.1-h0f81e16_7.tar.bz2 +QHULL_URL:=https://anaconda.org/conda-forge/qhull/2020.2/download/osx-arm64/qhull-2020.2-hc021e02_2.tar.bz2 OCTOMAP_URL:=https://anaconda.org/conda-forge/octomap/1.9.8/download/osx-arm64/octomap-1.9.8-hffc8910_0.tar.bz2 BOOSTCPP_URL:=https://anaconda.org/conda-forge/boost-cpp/1.78.0/download/osx-arm64/boost-cpp-1.78.0-h9ed8d21_3.conda ZLIB_URL:=https://anaconda.org/conda-forge/zlib/1.2.11/download/osx-arm64/zlib-1.2.11-h90dfc92_1014.tar.bz2 @@ -147,7 +144,8 @@ LXML_URL:=https://files.pythonhosted.org/packages/f6/71/65c80a4caa1617a4c6e8fe15 SHAPELY_URL:=https://files.pythonhosted.org/packages/81/8a/7ac076a86b2632f1872284c5e60ed5f2fc26094875a85b35d9fa17b52504/shapely-2.0.1-cp310-cp310-win_amd64.whl PILLOW_URL:=https://files.pythonhosted.org/packages/02/55/67a3c17b9e7d972ed8c246f104da99ca4f3ea42fba566697e479011b84b6/Pillow-9.2.0-cp310-cp310-win_amd64.whl endif -ASSIMP_URL:=https://anaconda.org/conda-forge/assimp/5.0.1/download/win-64/assimp-5.0.1-hc2aa0de_6.tar.bz2 +ASSIMP_URL:=https://anaconda.org/conda-forge/assimp/5.2.5/download/win-64/assimp-5.2.5-h4dcb625_0.tar.bz2 +QHULL_URL:=https://anaconda.org/conda-forge/qhull/2020.2/download/win-64/qhull-2020.2-h70d2c02_2.tar.bz2 OCTOMAP_URL:=https://anaconda.org/conda-forge/octomap/1.9.8/download/win-64/octomap-1.9.8-h91493d7_0.tar.bz2 BOOSTCPP_URL:=https://anaconda.org/conda-forge/boost-cpp/1.78.0/download/win-64/boost-cpp-1.78.0-h9f4b32c_3.conda ZLIB_URL:=https://anaconda.org/conda-forge/zlib/1.2.11/download/win-64/zlib-1.2.11-h62dcd97_1010.tar.bz2 @@ -171,7 +169,7 @@ endif cp -r blenderbim/* dist/blenderbim/ # Provides IfcOpenShell Python functionality - cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-$(PYNUMBER)-v0.7.0-dcc9d0e-$(PLATFORM)64.zip + cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-$(PYNUMBER)-v0.7.0-9cc1f5f-$(PLATFORM)64.zip cd dist/working && unzip ifcopenshell-python* cp -r dist/working/ifcopenshell dist/blenderbim/libs/site/packages/ @@ -437,8 +435,7 @@ ifeq ($(PLATFORM), win) endif rm -rf dist/working - # Required by hpp-fcl except on Windows -ifneq ($(PLATFORM), win) + # Required by hpp-fcl mkdir dist/working cd dist/working && wget $(QHULL_URL) cd dist/working && tar -xf qhull* @@ -451,8 +448,10 @@ endif ifeq ($(PLATFORM), macosm1) cp -r dist/working/lib/*.dylib dist/blenderbim/libs/ endif - rm -rf dist/working +ifeq ($(PLATFORM), win) + cp -r dist/working/Library/bin/*.dll dist/blenderbim/libs/site/packages/hppfcl/ endif + rm -rf dist/working # Required by hpp-fcl mkdir dist/working @@ -487,7 +486,7 @@ ifeq ($(PLATFORM), macosm1) cp -r dist/working/lib/*.dylib dist/blenderbim/libs/ endif ifeq ($(PLATFORM), win) - # Uh, do nothing, apparently? No DLLs are shipped. + cp -r dist/working/Library/bin/*.dll dist/blenderbim/libs/site/packages/hppfcl/ endif rm -rf dist/working diff --git a/src/blenderbim/blenderbim/bim/data/assets/symbols.svg b/src/blenderbim/blenderbim/bim/data/assets/symbols.svg index 7011554551..5aca6a35eb 100644 --- a/src/blenderbim/blenderbim/bim/data/assets/symbols.svg +++ b/src/blenderbim/blenderbim/bim/data/assets/symbols.svg @@ -44,17 +44,22 @@ - + - - + + + + + + + - + - + diff --git a/src/blenderbim/blenderbim/bim/data/libraries/IFC4 Demo Library.ifc b/src/blenderbim/blenderbim/bim/data/libraries/IFC4 Demo Library.ifc index a86680bbd0..190b13d9a4 100644 --- a/src/blenderbim/blenderbim/bim/data/libraries/IFC4 Demo Library.ifc +++ b/src/blenderbim/blenderbim/bim/data/libraries/IFC4 Demo Library.ifc @@ -1,13 +1,13 @@ ISO-10303-21; HEADER; FILE_DESCRIPTION(('ViewDefinition[DesignTransferView]'),'2;1'); -FILE_NAME('/dev/null','2023-07-30T21:07:33+10:00',(),(),'IfcOpenShell v0.7.0-8f41ae0c1','IfcOpenShell v0.7.0-8f41ae0c1','Nobody'); +FILE_NAME('/dev/null','2023-09-02T16:09:02+10:00',(),(),'IfcOpenShell v0.7.0-fbd8ea1ed','IfcOpenShell v0.7.0-fbd8ea1ed','Nobody'); FILE_SCHEMA(('IFC4')); ENDSEC; DATA; -#1=IFCPROJECT('0TszJmTqLCyxcP10DtK_MP',$,'BlenderBIM Demo',$,$,$,$,(#12,#16),#7); -#2=IFCPROJECTLIBRARY('0Y_CJXSsbCgBlSGFw0mbLa',$,'BlenderBIM Demo Library',$,$,$,$,$,$); -#3=IFCRELDECLARES('3IxQaOPH5Fx90CGmGm1gtQ',$,$,$,#1,(#2)); +#1=IFCPROJECT('1UlLTVwDzDvR$QNEAxFx6F',$,'BlenderBIM Demo',$,$,$,$,(#12,#16),#7); +#2=IFCPROJECTLIBRARY('3QN02XEh1FQh0fii364VEi',$,'BlenderBIM Demo Library',$,$,$,$,$,$); +#3=IFCRELDECLARES('0aiEyU5lL7mAdB5xwdXbUN',$,$,$,#1,(#2)); #4=IFCSIUNIT(*,.LENGTHUNIT.,$,.METRE.); #5=IFCSIUNIT(*,.AREAUNIT.,$,.SQUARE_METRE.); #6=IFCSIUNIT(*,.VOLUMEUNIT.,$,.CUBIC_METRE.); @@ -25,82 +25,82 @@ DATA; #18=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Body','Plan',*,*,*,*,#16,$,.PLAN_VIEW.,$); #19=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Annotation','Plan',*,*,*,*,#16,$,.PLAN_VIEW.,$); #20=IFCMATERIAL('Unknown',$,$); -#21=IFCWALLTYPE('3U0gAUglf9WBlcfpB15EK0',$,'WAL50',$,$,$,$,$,$,.NOTDEFINED.); +#21=IFCWALLTYPE('0Ir6HewafB2hnPRigxrUnM',$,'WAL50',$,$,$,$,$,$,.NOTDEFINED.); #22=IFCMATERIALLAYERSET((#24),$,$); -#23=IFCRELASSOCIATESMATERIAL('3J36H22Gz1ovVzdiKgApaM',$,$,$,(#21),#22); +#23=IFCRELASSOCIATESMATERIAL('3S$YbWQCnDCA2tmoikSnrh',$,$,$,(#21),#22); #24=IFCMATERIALLAYER(#20,0.05,$,$,$,$,$); -#25=IFCRELDECLARES('28ApgMJzH10h$Ng$fmCpYf',$,$,$,#2,(#63,#21,#54,#87,#153,#34,#67,#210,#38,#82,#77,#42,#26,#48,#72,#59,#92,#30,#96)); -#26=IFCWALLTYPE('1wGfdUdC10TuASJo4jnO99',$,'WAL100',$,$,$,$,$,$,.NOTDEFINED.); +#25=IFCRELDECLARES('1eLCJqssP2kvQIdmPlRK19',$,$,$,#2,(#38,#82,#77,#42,#26,#48,#72,#59,#92,#30,#96,#63,#54,#87,#153,#34,#67,#21,#210)); +#26=IFCWALLTYPE('2oUFJayonDlBTcbJtxYFSv',$,'WAL100',$,$,$,$,$,$,.NOTDEFINED.); #27=IFCMATERIALLAYERSET((#29),$,$); -#28=IFCRELASSOCIATESMATERIAL('0NRoxSQPzCgQHfsu5ru91z',$,$,$,(#26),#27); +#28=IFCRELASSOCIATESMATERIAL('0n3vPKxufDgO48f4yfGtUd',$,$,$,(#26),#27); #29=IFCMATERIALLAYER(#20,0.1,$,$,$,$,$); -#30=IFCWALLTYPE('1lt5MuGLLDfwmZd0tNruF7',$,'WAL200',$,$,$,$,$,$,.NOTDEFINED.); +#30=IFCWALLTYPE('0UDhWxnIXDMOxZ2yPDCUMP',$,'WAL200',$,$,$,$,$,$,.NOTDEFINED.); #31=IFCMATERIALLAYERSET((#33),$,$); -#32=IFCRELASSOCIATESMATERIAL('21rGVxPODC$xmWYFaDy1T6',$,$,$,(#30),#31); +#32=IFCRELASSOCIATESMATERIAL('2Boxz3uaP0LOy6luKau2R0',$,$,$,(#30),#31); #33=IFCMATERIALLAYER(#20,0.2,$,$,$,$,$); -#34=IFCWALLTYPE('0NZm1D8h12xAxyW7fL5nc1',$,'WAL300',$,$,$,$,$,$,.NOTDEFINED.); +#34=IFCWALLTYPE('20fPutWQrFyeMIiKxc$MkU',$,'WAL300',$,$,$,$,$,$,.NOTDEFINED.); #35=IFCMATERIALLAYERSET((#37),$,$); -#36=IFCRELASSOCIATESMATERIAL('2yvmN42crCJevtirp5cMeV',$,$,$,(#34),#35); +#36=IFCRELASSOCIATESMATERIAL('2qAjlTTxjBkvdp80A9XdMZ',$,$,$,(#34),#35); #37=IFCMATERIALLAYER(#20,0.3,$,$,$,$,$); -#38=IFCCOVERINGTYPE('1eb4wZ_5n6xRGrs3rlKMiM',$,'COV10',$,$,$,$,$,$,.NOTDEFINED.); +#38=IFCCOVERINGTYPE('100hdtcxzBdQATomAi0tv5',$,'COV10',$,$,$,$,$,$,.NOTDEFINED.); #39=IFCMATERIALLAYERSET((#41),$,$); -#40=IFCRELASSOCIATESMATERIAL('3HKIKf06T6xRqM6Lp69$OM',$,$,$,(#38),#39); +#40=IFCRELASSOCIATESMATERIAL('3tO1xRn9b06utMa1TZC$WW',$,$,$,(#38),#39); #41=IFCMATERIALLAYER(#20,0.01,$,$,$,$,$); -#42=IFCCOVERINGTYPE('2E89aYmtrEfxhX_jsNqviN',$,'COV20',$,$,(#46),$,$,$,.NOTDEFINED.); +#42=IFCCOVERINGTYPE('1sO51oul95ABWezHr7P7Dk',$,'COV20',$,$,(#46),$,$,$,.NOTDEFINED.); #43=IFCMATERIALLAYERSET((#45),$,$); -#44=IFCRELASSOCIATESMATERIAL('2115lEhhP4Z9G1Yhd_DqSC',$,$,$,(#42),#43); +#44=IFCRELASSOCIATESMATERIAL('0w6lexCg19TuiqqTxo91zc',$,$,$,(#42),#43); #45=IFCMATERIALLAYER(#20,0.02,$,$,$,$,$); -#46=IFCPROPERTYSET('0vtU63YmjAgg8DeHlTgNx7',$,'EPset_Parametric',$,(#47)); +#46=IFCPROPERTYSET('3ytgFxH0571uBCG4mBNXI9',$,'EPset_Parametric',$,(#47)); #47=IFCPROPERTYSINGLEVALUE('LayerSetDirection',$,IFCLABEL('AXIS2'),$); -#48=IFCCOVERINGTYPE('0XiSBI57L9Uf8f2NaQ_ksi',$,'COV30',$,$,(#52),$,$,$,.NOTDEFINED.); +#48=IFCCOVERINGTYPE('09GKC1aO56qwEPF9halcxJ',$,'COV30',$,$,(#52),$,$,$,.NOTDEFINED.); #49=IFCMATERIALLAYERSET((#51),$,$); -#50=IFCRELASSOCIATESMATERIAL('3U3JdGAmbABA4mB$Fgpq1l',$,$,$,(#48),#49); +#50=IFCRELASSOCIATESMATERIAL('0rDrnpiX58DgQhcURj_WlB',$,$,$,(#48),#49); #51=IFCMATERIALLAYER(#20,0.03,$,$,$,$,$); -#52=IFCPROPERTYSET('17nhQ5I4b9ZvhDu4gRJLKO',$,'EPset_Parametric',$,(#53)); +#52=IFCPROPERTYSET('14u6OUyPL5y8O$QkEnB6Rr',$,'EPset_Parametric',$,(#53)); #53=IFCPROPERTYSINGLEVALUE('LayerSetDirection',$,IFCLABEL('AXIS3'),$); -#54=IFCRAMPTYPE('1BTHjVDP19tgb3MDCSkWCJ',$,'RAM200',$,$,$,$,$,$,.NOTDEFINED.); +#54=IFCRAMPTYPE('2G4JrZ99j2B9SkIwWDk0sC',$,'RAM200',$,$,$,$,$,$,.NOTDEFINED.); #55=IFCMATERIALLAYERSET((#57),$,$); -#56=IFCRELASSOCIATESMATERIAL('3HN21bHtL16vvHA2GnnxH1',$,$,$,(#54),#55); +#56=IFCRELASSOCIATESMATERIAL('2nNlFzNPj0_h6_NwdVJvW$',$,$,$,(#54),#55); #57=IFCMATERIALLAYER(#20,0.2,$,$,$,$,$); #58=IFCCIRCLEPROFILEDEF(.AREA.,$,$,0.3); -#59=IFCPILETYPE('04_2WZSqH8TgT7ET6DCCpd',$,'P1',$,$,$,$,$,$,.NOTDEFINED.); +#59=IFCPILETYPE('3uIc5iHjP5Lel3X6KXyN8W',$,'P1',$,$,$,$,$,$,.NOTDEFINED.); #60=IFCMATERIALPROFILESET($,$,(#62),$); -#61=IFCRELASSOCIATESMATERIAL('1qTZZRXsPF9hmYwT$tgFgp',$,$,$,(#59),#60); +#61=IFCRELASSOCIATESMATERIAL('13buLvs1r5vAyhgbabb2g1',$,$,$,(#59),#60); #62=IFCMATERIALPROFILE($,$,#20,#58,$,$); -#63=IFCSLABTYPE('3_OV9$Sd52KRhIW2TWIxbW',$,'FLR150',$,$,$,$,$,$,.NOTDEFINED.); +#63=IFCSLABTYPE('2CbahfxvnFpPoHyMBpGTQm',$,'FLR150',$,$,$,$,$,$,.NOTDEFINED.); #64=IFCMATERIALLAYERSET((#66),$,$); -#65=IFCRELASSOCIATESMATERIAL('1agyf45$X1NhR127pUL0Iw',$,$,$,(#63),#64); +#65=IFCRELASSOCIATESMATERIAL('2Irb7$9xL8YOXW99sF0SOp',$,$,$,(#63),#64); #66=IFCMATERIALLAYER(#20,0.2,$,$,$,$,$); -#67=IFCSLABTYPE('0LvmpIGf9AVPcG$Kt06TEk',$,'FLR250',$,$,$,$,$,$,.NOTDEFINED.); +#67=IFCSLABTYPE('04dx2Vg4vEp9ZOKGI1ifWb',$,'FLR250',$,$,$,$,$,$,.NOTDEFINED.); #68=IFCMATERIALLAYERSET((#70),$,$); -#69=IFCRELASSOCIATESMATERIAL('0EKQPSL656secN86SgeutH',$,$,$,(#67),#68); +#69=IFCRELASSOCIATESMATERIAL('04tl3Me6T6xOb$WEvv_dL0',$,$,$,(#67),#68); #70=IFCMATERIALLAYER(#20,0.3,$,$,$,$,$); #71=IFCRECTANGLEPROFILEDEF(.AREA.,'500x600',$,0.5,0.6); -#72=IFCCOLUMNTYPE('1SI8m9m813tAXfSnM1fCbm',$,'C1',$,$,$,$,$,$,.NOTDEFINED.); +#72=IFCCOLUMNTYPE('13rk_6wmDApeVQWfrArKbl',$,'C1',$,$,$,$,$,$,.NOTDEFINED.); #73=IFCMATERIALPROFILESET($,$,(#75),$); -#74=IFCRELASSOCIATESMATERIAL('1EhwSTRtPBQgBt2x6dhuT$',$,$,$,(#72),#73); +#74=IFCRELASSOCIATESMATERIAL('0X3OUScT9E1BjOvJWiUrv3',$,$,$,(#72),#73); #75=IFCMATERIALPROFILE($,$,#20,#71,$,$); #76=IFCCIRCLEHOLLOWPROFILEDEF(.AREA.,'500.0x5.0 CHS',$,0.25,0.005); -#77=IFCCOLUMNTYPE('25GZwy2zLFcgi57r5S8iY3',$,'C2',$,$,$,$,$,$,.NOTDEFINED.); +#77=IFCCOLUMNTYPE('0IaS5T1Uv6DQu$BJ5I3bks',$,'C2',$,$,$,$,$,$,.NOTDEFINED.); #78=IFCMATERIALPROFILESET($,$,(#80),$); -#79=IFCRELASSOCIATESMATERIAL('2VURAOhz93ZQqT9YJUA1DO',$,$,$,(#77),#78); +#79=IFCRELASSOCIATESMATERIAL('1HWcDe7a17WwPeKCHnckid',$,$,$,(#77),#78); #80=IFCMATERIALPROFILE($,$,#20,#76,$,$); #81=IFCRECTANGLEHOLLOWPROFILEDEF(.AREA.,'150x75x2.0 RHS',$,0.075,0.15,0.002,0.005,0.005); -#82=IFCCOLUMNTYPE('1Wg$I84s9BTh$iDuZnmSkH',$,'C3',$,$,$,$,$,$,.NOTDEFINED.); +#82=IFCCOLUMNTYPE('13p6$J2Jv84RFI5QAp6FTQ',$,'C3',$,$,$,$,$,$,.NOTDEFINED.); #83=IFCMATERIALPROFILESET($,$,(#85),$); -#84=IFCRELASSOCIATESMATERIAL('3fIIwEe7DAle7x_zISvMUD',$,$,$,(#82),#83); +#84=IFCRELASSOCIATESMATERIAL('1GWCzHY657$9kUJrD0AEir',$,$,$,(#82),#83); #85=IFCMATERIALPROFILE($,$,#20,#81,$,$); #86=IFCISHAPEPROFILEDEF(.AREA.,'DEMO-I',$,0.1,0.2,0.005,0.01,0.005,$,$); -#87=IFCBEAMTYPE('2yg2Irs7L26gFsD_AvvFUu',$,'B1',$,$,$,$,$,$,.NOTDEFINED.); +#87=IFCBEAMTYPE('1Y_PtOyJP9YfZkU59r$vs1',$,'B1',$,$,$,$,$,$,.NOTDEFINED.); #88=IFCMATERIALPROFILESET($,$,(#90),$); -#89=IFCRELASSOCIATESMATERIAL('1PU_gbkifF9hYpi9DvXTAU',$,$,$,(#87),#88); +#89=IFCRELASSOCIATESMATERIAL('2CfmfKJ0X45xijxsDUHZ85',$,$,$,(#87),#88); #90=IFCMATERIALPROFILE($,$,#20,#86,$,$); #91=IFCCSHAPEPROFILEDEF(.AREA.,'DEMO-C',$,0.2,0.1,0.0015,0.03,0.005); -#92=IFCBEAMTYPE('2Nf2L5qpz8K8KhUNFBg_fT',$,'B2',$,$,$,$,$,$,.NOTDEFINED.); +#92=IFCBEAMTYPE('2qogXGNrb9EANilnZjnGjq',$,'B2',$,$,$,$,$,$,.NOTDEFINED.); #93=IFCMATERIALPROFILESET($,$,(#95),$); -#94=IFCRELASSOCIATESMATERIAL('1SgarGNCzEQQpfs29NMq0y',$,$,$,(#92),#93); +#94=IFCRELASSOCIATESMATERIAL('3$1pGIrVLA2RTOmJIsbelu',$,$,$,(#92),#93); #95=IFCMATERIALPROFILE($,$,#20,#91,$,$); -#96=IFCWINDOWTYPE('2zRUFP8Ef81xDOR2iJx1zK',$,'WT01',$,$,$,(#135,#152),$,$,.NOTDEFINED.,.NOTDEFINED.,$,$); +#96=IFCWINDOWTYPE('1Dc9pAExL3fxL9jScEo2EF',$,'WT01',$,$,$,(#135,#152),$,$,.NOTDEFINED.,.NOTDEFINED.,$,$); #97=IFCINDEXEDPOLYGONALFACE((13,17,18,14)); #98=IFCINDEXEDPOLYGONALFACE((5,6,3,4)); #99=IFCINDEXEDPOLYGONALFACE((7,8,2,1)); @@ -157,7 +157,7 @@ DATA; #150=IFCDIRECTION((0.,0.,1.)); #151=IFCAXIS2PLACEMENT3D(#148,#150,#149); #152=IFCREPRESENTATIONMAP(#151,#147); -#153=IFCDOORTYPE('0oekOIA$z8lxWiQs9_tFWU',$,'DT01',$,$,$,(#196,#209),$,$,.NOTDEFINED.,.NOTDEFINED.,$,$); +#153=IFCDOORTYPE('05Z1DhJjj11wczPRkvXSPE',$,'DT01',$,$,$,(#196,#209),$,$,.NOTDEFINED.,.NOTDEFINED.,$,$); #154=IFCINDEXEDPOLYGONALFACE((17,16,15,14)); #155=IFCINDEXEDPOLYGONALFACE((2,3,29,28)); #156=IFCINDEXEDPOLYGONALFACE((27,28,29,30,32,31)); @@ -214,7 +214,7 @@ DATA; #207=IFCDIRECTION((0.,0.,1.)); #208=IFCAXIS2PLACEMENT3D(#205,#207,#206); #209=IFCREPRESENTATIONMAP(#208,#204); -#210=IFCFURNITURETYPE('0I2mc$m3T60hLXZYg9Y1wu',$,'BUN01',$,$,$,(#931,#952),$,$,.NOTDEFINED.,.NOTDEFINED.); +#210=IFCFURNITURETYPE('3ZMmSB_rfBnhrBckPTFKvP',$,'BUN01',$,$,$,(#931,#952),$,$,.NOTDEFINED.,.NOTDEFINED.); #211=IFCINDEXEDPOLYGONALFACE((187,278,44)); #212=IFCINDEXEDPOLYGONALFACE((21,52,60)); #213=IFCINDEXEDPOLYGONALFACE((91,100,31)); @@ -957,131 +957,140 @@ DATA; #950=IFCDIRECTION((0.,0.,1.)); #951=IFCAXIS2PLACEMENT3D(#948,#950,#949); #952=IFCREPRESENTATIONMAP(#951,#947); -#953=IFCTYPEPRODUCT('2be705eyD4DPBr6j4K7ZEx',$,'DASHED',$,'IfcAnnotation/LINEWORK',(#954),$,$); -#954=IFCPROPERTYSET('0uEfutIxDDIxMIBUJOZpSM',$,'EPset_Annotation',$,(#955)); -#955=IFCPROPERTYSINGLEVALUE('Classes',$,IFCLABEL('dashed'),$); -#956=IFCTYPEPRODUCT('1M7mm9wUHFoxdM0MdAMyqM',$,'FINE',$,'IfcAnnotation/LINEWORK',(#957),$,$); -#957=IFCPROPERTYSET('3AZ8m1Bdn1gfbvagzKr2c3',$,'EPset_Annotation',$,(#958)); -#958=IFCPROPERTYSINGLEVALUE('Classes',$,IFCLABEL('fine'),$); -#959=IFCTYPEPRODUCT('3LdrNIFOvAkBzrNnqWAZfA',$,'THIN',$,'IfcAnnotation/LINEWORK',(#960),$,$); -#960=IFCPROPERTYSET('0$9iwgSID7xBul4Tb9r2du',$,'EPset_Annotation',$,(#961)); -#961=IFCPROPERTYSINGLEVALUE('Classes',$,IFCLABEL('thin'),$); -#962=IFCTYPEPRODUCT('3fFNO0t1TB$wYRaIkKwUCH',$,'MEDIUM',$,'IfcAnnotation/LINEWORK',(#963),$,$); -#963=IFCPROPERTYSET('33Fm$9zgb1cgsCOy859RlR',$,'EPset_Annotation',$,(#964)); -#964=IFCPROPERTYSINGLEVALUE('Classes',$,IFCLABEL('medium'),$); -#965=IFCTYPEPRODUCT('1CpMBhemj5VQC43bU9J7zG',$,'THICK',$,'IfcAnnotation/LINEWORK',(#966),$,$); -#966=IFCPROPERTYSET('1OLDsFSDz2TPz5ifkQvWo8',$,'EPset_Annotation',$,(#967)); -#967=IFCPROPERTYSINGLEVALUE('Classes',$,IFCLABEL('thick'),$); -#968=IFCTYPEPRODUCT('0NITtya3b16hoA3sh17SL4',$,'STRONG',$,'IfcAnnotation/LINEWORK',(#969),$,$); -#969=IFCPROPERTYSET('1_kfqWVDn0puI29Ah6eitX',$,'EPset_Annotation',$,(#970)); -#970=IFCPROPERTYSINGLEVALUE('Classes',$,IFCLABEL('strong'),$); -#971=IFCTYPEPRODUCT('0rRN9EsvL5AQ3PorxrZSKM',$,'DOOR-TAG',$,'IfcAnnotation/TEXT',(#972),(#991),$); -#972=IFCPROPERTYSET('3lRZ$d2VH8cxZYh$mm45iq',$,'EPset_Annotation',$,(#973)); -#973=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('door-tag'),$); -#974=IFCCARTESIANPOINT((0.,0.,0.)); -#975=IFCDIRECTION((0.,0.,1.)); -#976=IFCDIRECTION((1.,0.,0.)); -#977=IFCAXIS2PLACEMENT3D(#974,#975,#976); -#978=IFCPLANAREXTENT(1000.,1000.); -#979=IFCTEXTLITERALWITHEXTENT('{{type.Name}}',#977,.RIGHT.,#978,'center'); -#980=IFCCARTESIANPOINT((0.,0.,0.)); -#981=IFCDIRECTION((0.,0.,1.)); -#982=IFCDIRECTION((1.,0.,0.)); -#983=IFCAXIS2PLACEMENT3D(#980,#981,#982); -#984=IFCPLANAREXTENT(1000.,1000.); -#985=IFCTEXTLITERALWITHEXTENT('{{Name}}',#983,.RIGHT.,#984,'center'); -#986=IFCSHAPEREPRESENTATION(#19,'Annotation','Annotation2D',(#979,#985)); -#987=IFCCARTESIANPOINT((0.,0.,0.)); -#988=IFCDIRECTION((1.,0.,0.)); -#989=IFCDIRECTION((0.,0.,1.)); -#990=IFCAXIS2PLACEMENT3D(#987,#989,#988); -#991=IFCREPRESENTATIONMAP(#990,#986); -#992=IFCTYPEPRODUCT('0bFZUWHjbA2e03hRHNinTt',$,'WINDOW-TAG',$,'IfcAnnotation/TEXT',(#993),(#1006),$); -#993=IFCPROPERTYSET('3TJOWv5NDBXBNCshYFjsFV',$,'EPset_Annotation',$,(#994)); -#994=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('window-tag'),$); -#995=IFCCARTESIANPOINT((0.,0.,0.)); -#996=IFCDIRECTION((0.,0.,1.)); +#953=IFCTYPEPRODUCT('1hmWolVQb9buX2GhHwaVFB',$,'SETOUT-POINT',$,'IfcAnnotation/SYMBOL',(#954),$,$); +#954=IFCPROPERTYSET('2YNCFr62b8vhuhHGspQl4$',$,'EPset_Annotation',$,(#955)); +#955=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('setout-point'),$); +#956=IFCTYPEPRODUCT('3H4oE1a_9AwQE8OFsCjdfh',$,'CONTROL-POINT',$,'IfcAnnotation/SYMBOL',(#957),$,$); +#957=IFCPROPERTYSET('0ARWSOH$z3kQDRkpEPHWCt',$,'EPset_Annotation',$,(#958)); +#958=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('control-point'),$); +#959=IFCTYPEPRODUCT('3YDPnyrWn2wRWgRzb0V7Kx',$,'TRAVERSE-POINT',$,'IfcAnnotation/SYMBOL',(#960),$,$); +#960=IFCPROPERTYSET('1v$MzHU1b82hDqHdtz4e5y',$,'EPset_Annotation',$,(#961)); +#961=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('traverse-point'),$); +#962=IFCTYPEPRODUCT('31Em8$VNL0Mh$9EZ3pXjpL',$,'DASHED',$,'IfcAnnotation/LINEWORK',(#963),$,$); +#963=IFCPROPERTYSET('0K84$piRb31PmkAwZfP5kd',$,'EPset_Annotation',$,(#964)); +#964=IFCPROPERTYSINGLEVALUE('Classes',$,IFCLABEL('dashed'),$); +#965=IFCTYPEPRODUCT('3P0K0gDxP4NgwnuLGqlyW6',$,'FINE',$,'IfcAnnotation/LINEWORK',(#966),$,$); +#966=IFCPROPERTYSET('179vwKcVv9uet4_kI4NT9G',$,'EPset_Annotation',$,(#967)); +#967=IFCPROPERTYSINGLEVALUE('Classes',$,IFCLABEL('fine'),$); +#968=IFCTYPEPRODUCT('0Rh30iszn41xBCgQJl2w2c',$,'THIN',$,'IfcAnnotation/LINEWORK',(#969),$,$); +#969=IFCPROPERTYSET('0bZV_Tby121uj5O3n08l1l',$,'EPset_Annotation',$,(#970)); +#970=IFCPROPERTYSINGLEVALUE('Classes',$,IFCLABEL('thin'),$); +#971=IFCTYPEPRODUCT('3ti_6xztr7qPp9dASz9vP4',$,'MEDIUM',$,'IfcAnnotation/LINEWORK',(#972),$,$); +#972=IFCPROPERTYSET('19ZhF3bxL07evL8gi27Kei',$,'EPset_Annotation',$,(#973)); +#973=IFCPROPERTYSINGLEVALUE('Classes',$,IFCLABEL('medium'),$); +#974=IFCTYPEPRODUCT('2hlcxjzxv8zf0gh1md23xY',$,'THICK',$,'IfcAnnotation/LINEWORK',(#975),$,$); +#975=IFCPROPERTYSET('0wRtpbInzE2AkqX8RIreQr',$,'EPset_Annotation',$,(#976)); +#976=IFCPROPERTYSINGLEVALUE('Classes',$,IFCLABEL('thick'),$); +#977=IFCTYPEPRODUCT('1pefU$PA1ENw7kZG9T3mjr',$,'STRONG',$,'IfcAnnotation/LINEWORK',(#978),$,$); +#978=IFCPROPERTYSET('1RpJZBysn8shqevvAquPQ8',$,'EPset_Annotation',$,(#979)); +#979=IFCPROPERTYSINGLEVALUE('Classes',$,IFCLABEL('strong'),$); +#980=IFCTYPEPRODUCT('1HKC8zCIv5_ef3dEfa$L5N',$,'DOOR-TAG',$,'IfcAnnotation/TEXT',(#981),(#1000),$); +#981=IFCPROPERTYSET('0aC8JrqFX6HhKUhjdLw4mN',$,'EPset_Annotation',$,(#982)); +#982=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('door-tag'),$); +#983=IFCCARTESIANPOINT((0.,0.,0.)); +#984=IFCDIRECTION((0.,0.,1.)); +#985=IFCDIRECTION((1.,0.,0.)); +#986=IFCAXIS2PLACEMENT3D(#983,#984,#985); +#987=IFCPLANAREXTENT(1000.,1000.); +#988=IFCTEXTLITERALWITHEXTENT('{{type.Name}}',#986,.RIGHT.,#987,'center'); +#989=IFCCARTESIANPOINT((0.,0.,0.)); +#990=IFCDIRECTION((0.,0.,1.)); +#991=IFCDIRECTION((1.,0.,0.)); +#992=IFCAXIS2PLACEMENT3D(#989,#990,#991); +#993=IFCPLANAREXTENT(1000.,1000.); +#994=IFCTEXTLITERALWITHEXTENT('{{Name}}',#992,.RIGHT.,#993,'center'); +#995=IFCSHAPEREPRESENTATION(#19,'Annotation','Annotation2D',(#988,#994)); +#996=IFCCARTESIANPOINT((0.,0.,0.)); #997=IFCDIRECTION((1.,0.,0.)); -#998=IFCAXIS2PLACEMENT3D(#995,#996,#997); -#999=IFCPLANAREXTENT(1000.,1000.); -#1000=IFCTEXTLITERALWITHEXTENT('{{Name}}',#998,.RIGHT.,#999,'center'); -#1001=IFCSHAPEREPRESENTATION(#19,'Annotation','Annotation2D',(#1000)); -#1002=IFCCARTESIANPOINT((0.,0.,0.)); -#1003=IFCDIRECTION((1.,0.,0.)); -#1004=IFCDIRECTION((0.,0.,1.)); -#1005=IFCAXIS2PLACEMENT3D(#1002,#1004,#1003); -#1006=IFCREPRESENTATIONMAP(#1005,#1001); -#1007=IFCTYPEPRODUCT('2DlmDdasX05un_uMP4O23U',$,'SPACE-TAG',$,'IfcAnnotation/TEXT',(#1008),(#1033),$); -#1008=IFCPROPERTYSET('1$OAHURHr3GON7ZzAJnv72',$,'EPset_Annotation',$,(#1009)); -#1009=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('space-tag'),$); -#1010=IFCCARTESIANPOINT((0.,0.,0.)); -#1011=IFCDIRECTION((0.,0.,1.)); +#998=IFCDIRECTION((0.,0.,1.)); +#999=IFCAXIS2PLACEMENT3D(#996,#998,#997); +#1000=IFCREPRESENTATIONMAP(#999,#995); +#1001=IFCTYPEPRODUCT('2Jy_wl$FX8h9mgYT1$Y9oc',$,'WINDOW-TAG',$,'IfcAnnotation/TEXT',(#1002),(#1015),$); +#1002=IFCPROPERTYSET('2ncCyO$LPCzv7kqXZK_Omh',$,'EPset_Annotation',$,(#1003)); +#1003=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('window-tag'),$); +#1004=IFCCARTESIANPOINT((0.,0.,0.)); +#1005=IFCDIRECTION((0.,0.,1.)); +#1006=IFCDIRECTION((1.,0.,0.)); +#1007=IFCAXIS2PLACEMENT3D(#1004,#1005,#1006); +#1008=IFCPLANAREXTENT(1000.,1000.); +#1009=IFCTEXTLITERALWITHEXTENT('{{Name}}',#1007,.RIGHT.,#1008,'center'); +#1010=IFCSHAPEREPRESENTATION(#19,'Annotation','Annotation2D',(#1009)); +#1011=IFCCARTESIANPOINT((0.,0.,0.)); #1012=IFCDIRECTION((1.,0.,0.)); -#1013=IFCAXIS2PLACEMENT3D(#1010,#1011,#1012); -#1014=IFCPLANAREXTENT(1000.,1000.); -#1015=IFCTEXTLITERALWITHEXTENT('{{Name}}',#1013,.RIGHT.,#1014,'center'); -#1016=IFCCARTESIANPOINT((0.,0.,0.)); -#1017=IFCDIRECTION((0.,0.,1.)); -#1018=IFCDIRECTION((1.,0.,0.)); -#1019=IFCAXIS2PLACEMENT3D(#1016,#1017,#1018); -#1020=IFCPLANAREXTENT(1000.,1000.); -#1021=IFCTEXTLITERALWITHEXTENT('{{Description}}',#1019,.RIGHT.,#1020,'center'); -#1022=IFCCARTESIANPOINT((0.,0.,0.)); -#1023=IFCDIRECTION((0.,0.,1.)); -#1024=IFCDIRECTION((1.,0.,0.)); -#1025=IFCAXIS2PLACEMENT3D(#1022,#1023,#1024); -#1026=IFCPLANAREXTENT(1000.,1000.); -#1027=IFCTEXTLITERALWITHEXTENT('``round({{Qto_SpaceBaseQuantities.NetFloorArea}} or 0., 2)``',#1025,.RIGHT.,#1026,'center'); -#1028=IFCSHAPEREPRESENTATION(#19,'Annotation','Annotation2D',(#1015,#1021,#1027)); -#1029=IFCCARTESIANPOINT((0.,0.,0.)); -#1030=IFCDIRECTION((1.,0.,0.)); -#1031=IFCDIRECTION((0.,0.,1.)); -#1032=IFCAXIS2PLACEMENT3D(#1029,#1031,#1030); -#1033=IFCREPRESENTATIONMAP(#1032,#1028); -#1034=IFCTYPEPRODUCT('2vNx3CIlrFs8bLFlmGvJF3',$,'MATERIAL-TAG',$,'IfcAnnotation/TEXT',(#1035),(#1048),$); -#1035=IFCPROPERTYSET('1l$T4lUqnBb93zcgfbSPvx',$,'EPset_Annotation',$,(#1036)); -#1036=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('rectangle-tag'),$); -#1037=IFCCARTESIANPOINT((0.,0.,0.)); -#1038=IFCDIRECTION((0.,0.,1.)); +#1013=IFCDIRECTION((0.,0.,1.)); +#1014=IFCAXIS2PLACEMENT3D(#1011,#1013,#1012); +#1015=IFCREPRESENTATIONMAP(#1014,#1010); +#1016=IFCTYPEPRODUCT('1LTLrGzxH4qhuWGNeTlTRi',$,'SPACE-TAG',$,'IfcAnnotation/TEXT',(#1017),(#1042),$); +#1017=IFCPROPERTYSET('2JpTIsSZf3zQUWK48RLIcL',$,'EPset_Annotation',$,(#1018)); +#1018=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('space-tag'),$); +#1019=IFCCARTESIANPOINT((0.,0.,0.)); +#1020=IFCDIRECTION((0.,0.,1.)); +#1021=IFCDIRECTION((1.,0.,0.)); +#1022=IFCAXIS2PLACEMENT3D(#1019,#1020,#1021); +#1023=IFCPLANAREXTENT(1000.,1000.); +#1024=IFCTEXTLITERALWITHEXTENT('{{Name}}',#1022,.RIGHT.,#1023,'center'); +#1025=IFCCARTESIANPOINT((0.,0.,0.)); +#1026=IFCDIRECTION((0.,0.,1.)); +#1027=IFCDIRECTION((1.,0.,0.)); +#1028=IFCAXIS2PLACEMENT3D(#1025,#1026,#1027); +#1029=IFCPLANAREXTENT(1000.,1000.); +#1030=IFCTEXTLITERALWITHEXTENT('{{Description}}',#1028,.RIGHT.,#1029,'center'); +#1031=IFCCARTESIANPOINT((0.,0.,0.)); +#1032=IFCDIRECTION((0.,0.,1.)); +#1033=IFCDIRECTION((1.,0.,0.)); +#1034=IFCAXIS2PLACEMENT3D(#1031,#1032,#1033); +#1035=IFCPLANAREXTENT(1000.,1000.); +#1036=IFCTEXTLITERALWITHEXTENT('``round({{Qto_SpaceBaseQuantities.NetFloorArea}} or 0., 2)``',#1034,.RIGHT.,#1035,'center'); +#1037=IFCSHAPEREPRESENTATION(#19,'Annotation','Annotation2D',(#1024,#1030,#1036)); +#1038=IFCCARTESIANPOINT((0.,0.,0.)); #1039=IFCDIRECTION((1.,0.,0.)); -#1040=IFCAXIS2PLACEMENT3D(#1037,#1038,#1039); -#1041=IFCPLANAREXTENT(1000.,1000.); -#1042=IFCTEXTLITERALWITHEXTENT('{{material.Name}}',#1040,.RIGHT.,#1041,'center'); -#1043=IFCSHAPEREPRESENTATION(#19,'Annotation','Annotation2D',(#1042)); -#1044=IFCCARTESIANPOINT((0.,0.,0.)); -#1045=IFCDIRECTION((1.,0.,0.)); -#1046=IFCDIRECTION((0.,0.,1.)); -#1047=IFCAXIS2PLACEMENT3D(#1044,#1046,#1045); -#1048=IFCREPRESENTATIONMAP(#1047,#1043); -#1049=IFCTYPEPRODUCT('3hqzFtbSb56RjhAZNaB4Vh',$,'TYPE-TAG',$,'IfcAnnotation/TEXT',(#1050),(#1063),$); -#1050=IFCPROPERTYSET('3U7kaKt_nBUvD$U2ryif_H',$,'EPset_Annotation',$,(#1051)); -#1051=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('capsule-tag'),$); -#1052=IFCCARTESIANPOINT((0.,0.,0.)); -#1053=IFCDIRECTION((0.,0.,1.)); +#1040=IFCDIRECTION((0.,0.,1.)); +#1041=IFCAXIS2PLACEMENT3D(#1038,#1040,#1039); +#1042=IFCREPRESENTATIONMAP(#1041,#1037); +#1043=IFCTYPEPRODUCT('3Or$DhaVf6ygGBUpRb2PMs',$,'MATERIAL-TAG',$,'IfcAnnotation/TEXT',(#1044),(#1057),$); +#1044=IFCPROPERTYSET('2YfvIc6dPDdwWjERHZZlof',$,'EPset_Annotation',$,(#1045)); +#1045=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('rectangle-tag'),$); +#1046=IFCCARTESIANPOINT((0.,0.,0.)); +#1047=IFCDIRECTION((0.,0.,1.)); +#1048=IFCDIRECTION((1.,0.,0.)); +#1049=IFCAXIS2PLACEMENT3D(#1046,#1047,#1048); +#1050=IFCPLANAREXTENT(1000.,1000.); +#1051=IFCTEXTLITERALWITHEXTENT('{{material.Name}}',#1049,.RIGHT.,#1050,'center'); +#1052=IFCSHAPEREPRESENTATION(#19,'Annotation','Annotation2D',(#1051)); +#1053=IFCCARTESIANPOINT((0.,0.,0.)); #1054=IFCDIRECTION((1.,0.,0.)); -#1055=IFCAXIS2PLACEMENT3D(#1052,#1053,#1054); -#1056=IFCPLANAREXTENT(1000.,1000.); -#1057=IFCTEXTLITERALWITHEXTENT('{{type.Name}}',#1055,.RIGHT.,#1056,'center'); -#1058=IFCSHAPEREPRESENTATION(#19,'Annotation','Annotation2D',(#1057)); -#1059=IFCCARTESIANPOINT((0.,0.,0.)); -#1060=IFCDIRECTION((1.,0.,0.)); -#1061=IFCDIRECTION((0.,0.,1.)); -#1062=IFCAXIS2PLACEMENT3D(#1059,#1061,#1060); -#1063=IFCREPRESENTATIONMAP(#1062,#1058); -#1064=IFCTYPEPRODUCT('2SnSxJPiD9sh$NDx92OCSS',$,'NAME-TAG',$,'IfcAnnotation/TEXT',(#1065),(#1078),$); -#1065=IFCPROPERTYSET('3Lu86QVuv5nfjkuPLvdQSH',$,'EPset_Annotation',$,(#1066)); -#1066=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('capsule-tag'),$); -#1067=IFCCARTESIANPOINT((0.,0.,0.)); -#1068=IFCDIRECTION((0.,0.,1.)); +#1055=IFCDIRECTION((0.,0.,1.)); +#1056=IFCAXIS2PLACEMENT3D(#1053,#1055,#1054); +#1057=IFCREPRESENTATIONMAP(#1056,#1052); +#1058=IFCTYPEPRODUCT('01Hhk_DbjDROhyr91Hp$aV',$,'TYPE-TAG',$,'IfcAnnotation/TEXT',(#1059),(#1072),$); +#1059=IFCPROPERTYSET('0LyRD7lHr17uJo7XNzCvZq',$,'EPset_Annotation',$,(#1060)); +#1060=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('capsule-tag'),$); +#1061=IFCCARTESIANPOINT((0.,0.,0.)); +#1062=IFCDIRECTION((0.,0.,1.)); +#1063=IFCDIRECTION((1.,0.,0.)); +#1064=IFCAXIS2PLACEMENT3D(#1061,#1062,#1063); +#1065=IFCPLANAREXTENT(1000.,1000.); +#1066=IFCTEXTLITERALWITHEXTENT('{{type.Name}}',#1064,.RIGHT.,#1065,'center'); +#1067=IFCSHAPEREPRESENTATION(#19,'Annotation','Annotation2D',(#1066)); +#1068=IFCCARTESIANPOINT((0.,0.,0.)); #1069=IFCDIRECTION((1.,0.,0.)); -#1070=IFCAXIS2PLACEMENT3D(#1067,#1068,#1069); -#1071=IFCPLANAREXTENT(1000.,1000.); -#1072=IFCTEXTLITERALWITHEXTENT('{{Name}}',#1070,.RIGHT.,#1071,'center'); -#1073=IFCSHAPEREPRESENTATION(#19,'Annotation','Annotation2D',(#1072)); -#1074=IFCCARTESIANPOINT((0.,0.,0.)); -#1075=IFCDIRECTION((1.,0.,0.)); -#1076=IFCDIRECTION((0.,0.,1.)); -#1077=IFCAXIS2PLACEMENT3D(#1074,#1076,#1075); -#1078=IFCREPRESENTATIONMAP(#1077,#1073); +#1070=IFCDIRECTION((0.,0.,1.)); +#1071=IFCAXIS2PLACEMENT3D(#1068,#1070,#1069); +#1072=IFCREPRESENTATIONMAP(#1071,#1067); +#1073=IFCTYPEPRODUCT('0a8MHfTUzA5AV8H6p1bqC9',$,'NAME-TAG',$,'IfcAnnotation/TEXT',(#1074),(#1087),$); +#1074=IFCPROPERTYSET('2EboL9u1j68Q_Mp8dBa7ux',$,'EPset_Annotation',$,(#1075)); +#1075=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('capsule-tag'),$); +#1076=IFCCARTESIANPOINT((0.,0.,0.)); +#1077=IFCDIRECTION((0.,0.,1.)); +#1078=IFCDIRECTION((1.,0.,0.)); +#1079=IFCAXIS2PLACEMENT3D(#1076,#1077,#1078); +#1080=IFCPLANAREXTENT(1000.,1000.); +#1081=IFCTEXTLITERALWITHEXTENT('{{Name}}',#1079,.RIGHT.,#1080,'center'); +#1082=IFCSHAPEREPRESENTATION(#19,'Annotation','Annotation2D',(#1081)); +#1083=IFCCARTESIANPOINT((0.,0.,0.)); +#1084=IFCDIRECTION((1.,0.,0.)); +#1085=IFCDIRECTION((0.,0.,1.)); +#1086=IFCAXIS2PLACEMENT3D(#1083,#1085,#1084); +#1087=IFCREPRESENTATIONMAP(#1086,#1082); ENDSEC; END-ISO-10303-21; diff --git a/src/blenderbim/blenderbim/bim/data/libraries/IFC4 AU Library.ifc b/src/blenderbim/blenderbim/bim/data/libraries/IFC4 Furniture Library.ifc similarity index 100% rename from src/blenderbim/blenderbim/bim/data/libraries/IFC4 AU Library.ifc rename to src/blenderbim/blenderbim/bim/data/libraries/IFC4 Furniture Library.ifc diff --git a/src/blenderbim/blenderbim/bim/export_ifc.py b/src/blenderbim/blenderbim/bim/export_ifc.py index 5813e8081a..77ed7eaa50 100644 --- a/src/blenderbim/blenderbim/bim/export_ifc.py +++ b/src/blenderbim/blenderbim/bim/export_ifc.py @@ -93,6 +93,8 @@ class IfcExporter: try: if isinstance(obj, bpy.types.Material): continue + if obj.library: + continue tool.Collector.sync(obj) result = self.sync_object_placement(obj) if result: diff --git a/src/blenderbim/blenderbim/bim/import_ifc.py b/src/blenderbim/blenderbim/bim/import_ifc.py index 1ed67ac988..5c20aea10c 100644 --- a/src/blenderbim/blenderbim/bim/import_ifc.py +++ b/src/blenderbim/blenderbim/bim/import_ifc.py @@ -574,7 +574,13 @@ class IfcImporter: return if not self.does_element_likely_have_geometry_far_away(element): continue - shape = ifcopenshell.geom.create_shape(self.settings, element) + try: + shape = ifcopenshell.geom.create_shape(self.settings, element) + except: + try: + shape = ifcopenshell.geom.create_shape(self.settings_body_2d, element) + except: + continue m = shape.transformation.matrix.data mat = np.array( ([m[0], m[3], m[6], m[9]], [m[1], m[4], m[7], m[10]], [m[2], m[5], m[8], m[11]], [0, 0, 0, 1]) @@ -974,10 +980,14 @@ class IfcImporter: placement_matrix = self.get_element_matrix(product) vertex_list = [] for item in representation.Items: - if item.is_a("IfcCartesianPointList"): + if item.is_a("IfcCartesianPointList3D"): vertex_list.extend( mathutils.Vector(list(coordinates)) * self.unit_scale for coordinates in item.CoordList ) + elif item.is_a("IfcCartesianPointList2D"): + vertex_list.extend( + mathutils.Vector(list(coordinates)).to_3d() * self.unit_scale for coordinates in item.CoordList + ) elif item.is_a("IfcCartesianPoint"): vertex_list.append(mathutils.Vector(list(item.Coordinates)) * self.unit_scale) diff --git a/src/blenderbim/blenderbim/bim/module/csv/operator.py b/src/blenderbim/blenderbim/bim/module/csv/operator.py index c1b63e4dd3..daa85fbab9 100644 --- a/src/blenderbim/blenderbim/bim/module/csv/operator.py +++ b/src/blenderbim/blenderbim/bim/module/csv/operator.py @@ -101,6 +101,7 @@ class ImportCsvAttributes(bpy.types.Operator): new.sort = attribute["sort"] new.group = attribute["group"] new.summary = attribute["summary"] + new.formatting = attribute["formatting"] return {"FINISHED"} def invoke(self, context, event): @@ -123,7 +124,14 @@ class ExportCsvAttributes(bpy.types.Operator): data = { "query": tool.Search.export_filter_query(props.filter_groups), "attributes": [ - {"name": a.name, "header": a.header, "sort": a.sort, "group": a.group, "summary": a.summary} + { + "name": a.name, + "header": a.header, + "sort": a.sort, + "group": a.group, + "summary": a.summary, + "formatting": a.formatting, + } for a in props.csv_attributes ], } @@ -173,6 +181,7 @@ class ExportIfcCsv(bpy.types.Operator): sort = [] groups = [] summaries = [] + formatting = [] for attribute in props.csv_attributes: if attribute.sort != "NONE": sort.append({"name": attribute.name, "order": attribute.sort}) @@ -181,6 +190,9 @@ class ExportIfcCsv(bpy.types.Operator): if attribute.summary != "NONE": summaries.append({"name": attribute.name, "type": attribute.summary}) + if attribute.formatting != "{{value}}" and "{{value}}" in attribute.formatting: + formatting.append({"name": attribute.name, "format": attribute.formatting}) + sep = props.csv_custom_delimiter if props.csv_delimiter == "CUSTOM" else props.csv_delimiter ifc_csv.export( ifc_file, @@ -198,6 +210,7 @@ class ExportIfcCsv(bpy.types.Operator): sort=sort, groups=groups, summaries=summaries, + formatting=formatting, ) return {"FINISHED"} diff --git a/src/blenderbim/blenderbim/bim/module/csv/prop.py b/src/blenderbim/blenderbim/bim/module/csv/prop.py index 31a334e389..db1ae07071 100644 --- a/src/blenderbim/blenderbim/bim/module/csv/prop.py +++ b/src/blenderbim/blenderbim/bim/module/csv/prop.py @@ -58,6 +58,7 @@ class CsvAttribute(PropertyGroup): ("MAX", "Max", "Gets the maximum value of all rows"), ] ) + formatting: StringProperty(default="{{value}}", name="Formatting") class CsvProperties(PropertyGroup): @@ -94,4 +95,5 @@ class CsvProperties(PropertyGroup): should_show_sort: BoolProperty(default=False, name="Show Sorting") should_show_group: BoolProperty(default=False, name="Show Grouping") should_show_summary: BoolProperty(default=False, name="Show Summary") + should_show_formatting: BoolProperty(default=False, name="Show Formatting") should_load_from_memory: BoolProperty(default=False, name="Load from Memory") diff --git a/src/blenderbim/blenderbim/bim/module/csv/ui.py b/src/blenderbim/blenderbim/bim/module/csv/ui.py index c1e5c93222..425bb2cb8d 100644 --- a/src/blenderbim/blenderbim/bim/module/csv/ui.py +++ b/src/blenderbim/blenderbim/bim/module/csv/ui.py @@ -23,7 +23,7 @@ from blenderbim.bim.module.search.data import SearchData class BIM_PT_ifccsv(Panel): - bl_label = "CSV Import/Export" + bl_label = "Spreadsheet Import/Export" bl_idname = "BIM_PT_ifccsv" bl_options = {"DEFAULT_CLOSED"} bl_space_type = "PROPERTIES" @@ -86,6 +86,7 @@ class BIM_PT_ifccsv(Panel): row.prop(props, "should_show_sort", icon="SORTSIZE", text="") row.prop(props, "should_show_group", icon="OUTLINER_COLLECTION", text="") row.prop(props, "should_show_summary", icon="SYNTAX_ON", text="") + row.prop(props, "should_show_formatting", icon="CON_TRANSLIKE", text="") total = len(props.csv_attributes) for index, attribute in enumerate(props.csv_attributes): @@ -100,6 +101,8 @@ class BIM_PT_ifccsv(Panel): row.prop(attribute, "varies_value", text="") if props.should_show_summary: row.prop(attribute, "summary", text="") + if props.should_show_formatting: + row.prop(attribute, "formatting", text="") if total > 1: if index != 0: op = row.operator(f"bim.reorder_csv_attribute", icon="TRIA_UP", text="") diff --git a/src/blenderbim/blenderbim/bim/module/drawing/annotation.py b/src/blenderbim/blenderbim/bim/module/drawing/annotation.py index 10e1e59e5a..a4473764cc 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/annotation.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/annotation.py @@ -16,11 +16,13 @@ # You should have received a copy of the GNU General Public License # along with BlenderBIM Add-on. If not, see . -import bpy import os -import blenderbim.tool as tool -from mathutils import Vector +import bpy +import math import bmesh +import blenderbim.tool as tool +import ifcopenshell.util.element +from mathutils import Vector, Matrix class Annotator: @@ -90,6 +92,15 @@ class Annotator: return obj + @staticmethod + def add_vertex_to_annotation(obj): + verts_world_space = Annotator.get_placeholder_coords() + vert_local = obj.matrix_world.inverted() @ verts_world_space[0] + bm = tool.Blender.get_bmesh_for_mesh(obj.data, clean=True) + bm.verts.new(vert_local) + tool.Blender.apply_bmesh(obj.data, bm, obj) + return obj + @staticmethod def add_plane_to_annotation(obj, remove_face=False): # default order = bot left, top left, bot right, top right @@ -111,7 +122,7 @@ class Annotator: def get_annotation_obj(drawing, object_type, data_type): camera = tool.Ifc.get_object(drawing) co1, _, _, _ = Annotator.get_placeholder_coords(camera) - matrix_world = camera.matrix_world.copy() + matrix_world = tool.Drawing.get_camera_matrix(camera) matrix_world.translation = co1 collection = camera.BIMObjectProperties.collection @@ -142,6 +153,8 @@ class Annotator: data = bpy.data.curves.new(object_type, type="CURVE") data.dimensions = "3D" data.resolution_u = 2 + elif data_type == "empty": + data = None obj = bpy.data.objects.new(object_type, data) obj.matrix_world = matrix_world @@ -154,6 +167,13 @@ class Annotator: camera = bpy.context.scene.camera z_offset = camera.matrix_world.to_quaternion() @ Vector((0, 0, -1)) + + if ( + ifcopenshell.util.element.get_pset(tool.Ifc.get_entity(camera), "EPset_Drawing", "TargetView") + == "REFLECTED_PLAN_VIEW" + ): + z_offset *= -1 + y = camera.data.ortho_scale / 4 res_x = bpy.context.scene.render.resolution_x diff --git a/src/blenderbim/blenderbim/bim/module/drawing/decoration.py b/src/blenderbim/blenderbim/bim/module/drawing/decoration.py index 13c66efa5f..6b21e95bee 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/decoration.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/decoration.py @@ -520,7 +520,9 @@ class BaseDecorator: return matrix.inverted()[i].to_3d().normalized() text_dir_world_x_axis = get_basis_vector(obj.matrix_world) - camera_matrix = camera.matrix_world.normalized() + + camera_matrix = tool.Drawing.get_camera_matrix(camera) + text_dir = (camera_matrix.inverted().to_quaternion() @ text_dir_world_x_axis).to_2d().normalized() pos = location_3d_to_region_2d(region, region3d, text_world_position) diff --git a/src/blenderbim/blenderbim/bim/module/drawing/operator.py b/src/blenderbim/blenderbim/bim/module/drawing/operator.py index 232b845518..1285187b40 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/operator.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/operator.py @@ -70,15 +70,6 @@ class profile: print(self.task, timer() - self.start) -def open_with_user_command(user_command, path): - if user_command: - commands = eval(user_command) - for command in commands: - subprocess.Popen(command) - else: - webbrowser.open("file://" + path) - - class Operator: def execute(self, context): IfcStore.execute_ifc_operator(self, context) @@ -269,7 +260,7 @@ class CreateDrawing(bpy.types.Operator): with profile("Combine SVG layers"): svg_path = self.combine_svgs(context, underlay_svg, linework_svg, annotation_svg) - open_with_user_command(context.preferences.addons["blenderbim"].preferences.svg_command, svg_path) + tool.Drawing.open_with_user_command(context.preferences.addons["blenderbim"].preferences.svg_command, svg_path) if self.print_all: bpy.ops.bim.activate_drawing(drawing=original_drawing_id, camera_view_point=False) @@ -555,7 +546,7 @@ class CreateDrawing(bpy.types.Operator): if self.camera.data.BIMCameraProperties.calculate_shapely_surfaces: # shapely variant - group = root.findall(".//{http://www.w3.org/2000/svg}g")[0] + group = root.find("{http://www.w3.org/2000/svg}g") nm = group.attrib["{http://www.ifcopenshell.org/ns}name"] m4 = np.array(json.loads(group.attrib["{http://www.ifcopenshell.org/ns}plane"])) m3 = np.array(json.loads(group.attrib["{http://www.ifcopenshell.org/ns}matrix3"])) @@ -566,10 +557,21 @@ class CreateDrawing(bpy.types.Operator): m44[1][3] = m3[1][2] m44 = np.linalg.inv(m44) - projections = group.findall('.//{http://www.w3.org/2000/svg}g[@class="projection"]') or [] + elements_with_faces = set() + for element in drawing_elements.copy(): + obj = tool.Ifc.get_object(element) + if obj and obj.type == "MESH" and len(obj.data.polygons): + elements_with_faces.add(element.GlobalId) + projections = root.xpath( + ".//svg:g[contains(@class, 'projection')]", namespaces={"svg": "http://www.w3.org/2000/svg"} + ) + + boundary_lines = [] for projection in projections: - boundary_lines = [] + global_id = projection.attrib["{http://www.ifcopenshell.org/ns}guid"] + if global_id not in elements_with_faces: + continue for path in projection.findall("./{http://www.w3.org/2000/svg}path"): # Rounding is necessary to ensure coincident points are coincident start, end = [[round(float(o), 1) for o in co[1:].split(",")] for co in path.attrib["d"].split()] @@ -578,50 +580,55 @@ class CreateDrawing(bpy.types.Operator): # Extension by 0.5mm is necessary to ensure lines overlap with other diagonal lines start, end = tool.Drawing.extend_line(start, end, 0.5) boundary_lines.append(shapely.LineString([start, end])) - unioned_boundaries = shapely.union_all(shapely.GeometryCollection(boundary_lines)) - closed_polygons = shapely.polygonize(unioned_boundaries.geoms) - for polygon in closed_polygons.geoms: - # Less than 1mm2 is not worth styling on sheet - if polygon.area < 1: - continue - centroid = polygon.centroid - internal_point = centroid if polygon.contains(centroid) else polygon.representative_point() - if internal_point: - internal_point = [internal_point.x, internal_point.y] - a, b = self.drawing_to_model_co(m44, m4, internal_point, 0.0), self.drawing_to_model_co( - m44, m4, internal_point, -100.0 - ) - inside_elements = [e for e in tree.select(self.pythonize(a)) if not e.is_a("IfcAnnotation")] - if not inside_elements: - elements = [ - e - for e in tree.select_ray(self.pythonize(a), self.pythonize(b - a)) - if not e.instance.is_a("IfcAnnotation") - and tool.Cad.is_point_on_edge( - Vector(list(e.position)), (Vector(self.pythonize(a)), Vector(self.pythonize(b))) - ) - ] - if elements: - path = etree.Element("path") - d = ( - "M" - + " L".join( - [",".join([str(o) for o in co]) for co in polygon.exterior.coords[0:-1]] - ) + unioned_boundaries = shapely.union_all(shapely.GeometryCollection(boundary_lines)) + closed_polygons = shapely.polygonize(unioned_boundaries.geoms) + + for polygon in closed_polygons.geoms: + # Less than 1mm2 is not worth styling on sheet + if polygon.area < 1: + continue + centroid = polygon.centroid + internal_point = centroid if polygon.contains(centroid) else polygon.representative_point() + if internal_point: + internal_point = [internal_point.x, internal_point.y] + a, b = self.drawing_to_model_co(m44, m4, internal_point, 0.0), self.drawing_to_model_co( + m44, m4, internal_point, -100.0 + ) + inside_elements = [e for e in tree.select(self.pythonize(a)) if not e.is_a("IfcAnnotation")] + if not inside_elements: + elements = [ + e + for e in tree.select_ray(self.pythonize(a), self.pythonize(b - a)) + if not e.instance.is_a("IfcAnnotation") + and tool.Cad.is_point_on_edge( + Vector(list(e.position)), (Vector(self.pythonize(a)), Vector(self.pythonize(b))) + ) + ] + if elements: + path = etree.Element("path") + d = ( + "M" + + " L".join([",".join([str(o) for o in co]) for co in polygon.exterior.coords[0:-1]]) + + " Z" + ) + for interior in polygon.interiors: + d += ( + " M" + + " L".join([",".join([str(o) for o in co]) for co in interior.coords[0:-1]]) + " Z" ) - for interior in polygon.interiors: - d += ( - " M" - + " L".join([",".join([str(o) for o in co]) for co in interior.coords[0:-1]]) - + " Z" - ) - path.attrib["d"] = d - classes = self.get_svg_classes(ifc.by_id(elements[0].instance.id())) - classes.append("surface") - path.set("class", " ".join(list(classes))) - group.insert(0, path) + path.attrib["d"] = d + classes = self.get_svg_classes(ifc.by_id(elements[0].instance.id())) + classes.append(f"intpoint-{internal_point}") + classes.append(f"ab-{a}, {b}") + for i, ray_result in enumerate(elements): + classes.append(f"el{i}-{ray_result.instance.id()}") + classes.append(f"el{i}-pos-{list(ray_result.position)}") + classes.append(f"el{i}-dst-{ray_result.distance}") + classes.append("surface") + path.set("class", " ".join(list(classes))) + group.insert(0, path) if self.camera.data.BIMCameraProperties.calculate_svgfill_surfaces: results = etree.tostring(root).decode("utf8") @@ -636,7 +643,7 @@ class CreateDrawing(bpy.types.Operator): dom1 = parseString(svg_data_1) svg1 = dom1.childNodes[0] - groups1 = [g for g in yield_groups(svg1) if g.getAttribute("class") == "projection"] + groups1 = [g for g in yield_groups(svg1) if "projection" in g.getAttribute("class")] ls_groups = ifcopenshell.ifcopenshell_wrapper.svg_to_line_segments(results, "projection") @@ -840,7 +847,7 @@ class CreateDrawing(bpy.types.Operator): self.serialiser.setPolygonal(True) self.serialiser.setUseHlrPoly(True) # Objects with more than these edges are rendered as wireframe instead of HLR for optimisation - self.serialiser.setProfileThreshold(1000) + self.serialiser.setProfileThreshold(10000) self.serialiser.setUseNamespace(True) self.serialiser.setAlwaysProject(True) self.serialiser.setAutoElevation(False) @@ -853,6 +860,8 @@ class CreateDrawing(bpy.types.Operator): self.serialiser.setScale(self.scale) self.serialiser.setSubtractionSettings(ifcopenshell.ifcopenshell_wrapper.ALWAYS) self.serialiser.setUsePrefiltering(True) # See #3359 + self.serialiser.setUnifyInputs(True) + self.serialiser.setSegmentProjection(True) if target_view == "REFLECTED_PLAN_VIEW": self.serialiser.setMirrorY(True) # tree = ifcopenshell.geom.tree() @@ -912,7 +921,7 @@ class CreateDrawing(bpy.types.Operator): # Drawing convention states that same objects classes with the same material are merged when cut. join_criteria = ["class", "material.Name", 'r"Pset.*Common"."Status"'] - group = root.findall(".//{http://www.w3.org/2000/svg}g")[0] + group = root.find("{http://www.w3.org/2000/svg}g") joined_paths = {} self.is_manifold_cache = {} @@ -920,9 +929,15 @@ class CreateDrawing(bpy.types.Operator): for el in root.findall(".//{http://www.w3.org/2000/svg}g[@{http://www.ifcopenshell.org/ns}guid]"): element = ifc.by_guid(el.get("{http://www.ifcopenshell.org/ns}guid")) - classes = self.get_svg_classes(element) - classes.append("cut") - el.set("class", " ".join(classes)) + if "projection" in el.get("class", "").split(): + classes = self.get_svg_classes(element) + classes.append("projection") + el.set("class", " ".join(classes)) + continue + else: + classes = self.get_svg_classes(element) + classes.append("cut") + el.set("class", " ".join(classes)) obj = tool.Ifc.get_object(element) if not self.is_manifold(obj): @@ -1044,9 +1059,12 @@ class CreateDrawing(bpy.types.Operator): # IfcConvert puts the projection afterwards which is not correct since # projection should be drawn underneath the cut. group = root.find("{http://www.w3.org/2000/svg}g") - projection = group.find("{http://www.w3.org/2000/svg}g[@class='projection']") - projection.getparent().remove(projection) - group.insert(0, projection) + projections = root.xpath( + ".//svg:g[contains(@class, 'projection')]", namespaces={"svg": "http://www.w3.org/2000/svg"} + ) + for projection in projections: + projection.getparent().remove(projection) + group.insert(0, projection) def generate_annotation(self, context): if not ifcopenshell.util.element.get_pset(self.drawing, "EPset_Drawing", "HasAnnotation"): @@ -1260,11 +1278,15 @@ class CreateSheets(bpy.types.Operator, Operator): # These variables will be made available to the evaluated commands svg = references["SHEET"] - basename = os.path.basename(svg) - path = os.path.dirname(svg) pdf = os.path.splitext(svg)[0] + ".pdf" - eps = os.path.splitext(svg)[0] + ".eps" - dxf = os.path.splitext(svg)[0] + ".dxf" + replacements = { + "svg": svg, + "basename": os.path.basename(svg), + "path": os.path.dirname(svg), + "pdf": pdf, + "eps": os.path.splitext(svg)[0] + ".eps", + "dxf": os.path.splitext(svg)[0] + ".dxf", + } has_sheet_reference = False for reference in tool.Drawing.get_document_references(sheet): @@ -1297,22 +1319,23 @@ class CreateSheets(bpy.types.Operator, Operator): if svg2pdf_command: # With great power comes great responsibility. Example: - # [['inkscape', svg, '-o', pdf]] - commands = eval(svg2pdf_command) + # [["inkscape", "svg", "-o", "pdf"]] + commands = json.loads(svg2pdf_command) for command in commands: - subprocess.run(command) + subprocess.run([replacements.get(c, c) for c in command]) if svg2dxf_command: # With great power comes great responsibility. Example: - # [['inkscape', svg, '-o', eps], ['pstoedit', '-dt', '-f', 'dxf:-polyaslines -mm', eps, dxf, '-psarg', '-dNOSAFER']] - commands = eval(svg2dxf_command) + # [["inkscape", "svg", "-o", "eps"], ["pstoedit", "-dt", "-f", "dxf:-polyaslines -mm", "eps", "dxf", "-psarg", "-dNOSAFER"]] + commands = json.loads(svg2dxf_command) for command in commands: - subprocess.run(command) + command[0] = shutil.which(command[0]) or command[0] + subprocess.run([replacements.get(c, c) for c in command]) if svg2pdf_command: - open_with_user_command(context.preferences.addons["blenderbim"].preferences.pdf_command, pdf) + tool.Drawing.open_with_user_command(context.preferences.addons["blenderbim"].preferences.pdf_command, pdf) else: - open_with_user_command(context.preferences.addons["blenderbim"].preferences.svg_command, svg) + tool.Drawing.open_with_user_command(context.preferences.addons["blenderbim"].preferences.svg_command, svg) class SelectAllDrawings(bpy.types.Operator): @@ -1379,7 +1402,9 @@ class OpenDrawing(bpy.types.Operator): return {"CANCELLED"} for drawing_uri in drawing_uris: - open_with_user_command(context.preferences.addons["blenderbim"].preferences.svg_command, drawing_uri) + tool.Drawing.open_with_user_command( + context.preferences.addons["blenderbim"].preferences.svg_command, drawing_uri + ) return {"FINISHED"} @@ -1415,6 +1440,7 @@ class ActivateModel(bpy.types.Operator): if view3d_context: bpy.ops.object.hide_view_clear(view3d_context) + bpy.ops.bim.activate_status_filters() subcontext = ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Model", "Body", "MODEL_VIEW") @@ -1544,6 +1570,9 @@ class RemoveDrawing(bpy.types.Operator, Operator): removed_drawings = [drawing.id() for drawing in drawings] for drawing in drawings: + sheet_references = tool.Drawing.get_sheet_references(drawing) + for reference in sheet_references: + bpy.ops.bim.remove_drawing_from_sheet(reference=reference.id()) core.remove_drawing(tool.Ifc, tool.Drawing, drawing=drawing) diff --git a/src/blenderbim/blenderbim/bim/module/drawing/prop.py b/src/blenderbim/blenderbim/bim/module/drawing/prop.py index cab0f75ddc..0578ff8105 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/prop.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/prop.py @@ -502,6 +502,8 @@ ANNOTATION_TYPES_DATA = { "PLAN_LEVEL": ("Level (Plan)", "", "SORTBYEXT", "curve"), "SECTION_LEVEL": ("Level (Section)", "", "TRIA_DOWN", "curve"), "BREAKLINE": ("Breakline", "", "FCURVE", "mesh"), + "SYMBOL": ("Symbol", "", "KEYFRAME", "empty"), + "MULTI_SYMBOL": ("Multi-Symbol", "", "OUTLINER_DATA_POINTCLOUD", "mesh"), "LINEWORK": ("Line", "", "SNAP_MIDPOINT", "mesh"), "BATTING": ("Batting", "Add batting annotation.\nThickness could be changed through Thickness property of BBIM_Batting property set", "FORCE_FORCE", "mesh"), "REVISION_CLOUD":("Revision Cloud", "Add revision cloud", "VOLUME_DATA", "mesh"), diff --git a/src/blenderbim/blenderbim/bim/module/drawing/sheeter.py b/src/blenderbim/blenderbim/bim/module/drawing/sheeter.py index e309bf0997..f452bcc158 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/sheeter.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/sheeter.py @@ -174,6 +174,8 @@ class SheetBuilder: ET.register_namespace("", "http://www.w3.org/2000/svg") layout_path = tool.Drawing.get_document_uri(sheet, "LAYOUT") + if not os.path.exists(layout_path): + return layout_tree = ET.parse(layout_path) layout_root = layout_tree.getroot() diff --git a/src/blenderbim/blenderbim/bim/module/drawing/svgwriter.py b/src/blenderbim/blenderbim/bim/module/drawing/svgwriter.py index 48783336bd..23cbf1ab0f 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/svgwriter.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/svgwriter.py @@ -326,11 +326,12 @@ class SvgWriter: # We have to decide whether this should come from Blender or from IFC. # For the moment, for convenience of experimenting with ideas, it comes # from Blender. In the future, it should probably come from IFC. - if not isinstance(obj.data, bpy.types.Mesh): - return - classes = self.get_attribute_classes(obj) - if len(obj.data.vertices) and not len(obj.data.edges): + if obj.data is None: + return self.draw_empty_annotation(obj, classes) + elif not isinstance(obj.data, bpy.types.Mesh): + return + elif len(obj.data.vertices) and not len(obj.data.edges): return self.draw_point_annotation(obj, classes) elif len(obj.data.polygons) == 0: return self.draw_edge_annotation(obj, classes) @@ -774,11 +775,14 @@ class SvgWriter: "text-anchor": text_anchor, } - def add_fill_bg(self, element): - element = element.copy() + def add_fill_bg(self, element, copy=True): + if copy: + element = element.copy() if hasattr(element, "xml"): attrib = element.xml.attrib - else: + elif isinstance(element, ET.Element): + attrib = element.attrib + else: # assuming it's svgwrite.base.BaseElement attrib = element.attribs attrib["filter"] = "url(#fill-background)" return element @@ -802,7 +806,7 @@ class SvgWriter: text_dir_world_x_axis = get_basis_vector(text_obj.matrix_world) # RCP cameras may be scaled, so reset scales. - camera_matrix = self.camera.matrix_world.normalized() + camera_matrix = tool.Drawing.get_camera_matrix(self.camera) text_dir = (camera_matrix.inverted().to_quaternion() @ text_dir_world_x_axis).to_2d().normalized() angle = math.degrees(-text_dir.angle_signed(Vector((1, 0)))) @@ -831,7 +835,12 @@ class SvgWriter: field.attrib["class"] = classes_str if fill_bg: - self.svg.add(self.add_fill_bg(symbol_svg)) + symbol_copied = symbol_svg.copy() + for text_tag in symbol_copied.xml.findall("text"): + self.add_fill_bg(text_tag, copy=False) + # NOTE: in case we'll later need to add fill-bg for the entire symbol: + # self.add_fill_bg(symbol_svg, copy=False) + self.svg.add(symbol_copied) self.svg.add(symbol_svg) return None @@ -854,6 +863,27 @@ class SvgWriter: self.svg.add(tag) line_number += len(tag.elements) + def draw_empty_annotation(self, obj, classes): + x_offset = self.raw_width / 2 + y_offset = self.raw_height / 2 + + point = self.project_point_onto_camera(obj.matrix_world.translation) + + element = tool.Ifc.get_entity(obj) + svg_id = ifcopenshell.util.element.get_pset(element, "EPset_Annotation", "Symbol") + if not svg_id: + # EPset_AnnotationSurveyArea is not standard! See bSI-4.3 proposal #660. + svg_id = ifcopenshell.util.element.get_pset(element, "EPset_AnnotationSurveyArea", "PointType") + if not svg_id: + svg_id = str(ifcopenshell.util.element.get_predefined_type(element)) + if not svg_id: + return + + point = Vector(((x_offset + point.x), (y_offset - point.y))) + symbol_position_svg = point * self.svg_scale + self.svg.add(self.svg.use(f"#{svg_id}", insert=symbol_position_svg)) + + def draw_point_annotation(self, obj, classes): x_offset = self.raw_width / 2 y_offset = self.raw_height / 2 @@ -862,12 +892,14 @@ class SvgWriter: projected_points = [self.project_point_onto_camera(matrix_world @ v.co) for v in obj.data.vertices] element = tool.Ifc.get_entity(obj) - svg_id = str(ifcopenshell.util.element.get_predefined_type(element)) - - # EPset_AnnotationSurveyArea is not standard! See bSI-4.3 proposal #660. - point_type = ifcopenshell.util.element.get_pset(element, "EPset_AnnotationSurveyArea", "PointType") - if point_type: - svg_id += f"-{point_type}" + svg_id = ifcopenshell.util.element.get_pset(element, "EPset_Annotation", "Symbol") + if not svg_id: + # EPset_AnnotationSurveyArea is not standard! See bSI-4.3 proposal #660. + svg_id = ifcopenshell.util.element.get_pset(element, "EPset_AnnotationSurveyArea", "PointType") + if not svg_id: + svg_id = str(ifcopenshell.util.element.get_predefined_type(element)) + if not svg_id: + return for symbol_position in projected_points: symbol_position = Vector(((x_offset + symbol_position.x), (y_offset - symbol_position.y))) diff --git a/src/blenderbim/blenderbim/bim/module/georeference/operator.py b/src/blenderbim/blenderbim/bim/module/georeference/operator.py index eec2dab9ca..ed6d96d7d8 100644 --- a/src/blenderbim/blenderbim/bim/module/georeference/operator.py +++ b/src/blenderbim/blenderbim/bim/module/georeference/operator.py @@ -76,7 +76,7 @@ class SetIfcGridNorth(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.set_ifc_grid_north" bl_label = "Set IFC Grid North" bl_options = {"REGISTER", "UNDO"} - bl_description = "Set IFC grid north" + bl_description = "Set IFC grid north based on current Blender North Offset" def _execute(self, context): core.set_ifc_grid_north(tool.Georeference) @@ -86,7 +86,7 @@ class SetBlenderGridNorth(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.set_blender_grid_north" bl_label = "Set Blender Grid North" bl_options = {"REGISTER", "UNDO"} - bl_description = "Set Blender grid north" + bl_description = "Set Blender North Offset based on current IFC grid north" def _execute(self, context): core.set_blender_grid_north(tool.Georeference) @@ -125,7 +125,7 @@ class SetIfcTrueNorth(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.set_ifc_true_north" bl_label = "Set IFC True North" bl_options = {"REGISTER", "UNDO"} - bl_description = "Set IFC True north" + bl_description = "Set IFC true north based on current Blender North Offset" def _execute(self, context): core.set_ifc_true_north(tool.Georeference) @@ -135,7 +135,7 @@ class SetBlenderTrueNorth(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.set_blender_true_north" bl_label = "Set Blender True North" bl_options = {"REGISTER", "UNDO"} - bl_description = "Set Blender true north" + bl_description = "Set Blender North Offset based on current IFC true north" def _execute(self, context): core.set_blender_true_north(tool.Georeference) diff --git a/src/blenderbim/blenderbim/bim/module/material/ui.py b/src/blenderbim/blenderbim/bim/module/material/ui.py index 5c3b678a88..dc50a8db14 100644 --- a/src/blenderbim/blenderbim/bim/module/material/ui.py +++ b/src/blenderbim/blenderbim/bim/module/material/ui.py @@ -58,6 +58,8 @@ class BIM_PT_materials(Panel): row.alignment = "RIGHT" if self.props.material_type == "IfcMaterial": + if not self.props.active_material_id: + row.operator("bim.add_material", text="", icon="ADD") if self.props.materials and self.props.active_material_index < len(self.props.materials): material = self.props.materials[self.props.active_material_index] if material.ifc_definition_id: @@ -66,13 +68,10 @@ class BIM_PT_materials(Panel): row.operator("bim.disable_editing_material", text="", icon="CANCEL").material = material.ifc_definition_id self.draw_editable_material_attributes_ui() else: - row.operator("bim.add_material", text="", icon="ADD") op = row.operator("bim.select_by_material", text="", icon="RESTRICT_SELECT_OFF") op.material = material.ifc_definition_id row.operator("bim.enable_editing_material", text="", icon="GREASEPENCIL").material = material.ifc_definition_id row.operator("bim.remove_material", text="", icon="X").material = material.ifc_definition_id - else: - row.operator("bim.add_material", text="", icon="ADD") else: row.operator("bim.add_material_set", text="", icon="ADD").set_type = self.props.material_type if self.props.materials and self.props.active_material_index < len(self.props.materials): diff --git a/src/blenderbim/blenderbim/bim/module/model/mep.py b/src/blenderbim/blenderbim/bim/module/model/mep.py index 75681f9166..e6aaa734da 100644 --- a/src/blenderbim/blenderbim/bim/module/model/mep.py +++ b/src/blenderbim/blenderbim/bim/module/model/mep.py @@ -402,15 +402,23 @@ class MEPGenerator: return len(segments_data) == 0 def pack_return_data(fitting_type, ports, segments_data): + packed_data = {"fitting_type": fitting_type} + + if predefined_type == "OBSTRUCTION": + return packed_data + for port in ports: port_local_position = V(*port.ObjectPlacement.RelativePlacement.Location.Coordinates) if tool.Cad.is_x(port_local_position.length, 0.0): start_port = port break + connected_port = tool.System.get_connected_port(start_port) connected_element = tool.System.get_port_relating_element(connected_port) element_type = ifcopenshell.util.element.get_type(connected_element) - return {"fitting_type": fitting_type, "start_port_match": element_type == segments_data[0][0]} + packed_data["start_port_match"] = element_type == segments_data[0][0] + + return packed_data fitting_types = tool.Ifc.get().by_type(self.get_mep_element_class_name(segments[0], "FittingType")) for fitting_type in fitting_types: diff --git a/src/blenderbim/blenderbim/bim/module/model/ui.py b/src/blenderbim/blenderbim/bim/module/model/ui.py index 9ff0b0a282..6a0b8005f8 100644 --- a/src/blenderbim/blenderbim/bim/module/model/ui.py +++ b/src/blenderbim/blenderbim/bim/module/model/ui.py @@ -156,15 +156,14 @@ class BIM_PT_authoring(Panel): class BIM_PT_Grids(Panel): bl_label = "Grids" bl_idname = "BIM_PT_Grids" - bl_space_type = "VIEW_3D" - bl_region_type = "UI" bl_options = {"DEFAULT_CLOSED"} - bl_category = "BlenderBIM" + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "scene" + bl_parent_id = "BIM_PT_project_setup" def draw(self, context): - self.animation_props = context.scene.BIMAnimationProperties - row = self.layout.row() - row.operator("mesh.add_grid", icon="ADD", text="Add Grids") + self.layout.row().operator("mesh.add_grid", icon="ADD", text="Add Grids") class BIM_PT_array(bpy.types.Panel): diff --git a/src/blenderbim/blenderbim/bim/module/model/workspace.py b/src/blenderbim/blenderbim/bim/module/model/workspace.py index ba0ac78390..998de01b1b 100644 --- a/src/blenderbim/blenderbim/bim/module/model/workspace.py +++ b/src/blenderbim/blenderbim/bim/module/model/workspace.py @@ -568,7 +568,8 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator): if not bpy.context.selected_objects: return if self.active_material_usage == "LAYER2": - bpy.ops.bim.align_wall(align_type="CENTERLINE") + if bpy.ops.bim.align_wall.poll(): + bpy.ops.bim.align_wall(align_type="CENTERLINE") else: bpy.ops.bim.align_product(align_type="CENTERLINE") diff --git a/src/blenderbim/blenderbim/bim/module/project/__init__.py b/src/blenderbim/blenderbim/bim/module/project/__init__.py index 3fe18e7f2f..977bc54c64 100644 --- a/src/blenderbim/blenderbim/bim/module/project/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/project/__init__.py @@ -47,6 +47,7 @@ classes = ( operator.UnlinkIfc, operator.UnloadLink, operator.UnloadProject, + operator.ReloadLink, prop.LibraryElement, prop.FilterCategory, prop.Link, diff --git a/src/blenderbim/blenderbim/bim/module/project/operator.py b/src/blenderbim/blenderbim/bim/module/project/operator.py index b510494fd7..6c47978d98 100644 --- a/src/blenderbim/blenderbim/bim/module/project/operator.py +++ b/src/blenderbim/blenderbim/bim/module/project/operator.py @@ -850,7 +850,7 @@ class UnloadLink(bpy.types.Operator): for scene in bpy.data.scenes: if scene.library and scene.library.filepath == filepath: bpy.data.scenes.remove(scene) - link = context.scene.BIMProjectProperties.links.get(filepath) + link = context.scene.BIMProjectProperties.links.get(self.filepath) link.is_loaded = False return {"FINISHED"} @@ -866,7 +866,7 @@ class LoadLink(bpy.types.Operator): filepath = self.filepath if not os.path.isabs(filepath): filepath = os.path.abspath(os.path.join(bpy.path.abspath("//"), filepath)) - with bpy.data.libraries.load(self.filepath, link=True) as (data_from, data_to): + with bpy.data.libraries.load(filepath, link=True) as (data_from, data_to): data_to.scenes = data_from.scenes for scene in bpy.data.scenes: if not scene.library or scene.library.filepath != filepath: @@ -875,11 +875,31 @@ class LoadLink(bpy.types.Operator): if "IfcProject" not in child.name: continue bpy.data.scenes[0].collection.children.link(child) - link = context.scene.BIMProjectProperties.links.get(filepath) + link = context.scene.BIMProjectProperties.links.get(self.filepath) link.is_loaded = True return {"FINISHED"} +class ReloadLink(bpy.types.Operator): + bl_idname = "bim.reload_link" + bl_label = "Reload Link" + bl_options = {"REGISTER", "UNDO"} + bl_description = "Reload the selected file" + filepath: bpy.props.StringProperty() + + def execute(self, context): + def get_linked_ifcs(): + selected_filename = os.path.basename(self.filepath) + return [ + c.library + for c in bpy.data.collections + if "IfcProject" in c.name and c.library and os.path.basename(c.library.filepath) == selected_filename + ] + for library in get_linked_ifcs() or []: + library.reload() + return {"FINISHED"} + + class ToggleLinkSelectability(bpy.types.Operator): bl_idname = "bim.toggle_link_selectability" bl_label = "Toggle Link Selectability" @@ -890,6 +910,9 @@ class ToggleLinkSelectability(bpy.types.Operator): def execute(self, context): props = context.scene.BIMProjectProperties link = props.links.get(self.link) + self.filepath = self.link + if not os.path.isabs(self.filepath): + self.filepath = os.path.abspath(os.path.join(bpy.path.abspath("//"), self.filepath)) for collection in self.get_linked_collections(): collection.hide_select = not collection.hide_select link.is_selectable = not collection.hide_select @@ -897,7 +920,7 @@ class ToggleLinkSelectability(bpy.types.Operator): def get_linked_collections(self): return [ - c for c in bpy.data.collections if "IfcProject" in c.name and c.library and c.library.filepath == self.link + c for c in bpy.data.collections if "IfcProject" in c.name and c.library and c.library.filepath == self.filepath ] @@ -912,6 +935,9 @@ class ToggleLinkVisibility(bpy.types.Operator): def execute(self, context): props = context.scene.BIMProjectProperties link = props.links.get(self.link) + self.filepath = self.link + if not os.path.isabs(self.filepath): + self.filepath = os.path.abspath(os.path.join(bpy.path.abspath("//"), self.filepath)) if self.mode == "WIREFRAME": self.toggle_wireframe(link) elif self.mode == "VISIBLE": @@ -948,7 +974,7 @@ class ToggleLinkVisibility(bpy.types.Operator): def get_linked_collections(self): return [ - c for c in bpy.data.collections if "IfcProject" in c.name and c.library and c.library.filepath == self.link + c for c in bpy.data.collections if "IfcProject" in c.name and c.library and c.library.filepath == self.filepath ] @@ -1037,6 +1063,7 @@ class ExportIFC(bpy.types.Operator): settings.json_compact = self.json_compact ifc_exporter = export_ifc.IfcExporter(settings) + print("Starting export") settings.logger.info("Starting export") ifc_exporter.export() settings.logger.info("Export finished in {:.2f} seconds".format(time.time() - start)) diff --git a/src/blenderbim/blenderbim/bim/module/project/ui.py b/src/blenderbim/blenderbim/bim/module/project/ui.py index e0a2eea7e5..8b61c4320d 100644 --- a/src/blenderbim/blenderbim/bim/module/project/ui.py +++ b/src/blenderbim/blenderbim/bim/module/project/ui.py @@ -375,6 +375,8 @@ class BIM_UL_links(UIList): op.mode = "VISIBLE" op = row.operator("bim.unload_link", text="", icon="UNLINKED") op.filepath = item.name + op = row.operator("bim.reload_link", text="", icon="FILE_REFRESH") + op.filepath = item.name else: row.prop(item, "name", text="") op = row.operator("bim.load_link", text="", icon="LINKED") diff --git a/src/blenderbim/blenderbim/bim/module/root/data.py b/src/blenderbim/blenderbim/bim/module/root/data.py index 1efb72b543..6b91e18ab8 100644 --- a/src/blenderbim/blenderbim/bim/module/root/data.py +++ b/src/blenderbim/blenderbim/bim/module/root/data.py @@ -49,8 +49,8 @@ class IfcClassData: @classmethod def ifc_products(cls): products = [ - "IfcElement", "IfcElementType", + "IfcElement", "IfcSpatialElement", "IfcSpatialElementType", "IfcGroup", @@ -62,8 +62,8 @@ class IfcClassData: version = tool.Ifc.get_schema() if version == "IFC2X3": products = [ - "IfcElement", "IfcElementType", + "IfcElement", "IfcSpatialStructureElement", "IfcGroup", "IfcStructuralItem", diff --git a/src/blenderbim/blenderbim/bim/module/search/data.py b/src/blenderbim/blenderbim/bim/module/search/data.py index b34d80148f..9907ea8f85 100644 --- a/src/blenderbim/blenderbim/bim/module/search/data.py +++ b/src/blenderbim/blenderbim/bim/module/search/data.py @@ -104,7 +104,7 @@ class SelectSimilarData: psets = ifcopenshell.util.element.get_psets(element, psets_only=True) for pset, properties in psets.items(): if pset.endswith("Common"): - keys.extend([f'r".*Common"."{name}"' for name in properties.keys() if name != "id"]) + keys.extend([f'/.*Common/."{name}"' for name in properties.keys() if name != "id"]) else: keys.extend([f"{pset}.{name}" for name in properties.keys() if name != "id"]) return [(k, k, "") for k in keys] diff --git a/src/blenderbim/blenderbim/bim/module/tester/data.py b/src/blenderbim/blenderbim/bim/module/tester/data.py new file mode 100644 index 0000000000..742b471f89 --- /dev/null +++ b/src/blenderbim/blenderbim/bim/module/tester/data.py @@ -0,0 +1,45 @@ +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2023 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + +import bpy +import blenderbim.tool as tool + + +def refresh(): + TesterData.is_loaded = False + + +class TesterData: + data = {} + is_loaded = False + + @classmethod + def load(cls): + cls.data = {"has_report": cls.has_report(), "specification": cls.specification()} + cls.is_loaded = True + + @classmethod + def has_report(cls): + return tool.Tester.report + + @classmethod + def specification(cls): + if not tool.Tester.report: + return {} + props = bpy.context.scene.IfcTesterProperties + return tool.Tester.report[props.active_specification_index] diff --git a/src/blenderbim/blenderbim/bim/module/tester/operator.py b/src/blenderbim/blenderbim/bim/module/tester/operator.py index 0164f0a11d..4b88c3ae4b 100644 --- a/src/blenderbim/blenderbim/bim/module/tester/operator.py +++ b/src/blenderbim/blenderbim/bim/module/tester/operator.py @@ -21,15 +21,16 @@ import bpy import time import tempfile import webbrowser -import json import ifctester import ifctester.ids import ifctester.reporter import ifcopenshell import blenderbim.tool as tool +import blenderbim.bim.handler +from blenderbim.bim.module.tester.data import TesterData -class ExecuteIfcTester(bpy.types.Operator): +class ExecuteIfcTester(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.execute_ifc_tester" bl_label = "Execute IfcTester" @@ -54,28 +55,28 @@ class ExecuteIfcTester(bpy.types.Operator): print("Finished loading:", time.time() - start) start = time.time() specs.validate(ifc) - + print("Finished validating:", time.time() - start) start = time.time() if props.generate_html_report: engine = ifctester.reporter.Html(specs) - engine.report() + engine.report() engine.to_file(output) webbrowser.open("file://" + output) - + report = None - report = ifctester.reporter.Json(specs).report()['specifications'] + report = ifctester.reporter.Json(specs).report()["specifications"] if report: - props.has_report = True - props.report = json.dumps(report) + tool.Tester.specs = specs + tool.Tester.report = report props.specifications.clear() - c=0 for spec in report: new_spec = props.specifications.add() - new_spec.name = spec['name'] - new_spec.status = spec['status'] + new_spec.name = spec["name"] + new_spec.status = spec["status"] + blenderbim.bim.handler.refresh_ui_data() return {"FINISHED"} @@ -118,23 +119,24 @@ class SelectRequirement(bpy.types.Operator): bl_label = "Select Specification" bl_options = {"REGISTER", "UNDO"} spec_index: bpy.props.IntProperty() - req_index: bpy.props.IntProperty() + req_index: bpy.props.IntProperty() def execute(self, context): props = context.scene.IfcTesterProperties - report = json.loads(props.report) + report = tool.Tester.report props.old_index = self.spec_index - failed_entities = report[self.spec_index]['requirements'] [self.req_index]['failed_entities'] + failed_entities = report[self.spec_index]["requirements"][self.req_index]["failed_entities"] props.n_entities = len(failed_entities) props.has_entities = True if props.n_entities > 0 else False props.failed_entities.clear() for e in failed_entities: - new_entity = props.failed_entities.add() - new_entity.element = e['element'] - new_entity.reason = e['reason'] + new_entity = props.failed_entities.add() + new_entity.element = e["element"] + new_entity.reason = e["reason"] return {"FINISHED"} + class SelectEntity(bpy.types.Operator): bl_idname = "bim.select_entity" bl_label = "Select Entity" @@ -142,33 +144,28 @@ class SelectEntity(bpy.types.Operator): ifc_id: bpy.props.IntProperty() def execute(self, context): - bpy.ops.object.select_all(action='DESELECT') + bpy.ops.object.select_all(action="DESELECT") for obj in context.scene.objects: if obj.BIMObjectProperties.ifc_definition_id == self.ifc_id: obj.select_set(True) bpy.context.view_layer.objects.active = obj return {"FINISHED"} - + + class ExportBcf(bpy.types.Operator): bl_idname = "bim.export_bcf" bl_label = "Export BCF" bl_options = {"REGISTER", "UNDO"} + filter_glob: bpy.props.StringProperty(default="*.bcf", options={"HIDDEN"}) + filepath: bpy.props.StringProperty(subtype="FILE_PATH") def execute(self, context): - props = context.scene.IfcTesterProperties - if tool.Ifc.get(): - ifc = tool.Ifc.get() - else: - ifc = ifcopenshell.open(props.ifc_file) - with tempfile.TemporaryDirectory() as dirpath: - output = os.path.join(dirpath, "{}.bcf".format(props.specs)) - specs = ifctester.ids.open(props.specs) - specs.validate(ifc) - bcf_reporter = ifctester.reporter.Bcf(specs) - bcf_reporter.report() - bcf_reporter.to_file(output) - print("Finished exporting!") - self.report({"INFO"}, 'Finished exporting!') - + bcf_reporter = ifctester.reporter.Bcf(tool.Tester.specs) + bcf_reporter.report() + bcf_reporter.to_file(self.filepath) + self.report({"INFO"}, "Finished exporting!") return {"FINISHED"} + def invoke(self, context, event): + context.window_manager.fileselect_add(self) + return {"RUNNING_MODAL"} diff --git a/src/blenderbim/blenderbim/bim/module/tester/prop.py b/src/blenderbim/blenderbim/bim/module/tester/prop.py index 63637301a3..b68600f053 100644 --- a/src/blenderbim/blenderbim/bim/module/tester/prop.py +++ b/src/blenderbim/blenderbim/bim/module/tester/prop.py @@ -16,6 +16,7 @@ # You should have received a copy of the GNU General Public License # along with BlenderBIM Add-on. If not, see . +from blenderbim.bim.module.tester.data import TesterData from blenderbim.bim.prop import StrProperty from bpy.types import PropertyGroup from bpy.props import ( @@ -34,8 +35,8 @@ def purge(): pass -def get_failure_entities(): - return +def update_active_specification_index(self, context): + TesterData.load() class Specification(PropertyGroup): @@ -53,13 +54,10 @@ class IfcTesterProperties(PropertyGroup): ifc_file: StringProperty(default="", name="IFC File") should_load_from_memory: BoolProperty(default=False, name="Load from Memory") generate_html_report: BoolProperty(default=False, name="Generate HTML report") - active_specification_index: IntProperty(name="Active Specification Index") - active_requirement_index: IntProperty(name="Active Requirement Index") + active_specification_index: IntProperty(name="Active Specification Index", update=update_active_specification_index) old_index: IntProperty(name="", default=0) active_failed_entity_index: IntProperty(name="Active Failed Entity Index") - report: StringProperty(default="", name="JSON report") specifications: CollectionProperty(name="Specifications", type=Specification) failed_entities: CollectionProperty(name="FailedEntities", type=FailedEntities) - has_report: BoolProperty(default=False, name="") has_entities: BoolProperty(default=False, name="") n_entities: IntProperty(name="", default=0) diff --git a/src/blenderbim/blenderbim/bim/module/tester/ui.py b/src/blenderbim/blenderbim/bim/module/tester/ui.py index 3db173884e..2af3f7af98 100644 --- a/src/blenderbim/blenderbim/bim/module/tester/ui.py +++ b/src/blenderbim/blenderbim/bim/module/tester/ui.py @@ -18,7 +18,7 @@ import blenderbim.tool as tool from bpy.types import Panel, UIList -import json +from blenderbim.bim.module.tester.data import TesterData class BIM_PT_tester(Panel): @@ -31,6 +31,9 @@ class BIM_PT_tester(Panel): bl_parent_id = "BIM_PT_tab_quality_control" def draw(self, context): + if not TesterData.is_loaded: + TesterData.load() + self.layout.use_property_split = True props = context.scene.IfcTesterProperties @@ -53,7 +56,7 @@ class BIM_PT_tester(Panel): row = self.layout.row() row.operator("bim.execute_ifc_tester") - if props.has_report: + if TesterData.data["has_report"]: self.layout.template_list( "BIM_UL_tester_specifications", "", @@ -69,33 +72,26 @@ class BIM_PT_tester(Panel): def draw_editable_ui(self, context): props = context.scene.IfcTesterProperties - i = props.active_specification_index - dic_report = json.loads(props.report) + specification = TesterData.data["specification"] - total_successes = dic_report[i]["total_successes"] - total = dic_report[i]["total"] - percentage = dic_report[i]["percentage"] - n_requirements = len(dic_report[i]["requirements"]) + n_requirements = len(specification["requirements"]) row = self.layout.row() - row.label(text=f"Passed: {total_successes}/{total} ({percentage}%)") + row.label( + text=f'Passed: {specification["total_checks_pass"]}/{specification["total_checks"]} ({specification["percent_checks_pass"]}%)' + ) row = self.layout.row() row.label(text=f"Requirements ({n_requirements}):") - c = 0 box = self.layout.box() - for req in dic_report[i]["requirements"]: + for i, requirement in enumerate(specification["requirements"]): row = box.row(align=True) - row.label(text=f" {c+1}. {req['description']}") - if req["status"]: - row.label(text="PASS", icon="CHECKMARK") - else: - row.label(text="FAIL", icon="CANCEL") + row.label(text=requirement["description"], icon="CHECKMARK" if requirement["status"] else "CANCEL") + if not requirement["status"]: op = row.operator("bim.select_requirement", text="", icon="LONGDISPLAY") - op.spec_index = i - op.req_index = c - c += 1 + op.spec_index = props.active_specification_index + op.req_index = i - if props.old_index == i and props.n_entities > 0: + if props.old_index == props.active_specification_index and props.n_entities > 0: row = self.layout.row() row.label(text=f"Failed entities [{props.n_entities}]:") self.layout.template_list( @@ -112,11 +108,7 @@ class BIM_UL_tester_specifications(UIList): def draw_item(self, context, layout, data, item, icon, active_data, active_propname): if item: row = layout.row(align=True) - row.label(text=item.name, icon="WORDWRAP_ON") - if item.status: - row.label(text="PASS") - else: - row.label(text="FAIL") + row.label(text=item.name, icon="CHECKMARK" if item.status else "CANCEL") class BIM_UL_tester_failed_entities(UIList): diff --git a/src/blenderbim/blenderbim/bim/module/type/operator.py b/src/blenderbim/blenderbim/bim/module/type/operator.py index ea07090640..ebb624829d 100644 --- a/src/blenderbim/blenderbim/bim/module/type/operator.py +++ b/src/blenderbim/blenderbim/bim/module/type/operator.py @@ -445,7 +445,7 @@ class AddType(bpy.types.Operator, tool.Ifc.Operator): should_add_representation=True, context=body, ) - tool.Blender.select_and_activate_single_object(obj) + tool.Blender.select_and_activate_single_object(context, obj) bpy.ops.bim.add_roof() bpy.ops.bim.load_type_thumbnails(ifc_class=ifc_class) diff --git a/src/blenderbim/blenderbim/bim/ui.py b/src/blenderbim/blenderbim/bim/ui.py index 706a3b8163..33112cbb8f 100644 --- a/src/blenderbim/blenderbim/bim/ui.py +++ b/src/blenderbim/blenderbim/bim/ui.py @@ -126,14 +126,14 @@ class BIM_UL_topics(bpy.types.UIList): class BIM_ADDON_preferences(bpy.types.AddonPreferences): bl_idname = "blenderbim" - svg2pdf_command: StringProperty(name="SVG to PDF Command", description="E.g. [['inkscape', svg, '-o', pdf]]") + svg2pdf_command: StringProperty(name="SVG to PDF Command", description='E.g. [["inkscape", "svg", "-o", pdf]]') svg2dxf_command: StringProperty( name="SVG to DXF Command", - description="E.g. [['inkscape', svg, '-o', eps], ['pstoedit', '-dt', '-f', 'dxf:-polyaslines -mm', eps, dxf, '-psarg', '-dNOSAFER']]", + description='E.g. [["inkscape", "svg", "-o", "eps"], ["pstoedit", "-dt", "-f", "dxf:-polyaslines -mm", "eps", "dxf", "-psarg", "-dNOSAFER"]]', ) - svg_command: StringProperty(name="SVG Command", description="E.g. [['firefox', path]]") - pdf_command: StringProperty(name="PDF Command", description="E.g. [['firefox', path]]") - spreadsheet_command: StringProperty(name="Spreadsheet Command", description="E.g. [['libreoffice', path]]") + svg_command: StringProperty(name="SVG Command", description='E.g. [["firefox", "path"]]') + pdf_command: StringProperty(name="PDF Command", description='E.g. [["firefox", "path"]]') + spreadsheet_command: StringProperty(name="Spreadsheet Command", description='E.g. [["libreoffice", "path"]]') openlca_port: IntProperty(name="OpenLCA IPC Port", default=8080) should_hide_empty_props: BoolProperty(name="Should Hide Empty Properties", default=True) should_setup_workspace: BoolProperty(name="Should Setup Workspace Layout for BIM", default=True) diff --git a/src/blenderbim/blenderbim/tool/__init__.py b/src/blenderbim/blenderbim/tool/__init__.py index 118f61a047..54f5e9ea57 100644 --- a/src/blenderbim/blenderbim/tool/__init__.py +++ b/src/blenderbim/blenderbim/tool/__init__.py @@ -51,6 +51,7 @@ from blenderbim.tool.structural import Structural from blenderbim.tool.style import Style from blenderbim.tool.surveyor import Surveyor from blenderbim.tool.system import System +from blenderbim.tool.tester import Tester from blenderbim.tool.type import Type from blenderbim.tool.unit import Unit from blenderbim.tool.search import Search diff --git a/src/blenderbim/blenderbim/tool/drawing.py b/src/blenderbim/blenderbim/tool/drawing.py index aa3af91100..8c2751c4f6 100644 --- a/src/blenderbim/blenderbim/tool/drawing.py +++ b/src/blenderbim/blenderbim/tool/drawing.py @@ -20,6 +20,7 @@ import os import re import bpy import math +import json import lark import bmesh import shutil @@ -70,6 +71,8 @@ class Drawing(blenderbim.core.tool.Drawing): obj = annotation.Annotator.add_plane_to_annotation(obj) elif object_type == "REVISION_CLOUD": obj = annotation.Annotator.add_plane_to_annotation(obj, remove_face=True) + elif object_type == "MULTI_SYMBOL": + obj = annotation.Annotator.add_vertex_to_annotation(obj) elif object_type == "TEXT_LEADER": co1, _, co2, _ = annotation.Annotator.get_placeholder_coords() obj = annotation.Annotator.add_line_to_annotation(obj, co2, co1) @@ -835,9 +838,11 @@ class Drawing(blenderbim.core.tool.Drawing): @classmethod def open_with_user_command(cls, user_command, path): if user_command: - commands = eval(user_command) + commands = json.loads(user_command) + replacements = {"path": path} for command in commands: - subprocess.Popen(command) + command[0] = shutil.which(command[0]) or command[0] + subprocess.Popen([replacements.get(c, c) for c in command]) else: webbrowser.open("file://" + path) @@ -1835,3 +1840,27 @@ class Drawing(blenderbim.core.tool.Drawing): direction = end - start offset = distance * (direction / np.linalg.norm(direction)) return (start - offset).tolist(), (end + offset).tolist() + + @classmethod + def get_sheet_references(cls, drawing): + sheet_references = [] + drawing_reference = cls.get_drawing_document(drawing) + for sheet in tool.Ifc.get().by_type("IfcDocumentInformation"): + if not sheet.Scope == "SHEET": + continue + references = cls.get_document_references(sheet) + for reference in references: + if reference.Location == drawing_reference.Location: + sheet_references.append(reference) + break + return sheet_references + + @classmethod + def get_camera_matrix(cls, camera): + matrix_world = camera.matrix_world.copy().normalized() + location, rotation, scale = matrix_world.decompose() + if scale.x < 0 or scale.y < 0 or scale.z < 0: + # RCPs may be inversely scaled. We discard the scale and rotate the Z to compensate. + rotate180z = mathutils.Matrix.Rotation(math.radians(180.0), 4, "Z") + return mathutils.Matrix.Translation(location) @ rotation.to_matrix().to_4x4() @ rotate180z + return mathutils.Matrix.Translation(location) @ rotation.to_matrix().to_4x4() diff --git a/src/blenderbim/blenderbim/tool/geometry.py b/src/blenderbim/blenderbim/tool/geometry.py index 3b78dfb8d4..0544c9d330 100644 --- a/src/blenderbim/blenderbim/tool/geometry.py +++ b/src/blenderbim/blenderbim/tool/geometry.py @@ -29,7 +29,7 @@ import blenderbim.core.spatial import blenderbim.tool as tool import blenderbim.bim.import_ifc from math import radians -from mathutils import Vector +from mathutils import Vector, Matrix from blenderbim.bim.ifc import IfcStore @@ -59,6 +59,8 @@ class Geometry(blenderbim.core.tool.Geometry): # Note that clearing scale has no impact on cameras. if (obj.scale - Vector((1.0, 1.0, 1.0))).length > 1e-4: if not obj.data: + location, rotation, _ = obj.matrix_world.decompose() + obj.matrix_world = Matrix.Translation(location) @ rotation.to_matrix().to_4x4() obj.matrix_world.normalize() elif obj.data.users == 1: context_override = {} diff --git a/src/blenderbim/blenderbim/tool/ifcgit.py b/src/blenderbim/blenderbim/tool/ifcgit.py index 33f2fc39e3..6dd445c498 100644 --- a/src/blenderbim/blenderbim/tool/ifcgit.py +++ b/src/blenderbim/blenderbim/tool/ifcgit.py @@ -220,7 +220,7 @@ class IfcGit: bpy.data.orphans_purge(do_recursive=True) - bpy.ops.bim.load_project(filepath=path_ifc) + bpy.ops.bim.load_project(filepath=path_ifc, should_start_fresh_session=False) bpy.ops.object.select_all(action="DESELECT") @classmethod diff --git a/src/blenderbim/blenderbim/tool/tester.py b/src/blenderbim/blenderbim/tool/tester.py new file mode 100644 index 0000000000..f6f66e97ce --- /dev/null +++ b/src/blenderbim/blenderbim/tool/tester.py @@ -0,0 +1,22 @@ +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + + +class Tester: + specs = None + report = {} diff --git a/src/blenderbim/docs/devs/writing_docs.rst b/src/blenderbim/docs/devs/writing_docs.rst index c5be24d5b2..0d14e89bee 100644 --- a/src/blenderbim/docs/devs/writing_docs.rst +++ b/src/blenderbim/docs/devs/writing_docs.rst @@ -60,3 +60,11 @@ download. See also blocks should be used to reference `further reading `__ links. + +Tables can be very annoying to format. You can use a CSV table instead. + +.. csv-table:: + :header: "Foo", "Bar", "Baz" + + "ABC", "01", "02" + "DEF", "03", "04" diff --git a/src/blenderbim/scripts/generate_demo_library.py b/src/blenderbim/scripts/generate_demo_library.py index e0017925db..7f5775a39f 100644 --- a/src/blenderbim/scripts/generate_demo_library.py +++ b/src/blenderbim/scripts/generate_demo_library.py @@ -146,6 +146,9 @@ class LibraryGenerator: self.create_type("IfcDoorType", "DT01", {"model_body": "Door", "plan_body": "Door-Plan"}) self.create_type("IfcFurnitureType", "BUN01", {"model_body": "Bunny", "plan_body": "Bunny-Plan"}) + self.create_symbol_type("SETOUT-POINT", "setout-point") + self.create_symbol_type("CONTROL-POINT", "control-point") + self.create_symbol_type("TRAVERSE-POINT", "traverse-point") self.create_line_type("DASHED", "dashed") self.create_line_type("FINE", "fine") self.create_line_type("THIN", "thin") @@ -161,6 +164,12 @@ class LibraryGenerator: self.file.write("IFC4 Demo Library.ifc") + def create_symbol_type(self, name, symbol): + element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcTypeProduct", name=name) + element.ApplicableOccurrence = "IfcAnnotation/SYMBOL" + pset = ifcopenshell.api.run("pset.add_pset", self.file, product=element, name="EPset_Annotation") + ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"Symbol": symbol}) + def create_line_type(self, name, classes): element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcTypeProduct", name=name) element.ApplicableOccurrence = "IfcAnnotation/LINEWORK" diff --git a/src/blenderbim/scripts/generate_furniture_library.py b/src/blenderbim/scripts/generate_furniture_library.py index b0bf34c3a1..d18920e900 100644 --- a/src/blenderbim/scripts/generate_furniture_library.py +++ b/src/blenderbim/scripts/generate_furniture_library.py @@ -1877,4 +1877,4 @@ class LibraryGenerator: if __name__ == "__main__": path = Path(__file__).parents[1] / "blenderbim/bim/data/libraries" - LibraryGenerator().generate(output_filename=str(path / "IFC4 AU Library.ifc")) + LibraryGenerator().generate(output_filename=str(path / "IFC4 Furniture Library.ifc")) diff --git a/src/ifcconvert/IfcConvert.cpp b/src/ifcconvert/IfcConvert.cpp index fcf7afc7f6..059aebd45c 100644 --- a/src/ifcconvert/IfcConvert.cpp +++ b/src/ifcconvert/IfcConvert.cpp @@ -416,6 +416,8 @@ int main(int argc, char** argv) { "Uses the polygonal algorithm for hidden line rendering") ("svg-prefilter", "Prefilter faces and shapes before feeding to HLR algorithm") + ("svg-segment-projection", + "Segment result of projection wrt original products") ("svg-write-poly", "Approximate every curve as polygonal in SVG output") ("svg-project", @@ -1085,6 +1087,7 @@ int main(int argc, char** argv) { static_cast(serializer.get())->setUseNamespace(vmap.count("svg-xmlns") > 0); static_cast(serializer.get())->setUseHlrPoly(vmap.count("svg-poly") > 0); static_cast(serializer.get())->setUsePrefiltering(vmap.count("svg-prefilter") > 0); + static_cast(serializer.get())->setSegmentProjection(vmap.count("svg-segment-projection") > 0); static_cast(serializer.get())->setPolygonal(vmap.count("svg-write-poly") > 0); static_cast(serializer.get())->setAlwaysProject(vmap.count("svg-project") > 0); static_cast(serializer.get())->setWithoutStoreys(vmap.count("svg-without-storeys") > 0); diff --git a/src/ifccsv/ifccsv.py b/src/ifccsv/ifccsv.py index 621f2d6595..1f72bf6c54 100755 --- a/src/ifccsv/ifccsv.py +++ b/src/ifccsv/ifccsv.py @@ -75,6 +75,7 @@ class IfcCsv: sort=None, groups=None, summaries=None, + formatting=None, ): self.ifc_file = ifc_file self.results = [] @@ -90,10 +91,6 @@ class IfcCsv: for element in elements: result = [] - for index, attribute in enumerate(attributes): - if "*" in attribute: - attributes.extend(self.get_wildcard_attributes(attribute)) - del attributes[index] for attribute in attributes: value = ifcopenshell.util.selector.get_element_value(element, attribute) @@ -116,6 +113,7 @@ class IfcCsv: self.group_results(groups, attributes) self.summarise_results(summaries, attributes) self.sort_results(sort, attributes, include_global_id) + self.format_results(formatting, attributes, null) if format == "csv": self.export_csv(output, delimiter=delimiter) @@ -225,7 +223,27 @@ class IfcCsv: self.summaries[si] = max(summary_values[si]) self.summaries[si] = summary_type.title() + ": " + str(self.summaries[si]) + def format_results(self, formatting, attributes, null): + if not formatting: + return + + formatting_indices = {} + + for data in formatting: + index = attributes.index(data["name"]) + formatting_indices[index] = data["format"] + + for row in self.results: + for index, format_query in formatting_indices.items(): + if row[index] == null: + continue + if not isinstance(row[index], str): + row[index] = '"' + str(row[index]) + '"' + row[index] = ifcopenshell.util.selector.format(format_query.replace("{{value}}", row[index])) + def sort_results(self, sort, attributes, include_global_id): + if not self.results: + return if sort: def natural_sort(value): if isinstance(value, str): @@ -240,7 +258,10 @@ class IfcCsv: reverse = sort_data["order"] == "DESC" self.results = sorted(self.results, key=lambda x: natural_sort(x[i]), reverse=reverse) else: - self.results = sorted(self.results, key=lambda x: x[1 if include_global_id else 0]) + if include_global_id and len(list(self.results)[0]) > 1: + self.results = sorted(self.results, key=lambda x: x[1]) + elif not include_global_id: + self.results = sorted(self.results, key=lambda x: x[0]) def export_csv(self, output, delimiter=None): with open(output, "w", newline="", encoding="utf-8") as f: @@ -372,36 +393,12 @@ class IfcCsv: self.process_row(ifc_file, row, headers, attributes, null, bool_true, bool_false) def import_xlsx(self, ifc_file, table, attributes, null, bool_true, bool_false): - workbook = openpyxl.load_workbook(filename=table, read_only=True) - worksheet = workbook.active # Assuming data is on the first sheet - headers = None - - for row in worksheet.iter_rows(values_only=True): - if not headers: - headers = list(row) - if not attributes: - attributes = [None] * len(headers) - elif len(attributes) == len(headers) - 1: - attributes.insert(0, "") # The GlobalId column - continue - self.process_row(ifc_file, row, headers, attributes, null, bool_true, bool_false) + df = pd.read_excel(table) + self.import_pd(ifc_file, df, attributes, null, bool_true, bool_false) def import_ods(self, ifc_file, table, attributes, null, bool_true, bool_false): - doc = load(table) - first_sheet = doc.spreadsheet.getElementsByType(Table)[0] - rows = first_sheet.getElementsByType(TableRow) - headers = None - - for row in rows: - values = [cell.getElementsByType(P)[0].childNodes[0].data for cell in row.getElementsByType(TableCell)] - if not headers: - headers = values - if not attributes: - attributes = [None] * len(headers) - elif len(attributes) == len(headers) - 1: - attributes.insert(0, "") # The GlobalId column - continue - self.process_row(ifc_file, values, headers, attributes, null, bool_true, bool_false) + df = pd.read_excel(table, engine="odf") + self.import_pd(ifc_file, df, attributes, null, bool_true, bool_false) def import_pd(self, ifc_file, df, attributes=None, null="-", bool_true="YES", bool_false="NO"): headers = df.columns.tolist() diff --git a/src/ifcopenshell-python/Makefile b/src/ifcopenshell-python/Makefile index 83e1d840e7..9c47c7ae68 100644 --- a/src/ifcopenshell-python/Makefile +++ b/src/ifcopenshell-python/Makefile @@ -103,7 +103,7 @@ endif mkdir -p dist/ifcopenshell cp -r ifcopenshell/* dist/ifcopenshell/ - cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-$(PYNUMBER)-v0.7.0-dcc9d0e-$(PLATFORM)64.zip + cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-$(PYNUMBER)-v0.7.0-9cc1f5f-$(PLATFORM)64.zip cd dist/working && unzip ifcopenshell-python* cp -r dist/working/ifcopenshell/ifcopenshell_wrapper.py dist/ifcopenshell/ ifeq ($(PLATFORM), win) diff --git a/src/ifcopenshell-python/docs/ifcconvert/installation.rst b/src/ifcopenshell-python/docs/ifcconvert/installation.rst index b69e2244da..da8fecf14d 100644 --- a/src/ifcopenshell-python/docs/ifcconvert/installation.rst +++ b/src/ifcopenshell-python/docs/ifcconvert/installation.rst @@ -20,11 +20,11 @@ Pre-built packages | build-linux64_ | build-win32_ | build-win64_ | build-macos64_ | build-macosm164_ | +----------------+----------------+----------------+----------------+------------------+ -.. _build-linux64: https://s3.amazonaws.com/ifcopenshell-builds/IfcConvert-v0.7.0-dcc9d0e-linux64.zip -.. _build-win32: https://s3.amazonaws.com/ifcopenshell-builds/IfcConvert-v0.7.0-dcc9d0e-win32.zip -.. _build-win64: https://s3.amazonaws.com/ifcopenshell-builds/IfcConvert-v0.7.0-dcc9d0e-win64.zip -.. _build-macos64: https://s3.amazonaws.com/ifcopenshell-builds/IfcConvert-v0.7.0-dcc9d0e-macos64.zip -.. _build-macosm164: https://s3.amazonaws.com/ifcopenshell-builds/IfcConvert-v0.7.0-dcc9d0e-macosm164.zip +.. _build-linux64: https://s3.amazonaws.com/ifcopenshell-builds/IfcConvert-v0.7.0-9cc1f5f-linux64.zip +.. _build-win32: https://s3.amazonaws.com/ifcopenshell-builds/IfcConvert-v0.7.0-9cc1f5f-win32.zip +.. _build-win64: https://s3.amazonaws.com/ifcopenshell-builds/IfcConvert-v0.7.0-9cc1f5f-win64.zip +.. _build-macos64: https://s3.amazonaws.com/ifcopenshell-builds/IfcConvert-v0.7.0-9cc1f5f-macos64.zip +.. _build-macosm164: https://s3.amazonaws.com/ifcopenshell-builds/IfcConvert-v0.7.0-9cc1f5f-macosm164.zip 2. Unzip the downloaded file and run IfcConvert using the command line. diff --git a/src/ifcopenshell-python/docs/ifcopenshell-python.rst b/src/ifcopenshell-python/docs/ifcopenshell-python.rst index ddaa9a33eb..0861a2b374 100644 --- a/src/ifcopenshell-python/docs/ifcopenshell-python.rst +++ b/src/ifcopenshell-python/docs/ifcopenshell-python.rst @@ -15,4 +15,5 @@ system, as well as high level analysis and authoring functions. ifcopenshell-python/geometry_processing ifcopenshell-python/geometry_creation ifcopenshell-python/geometry_tree + ifcopenshell-python/selector_syntax ifcopenshell-python/developer_guide diff --git a/src/ifcopenshell-python/docs/ifcopenshell-python/installation.rst b/src/ifcopenshell-python/docs/ifcopenshell-python/installation.rst index 83552ffb0a..5c0d1b045a 100644 --- a/src/ifcopenshell-python/docs/ifcopenshell-python/installation.rst +++ b/src/ifcopenshell-python/docs/ifcopenshell-python/installation.rst @@ -40,34 +40,34 @@ changes in the IfcOpenShell C++ core. | Python 3.11 | py311-linux64_ | py311-win32_ | py311-win64_ | N/A | py311-macosm164_ | +-------------+----------------+----------------+----------------+----------------+------------------+ -.. _py36-linux64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-36-v0.7.0-dcc9d0e-linux64.zip -.. _py37-linux64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-37-v0.7.0-dcc9d0e-linux64.zip -.. _py38-linux64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-38-v0.7.0-dcc9d0e-linux64.zip -.. _py39-linux64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-39-v0.7.0-dcc9d0e-linux64.zip -.. _py310-linux64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-310-v0.7.0-dcc9d0e-linux64.zip -.. _py311-linux64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-311-v0.7.0-dcc9d0e-linux64.zip -.. _py36-win32: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-36-v0.7.0-dcc9d0e-win32.zip -.. _py37-win32: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-37-v0.7.0-dcc9d0e-win32.zip -.. _py38-win32: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-38-v0.7.0-dcc9d0e-win32.zip -.. _py39-win32: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-39-v0.7.0-dcc9d0e-win32.zip -.. _py310-win32: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-310-v0.7.0-dcc9d0e-win32.zip -.. _py311-win32: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-311-v0.7.0-dcc9d0e-win32.zip -.. _py36-win64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-36-v0.7.0-dcc9d0e-win64.zip -.. _py37-win64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-37-v0.7.0-dcc9d0e-win64.zip -.. _py38-win64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-38-v0.7.0-dcc9d0e-win64.zip -.. _py39-win64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-39-v0.7.0-dcc9d0e-win64.zip -.. _py310-win64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-310-v0.7.0-dcc9d0e-win64.zip -.. _py311-win64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-311-v0.7.0-dcc9d0e-win64.zip -.. _py36-macos64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-36-v0.7.0-dcc9d0e-macos64.zip -.. _py37-macos64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-37-v0.7.0-dcc9d0e-macos64.zip -.. _py38-macos64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-38-v0.7.0-dcc9d0e-macos64.zip -.. _py39-macos64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-39-v0.7.0-dcc9d0e-macos64.zip -.. _py310-macos64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-310-v0.7.0-dcc9d0e-macos64.zip -.. _py37-macosm164: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-37-v0.7.0-dcc9d0e-macosm164.zip -.. _py38-macosm164: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-38-v0.7.0-dcc9d0e-macosm164.zip -.. _py39-macosm164: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-39-v0.7.0-dcc9d0e-macosm164.zip -.. _py310-macosm164: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-310-v0.7.0-dcc9d0e-macosm164.zip -.. _py311-macosm164: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-311-v0.7.0-dcc9d0e-macosm164.zip +.. _py36-linux64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-36-v0.7.0-9cc1f5f-linux64.zip +.. _py37-linux64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-37-v0.7.0-9cc1f5f-linux64.zip +.. _py38-linux64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-38-v0.7.0-9cc1f5f-linux64.zip +.. _py39-linux64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-39-v0.7.0-9cc1f5f-linux64.zip +.. _py310-linux64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-310-v0.7.0-9cc1f5f-linux64.zip +.. _py311-linux64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-311-v0.7.0-9cc1f5f-linux64.zip +.. _py36-win32: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-36-v0.7.0-9cc1f5f-win32.zip +.. _py37-win32: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-37-v0.7.0-9cc1f5f-win32.zip +.. _py38-win32: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-38-v0.7.0-9cc1f5f-win32.zip +.. _py39-win32: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-39-v0.7.0-9cc1f5f-win32.zip +.. _py310-win32: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-310-v0.7.0-9cc1f5f-win32.zip +.. _py311-win32: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-311-v0.7.0-9cc1f5f-win32.zip +.. _py36-win64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-36-v0.7.0-9cc1f5f-win64.zip +.. _py37-win64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-37-v0.7.0-9cc1f5f-win64.zip +.. _py38-win64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-38-v0.7.0-9cc1f5f-win64.zip +.. _py39-win64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-39-v0.7.0-9cc1f5f-win64.zip +.. _py310-win64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-310-v0.7.0-9cc1f5f-win64.zip +.. _py311-win64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-311-v0.7.0-9cc1f5f-win64.zip +.. _py36-macos64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-36-v0.7.0-9cc1f5f-macos64.zip +.. _py37-macos64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-37-v0.7.0-9cc1f5f-macos64.zip +.. _py38-macos64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-38-v0.7.0-9cc1f5f-macos64.zip +.. _py39-macos64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-39-v0.7.0-9cc1f5f-macos64.zip +.. _py310-macos64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-310-v0.7.0-9cc1f5f-macos64.zip +.. _py37-macosm164: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-37-v0.7.0-9cc1f5f-macosm164.zip +.. _py38-macosm164: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-38-v0.7.0-9cc1f5f-macosm164.zip +.. _py39-macosm164: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-39-v0.7.0-9cc1f5f-macosm164.zip +.. _py310-macosm164: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-310-v0.7.0-9cc1f5f-macosm164.zip +.. _py311-macosm164: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-311-v0.7.0-9cc1f5f-macosm164.zip 2. Unzip the downloaded file and copy the ``ifcopenshell`` directory into your Python path. If you're not sure where your Python path is, run the following diff --git a/src/ifcopenshell-python/docs/ifcopenshell-python/selector_syntax.rst b/src/ifcopenshell-python/docs/ifcopenshell-python/selector_syntax.rst new file mode 100644 index 0000000000..e3775e8d02 --- /dev/null +++ b/src/ifcopenshell-python/docs/ifcopenshell-python/selector_syntax.rst @@ -0,0 +1,230 @@ +Selector syntax +=============== + +A common task in querying IFC models is to filter or search for elements which +match particular criteria. For example, you might want to find all plasterboard +walls with a 2 hour fire rating on level 3. + +Alternatively, you might want to fetch some data about a single element. For +example, you might want to fetch the fire rating property of an element, or the +type description of an element, or the net volume of a list of elements. + +Once you've retreived your data, you might want to format it in some way. You +might want to ensure that all names are always uppercase. Or you might want to +take length values defined in feet, and apply imperial formatting such that it +shows both feet and inches including fractions. + +These three usecases of filtering, getting a value, and formatting that value +are common and used in many utilities, such as in the BlenderBIM Add-on, +IfcCSV, IfcDiff, IfcClash, IfcPatch, and IfcFM. + +IfcOpenShell provides a custom syntax to consistently and concisely describe +filters, value queries, and formatting rules. + +Filtering elements +------------------ + +Filtering is typically used to select any IFC element or type. + +.. code-block:: python + + import ifcopenshell + import ifcopenshell.util.selector + + model = ifcopenshell.open("model.ifc") + # Get all concrete walls and slabs. + ifcopenshell.util.selector.filter_elements(model, "IfcWall, IfcSlab, material=concrete") + +.. csv-table:: + :header: "Example Query", "Description" + + "``IfcElement``", "All physical IfcElements including subclasses like walls, doors, windows, etc. Yep, that's it! Nothing else. Literally just ``IfcElement``." + "``IfcWall, IfcSlab``", "All walls and slabs. Technically, this is either a wall or a slab, but it's easier to describe it as all walls and slabs" + "``IfcWall, IfcSlab, material=concrete``", "All walls made out of concrete and slabs made out of concrete. The material checks any assigned IfcMaterial with a matching name or category attribute." + + "``325Q7Fhnf67OZC$$r43uzK``", "A single element. Yep, just the GlobalId, nothing else! Easy." + + "``325Q7Fhnf67OZC$$r43uzK, 2VlJ7nbF5AFfQQuRvSWexT``", "A bunch of arbitrary elements." + + "``IfcWall, ! 325Q7Fhnf67OZC$$r43uzK``", "All walls except that one element." + + "``IfcElement, ! IfcWall``", "All elements except for walls." + + "``IfcDoor, Name=D01``", "Any doors named D01, notice how attributes match the IFC Attribute naming exactly" + + "``IfcDoor, Name=/D[0-9]{2}/``", "Any doors with the naming scheme of D followed by two numbers:" + + "``IfcWall, Pset_WallCommon.FireRating=2HR``", "Any 2 hour fire rated wall" + + "``IfcWall, IfcColumn, IfcBeam, IfcFooting, /Pset_.*Common/.LoadBearing=TRUE``", "Any load bearing structure" + + "``IfcElement, /Pset_.*Common/.FireRating != NULL``", "Any element with a fire rating property" + + "``IfcWall, type=WT01, location=""Level 3""``", "Any walls of wall type WT01 on level 3 (we quote Level 3 since it has a space)" + + "``IfcElement, classification=/Pr_.*/``", "Any maintainable product according to Uniclass tables" + + "``IfcWall, IfcSlab, ! 325Q7Fhnf67OZC$$r43uzK, material=concrete, /Pset_.*Common/.FireRating=2HR``", "Notice how there are intuitive rules that class and instance filters are OR whereas other filters are AND So here is any wall or slab except that one element that has a material of concrete and has a 2 hour fire rating" + + "``IfcSlab, material=concrete + IfcDoor``", "Finally, you can union facet lists together. So here is all concrete slabs, as well as all doors (regardless of concrete)" + + "``IfcDoor, IfcWindow + IfcWall, IfcSlab, material=concrete + 325Q7Fhnf67OZC$$r43uzK``", "Here's another example of unioning facet groups. All doors and window, and all concrete walls and slabs, plus that one random element" + + "``IfcPump, location=""Level 3""``", "Locations bubble up the hierarchy. So if a pump is in a space and that space is on Level 3, then you can say ""all pumps on level 3"" which will include that pump in the space." + +The filter elements syntax works by specifying one or more groups of filters +separated by a ``+`` character. Each filter group will return a set of filtered +elements, and these are unioned together. + +.. code-block:: + + filter_group[ + filter_group]* + +A filter group consists of one or more filters separated by a ``,`` character. +The filters are chained and apply from left to right. + +.. code-block:: + + filter[, filter]* + +There are nine types of filters to choose from. Some of these filters will add +new elements to your filter group, and some will filter previously added +elements in your filter group based on their criteria. + +.. csv-table:: + :header: "Filter", "Type", "Usage", "Example" + + "Class", "Add", "``[!] {{ifc_class_name}}``", "``IfcWall`` adds all IfcWall elements and their subclasses. ``! IfcWall`` subtracts all non-IfcWall elements from the filter group." + "GlobalId", "Add", "``[!] {{global_id}}``", "``325Q7Fhnf67OZC$$r43uzK`` adds the single element with that GlobalId attribute. ``! 325Q7Fhnf67OZC$$r43uzK`` subtracts that single element." + "Attribute", "Filter", "``{{name}}{{=}}{{value}}``", "``Name=Foo`` specifies the criteria that elements must have a ``Name`` attribute with a value of ``Foo``. Attribute names must be spelled exactly the same as in IFC, which means that they must start with an uppercase character." + "Property", "Filter", "``{{pset}}.{{prop}}{{=}}{{value}}``", "``Pset_WallCommon.FireRating=2HR`` specifies the criteria that elements must have a ``Pset_WallCommon`` property set, with a ``FireRating`` property within it with a value of ``2HR``. The property set name and the property name are separated by a ``.``." + "Type", "Filter", "``type{{=}}{{value}}``", "``type=Foo`` specifies the criteria that elements must have a type which has a ``Name`` attribute with a value of ``Foo``." + "Material", "Filter", "``material{{=}}{{value}}``", "``material=Foo`` specifies the criteria that elements must have a IfcMaterial assigned directly or indirectly (such as within a layer set). That IfcMaterial must have either a ``Name`` or ``Category`` attribute with a value of ``Foo``." + "Classification", "Filter", "``classification{{=}}{{value}}``", "``classification=Foo`` specifies the criteria that elements must have an IfcClassificationReference with an ``Identification`` attribute with a value of ``Foo``." + "Location", "Filter", "``location{{=}}{{value}}``", "``location=Foo`` specifies the criteria that elements must be contained directly or indirectly in a spatial element with a ``Name`` attribute with a value of ``Foo``." + "Query", "Filter", "``query:{{keys}}{{=}}{{value}}``", "``query:types.count=0`` specifies the criteria that elements must have zero type occurrences. The query keys corresponds to the syntax used in the `Getting element values`_ section" + +When you specify a filter with a ``{{=}}`` check, you can choose from one of +the following comparison checks: + +.. csv-table:: + :header: "Comparison", "Description" + + "``=``", "Must equal the value. The data type of the value is automatically converted to match." + "``!=``", "Must not equal the value." + "``>``", "Must be greater than the value." + "``>=``", "Must be greater than or equal to the value." + "``<``", "Must be less than the value." + "``<=``", "Must be less than or equal to the value." + +When you specify a ``{{pset}}``, ``{{prop}}``, or ``{{value}}``, there are +three ways you can do so: + +.. csv-table:: + :header: "Value Type", "Example", "Description" + + "Quoted string", "``""foo \""bar\"" baz""``", "The value must be in double quotes. The value may contain spaces, symbols, and other characters. If you need to use a double quote, you can escape it with a backslash. This is the safest, most general way to specify a value." + "Unquoted string", "``foobarbaz``", "For convenience, if you have a simple value which contains no spaces or special characters, you are free to specify it as an unquoted string." + "Regex string", "``/foo.*baz/``", "You may specify a Python-compatible regex pattern delimited by forward slashes." + +Getting element values +---------------------- + +Given a single element, this syntax provides a simple way to extract a value +without needing to write complex code for it. + +.. code-block:: python + + import ifcopenshell + import ifcopenshell.util.selector + + # Get the Name attribute of the wall's type. + ifcopenshell.util.selector.get_element_value(wall, "type.Name") + +.. csv-table:: + :header: "Example Query", "Description" + + "``class``", "Get the IFC class of the element." + "``Name``", "Get the ``Name`` attribute." + "``Pset_WallCommon.Status``", "Get the value of the ``Status`` property in the ``Pset_WallCommon`` property set." + "``/Pset_.*Common/.Status``", "Get the value of the ``Status`` property in the any common property set." + "``type.Name``", "Get the ``Name`` attribute of the element's relating type." + "``types.count``", "Count the number of occurrences of a type." + "``storey.Name``", "Get the ``Name`` attribute of the storey that the element is contained in." + "``materials.count``", "Count the number of materials assigned to an element." + "``material.Name``", "Get the name of the assigned material." + "``material.item.0.Name``", "Get the name of the first item in a material set (e.g. the first material layer)" + +The element value syntax works by specifying one or more query keys separated +by a ``.`` character. Each query key returns data based of the results of the +previous key. + +.. code-block:: + + key[.key]* + +Valid keys are: + +.. csv-table:: + :header: "Key", "Description" + + "``id``", "Gets the IFC ID (equivalent to ``.id()``)" + "``class``", "Gets the IFC class (equivalent to ``.is_a()``)" + "``predefined_type``", "Gets the predefined type of the element, taking into account inheritance." + "``{{attribute}}``", "Gets the value of the attribute you specify. Attributes always start with an uppercase letter." + "``{{pset}}``", "This gets the property set with the same name specified in ``{{pset}}``. Note that this can be ambiguous with ``{{attribute}}``. If there is an ambiguity, ``{{attribute}}`` takes priority." + "``{{prop}}``", "If the previous key returns a property set, ``{{prop}}`` gets the value of a property with the same name specified in ``{{prop}}``. For this reason, often you specify both keys together, like this: ``{{pset}}.{{prop}}``." + "``type``", "Gets the relating type of an element occurrence." + "``types`` or ``occurrences``", "Gets the related objects of an element type." + "``container``", "Gets the immediate spatial element that an element is contained in." + "``space``", "Gets the first IfcSpace spatial element that an element is contained in." + "``storey``", "Gets the first IfcBuildingStorey spatial element that an element is contained in." + "``building``", "Gets the first IfcBuilding spatial element that an element is contained in." + "``site``", "Gets the first IccSite spatial element that an element is contained in." + "``material`` or ``mat``", "Gets the assigned material, which may be a material set." + "``item`` or ``i``", "If the previous key returns a material set, gets the relevant material set items" + "``materials`` or ``mats``", "Gets a list of IfcMaterials assigned directly or indirectly (such as via a material set) to the element" + "``count``", "If the previous key returns multiple things, count that list. Otherwise, return 1." + "``{{number}}``", "If the previous key returns multiple things, fetch the ``{{number}}`` index (e.g. 0, 1, 2, 3, etc) item in that list." + +When you specify a ``{{pset}}`` or ``{{prop}}``, there are three ways you can +do so: + +.. csv-table:: + :header: "Value Type", "Example", "Description" + + "Quoted string", "``""foo \""bar\"" baz""``", "The value must be in double quotes. The value may contain spaces, symbols, and other characters. If you need to use a double quote, you can escape it with a backslash. This is the safest, most general way to specify a value." + "Unquoted string", "``foobarbaz``", "For convenience, if you have a simple value which contains no spaces or special characters, you are free to specify it as an unquoted string." + "Regex string", "``/foo.*baz/``", "You may specify a Python-compatible regex pattern delimited by forward slashes." + +Formatting +---------- + +Given a value, this syntax allows a simple way to specify a set of formatting +rules. This is useful for configuring outputs of how data should be presented. + +.. code-block:: python + + import ifcopenshell + import ifcopenshell.util.selector + + # Get the Name attribute of the wall's type. + value = ifcopenshell.util.selector.get_element_value(wall, "type.Name") + # Always display names in uppercase. + ifcopenshell.util.selector.format(f'upper("{value}")') + +Formatting queries are written similar to how you'd write functions or formulas +in spreadsheets. For example ``upper("foo")`` will produce ``FOO``. You may +nest formulas, for example ``concat(title("foo"), lower("Bar"))`` will produce +``Foobar``. Strings must be double quoted. + +.. csv-table:: + :header: "Function", "Example", "Result", "Description" + + "``upper({{value}})``", "``upper(""Foo"")``", "``FOO``", "Uppercases a string." + "``lower({{value}})``", "``lower(""Foo"")``", "``foo``", "Lowercases a string." + "``title({{value}})``", "``title(""foo"")``", "``Foo``", "Titlecases a string." + "``concat({{value}}[, {{value2}}]*)``", "``concat(""foo"", ""bar"")``", "``foobar``", "Concatenates two or more strings." + "``round({{value}}, {{precision}})``", "``round(3.123, 0.1)``", "``3.1``", "Rounds ``{{value}}`` to the nearest ``{{precision}}``." + "``metric_length({{value}}, {{precision}}, {{decimals}})``", "``metric_length(3.123, 0.1, 2)``", "``3.10``", "Rounds ``{{value}}`` to the nearest ``{{precision}}`` then displays using a certain amount of decimal places." + "``imperial_length({{value}}, {{precision}}, {{unit}})``", "``imperial_length(3.22, 4, ""foot"")``", "``3' - 3 3/4""``", "Rounds ``{{value}}`` to the nearest ``1/{{precision}}`` inch then displays using fractional feet and inches. The ``{{value}}`` may be specified either as ``foot`` or ``inch`` depending on ``{{unit}}``." diff --git a/src/ifcopenshell-python/docs/ifcopenshell/installation.rst b/src/ifcopenshell-python/docs/ifcopenshell/installation.rst index 33ac16898e..c9942bb038 100644 --- a/src/ifcopenshell-python/docs/ifcopenshell/installation.rst +++ b/src/ifcopenshell-python/docs/ifcopenshell/installation.rst @@ -136,13 +136,13 @@ operating systems. GCC (4.7 or newer) or Clang (any version) is required. -DOCC_INCLUDE_DIR=/usr/include/ \ # Optional Collada support - -DCOLLADA_SUPPORT=On + -DCOLLADA_SUPPORT=On \ -DOPENCOLLADA_INCLUDE_DIR="/usr/local/include/opencollada" \ -DOPENCOLLADA_LIBRARY_DIR="/usr/local/lib/opencollada" \ -DPCRE_LIBRARY_DIR=/usr/lib/x86_64-linux-gnu/ \ # Optional HDF5 support - -DHDF5_SUPPORT=On + -DHDF5_SUPPORT=On \ -DHDF5_LIBRARIES="/usr/local/hdf5/lib/libhdf5_cpp.so;/usr/local/hdf5/lib/libhdf5.so;/usr/lib64/libz.so;/usr/lib64/libsz.so;/usr/lib64/libaec.so" \ -DHDF5_INCLUDE_DIR="/usr/local/hdf5/include" \ @@ -190,7 +190,7 @@ GCC (4.7 or newer) or Clang (any version) is required. $ mkdir build && cd build # set library flags $ export LDFLAGS="$LDFLAGS -Wl,-flat_namespace,-undefined,suppress" - $ cmake ../cmake + $ cmake ../cmake \ -DPYTHON_EXECUTABLE=/opt/homebrew/bin/python3.10 \ -DPYTHON_LIBRARY=/opt/homebrew/opt/python@3.10/Frameworks/Python.framework/Versions/3.10/lib/libpython3.10.dylib \ -DPYTHON_INCLUDE_DIR=/opt/homebrew/opt/python@3.10/Frameworks/Python.framework/Versions/3.10/include/python3.10/ \ @@ -198,10 +198,10 @@ GCC (4.7 or newer) or Clang (any version) is required. -DOCC_INCLUDE_DIR=/opt/homebrew/include/opencascade/ \ -DCGAL_INCLUDE_DIR=/opt/homebrew/include/ \ -DGMP_LIBRARY_DIR=/opt/homebrew/lib/ \ - -DMPFR_LIBRARY_DIR=/opt/homebrew/lib/ + -DMPFR_LIBRARY_DIR=/opt/homebrew/lib/ \ -DHDF5_LIBRARY_DIR=/opt/homebrew/lib/ \ -DHDF5_INCLUDE_DIR=/opt/homebrew/include/ \ - -DCOLLADA_SUPPORT=0 \ + -DCOLLADA_SUPPORT=0 # `sysctl -n hw.ncpu` returns the number of cpu cores on macOS $ make -j$(sysctl -n hw.ncpu) diff --git a/src/ifcopenshell-python/ifcopenshell/api/geometry/add_representation.py b/src/ifcopenshell-python/ifcopenshell/api/geometry/add_representation.py index 8b458ef777..31bf7f1f98 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/geometry/add_representation.py +++ b/src/ifcopenshell-python/ifcopenshell/api/geometry/add_representation.py @@ -823,6 +823,8 @@ class Usecase: def create_annotation2d_representation(self): if isinstance(self.settings["geometry"], bpy.types.Mesh) and len(self.settings["geometry"].polygons): items = self.create_annotation_fill_areas(is_2d=True) + elif isinstance(self.settings["geometry"], bpy.types.Mesh) and not len(self.settings["geometry"].edges): + return self.create_point_cloud_representation(is_2d=True) else: items = [self.file.createIfcGeometricCurveSet(self.create_curves(is_2d=True))] return self.file.createIfcShapeRepresentation( diff --git a/src/ifcopenshell-python/ifcopenshell/express/rule_compiler.py b/src/ifcopenshell-python/ifcopenshell/express/rule_compiler.py index e844c8d605..b617036f8a 100644 --- a/src/ifcopenshell-python/ifcopenshell/express/rule_compiler.py +++ b/src/ifcopenshell-python/ifcopenshell/express/rule_compiler.py @@ -420,6 +420,15 @@ def process_expression(context): exclude=[context.rel_op_extended], ) else: + if len(context.simple_expression.branches()) == 2 and str(context.rel_op_extended) == 'in': + # IfcBlobTexture + try: + is_literal_str_list = set(map(type, ast.literal_eval(str(context.simple_expression.branches()[1])))) == {str} + except: + is_literal_str_list = False + if is_literal_str_list: + a, b = map(str, context.simple_expression.branches()) + return f"{a}.lower() {str(context.rel_op_extended)} {b}" return concat(context.rel_op_extended, context.simple_expression) elif context.multiplication_like_op: if str(context.multiplication_like_op.branches()[0]) == "||": diff --git a/src/ifcopenshell-python/ifcopenshell/express/rule_executor.py b/src/ifcopenshell-python/ifcopenshell/express/rule_executor.py index 89c2d3bfa9..d7d20f2122 100644 --- a/src/ifcopenshell-python/ifcopenshell/express/rule_executor.py +++ b/src/ifcopenshell-python/ifcopenshell/express/rule_executor.py @@ -217,7 +217,14 @@ def run(f, logger): check(value[0], S.declaration_by_name(value.is_a()), instance=inst) for inst in f: - values = list(inst) + try: + values = list(inst) + except Exception as e: + if hasattr(logger, "set_state"): + logger.error(str(e)) + else: + logger.error("For instance:\n %s\n%s", inst, e) + continue entity = S.declaration_by_name(inst.is_a()) attrs = entity.all_attributes() for i, (attr, val, is_derived) in enumerate( diff --git a/src/ifcopenshell-python/ifcopenshell/util/selector.py b/src/ifcopenshell-python/ifcopenshell/util/selector.py index 097ea409ff..e88dfac47b 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/selector.py +++ b/src/ifcopenshell-python/ifcopenshell/util/selector.py @@ -20,6 +20,7 @@ import re import lark import ifcopenshell.util import ifcopenshell.util.fm +import ifcopenshell.util.unit import ifcopenshell.util.element import ifcopenshell.util.classification @@ -51,8 +52,8 @@ filter_elements_grammar = lark.Lark( value: special | quoted_string | regex_string | unquoted_string unquoted_string: /[^.=\\s]+/ - quoted_string: ESCAPED_STRING regex_string: "/" /[^\\/]+/ "/" + quoted_string: ESCAPED_STRING special: null | true | false @@ -93,30 +94,151 @@ filter_elements_grammar = lark.Lark( ) get_element_grammar = lark.Lark( - """start: WORD | ESCAPED_STRING | keys_regex | keys_quoted | keys_simple - keys_regex: "r" ESCAPED_STRING ("." ESCAPED_STRING)* - keys_quoted: ESCAPED_STRING ("." ESCAPED_STRING)* - keys_simple: /[^\\W][^.=<>!%*\\]]*/ ("." /[^\\W][^.=<>!%*\\]]*/)* + """start: keys + + keys: key ("." key)* + key: quoted_string | regex_string | unquoted_string + unquoted_string: /[^.=\\/\\s]+/ + regex_string: "/" /[^\\/]+/ "/" + quoted_string: ESCAPED_STRING // Embed common.lark for packaging _STRING_INNER: /.*?/ _STRING_ESC_INNER: _STRING_INNER /(? items - could be a list or single curve/IfcExtrudedAreaSolid - # < IfcShapeRepresentation + def get_representation(self, context, items, representation_type:str = None): + """Create IFC representation for the specified context and items. + + :param context: IfcGeometricRepresentationSubContext + :param items: could be a list or single curve/IfcExtrudedAreaSolid + :param representation_type: Explicitly specified RepresentationType, defaults to `None`. + If not provided it will be guessed from the items types. + :type representation_type: str, optional + + :return: IfcRepresentation + """ if not isinstance(items, collections.abc.Iterable): items = [items] diff --git a/src/ifcopenshell-python/ifcopenshell/util/unit.py b/src/ifcopenshell-python/ifcopenshell/util/unit.py index 6f6986ab8a..e3dc8aa6d0 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/unit.py +++ b/src/ifcopenshell-python/ifcopenshell/util/unit.py @@ -17,6 +17,7 @@ # along with IfcOpenShell. If not, see . from math import pi +from fractions import Fraction prefixes = { "EXA": 1e18, @@ -573,9 +574,10 @@ def format_length( if imperial_unit == "foot": feet = int(value) inches = (value - feet) * 12 + elif imperial_unit == "inch": - inches = value % 12 - feet = int(round((value - inches) / 12)) + inches = value * 12 + # Round to the nearest 1/N nearest = round(inches * precision) @@ -585,14 +587,22 @@ def format_length( # If fraction is a whole number, format it accordingly if frac.denominator == 1: - if suppress_zero_inches and frac.numerator == 0: - return f"{feet}'" - return f"{feet}' - {frac.numerator}\"" - if frac.numerator > frac.denominator: + if imperial_unit == "inch": + return f"{round(inches)}\"" + if suppress_zero_inches: + if imperial_unit == "foot": + return f"{round(value)}'" + elif not suppress_zero_inches: + if imperial_unit == "foot": + return f"{round(value)}' - 0\"" + if frac.numerator > frac.denominator and not frac.denominator == 0: remainder = frac.numerator % frac.denominator whole = int((frac.numerator - remainder) / frac.denominator) - return f"{feet}' - {whole} {remainder}/{frac.denominator}\"" - return f"{feet}' - {frac.numerator}/{frac.denominator}\"" + if imperial_unit == "foot": + return f"{feet}' - {whole} {remainder}/{frac.denominator}\"" + elif imperial_unit == "inch": + return f"{whole} {remainder}/{frac.denominator}\"" + elif unit_system == "metric": rounded_val = round(value / precision) * precision return f"{rounded_val:.{decimal_places}f}" diff --git a/src/ifcopenshell-python/ifcopenshell/validate.py b/src/ifcopenshell-python/ifcopenshell/validate.py index dc2e67d4db..bd1b8cfd66 100644 --- a/src/ifcopenshell-python/ifcopenshell/validate.py +++ b/src/ifcopenshell-python/ifcopenshell/validate.py @@ -323,7 +323,8 @@ def validate(f, logger, express_rules=False): f = ifcopenshell.open(f) else: - raise e + logger.error(f'Unsupported schema: {schema_name}') + return log_internal_cpp_errors(filename, logger) @@ -406,7 +407,15 @@ def validate(f, logger, express_rules=False): ) for attr in entity.all_inverse_attributes(): - val = getattr(inst, attr.name()) + try: + val = getattr(inst, attr.name()) + except Exception as e: + if hasattr(logger, "set_state"): + logger.set_state('attribute', f"{entity.name()}.{attr.name()}") + logger.error(str(e)) + else: + logger.error("For instance:\n %s\n%s", inst, e) + continue try: assert_valid_inverse(attr, val, schema) except ValidationError as e: diff --git a/src/ifcopenshell-python/test/util/test_selector.py b/src/ifcopenshell-python/test/util/test_selector.py index 67749417bd..a63a8fdfb2 100644 --- a/src/ifcopenshell-python/test/util/test_selector.py +++ b/src/ifcopenshell-python/test/util/test_selector.py @@ -22,6 +22,32 @@ import ifcopenshell.api import ifcopenshell.util.selector as subject +class TestFormat(): + def test_no_formatting(self): + assert subject.format("123") == "123" + assert subject.format('\"123\"') == "123" + assert subject.format('\"foo\"') == "foo" + + def test_string_formatting(self): + assert subject.format('upper(\"fOo\")') == "FOO" + assert subject.format('lower(\"fOo\")') == "foo" + assert subject.format('title(\"fOo\")') == "Foo" + assert subject.format('concat(\"fOo\", \"bar\")') == "fOobar" + assert subject.format('upper(concat(\"fOo\", \"bar\"))') == "FOOBAR" + + def test_number_formatting(self): + assert subject.format("round(123, 5)") == "125.0" + assert subject.format('round(\"123\", 5)') == "125.0" + assert subject.format('metric_length(123, 5, 2)') == "125.00" + assert subject.format('metric_length(123.123, 0.1, 2)') == "123.10" + assert subject.format('metric_length(\"123\", 5, 2)') == "125.00" + assert subject.format('imperial_length(1, 1)') == "1'" + assert subject.format('imperial_length(3.123, 1)') == "3' - 1\"" + assert subject.format('imperial_length(3.123, 2)') == "3' - 1 1/2\"" + assert subject.format('imperial_length(\"3.123\", 2)') == "3' - 1 1/2\"" + assert subject.format('imperial_length(\"123.123\", 2, \"inch\")') == "10' - 3\"" + + class TestGetElementValue(test.bootstrap.IFC4): def test_selecting_an_elements_class_or_id_using_a_query(self): element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") @@ -52,7 +78,7 @@ class TestGetElementValue(test.bootstrap.IFC4): assert subject.get_element_value(element, "material.item.Name.0") == "L1" assert subject.get_element_value(element, "material.item.Name.1") == "L2" assert subject.get_element_value(element, '"material"."item"."Name"') == ["L1", "L2"] - assert subject.get_element_value(element, 'r"material"."item"."Name"') == ["L1", "L2"] + assert subject.get_element_value(element, 'material."item"."Name"') == ["L1", "L2"] # Provide shortform for convenience assert subject.get_element_value(element, "mat.i.Name") == ["L1", "L2"] @@ -60,7 +86,7 @@ class TestGetElementValue(test.bootstrap.IFC4): element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") assert subject.get_element_value(element, "material.item.Name.0") is None - def test_selceting_a_list_item_that_fails_silently(self): + def test_selecting_a_list_item_that_fails_silently(self): element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") material = ifcopenshell.api.run("material.add_material", self.file, name="CON01") material2 = ifcopenshell.api.run("material.add_material", self.file, name="CON02") @@ -73,6 +99,24 @@ class TestGetElementValue(test.bootstrap.IFC4): assert subject.get_element_value(element, "material.item.Name.0") == "L1" assert subject.get_element_value(element, "material.item.Name.1") is None + def test_selecting_a_pset(self): + element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + element2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + pset = ifcopenshell.api.run("pset.add_pset", self.file, product=element, name="Foobar") + ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"Foo": "Bar"}) + assert subject.get_element_value(element, "Foobar.Foo") == "Bar" + assert subject.get_element_value(element, "Foobar./F.*/") == "Bar" + assert subject.get_element_value(element, "/Foo.*/./F.*/") == "Bar" + ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"Baz": 123}) + assert subject.get_element_value(element, "/Foo.*/./B.*/") == 123 + assert subject.get_element_value(element, "/Foo.*/./.*/") == ["Bar", 123] + ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"Bay": 123.3}) + assert subject.get_element_value(element, "/Foo.*/./B.*/") == [123, 123.3] + pset = ifcopenshell.api.run("pset.add_pset", self.file, product=element, name="Pset_WallCommon") + ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"Status": ["New"]}) + assert subject.get_element_value(element, "/Pset_.*Common/.Status") == ["New"] + assert subject.get_element_value(element, "/Pset_.*Common/.Status.0") == "New" + class TestFilterElements(test.bootstrap.IFC4): def test_selecting_by_globalid(self): diff --git a/src/ifctester/ifctester/ids.py b/src/ifctester/ifctester/ids.py index 692d35254c..38f0fc3f95 100644 --- a/src/ifctester/ifctester/ids.py +++ b/src/ifctester/ifctester/ids.py @@ -168,6 +168,8 @@ class Specification: def parse(self, ids_dict): self.name = ids_dict.get("@name", "") + self.description = ids_dict.get("@description", "") + self.instructions = ids_dict.get("@instructions", "") self.minOccurs = ids_dict["@minOccurs"] self.maxOccurs = ids_dict["@maxOccurs"] self.ifcVersion = ids_dict["@ifcVersion"] diff --git a/src/ifctester/ifctester/reporter.py b/src/ifctester/ifctester/reporter.py index ee9e952269..c982e1ec9b 100644 --- a/src/ifctester/ifctester/reporter.py +++ b/src/ifctester/ifctester/reporter.py @@ -21,8 +21,11 @@ import sys import math import logging import datetime +import numpy as np import ifcopenshell +import ifcopenshell.util.unit import ifcopenshell.util.element +import ifcopenshell.util.placement cwd = os.path.dirname(os.path.realpath(__file__)) @@ -159,31 +162,85 @@ class Json(Reporter): def report(self): self.results["title"] = self.ids.info.get("title", "Untitled IDS") + self.results["date"] = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") + total_specifications = 0 + total_specifications_pass = 0 + total_requirements = 0 + total_requirements_pass = 0 + total_checks = 0 + total_checks_pass = 0 + status = True self.results["specifications"] = [] for specification in self.ids.specifications: - self.results["specifications"].append(self.report_specification(specification)) + specification_report = self.report_specification(specification) + self.results["specifications"].append(specification_report) + total_specifications += 1 + total_specifications_pass += 1 if specification_report["status"] else 0 + total_requirements += len(specification_report["requirements"]) + total_requirements_pass += len([r for r in specification_report["requirements"] if r["status"]]) + total_checks += specification_report["total_checks"] + total_checks_pass += specification_report["total_checks_pass"] + if not specification_report["status"]: + status = False + self.results["status"] = status + self.results["total_specifications"] = total_specifications + self.results["total_specifications_pass"] = total_specifications_pass + self.results["total_specifications_fail"] = total_specifications - total_specifications_pass + self.results["percent_specifications_pass"] = ( + math.floor((total_specifications_pass / total_specifications) * 100) if total_specifications else "N/A" + ) + self.results["total_requirements"] = total_requirements + self.results["total_requirements_pass"] = total_requirements_pass + self.results["total_requirements_fail"] = total_requirements - total_requirements_pass + self.results["percent_requirements_pass"] = ( + math.floor((total_requirements_pass / total_requirements) * 100) if total_requirements else "N/A" + ) + self.results["total_checks"] = total_checks + self.results["total_checks_pass"] = total_checks_pass + self.results["total_checks_fail"] = total_checks - total_checks_pass + self.results["percent_checks_pass"] = ( + math.floor((total_checks_pass / total_checks) * 100) if total_checks else "N/A" + ) return self.results def report_specification(self, specification): applicability = [a.to_string("applicability") for a in specification.applicability] + total_applicable = len(specification.applicable_entities) + total_checks = 0 + total_checks_pass = 0 requirements = [] for requirement in specification.requirements: + total_fail = len(requirement.failed_entities) + total_checks += total_applicable + total_checks_pass += total_applicable - total_fail requirements.append( { "description": requirement.to_string("requirement"), "status": requirement.status, "failed_entities": self.report_failed_entities(requirement), + "total_applicable": total_applicable, + "total_pass": total_applicable - total_fail, + "total_fail": total_fail, } ) - total = len(specification.applicable_entities) - total_successes = total - len(specification.failed_entities) - percentage = math.floor((total_successes / total) * 100) if total else "N/A" + total_applicable_pass = total_applicable - len(specification.failed_entities) + percent_applicable_pass = ( + math.floor((total_applicable_pass / total_applicable) * 100) if total_applicable else "N/A" + ) + percent_checks_pass = math.floor((total_checks_pass / total_checks) * 100) if total_checks else "N/A" return { "name": specification.name, + "description": specification.description, + "instructions": specification.instructions, "status": specification.status, - "total_successes": total_successes, - "total": total, - "percentage": percentage, + "total_applicable": total_applicable, + "total_applicable_pass": total_applicable_pass, + "total_applicable_fail": total_applicable - total_applicable_pass, + "percent_applicable_pass": percent_applicable_pass, + "total_checks": total_checks, + "total_checks_pass": total_checks_pass, + "total_checks_fail": total_checks - total_checks_pass, + "percent_checks_pass": percent_checks_pass, "required": specification.minOccurs != 0, "applicability": applicability, "requirements": requirements, @@ -223,12 +280,15 @@ class Html(Json): self.results = {} def report(self): - self.results["title"] = self.ids.info.get("title", "Untitled IDS") - self.results["time"] = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") - self.results["specifications"] = [] - for specification in self.ids.specifications: - self.results["specifications"].append(self.report_specification(specification)) - return self.results + super().report() + entity_limit = 100 + for spec in self.results["specifications"]: + for requirement in spec["requirements"]: + total = len(requirement["failed_entities"]) + requirement["failed_entities"] = requirement["failed_entities"][0:entity_limit] + requirement["has_omitted"] = total > entity_limit + requirement["total_entities"] = total + requirement["total_omitted"] = total - entity_limit def to_string(self): import pystache @@ -272,7 +332,7 @@ class Ods(Json): table = Table(name=self.results["title"]) tr = TableRow() - for header in ["Specification", "Status", "Total Compliant", "Total Applicable", "Percentage Compliant"]: + for header in ["Specification", "Status", "Total Pass", "Total Checks", "Percentage Pass"]: tc = TableCell(valuetype="string", stylename="h") tc.addElement(P(text=header)) tr.addElement(tc) @@ -284,9 +344,9 @@ class Ods(Json): [ specification["name"], "Pass" if specification["status"] else "Fail", - str(specification["total_successes"]), - str(specification["total"]), - str(specification["percentage"]), + str(specification["total_checks_pass"]), + str(specification["total_checks"]), + str(specification["percent_checks_pass"]), ] ) @@ -309,7 +369,17 @@ class Ods(Json): continue table = Table(name=specification["name"]) tr = TableRow() - for header in ["Requirement", "Problem", "Element"]: + for header in [ + "Requirement", + "Problem", + "Class", + "PredefinedType", + "Name", + "Description", + "GlobalId", + "Tag", + "Element", + ]: tc = TableCell(valuetype="string", stylename="h") tc.addElement(P(text=header)) tr.addElement(tc) @@ -321,6 +391,12 @@ class Ods(Json): row = [ requirement["description"], failure.get("reason", "No reason provided"), + failure["class"], + failure["predefined_type"], + failure["name"], + failure["description"], + failure["global_id"], + failure["tag"], str(failure.get("element", "No element found")), ] tr = TableRow() @@ -347,6 +423,7 @@ class Bcf(Json): def to_file(self, filepath): from bcf.v2.bcfxml import BcfXml + unit_scale = None bcfxml = BcfXml.create_new(self.results["title"]) for specification in self.results["specifications"]: if specification["status"]: @@ -356,11 +433,22 @@ class Bcf(Json): continue for failure in requirement["failed_entities"]: element = failure["element"] - title = f"ID:[{element.id()}]/GUID:[{element.GlobalId}]/{element.is_a()}/" - title += getattr(element, "Name", None) or "Unnamed" - title += " - " + failure.get("reason", "No reason") + title_components = [ + element.is_a(), + getattr(element, "Name", None) or "Unnamed", + failure.get("reason", "No reason"), + getattr(element, "GlobalId", ""), + getattr(element, "Tag", ""), + ] + title = " - ".join(title_components) description = f'{specification["name"]} - {requirement["description"]}' topic = bcfxml.add_topic(title, description, "IfcTester") + if getattr(element, "ObjectPlacement", None): + placement = ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement) + if unit_scale is None: + unit_scale = ifcopenshell.util.unit.calculate_unit_scale(element.wrapped_data.file) + location = [(o * unit_scale) + 5.0 for o in placement[:, 3][:3]] + viewpoint = topic.add_viewpoint_from_point_and_guids(np.array(location), element.GlobalId) if element.is_a("IfcElement"): topic.add_viewpoint(element) bcfxml.save_project(filepath) diff --git a/src/ifctester/ifctester/templates/report.html b/src/ifctester/ifctester/templates/report.html index e452837ba4..8ab743e1ae 100644 --- a/src/ifctester/ifctester/templates/report.html +++ b/src/ifctester/ifctester/templates/report.html @@ -26,173 +26,153 @@ {{name}}

{{title}}

-

{{time}}

+

{{date}}

+

Summary

+
+
{{percent_checks_pass}}%
+
+

+ {{#status}}Pass{{/status}}{{^status}}Fail{{/status}} + + Specifications passed: {{total_specifications_pass}} / {{total_specifications}} + + + Requirements passed: {{total_requirements_pass}} / {{total_requirements}} + + + Checks passed: {{total_checks_pass}} / {{total_checks}} + +

+
{{#specifications}}

{{name}}

+ {{#description}} +

{{description}}

+ {{/description}} + {{#instructions}} +

{{instructions}}

+ {{/instructions}} +
-
{{percentage}}%
+
{{percent_checks_pass}}%
-
- -

- {{#status}}Pass{{/status}}{{^status}}Fail{{/status}} - Passed: {{total_successes}} / {{total}} ({{percentage}}%) - click here to see details -

-
-
    - {{#requirements}} -
  1. - {{description}} - {{^status}}{{#total}} -

    - {{#failed_entities}} - {{reason}} {{element}}
    - {{/failed_entities}} -

    - {{/total}}{{/status}} -
  2. - {{/requirements}} -
-
+

+ {{#status}}Pass{{/status}}{{^status}}Fail{{/status}} + + Checks passed: {{total_checks_pass}} / {{total_checks}} + + + Elements passed: {{total_applicable_pass}} / {{total_applicable}} + +

+

+ Applicability +

+
    + {{#applicability}} +
  • {{.}}
  • + {{/applicability}} +
+

+ Requirements +

+
    + {{#requirements}} +
  1. +
    + + {{description}} + + {{#total_fail}} + + + + + + + + + + + + + + {{#failed_entities}} + + + + + + + + + + {{/failed_entities}} + {{#has_omitted}} + + + + {{/has_omitted}} + +
    ClassPredefinedTypeNameDescriptionWarningGlobalIdTag
    {{class}}{{predefined_type}}{{name}}{{description}}{{reason}}{{global_id}}{{tag}}
    ... {{total_omitted}} more elements not shown out of {{total_entities}} total ...
    + {{/total_fail}} +
    +
  2. + {{/requirements}} +
{{/specifications}}
diff --git a/src/qtviewer/CMakeLists.txt b/src/qtviewer/CMakeLists.txt index 473013a726..bce0fbcedf 100644 --- a/src/qtviewer/CMakeLists.txt +++ b/src/qtviewer/CMakeLists.txt @@ -20,22 +20,48 @@ cmake_minimum_required(VERSION 3.1.3) message("Running CMakeLists.txt in /src/qtviewer") -message("Provide the path to Qt5 via CMAKE_PREFIX_PATH") -find_package(Qt5 COMPONENTS Core Gui OpenGL Widgets REQUIRED) +# Specify the Qt version and components to use +set(QT_VERSION 6 CACHE STRING "Qt version") +set(QT_COMPONENTS Core Gui OpenGL OpenGLWidgets Widgets CACHE STRING "Qt components") -add_executable(QtViewer - ../../src/qtviewer/mainwindow.h - ../../src/qtviewer/mainwindow.cpp - ../../src/qtviewer/main.cpp +find_package(Qt${QT_VERSION} COMPONENTS ${QT_COMPONENTS} REQUIRED PATHS ${QT_DIR}) + +if(Qt${QT_VERSION}_FOUND) + message(STATUS "Found Qt Version: ${Qt${QT_VERSION}_VERSION}") +endif() + +set(targetName "QtViewer") + +add_executable(${targetName} + IfcViewerWidget.h + IfcViewerWidget.cpp + ParseIfcFile.h + ParseIfcFile.cpp + MainWindow.h + MainWindow.cpp + main.cpp ) -set_target_properties(QtViewer PROPERTIES - AUTOMOC On +set_target_properties(${targetName} PROPERTIES + AUTOMOC On + WIN32_EXECUTABLE ON + MACOSX_BUNDLE ON ) -target_link_libraries(QtViewer - ${IFCLIBS} - Qt5::Core Qt5::Gui Qt5::OpenGL Qt5::Widgets - ${OPENCASCADE_LIBRARIES} -) \ No newline at end of file +target_link_libraries(${targetName} + ${IFCOPENSHELL_LIBRARIES} + Qt${QT_VERSION}::Core + Qt${QT_VERSION}::Gui + Qt${QT_VERSION}::OpenGL + Qt${QT_VERSION}::OpenGLWidgets + Qt${QT_VERSION}::Widgets + ${OPENCASCADE_LIBRARIES} +) + +target_include_directories(${targetName} PUBLIC + ${QT_DIR}/include +) + +get_target_property(targetIncludeDirs ${targetName} INCLUDE_DIRECTORIES) +message(STATUS "target_include_directories: ${targetIncludeDirs}") \ No newline at end of file diff --git a/src/qtviewer/IfcViewerWidget.cpp b/src/qtviewer/IfcViewerWidget.cpp new file mode 100644 index 0000000000..c2a77bed83 --- /dev/null +++ b/src/qtviewer/IfcViewerWidget.cpp @@ -0,0 +1,27 @@ +#include "IfcViewerWidget.h" + +IfcViewerWidget::IfcViewerWidget(QWidget *parent) + : QOpenGLWidget(parent) +{} + +void IfcViewerWidget::initializeGL() +{ + // Set up the rendering context, load shaders and other resources, etc.: + QOpenGLFunctions *f = QOpenGLContext::currentContext()->functions(); + f->glClearColor(1.0f, 1.0f, 1.0f, 1.0f); +} + +void IfcViewerWidget::resizeGL(int w, int h) +{ + // Update projection matrix and other size related settings: + m_projection.setToIdentity(); + m_projection.perspective(45.0f, w / float(h), 0.01f, 100.0f); +} + +void IfcViewerWidget::paintGL() +{ + // Render geometries from the parsed IFC file + // Draw the scene: + QOpenGLFunctions *f = QOpenGLContext::currentContext()->functions(); + f->glClear(GL_COLOR_BUFFER_BIT); +} diff --git a/src/qtviewer/IfcViewerWidget.h b/src/qtviewer/IfcViewerWidget.h new file mode 100644 index 0000000000..8ce7937069 --- /dev/null +++ b/src/qtviewer/IfcViewerWidget.h @@ -0,0 +1,23 @@ +#ifndef IFCVIEWERWIDGET_H +#define IFCVIEWERWIDGET_H + +#include +#include +#include +#include + +class IfcViewerWidget : public QOpenGLWidget +{ +public: + IfcViewerWidget(QWidget *parent = nullptr); + +protected: + void initializeGL() override; + void resizeGL(int w, int h) override; + void paintGL() override; + +private: + QMatrix4x4 m_projection; +}; + +#endif // IFCVIEWERWIDGET_H \ No newline at end of file diff --git a/src/qtviewer/MainWindow.cpp b/src/qtviewer/MainWindow.cpp new file mode 100644 index 0000000000..d7f1189a10 --- /dev/null +++ b/src/qtviewer/MainWindow.cpp @@ -0,0 +1,127 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "MainWindow.h" +#include "IfcViewerWidget.h" + +MainWindow::MainWindow(QWidget *parent) + : QMainWindow(parent) +{ + this->setWindowTitle("IfcOpenShell Viewer"); + this->resize(800, 600); //temporary reasonable initial size + + m_glWidget = new IfcViewerWidget(this); + QSizePolicy glSizePolicy = m_glWidget->sizePolicy(); + glSizePolicy.setVerticalStretch(3); + m_glWidget->setSizePolicy(glSizePolicy); + + m_outputText = new QPlainTextEdit(this); + m_outputText->setReadOnly(true); + + QSplitter* splitter = new QSplitter(Qt::Vertical, this); + splitter->addWidget(m_glWidget); + splitter->addWidget(m_outputText); + + setCentralWidget(splitter); + + createActions(); + createMenus(); + createConnections(); +} + +void MainWindow::createActions() +{ + openAction = new QAction(tr("&Open"), this); + openAction->setShortcut(QKeySequence(tr("Ctrl+O"))); + + quitAction = new QAction(tr("E&xit"), this); + quitAction->setShortcut(QKeySequence::Quit); + + m_backgroundAction = new QAction(tr("&Background")); + m_backgroundAction->setEnabled(false); + m_backgroundAction->setCheckable(true); + m_backgroundAction->setChecked(false); + + m_outlineAction = new QAction(tr("&Outline")); + m_outlineAction->setEnabled(false); + m_outlineAction->setCheckable(true); + m_outlineAction->setChecked(false); +} + +void MainWindow::createMenus() +{ + QMenuBar *menuBar = new QMenuBar(); + setMenuBar(menuBar); + + fileMenu = new QMenu(tr("&File"), menuBar); + menuBar->addMenu(fileMenu); + + fileMenu->addAction(openAction); + fileMenu->addAction(quitAction); + + viewMenu = new QMenu(tr("&View"), menuBar); + menuBar->addMenu(viewMenu); + + viewMenu->addAction(m_backgroundAction); + viewMenu->addAction(m_outlineAction); +} + +void MainWindow::createConnections() +{ + connect(openAction, SIGNAL(triggered()), this, SLOT(openFile())); + connect(quitAction, SIGNAL(triggered()), qApp, SLOT(quit())); + //connect(m_backgroundAction, SIGNAL(toggled(bool)), (QWidget*)m_v, SLOT(setViewBackground(bool))); + //connect(m_outlineAction, SIGNAL(toggled(bool)), (QWidget*)m_v, SLOT(setViewOutline(bool))); + + connect(&m_parser, &ParseIfcFile::parsingInfo, this, &MainWindow::appendToOutputText); +} + +void MainWindow::appendToOutputText(const QString& message) +{ + m_outputText->appendPlainText(message); +} + +void MainWindow::openFile() +{ + QString filePath = QFileDialog::getOpenFileName(this, tr("Open IFC File"), + ".", tr("IFC Files (*.ifc)")); + + QFileInfo fileInfo(filePath); + QString fileName = fileInfo.fileName(); + + if (fileName.isEmpty()) + return; + + // Check if the file is a Qt resource file + if (fileName.startsWith(":/")) + return; + + QFile file(filePath); + if (!file.exists()) { + QMessageBox::critical(this, tr("Open IFC"), + QString("Could not open '%1'.").arg(filePath)); + + m_outlineAction->setEnabled(false); + m_backgroundAction->setEnabled(false); + return; + } + + QString message = tr("Opening file: %1\n").arg(filePath); + appendToOutputText(message); + + m_parser.Parse(filePath.toStdString()); + + m_currentPath = filePath; + setWindowTitle(tr("%1 - IFCViewer").arg(fileName)); + + m_outlineAction->setEnabled(true); + m_backgroundAction->setEnabled(true); +} \ No newline at end of file diff --git a/src/qtviewer/MainWindow.h b/src/qtviewer/MainWindow.h new file mode 100644 index 0000000000..2cd41e1ca2 --- /dev/null +++ b/src/qtviewer/MainWindow.h @@ -0,0 +1,49 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include +#include +#include +#include +#include +#include + +#include "ParseIfcFile.h" + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + MainWindow(QWidget *parent = nullptr); + +public slots: + void openFile(); + +private: + void createActions(); + void createMenus(); + void createConnections(); + void appendToOutputText(const QString& message); +private: + QMenu *fileMenu; + QAction *openAction; + QAction *quitAction; + + QMenu *viewMenu; + QAction *m_nativeAction; + QAction *m_glAction; + QAction *m_imageAction; + QAction *m_highQualityAntialiasingAction; + QAction *m_backgroundAction; + QAction *m_outlineAction; + + QString m_currentPath; + + QOpenGLWidget *m_glWidget; + QPlainTextEdit *m_outputText; + + ParseIfcFile m_parser; +}; + +#endif // MAINWINDOW_H diff --git a/src/qtviewer/ParseIfcFile.cpp b/src/qtviewer/ParseIfcFile.cpp new file mode 100644 index 0000000000..e5dc8e99bf --- /dev/null +++ b/src/qtviewer/ParseIfcFile.cpp @@ -0,0 +1,42 @@ +#include "ParseIfcFile.h" + +#include "../ifcgeom_schema_agnostic/IfcGeomIterator.h" + +ParseIfcFile::ParseIfcFile() {} + +ParseIfcFile::~ParseIfcFile() {} + +void ParseIfcFile::outputMsg(const std::string& msg) +{ + emit parsingInfo(QString::fromStdString(msg)); +} + +void ParseIfcFile::Parse(const std::string& filePath) +{ + IfcParse::IfcFile file(filePath); + + IfcGeom::IteratorSettings settings; + settings.set(IfcGeom::IteratorSettings::USE_WORLD_COORDS, true); + settings.set(IfcGeom::IteratorSettings::WELD_VERTICES, false); + + IfcGeom::Iterator* it = new IfcGeom::Iterator(settings, &file); + if (!it->initialize()) { + outputMsg("Error: Iterator failed to initialize! Aborting."); + delete it; + return; + } + + do { + //const IfcGeom::BRepElement* bRepElem = it->get_native(); + const IfcGeom::TriangulationElement* triElem = static_cast(it->get()); + outputMsg(triElem->type() + ": " + triElem->name()); + + const boost::shared_ptr& triElemGeom = triElem->geometry_pointer(); + + // materials + const std::vector& elemMats = triElemGeom->materials(); + for (auto mat : elemMats) { + outputMsg(" " + mat.original_name()); + } + } while (it->next()); +} \ No newline at end of file diff --git a/src/qtviewer/ParseIfcFile.h b/src/qtviewer/ParseIfcFile.h new file mode 100644 index 0000000000..2d1a7c5eb0 --- /dev/null +++ b/src/qtviewer/ParseIfcFile.h @@ -0,0 +1,25 @@ +#ifndef PARSEIFCFILE_H +#define PARSEIFCFILE_H + +#include +#include +#include + +class ParseIfcFile : public QObject +{ + Q_OBJECT + +signals: + void parsingInfo(const QString& info); + +private: + void outputMsg(const std::string& msg); + +public: + ParseIfcFile(); + ~ParseIfcFile(); + + void Parse(const std::string& filePath); +}; + +#endif // PARSEIFCFILE_H \ No newline at end of file diff --git a/src/qtviewer/main.cpp b/src/qtviewer/main.cpp index c0171fef3b..b65d413813 100644 --- a/src/qtviewer/main.cpp +++ b/src/qtviewer/main.cpp @@ -1,24 +1,12 @@ - +#include "MainWindow.h" + #include -#include -#ifndef QT_NO_OPENGL -#include -#endif -#include "mainwindow.h" - -int main(int argc, char **argv) +int main(int argc, char *argv[]) { - QApplication app(argc, argv); - - //QGLViewer viewer; - - // Restore the previous viewer state. - //viewer.restoreStateFromFile(); - - MainWindow window; - //window.openFile(" "); + QApplication app(argc, argv); + MainWindow window; window.show(); return app.exec(); diff --git a/src/qtviewer/mainwindow.cpp b/src/qtviewer/mainwindow.cpp deleted file mode 100644 index cdc0d18bf3..0000000000 --- a/src/qtviewer/mainwindow.cpp +++ /dev/null @@ -1,97 +0,0 @@ - - -#include "mainwindow.h" -#include - -#include - -MainWindow::MainWindow() - : QMainWindow() -{ - - - IfcGeomObjects::Settings(IfcGeomObjects::USE_WORLD_COORDS,true); - IfcGeomObjects::Settings(IfcGeomObjects::WELD_VERTICES,false); - IfcGeomObjects::Settings(IfcGeomObjects::SEW_SHELLS,true); - - - - QMenu *fileMenu = new QMenu(tr("&File"), this); - QAction *openAction = fileMenu->addAction(tr("&Open...")); - openAction->setShortcut(QKeySequence(tr("Ctrl+O"))); - QAction *quitAction = fileMenu->addAction(tr("E&xit")); - quitAction->setShortcuts(QKeySequence::Quit); - - menuBar()->addMenu(fileMenu); - - QMenu *viewMenu = new QMenu(tr("&View"), this); - m_backgroundAction = viewMenu->addAction(tr("&Background")); - m_backgroundAction->setEnabled(false); - m_backgroundAction->setCheckable(true); - m_backgroundAction->setChecked(false); - //connect(m_backgroundAction, SIGNAL(toggled(bool)), (QWidget*)m_v, SLOT(setViewBackground(bool))); - - m_outlineAction = viewMenu->addAction(tr("&Outline")); - m_outlineAction->setEnabled(false); - m_outlineAction->setCheckable(true); - m_outlineAction->setChecked(true); - // connect(m_outlineAction, SIGNAL(toggled(bool)), (QWidget*)m_v, SLOT(setViewOutline(bool))); - - menuBar()->addMenu(viewMenu); - - connect(openAction, SIGNAL(triggered()), this, SLOT(openFile())); - connect(quitAction, SIGNAL(triggered()), qApp, SLOT(quit())); - - //setCentralWidget((QWidget*)m_v); - setWindowTitle(tr("IfcOpenShell QT Viewer")); -} - -void MainWindow::openFile(const QString &path) -{ - QString fileName; - if (path.isNull()) - fileName = QFileDialog::getOpenFileName(this, tr("Open IFC"), - m_currentPath, "IFC files (*.ifc)"); - else - fileName = path; - - if (!fileName.isEmpty()) { - QFile file(fileName); - if (!file.exists()) { - QMessageBox::critical(this, tr("Open IFC"), - QString("Could not open file '%1'.").arg(fileName)); - - m_outlineAction->setEnabled(false); - m_backgroundAction->setEnabled(false); - return; - } - std::stringstream ss; - if ( ! IfcGeomObjects::Init(fileName.toStdString(),&std::cout,&ss) ) { - QMessageBox::critical(this, tr("Open IFC"), - QString("[Error] unable to parse file '%1'. Or no geometrical entities found" ).arg(fileName)); - return; - } - - //connect((QWidget*)m_view, SIGNAL(drawNeeded()), this, SLOT(drawIfcObject())); - - if (!fileName.startsWith(":/")) { - m_currentPath = fileName; - setWindowTitle(tr("%1 - IFCViewer").arg(m_currentPath)); - } - - m_outlineAction->setEnabled(true); - m_backgroundAction->setEnabled(true); - -// resize(m_view->sizeHint() + QSize(80, 80 + menuBar()->height())); - } -} - - -void MainWindow::draw() -{ - - - -} - - diff --git a/src/qtviewer/mainwindow.h b/src/qtviewer/mainwindow.h deleted file mode 100644 index a62e823815..0000000000 --- a/src/qtviewer/mainwindow.h +++ /dev/null @@ -1,47 +0,0 @@ - - -#ifndef MAINWINDOW_H -#define MAINWINDOW_H - -#include -#include -#include -#include "../ifcgeom/IfcGeomObjects.h" - -class ObjectsView; - -class QGLViewer; - -QT_BEGIN_NAMESPACE -class QAction; -class QGraphicsView; -class QGraphicsScene; -class QGraphicsRectItem; -QT_END_NAMESPACE - -class MainWindow : public QMainWindow -{ - Q_OBJECT - -public: - MainWindow(); - - -public Q_SLOTS: - void draw(); - -public slots: - void openFile(const QString &path = QString()); - -private: - QAction *m_nativeAction; - QAction *m_glAction; - QAction *m_imageAction; - QAction *m_highQualityAntialiasingAction; - QAction *m_backgroundAction; - QAction *m_outlineAction; - - QString m_currentPath; -}; - -#endif diff --git a/src/serializers/GltfSerializer.cpp b/src/serializers/GltfSerializer.cpp index ffebf97935..3c285b7c03 100644 --- a/src/serializers/GltfSerializer.cpp +++ b/src/serializers/GltfSerializer.cpp @@ -44,6 +44,9 @@ static const uint32_t PRIM_TRIANGLES = 4; static const uint32_t PRIM_TRIANGLE_STRIP = 5; static const uint32_t PRIM_TRIANGLE_FAN = 6; +static const uint32_t ELEMENT_ARRAY_BUFFER = 34963; +static const uint32_t ARRAY_BUFFER = 34962; + GltfSerializer::GltfSerializer(const std::string& filename, const SerializerSettings& settings) : WriteOnlyGeometrySerializer(settings) , filename_(filename) @@ -52,6 +55,7 @@ GltfSerializer::GltfSerializer(const std::string& filename, const SerializerSett , fstream_(IfcUtil::path::from_utf8(filename).c_str(), std::ios_base::binary) , tmp_fstream1_(IfcUtil::path::from_utf8(tmp_filename1_).c_str(), std::ios_base::binary) , tmp_fstream2_(IfcUtil::path::from_utf8(tmp_filename2_).c_str(), std::ios_base::binary) + , bufferViewId(0) {} GltfSerializer::~GltfSerializer() { @@ -121,17 +125,25 @@ const uint32_t component_type::value = CT_UNSIGNED_INT; template <> const uint32_t component_type::value = CT_FLOAT; + template -size_t write_accessor(json& j, std::ofstream& ofs, It begin, It end) { +size_t write_accessor(json& j, std::ofstream& ofs, It begin, It end, int bufferViewId) { auto num = std::distance(begin, end) / N; json accessor = json::object(); - accessor["bufferView"] = N == 1 ? 0 : 1; - accessor["byteOffset"] = (size_t)ofs.tellp(); + accessor["bufferView"] = bufferViewId; + accessor["byteOffset"] = 0; accessor["componentType"] = component_type::value; accessor["count"] = num; + if (N == 1) { + j["bufferViews"].push_back({ {"buffer", 0}, {"byteOffset", (size_t)ofs.tellp()}, { "byteLength", num * 4}, {"target", ELEMENT_ARRAY_BUFFER} }); + } else { + j["bufferViews"].push_back({ {"buffer", 0}, {"byteStride", 12}, { "byteOffset", (size_t)ofs.tellp()}, { "byteLength", num * 12}, {"target", ARRAY_BUFFER}}); + } + + std::array min, max; min.fill(std::numeric_limits::max()); max.fill(std::numeric_limits::lowest()); @@ -233,16 +245,16 @@ void GltfSerializer::write(const IfcGeom::TriangulationElement* o) { json primitive = json::object(); - primitive["indices"] = write_accessor<1U>(json_, tmp_fstream1_, idx_transformed.begin(), idx_transformed.end()); + primitive["indices"] = write_accessor<1U>(json_, tmp_fstream1_, idx_transformed.begin(), idx_transformed.end(), bufferViewId++); auto vbegin = o->geometry().verts().begin(); std::vector vf(vbegin + idx_begin * 3, vbegin + idx_end * 3); - primitive["attributes"]["POSITION"] = write_accessor<3U>(json_, tmp_fstream2_, vf.begin(), vf.end()); + primitive["attributes"]["POSITION"] = write_accessor<3U>(json_, tmp_fstream2_, vf.begin(), vf.end(), bufferViewId++); if (o->geometry().normals().size()) { auto nbegin = o->geometry().normals().begin(); std::vector nf(nbegin + idx_begin * 3, nbegin + idx_end * 3); - primitive["attributes"]["NORMAL"] = write_accessor<3U>(json_, tmp_fstream2_, nf.begin(), nf.end()); + primitive["attributes"]["NORMAL"] = write_accessor<3U>(json_, tmp_fstream2_, nf.begin(), nf.end(), bufferViewId++); } primitive["material"] = writeMaterial(o->geometry().materials()[*mid0]); @@ -325,23 +337,38 @@ void GltfSerializer::finalize() { json scene_0; scene_0["nodes"] = node_array_; json_["scenes"].push_back(scene_0); - json_["bufferViews"].push_back({ {"buffer", 0}, { "byteLength", indices_length } }); - json_["bufferViews"].push_back({ {"buffer", 0}, {"byteStride", 12}, { "byteOffset", indices_length }, { "byteLength", binary_length - indices_length } }); + + //The generated glb file will contain the indices buffer followed by the vertices buffer. + //Therefore once we know the size of the indices buffer, we update our vertices buffer + //to have an offset equal to the size of the indices buffer. + for (auto &n : json_["bufferViews"]) { + if (n.contains("byteStride")) { + n["byteOffset"] = (int)n["byteOffset"] + indices_length; + } + } + json_["buffers"].push_back({ {"byteLength", binary_length} }); std::string json_contents = json_.dump(); uint32_t json_length = (uint32_t) json_contents.size(); - uint32_t header[] = { GLTF, 2U, 12 + 8 + json_length + padding_for(json_length) + 8 + binary_length + padding_for(binary_length) }; + const int GLB_FILE_HEADER = 12; + const int GLB_JSON_HEADER = 8; + const int GLB_BINARY_CHUNK_HEADER = 8; + + uint32_t header[] = { GLTF, 2U, GLB_FILE_HEADER + GLB_JSON_HEADER + json_length + padding_for(json_length) + + GLB_BINARY_CHUNK_HEADER + binary_length + padding_for(binary_length) }; fstream_.write((const char*)header, sizeof(header)); write_block(fstream_, json_contents.begin(), json_contents.end()); write_header(fstream_, binary_length); { + //First, write the indices buffer into our glb file std::ifstream ifs(IfcUtil::path::from_utf8(tmp_filename1_).c_str(), std::ios::binary); fstream_ << ifs.rdbuf(); } { + //Next, write the vertices buffer into our glb file std::ifstream ifs(IfcUtil::path::from_utf8(tmp_filename2_).c_str(), std::ios::binary); fstream_ << ifs.rdbuf(); } diff --git a/src/serializers/GltfSerializer.h b/src/serializers/GltfSerializer.h index ca8c68303a..7450fa2afb 100644 --- a/src/serializers/GltfSerializer.h +++ b/src/serializers/GltfSerializer.h @@ -36,6 +36,8 @@ private: std::ofstream fstream_, tmp_fstream1_, tmp_fstream2_; std::map materials_, meshes_; json json_, node_array_; + int bufferViewId; + int writeMaterial(const IfcGeom::Material& style); public: diff --git a/src/serializers/SvgSerializer.cpp b/src/serializers/SvgSerializer.cpp index b11db7287c..92a58485a5 100644 --- a/src/serializers/SvgSerializer.cpp +++ b/src/serializers/SvgSerializer.cpp @@ -1149,14 +1149,14 @@ void SvgSerializer::write(const geometry_data& data) { if (storey) { auto it = storey_hlr.find(storey); if (it == storey_hlr.end()) { - it = storey_hlr.insert({ storey, hlr_t(use_prefiltering_, use_hlr_poly_, projection_plane) }).first; + it = storey_hlr.insert({ storey, hlr_t(use_prefiltering_, use_hlr_poly_, segment_projection_, projection_plane) }).first; } - it->second.add(*compound_to_hlr); + it->second.add(*compound_to_hlr, data.product); } else { Logger::Warning("Unable to invoke HLR due to absence of storey containment", data.product); } } else if (hlr) { - hlr->add(*compound_to_hlr); + hlr->add(*compound_to_hlr, data.product); } } } @@ -1699,55 +1699,66 @@ std::array, 3> SvgSerializer::resize() { } void SvgSerializer::draw_hlr(const gp_Pln& pln, const drawing_key& drawing_name) { - TopoDS_Shape hlr_compound_unmirrored = (drawing_name.first ? this->storey_hlr.find(drawing_name.first)->second : *hlr).build(); + auto hlr_items = (drawing_name.first ? this->storey_hlr.find(drawing_name.first)->second : *hlr).build(); - if (!hlr_compound_unmirrored.IsNull()) { - // Compound 3D curves for mirroring to work - ShapeFix_Edge sfe; - TopExp_Explorer exp(hlr_compound_unmirrored, TopAbs_EDGE); - for (; exp.More(); exp.Next()) { - sfe.FixAddCurve3d(TopoDS::Edge(exp.Current())); - } + for (auto& p : hlr_items) { + const TopoDS_Shape& hlr_compound_unmirrored = p.second; - // Mirror to match SVG coord system. - // @todo this is very wasteful. We better do the Y-mirror in the SVG writing and - // not on the TopoDS_Shape input. - - TopoDS_Shape hlr_compound; - if (drawing_name.first == nullptr) { - gp_Trsf trsf_mirror; - if (!mirror_y_) { - trsf_mirror.SetMirror(gp_Ax2(gp::Origin(), gp::DY())); + if (!hlr_compound_unmirrored.IsNull()) { + // Compound 3D curves for mirroring to work + ShapeFix_Edge sfe; + TopExp_Explorer exp(hlr_compound_unmirrored, TopAbs_EDGE); + for (; exp.More(); exp.Next()) { + sfe.FixAddCurve3d(TopoDS::Edge(exp.Current())); } - if (mirror_x_) { - gp_Trsf mirror_x; - mirror_x.SetMirror(gp_Ax2(gp::Origin(), gp::DX())); - trsf_mirror.PreMultiply(mirror_x); + + // Mirror to match SVG coord system. + // @todo this is very wasteful. We better do the Y-mirror in the SVG writing and + // not on the TopoDS_Shape input. + + TopoDS_Shape hlr_compound; + if (drawing_name.first == nullptr) { + gp_Trsf trsf_mirror; + if (!mirror_y_) { + trsf_mirror.SetMirror(gp_Ax2(gp::Origin(), gp::DY())); + } + if (mirror_x_) { + gp_Trsf mirror_x; + mirror_x.SetMirror(gp_Ax2(gp::Origin(), gp::DX())); + trsf_mirror.PreMultiply(mirror_x); + } + BRepBuilderAPI_Transform make_transform_mirror(hlr_compound_unmirrored, trsf_mirror, true); + make_transform_mirror.Build(); + hlr_compound = make_transform_mirror.Shape(); + } else { + // In case of building storey-based floor plan the mirroring has already + // been taken into account before projection. + hlr_compound = hlr_compound_unmirrored; } - BRepBuilderAPI_Transform make_transform_mirror(hlr_compound_unmirrored, trsf_mirror, true); - make_transform_mirror.Build(); - hlr_compound = make_transform_mirror.Shape(); - } else { - // In case of building storey-based floor plan the mirroring has already - // been taken into account before projection. - hlr_compound = hlr_compound_unmirrored; - } - exp.Init(hlr_compound, TopAbs_EDGE); - BRep_Builder B; - path_object* po; - if (drawing_name.first) { - po = &start_path(pln, drawing_name.first, "class=\"projection\""); - } else { - po = &start_path(pln, drawing_name.second, "class=\"projection\""); - } - for (; exp.More(); exp.Next()) { - TopoDS_Wire w; - B.MakeWire(w); - B.Add(w, exp.Current()); - write(*po, w); - } + exp.Init(hlr_compound, TopAbs_EDGE); + BRep_Builder B; + path_object* po; + std::string name; + if (p.first) { + name = nameElement(p.first); + boost::replace_all(name, "class=\"", "class=\"projection "); + } else { + name = "class=\"projection\""; + } + if (drawing_name.first) { + po = &start_path(pln, drawing_name.first, name); + } else { + po = &start_path(pln, drawing_name.second, name); + } + for (; exp.More(); exp.Next()) { + TopoDS_Wire w; + B.MakeWire(w); + B.Add(w, exp.Current()); + write(*po, w); + } + } } } @@ -1812,18 +1823,19 @@ void SvgSerializer::addTextAnnotations(const drawing_key& k) { v.Transform(trsf_view); auto svg_name = nameElement(ann); - path_object* po; - if (k.first) { - po = &start_path(meta.pln_3d, k.first, svg_name); - } else { - po = &start_path(meta.pln_3d, k.second, svg_name); - } if (object_type.size()) { // postfix the object_type for CSS matching boost::replace_all(svg_name, "class=\"IfcAnnotation\"", "class=\"IfcAnnotation " + object_type + "\""); } + path_object* po; + if (k.first) { + po = &start_path(meta.pln_3d, k.first, svg_name); + } else { + po = &start_path(meta.pln_3d, k.second, svg_name); + } + boost::optional font_size; std::vector tokens; boost::split(tokens, name, boost::is_any_of("_")); @@ -1987,7 +1999,7 @@ void SvgSerializer::finalize() { // @todo do we have always have pln here? if (use_hlr && pln) { - hlr = new hlr_t(use_prefiltering_, use_hlr_poly_, *pln); + hlr = new hlr_t(use_prefiltering_, use_hlr_poly_, segment_projection_, *pln); } section_data_ = std::vector{ sd }; diff --git a/src/serializers/SvgSerializer.h b/src/serializers/SvgSerializer.h index 6cd806737c..9aeff1e0e6 100644 --- a/src/serializers/SvgSerializer.h +++ b/src/serializers/SvgSerializer.h @@ -205,31 +205,52 @@ namespace { class hlr_calc { private: const HLRAlgo_Projector& projector_; + const std::list>* product_shapes_ = nullptr; public: - typedef TopoDS_Shape result_type; + typedef std::list> result_type; hlr_calc(const HLRAlgo_Projector& projector) : projector_(projector) {} - TopoDS_Shape operator()(boost::blank&) const { + void set_product_shape(const std::list>* product_shapes) { + product_shapes_ = product_shapes; + } + + result_type operator()(boost::blank&) const { throw std::runtime_error(""); } - TopoDS_Shape operator()(opencascade::handle& algo) { + result_type operator()(opencascade::handle& algo) { algo->Projector(projector_); algo->Update(); algo->Hide(); HLRBRep_HLRToShape hlr_shapes(algo); - return occt_join(hlr_shapes.OutLineVCompound(), hlr_shapes.VCompound()); + if (product_shapes_) { + std::list> r; + for (auto& p : *product_shapes_) { + r.push_back({ p.first, occt_join(hlr_shapes.OutLineVCompound(p.second), hlr_shapes.VCompound(p.second)) }); + } + return r; + } else { + return { {nullptr, occt_join(hlr_shapes.OutLineVCompound(), hlr_shapes.VCompound())}}; + } } - TopoDS_Shape operator()(opencascade::handle& algo) { + result_type operator()(opencascade::handle& algo) { algo->Projector(projector_); algo->Update(); HLRBRep_PolyHLRToShape hlr_shapes; hlr_shapes.Update(algo); - return occt_join(hlr_shapes.OutLineVCompound(), hlr_shapes.VCompound()); + if (product_shapes_) { + std::list> r; + for (auto& p : *product_shapes_) { + r.push_back({ p.first, occt_join(hlr_shapes.OutLineVCompound(p.second), hlr_shapes.VCompound(p.second)) }); + } + return r; + } else { + return { {nullptr, occt_join(hlr_shapes.OutLineVCompound(), hlr_shapes.VCompound()) } }; + } } }; @@ -240,13 +261,13 @@ namespace { gp_XYZ dxyz, xdir, ydir; public: - std::list::const_iterator item; + TopoDS_Shape* item; TopoDS_Face face; bool is_convex; // @note copying the BRepTopAdaptor_FClass2d didn't work so it's a pointer BRepTopAdaptor_FClass2d* fclass; - face_info(std::list::const_iterator it, const TopoDS_Face& fa) + face_info(TopoDS_Shape* it, const TopoDS_Face& fa) : item(it) , face(fa) , fclass(nullptr) @@ -334,17 +355,19 @@ namespace { hlr_brep_or_poly_t engine_; bool use_prefiltering_; bool use_hlr_poly_; + bool segment_projection_; gp_Ax1 view_direction_; HLRAlgo_Projector projector_; std::multimap large_ortho_faces_; - std::list items_; + std::list> items_; public: - prefiltered_hlr(bool use_prefiltering, bool use_hlr_poly, const gp_Pln& view_direction) + prefiltered_hlr(bool use_prefiltering, bool use_hlr_poly, bool segment_projection, const gp_Pln& view_direction) : use_prefiltering_(use_prefiltering) , use_hlr_poly_(use_hlr_poly) + , segment_projection_(segment_projection) // @nb negative z in accordance with occt projector convention (and opengl) , view_direction_(view_direction.Axis()) { @@ -359,7 +382,7 @@ namespace { projector_ = HLRAlgo_Projector(trsf, false, 1.); } - bool is_obscured_(std::list::const_iterator sit) { + bool is_obscured_(TopoDS_Shape* sit) { const TopoDS_Shape& s = *sit; double min_d = std::numeric_limits::infinity(); @@ -393,9 +416,9 @@ namespace { return false; } - void add(const TopoDS_Shape& s) { + void add(const TopoDS_Shape& s, const IfcUtil::IfcBaseEntity* product) { if (!use_prefiltering_) { - items_.insert(items_.end(), s); + items_.insert(items_.end(), {product, s}); return; } @@ -434,7 +457,7 @@ namespace { Logger::Notice("Included " + std::to_string(n_faces_included) + " faces out of " + std::to_string(n_total) + " after prefiltering"); - auto it = items_.insert(items_.end(), C); + auto it = items_.insert(items_.end(), { product, C }); { TopExp_Explorer exp(C, TopAbs_FACE); @@ -458,7 +481,7 @@ namespace { auto d = -(pnt.XYZ() - view_direction_.Location().XYZ()).Dot(view_direction_.Direction().XYZ()); if (d > 1.e-5) { - large_ortho_faces_.insert({ d, face_info(it, face) }); + large_ortho_faces_.insert({ d, face_info(&it->second, face) }); } } } @@ -467,15 +490,15 @@ namespace { } } } else { - items_.insert(items_.end(), s); + items_.insert(items_.end(), { product, s }); } } - TopoDS_Shape build() { + std::list> build() { size_t n_included = 0; for (auto it = items_.begin(); it != items_.end(); ++it) { - if (!use_prefiltering_ || !is_obscured_(it)) { - hlr_writer vis(*it); + if (!use_prefiltering_ || !is_obscured_(&it->second)) { + hlr_writer vis(it->second); boost::apply_visitor(vis, engine_); n_included++; } @@ -483,7 +506,11 @@ namespace { if (use_prefiltering_) { Logger::Notice("Included " + std::to_string(n_included) + " elements out of " + std::to_string(items_.size()) + " after prefiltering"); } + hlr_calc vis(projector_); + if (segment_projection_) { + vis.set_product_shape(&items_); + } return boost::apply_visitor(vis, engine_); } }; @@ -517,7 +544,7 @@ protected: storey_height_display_types storey_height_display_; bool draw_door_arcs_, is_floor_plan_; bool auto_section_, auto_elevation_; - bool use_namespace_, use_hlr_poly_, use_prefiltering_, always_project_, polygonal_; + bool use_namespace_, use_hlr_poly_, use_prefiltering_, segment_projection_, always_project_, polygonal_; bool emit_building_storeys_; bool no_css_; bool unify_inputs_; @@ -570,6 +597,7 @@ public: , use_namespace_(false) , use_hlr_poly_(false) , use_prefiltering_(false) + , segment_projection_(false) , always_project_(false) , polygonal_(false) , emit_building_storeys_(true) @@ -657,6 +685,14 @@ public: return use_prefiltering_; } + void setSegmentProjection(bool b) { + segment_projection_ = b; + } + + bool getSegmentProjection() const { + return segment_projection_; + } + void setPolygonal(bool b) { polygonal_ = b; }