diff --git a/.github/workflows/build_osx.yml b/.github/workflows/build_osx.yml index 6732a29761..3455d90c61 100644 --- a/.github/workflows/build_osx.yml +++ b/.github/workflows/build_osx.yml @@ -110,7 +110,21 @@ jobs: run: | VERSION=v`cat VERSION` cd ./build/`uname`/*/10.15/install/ifcopenshell - mkdir ~/output + mkdir -p ~/output + install_root="$PWD" + + stage_runtime_payload() { + dest="$1" + mkdir -p "$dest" + while IFS= read -r runtime_file; do + cp -L "$runtime_file" "$dest/" + done < <( + for runtime_dir in "$install_root/bin" "$install_root/lib"; do + [ -d "$runtime_dir" ] || continue + find "$runtime_dir" -type f \( -name "*.so" -o -name "*.so.*" -o -name "*.dylib" -o -name "*.dll" \) + done + ) + } ls -d python-* | while read py_version; do postfix=`echo ${py_version: -1} | sed s/[0-9]//` @@ -125,18 +139,26 @@ jobs: fi [ -d ifcopenshell/__pycache__ ] && rm -rf ifcopenshell/__pycache__ find ifcopenshell -name "*.pyc" -delete - zip -r -qq ifcopenshell-${py_version_major}-${VERSION}-${GITHUB_SHA:0:7}-macos${{ matrix.oldarch }}64.zip ifcopenshell/* + stage_runtime_payload ifcopenshell + zip -r -qq ifcopenshell-${py_version_major}-${VERSION}-${GITHUB_SHA:0:7}-macos${{ matrix.oldarch }}64.zip ifcopenshell mv *.zip ~/output popd > /dev/null done - cd bin - rm *.zip || true - ls | while read exe; do - zip -qq -r ${exe}-${VERSION}-${GITHUB_SHA:0:7}-macos${{ matrix.oldarch }}64.zip $exe + rm -f "$install_root"/bin/*.zip + find "$install_root/bin" -maxdepth 1 -type f -perm /111 ! -name "*.zip" ! -name "*.so" ! -name "*.so.*" ! -name "*.dylib" ! -name "*.dll" | while read exe_path; do + exe=`basename "$exe_path"` + package_dir="$install_root/.package-${exe}" + rm -rf "$package_dir" + mkdir -p "$package_dir" + cp "$exe_path" "$package_dir/" + stage_runtime_payload "$package_dir" + pushd "$package_dir" > /dev/null + zip -qq -r "$install_root/bin/${exe}-${VERSION}-${GITHUB_SHA:0:7}-macos${{ matrix.oldarch }}64.zip" . + popd > /dev/null + rm -rf "$package_dir" done - mv *.zip ~/output - cd .. + mv "$install_root"/bin/*.zip ~/output - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@v5 diff --git a/.github/workflows/build_rocky.yml b/.github/workflows/build_rocky.yml index 292e386a8b..4b85658cf5 100644 --- a/.github/workflows/build_rocky.yml +++ b/.github/workflows/build_rocky.yml @@ -89,7 +89,21 @@ jobs: run: | VERSION=v`cat VERSION` cd ./build/`uname`/*/install/ifcopenshell - mkdir ~/output + mkdir -p ~/output + install_root="$PWD" + + stage_runtime_payload() { + dest="$1" + mkdir -p "$dest" + while IFS= read -r runtime_file; do + cp -L "$runtime_file" "$dest/" + done < <( + for runtime_dir in "$install_root/bin" "$install_root/lib"; do + [ -d "$runtime_dir" ] || continue + find "$runtime_dir" -type f \( -name "*.so" -o -name "*.so.*" -o -name "*.dylib" -o -name "*.dll" \) + done + ) + } ls -d python-* | while read py_version; do postfix=`echo ${py_version: -1} | sed s/[0-9]//` @@ -104,18 +118,26 @@ jobs: fi [ -d ifcopenshell/__pycache__ ] && rm -rf ifcopenshell/__pycache__ find ifcopenshell -name "*.pyc" -delete - zip -r -qq ifcopenshell-${py_version_major}-${VERSION}-${GITHUB_SHA:0:7}-linux64.zip ifcopenshell/* + stage_runtime_payload ifcopenshell + zip -r -qq ifcopenshell-${py_version_major}-${VERSION}-${GITHUB_SHA:0:7}-linux64.zip ifcopenshell mv *.zip ~/output popd > /dev/null done - cd bin - rm *.zip || true - ls | while read exe; do - zip -qq -r ${exe}-${VERSION}-${GITHUB_SHA:0:7}-linux64.zip $exe + rm -f "$install_root"/bin/*.zip + find "$install_root/bin" -maxdepth 1 -type f -perm /111 ! -name "*.zip" ! -name "*.so" ! -name "*.so.*" ! -name "*.dylib" ! -name "*.dll" | while read exe_path; do + exe=`basename "$exe_path"` + package_dir="$install_root/.package-${exe}" + rm -rf "$package_dir" + mkdir -p "$package_dir" + cp "$exe_path" "$package_dir/" + stage_runtime_payload "$package_dir" + pushd "$package_dir" > /dev/null + zip -qq -r "$install_root/bin/${exe}-${VERSION}-${GITHUB_SHA:0:7}-linux64.zip" . + popd > /dev/null + rm -rf "$package_dir" done - mv *.zip ~/output - cd .. + mv "$install_root"/bin/*.zip ~/output - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@v5 diff --git a/.github/workflows/build_rocky_arm.yml b/.github/workflows/build_rocky_arm.yml index a149af42c8..b7a434662f 100644 --- a/.github/workflows/build_rocky_arm.yml +++ b/.github/workflows/build_rocky_arm.yml @@ -89,7 +89,21 @@ jobs: run: | VERSION=v`cat VERSION` cd ./build/`uname`/*/install/ifcopenshell - mkdir ~/output + mkdir -p ~/output + install_root="$PWD" + + stage_runtime_payload() { + dest="$1" + mkdir -p "$dest" + while IFS= read -r runtime_file; do + cp -L "$runtime_file" "$dest/" + done < <( + for runtime_dir in "$install_root/bin" "$install_root/lib"; do + [ -d "$runtime_dir" ] || continue + find "$runtime_dir" -type f \( -name "*.so" -o -name "*.so.*" -o -name "*.dylib" -o -name "*.dll" \) + done + ) + } ls -d python-* | while read py_version; do postfix=`echo ${py_version: -1} | sed s/[0-9]//` @@ -104,18 +118,26 @@ jobs: fi [ -d ifcopenshell/__pycache__ ] && rm -rf ifcopenshell/__pycache__ find ifcopenshell -name "*.pyc" -delete - zip -r -qq ifcopenshell-${py_version_major}-${VERSION}-${GITHUB_SHA:0:7}-linuxarm64.zip ifcopenshell/* + stage_runtime_payload ifcopenshell + zip -r -qq ifcopenshell-${py_version_major}-${VERSION}-${GITHUB_SHA:0:7}-linuxarm64.zip ifcopenshell mv *.zip ~/output popd > /dev/null done - cd bin - rm *.zip || true - ls | while read exe; do - zip -qq -r ${exe}-${VERSION}-${GITHUB_SHA:0:7}-linuxarm64.zip $exe + rm -f "$install_root"/bin/*.zip + find "$install_root/bin" -maxdepth 1 -type f -perm /111 ! -name "*.zip" ! -name "*.so" ! -name "*.so.*" ! -name "*.dylib" ! -name "*.dll" | while read exe_path; do + exe=`basename "$exe_path"` + package_dir="$install_root/.package-${exe}" + rm -rf "$package_dir" + mkdir -p "$package_dir" + cp "$exe_path" "$package_dir/" + stage_runtime_payload "$package_dir" + pushd "$package_dir" > /dev/null + zip -qq -r "$install_root/bin/${exe}-${VERSION}-${GITHUB_SHA:0:7}-linuxarm64.zip" . + popd > /dev/null + rm -rf "$package_dir" done - mv *.zip ~/output - cd .. + mv "$install_root"/bin/*.zip ~/output - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@v5 diff --git a/.github/workflows/build_win.yml b/.github/workflows/build_win.yml index 508f4278bd..c16a006ae8 100644 --- a/.github/workflows/build_win.yml +++ b/.github/workflows/build_win.yml @@ -52,6 +52,64 @@ jobs: cd win python build-all-win.py + - name: Repack .zip Archives With Runtime Plugins + shell: powershell + run: | + $version = (Get-Content VERSION | Select-Object -First 1).Trim() + $sha = $env:GITHUB_SHA.Substring(0, 7) + $outputDir = Join-Path $env:USERPROFILE "output" + $installDir = Get-ChildItem -Path . -Directory | Where-Object { $_.Name -like "_installed*" } | Select-Object -First 1 + if (-not $installDir) { + throw "Install directory not found." + } + + $runtimeFiles = @() + foreach ($runtimeDir in @((Join-Path $installDir.FullName "bin"), (Join-Path $installDir.FullName "lib"))) { + if (Test-Path $runtimeDir) { + $runtimeFiles += Get-ChildItem -Path $runtimeDir -Recurse -File | Where-Object { $_.Extension -ieq ".dll" } + } + } + $runtimeFiles = $runtimeFiles | Sort-Object FullName -Unique + + Get-ChildItem -Path $outputDir -Filter *.zip -ErrorAction SilentlyContinue | Remove-Item -Force + + $binDir = Join-Path $installDir.FullName "bin" + foreach ($exe in Get-ChildItem -Path $binDir -File -Filter *.exe) { + $stageDir = Join-Path $env:RUNNER_TEMP ("package-" + $exe.BaseName) + Remove-Item -Path $stageDir -Recurse -Force -ErrorAction SilentlyContinue + New-Item -ItemType Directory -Path $stageDir | Out-Null + Copy-Item -Path $exe.FullName -Destination $stageDir + foreach ($runtimeFile in $runtimeFiles) { + Copy-Item -Path $runtimeFile.FullName -Destination $stageDir -Force + } + $zipPath = Join-Path $outputDir ("{0}-v{1}-{2}-win64.zip" -f $exe.BaseName, $version, $sha) + Compress-Archive -Path (Join-Path $stageDir '*') -DestinationPath $zipPath -Force + } + + $depsRoot = Join-Path (Get-Location) "_deps" + if (Test-Path $depsRoot) { + foreach ($pythonRoot in Get-ChildItem -Path $depsRoot -Directory | Where-Object { $_.Name -like "python.*" }) { + $pythonVersion = $pythonRoot.Name.Substring("python.".Length) + $pythonVersionMajorMinor = ($pythonVersion.Split(".")[0..1] -join "") + $sitePackages = Join-Path $pythonRoot.FullName "tools\\Lib\\site-packages" + $packagePath = Join-Path $sitePackages "ifcopenshell" + if (!(Test-Path $packagePath)) { + continue + } + Get-ChildItem -Path $packagePath -Recurse -Filter *.pyc -ErrorAction SilentlyContinue | Remove-Item -Force + foreach ($runtimeFile in $runtimeFiles) { + Copy-Item -Path $runtimeFile.FullName -Destination $packagePath -Force + } + $zipPath = Join-Path $outputDir ("ifcopenshell-python-{0}-v{1}-{2}-win64.zip" -f $pythonVersionMajorMinor, $version, $sha) + Push-Location $sitePackages + try { + Compress-Archive -Path "ifcopenshell" -DestinationPath $zipPath -Force + } finally { + Pop-Location + } + } + } + - name: Pack Dependencies run: | cd _deps-vs2022-x64-installed diff --git a/src/ifcconvert/CMakeLists.txt b/src/ifcconvert/CMakeLists.txt index 262ff1b3bf..603f1538c2 100644 --- a/src/ifcconvert/CMakeLists.txt +++ b/src/ifcconvert/CMakeLists.txt @@ -1,9 +1,29 @@ # IfcConvert add_executable(IfcConvert IfcConvert.cpp) -target_link_libraries(IfcConvert IfcGeom IfcParse Serializers ${OpenCASCADE_LIBRARIES} ${Boost_LIBRARIES} ${HDF5_LIBRARIES} ${USD_LIBRARIES}) -if(kernel_libraries OR mapping_libraries OR document_serializer_libraries) - add_dependencies(IfcConvert ${kernel_libraries} ${mapping_libraries} ${document_serializer_libraries}) +target_link_libraries(IfcConvert PRIVATE IfcGeom IfcParse ${OpenCASCADE_LIBRARIES} ${Boost_LIBRARIES} ${HDF5_LIBRARIES} ${USD_LIBRARIES}) +if(TARGET geometry_serializer_hdf) + target_link_libraries(IfcConvert PRIVATE geometry_serializer_hdf) +endif() +if(kernel_libraries OR mapping_libraries OR geometry_serializer_libraries OR document_serializer_libraries) + add_dependencies(IfcConvert ${kernel_libraries} ${mapping_libraries} ${geometry_serializer_libraries} ${document_serializer_libraries}) endif() +set(_plugin_targets + ${kernel_libraries} + ${mapping_libraries} + ${geometry_serializer_libraries} + ${document_serializer_libraries} +) + +set(_plugin_paths "") +foreach(t IN LISTS _plugin_targets) + string(APPEND _plugin_paths ";$") +endforeach() + +set_target_properties(IfcConvert PROPERTIES + VS_DEBUGGER_ENVIRONMENT + "PATH=%PATH%;$,;>;${_plugin_paths}" +) + install(TARGETS IfcConvert) diff --git a/src/ifcconvert/IfcConvert.cpp b/src/ifcconvert/IfcConvert.cpp index 7873bddb7b..6c8764aa71 100644 --- a/src/ifcconvert/IfcConvert.cpp +++ b/src/ifcconvert/IfcConvert.cpp @@ -30,18 +30,8 @@ // error C2589: '(': illegal token on right side of '::' #define NOMINMAX -#include "../serializers/ColladaSerializer.h" -#include "../serializers/GltfSerializer.h" -#include "../serializers/HdfSerializer.h" -#include "../serializers/IgesSerializer.h" -#include "../serializers/StepSerializer.h" -#include "../serializers/WavefrontObjSerializer.h" -#include "../serializers/XmlSerializer.h" -#include "../serializers/SvgSerializer.h" -#include "../serializers/USDSerializer.h" -#include "../serializers/TtlWktSerializer.h" -#include "../serializers/RocksDbSerializer.h" -#include "../serializers/JsonSerializer.h" +#include "../serializers/document_serializer_plugin.h" +#include "../serializers/geometry_serializer_plugin.h" #include "../ifcgeom/IfcGeomFilter.h" #include "../ifcgeom/Iterator.h" @@ -54,10 +44,6 @@ #include -#if OCC_VERSION_HEX < 0x60900 -#include -#endif - #endif #include @@ -352,14 +338,6 @@ int main(int argc, char** argv) { std::string unicode_mode; #endif short precision; - double section_height; - std::string svg_scale, svg_center; - std::string section_ref, elevation_ref, elevation_ref_guid; - // "none", "full" or "left" - std::string storey_height_display; -#ifdef IFOPSH_WITH_OPENCASCADE - SvgSerializer::storey_height_display_types svg_storey_height_display = SvgSerializer::SH_NONE; -#endif ifcopenshell::geometry::SerializerSettings serializer_settings; @@ -370,52 +348,6 @@ int main(int argc, char** argv) { "Specifies the Unicode handling behavior when parsing the IFC file. " "Accepted values 'utf8' (the default) and 'escape'.") #endif - ("bounds", po::value(&bounds), - "Specifies the bounding rectangle, for example 512x512, to which the " - "output will be scaled. Only used when converting to SVG.") - ("scale", po::value(&svg_scale), - "Interprets SVG bounds in mm, centers layout and draw elements to scale. " - "Only used when converting to SVG. Example 1:100.") - ("center", po::value(&svg_center), - "When using --scale, specifies the location in the range [0 1]x[0 1] around which" - "to center the drawings. Example 0.5x0.5 (default).") - ("section-ref", po::value(§ion_ref), - "Element at which cross sections should be created") - ("elevation-ref", po::value(&elevation_ref), - "Element at which drawings should be created") - ("elevation-ref-guid", po::value(&elevation_ref_guid), - "Element guids at which drawings should be created") - ("auto-section", - "Creates SVG cross section drawings automatically based on model extents") - ("auto-elevation", - "Creates SVG elevation drawings automatically based on model extents") - ("draw-storey-heights", - po::value(&storey_height_display)->default_value("none")->implicit_value("full"), - "Draws a horizontal line at the height of building storeys in vertical drawings") - ("storey-height-line-length", po::value(), - "Length of the line when --draw-storey-heights=left") - ("svg-xmlns", - "Stores name and guid in a separate namespace as opposed to data-name, data-guid") - ("svg-poly", - "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", - "Always enable hidden line rendering instead of only on elevations") - ("svg-without-storeys", "Don't emit drawings for building storeys") - ("svg-no-css", "Don't emit CSS style declarations") - ("door-arcs", "Draw door openings arcs for IfcDoor elements") - ("section-height", po::value(§ion_height), - "Specifies the cut section height for SVG 2D geometry.") - ("section-height-from-storeys", "Derives section height from storey elevation. Use --section-height to override default offset of 1.2") - ("print-space-names", "Prints IfcSpace LongName and Name in the geometry output. Applicable for SVG output") - ("print-space-areas", "Prints calculated IfcSpace areas in square meters. Applicable for SVG output") - ("space-name-transform", po::value(), - "Additional transform to the space labels in SVG") ; serializer_settings.define_options(serializer_options); @@ -476,24 +408,6 @@ int main(int argc, char** argv) { return EXIT_FAILURE; } -#ifdef IFOPSH_WITH_OPENCASCADE - if (vmap.count("draw-storey-heights")) { - boost::to_lower(storey_height_display); - - if (storey_height_display == "none") { - svg_storey_height_display = SvgSerializer::SH_NONE; - } else if (storey_height_display == "full") { - svg_storey_height_display = SvgSerializer::SH_FULL; - } else if (storey_height_display == "left") { - svg_storey_height_display = SvgSerializer::SH_LEFT; - } else { - cerr_ << "[error] --draw-storey-heights should be none|full|left" << std::endl; - print_usage(); - return EXIT_FAILURE; - } - } -#endif - if (num_threads <= 0) { num_threads = std::thread::hardware_concurrency(); logger::notice("Using " + std::to_string(num_threads) + " threads"); @@ -546,32 +460,6 @@ int main(int argc, char** argv) { } } - boost::optional bounding_width, bounding_height, relative_center_x, relative_center_y; - - if (vmap.count("bounds") == 1) { - int w, h; - if (sscanf(bounds.c_str(), "%ux%u", &w, &h) == 2 && w > 0 && h > 0) { - bounding_width = w; - bounding_height = h; - } else { - cerr_ << "[error] Invalid use of --bounds" << std::endl; - print_options(serializer_options); - return EXIT_FAILURE; - } - } - - if (vmap.count("center") == 1) { - double cx, cy; - if (sscanf(svg_center.c_str(), "%lfx%lf", &cx, &cy) == 2 && cx >= 0. && cy >= 0. && cx <= 1. && cy <= 1.) { - relative_center_x = cx; - relative_center_y = cy; - } else { - cerr_ << "[error] Invalid use of --bounds" << std::endl; - print_options(serializer_options); - return EXIT_FAILURE; - } - } - const path_t input_filename = vmap["input-file"].as(); /* // todo also allow rocksdb dir @@ -640,53 +528,58 @@ int main(int argc, char** argv) { path_t output_extension = dot + ext; boost::to_lower(output_extension); + const auto output_extension_utf8 = ifcopenshell::path::to_utf8(output_extension); ifcopenshell::file* ifc_file = 0; boost::optional> elems_from_adaptor; - const path_t OBJ = ifcopenshell::path::from_utf8(".obj"), - MTL = ifcopenshell::path::from_utf8(".mtl"), - DAE = ifcopenshell::path::from_utf8(".dae"), - GLB = ifcopenshell::path::from_utf8(".glb"), - STP = ifcopenshell::path::from_utf8(".stp"), - IGS = ifcopenshell::path::from_utf8(".igs"), - SVG = ifcopenshell::path::from_utf8(".svg"), - CACHE = ifcopenshell::path::from_utf8(".cache"), + const path_t CACHE = ifcopenshell::path::from_utf8(".cache"), HDF = ifcopenshell::path::from_utf8(".h5"), - XML = ifcopenshell::path::from_utf8(".xml"), - JSON = ifcopenshell::path::from_utf8(".json"), - // @todo this is just temporary as it doesn't make sense to require an extension for a DB - RDB = ifcopenshell::path::from_utf8(".rdb"), - IFC = ifcopenshell::path::from_utf8(".ifc"), - USD = ifcopenshell::path::from_utf8(".usd"), - USDA = ifcopenshell::path::from_utf8(".usda"), - USDC = ifcopenshell::path::from_utf8(".usdc"), - TTL = ifcopenshell::path::from_utf8(".ttl"); + IFC = ifcopenshell::path::from_utf8(".ifc"); - // @todo clean up serializer selection - // @todo detect program options that conflict with the chosen serializer - if (output_extension == XML || output_extension == JSON) { + auto& document_serializer_registry = ifcopenshell::serializers::document_serializer_registry_instance(); + const auto* document_serializer_info = document_serializer_registry.find(output_extension_utf8); + if (document_serializer_info) { int exit_code = EXIT_FAILURE; try { - if (init_input_file(ifcopenshell::path::to_utf8(input_filename), ifc_file, no_progress || quiet, mmap)) { + const bool use_input_filename = vmap.count("stream") && document_serializer_info->supports_input_filename; + if (!use_input_filename && !document_serializer_info->supports_ifc_file) { + throw ifcopenshell::exception("Selected document serializer requires --stream"); + } + + if (use_input_filename || init_input_file(ifcopenshell::path::to_utf8(input_filename), ifc_file, no_progress || quiet, mmap)) { + if (!use_input_filename) { + document_serializer_info = document_serializer_registry.find(output_extension_utf8, ifc_file->schema()->name()); + if (!document_serializer_info) { + throw ifcopenshell::exception("No document serializer registered for " + output_extension_utf8 + " and schema " + ifc_file->schema()->name()); + } + } + time_t start, end; time(&start); - if (output_extension == XML) { - XmlSerializer s(ifc_file, ifcopenshell::path::to_utf8(output_temp_filename)); - logger::status("Writing XML output..."); - s.finalize(); - } else { -#ifdef WITH_GLTF - JsonSerializer s(ifc_file, ifcopenshell::path::to_utf8(output_temp_filename), JsonSerializer::JSON_DIALECT_CREOOX); - logger::status("Writing JSON output..."); - s.finalize(); -#endif - } + + ifcopenshell::serializers::document_serializer_context context; + context.file = ifc_file; + context.input_filename = ifcopenshell::path::to_utf8(input_filename); + context.output_filename = ifcopenshell::path::to_utf8(document_serializer_info->writes_final_output ? output_filename : output_temp_filename); + context.schema_name = ifc_file ? ifc_file->schema()->name() : document_serializer_info->schema_name; + context.stream = use_input_filename; + + boost::shared_ptr serializer = document_serializer_registry.create(output_extension_utf8, context); + logger::status("Writing " + boost::to_upper_copy(document_serializer_info->format) + " output..."); + serializer->finalize(); + serializer.reset(); + time(&end); logger::status("Done! Conversion took " + format_duration(start, end)); - ifcopenshell::path::rename_file(ifcopenshell::path::to_utf8(output_temp_filename), ifcopenshell::path::to_utf8(output_filename)); + if (!document_serializer_info->writes_final_output && + !ifcopenshell::path::rename_file(ifcopenshell::path::to_utf8(output_temp_filename), ifcopenshell::path::to_utf8(output_filename))) { + throw ifcopenshell::exception( + "Unable to write output file '" + ifcopenshell::path::to_utf8(output_filename) + + "', see '" + ifcopenshell::path::to_utf8(output_temp_filename) + "' for the conversion result"); + } exit_code = EXIT_SUCCESS; } } catch (const std::exception& e) { @@ -719,38 +612,15 @@ int main(int argc, char** argv) { write_log(!quiet); return exit_code; } -#ifdef WITH_ROCKSDB - else if (output_extension == RDB) { - int exit_code = EXIT_FAILURE; - try { - if (vmap.count("stream")) { - time_t start, end; - time(&start); - RocksDbSerializer s(ifcopenshell::path::to_utf8(input_filename), ifcopenshell::path::to_utf8(output_filename), true); - logger::status("Populating RocksDB Key-Value store..."); - s.finalize(); - time(&end); - logger::status("Done! Conversion took " + format_duration(start, end)); - exit_code = EXIT_SUCCESS; - } else { - if (init_input_file(ifcopenshell::path::to_utf8(input_filename), ifc_file, no_progress || quiet, mmap)) { - time_t start, end; - time(&start); - RocksDbSerializer s(ifc_file, ifcopenshell::path::to_utf8(output_filename)); - logger::status("Populating RocksDB Key-Value store..."); - s.finalize(); - time(&end); - logger::status("Done! Conversion took " + format_duration(start, end)); - exit_code = EXIT_SUCCESS; - } - } - } catch (const std::exception& e) { - logger::error(e); - } + + auto& geometry_serializer_registry = ifcopenshell::serializers::geometry_serializer_registry_instance(); + const auto* geometry_serializer_info = geometry_serializer_registry.find(output_extension_utf8); + if (!geometry_serializer_info) { + cerr_ << "[error] Unknown output filename extension '" << output_extension << "'\n"; write_log(!quiet); - return exit_code; + print_usage(); + return EXIT_FAILURE; } -#endif /// @todo Clean up this filter code further. std::vector used_filters; @@ -769,11 +639,7 @@ int main(int argc, char** argv) { if (!layer_filter.values.empty()) { layer_filter.update_description(); logger::notice(layer_filter.description); } if (!attribute_filter.attribute_name.empty()) { attribute_filter.update_description(); logger::notice(attribute_filter.description); } -#ifdef _MSC_VER - if (output_extension == DAE || output_extension == STP || output_extension == IGS) { -#else - if (output_extension == DAE) { -#endif + if (geometry_serializer_info && geometry_serializer_info->requires_ascii_temp_file) { // These serializers do not support opening unicode paths. Therefore // a random temp file is generated using only ASCII characters instead. std::random_device rng; @@ -798,15 +664,6 @@ int main(int argc, char** argv) { geometry_settings.get().value = true; } - if (geometry_settings.get().get() && output_extension != DAE && output_extension != USD && output_extension != USDA && output_extension != USDC && output_extension != GLB) { - cerr_ << "[error] --use-element-hierarchy can be used only with .dae or .usd or .glb output.\n"; - /// @todo Lots of duplicate error-and-exit code. - write_log(!quiet); - print_usage(); - ifcopenshell::path::delete_file(ifcopenshell::path::to_utf8(output_temp_filename)); - return EXIT_FAILURE; - } - if (vmap[ifcopenshell::geometry::settings::WeldVertices::name].defaulted()) { geometry_settings.get().value = false; } @@ -815,60 +672,31 @@ int main(int argc, char** argv) { IfcGeom::update_default_style("IfcSpace")->transparency = geometry_settings.get().get(); } - if (output_extension == OBJ || output_extension == STP || output_extension == IGS) { - geometry_settings.get().value = true; - } - - if (output_extension == TTL) { - geometry_settings.get().value = ifcopenshell::geometry::settings::TriangulationMethod::POLYHEDRON_WITH_HOLES; - } - - if (output_extension == SVG) { - // SVG serialiazation depends on element hierarchy now to look up the parent - geometry_settings.get().value = true; - } - - boost::shared_ptr serializer; /**< @todo use std::unique_ptr when possible */ - if (output_extension == OBJ) { - // Do not use temp file for MTL as it's such a small file. - const path_t mtl_filename = change_extension(output_filename, MTL); - serializer = boost::make_shared(ifcopenshell::path::to_utf8(output_temp_filename), ifcopenshell::path::to_utf8(mtl_filename), geometry_settings, serializer_settings); -#ifdef WITH_OPENCOLLADA - } else if (output_extension == DAE) { - serializer = boost::make_shared(ifcopenshell::path::to_utf8(output_temp_filename), geometry_settings, serializer_settings); -#endif -#ifdef WITH_GLTF - } else if (output_extension == GLB) { - serializer = boost::make_shared(ifcopenshell::path::to_utf8(output_temp_filename), geometry_settings, serializer_settings); -#endif -#ifdef WITH_USD - } else if (output_extension == USD || output_extension == USDA || output_extension == USDC) { - serializer = boost::make_shared(ifcopenshell::path::to_utf8(output_filename), geometry_settings, serializer_settings); -#endif -#ifdef IFOPSH_WITH_OPENCASCADE - } else if (output_extension == STP) { - serializer = boost::make_shared(ifcopenshell::path::to_utf8(output_temp_filename), geometry_settings, serializer_settings); - } else if (output_extension == IGS) { -#if OCC_VERSION_HEX < 0x60900 - // According to https://tracker.dev.opencascade.org/view.php?id=25689 something has been fixed in 6.9.0 - IGESControl_Controller::Init(); // work around Open Cascade bug -#endif - serializer = boost::make_shared(ifcopenshell::path::to_utf8(output_temp_filename), geometry_settings, serializer_settings); - } else if (output_extension == SVG) { - geometry_settings.get().value = ifcopenshell::geometry::settings::NATIVE; - serializer = boost::make_shared(ifcopenshell::path::to_utf8(output_temp_filename), geometry_settings, serializer_settings); -#ifdef WITH_HDF5 - } else if (output_extension == HDF) { - geometry_settings.get().value = ifcopenshell::geometry::settings::NATIVE; - serializer = boost::make_shared(ifcopenshell::path::to_utf8(output_temp_filename), geometry_settings, serializer_settings); -#endif -#endif - } else if (output_extension == TTL) { - serializer = boost::make_shared(ifcopenshell::path::to_utf8(output_temp_filename), geometry_settings, serializer_settings); - } else { - cerr_ << "[error] Unknown output filename extension '" << output_extension << "'\n"; + if (geometry_settings.get().get() && + !geometry_serializer_info->supports_user_element_hierarchy) { + cerr_ << "[error] --use-element-hierarchy is not supported by the selected geometry serializer.\n"; write_log(!quiet); print_usage(); + ifcopenshell::path::delete_file(ifcopenshell::path::to_utf8(output_temp_filename)); + return EXIT_FAILURE; + } + + ifcopenshell::serializers::geometry_serializer_context serializer_context{ + ifcopenshell::path::to_utf8(output_filename), + ifcopenshell::path::to_utf8(output_temp_filename), + geometry_settings, + serializer_settings + }; + + boost::shared_ptr serializer; /**< @todo use std::unique_ptr when possible */ + try { + geometry_serializer_registry.configure(output_extension_utf8, serializer_context); + serializer = geometry_serializer_registry.create(output_extension_utf8, serializer_context); + } catch (const std::exception& e) { + cerr_ << "[error] " << e.what() << std::endl; + write_log(!quiet); + print_options(serializer_options); + ifcopenshell::path::delete_file(ifcopenshell::path::to_utf8(output_temp_filename)); return EXIT_FAILURE; } @@ -897,9 +725,9 @@ int main(int argc, char** argv) { time(&start); // @nb last argument true -> bypass_properties which are not read by any of the geometry serializers - // XML, RocksDB, IFC are already special-cased above + // Document serializers and IFC are already special-cased above // SVG requires properties for IfcAnnotation/DRAWING properties - if (!init_input_file(ifcopenshell::path::to_utf8(input_filename), ifc_file, no_progress || quiet, mmap, output_extension != SVG)) { + if (!init_input_file(ifcopenshell::path::to_utf8(input_filename), ifc_file, no_progress || quiet, mmap, geometry_serializer_info->bypass_properties)) { write_log(!quiet); serializer.reset(); ifcopenshell::path::delete_file(ifcopenshell::path::to_utf8(output_temp_filename)); /**< @todo Windows Unicode support */ @@ -1003,12 +831,37 @@ int main(int argc, char** argv) { } #if defined(WITH_HDF5) && defined(IFOPSH_WITH_OPENCASCADE) - std::unique_ptr cache; - if (context_iterator && vmap.count("cache-file") || vmap.count("cache")) { + boost::shared_ptr cache; + const bool use_cache = context_iterator && (vmap.count("cache-file") || vmap.count("cache")); + if (use_cache) { if (!vmap.count("cache-file")) { cache_file = input_filename + CACHE + HDF; } - cache.reset(new HdfSerializer(ifcopenshell::path::to_utf8(cache_file), geometry_settings, serializer_settings)); + + try { + ifcopenshell::serializers::geometry_serializer_context cache_context{ + ifcopenshell::path::to_utf8(cache_file), + ifcopenshell::path::to_utf8(cache_file), + geometry_settings, + serializer_settings + }; + cache = geometry_serializer_registry.create(".h5", cache_context); + } catch (const std::exception& e) { + cerr_ << "[error] Unable to initialize geometry cache: " << e.what() << std::endl; + serializer.reset(); + ifcopenshell::path::delete_file(ifcopenshell::path::to_utf8(output_temp_filename)); + write_log(!quiet); + return EXIT_FAILURE; + } + + if (!cache || !cache->ready()) { + cerr_ << "[error] Unable to initialize geometry cache serializer\n"; + serializer.reset(); + ifcopenshell::path::delete_file(ifcopenshell::path::to_utf8(output_temp_filename)); + write_log(!quiet); + return EXIT_FAILURE; + } + context_iterator->set_cache(cache.get()); } #endif @@ -1027,83 +880,6 @@ int main(int argc, char** argv) { serializer->setFile(ifc_file); -#ifdef IFOPSH_WITH_OPENCASCADE - if (output_extension == SVG) { - // @todo turn these all into proper settings - if (vmap.count("section-height-from-storeys") != 0) { - if (vmap.count("section-height")) { - static_cast(serializer.get())->setSectionHeightsFromStoreys(section_height); - } else { - static_cast(serializer.get())->setSectionHeightsFromStoreys(); - } - } else if (vmap.count("section-height") != 0) { - logger::notice("Overriding section height"); - static_cast(serializer.get())->setSectionHeight(section_height); - } - if (vmap.count("print-space-names") != 0) { - static_cast(serializer.get())->setPrintSpaceNames(true); - } - if (vmap.count("print-space-areas") != 0) { - static_cast(serializer.get())->setPrintSpaceAreas(true); - } - if (vmap.count("draw-storey-heights") != 0) { - static_cast(serializer.get())->setDrawStoreyHeights(svg_storey_height_display); - } - if (bounding_width.is_initialized() && bounding_height.is_initialized()) { - static_cast(serializer.get())->setBoundingRectangle(bounding_width.get(), bounding_height.get()); - } - if (vmap.count("door-arcs")) { - static_cast(serializer.get())->setDrawDoorArcs(true); - } - if (vmap.count("scale")) { - int s0, s1; - if (sscanf(svg_scale.c_str(), "%u:%u", &s0, &s1) == 2 && s0 > 0 && s1 > 0) { - static_cast(serializer.get())->setScale((double)s0 / s1); - } else { - cerr_ << "[error] Invalid use of --scale" << std::endl; - print_options(serializer_options); - return EXIT_FAILURE; - } - } - if (vmap.count("section-ref")) { - static_cast(serializer.get())->setSectionRef(section_ref); - } - if (vmap.count("elevation-ref")) { - static_cast(serializer.get())->setElevationRef(elevation_ref); - } - if (vmap.count("elevation-ref-guid")) { - static_cast(serializer.get())->setElevationRefGuid(elevation_ref_guid); - } - if (vmap.count("auto-section")) { - static_cast(serializer.get())->setAutoSection(true); - } - if (vmap.count("auto-elevation")) { - static_cast(serializer.get())->setAutoElevation(true); - } - 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); - static_cast(serializer.get())->setNoCSS(vmap.count("svg-no-css") > 0); - if (relative_center_x && relative_center_y) { - static_cast(serializer.get())->setDrawingCenter(*relative_center_x, *relative_center_y); - } - if (vmap.count("storey-height-line-length")) { - static_cast(serializer.get())->setStoreyHeightLineLength( - vmap["storey-height-line-length"].as() - ); - } - if (vmap.count("space-name-transform")) { - static_cast(serializer.get())->setSpaceNameTransform( - vmap["space-name-transform"].as() - ); - } - } -#endif - if (context_iterator && geometry_settings.get().get()) { serializer->setUnitNameAndMagnitude(context_iterator->unit_name(), static_cast(context_iterator->unit_magnitude())); } else { @@ -1203,7 +979,7 @@ int main(int argc, char** argv) { logger::message(logger::LOG_PERF, "done file geometry conversion"); bool successful; - if(output_extension == USD || output_extension == USDC || output_extension == USDA) { + if (geometry_serializer_info->writes_final_output) { // No need to rename the file successful = true; } diff --git a/src/ifcgeom/CMakeLists.txt b/src/ifcgeom/CMakeLists.txt index 993e480149..9dc9045dec 100644 --- a/src/ifcgeom/CMakeLists.txt +++ b/src/ifcgeom/CMakeLists.txt @@ -4,12 +4,16 @@ set(mapping_libraries ${mapping_libraries} PARENT_SCOPE) # IfcGeom (schema agnostic) file(GLOB SCHEMA_AGNOSTIC_H_FILES *.h) file(GLOB SCHEMA_AGNOSTIC_CPP_FILES *.cpp) -set(SCHEMA_AGNOSTIC_FILES ${SCHEMA_AGNOSTIC_H_FILES} ${SCHEMA_AGNOSTIC_CPP_FILES}) +set(SERIALIZER_RUNTIME_CPP_FILES + ../serializers/document_serializer_plugin.cpp + ../serializers/geometry_serializer_plugin.cpp +) +set(SCHEMA_AGNOSTIC_FILES ${SCHEMA_AGNOSTIC_H_FILES} ${SCHEMA_AGNOSTIC_CPP_FILES} ${SERIALIZER_RUNTIME_CPP_FILES}) add_library(IfcGeom ${SCHEMA_AGNOSTIC_FILES}) add_library(geometry ALIAS IfcGeom) set_target_properties(IfcGeom PROPERTIES COMPILE_FLAGS -DIFC_GEOM_EXPORTS VERSION "${PROJECT_VERSION}" SOVERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}") -target_compile_definitions(IfcGeom PRIVATE BOOST_DLL_USE_STD_FS) +target_compile_definitions(IfcGeom PRIVATE BOOST_DLL_USE_STD_FS SERIALIZERS_EXPORTS) if(UNIX) find_package(Threads) diff --git a/src/ifcgeom/GeometrySerializer.h b/src/ifcgeom/GeometrySerializer.h index 603b48a261..8fbb2834a4 100644 --- a/src/ifcgeom/GeometrySerializer.h +++ b/src/ifcgeom/GeometrySerializer.h @@ -93,11 +93,179 @@ inline namespace settings { static constexpr const char* const description = "Introduce a separate Z-Up node into the GlTF hierarchy instead of multiplying the transform into the root node matrices"; static constexpr bool defaultvalue = false; }; + + struct SvgBounds : public SettingBase { + static constexpr const char* const name = "bounds"; + static constexpr const char* const description = "Specifies the bounding rectangle, for example 512x512, to which the output will be scaled. Only used when converting to SVG."; + }; + + struct SvgScale : public SettingBase { + static constexpr const char* const name = "scale"; + static constexpr const char* const description = "Interprets SVG bounds in mm, centers layout and draw elements to scale. Only used when converting to SVG. Example 1:100."; + }; + + struct SvgCenter : public SettingBase { + static constexpr const char* const name = "center"; + static constexpr const char* const description = "When using --scale, specifies the location in the range [0 1]x[0 1] around which to center the drawings. Example 0.5x0.5."; + }; + + struct SvgSectionRef : public SettingBase { + static constexpr const char* const name = "section-ref"; + static constexpr const char* const description = "Element at which cross sections should be created."; + }; + + struct SvgElevationRef : public SettingBase { + static constexpr const char* const name = "elevation-ref"; + static constexpr const char* const description = "Element at which drawings should be created."; + }; + + struct SvgElevationRefGuid : public SettingBase { + static constexpr const char* const name = "elevation-ref-guid"; + static constexpr const char* const description = "Element guids at which drawings should be created."; + }; + + struct SvgAutoSection : public SettingBase { + static constexpr const char* const name = "auto-section"; + static constexpr const char* const description = "Creates SVG cross section drawings automatically based on model extents."; + static constexpr bool defaultvalue = false; + }; + + struct SvgAutoElevation : public SettingBase { + static constexpr const char* const name = "auto-elevation"; + static constexpr const char* const description = "Creates SVG elevation drawings automatically based on model extents."; + static constexpr bool defaultvalue = false; + }; + + struct SvgDrawStoreyHeights : public SettingBase { + static constexpr const char* const name = "draw-storey-heights"; + static constexpr const char* const description = "Draws a horizontal line at the height of building storeys in vertical drawings. Accepted values are none, full, and left."; + }; + + struct SvgStoreyHeightLineLength : public SettingBase { + static constexpr const char* const name = "storey-height-line-length"; + static constexpr const char* const description = "Length of the line when --draw-storey-heights=left."; + }; + + struct SvgUseNamespace : public SettingBase { + static constexpr const char* const name = "svg-xmlns"; + static constexpr const char* const description = "Stores name and guid in a separate namespace as opposed to data-name, data-guid."; + static constexpr bool defaultvalue = false; + }; + + struct SvgUseHlrPoly : public SettingBase { + static constexpr const char* const name = "svg-poly"; + static constexpr const char* const description = "Uses the polygonal algorithm for hidden line rendering."; + static constexpr bool defaultvalue = false; + }; + + struct SvgUsePrefiltering : public SettingBase { + static constexpr const char* const name = "svg-prefilter"; + static constexpr const char* const description = "Prefilter faces and shapes before feeding to the hidden-line algorithm."; + static constexpr bool defaultvalue = false; + }; + + struct SvgSegmentProjection : public SettingBase { + static constexpr const char* const name = "svg-segment-projection"; + static constexpr const char* const description = "Segment result of projection with respect to original products."; + static constexpr bool defaultvalue = false; + }; + + struct SvgPolygonal : public SettingBase { + static constexpr const char* const name = "svg-write-poly"; + static constexpr const char* const description = "Approximate every curve as polygonal in SVG output."; + static constexpr bool defaultvalue = false; + }; + + struct SvgAlwaysProject : public SettingBase { + static constexpr const char* const name = "svg-project"; + static constexpr const char* const description = "Always enable hidden line rendering instead of only on elevations."; + static constexpr bool defaultvalue = false; + }; + + struct SvgWithoutStoreys : public SettingBase { + static constexpr const char* const name = "svg-without-storeys"; + static constexpr const char* const description = "Do not emit drawings for building storeys."; + static constexpr bool defaultvalue = false; + }; + + struct SvgNoCss : public SettingBase { + static constexpr const char* const name = "svg-no-css"; + static constexpr const char* const description = "Do not emit CSS style declarations."; + static constexpr bool defaultvalue = false; + }; + + struct SvgDoorArcs : public SettingBase { + static constexpr const char* const name = "door-arcs"; + static constexpr const char* const description = "Draw door opening arcs for IfcDoor elements."; + static constexpr bool defaultvalue = false; + }; + + struct SvgSectionHeight : public SettingBase { + static constexpr const char* const name = "section-height"; + static constexpr const char* const description = "Specifies the cut section height for SVG 2D geometry."; + }; + + struct SvgSectionHeightFromStoreys : public SettingBase { + static constexpr const char* const name = "section-height-from-storeys"; + static constexpr const char* const description = "Derives section height from storey elevation. Use --section-height to override the default offset of 1.2."; + static constexpr bool defaultvalue = false; + }; + + struct SvgPrintSpaceNames : public SettingBase { + static constexpr const char* const name = "print-space-names"; + static constexpr const char* const description = "Prints IfcSpace LongName and Name in the geometry output. Applicable for SVG output."; + static constexpr bool defaultvalue = false; + }; + + struct SvgPrintSpaceAreas : public SettingBase { + static constexpr const char* const name = "print-space-areas"; + static constexpr const char* const description = "Prints calculated IfcSpace areas in square meters. Applicable for SVG output."; + static constexpr bool defaultvalue = false; + }; + + struct SvgSpaceNameTransform : public SettingBase { + static constexpr const char* const name = "space-name-transform"; + static constexpr const char* const description = "Additional transform to the space labels in SVG."; + }; } class SerializerSettings : public SettingsContainer < // @todo should we use tuple_cat here to unify the settings into a single class? - std::tuple> + std::tuple< + UseElementNames, + UseElementGuids, + UseElementStepIds, + UseElementTypes, + UseYUp, + WriteGltfEcef, + FloatingPointDigits, + BaseUri, + WktUseSection, + SeparateZUpNode, + SvgBounds, + SvgScale, + SvgCenter, + SvgSectionRef, + SvgElevationRef, + SvgElevationRefGuid, + SvgAutoSection, + SvgAutoElevation, + SvgDrawStoreyHeights, + SvgStoreyHeightLineLength, + SvgUseNamespace, + SvgUseHlrPoly, + SvgUsePrefiltering, + SvgSegmentProjection, + SvgPolygonal, + SvgAlwaysProject, + SvgWithoutStoreys, + SvgNoCss, + SvgDoorArcs, + SvgSectionHeight, + SvgSectionHeightFromStoreys, + SvgPrintSpaceNames, + SvgPrintSpaceAreas, + SvgSpaceNameTransform>> {}; } diff --git a/src/ifcwrap/CMakeLists.txt b/src/ifcwrap/CMakeLists.txt index b336a57a4c..13c0e0113a 100644 --- a/src/ifcwrap/CMakeLists.txt +++ b/src/ifcwrap/CMakeLists.txt @@ -147,6 +147,12 @@ target_link_libraries(ifcopenshell_wrapper PRIVATE ${IFCOPENSHELL_LIBRARIES} ${O else() target_link_libraries(ifcopenshell_wrapper PRIVATE ${IFCOPENSHELL_LIBRARIES} ${LIBSVGFILL}) endif() +if(geometry_serializer_libraries) + target_link_libraries(ifcopenshell_wrapper PRIVATE ${geometry_serializer_libraries}) +endif() +if(TARGET document_serializer_rdb) + target_link_libraries(ifcopenshell_wrapper PRIVATE document_serializer_rdb) +endif() if(kernel_libraries OR tree_libraries OR mapping_libraries OR geometry_serializer_libraries OR document_serializer_libraries) add_dependencies(ifcopenshell_wrapper ${kernel_libraries} ${tree_libraries} ${mapping_libraries} ${geometry_serializer_libraries} ${document_serializer_libraries}) endif() diff --git a/src/ifcwrap/IfcPython.i b/src/ifcwrap/IfcPython.i index 02c18f7b2b..ff7eaa5692 100644 --- a/src/ifcwrap/IfcPython.i +++ b/src/ifcwrap/IfcPython.i @@ -163,9 +163,6 @@ %ignore ApplyOffset; %ignore SeparateZUpNode; -%ignore XmlSerializerFactory; -%ignore JsonSerializerFactory; - // Triangulated representation helper struct %ignore EdgeKey; diff --git a/src/serializers/CMakeLists.txt b/src/serializers/CMakeLists.txt index 9d0b0fab46..f875e68954 100644 --- a/src/serializers/CMakeLists.txt +++ b/src/serializers/CMakeLists.txt @@ -1,34 +1,87 @@ -add_subdirectory(schema_dependent) +set(document_serializer_plugin_runtime_dir "${CMAKE_BINARY_DIR}/serializers/$") +set(geometry_serializer_plugin_runtime_dir "${CMAKE_BINARY_DIR}/serializers/$") file(GLOB SERIALIZERS_H_FILES *.h) -file(GLOB SERIALIZERS_CPP_FILES *.cpp) file(GLOB SERIALIZERS_S_H_FILES schema_dependent/*.h) -set(SERIALIZERS_FILES ${SERIALIZERS_H_FILES} ${SERIALIZERS_CPP_FILES}) -add_library(Serializers ${SERIALIZERS_FILES}) -add_library(serialization ALIAS Serializers) -set_target_properties(Serializers PROPERTIES COMPILE_FLAGS "-DSERIALIZERS_EXPORTS" VERSION "${PROJECT_VERSION}" SOVERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}") +function(add_document_serializer_plugin target output_name) + cmake_parse_arguments(PLUGIN "" "" "SOURCES;LIBRARIES" ${ARGN}) -if(WITH_PROJ) - find_package(proj REQUIRED) - target_compile_definitions(Serializers PRIVATE "WITH_PROJ") - if(PROJ_STATIC) - target_compile_definitions(Serializers PRIVATE "PROJ_DLL=") + add_library(${target} SHARED ${PLUGIN_SOURCES}) + target_compile_definitions(${target} PRIVATE SERIALIZERS_EXPORTS BOOST_DLL_USE_STD_FS) + target_link_libraries(${target} PRIVATE plugin IfcGeom IfcParse ${PLUGIN_LIBRARIES}) + set_target_properties(${target} PROPERTIES + OUTPUT_NAME "${output_name}" + RUNTIME_OUTPUT_DIRECTORY "${document_serializer_plugin_runtime_dir}" + LIBRARY_OUTPUT_DIRECTORY "${document_serializer_plugin_runtime_dir}" + ) + + list(APPEND document_serializer_libraries ${target}) + set(document_serializer_libraries ${document_serializer_libraries} PARENT_SCOPE) +endfunction() + +function(add_geometry_serializer_plugin target output_name) + cmake_parse_arguments(PLUGIN "" "" "SOURCES;LIBRARIES" ${ARGN}) + + add_library(${target} SHARED ${PLUGIN_SOURCES}) + target_compile_definitions(${target} PRIVATE SERIALIZERS_EXPORTS BOOST_DLL_USE_STD_FS) + target_link_libraries(${target} PRIVATE plugin IfcGeom IfcParse ${PLUGIN_LIBRARIES}) + if(WITH_PROJ) + find_package(proj REQUIRED) + target_compile_definitions(${target} PRIVATE "WITH_PROJ") + if(PROJ_STATIC) + target_compile_definitions(${target} PRIVATE "PROJ_DLL=") + endif() + target_link_libraries(${target} PRIVATE proj::proj) endif() - target_link_libraries(Serializers PRIVATE proj::proj) + set_target_properties(${target} PROPERTIES + OUTPUT_NAME "${output_name}" + RUNTIME_OUTPUT_DIRECTORY "${geometry_serializer_plugin_runtime_dir}" + LIBRARY_OUTPUT_DIRECTORY "${geometry_serializer_plugin_runtime_dir}" + ) + + list(APPEND geometry_serializer_libraries ${target}) + set(geometry_serializer_libraries ${geometry_serializer_libraries} PARENT_SCOPE) +endfunction() + +if(WITH_ROCKSDB) + add_document_serializer_plugin(document_serializer_rdb "document.rdb" SOURCES document_rdb_plugin.cpp RocksDbSerializer.cpp LIBRARIES RocksDB::rocksdb) endif() -target_link_libraries(Serializers - PRIVATE - plugin ${OPENCOLLADA_LIBRARIES} ${USD_LIBRARIES} - IfcGeom ${OpenCASCADE_LIBRARIES} IfcParse -) +add_subdirectory(schema_dependent) + +set(ttl_geometry_serializer_libraries) +if(WITH_OPENCASCADE) + set(ttl_geometry_serializer_libraries geometry_kernel_opencascade ${OpenCASCADE_LIBRARIES}) +endif() + +add_geometry_serializer_plugin(geometry_serializer_obj "geometry.obj" SOURCES geometry_obj_plugin.cpp WavefrontObjSerializer.cpp) +add_geometry_serializer_plugin(geometry_serializer_ttl "geometry.ttl" SOURCES geometry_ttl_plugin.cpp TtlWktSerializer.cpp LIBRARIES ${ttl_geometry_serializer_libraries}) + +if(OPENCOLLADA_FOUND) + add_geometry_serializer_plugin(geometry_serializer_dae "geometry.dae" SOURCES geometry_dae_plugin.cpp ColladaSerializer.cpp LIBRARIES ${OPENCOLLADA_LIBRARIES}) +endif() + +if(GLTF_SUPPORT) + add_geometry_serializer_plugin(geometry_serializer_glb "geometry.glb" SOURCES geometry_glb_plugin.cpp GltfSerializer.cpp) +endif() + +if(USD_SUPPORT) + add_geometry_serializer_plugin(geometry_serializer_usd "geometry.usd" SOURCES geometry_usd_plugin.cpp USDSerializer.cpp LIBRARIES ${USD_LIBRARIES}) +endif() if(WITH_OPENCASCADE) - target_link_libraries(Serializers PRIVATE geometry_kernel_opencascade) + set(opencascade_geometry_serializer_libraries geometry_kernel_opencascade ${OpenCASCADE_LIBRARIES}) + add_geometry_serializer_plugin(geometry_serializer_stp "geometry.stp" SOURCES geometry_stp_plugin.cpp OpenCascadeBasedSerializer.cpp LIBRARIES ${opencascade_geometry_serializer_libraries}) + add_geometry_serializer_plugin(geometry_serializer_igs "geometry.igs" SOURCES geometry_igs_plugin.cpp OpenCascadeBasedSerializer.cpp LIBRARIES ${opencascade_geometry_serializer_libraries}) + add_geometry_serializer_plugin(geometry_serializer_svg "geometry.svg" SOURCES geometry_svg_plugin.cpp SvgSerializer.cpp util.cpp LIBRARIES ${opencascade_geometry_serializer_libraries}) + + if(HDF5_SUPPORT) + add_geometry_serializer_plugin(geometry_serializer_hdf "geometry.hdf" SOURCES geometry_hdf_plugin.cpp HdfSerializer.cpp LIBRARIES ${opencascade_geometry_serializer_libraries} ${HDF5_LIBRARIES}) + endif() endif() -install(TARGETS Serializers ${document_serializer_libraries}) +install(TARGETS ${document_serializer_libraries} ${geometry_serializer_libraries}) install(FILES ${SERIALIZERS_H_FILES} DESTINATION ${INCLUDEDIR}/serializers/ @@ -38,6 +91,5 @@ install(FILES ${SERIALIZERS_S_H_FILES} DESTINATION ${INCLUDEDIR}/serializers/schema_dependent ) -set(IFCOPENSHELL_LIBRARIES ${IFCOPENSHELL_LIBRARIES} Serializers) -set(IFCOPENSHELL_LIBRARIES ${IFCOPENSHELL_LIBRARIES} PARENT_SCOPE) set(document_serializer_libraries ${document_serializer_libraries} PARENT_SCOPE) +set(geometry_serializer_libraries ${geometry_serializer_libraries} PARENT_SCOPE) diff --git a/src/serializers/JsonSerializer.cpp b/src/serializers/JsonSerializer.cpp deleted file mode 100644 index d29e4e8101..0000000000 --- a/src/serializers/JsonSerializer.cpp +++ /dev/null @@ -1,56 +0,0 @@ -/******************************************************************************** -* * -* This file is part of IfcOpenShell. * -* * -* IfcOpenShell is free software: you can redistribute it and/or modify * -* it under the terms of the Lesser GNU General Public License as published by * -* the Free Software Foundation, either version 3.0 of the License, or * -* (at your option) any later version. * -* * -* IfcOpenShell is distributed in the hope that it will be useful, * -* but WITHOUT ANY WARRANTY; without even the implied warranty of * -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * -* Lesser GNU General Public License for more details. * -* * -* You should have received a copy of the Lesser GNU General Public License * -* along with this program. If not, see . * -* * -********************************************************************************/ - -#ifdef WITH_GLTF - -#include "JsonSerializer.h" -#include "document_serializer_plugin.h" - -#include - -JsonSerializerFactory::Factory::Factory() { - ifcopenshell::serializers::load_document_serializer_plugins(*this, "json"); -} - -void JsonSerializerFactory::Factory::bind(const std::string& schema_name, fn f, const ifcopenshell::plugin::module& module) { - const std::string schema_name_lower = boost::to_lower_copy(schema_name); - entries_[schema_name_lower] = { f, module }; -} - -JsonSerializer* JsonSerializerFactory::Factory::construct(const std::string& schema_name, ifcopenshell::file* file, std::string json_filename, JsonSerializer::Dialect dialect) { - const std::string schema_name_lower = boost::to_lower_copy(schema_name); - auto it = entries_.find(schema_name_lower); - if (it == entries_.end()) { - throw ifcopenshell::exception("No Json serializer registered for " + schema_name); - } - return it->second.fn_(file, json_filename, dialect); -} - -JsonSerializer::JsonSerializer(ifcopenshell::file* file, const std::string& json_filename, JsonSerializer::Dialect dialect) { - if (file) { - implementation_ = JsonSerializerFactory::implementations().construct(file->schema()->name(), file, json_filename, dialect); - } -} - -JsonSerializerFactory::Factory& JsonSerializerFactory::implementations() { - static JsonSerializerFactory::Factory impl; - return impl; -} - -#endif diff --git a/src/serializers/JsonSerializer.h b/src/serializers/JsonSerializer.h index 3137214739..f1021ba3d3 100644 --- a/src/serializers/JsonSerializer.h +++ b/src/serializers/JsonSerializer.h @@ -7,55 +7,53 @@ #include "../ifcparse/file.h" #include "../plugin/plugin.h" #include "../serializers/serializers_api.h" +#include "../serializers/document_serializer_plugin.h" -#include -#include +#include -class SERIALIZERS_API JsonSerializer : public Serializer { +class JsonSerializer : public Serializer { public: enum Dialect { JSON_DIALECT_CREOOX }; private: - JsonSerializer* implementation_ = nullptr; + boost::shared_ptr implementation_; protected: std::string json_filename; Dialect dialect_; public: - JsonSerializer(ifcopenshell::file* file, const std::string& json_filename, Dialect dialect = Dialect::JSON_DIALECT_CREOOX); + JsonSerializer(ifcopenshell::file* file, const std::string& json_filename, Dialect dialect = Dialect::JSON_DIALECT_CREOOX) + : json_filename(json_filename) + , dialect_(dialect) + { + if (!file) { + return; + } + + ifcopenshell::serializers::document_serializer_context context; + context.file = file; + context.output_filename = json_filename; + context.schema_name = file->schema()->name(); + context.dialect = static_cast(dialect); + implementation_ = ifcopenshell::serializers::document_serializer_registry_instance().create("json", context); + } virtual ~JsonSerializer() {} bool ready() { return true; } void writeHeader() {} - void finalize() { implementation_->finalize(); } + void finalize() { + if (!implementation_) { + throw ifcopenshell::exception("No JSON serializer implementation constructed"); + } + implementation_->finalize(); + } void setFile(ifcopenshell::file*) { throw ifcopenshell::exception("Should be supplied on construction"); } }; -struct SERIALIZERS_API JsonSerializerFactory { - typedef boost::function3 fn; - - class SERIALIZERS_API Factory { - public: - Factory(); - void bind(const std::string& schema_name, fn, const ifcopenshell::plugin::module& module = ifcopenshell::plugin::module()); - JsonSerializer* construct(const std::string& schema_name, ifcopenshell::file*, std::string, JsonSerializer::Dialect); - - private: - struct entry { - fn fn_; - ifcopenshell::plugin::module module_; - }; - - std::map entries_; - }; - - static Factory& implementations(); -}; - #endif #endif diff --git a/src/serializers/SvgSerializer.cpp b/src/serializers/SvgSerializer.cpp index dd1951b75c..8fc142df3b 100644 --- a/src/serializers/SvgSerializer.cpp +++ b/src/serializers/SvgSerializer.cpp @@ -86,17 +86,132 @@ #include "../ifcgeom/kernels/opencascade/OpenCascadeConversionResult.h" +#include #include #include #include "SvgSerializer.h" +namespace { + const double PI2 = M_PI * 2.; +std::pair parse_svg_bounds(const std::string& value) { + unsigned int width = 0; + unsigned int height = 0; + if (std::sscanf(value.c_str(), "%ux%u", &width, &height) == 2 && width > 0 && height > 0) { + return { static_cast(width), static_cast(height) }; + } + throw std::runtime_error("Invalid use of --bounds"); +} + +std::pair parse_svg_center(const std::string& value) { + double center_x = 0.; + double center_y = 0.; + if (std::sscanf(value.c_str(), "%lfx%lf", ¢er_x, ¢er_y) == 2 && + center_x >= 0. && center_y >= 0. && + center_x <= 1. && center_y <= 1.) { + return { center_x, center_y }; + } + throw std::runtime_error("Invalid use of --center"); +} + +double parse_svg_scale(const std::string& value) { + unsigned int numerator = 0; + unsigned int denominator = 0; + if (std::sscanf(value.c_str(), "%u:%u", &numerator, &denominator) == 2 && + numerator > 0 && denominator > 0) { + return static_cast(numerator) / denominator; + } + throw std::runtime_error("Invalid use of --scale"); +} + +SvgSerializer::storey_height_display_types parse_storey_height_display(const std::string& value) { + const auto display = boost::to_lower_copy(value); + if (display == "none") { + return SvgSerializer::SH_NONE; + } + if (display == "full") { + return SvgSerializer::SH_FULL; + } + if (display == "left") { + return SvgSerializer::SH_LEFT; + } + throw std::runtime_error("Invalid use of --draw-storey-heights, expected none|full|left"); +} + +} + bool SvgSerializer::ready() { return true; } +void SvgSerializer::apply_settings() { + using namespace ifcopenshell::geometry::settings; + + if (settings().get().has()) { + const auto bounds = parse_svg_bounds(settings().get().get()); + setBoundingRectangle(bounds.first, bounds.second); + } + + if (settings().get().has()) { + setScale(parse_svg_scale(settings().get().get())); + } + + if (settings().get().has()) { + const auto center = parse_svg_center(settings().get().get()); + setDrawingCenter(center.first, center.second); + } + + if (settings().get().get()) { + if (settings().get().has()) { + setSectionHeightsFromStoreys(settings().get().get()); + } else { + setSectionHeightsFromStoreys(); + } + } else if (settings().get().has()) { + setSectionHeight(settings().get().get()); + } + + setPrintSpaceNames(settings().get().get()); + setPrintSpaceAreas(settings().get().get()); + setDrawDoorArcs(settings().get().get()); + setAutoSection(settings().get().get()); + setAutoElevation(settings().get().get()); + setUseNamespace(settings().get().get()); + setUseHlrPoly(settings().get().get()); + setUsePrefiltering(settings().get().get()); + setSegmentProjection(settings().get().get()); + setPolygonal(settings().get().get()); + setAlwaysProject(settings().get().get()); + setWithoutStoreys(settings().get().get()); + setNoCSS(settings().get().get()); + + if (settings().get().has()) { + setDrawStoreyHeights(parse_storey_height_display(settings().get().get())); + } + + if (settings().get().has()) { + setStoreyHeightLineLength(settings().get().get()); + } + + if (settings().get().has()) { + setSpaceNameTransform(settings().get().get()); + } + + if (settings().get().has()) { + setSectionRef(settings().get().get()); + } + + if (settings().get().has()) { + setElevationRef(settings().get().get()); + } + + if (settings().get().has()) { + setElevationRefGuid(settings().get().get()); + } +} + void SvgSerializer::write(path_object& p, const TopoDS_Shape& comp_or_wire, std::optional> dash_array) { /* ShapeFix_Wire fix; Handle(ShapeExtend_WireData) data = new ShapeExtend_WireData; diff --git a/src/serializers/SvgSerializer.h b/src/serializers/SvgSerializer.h index 548d717a96..d62c2b0bf9 100644 --- a/src/serializers/SvgSerializer.h +++ b/src/serializers/SvgSerializer.h @@ -589,6 +589,7 @@ protected: Bnd_Box bnd_; void draw_hlr(const gp_Pln& pln, const drawing_key& drawing_name); + void apply_settings(); subtract_before_project subtraction_settings_; @@ -627,7 +628,9 @@ public: , hlr(nullptr) , namespace_prefix_("data-") , subtraction_settings_(ON_SLABS_AT_FLOORPLANS) - {} + { + apply_settings(); + } void addXCoordinate(const boost::shared_ptr& fi) { xcoords.push_back(fi); } void addYCoordinate(const boost::shared_ptr& fi) { ycoords.push_back(fi); } void addSizeComponent(const boost::shared_ptr& fi) { radii.push_back(fi); } diff --git a/src/serializers/XmlSerializer.cpp b/src/serializers/XmlSerializer.cpp deleted file mode 100644 index da303059ab..0000000000 --- a/src/serializers/XmlSerializer.cpp +++ /dev/null @@ -1,33 +0,0 @@ -#include "XmlSerializer.h" -#include "document_serializer_plugin.h" - -#include - -XmlSerializerFactory::Factory::Factory() { - ifcopenshell::serializers::load_document_serializer_plugins(*this, "xml"); -} - -void XmlSerializerFactory::Factory::bind(const std::string& schema_name, fn f, const ifcopenshell::plugin::module& module) { - const std::string schema_name_lower = boost::to_lower_copy(schema_name); - entries_[schema_name_lower] = { f, module }; -} - -XmlSerializer* XmlSerializerFactory::Factory::construct(const std::string& schema_name, ifcopenshell::file* file, std::string xml_filename) { - const std::string schema_name_lower = boost::to_lower_copy(schema_name); - auto it = entries_.find(schema_name_lower); - if (it == entries_.end()) { - throw ifcopenshell::exception("No XML serializer registered for " + schema_name); - } - return it->second.fn_(file, xml_filename); -} - -XmlSerializer::XmlSerializer(ifcopenshell::file* file, const std::string& xml_filename) { - if (file) { - implementation_ = XmlSerializerFactory::implementations().construct(file->schema()->name(), file, xml_filename); - } -} - -XmlSerializerFactory::Factory& XmlSerializerFactory::implementations() { - static XmlSerializerFactory::Factory impl; - return impl; -} diff --git a/src/serializers/XmlSerializer.h b/src/serializers/XmlSerializer.h index e5da03b8be..30cfb0a1e2 100644 --- a/src/serializers/XmlSerializer.h +++ b/src/serializers/XmlSerializer.h @@ -7,49 +7,44 @@ #include "../ifcgeom/Serializer.h" #include "../ifcparse/file.h" #include "../plugin/plugin.h" +#include "../serializers/document_serializer_plugin.h" -#include +#include -#include - -class SERIALIZERS_API XmlSerializer : public Serializer { +class XmlSerializer : public Serializer { private: - XmlSerializer* implementation_ = nullptr; + boost::shared_ptr implementation_; protected: std::string xml_filename; public: - XmlSerializer(ifcopenshell::file* file, const std::string& xml_filename); + XmlSerializer(ifcopenshell::file* file, const std::string& xml_filename) + : xml_filename(xml_filename) + { + if (!file) { + return; + } + + ifcopenshell::serializers::document_serializer_context context; + context.file = file; + context.output_filename = xml_filename; + context.schema_name = file->schema()->name(); + implementation_ = ifcopenshell::serializers::document_serializer_registry_instance().create("xml", context); + } virtual ~XmlSerializer() {} bool ready() { return true; } void writeHeader() {} - void finalize() { implementation_->finalize(); } + void finalize() { + if (!implementation_) { + throw ifcopenshell::exception("No XML serializer implementation constructed"); + } + implementation_->finalize(); + } void setFile(ifcopenshell::file*) { throw ifcopenshell::exception("Should be supplied on construction"); } }; -struct SERIALIZERS_API XmlSerializerFactory { - typedef boost::function2 fn; - - class SERIALIZERS_API Factory { - public: - Factory(); - void bind(const std::string& schema_name, fn, const ifcopenshell::plugin::module& module = ifcopenshell::plugin::module()); - XmlSerializer* construct(const std::string& schema_name, ifcopenshell::file*, std::string); - - private: - struct entry { - fn fn_; - ifcopenshell::plugin::module module_; - }; - - std::map entries_; - }; - - static Factory& implementations(); -}; - #endif diff --git a/src/serializers/document_rdb_plugin.cpp b/src/serializers/document_rdb_plugin.cpp new file mode 100644 index 0000000000..5d4b2e4a79 --- /dev/null +++ b/src/serializers/document_rdb_plugin.cpp @@ -0,0 +1,68 @@ +/******************************************************************************** + * * + * 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 . * + * * + ********************************************************************************/ + +#include "document_serializer_plugin.h" +#include "RocksDbSerializer.h" + +#ifdef IFOPSH_WITH_ROCKSDB + +#include +#include + +namespace { + +boost::shared_ptr create_serializer(const ifcopenshell::serializers::document_serializer_context& context) { + if (!context.stream || context.input_filename.empty()) { + throw ifcopenshell::exception("RocksDB document serializer requires --stream input"); + } + return boost::make_shared(context.input_filename, context.output_filename, true); +} + +} + +namespace ifcopenshell { +namespace serializers { +namespace rdb_document_serializer_plugin { + +plugin::abi_info plugin_abi() { + return plugin::host_abi(); +} + +plugin::metadata plugin_metadata() { + return document_serializer_plugin_metadata("rdb"); +} + +void register_plugin(document_serializer_registry& registry, const plugin::module& module) { + document_serializer_info info; + info.format = "rdb"; + info.supports_ifc_file = false; + info.supports_input_filename = true; + info.writes_final_output = true; + registry.bind(info, create_serializer, module); +} + +} +} +} + +BOOST_DLL_ALIAS(ifcopenshell::serializers::rdb_document_serializer_plugin::plugin_abi, ifcopenshell_plugin_abi_v1) +BOOST_DLL_ALIAS(ifcopenshell::serializers::rdb_document_serializer_plugin::plugin_metadata, ifcopenshell_plugin_metadata_v1) +BOOST_DLL_ALIAS(ifcopenshell::serializers::rdb_document_serializer_plugin::register_plugin, ifcopenshell_register_document_serializer_plugin_v1) + +#endif diff --git a/src/serializers/document_serializer_plugin.cpp b/src/serializers/document_serializer_plugin.cpp index 7f4d466fb5..8a00ae8944 100644 --- a/src/serializers/document_serializer_plugin.cpp +++ b/src/serializers/document_serializer_plugin.cpp @@ -21,35 +21,117 @@ #include +#include "../ifcparse/logger.h" + +#include +#include + namespace { -std::string document_serializer_plugin_prefix(const std::string& format) { - return "document." + boost::to_lower_copy(format) + "."; +std::string document_serializer_key(const std::string& format) { + if (format.empty()) { + return format; + } + + auto key = boost::to_lower_copy(format); + if (key.front() == '.') { + key.erase(key.begin()); + } + return key; } -template -void load_document_serializer_plugins_impl(registry_t& registry, const std::string& format) { - ifcopenshell::plugin::manager manager; - manager.add_search_path(ifcopenshell::serializers::document_serializer_plugin_directory()); +std::string document_serializer_schema_key(const std::string& schema_name) { + return boost::to_upper_copy(schema_name); +} - const auto format_lower = boost::to_lower_copy(format); - const auto prefix = document_serializer_plugin_prefix(format_lower); - for (const auto& path : manager.discover(prefix)) { - auto module = manager.load(path); - if (module.meta().kind_ != ifcopenshell::plugin::kind::document_serializer) { - continue; - } - if (boost::to_lower_copy(module.meta().format) != format_lower) { - continue; - } +std::string document_serializer_plugin_prefix(const std::string& format = std::string()) { + const auto format_key = document_serializer_key(format); + return format_key.empty() ? "document." : "document." + format_key; +} - auto register_plugin = module.get_alias(ifcopenshell::serializers::document_serializer_plugin_registration_symbol()); - register_plugin(registry, module); +void add_document_serializer_search_paths(ifcopenshell::plugin::manager& manager) { + const auto directory = ifcopenshell::serializers::document_serializer_plugin_directory(); + manager.add_search_path(directory); + + const auto sibling_directory = directory.parent_path().parent_path() / "serializers" / directory.filename(); + if (sibling_directory != directory && std::filesystem::exists(sibling_directory)) { + manager.add_search_path(sibling_directory); } } } +void ifcopenshell::serializers::document_serializer_registry::bind(const document_serializer_info& info, create_fn create, const plugin::module& module) { + entry entry; + entry.info_ = info; + entry.info_.format = document_serializer_key(entry.info_.format); + entry.info_.schema_name = document_serializer_schema_key(entry.info_.schema_name); + entry.create_ = create; + entry.module_ = module.meta().id.empty() ? plugin::module(document_serializer_plugin_metadata(entry.info_.format, entry.info_.schema_name)) : module; + + entries_[entry.info_.format].push_back(entry); +} + +const ifcopenshell::serializers::document_serializer_registry::entry* ifcopenshell::serializers::document_serializer_registry::find_entry_(const std::string& format, const std::string& schema_name) const { + const auto format_key = document_serializer_key(format); + const auto iter = entries_.find(format_key); + if (iter == entries_.end()) { + return nullptr; + } + + const auto schema_key = document_serializer_schema_key(schema_name); + const entry* fallback = nullptr; + + for (const auto& candidate : iter->second) { + if (candidate.info_.schema_name.empty()) { + if (!fallback) { + fallback = &candidate; + } + continue; + } + if (!schema_key.empty() && candidate.info_.schema_name == schema_key) { + return &candidate; + } + if (schema_key.empty() && !fallback) { + fallback = &candidate; + } + } + + return fallback; +} + +const ifcopenshell::serializers::document_serializer_info* ifcopenshell::serializers::document_serializer_registry::find(const std::string& format, const std::string& schema_name) const { + const auto* entry = find_entry_(format, schema_name); + return entry ? &entry->info_ : nullptr; +} + +boost::shared_ptr ifcopenshell::serializers::document_serializer_registry::create(const std::string& format, const document_serializer_context& context) const { + const auto schema_name = !context.schema_name.empty() ? context.schema_name : + (context.file ? context.file->schema()->name() : std::string()); + const auto* entry = find_entry_(format, schema_name); + if (!entry) { + throw ifcopenshell::exception("No document serializer registered for " + format + (schema_name.empty() ? std::string() : " and schema " + schema_name)); + } + return entry->create_(context); +} + +std::vector ifcopenshell::serializers::document_serializer_registry::serializers() const { + std::vector result; + std::set seen; + + for (const auto& pair : entries_) { + for (const auto& entry : pair.second) { + const auto key = entry.info_.format + "|" + entry.info_.schema_name; + if (!seen.insert(key).second) { + continue; + } + result.push_back(entry.info_); + } + } + + return result; +} + const char* ifcopenshell::serializers::document_serializer_plugin_registration_symbol() { return "ifcopenshell_register_document_serializer_plugin_v1"; } @@ -57,9 +139,12 @@ const char* ifcopenshell::serializers::document_serializer_plugin_registration_s ifcopenshell::plugin::metadata ifcopenshell::serializers::document_serializer_plugin_metadata(const std::string& format, const std::string& schema_name) { plugin::metadata metadata; metadata.kind_ = plugin::kind::document_serializer; - metadata.id = document_serializer_plugin_prefix(format) + boost::to_lower_copy(schema_name); - metadata.schema = boost::to_upper_copy(schema_name); - metadata.format = boost::to_lower_copy(format); + metadata.id = document_serializer_plugin_prefix(format); + if (!schema_name.empty()) { + metadata.id += "." + boost::to_lower_copy(schema_name); + } + metadata.schema = document_serializer_schema_key(schema_name); + metadata.format = document_serializer_key(format); return metadata; } @@ -67,12 +152,30 @@ std::filesystem::path ifcopenshell::serializers::document_serializer_plugin_dire return plugin::module_directory(reinterpret_cast(&ifcopenshell::serializers::document_serializer_plugin_directory)); } -void ifcopenshell::serializers::load_document_serializer_plugins(XmlSerializerFactory::Factory& registry, const std::string& format) { - load_document_serializer_plugins_impl(registry, format); +void ifcopenshell::serializers::load_document_serializer_plugins(document_serializer_registry& registry) { + ifcopenshell::plugin::manager manager; + add_document_serializer_search_paths(manager); + + for (const auto& path : manager.discover(document_serializer_plugin_prefix())) { + ifcopenshell::plugin::module module; + try { + module = manager.load(path); + } catch (const std::exception& e) { + logger::error(e); + continue; + } + if (module.meta().kind_ != ifcopenshell::plugin::kind::document_serializer) { + continue; + } + + auto register_plugin = module.get_alias(document_serializer_plugin_registration_symbol()); + register_plugin(registry, module); + } } -#ifdef WITH_GLTF -void ifcopenshell::serializers::load_document_serializer_plugins(JsonSerializerFactory::Factory& registry, const std::string& format) { - load_document_serializer_plugins_impl(registry, format); +ifcopenshell::serializers::document_serializer_registry& ifcopenshell::serializers::document_serializer_registry_instance() { + static document_serializer_registry registry; + static std::once_flag once; + std::call_once(once, load_document_serializer_plugins, std::ref(registry)); + return registry; } -#endif diff --git a/src/serializers/document_serializer_plugin.h b/src/serializers/document_serializer_plugin.h index ce68247089..47d7795772 100644 --- a/src/serializers/document_serializer_plugin.h +++ b/src/serializers/document_serializer_plugin.h @@ -20,30 +20,67 @@ #ifndef IFCOPENSHELL_DOCUMENT_SERIALIZER_PLUGIN_H #define IFCOPENSHELL_DOCUMENT_SERIALIZER_PLUGIN_H -#include "XmlSerializer.h" +#include "../serializers/serializers_api.h" +#include "../ifcgeom/Serializer.h" +#include "../ifcparse/file.h" +#include "../plugin/plugin.h" -#ifdef WITH_GLTF -#include "JsonSerializer.h" -#endif +#include +#include #include +#include #include +#include namespace ifcopenshell { namespace serializers { -typedef void register_xml_document_serializer_plugin_fn(XmlSerializerFactory::Factory&, const ifcopenshell::plugin::module&); +struct SERIALIZERS_API document_serializer_info { + std::string format; + std::string schema_name; + bool supports_ifc_file = true; + bool supports_input_filename = false; + bool writes_final_output = false; +}; + +struct SERIALIZERS_API document_serializer_context { + ifcopenshell::file* file = nullptr; + std::string input_filename; + std::string output_filename; + std::string schema_name; + bool stream = false; + int dialect = 0; +}; + +class SERIALIZERS_API document_serializer_registry { +public: + typedef boost::function(const document_serializer_context&)> create_fn; + + void bind(const document_serializer_info& info, create_fn create, const ifcopenshell::plugin::module& module = ifcopenshell::plugin::module()); + const document_serializer_info* find(const std::string& format, const std::string& schema_name = std::string()) const; + boost::shared_ptr create(const std::string& format, const document_serializer_context& context) const; + std::vector serializers() const; + +private: + struct entry { + document_serializer_info info_; + create_fn create_; + ifcopenshell::plugin::module module_; + }; + + const entry* find_entry_(const std::string& format, const std::string& schema_name) const; + + std::map> entries_; +}; + +typedef void register_document_serializer_plugin_fn(document_serializer_registry&, const ifcopenshell::plugin::module&); SERIALIZERS_API const char* document_serializer_plugin_registration_symbol(); -SERIALIZERS_API ifcopenshell::plugin::metadata document_serializer_plugin_metadata(const std::string& format, const std::string& schema_name); +SERIALIZERS_API ifcopenshell::plugin::metadata document_serializer_plugin_metadata(const std::string& format, const std::string& schema_name = std::string()); SERIALIZERS_API std::filesystem::path document_serializer_plugin_directory(); -SERIALIZERS_API void load_document_serializer_plugins(XmlSerializerFactory::Factory& registry, const std::string& format); - -#ifdef WITH_GLTF -typedef void register_json_document_serializer_plugin_fn(JsonSerializerFactory::Factory&, const ifcopenshell::plugin::module&); - -SERIALIZERS_API void load_document_serializer_plugins(JsonSerializerFactory::Factory& registry, const std::string& format); -#endif +SERIALIZERS_API void load_document_serializer_plugins(document_serializer_registry& registry); +SERIALIZERS_API document_serializer_registry& document_serializer_registry_instance(); } } diff --git a/src/serializers/geometry_dae_plugin.cpp b/src/serializers/geometry_dae_plugin.cpp new file mode 100644 index 0000000000..1bbc9d3061 --- /dev/null +++ b/src/serializers/geometry_dae_plugin.cpp @@ -0,0 +1,62 @@ +/******************************************************************************** + * * + * 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 . * + * * + ********************************************************************************/ + +#ifdef WITH_OPENCOLLADA + +#include "ColladaSerializer.h" +#include "geometry_serializer_plugin.h" + +#include +#include + +namespace ifcopenshell { +namespace serializers { +namespace geometry_dae_plugin { + +plugin::abi_info plugin_abi() { + return plugin::host_abi(); +} + +plugin::metadata plugin_metadata() { + return geometry_serializer_plugin_metadata("dae"); +} + +boost::shared_ptr create_serializer(const geometry_serializer_context& context) { + return boost::make_shared(context.output_temp_filename, context.geometry_settings, context.serializer_settings); +} + +void register_plugin(geometry_serializer_registry& registry, const plugin::module& module) { + geometry_serializer_info info; + info.format = "dae"; + info.extensions = { ".dae" }; + info.supports_triangulation = true; + info.supports_user_element_hierarchy = true; + info.requires_ascii_temp_file = true; + registry.bind(info, create_serializer, geometry_serializer_registry::configure_fn(), module); +} + +} +} +} + +BOOST_DLL_ALIAS(ifcopenshell::serializers::geometry_dae_plugin::plugin_abi, ifcopenshell_plugin_abi_v1) +BOOST_DLL_ALIAS(ifcopenshell::serializers::geometry_dae_plugin::plugin_metadata, ifcopenshell_plugin_metadata_v1) +BOOST_DLL_ALIAS(ifcopenshell::serializers::geometry_dae_plugin::register_plugin, ifcopenshell_register_geometry_serializer_plugin_v1) + +#endif diff --git a/src/serializers/geometry_glb_plugin.cpp b/src/serializers/geometry_glb_plugin.cpp new file mode 100644 index 0000000000..d7bfb17f09 --- /dev/null +++ b/src/serializers/geometry_glb_plugin.cpp @@ -0,0 +1,61 @@ +/******************************************************************************** + * * + * 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 . * + * * + ********************************************************************************/ + +#ifdef WITH_GLTF + +#include "geometry_serializer_plugin.h" +#include "GltfSerializer.h" + +#include +#include + +namespace ifcopenshell { +namespace serializers { +namespace geometry_glb_plugin { + +plugin::abi_info plugin_abi() { + return plugin::host_abi(); +} + +plugin::metadata plugin_metadata() { + return geometry_serializer_plugin_metadata("glb"); +} + +boost::shared_ptr create_serializer(const geometry_serializer_context& context) { + return boost::make_shared(context.output_temp_filename, context.geometry_settings, context.serializer_settings); +} + +void register_plugin(geometry_serializer_registry& registry, const plugin::module& module) { + geometry_serializer_info info; + info.format = "glb"; + info.extensions = { ".glb" }; + info.supports_triangulation = true; + info.supports_user_element_hierarchy = true; + registry.bind(info, create_serializer, geometry_serializer_registry::configure_fn(), module); +} + +} +} +} + +BOOST_DLL_ALIAS(ifcopenshell::serializers::geometry_glb_plugin::plugin_abi, ifcopenshell_plugin_abi_v1) +BOOST_DLL_ALIAS(ifcopenshell::serializers::geometry_glb_plugin::plugin_metadata, ifcopenshell_plugin_metadata_v1) +BOOST_DLL_ALIAS(ifcopenshell::serializers::geometry_glb_plugin::register_plugin, ifcopenshell_register_geometry_serializer_plugin_v1) + +#endif diff --git a/src/serializers/geometry_hdf_plugin.cpp b/src/serializers/geometry_hdf_plugin.cpp new file mode 100644 index 0000000000..62037be3c1 --- /dev/null +++ b/src/serializers/geometry_hdf_plugin.cpp @@ -0,0 +1,66 @@ +/******************************************************************************** + * * + * 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 defined(WITH_HDF5) && defined(IFOPSH_WITH_OPENCASCADE) + +#include "geometry_serializer_plugin.h" +#include "HdfSerializer.h" + +#include +#include + +namespace ifcopenshell { +namespace serializers { +namespace geometry_hdf_plugin { + +plugin::abi_info plugin_abi() { + return plugin::host_abi(); +} + +plugin::metadata plugin_metadata() { + return geometry_serializer_plugin_metadata("hdf"); +} + +boost::shared_ptr create_serializer(const geometry_serializer_context& context) { + return boost::make_shared(context.output_temp_filename, context.geometry_settings, context.serializer_settings); +} + +void configure_serializer(geometry_serializer_context& context) { + context.geometry_settings.get().value = ifcopenshell::geometry::settings::NATIVE; +} + +void register_plugin(geometry_serializer_registry& registry, const plugin::module& module) { + geometry_serializer_info info; + info.format = "hdf"; + info.extensions = { ".h5" }; + info.kernel_ids = { "opencascade" }; + info.supports_triangulation = true; + info.supports_brep = true; + registry.bind(info, create_serializer, configure_serializer, module); +} + +} +} +} + +BOOST_DLL_ALIAS(ifcopenshell::serializers::geometry_hdf_plugin::plugin_abi, ifcopenshell_plugin_abi_v1) +BOOST_DLL_ALIAS(ifcopenshell::serializers::geometry_hdf_plugin::plugin_metadata, ifcopenshell_plugin_metadata_v1) +BOOST_DLL_ALIAS(ifcopenshell::serializers::geometry_hdf_plugin::register_plugin, ifcopenshell_register_geometry_serializer_plugin_v1) + +#endif diff --git a/src/serializers/geometry_igs_plugin.cpp b/src/serializers/geometry_igs_plugin.cpp new file mode 100644 index 0000000000..b0b17f3752 --- /dev/null +++ b/src/serializers/geometry_igs_plugin.cpp @@ -0,0 +1,76 @@ +/******************************************************************************** + * * + * 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 . * + * * + ********************************************************************************/ + +#ifdef IFOPSH_WITH_OPENCASCADE + +#include "geometry_serializer_plugin.h" +#include "IgesSerializer.h" + +#include +#include + +#include + +#if OCC_VERSION_HEX < 0x60900 +#include +#endif + +namespace ifcopenshell { +namespace serializers { +namespace geometry_igs_plugin { + +plugin::abi_info plugin_abi() { + return plugin::host_abi(); +} + +plugin::metadata plugin_metadata() { + return geometry_serializer_plugin_metadata("igs"); +} + +boost::shared_ptr create_serializer(const geometry_serializer_context& context) { +#if OCC_VERSION_HEX < 0x60900 + IGESControl_Controller::Init(); +#endif + return boost::make_shared(context.output_temp_filename, context.geometry_settings, context.serializer_settings); +} + +void configure_serializer(geometry_serializer_context& context) { + context.geometry_settings.get().value = true; + context.geometry_settings.get().value = ifcopenshell::geometry::settings::NATIVE; +} + +void register_plugin(geometry_serializer_registry& registry, const plugin::module& module) { + geometry_serializer_info info; + info.format = "igs"; + info.extensions = { ".igs" }; + info.kernel_ids = { "opencascade" }; + info.supports_brep = true; + info.requires_ascii_temp_file = true; + registry.bind(info, create_serializer, configure_serializer, module); +} + +} +} +} + +BOOST_DLL_ALIAS(ifcopenshell::serializers::geometry_igs_plugin::plugin_abi, ifcopenshell_plugin_abi_v1) +BOOST_DLL_ALIAS(ifcopenshell::serializers::geometry_igs_plugin::plugin_metadata, ifcopenshell_plugin_metadata_v1) +BOOST_DLL_ALIAS(ifcopenshell::serializers::geometry_igs_plugin::register_plugin, ifcopenshell_register_geometry_serializer_plugin_v1) + +#endif diff --git a/src/serializers/geometry_obj_plugin.cpp b/src/serializers/geometry_obj_plugin.cpp new file mode 100644 index 0000000000..b9169a8e1b --- /dev/null +++ b/src/serializers/geometry_obj_plugin.cpp @@ -0,0 +1,74 @@ +/******************************************************************************** + * * + * 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 . * + * * + ********************************************************************************/ + +#include "geometry_serializer_plugin.h" +#include "WavefrontObjSerializer.h" + +#include "../ifcparse/utils.h" + +#include +#include + +#include + +namespace { + +std::string obj_mtl_filename(const std::string& output_filename) { + std::filesystem::path path(ifcopenshell::path::from_utf8(output_filename)); + path.replace_extension(ifcopenshell::path::from_utf8(".mtl")); + return ifcopenshell::path::to_utf8(path.native()); +} + +} + +namespace ifcopenshell { +namespace serializers { +namespace geometry_obj_plugin { + +plugin::abi_info plugin_abi() { + return plugin::host_abi(); +} + +plugin::metadata plugin_metadata() { + return geometry_serializer_plugin_metadata("obj"); +} + +boost::shared_ptr create_serializer(const geometry_serializer_context& context) { + return boost::make_shared(context.output_temp_filename, obj_mtl_filename(context.output_filename), context.geometry_settings, context.serializer_settings); +} + +void configure_serializer(geometry_serializer_context& context) { + context.geometry_settings.get().value = true; +} + +void register_plugin(geometry_serializer_registry& registry, const plugin::module& module) { + geometry_serializer_info info; + info.format = "obj"; + info.extensions = { ".obj" }; + info.supports_triangulation = true; + registry.bind(info, create_serializer, configure_serializer, module); +} + +} +} +} + +BOOST_DLL_ALIAS(ifcopenshell::serializers::geometry_obj_plugin::plugin_abi, ifcopenshell_plugin_abi_v1) +BOOST_DLL_ALIAS(ifcopenshell::serializers::geometry_obj_plugin::plugin_metadata, ifcopenshell_plugin_metadata_v1) +BOOST_DLL_ALIAS(ifcopenshell::serializers::geometry_obj_plugin::register_plugin, ifcopenshell_register_geometry_serializer_plugin_v1) diff --git a/src/serializers/geometry_serializer_plugin.cpp b/src/serializers/geometry_serializer_plugin.cpp new file mode 100644 index 0000000000..5693d9707c --- /dev/null +++ b/src/serializers/geometry_serializer_plugin.cpp @@ -0,0 +1,160 @@ +/******************************************************************************** + * * + * 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 . * + * * + ********************************************************************************/ + +#include "geometry_serializer_plugin.h" + +#include + +#include +#include + +namespace { + +std::string geometry_serializer_plugin_prefix(const std::string& format = std::string()) { + const auto format_lower = boost::to_lower_copy(format); + return format_lower.empty() ? "geometry." : "geometry." + format_lower; +} + +std::string geometry_serializer_key(const std::string& extension) { + if (extension.empty()) { + return extension; + } + + auto key = boost::to_lower_copy(extension); + if (key.front() != '.') { + key.insert(key.begin(), '.'); + } + return key; +} + +void add_geometry_serializer_search_paths(ifcopenshell::plugin::manager& manager) { + const auto directory = ifcopenshell::serializers::geometry_serializer_plugin_directory(); + manager.add_search_path(directory); + + const auto sibling_directory = directory.parent_path().parent_path() / "serializers" / directory.filename(); + if (sibling_directory != directory && std::filesystem::exists(sibling_directory)) { + manager.add_search_path(sibling_directory); + } +} + +} + +void ifcopenshell::serializers::geometry_serializer_registry::bind(const geometry_serializer_info& info, create_fn create, configure_fn configure, const plugin::module& module) { + entry entry; + entry.info_ = info; + entry.info_.format = boost::to_lower_copy(entry.info_.format); + entry.create_ = create; + entry.configure_ = configure; + entry.module_ = module.meta().id.empty() ? plugin::module(geometry_serializer_plugin_metadata(entry.info_.format)) : module; + + if (entry.info_.extensions.empty() && !entry.info_.format.empty()) { + entry.info_.extensions.push_back("." + entry.info_.format); + } + + for (auto& extension : entry.info_.extensions) { + extension = geometry_serializer_key(extension); + entries_[extension] = entry; + } +} + +bool ifcopenshell::serializers::geometry_serializer_registry::has(const std::string& extension) const { + return entries_.find(geometry_serializer_key(extension)) != entries_.end(); +} + +const ifcopenshell::serializers::geometry_serializer_info* ifcopenshell::serializers::geometry_serializer_registry::find(const std::string& extension) const { + const auto iter = entries_.find(geometry_serializer_key(extension)); + if (iter == entries_.end()) { + return nullptr; + } + return &iter->second.info_; +} + +void ifcopenshell::serializers::geometry_serializer_registry::configure(const std::string& extension, geometry_serializer_context& context) const { + const auto iter = entries_.find(geometry_serializer_key(extension)); + if (iter == entries_.end()) { + throw ifcopenshell::exception("No geometry serializer registered for " + extension); + } + if (iter->second.configure_) { + iter->second.configure_(context); + } +} + +boost::shared_ptr ifcopenshell::serializers::geometry_serializer_registry::create(const std::string& extension, const geometry_serializer_context& context) const { + const auto iter = entries_.find(geometry_serializer_key(extension)); + if (iter == entries_.end()) { + throw ifcopenshell::exception("No geometry serializer registered for " + extension); + } + return iter->second.create_(context); +} + +std::vector ifcopenshell::serializers::geometry_serializer_registry::serializers() const { + std::vector result; + std::set seen_formats; + for (const auto& pair : entries_) { + if (!seen_formats.insert(pair.second.info_.format).second) { + continue; + } + result.push_back(pair.second.info_); + } + return result; +} + +const char* ifcopenshell::serializers::geometry_serializer_plugin_registration_symbol() { + return "ifcopenshell_register_geometry_serializer_plugin_v1"; +} + +ifcopenshell::plugin::metadata ifcopenshell::serializers::geometry_serializer_plugin_metadata(const std::string& format) { + plugin::metadata metadata; + metadata.kind_ = plugin::kind::geometry_serializer; + metadata.id = geometry_serializer_plugin_prefix(format); + metadata.format = boost::to_lower_copy(format); + return metadata; +} + +std::filesystem::path ifcopenshell::serializers::geometry_serializer_plugin_directory() { + return plugin::module_directory(reinterpret_cast(&ifcopenshell::serializers::geometry_serializer_plugin_directory)); +} + +void ifcopenshell::serializers::load_geometry_serializer_plugins(geometry_serializer_registry& registry) { + ifcopenshell::plugin::manager manager; + add_geometry_serializer_search_paths(manager); + + for (const auto& path : manager.discover(geometry_serializer_plugin_prefix())) { + ifcopenshell::plugin::module module; + try { + module = manager.load(path); + } catch (const std::exception& e) { + logger::error(e); + continue; + } + if (module.meta().kind_ != ifcopenshell::plugin::kind::geometry_serializer) { + continue; + } + + auto register_plugin = module.get_alias(geometry_serializer_plugin_registration_symbol()); + register_plugin(registry, module); + } +} + +ifcopenshell::serializers::geometry_serializer_registry& ifcopenshell::serializers::geometry_serializer_registry_instance() { + static geometry_serializer_registry registry; + static std::once_flag once; + std::call_once(once, load_geometry_serializer_plugins, std::ref(registry)); + return registry; +} diff --git a/src/serializers/geometry_serializer_plugin.h b/src/serializers/geometry_serializer_plugin.h new file mode 100644 index 0000000000..7bb3a69324 --- /dev/null +++ b/src/serializers/geometry_serializer_plugin.h @@ -0,0 +1,91 @@ +/******************************************************************************** + * * + * 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 . * + * * + ********************************************************************************/ + +#ifndef IFCOPENSHELL_GEOMETRY_SERIALIZER_PLUGIN_H +#define IFCOPENSHELL_GEOMETRY_SERIALIZER_PLUGIN_H + +#include "../serializers/serializers_api.h" +#include "../ifcgeom/GeometrySerializer.h" +#include "../plugin/plugin.h" + +#include +#include + +#include +#include +#include +#include + +namespace ifcopenshell { +namespace serializers { + +struct SERIALIZERS_API geometry_serializer_info { + std::string format; + std::vector extensions; + std::vector kernel_ids; + bool supports_triangulation = false; + bool supports_brep = false; + bool supports_user_element_hierarchy = false; + bool bypass_properties = true; + bool requires_ascii_temp_file = false; + bool writes_final_output = false; +}; + +struct SERIALIZERS_API geometry_serializer_context { + std::string output_filename; + std::string output_temp_filename; + ifcopenshell::geometry::Settings& geometry_settings; + const ifcopenshell::geometry::SerializerSettings& serializer_settings; +}; + +class SERIALIZERS_API geometry_serializer_registry { +public: + typedef boost::function(const geometry_serializer_context&)> create_fn; + typedef boost::function configure_fn; + + void bind(const geometry_serializer_info& info, create_fn create, configure_fn configure = configure_fn(), const ifcopenshell::plugin::module& module = ifcopenshell::plugin::module()); + bool has(const std::string& extension) const; + const geometry_serializer_info* find(const std::string& extension) const; + void configure(const std::string& extension, geometry_serializer_context& context) const; + boost::shared_ptr create(const std::string& extension, const geometry_serializer_context& context) const; + std::vector serializers() const; + +private: + struct entry { + geometry_serializer_info info_; + create_fn create_; + configure_fn configure_; + ifcopenshell::plugin::module module_; + }; + + std::map entries_; +}; + +typedef void register_geometry_serializer_plugin_fn(geometry_serializer_registry&, const ifcopenshell::plugin::module&); + +SERIALIZERS_API const char* geometry_serializer_plugin_registration_symbol(); +SERIALIZERS_API ifcopenshell::plugin::metadata geometry_serializer_plugin_metadata(const std::string& format); +SERIALIZERS_API std::filesystem::path geometry_serializer_plugin_directory(); +SERIALIZERS_API void load_geometry_serializer_plugins(geometry_serializer_registry& registry); +SERIALIZERS_API geometry_serializer_registry& geometry_serializer_registry_instance(); + +} +} + +#endif diff --git a/src/serializers/geometry_stp_plugin.cpp b/src/serializers/geometry_stp_plugin.cpp new file mode 100644 index 0000000000..416de34e02 --- /dev/null +++ b/src/serializers/geometry_stp_plugin.cpp @@ -0,0 +1,67 @@ +/******************************************************************************** + * * + * 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 . * + * * + ********************************************************************************/ + +#ifdef IFOPSH_WITH_OPENCASCADE + +#include "geometry_serializer_plugin.h" +#include "StepSerializer.h" + +#include +#include + +namespace ifcopenshell { +namespace serializers { +namespace geometry_stp_plugin { + +plugin::abi_info plugin_abi() { + return plugin::host_abi(); +} + +plugin::metadata plugin_metadata() { + return geometry_serializer_plugin_metadata("stp"); +} + +boost::shared_ptr create_serializer(const geometry_serializer_context& context) { + return boost::make_shared(context.output_temp_filename, context.geometry_settings, context.serializer_settings); +} + +void configure_serializer(geometry_serializer_context& context) { + context.geometry_settings.get().value = true; + context.geometry_settings.get().value = ifcopenshell::geometry::settings::NATIVE; +} + +void register_plugin(geometry_serializer_registry& registry, const plugin::module& module) { + geometry_serializer_info info; + info.format = "stp"; + info.extensions = { ".stp" }; + info.kernel_ids = { "opencascade" }; + info.supports_brep = true; + info.requires_ascii_temp_file = true; + registry.bind(info, create_serializer, configure_serializer, module); +} + +} +} +} + +BOOST_DLL_ALIAS(ifcopenshell::serializers::geometry_stp_plugin::plugin_abi, ifcopenshell_plugin_abi_v1) +BOOST_DLL_ALIAS(ifcopenshell::serializers::geometry_stp_plugin::plugin_metadata, ifcopenshell_plugin_metadata_v1) +BOOST_DLL_ALIAS(ifcopenshell::serializers::geometry_stp_plugin::register_plugin, ifcopenshell_register_geometry_serializer_plugin_v1) + +#endif diff --git a/src/serializers/geometry_svg_plugin.cpp b/src/serializers/geometry_svg_plugin.cpp new file mode 100644 index 0000000000..0176811479 --- /dev/null +++ b/src/serializers/geometry_svg_plugin.cpp @@ -0,0 +1,67 @@ +/******************************************************************************** + * * + * 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 . * + * * + ********************************************************************************/ + +#ifdef IFOPSH_WITH_OPENCASCADE + +#include "geometry_serializer_plugin.h" +#include "SvgSerializer.h" + +#include +#include + +namespace ifcopenshell { +namespace serializers { +namespace geometry_svg_plugin { + +plugin::abi_info plugin_abi() { + return plugin::host_abi(); +} + +plugin::metadata plugin_metadata() { + return geometry_serializer_plugin_metadata("svg"); +} + +boost::shared_ptr create_serializer(const geometry_serializer_context& context) { + return boost::make_shared(context.output_temp_filename, context.geometry_settings, context.serializer_settings); +} + +void configure_serializer(geometry_serializer_context& context) { + context.geometry_settings.get().value = true; + context.geometry_settings.get().value = ifcopenshell::geometry::settings::NATIVE; +} + +void register_plugin(geometry_serializer_registry& registry, const plugin::module& module) { + geometry_serializer_info info; + info.format = "svg"; + info.extensions = { ".svg" }; + info.kernel_ids = { "opencascade" }; + info.supports_brep = true; + info.bypass_properties = false; + registry.bind(info, create_serializer, configure_serializer, module); +} + +} +} +} + +BOOST_DLL_ALIAS(ifcopenshell::serializers::geometry_svg_plugin::plugin_abi, ifcopenshell_plugin_abi_v1) +BOOST_DLL_ALIAS(ifcopenshell::serializers::geometry_svg_plugin::plugin_metadata, ifcopenshell_plugin_metadata_v1) +BOOST_DLL_ALIAS(ifcopenshell::serializers::geometry_svg_plugin::register_plugin, ifcopenshell_register_geometry_serializer_plugin_v1) + +#endif diff --git a/src/serializers/geometry_ttl_plugin.cpp b/src/serializers/geometry_ttl_plugin.cpp new file mode 100644 index 0000000000..ced5a3a6c5 --- /dev/null +++ b/src/serializers/geometry_ttl_plugin.cpp @@ -0,0 +1,64 @@ +/******************************************************************************** + * * + * 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 . * + * * + ********************************************************************************/ + +#include "geometry_serializer_plugin.h" +#include "TtlWktSerializer.h" + +#include +#include + +namespace ifcopenshell { +namespace serializers { +namespace geometry_ttl_plugin { + +plugin::abi_info plugin_abi() { + return plugin::host_abi(); +} + +plugin::metadata plugin_metadata() { + return geometry_serializer_plugin_metadata("ttl"); +} + +boost::shared_ptr create_serializer(const geometry_serializer_context& context) { + return boost::make_shared(context.output_temp_filename, context.geometry_settings, context.serializer_settings); +} + +void configure_serializer(geometry_serializer_context& context) { + context.geometry_settings.get().value = ifcopenshell::geometry::settings::POLYHEDRON_WITH_HOLES; + if (context.serializer_settings.get().get()) { + context.geometry_settings.get().value = ifcopenshell::geometry::settings::NATIVE; + } +} + +void register_plugin(geometry_serializer_registry& registry, const plugin::module& module) { + geometry_serializer_info info; + info.format = "ttl"; + info.extensions = { ".ttl" }; + info.supports_triangulation = true; + info.supports_brep = true; + registry.bind(info, create_serializer, configure_serializer, module); +} + +} +} +} + +BOOST_DLL_ALIAS(ifcopenshell::serializers::geometry_ttl_plugin::plugin_abi, ifcopenshell_plugin_abi_v1) +BOOST_DLL_ALIAS(ifcopenshell::serializers::geometry_ttl_plugin::plugin_metadata, ifcopenshell_plugin_metadata_v1) +BOOST_DLL_ALIAS(ifcopenshell::serializers::geometry_ttl_plugin::register_plugin, ifcopenshell_register_geometry_serializer_plugin_v1) diff --git a/src/serializers/geometry_usd_plugin.cpp b/src/serializers/geometry_usd_plugin.cpp new file mode 100644 index 0000000000..cba3abb9da --- /dev/null +++ b/src/serializers/geometry_usd_plugin.cpp @@ -0,0 +1,62 @@ +/******************************************************************************** + * * + * 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 . * + * * + ********************************************************************************/ + +#ifdef WITH_USD + +#include "geometry_serializer_plugin.h" +#include "USDSerializer.h" + +#include +#include + +namespace ifcopenshell { +namespace serializers { +namespace geometry_usd_plugin { + +plugin::abi_info plugin_abi() { + return plugin::host_abi(); +} + +plugin::metadata plugin_metadata() { + return geometry_serializer_plugin_metadata("usd"); +} + +boost::shared_ptr create_serializer(const geometry_serializer_context& context) { + return boost::make_shared(context.output_filename, context.geometry_settings, context.serializer_settings); +} + +void register_plugin(geometry_serializer_registry& registry, const plugin::module& module) { + geometry_serializer_info info; + info.format = "usd"; + info.extensions = { ".usd", ".usda", ".usdc" }; + info.supports_triangulation = true; + info.supports_user_element_hierarchy = true; + info.writes_final_output = true; + registry.bind(info, create_serializer, geometry_serializer_registry::configure_fn(), module); +} + +} +} +} + +BOOST_DLL_ALIAS(ifcopenshell::serializers::geometry_usd_plugin::plugin_abi, ifcopenshell_plugin_abi_v1) +BOOST_DLL_ALIAS(ifcopenshell::serializers::geometry_usd_plugin::plugin_metadata, ifcopenshell_plugin_metadata_v1) +BOOST_DLL_ALIAS(ifcopenshell::serializers::geometry_usd_plugin::register_plugin, ifcopenshell_register_geometry_serializer_plugin_v1) + +#endif diff --git a/src/serializers/schema_dependent/CMakeLists.txt b/src/serializers/schema_dependent/CMakeLists.txt index ef7010333b..f65c1e9fdb 100644 --- a/src/serializers/schema_dependent/CMakeLists.txt +++ b/src/serializers/schema_dependent/CMakeLists.txt @@ -1,26 +1,10 @@ -set(document_serializer_plugin_runtime_dir "${CMAKE_BINARY_DIR}/serializers/$") - foreach(schema ${SCHEMA_VERSIONS}) - add_library(document_serializer_xml_ifc${schema} SHARED XmlSerializer.cpp xml_plugin.cpp) - target_link_libraries(document_serializer_xml_ifc${schema} PRIVATE plugin Serializers IfcGeom IfcParse) - set_target_properties(document_serializer_xml_ifc${schema} PROPERTIES - COMPILE_FLAGS "-DIfcSchema=Ifc${schema}" - OUTPUT_NAME "document.xml.ifc${schema}" - RUNTIME_OUTPUT_DIRECTORY "${document_serializer_plugin_runtime_dir}" - LIBRARY_OUTPUT_DIRECTORY "${document_serializer_plugin_runtime_dir}" - ) - list(APPEND document_serializer_libraries document_serializer_xml_ifc${schema}) + add_document_serializer_plugin(document_serializer_xml_ifc${schema} "document.xml.ifc${schema}" SOURCES XmlSerializer.cpp xml_plugin.cpp) + target_compile_definitions(document_serializer_xml_ifc${schema} PRIVATE IfcSchema=Ifc${schema}) - if(WITH_GLTF) - add_library(document_serializer_json_ifc${schema} SHARED JsonSerializer.cpp json_plugin.cpp) - target_link_libraries(document_serializer_json_ifc${schema} PRIVATE plugin Serializers IfcGeom IfcParse) - set_target_properties(document_serializer_json_ifc${schema} PROPERTIES - COMPILE_FLAGS "-DIfcSchema=Ifc${schema}" - OUTPUT_NAME "document.json.ifc${schema}" - RUNTIME_OUTPUT_DIRECTORY "${document_serializer_plugin_runtime_dir}" - LIBRARY_OUTPUT_DIRECTORY "${document_serializer_plugin_runtime_dir}" - ) - list(APPEND document_serializer_libraries document_serializer_json_ifc${schema}) + if(GLTF_SUPPORT) + add_document_serializer_plugin(document_serializer_json_ifc${schema} "document.json.ifc${schema}" SOURCES JsonSerializer.cpp json_plugin.cpp) + target_compile_definitions(document_serializer_json_ifc${schema} PRIVATE IfcSchema=Ifc${schema}) endif() endforeach() diff --git a/src/serializers/schema_dependent/json_plugin.cpp b/src/serializers/schema_dependent/json_plugin.cpp index ee3f9b4e4b..f4742d732e 100644 --- a/src/serializers/schema_dependent/json_plugin.cpp +++ b/src/serializers/schema_dependent/json_plugin.cpp @@ -25,16 +25,16 @@ #include "../../ifcparse/macros.h" #include +#include namespace { -struct factory_t { - JsonSerializer* operator()(ifcopenshell::file* file, const std::string& json_filename, JsonSerializer::Dialect dialect) const { - auto* serializer = new POSTFIX_SCHEMA(JsonSerializer)(file, json_filename, dialect); - serializer->setFile(file); - return serializer; - } -}; +boost::shared_ptr create_serializer(const ifcopenshell::serializers::document_serializer_context& context) { + return boost::make_shared( + context.file, + context.output_filename, + static_cast(context.dialect)); +} } @@ -50,9 +50,11 @@ plugin::metadata plugin_metadata() { return document_serializer_plugin_metadata("json", STRINGIFY(IfcSchema)); } -void register_plugin(JsonSerializerFactory::Factory& registry, const plugin::module& module) { - factory_t factory; - registry.bind(STRINGIFY(IfcSchema), factory, module); +void register_plugin(document_serializer_registry& registry, const plugin::module& module) { + document_serializer_info info; + info.format = "json"; + info.schema_name = STRINGIFY(IfcSchema); + registry.bind(info, create_serializer, module); } } diff --git a/src/serializers/schema_dependent/xml_plugin.cpp b/src/serializers/schema_dependent/xml_plugin.cpp index 43f6289bac..d4509f71d9 100644 --- a/src/serializers/schema_dependent/xml_plugin.cpp +++ b/src/serializers/schema_dependent/xml_plugin.cpp @@ -23,16 +23,13 @@ #include "../../ifcparse/macros.h" #include +#include namespace { -struct factory_t { - XmlSerializer* operator()(ifcopenshell::file* file, const std::string& xml_filename) const { - auto* serializer = new POSTFIX_SCHEMA(XmlSerializer)(file, xml_filename); - serializer->setFile(file); - return serializer; - } -}; +boost::shared_ptr create_serializer(const ifcopenshell::serializers::document_serializer_context& context) { + return boost::make_shared(context.file, context.output_filename); +} } @@ -48,9 +45,11 @@ plugin::metadata plugin_metadata() { return document_serializer_plugin_metadata("xml", STRINGIFY(IfcSchema)); } -void register_plugin(XmlSerializerFactory::Factory& registry, const plugin::module& module) { - factory_t factory; - registry.bind(STRINGIFY(IfcSchema), factory, module); +void register_plugin(document_serializer_registry& registry, const plugin::module& module) { + document_serializer_info info; + info.format = "xml"; + info.schema_name = STRINGIFY(IfcSchema); + registry.bind(info, create_serializer, module); } }