diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt index 0b240ce0c8..a4e9c2a5b3 100644 --- a/cmake/CMakeLists.txt +++ b/cmake/CMakeLists.txt @@ -178,26 +178,31 @@ if((BUILD_CONVERT OR BUILD_GEOMSERVER OR BUILD_IFCPYTHON) AND(NOT BUILD_IFCGEOM) set(BUILD_IFCGEOM ON) endif() -find_program(CCACHE_FOUND ccache) -if(CCACHE_FOUND) - message(STATUS "`ccache` is found, using it as a compiler launcher.") - set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "${CCACHE_FOUND}") - if(MSVC) - # By default Visual Studio generators will use /Zi which is not compatible - # with ccache, so tell Visual Studio to use /Z7 instead. - set(CMAKE_MSVC_DEBUG_INFORMATION_FORMAT "$<$:Embedded>") - # Not needed for Ninja. - if(CMAKE_GENERATOR MATCHES "Visual Studio") - file(COPY_FILE - ${CCACHE_FOUND} ${CMAKE_BINARY_DIR}/cl.exe - ONLY_IF_DIFFERENT) - set(CMAKE_VS_GLOBALS - "CLToolExe=cl.exe" - "CLToolPath=${CMAKE_BINARY_DIR}" - "UseMultiToolTask=true" - ) +option(USE_CCACHE "Use ccache as a compiler launcher if it is found" OFF) +if(USE_CCACHE) + find_program(CCACHE_FOUND ccache) + if(CCACHE_FOUND) + message(STATUS "`ccache` is found, using it as a compiler launcher.") + set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "${CCACHE_FOUND}") + if(MSVC) + # By default Visual Studio generators will use /Zi which is not compatible + # with ccache, so tell Visual Studio to use /Z7 instead. + set(CMAKE_MSVC_DEBUG_INFORMATION_FORMAT "$<$:Embedded>") + # Not needed for Ninja. + if(CMAKE_GENERATOR MATCHES "Visual Studio") + file(COPY_FILE + ${CCACHE_FOUND} ${CMAKE_BINARY_DIR}/cl.exe + ONLY_IF_DIFFERENT) + set(CMAKE_VS_GLOBALS + "CLToolExe=cl.exe" + "CLToolPath=${CMAKE_BINARY_DIR}" + "UseMultiToolTask=true" + ) + endif() endif() endif() +else() + message(STATUS "ccache usage disabled via USE_CCACHE=OFF") endif() if(MSVC AND MSVC_PARALLEL_BUILD) @@ -311,12 +316,25 @@ if (WITH_ROCKSDB) set(SWIG_DEFINES ${SWIG_DEFINES} -DIFOPSH_WITH_ROCKSDB) # See https://github.com/facebook/rocksdb/issues/981. if(TARGET RocksDB::rocksdb) - target_link_libraries(IFCOPENSHELL_RocksDB INTERFACE RocksDB::rocksdb) + set(IFCOPENSHELL_ROCKSDB_IMPORTED_TARGET RocksDB::rocksdb) elseif(TARGET RocksDB::rocksdb-shared) - target_link_libraries(IFCOPENSHELL_RocksDB INTERFACE RocksDB::rocksdb-shared) + set(IFCOPENSHELL_ROCKSDB_IMPORTED_TARGET RocksDB::rocksdb-shared) else() message(FATAL_ERROR "RocksDB found but neither RocksDB::rocksdb nor RocksDB::rocksdb-shared target exists") endif() + # Our win/build-deps.cmd builds RocksDB separately per Debug/Release config into the + # same install prefix, so the imported target only ever has DEBUG and RELEASE listed in + # IMPORTED_CONFIGURATIONS. On a multi-config generator (Visual Studio), CMake maps any + # unmatched build config to the *first* entry of that list, which happens to be DEBUG + # (RocksDBTargets-debug.cmake sorts before RocksDBTargets-release.cmake). Without an + # explicit mapping, RelWithDebInfo and MinSizeRel builds would end up linking the + # /MDd-flavored rocksdb_d.lib into an /MD (NDEBUG) binary, causing a CRT/runtime-library + # mismatch that depends on nothing but that alphabetical ordering. + set_target_properties(${IFCOPENSHELL_ROCKSDB_IMPORTED_TARGET} PROPERTIES + MAP_IMPORTED_CONFIG_RELWITHDEBINFO RELEASE + MAP_IMPORTED_CONFIG_MINSIZEREL RELEASE + ) + target_link_libraries(IFCOPENSHELL_RocksDB INTERFACE ${IFCOPENSHELL_ROCKSDB_IMPORTED_TARGET}) if (WITH_ZSTD) # @todo do we actually need the zstd include dir or rather just pass diff --git a/src/ifcparse/alignment_helper.cpp b/src/ifcparse/alignment_helper.cpp index 7711d6e001..5922d75756 100644 --- a/src/ifcparse/alignment_helper.cpp +++ b/src/ifcparse/alignment_helper.cpp @@ -536,7 +536,7 @@ std::pair mapAlignme auto curve_segment = file.create(); curve_segment.setTransition(Ifc4x3_add2::IfcTransitionCode::IfcTransitionCode_CONTSAMEGRADIENT); curve_segment.setPlacement(file.addPlacement2d(start_point.Coordinates()[0], start_point.Coordinates()[1], cos(start_direction), sin(start_direction))); - curve_segment.setSegmentLength(create_length(file, 0.0)); + curve_segment.setSegmentStart(create_length(file, 0.0)); curve_segment.setSegmentLength(create_length(file, length)); curve_segment.setParentCurve(parent_curve); @@ -549,7 +549,7 @@ std::pair mapAlignme auto curve_segment = file.create(); curve_segment.setTransition(Ifc4x3_add2::IfcTransitionCode::IfcTransitionCode_CONTSAMEGRADIENT); curve_segment.setPlacement(file.addPlacement2d(start_point.Coordinates()[0], start_point.Coordinates()[1], cos(start_direction), sin(start_direction))); - curve_segment.setSegmentLength(create_length(file, 0.0)); + curve_segment.setSegmentStart(create_length(file, 0.0)); curve_segment.setSegmentLength(create_length(file, length * start_radius / std::fabs(start_radius))); curve_segment.setParentCurve(parent_curve); @@ -570,7 +570,7 @@ std::pair mapAlignme auto curve_segment = file.create(); curve_segment.setTransition(Ifc4x3_add2::IfcTransitionCode::IfcTransitionCode_CONTSAMEGRADIENT); curve_segment.setPlacement(file.addPlacement2d(start_point.Coordinates()[0], start_point.Coordinates()[1], cos(start_direction), sin(start_direction))); - curve_segment.setSegmentLength(create_length(file, offset)); + curve_segment.setSegmentStart(create_length(file, offset)); curve_segment.setSegmentLength(create_length(file, length)); curve_segment.setParentCurve(parent_curve); @@ -606,7 +606,7 @@ std::pair mapAlignme Ifc4x3_add2::IfcCurveSegment curve_segment = file.create(); curve_segment.setTransition(Ifc4x3_add2::IfcTransitionCode::IfcTransitionCode_CONTSAMEGRADIENT); curve_segment.setPlacement(file.addPlacement2d(start_point.Coordinates()[0], start_point.Coordinates()[1], cos(start_direction), sin(start_direction))); - curve_segment.setSegmentLength(create_length(file, 0.0)); + curve_segment.setSegmentStart(create_length(file, 0.0)); curve_segment.setSegmentLength(create_length(file, length)); curve_segment.setParentCurve(parent_curve); @@ -628,7 +628,7 @@ std::pair mapAlignme Ifc4x3_add2::IfcCurveSegment curve_segment = file.create(); curve_segment.setTransition(Ifc4x3_add2::IfcTransitionCode::IfcTransitionCode_CONTSAMEGRADIENT); curve_segment.setPlacement(file.addPlacement2d(start_point.Coordinates()[0], start_point.Coordinates()[1], cos(start_direction), sin(start_direction))); - curve_segment.setSegmentLength(create_length(file, 0.0)); + curve_segment.setSegmentStart(create_length(file, 0.0)); curve_segment.setSegmentLength(create_length(file, length)); curve_segment.setParentCurve(parent_curve); @@ -659,7 +659,7 @@ std::pair mapAlignme Ifc4x3_add2::IfcCurveSegment curve_segment = file.create(); curve_segment.setTransition(Ifc4x3_add2::IfcTransitionCode::IfcTransitionCode_CONTSAMEGRADIENT); curve_segment.setPlacement(file.addPlacement2d(start_point.Coordinates()[0], start_point.Coordinates()[1], cos(start_direction), sin(start_direction))); - curve_segment.setSegmentLength(create_length(file, offset)); + curve_segment.setSegmentStart(create_length(file, offset)); curve_segment.setSegmentLength(create_length(file, length)); curve_segment.setParentCurve(parent_curve); @@ -692,7 +692,7 @@ std::pair mapAlignme Ifc4x3_add2::IfcCurveSegment curve_segment1 = file.create(); curve_segment1.setTransition(Ifc4x3_add2::IfcTransitionCode::IfcTransitionCode_CONTSAMEGRADIENT); curve_segment1.setPlacement(file.addPlacement2d(start_point.Coordinates()[0], start_point.Coordinates()[1], cos(start_direction), sin(start_direction))); - curve_segment1.setSegmentLength(create_length(file, 0.0)); + curve_segment1.setSegmentStart(create_length(file, 0.0)); curve_segment1.setSegmentLength(create_length(file, length / 2)); curve_segment1.setParentCurve(parent_curve1); @@ -724,7 +724,7 @@ std::pair mapAlignme Ifc4x3_add2::IfcCurveSegment curve_segment2 = file.create(); curve_segment2.setTransition(Ifc4x3_add2::IfcTransitionCode::IfcTransitionCode_CONTSAMEGRADIENT); curve_segment2.setPlacement(file.addPlacement2d(start_point.Coordinates()[0], start_point.Coordinates()[1], cos(start_direction), sin(start_direction))); - curve_segment2.setSegmentLength(create_length(file, length / 2)); + curve_segment2.setSegmentStart(create_length(file, length / 2)); curve_segment2.setSegmentLength(create_length(file, length / 2)); curve_segment2.setParentCurve(parent_curve2); @@ -756,7 +756,7 @@ std::pair mapAlignme Ifc4x3_add2::IfcCurveSegment curve_segment = file.create(); curve_segment.setTransition(Ifc4x3_add2::IfcTransitionCode::IfcTransitionCode_CONTSAMEGRADIENT); curve_segment.setPlacement(file.addPlacement2d(start_point.Coordinates()[0], start_point.Coordinates()[1], cos(start_direction), sin(start_direction))); - curve_segment.setSegmentLength(create_length(file, 0.0)); + curve_segment.setSegmentStart(create_length(file, 0.0)); curve_segment.setSegmentLength(create_length(file, length)); curve_segment.setParentCurve(parent_curve); @@ -795,7 +795,7 @@ std::pair mapAlignme auto curve_segment = file.create(); curve_segment.setTransition(Ifc4x3_add2::IfcTransitionCode::IfcTransitionCode_CONTSAMEGRADIENT); curve_segment.setPlacement(file.addPlacement2d(start_distance_along, start_height, dx, dy)); - curve_segment.setSegmentLength(create_length(file, 0.0)); + curve_segment.setSegmentStart(create_length(file, 0.0)); curve_segment.setSegmentLength(create_length(file, segment_curve_length)); curve_segment.setParentCurve(parent_curve); @@ -822,7 +822,7 @@ std::pair mapAlignme auto curve_segment = file.create(); curve_segment.setTransition(Ifc4x3_add2::IfcTransitionCode::IfcTransitionCode_CONTSAMEGRADIENT); curve_segment.setPlacement(file.addPlacement2d(start_distance_along, start_height, dx, dy)); - curve_segment.setSegmentLength(create_length(file, 0.0)); + curve_segment.setSegmentStart(create_length(file, 0.0)); curve_segment.setSegmentLength(create_length(file, segment_curve_length)); curve_segment.setParentCurve(parent_curve); @@ -848,7 +848,7 @@ std::pair mapAlignme Ifc4x3_add2::IfcCurveSegment curve_segment = file.create(); curve_segment.setTransition(Ifc4x3_add2::IfcTransitionCode::IfcTransitionCode_CONTSAMEGRADIENT); curve_segment.setPlacement(file.addPlacement2d(start_distance_along, start_height, 1.0, 0.)); - curve_segment.setSegmentLength(create_length(file, 0.0)); + curve_segment.setSegmentStart(create_length(file, 0.0)); curve_segment.setSegmentLength(create_length(file, segment_curve_length)); curve_segment.setParentCurve(parent_curve); diff --git a/src/ifcparse/parse.cpp b/src/ifcparse/parse.cpp index a775118817..188bce0668 100644 --- a/src/ifcparse/parse.cpp +++ b/src/ifcparse/parse.cpp @@ -1651,27 +1651,42 @@ express::base::set_attribute_value(size_t i, const T& t) { apply_individual_instance_visitor(current_attribute, (int)i).apply(visitor); } - data()->set_attribute_value(i, t); - auto new_attribute = get_attribute_value(i); - - // Register inverse indices in file - if constexpr (std::is_same_v || std::is_same_v> || std::is_same_v>>) { - register_inverse_visitor visitor(*file(), *this); - apply_individual_instance_visitor(new_attribute, (int)i).apply(visitor); + // A null/empty single instance attribute (e.g. an omitted optional like + // OwnerHistory, ObjectPlacement or Representation) must not be persisted + // as a "set" attribute: doing so leaves isNull() false for it afterwards, + // so generated getters proceed to as() and dereference a null instance. + bool should_set = true; + if constexpr (std::is_same_v) { + should_set = static_cast(t); } - // Register new attribute guid in guid map - if (i == 0 && (file()->ifcroot_type() != nullptr) && this->declaration().is(*file()->ifcroot_type())) { - try { - auto guid = (std::string) new_attribute; - auto it = file()->internal_guid_map().find(guid); - if (it != file()->internal_guid_map().end()) { - file()->logger().warning("Duplicate guid " + guid); - } - file()->internal_guid_map().insert({guid, *this}); - } catch (ifcopenshell::exception& e) { - file()->logger().error(e); + if (should_set) { + data()->set_attribute_value(i, t); + auto new_attribute = get_attribute_value(i); + + // Register inverse indices in file + if constexpr (std::is_same_v || std::is_same_v> || std::is_same_v>>) { + register_inverse_visitor visitor(*file(), *this); + apply_individual_instance_visitor(new_attribute, (int)i).apply(visitor); } + + // Register new attribute guid in guid map + if (i == 0 && (file()->ifcroot_type() != nullptr) && this->declaration().is(*file()->ifcroot_type())) { + try { + auto guid = (std::string) new_attribute; + auto it = file()->internal_guid_map().find(guid); + if (it != file()->internal_guid_map().end()) { + file()->logger().warning("Duplicate guid " + guid); + } + file()->internal_guid_map().insert({guid, *this}); + } catch (ifcopenshell::exception& e) { + file()->logger().error(e); + } + } + } else if (!current_attribute.isNull()) { + // The attribute previously held a value, so record the clearing + // explicitly as blank instead of silently leaving the old value in place. + data()->set_attribute_value(i, blank{}); } } diff --git a/src/plugin/plugin.h b/src/plugin/plugin.h index 10e99211ec..edf91e879d 100644 --- a/src/plugin/plugin.h +++ b/src/plugin/plugin.h @@ -96,7 +96,7 @@ public: std::vector discover(const std::string& basename_prefix) const; std::vector discover_exact(const std::string& basename) const; - module load(const std::filesystem::path& path) const; + ifcopenshell::plugin::module load(const std::filesystem::path& path) const; private: std::vector search_paths_; diff --git a/win/build-deps.cmd b/win/build-deps.cmd index 9cf9b63da2..5d7e96580d 100644 --- a/win/build-deps.cmd +++ b/win/build-deps.cmd @@ -123,7 +123,7 @@ if "%CMAKE_VERSION%" LSS "cmake version 3.11.4" ( ) :: NOTE Boost < 1.64 doesn't work without tricks if the user has only VS 2017 installed and no earlier versions. -set BOOST_VERSION=1.86.0 +set BOOST_VERSION=1.92.0 :: Version string with underscores instead of dots. set BOOST_VER=%BOOST_VERSION:.=_% @@ -464,6 +464,10 @@ cd "%DEPENDENCY_DIR%" :: so disable it from the build altogether as we have no use for it findstr #add_subdirectory(COLLADAValidator) CMakeLists.txt>NUL IF NOT %ERRORLEVEL%==0 git apply --reject --whitespace=fix "%~dp0patches\OpenCOLLADA_CMakeLists.txt.patch" --ignore-whitespace +:: std::tr1::unordered_map was a legacy MSVC compatibility shim kept around through VS2022's STL, but newer +:: toolsets (e.g. VS2026/v145) no longer provide it, breaking the build with error C2039: 'tr1' is not a member of 'std'. +findstr /C:"typedef std::unordered_map MarkIdToFilePos;" common\libBuffer\include\CommonFWriteBufferFlusher.h>NUL +IF NOT %ERRORLEVEL%==0 git apply --reject --whitespace=fix "%~dp0patches\OpenCOLLADA_CommonFWriteBufferFlusher_tr1.patch" --ignore-whitespace :: NOTE OpenCOLLADA has been observed to have problems with switching between debug and release builds so :: uncomment to following line in order to delete the CMakeCache.txt always if experiencing problems. REM IF EXIST "%DEPENDENCY_DIR%\%BUILD_DIR%\CMakeCache.txt". del "%DEPENDENCY_DIR%\%BUILD_DIR%\CMakeCache.txt" @@ -747,7 +751,12 @@ set QT6_MSVC_YEAR=%VS_VER% IF /I "%VS_TOOLSET%"=="v141" set QT6_MSVC_YEAR=2017 IF /I "%VS_TOOLSET%"=="v142" set QT6_MSVC_YEAR=2019 IF /I "%VS_TOOLSET%"=="v143" set QT6_MSVC_YEAR=2022 -IF /I "%VS_TOOLSET%"=="v145" set QT6_MSVC_YEAR=2026 +:: Qt has not published prebuilt msvc2026 binaries yet (aqt only lists win64_msvc2022_64 as of +:: Qt 6.7-6.10). The v14x MSVC toolsets share a stable ABI/CRT, so fall back to the msvc2022 +:: binaries until Qt ships msvc2026 ones. Revisit once `aqt list-qt windows desktop --arch ` +:: shows a msvc2026 entry. +IF /I "%VS_TOOLSET%"=="v145" set QT6_MSVC_YEAR=2022 +IF "%VS_VER%"=="2026" set QT6_MSVC_YEAR=2022 set QT6_ARCH= set QT6_INSTALL_SUFFIX= diff --git a/win/patches/OpenCOLLADA_CommonFWriteBufferFlusher_tr1.patch b/win/patches/OpenCOLLADA_CommonFWriteBufferFlusher_tr1.patch new file mode 100644 index 0000000000..9e2a62b5b6 --- /dev/null +++ b/win/patches/OpenCOLLADA_CommonFWriteBufferFlusher_tr1.patch @@ -0,0 +1,13 @@ +diff --git a/common/libBuffer/include/CommonFWriteBufferFlusher.h b/common/libBuffer/include/CommonFWriteBufferFlusher.h +index c7af45b2..62b0f991 100644 +--- a/common/libBuffer/include/CommonFWriteBufferFlusher.h ++++ b/common/libBuffer/include/CommonFWriteBufferFlusher.h +@@ -58,7 +58,7 @@ namespace Common + #else + typedef __int64 FilePosType; + #endif +- typedef std::tr1::unordered_map MarkIdToFilePos; ++ typedef std::unordered_map MarkIdToFilePos; + + public: + static const size_t DEFAUL_BUFFER_SIZE = 64*1024; diff --git a/win/run-cmake.bat b/win/run-cmake.bat index 09244debb1..d21234115b 100755 --- a/win/run-cmake.bat +++ b/win/run-cmake.bat @@ -20,10 +20,14 @@ :: Example usage: :: run-cmake.bat vs2022-x64 :: run-cmake.bat vs2022-x64 -DGLTF_SUPPORT=ON +:: run-cmake.bat vs2022-x64 -DUSE_CCACHE=OFF :: :: Used environment variables: :: - `ADD_COMMIT_SHA` - if defined then `ADD_COMMIT_SHA` and `VERSION_OVERRIDE` cmake args will be set to `ON`. :: - `USE_NINJA` - if defined then the Ninja generator will be used instead of the Visual Studio. +:: +:: -DUSE_CCACHE=OFF (also accepts 0/FALSE/NO/N) disables ccache: the bundled ccache +:: install dir is kept out of CMAKE_PREFIX_PATH, and CMakeLists.txt skips detecting it. @if not defined ECHO_ON ( echo off ) @@ -81,6 +85,27 @@ if not (%1)==() ( call set ARGUMENTS=%%ARGUMENTS:%1=%% ) +:: Honor -DUSE_CCACHE=OFF (also accepts 0/FALSE/NO/N, case-insensitive) by keeping +:: the bundled ccache out of CMAKE_PREFIX_PATH so it can't be auto-detected below. +set USE_CCACHE_VALUE= +for %%A in (%*) do ( + set "ARG=%%~A" + if not "!ARG:-DUSE_CCACHE=!"=="!ARG!" ( + for /f "tokens=2 delims==" %%V in ("!ARG!") do set "USE_CCACHE_VALUE=%%V" + ) +) +if defined USE_CCACHE_VALUE ( + if /I "!USE_CCACHE_VALUE!"=="OFF" set DISABLE_CCACHE=1 + if /I "!USE_CCACHE_VALUE!"=="0" set DISABLE_CCACHE=1 + if /I "!USE_CCACHE_VALUE!"=="FALSE" set DISABLE_CCACHE=1 + if /I "!USE_CCACHE_VALUE!"=="NO" set DISABLE_CCACHE=1 + if /I "!USE_CCACHE_VALUE!"=="N" set DISABLE_CCACHE=1 +) +if defined DISABLE_CCACHE ( + echo USE_CCACHE=%USE_CCACHE_VALUE% passed, disabling ccache. + set "CCACHE_INSTALL_DIR=" +) + pushd .. set CMAKE_INSTALL_PREFIX=%CD%\_installed-%GEN_SHORTHAND% popd