Explorar o código

Introduce VRML format (.wrl and .x3dv) 3D model support (#5857)

- Introduce VRML format (.wrl and .x3dv) 3D model support
- Add samples
---------

Co-authored-by: Steve M <[email protected]>
Co-authored-by: Kim Kulling <[email protected]>
Steve M hai 6 meses
pai
achega
69558d8889

+ 2 - 0
.gitignore

@@ -122,5 +122,7 @@ tools/assimp_qt_viewer/ui_mainwindow.h
 generated/*
 
 # 3rd party cloned repos/tarballs etc
+# meshlab repo, automatically cloned via CMake (to gain 2 source files for VRML file format conversion)
+contrib/meshlab/autoclone
 # tinyusdz repo, automatically cloned via CMake
 contrib/tinyusdz/autoclone

+ 35 - 13
CMakeLists.txt

@@ -40,12 +40,47 @@ SET(CMAKE_POLICY_DEFAULT_CMP0092 NEW)
 
 CMAKE_MINIMUM_REQUIRED( VERSION 3.22 )
 
+#================================================================================#
+#                    Model formats not enabled by default
+#
+#    3rd party projects may not adhere to strict standards enforced by assimp,
+#    in which case those formats must be opt-in; otherwise the 3rd party code
+#    would fail assimp CI checks
+#================================================================================#
+# M3D format import support (assimp integration no longer supported by M3D format author)
+# User may override these in their CMake script to provide M3D import/export support
+# (M3D importer/exporter was disabled for assimp release 5.1 or later)
+option(ASSIMP_BUILD_M3D_IMPORTER "Enable M3D file import" off)
+option(ASSIMP_BUILD_M3D_EXPORTER "Enable M3D file export" off)
+
 # Experimental USD importer: disabled, need to opt-in
 # Note: assimp github PR automatic checks will fail the PR due to compiler warnings in
 # the external, 3rd party tinyusdz code which isn't technically part of the PR since it's
 # auto-cloned during build; so MUST disable the feature or the PR will be rejected
 option(ASSIMP_BUILD_USD_IMPORTER "Enable USD file import" off)
 option(ASSIMP_BUILD_USD_VERBOSE_LOGS "Enable verbose USD import debug logging" off)
+
+# VRML (.wrl/.x3dv) file import support by leveraging X3D importer and 3rd party file
+# format converter to convert .wrl/.x3dv files to X3D-compatible .xml
+# (Need to make this opt-in because 3rd party code triggers lots of CI code quality warnings)
+option(ASSIMP_BUILD_VRML_IMPORTER "Enable VRML (.wrl/.x3dv) file import" off)
+#--------------------------------------------------------------------------------#
+#                  Internal impl for optional model formats
+#--------------------------------------------------------------------------------#
+# Internal/private M3D logic
+if (NOT ASSIMP_BUILD_M3D_IMPORTER)
+    ADD_DEFINITIONS( -DASSIMP_BUILD_NO_M3D_IMPORTER)
+endif () # if (not ASSIMP_BUILD_M3D_IMPORTER)
+if (NOT ASSIMP_BUILD_M3D_EXPORTER)
+    ADD_DEFINITIONS( -DASSIMP_BUILD_NO_M3D_EXPORTER)
+endif () # if (not ASSIMP_BUILD_M3D_EXPORTER)
+
+# Internal/private VRML logic
+if (NOT ASSIMP_BUILD_VRML_IMPORTER)
+    ADD_DEFINITIONS( -DASSIMP_BUILD_NO_VRML_IMPORTER)
+endif () # if (not ASSIMP_BUILD_VRML_IMPORTER)
+#================================================================================#
+
 option(ASSIMP_BUILD_USE_CCACHE "Use ccache to speed up compilation." on)
 
 if(ASSIMP_BUILD_USE_CCACHE)
@@ -56,19 +91,6 @@ if(ASSIMP_BUILD_USE_CCACHE)
   endif()
 endif()
 
-# User may override these in their CMake script to provide M3D import/export support
-# (M3D importer/exporter was disabled for assimp release 5.1 or later)
-option(ASSIMP_BUILD_M3D_IMPORTER "Enable M3D file import" off)
-option(ASSIMP_BUILD_M3D_EXPORTER "Enable M3D file export" off)
-
-# Internal/private M3D logic
-if (NOT ASSIMP_BUILD_M3D_IMPORTER)
-    ADD_DEFINITIONS( -DASSIMP_BUILD_NO_M3D_IMPORTER)
-endif () # if (not ASSIMP_BUILD_M3D_IMPORTER)
-if (NOT ASSIMP_BUILD_M3D_EXPORTER)
-    ADD_DEFINITIONS( -DASSIMP_BUILD_NO_M3D_EXPORTER)
-endif () # if (not ASSIMP_BUILD_M3D_EXPORTER)
-
 # Toggles the use of the hunter package manager
 option(ASSIMP_HUNTER_ENABLED "Enable Hunter package manager support" OFF)
 

+ 21 - 0
code/AssetLib/VRML/README.md

@@ -0,0 +1,21 @@
+# WRL/X3DV to X3D file format converter
+
+## VRML and X3D 3D model formats background
+"VRML" 3D model files use either `VRML97` (`.wrl`) or "Classic VRML" (`.x3dv`)
+file formats.
+
+The X3D model specification was introduced after these formats, as a superset of both WRL and X3DV.
+While X3D can understand the _content_ of WRL/X3DV files, it can't directly parse them because
+X3D uses `.xml` files, rather than `VRML97` or "Classic VRML" format.
+
+But, if a converter is available to migrate just the file format (preserving the content), so that
+the `.wrl`/`.x3dv` files can be converted to an X3D-compatible `.xml` file, then the X3D importer
+will be able to load the resulting model file.
+
+## How this code is used
+The sole purpose of `Parser`/`Scanner` (adopted from the `meshlab` project) is to take a 
+`VRML97` (`.wrl`) or "Classic VRML" (`.x3dv`) file as input, and convert to an X3D `.xml` file.
+That's it.
+
+By passing the converted in-memory `.xml` file content to the `X3DImporter`, the `.wrl` or `x3dv`
+model can be loaded via assimp.

+ 103 - 0
code/AssetLib/VRML/VrmlConverter.cpp

@@ -0,0 +1,103 @@
+/*
+Open Asset Import Library (assimp)
+----------------------------------------------------------------------
+
+Copyright (c) 2006-2024, assimp team
+
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the
+following conditions are met:
+
+* Redistributions of source code must retain the above
+copyright notice, this list of conditions and the
+following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the
+following disclaimer in the documentation and/or other
+materials provided with the distribution.
+
+* Neither the name of the assimp team, nor the names of its
+contributors may be used to endorse or promote products
+derived from this software without specific prior
+written permission of the assimp team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+            LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+----------------------------------------------------------------------
+*/
+/// \file   VrmlImporter.cpp
+/// \brief  Convert VRML-formatted (.wrl, .x3dv) files to X3D .xml format
+/// \date   2024
+/// \author tellypresence
+
+#ifndef ASSIMP_BUILD_NO_X3D_IMPORTER
+
+#include <memory> // std::unique_ptr
+#include "VrmlConverter.hpp"
+
+namespace Assimp {
+
+bool isFileWrlVrml97Ext(const std::string &pFile) {
+    size_t pos = pFile.find_last_of('.');
+    if (pos == std::string::npos) {
+        return false;
+    }
+    std::string ext = pFile.substr(pos + 1);
+    if (ext.size() != 3) {
+        return false;
+    }
+    return (ext[0] == 'w' || ext[0] == 'W') && (ext[1] == 'r' || ext[1] == 'R') && (ext[2] == 'l' || ext[2] == 'L');
+}
+
+bool isFileX3dvClassicVrmlExt(const std::string &pFile) {
+    size_t pos = pFile.find_last_of('.');
+    if (pos == std::string::npos) {
+        return false;
+    }
+    std::string ext = pFile.substr(pos + 1);
+    if (ext.size() != 4) {
+        return false;
+    }
+    return (ext[0] == 'x' || ext[0] == 'X') && (ext[1] == '3') && (ext[2] == 'd' || ext[2] == 'D') && (ext[3] == 'v' || ext[3] == 'V');
+}
+
+#if !defined(ASSIMP_BUILD_NO_VRML_IMPORTER)
+static VrmlTranslator::Scanner createScanner(const std::string &pFile) {
+    std::unique_ptr<wchar_t[]> wide_stringPtr{ new wchar_t[ pFile.length() + 1 ] };
+    std::copy(pFile.begin(), pFile.end(), wide_stringPtr.get());
+    wide_stringPtr[ pFile.length() ] = 0;
+
+    return VrmlTranslator::Scanner(wide_stringPtr.get());
+} // wide_stringPtr auto-deleted when leaving scope
+#endif // #if !defined(ASSIMP_BUILD_NO_VRML_IMPORTER)
+
+std::stringstream ConvertVrmlFileToX3dXmlFile(const std::string &pFile) {
+    std::stringstream ss;
+    if (isFileWrlVrml97Ext(pFile) || isFileX3dvClassicVrmlExt(pFile)) {
+#if !defined(ASSIMP_BUILD_NO_VRML_IMPORTER)
+        VrmlTranslator::Scanner scanner = createScanner(pFile);
+        VrmlTranslator::Parser parser(&scanner);
+        parser.Parse();
+        ss.str("");
+        parser.doc_.save(ss);
+#endif // #if !defined(ASSIMP_BUILD_NO_VRML_IMPORTER)
+    }
+    return ss;
+}
+
+} // namespace Assimp
+
+#endif // !ASSIMP_BUILD_NO_X3D_IMPORTER

+ 57 - 0
code/AssetLib/VRML/VrmlConverter.hpp

@@ -0,0 +1,57 @@
+/*
+Open Asset Import Library (assimp)
+----------------------------------------------------------------------
+
+Copyright (c) 2006-2024, assimp team
+
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the
+following conditions are met:
+
+* Redistributions of source code must retain the above
+copyright notice, this list of conditions and the
+following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the
+following disclaimer in the documentation and/or other
+materials provided with the distribution.
+
+* Neither the name of the assimp team, nor the names of its
+contributors may be used to endorse or promote products
+derived from this software without specific prior
+written permission of the assimp team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+----------------------------------------------------------------------
+*/
+
+#pragma once
+
+#include <sstream>
+#include <string>
+
+#if !defined(ASSIMP_BUILD_NO_VRML_IMPORTER)
+#include "contrib/meshlab/autoclone/meshlab_repo-src/src/meshlabplugins/io_x3d/vrml/Parser.h"
+#endif // #if !defined(ASSIMP_BUILD_NO_VRML_IMPORTER)
+
+namespace Assimp {
+
+bool isFileWrlVrml97Ext(const std::string &pFile);
+bool isFileX3dvClassicVrmlExt(const std::string &pFile);
+
+std::stringstream ConvertVrmlFileToX3dXmlFile(const std::string &pFile);
+} // namespace Assimp

+ 37 - 8
code/AssetLib/X3D/X3DImporter.cpp

@@ -45,6 +45,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 #ifndef ASSIMP_BUILD_NO_X3D_IMPORTER
 
+#include "AssetLib/VRML/VrmlConverter.hpp"
 #include "X3DImporter.hpp"
 #include "X3DImporter_Macro.hpp"
 
@@ -54,11 +55,19 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include <iterator>
 #include <memory>
 
+#if defined(ASSIMP_BUILD_NO_VRML_IMPORTER)
+#define X3D_FORMATS_DESCR_STR "Extensible 3D(X3D, X3DB) Importer"
+#define X3D_FORMATS_EXTENSIONS_STR "x3d x3db"
+#else
+#define X3D_FORMATS_DESCR_STR "VRML(WRL, X3DV) and Extensible 3D(X3D, X3DB) Importer"
+#define X3D_FORMATS_EXTENSIONS_STR "wrl x3d x3db x3dv"
+#endif // #if defined(ASSIMP_BUILD_NO_VRML_IMPORTER)
+
 namespace Assimp {
 
 /// Constant which holds the importer description
 const aiImporterDesc X3DImporter::Description = {
-    "Extensible 3D(X3D) Importer",
+    X3D_FORMATS_DESCR_STR,
     "smalcom",
     "",
     "See documentation in source code. Chapter: Limitations.",
@@ -67,7 +76,7 @@ const aiImporterDesc X3DImporter::Description = {
     0,
     0,
     0,
-    "x3d x3db"
+    X3D_FORMATS_EXTENSIONS_STR
 };
 
 bool X3DImporter::isNodeEmpty(XmlNode &node) {
@@ -215,7 +224,19 @@ void X3DImporter::ParseFile(const std::string &file, IOSystem *pIOHandler) {
     if (!theParser.parse(fileStream.get())) {
         return;
     }
+    ParseFile(theParser);
+}
+
+void X3DImporter::ParseFile(std::istream &myIstream) {
+    XmlParser theParser;
+    if (!theParser.parse(myIstream)) {
+        LogInfo("ParseFile(): ERROR: failed to convert VRML istream to xml");
+        return;
+    }
+    ParseFile(theParser);
+}
 
+void X3DImporter::ParseFile(XmlParser &theParser) {
     XmlNode *node = theParser.findNode("X3D");
     if (nullptr == node) {
         return;
@@ -246,9 +267,13 @@ void X3DImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSy
     mpIOHandler = pIOHandler;
 
     Clear();
-    std::shared_ptr<IOStream> stream(pIOHandler->Open(pFile, "rb"));
-    if (!stream) {
-        throw DeadlyImportError("Could not open file for reading");
+    std::stringstream ss = ConvertVrmlFileToX3dXmlFile(pFile);
+    const bool isReadFromMem{ ss.str().length() > 0 };
+    if (!isReadFromMem) {
+        std::shared_ptr<IOStream> stream(pIOHandler->Open(pFile, "rb"));
+        if (!stream) {
+            throw DeadlyImportError("Could not open file for reading");
+        }
     }
     std::string::size_type slashPos = pFile.find_last_of("\\/");
 
@@ -257,9 +282,13 @@ void X3DImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSy
     pScene->mRootNode->mParent = nullptr;
     pScene->mFlags |= AI_SCENE_FLAGS_ALLOW_SHARED;
 
-    pIOHandler->PushDirectory(slashPos == std::string::npos ? std::string() : pFile.substr(0, slashPos + 1));
-    ParseFile(pFile, pIOHandler);
-    pIOHandler->PopDirectory();
+    if (isReadFromMem) {
+        ParseFile(ss);
+    } else {
+        pIOHandler->PushDirectory(slashPos == std::string::npos ? std::string() : pFile.substr(0, slashPos + 1));
+        ParseFile(pFile, pIOHandler);
+        pIOHandler->PopDirectory();
+    }
 
     //search for root node element
 

+ 3 - 1
code/AssetLib/X3D/X3DImporter.hpp

@@ -275,7 +275,9 @@ public:
     /// Also exception can be thrown if trouble will found.
     /// \param [in] pFile - name of file to be parsed.
     /// \param [in] pIOHandler - pointer to IO helper object.
-    void ParseFile(const std::string &pFile, IOSystem *pIOHandler);
+    void ParseFile(const std::string &file, IOSystem *pIOHandler);
+    void ParseFile(std::istream &myIstream);
+    void ParseFile(XmlParser &theParser);
     bool CanRead(const std::string &pFile, IOSystem *pIOHandler, bool pCheckSig) const;
     void InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler);
     const aiImporterDesc *GetInfo() const;

+ 65 - 0
code/CMakeLists.txt

@@ -864,6 +864,7 @@ ADD_ASSIMP_IMPORTER( X3D
   AssetLib/X3D/X3DGeoHelper.h
   AssetLib/X3D/X3DXmlHelper.cpp
   AssetLib/X3D/X3DXmlHelper.h
+  AssetLib/VRML/VrmlConverter.cpp
 )
 
 ADD_ASSIMP_IMPORTER( GLTF
@@ -930,6 +931,69 @@ SET( Extra_SRCS
 )
 SOURCE_GROUP( Extra FILES ${Extra_SRCS})
 
+# VRML (.wrl/.x3dv) support
+IF (ASSIMP_BUILD_VRML_IMPORTER)
+    # Note: ALWAYS specify a git commit hash (or tag) instead of a branch name; using a branch name
+    #       can lead to non-deterministic (unpredictable) results since the code is potentially in flux
+    # "main" branch, 18 Nov 2024
+    set(Meshlab_GIT_TAG "ad55b47a9b0700e7b427db6db287bb3a39aa31e7")
+    message("****")
+    message("\n\n**** Cloning meshlab repo, git tag ${Meshlab_GIT_TAG}\n\n")
+
+    # Use CMAKE_CURRENT_SOURCE_DIR which provides assimp-local path (CMAKE_SOURCE_DIR is
+    # relative to top-level/main project)
+    set(Meshlab_BASE_ABSPATH "${CMAKE_CURRENT_SOURCE_DIR}/../contrib/meshlab")
+
+    # Note: Depending on user's OS, build environment etc it may be necessary to change line endings of
+    #       "patches/meshlab.patch" file from CRLF to LF in order for patch operation to succeed
+    # Patch required to
+    #  - replace QtXml w/pugixml
+    #  - disable meshlab cmake scripts to prevent breaking assimp build
+    #  - address compiler warnings to avoid breaking build for users who wisely treat warnings-as-errors
+    set(Meshlab_PATCH_CMD git apply ${Meshlab_BASE_ABSPATH}/patches/meshlab.patch)
+
+    # Note: cloning entire meshlab repo is wasteful since we require literally only two source files.
+    #       There is a technique using "git archive" e.g.
+    #           execute_process(
+    #              COMMAND git archive --remote=${Meshlab_GIT_REPO} ${Meshlab_GIT_TAG} <files> | \
+    #                  tar -x -C ${Meshlab_BASE_ABSPATH}
+    #              RESULT_VARIABLE result
+    #           )
+    #       But this doesn't work with git "https" protocol so not a viable solution
+
+    # Note: CMake's "FetchContent" (which executes at configure time) is much better for this use case
+    #       than "ExternalProject" (which executes at build time); we just want to clone a repo and
+    #       block (wait) as long as necessary until cloning is complete, so we immediately have full
+    #       access to the cloned source files
+    include(FetchContent)
+    set(Meshlab_REPO_ABSPATH "${Meshlab_BASE_ABSPATH}/autoclone")
+    # Only want to clone once (on Android, using SOURCE_DIR will clone per-ABI (x86, x86_64 etc))
+    set(FETCHCONTENT_BASE_DIR ${Meshlab_REPO_ABSPATH})
+    set(FETCHCONTENT_QUIET on) # Turn off to troubleshoot repo clone problems
+    set(FETCHCONTENT_UPDATES_DISCONNECTED on) # Prevent other ABIs from re-cloning/re-patching etc
+    set(Meshlab_GIT_REPO "https://github.com/cnr-isti-vclab/meshlab")
+    FetchContent_Declare(
+            meshlab_repo
+            GIT_REPOSITORY ${Meshlab_GIT_REPO}
+            GIT_TAG        ${Meshlab_GIT_TAG}
+            PATCH_COMMAND  ${Meshlab_PATCH_CMD}
+    )
+    FetchContent_MakeAvailable(meshlab_repo)
+    message("**** Finished cloning meshlab repo")
+    message("****")
+    set(Meshlab_SRC_ABSPATH "${Meshlab_REPO_ABSPATH}/meshlab_repo-src/src/meshlabplugins/io_x3d/vrml")
+    set(Meshlab_SRCS
+            ${Meshlab_SRC_ABSPATH}/Parser.cpp
+            ${Meshlab_SRC_ABSPATH}/Scanner.cpp
+    )
+    set(Meshlab_INCLUDE_DIRS "${Meshlab_SRC_ABSPATH}")
+    INCLUDE_DIRECTORIES(${Meshlab_INCLUDE_DIRS})
+    MESSAGE(STATUS "VRML enabled")
+ELSE() # IF (ASSIMP_BUILD_VRML_IMPORTER)
+    set(Meshlab_SRCS "")
+    MESSAGE(STATUS "VRML disabled")
+ENDIF() # IF (ASSIMP_BUILD_VRML_IMPORTER)
+
 # USD/USDA/USDC/USDZ support
 # tinyusdz
 IF (ASSIMP_BUILD_USD_IMPORTER)
@@ -1297,6 +1361,7 @@ SET( assimp_src
   ${openddl_parser_SRCS}
   ${open3dgc_SRCS}
   ${ziplib_SRCS}
+  ${Meshlab_SRCS}
   ${Tinyusdz_SRCS}
   ${Tinyusdz_DEP_SOURCES}
   ${Pugixml_SRCS}

+ 11 - 0
contrib/meshlab/README.md

@@ -0,0 +1,11 @@
+# meshlab
+Meshlab project cloned in entirety but only using two files: "Parser" and "Scanner" in order to
+reformat .wrl/.x3dv files as .xml
+
+## Automatic repo clone
+Meshlab repo is automatically cloned.  Users who haven't opted-in to VRML support
+won't be burdened with the extra download volume.
+
+To update the git commit hash pulled down, modify `Meshlab_GIT_TAG` in file
+`code/CMakeLists.txt`; it is not expected that the sole files of interest "Parser" and "Scanner"
+will change frequently, if at all, going forward

+ 14 - 0
contrib/meshlab/patches/README.md

@@ -0,0 +1,14 @@
+# meshlab patch
+
+## Notes
+Depending on user's OS, build environment etc it may be necessary to change line endings of
+`patches/meshlab.patch` file from `CRLF` to `LF` in order for patch operation to succeed
+
+## Overview
+"Parser" based on QtXml, need to change to use pugixml
+
+## pugixml notes
+Note that it isn't possible to add an unattached pugixml Node object, modify it, then
+add it to the tree later; the node needs to be attached somewhere in the tree on instantiation
+(even if it's a temporary at root level to be removed when finished) before populating, adding
+children etc.

+ 1191 - 0
contrib/meshlab/patches/meshlab.patch

@@ -0,0 +1,1191 @@
+diff -rupN -x .git autoclone/meshlab_repo-src/CMakeLists.txt meshlab_repo_patch/CMakeLists.txt
+--- autoclone/meshlab_repo-src/CMakeLists.txt	2024-12-06 18:01:10.831623800 -0800
++++ meshlab_repo_patch/CMakeLists.txt	2024-12-06 18:15:09.647799500 -0800
+@@ -16,4 +16,6 @@ option(MESHLAB_USE_DEFAULT_BUILD_AND_INS
+ 
+ option(MESHLAB_IS_NIGHTLY_VERSION "Nightly version of meshlab will be used instead of ML_VERSION" OFF)
+ 
+-add_subdirectory(src)
+\ No newline at end of file
++# Disable meshlab project to avoid breaking assimp build (for converting VMRL (.wrl/.x3dv) files to
++# .xml format, just need to compile two source files)
++#add_subdirectory(src)
+diff -rupN -x .git autoclone/meshlab_repo-src/src/meshlabplugins/io_x3d/vrml/Parser.cpp meshlab_repo_patch/src/meshlabplugins/io_x3d/vrml/Parser.cpp
+--- autoclone/meshlab_repo-src/src/meshlabplugins/io_x3d/vrml/Parser.cpp	2024-12-06 18:01:12.268963700 -0800
++++ meshlab_repo_patch/src/meshlabplugins/io_x3d/vrml/Parser.cpp	2024-12-06 14:19:26.138842300 -0800
+@@ -32,11 +32,11 @@
+ 
+ *****************************************************************************/
+ 
++#include <vector>
+ #include <wchar.h>
+ #include "Parser.h"
+ #include "Scanner.h"
+ 
+-
+ namespace VrmlTranslator {
+ 
+ 
+@@ -95,9 +95,11 @@ bool Parser::WeakSeparator(int n, int sy
+ }
+ 
+ void Parser::VrmlTranslator() {
+-		QDomElement root = doc->createElement("X3D");
+-		QDomElement scene = doc->createElement("Scene");
+-		root.appendChild(scene);
++//		QDomElement root = doc->createElement("X3D");
++        pugi::xml_node root = doc->append_child("X3D");
++//		QDomElement scene = doc->createElement("Scene");
++//		root.appendChild(scene);
++        pugi::xml_node scene = root.append_child("Scene");
+ 		InitX3dNode();
+ 		if (la->kind == 7) {
+ 			HeaderStatement();
+@@ -108,7 +110,7 @@ void Parser::VrmlTranslator() {
+ 		ComponentStatements();
+ 		MetaStatements();
+ 		Statements(scene);
+-		doc->appendChild(root);
++//		doc->appendChild(root);
+ }
+ 
+ void Parser::HeaderStatement() {
+@@ -147,7 +149,7 @@ void Parser::MetaStatements() {
+ 		}
+ }
+ 
+-void Parser::Statements(QDomElement& parent) {
++void Parser::Statements(pugi::xml_node& parent) {
+ 		while (StartOf(1)) {
+ 			Statement(parent);
+ 		}
+@@ -173,16 +175,16 @@ void Parser::ComponentSupportLevel() {
+ }
+ 
+ void Parser::ExportStatement() {
+-		QString str;
++		std::string str;
+ 		Expect(14);
+ 		NodeNameId(str);
+ 		Expect(15);
+ 		ExportedNodeNameId();
+ }
+ 
+-void Parser::NodeNameId(QString& str) {
++void Parser::NodeNameId(std::string& str) {
+ 		Expect(1);
+-		str = QString(coco_string_create_char(t->val));
++		str = std::string(coco_string_create_char(t->val));
+ }
+ 
+ void Parser::ExportedNodeNameId() {
+@@ -190,7 +192,7 @@ void Parser::ExportedNodeNameId() {
+ }
+ 
+ void Parser::ImportStatement() {
+-		QString str;
++		std::string str;
+ 		Expect(16);
+ 		InlineNodeNameId();
+ 		Expect(17);
+@@ -217,7 +219,7 @@ void Parser::Metavalue() {
+ 		Expect(4);
+ }
+ 
+-void Parser::Statement(QDomElement& parent) {
++void Parser::Statement(pugi::xml_node& parent) {
+ 		if (StartOf(2)) {
+ 			NodeStatement(parent);
+ 		} else if (la->kind == 16) {
+@@ -231,8 +233,8 @@ void Parser::Statement(QDomElement& pare
+ 		} else SynErr(87);
+ }
+ 
+-void Parser::NodeStatement(QDomElement& parent) {
+-		QString tagName, attrValue;
++void Parser::NodeStatement(pugi::xml_node& parent) {
++		std::string tagName, attrValue;
+ 		if (la->kind == 1 || la->kind == 38) {
+ 			Node(parent, tagName, "");
+ 		} else if (la->kind == 19) {
+@@ -242,17 +244,19 @@ void Parser::NodeStatement(QDomElement&
+ 		} else if (la->kind == 20) {
+ 			Get();
+ 			NodeNameId(attrValue);
+-			std::map<QString, QString>::const_iterator iter = defNode.find(attrValue);
++			std::map<std::string, std::string>::const_iterator iter = defNode.find(attrValue);
+ 			if(iter != defNode.end())
+ 			{
+-			  QDomElement node = doc->createElement(iter->second);
+-			  node.setAttribute("USE", attrValue);
+-			  parent.appendChild(node);
++//			  QDomElement node = doc->createElement(iter->second);
++              pugi::xml_node node = parent.append_child((iter->second).c_str());
++//			  node.setAttribute("USE", attrValue);
++              node.append_attribute("USE") = attrValue.c_str();
++//			  parent.appendChild(node);
+ 			}
+ 		} else SynErr(88);
+ }
+ 
+-void Parser::ProtoStatement(QDomElement& parent) {
++void Parser::ProtoStatement(pugi::xml_node& parent) {
+ 		if (la->kind == 21) {
+ 			Proto(parent);
+ 		} else if (la->kind == 34) {
+@@ -261,7 +265,7 @@ void Parser::ProtoStatement(QDomElement&
+ }
+ 
+ void Parser::RouteStatement() {
+-		QString str;
++		std::string str;
+ 		Expect(35);
+ 		NodeNameId(str);
+ 		Expect(17);
+@@ -272,22 +276,27 @@ void Parser::RouteStatement() {
+ 		InputOnlyId(str);
+ }
+ 
+-void Parser::Node(QDomElement& parent, QString& tagName, const QString defValue) {
+-		bool flag = false; QDomElement node;
++void Parser::Node(pugi::xml_node& parent, std::string& tagName, const std::string defValue) {
++		bool flag = false; pugi::xml_node node;
+ 		if (la->kind == 1) {
+ 			NodeTypeId(tagName);
+-			std::set<QString>::const_iterator iter = proto.find(tagName);
++			std::set<std::string>::const_iterator iter = proto.find(tagName);
+ 			if (iter != proto.end())
+ 			{
+-			  node = doc->createElement("ProtoInstance");
+-			  node.setAttribute("name", tagName);
++//			  node = doc->createElement("ProtoInstance");
++              node = parent.append_child("ProtoInstance");
++//			  node.setAttribute("name", tagName);
++              node.append_attribute("name") = tagName.c_str();
+ 			  flag = true;
+ 			}
+-			else
+-			  node = doc->createElement(tagName);
++			else {
++//			  node = doc->createElement(tagName);
++              node = parent.append_child(tagName.c_str());
++            }
+ 			if (defValue != "")
+ 			{
+-			  node.setAttribute("DEF", defValue);
++//			  node.setAttribute("DEF", defValue);
++              node.append_attribute("DEF") = defValue.c_str();
+ 			  defNode[defValue] = tagName;
+ 			}
+ 			Expect(24);
+@@ -298,13 +307,14 @@ void Parser::Node(QDomElement& parent, Q
+ 			Expect(24);
+ 			ScriptBody();
+ 			Expect(25);
+-			node = doc->createElement("Script");
++//			node = doc->createElement("Script");
++            node = parent.append_child("Script");
+ 		} else SynErr(90);
+-		parent.appendChild(node);
++//		parent.appendChild(node);
+ }
+ 
+-void Parser::RootNodeStatement(QDomElement& parent) {
+-		QString tagName, attrValue;
++void Parser::RootNodeStatement(pugi::xml_node& parent) {
++		std::string tagName, attrValue;
+ 		if (la->kind == 1 || la->kind == 38) {
+ 			Node(parent, tagName, "");
+ 		} else if (la->kind == 19) {
+@@ -314,70 +324,82 @@ void Parser::RootNodeStatement(QDomEleme
+ 		} else SynErr(91);
+ }
+ 
+-void Parser::Proto(QDomElement& parent) {
+-		QString name; QDomElement node;
++void Parser::Proto(pugi::xml_node& parent) {
++//        QString name; QDomElement node;
++		std::string name; pugi::xml_node node;
+ 		Expect(21);
+ 		NodeTypeId(name);
+-		node = doc->createElement("ProtoDeclare");
+-		node.setAttribute("name", name);
++//		node = doc->createElement("ProtoDeclare");
++        node = parent.append_child("ProtoDeclare");
++//		node.setAttribute("name", name);
++        node.append_attribute("name") = name.c_str();
+ 		proto.insert(name);
+ 		Expect(22);
+-		QDomElement interf = doc->createElement("ProtoInterface");
++//		QDomElement interf = doc->createElement("ProtoInterface");
++        pugi::xml_node interf = node.append_child("ProtoInterface");
+ 		InterfaceDeclarations(interf);
+-		node.appendChild(interf);
++//		node.appendChild(interf);
+ 		Expect(23);
+ 		Expect(24);
+-		QDomElement body = doc->createElement("ProtoBody");
++//		QDomElement body = doc->createElement("ProtoBody");
++        pugi::xml_node body = node.append_child("ProtoBody");
+ 		ProtoBody(body);
+-		node.appendChild(body);
++//		node.appendChild(body);
+ 		Expect(25);
+-		parent.appendChild(node);
++//		parent.appendChild(node);
+ }
+ 
+-void Parser::Externproto(QDomElement& parent) {
+-		QString name, url;
+-		QDomElement node = doc->createElement("ExternProtoDeclare");
++void Parser::Externproto(pugi::xml_node& parent) {
++//        QString name, url;
++		std::string name, url;
++//		QDomElement node = doc->createElement("ExternProtoDeclare");
++        pugi::xml_node node = doc->append_child("ExternProtoDeclare");
+ 		Expect(34);
+ 		NodeTypeId(name);
+ 		Expect(22);
+ 		ExternInterfaceDeclarations(node);
+ 		Expect(23);
+ 		URLList(url);
+-		std::set<QString>::const_iterator iter = x3dNode.find(name);
++		std::set<std::string>::const_iterator iter = x3dNode.find(name);
+ 		if (iter == x3dNode.end())
+ 		{
+-		  node.setAttribute("name", name);
+-		  node.setAttribute("url", url);
+-		  parent.appendChild(node);
++//		  node.setAttribute("name", name);
++          node.append_attribute("name") = name.c_str();
++//		  node.setAttribute("url", url);
++          node.append_attribute("url") = url.c_str();
++//		  parent.appendChild(node);
++          parent.append_copy(node);
+ 		  proto.insert(name);
+ 		}
++        doc->remove_child(node);
+ }
+ 
+-void Parser::ProtoStatements(QDomElement& parent) {
++void Parser::ProtoStatements(pugi::xml_node& parent) {
+ 		while (la->kind == 21 || la->kind == 34) {
+ 			ProtoStatement(parent);
+ 		}
+ }
+ 
+-void Parser::NodeTypeId(QString& str) {
++void Parser::NodeTypeId(std::string& str) {
+ 		Expect(1);
+-		str = QString(coco_string_create_char(t->val));
++		str = std::string(coco_string_create_char(t->val));
+ }
+ 
+-void Parser::InterfaceDeclarations(QDomElement& parent) {
++void Parser::InterfaceDeclarations(pugi::xml_node& parent) {
+ 		while (StartOf(3)) {
+ 			InterfaceDeclaration(parent);
+ 		}
+ }
+ 
+-void Parser::ProtoBody(QDomElement& parent) {
++void Parser::ProtoBody(pugi::xml_node& parent) {
+ 		ProtoStatements(parent);
+ 		RootNodeStatement(parent);
+ 		Statements(parent);
+ }
+ 
+-void Parser::InterfaceDeclaration(QDomElement& parent) {
+-		QString name, type, val; QDomElement node;
++void Parser::InterfaceDeclaration(pugi::xml_node& parent) {
++//        QString name, type, val; QDomElement node;
++		std::string name, type, val; pugi::xml_node node;
+ 		if (StartOf(4)) {
+ 			RestrictedInterfaceDeclaration(parent);
+ 		} else if (la->kind == 32 || la->kind == 33) {
+@@ -388,18 +410,23 @@ void Parser::InterfaceDeclaration(QDomEl
+ 			}
+ 			FieldType(type);
+ 			FieldId(name);
++            node = parent.append_child("field");
+ 			FieldValue(node, "value", false);
+-			node = doc->createElement("field");
+-			node.setAttribute("name", name);
+-			node.setAttribute("type", type);
+-			node.setAttribute("accessType", "inputOutput");
+-			parent.appendChild(node);
++//			node = doc->createElement("field");
++//			node.setAttribute("name", name);
++            node.append_attribute("name") = name.c_str();
++//			node.setAttribute("type", type);
++            node.append_attribute("type") = type.c_str();
++//			node.setAttribute("accessType", "inputOutput");
++            node.append_attribute("accessType") = "inputOutput";
++//			parent.appendChild(node);
+ 		} else SynErr(92);
+ }
+ 
+-void Parser::RestrictedInterfaceDeclaration(QDomElement& parent) {
+-		QString name; QString type; QString val;
+-		QDomElement node = doc->createElement("field");
++void Parser::RestrictedInterfaceDeclaration(pugi::xml_node& parent) {
++		std::string name; std::string type; std::string val;
++//		QDomElement node = doc->createElement("field");
++        pugi::xml_node node = parent.append_child("field");
+ 		if (la->kind == 26 || la->kind == 27) {
+ 			if (la->kind == 26) {
+ 				Get();
+@@ -408,7 +435,8 @@ void Parser::RestrictedInterfaceDeclarat
+ 			}
+ 			FieldType(type);
+ 			InputOnlyId(name);
+-			node.setAttribute("accessType", "inputOnly");
++//			node.setAttribute("accessType", "inputOnly");
++            node.append_attribute("accessType") = "inputOnly";
+ 		} else if (la->kind == 28 || la->kind == 29) {
+ 			if (la->kind == 28) {
+ 				Get();
+@@ -417,7 +445,8 @@ void Parser::RestrictedInterfaceDeclarat
+ 			}
+ 			FieldType(type);
+ 			OutputOnlyId(name);
+-			node.setAttribute("accessType", "outputOnly");
++//			node.setAttribute("accessType", "outputOnly");
++            node.append_attribute("accessType") = "outputOnly";
+ 		} else if (la->kind == 30 || la->kind == 31) {
+ 			if (la->kind == 30) {
+ 				Get();
+@@ -427,14 +456,17 @@ void Parser::RestrictedInterfaceDeclarat
+ 			FieldType(type);
+ 			InitializeOnlyId(name);
+ 			FieldValue(node, "value", false);
+-			node.setAttribute("accessType", "initializeOnly");
++//			node.setAttribute("accessType", "initializeOnly");
++            node.append_attribute("accessType") = "initializeOnly";
+ 		} else SynErr(93);
+-		node.setAttribute("name", name);
+-		node.setAttribute("type", type);
+-		parent.appendChild(node);
++//		node.setAttribute("name", name);
++        node.append_attribute("name") = name.c_str();
++//		node.setAttribute("type", type);
++        node.append_attribute("type") = type.c_str();
++//		parent.appendChild(node);
+ }
+ 
+-void Parser::FieldType(QString& str) {
++void Parser::FieldType(std::string& str) {
+ 		switch (la->kind) {
+ 		case 40: {
+ 			Get();
+@@ -606,25 +638,25 @@ void Parser::FieldType(QString& str) {
+ 		}
+ 		default: SynErr(94); break;
+ 		}
+-		str = QString(coco_string_create_char(t->val));
++		str = std::string(coco_string_create_char(t->val));
+ }
+ 
+-void Parser::InputOnlyId(QString& str) {
++void Parser::InputOnlyId(std::string& str) {
+ 		Expect(1);
+-		str = QString(coco_string_create_char(t->val));
++		str = std::string(coco_string_create_char(t->val));
+ }
+ 
+-void Parser::OutputOnlyId(QString& str) {
++void Parser::OutputOnlyId(std::string& str) {
+ 		Expect(1);
+-		str = QString(coco_string_create_char(t->val));
++		str = std::string(coco_string_create_char(t->val));
+ }
+ 
+-void Parser::InitializeOnlyId(QString& str) {
++void Parser::InitializeOnlyId(std::string& str) {
+ 		Expect(1);
+-		str = QString(coco_string_create_char(t->val));
++		str = std::string(coco_string_create_char(t->val));
+ }
+ 
+-void Parser::FieldValue(QDomElement& parent, QString fieldName, bool flag) {
++void Parser::FieldValue(pugi::xml_node& parent, std::string fieldName, bool flag) {
+ 		if (StartOf(5)) {
+ 			SingleValue(parent, fieldName, flag);
+ 		} else if (la->kind == 22) {
+@@ -632,21 +664,21 @@ void Parser::FieldValue(QDomElement& par
+ 		} else SynErr(95);
+ }
+ 
+-void Parser::FieldId(QString& str) {
++void Parser::FieldId(std::string& str) {
+ 		Expect(1);
+-		str = QString(coco_string_create_char(t->val));
++		str = std::string(coco_string_create_char(t->val));
+ }
+ 
+-void Parser::ExternInterfaceDeclarations(QDomElement& parent) {
++void Parser::ExternInterfaceDeclarations(pugi::xml_node& parent) {
+ 		while (StartOf(3)) {
+ 			ExternInterfaceDeclaration(parent);
+ 		}
+ }
+ 
+-void Parser::URLList(QString& url) {
++void Parser::URLList(std::string& url) {
+ 		if (la->kind == 4) {
+ 			Get();
+-			url = QString(coco_string_create_char(t->val));
++			url = std::string(coco_string_create_char(t->val));
+ 		} else if (la->kind == 22) {
+ 			Get();
+ 			while (la->kind == 4) {
+@@ -660,9 +692,10 @@ void Parser::URLList(QString& url) {
+ 		} else SynErr(96);
+ }
+ 
+-void Parser::ExternInterfaceDeclaration(QDomElement& parent) {
+-		QString type, name;
+-		QDomElement node = doc->createElement("field");
++void Parser::ExternInterfaceDeclaration(pugi::xml_node& parent) {
++		std::string type, name;
++//		QDomElement node = doc->createElement("field");
++        pugi::xml_node node = parent.append_child("field");
+ 		if (la->kind == 26 || la->kind == 27) {
+ 			if (la->kind == 26) {
+ 				Get();
+@@ -671,7 +704,8 @@ void Parser::ExternInterfaceDeclaration(
+ 			}
+ 			FieldType(type);
+ 			InputOnlyId(name);
+-			node.setAttribute("accessType", "inputOnly");
++//			node.setAttribute("accessType", "inputOnly");
++            node.append_attribute("accessType") = "inputOnly";
+ 		} else if (la->kind == 28 || la->kind == 29) {
+ 			if (la->kind == 28) {
+ 				Get();
+@@ -680,7 +714,8 @@ void Parser::ExternInterfaceDeclaration(
+ 			}
+ 			FieldType(type);
+ 			OutputOnlyId(name);
+-			node.setAttribute("accessType", "outputOnly");
++//			node.setAttribute("accessType", "outputOnly");
++            node.append_attribute("accessType") = "outputOnly";
+ 		} else if (la->kind == 30 || la->kind == 31) {
+ 			if (la->kind == 30) {
+ 				Get();
+@@ -689,7 +724,8 @@ void Parser::ExternInterfaceDeclaration(
+ 			}
+ 			FieldType(type);
+ 			InitializeOnlyId(name);
+-			node.setAttribute("accessType", "initializeOnly");
++//			node.setAttribute("accessType", "initializeOnly");
++            node.append_attribute("accessType") = "initializeOnly";
+ 		} else if (la->kind == 32 || la->kind == 33) {
+ 			if (la->kind == 32) {
+ 				Get();
+@@ -698,14 +734,17 @@ void Parser::ExternInterfaceDeclaration(
+ 			}
+ 			FieldType(type);
+ 			FieldId(name);
+-			node.setAttribute("accessType", "inputOutput");
++//			node.setAttribute("accessType", "inputOutput");
++            node.append_attribute("accessType") = "inputOutput";
+ 		} else SynErr(97);
+-		node.setAttribute("name" , name);
+-		node.setAttribute("type", type);
+-		parent.appendChild(node);
++//		node.setAttribute("name" , name);
++        node.append_attribute("name") = name.c_str();
++//		node.setAttribute("type", type);
++        node.append_attribute("type") = type.c_str();
++//		parent.appendChild(node);
+ }
+ 
+-void Parser::NodeBody(QDomElement& parent, bool flag) {
++void Parser::NodeBody(pugi::xml_node& parent, bool flag) {
+ 		while (StartOf(6)) {
+ 			NodeBodyElement(parent, flag);
+ 		}
+@@ -717,24 +756,29 @@ void Parser::ScriptBody() {
+ 		}
+ }
+ 
+-void Parser::NodeBodyElement(QDomElement& parent, bool flag) {
+-		QString idName, idProto; QDomElement node;
++void Parser::NodeBodyElement(pugi::xml_node& parent, bool flag) {
++//        QString idName, idProto; QDomElement node;
++		std::string idName, idProto; pugi::xml_node node;
+ 		if (la->kind == 1) {
+ 			Get();
+-			idName = QString(coco_string_create_char(t->val));
++			idName = std::string(coco_string_create_char(t->val));
+ 			if (StartOf(8)) {
+ 				FieldValue(parent, idName, flag);
+ 			} else if (la->kind == 39) {
+ 				Get();
+ 				Expect(1);
+-				idProto = QString(coco_string_create_char(t->val));
+-				node = doc->createElement("IS");
+-				QDomElement connect = doc->createElement("connect");
+-				connect.setAttribute("nodeField", idName);
+-				connect.setAttribute("protoField", idProto);
+-				node.appendChild(connect);
+-				parent.appendChild(node);
+-				
++				idProto = std::string(coco_string_create_char(t->val));
++//				node = doc->createElement("IS");
++                node = parent.append_child("IS");
++//				QDomElement connect = doc->createElement("connect");
++                pugi::xml_node connect = node.append_child("connect");
++//				connect.setAttribute("nodeField", idName);
++                connect.append_attribute("nodeField") = idName.c_str();
++//				connect.setAttribute("protoField", idProto);
++                connect.append_attribute("protoField") = idProto.c_str();
++//				node.appendChild(connect);
++//				parent.appendChild(node);
++
+ 			} else SynErr(98);
+ 		} else if (la->kind == 35) {
+ 			RouteStatement();
+@@ -744,7 +788,7 @@ void Parser::NodeBodyElement(QDomElement
+ }
+ 
+ void Parser::ScriptBodyElement() {
+-		QString str; QDomElement elem;
++		std::string str; pugi::xml_node elem;
+ 		if (StartOf(6)) {
+ 			NodeBodyElement(elem, false);
+ 		} else if (la->kind == 26 || la->kind == 27) {
+@@ -798,17 +842,21 @@ void Parser::ScriptBodyElement() {
+ 		} else SynErr(101);
+ }
+ 
+-void Parser::InputOutputId(QString& str) {
++void Parser::InputOutputId(std::string& str) {
+ 		Expect(1);
+-		str = QString(coco_string_create_char(t->val));
++		str = std::string(coco_string_create_char(t->val));
+ }
+ 
+-void Parser::SingleValue(QDomElement& parent, QString fieldName, bool flag) {
+-		QString value; QDomElement tmpParent = doc->createElement("tmp");
++void Parser::SingleValue(pugi::xml_node& parent, std::string fieldName, bool flag) {
++//        QString value; QDomElement tmpParent = doc->createElement("tmp");
++		std::string value; pugi::xml_node tmpParent = doc->append_child("tmpParent");
+ 		if (StartOf(9)) {
+ 			if (la->kind == 4) {
+ 				Get();
+-				value.append(coco_string_create_char(t->val)); value.remove("\"");
++				value.append(coco_string_create_char(t->val)); //value.remove("\"");
++                // TODO: modify quotation removal; below violates const-correctness:
++                //   error: cannot convert ‘std::__cxx11::basic_string<char>::iterator’ to ‘const char*’
++//                value.erase(std::remove(value.begin(), value.end(), '"'), value.end());
+ 			} else if (la->kind == 2 || la->kind == 3) {
+ 				if (la->kind == 2) {
+ 					Get();
+@@ -839,29 +887,41 @@ void Parser::SingleValue(QDomElement& pa
+ 			}
+ 			if (flag)
+ 			{
+-			  QDomElement node = doc->createElement("fieldValue");
+-			  node.setAttribute("name", fieldName);
+-			  node.setAttribute("value", value);
+-			  parent.appendChild(node);
+-			}
+-			else
+-			  parent.setAttribute(fieldName, value);
++//			  QDomElement node = doc->createElement("fieldValue");
++              pugi::xml_node node = parent.append_child("fieldValue");
++//			  node.setAttribute("name", fieldName);
++              node.append_attribute("name") = fieldName.c_str();
++//			  node.setAttribute("value", value);
++              node.append_attribute("value") = value.c_str();
++//			  parent.appendChild(node);
++			}
++			else {
++//			  parent.setAttribute(fieldName, value);
++              parent.append_attribute(fieldName.c_str()) = value.c_str();
++            }
+ 		} else if (StartOf(2)) {
+ 			NodeStatement(tmpParent);
+ 			if (flag)
+ 			{
+-			  QDomElement tmp = doc->createElement("fieldValue");
+-			  tmp.setAttribute("name", fieldName);
+-			  tmp.appendChild(tmpParent.firstChildElement());
+-			  parent.appendChild(tmp);
+-			}
+-			else
+-			  parent.appendChild(tmpParent.firstChildElement());
++//			  QDomElement tmp = doc->createElement("fieldValue");
++              pugi::xml_node tmp = parent.append_child("fieldValue");
++//			  tmp.setAttribute("name", fieldName);
++              tmp.append_attribute("name") = fieldName.c_str();
++//			  tmp.appendChild(tmpParent.firstChildElement());
++              tmp.insert_child_before(pugi::node_element, tmpParent.first_child());
++//			  parent.appendChild(tmp);
++			}
++			else {
++//              parent.appendChild(tmpParent.firstChildElement());
++              parent.append_copy(tmpParent.first_child());
++            }
+ 		} else SynErr(102);
++        doc->remove_child(tmpParent);
+ }
+ 
+-void Parser::MultiValue(QDomElement& parent, QString fieldName, bool flag) {
+-		QString value; QDomElement tmpParent = doc->createElement("tmp");
++void Parser::MultiValue(pugi::xml_node& parent, std::string fieldName, bool flag) {
++//        QString value; QDomElement tmpParent = doc->createElement("tmp");
++		std::string value; pugi::xml_node tmpParent = doc->append_child("tmpParent");
+ 		Expect(22);
+ 		if (StartOf(10)) {
+ 			if (la->kind == 2 || la->kind == 3) {
+@@ -873,14 +933,19 @@ void Parser::MultiValue(QDomElement& par
+ 			}
+ 			if (flag)
+ 			{
+-			  QDomElement tmp = doc->createElement("fieldValue");
+-			  tmp.setAttribute("name", fieldName);
+-			  tmp.setAttribute("value", value);
+-			  parent.appendChild(tmp);
+-			}
+-			else
+-			  parent.setAttribute(fieldName, value);
+-			
++//			  QDomElement tmp = doc->createElement("fieldValue");
++              pugi::xml_node tmp = parent.append_child("fieldValue");
++//			  tmp.setAttribute("name", fieldName);
++              tmp.append_attribute("name") = fieldName.c_str();
++//			  tmp.setAttribute("value", value);
++              tmp.append_attribute("value") = value.c_str();
++//			  parent.appendChild(tmp);
++			}
++			else {
++//              parent.setAttribute(fieldName, value);
++              parent.append_attribute(fieldName.c_str()) = value.c_str();
++            }
++
+ 		} else if (StartOf(11)) {
+ 			while (StartOf(2)) {
+ 				NodeStatement(tmpParent);
+@@ -888,28 +953,41 @@ void Parser::MultiValue(QDomElement& par
+ 					Get();
+ 				}
+ 			}
+-			QDomElement child;
+-			QDomNodeList list = tmpParent.childNodes();
+-			QDomElement field = doc->createElement("field");
+-			field.setAttribute("name", fieldName);
+-			int i = 0;
++//			QDomElement child;
++            pugi::xml_node child;
++//			QDomNodeList list = tmpParent.childNodes();
++            std::vector<pugi::xml_node> list;
++            for (auto item : tmpParent.children()) {
++                list.push_back(item);
++            }
++//			QDomElement field = doc->createElement("field");
++            pugi::xml_node field = parent.append_child("field");
++//			field.setAttribute("name", fieldName);
++            field.append_attribute("name") = fieldName.c_str();
++			unsigned int i = 0;
+ 			while(i < list.size())
+ 			{
+-			  child = list.at(i).toElement();
+-			  if (flag)
+-			    field.appendChild(child.cloneNode());
+-			  else
+-			    parent.appendChild(child.cloneNode());
++			  child = list.at(i);//.toElement();
++			  if (flag) {
++//                field.appendChild(child.cloneNode());
++                  field.append_copy(child);
++              } else {
++//                parent.appendChild(child.cloneNode());
++                  parent.append_copy(child);
++              }
+ 			  i++;
+ 			}
+-			if (flag)
+-			  parent.appendChild(field);
+-			
++			if (flag) {
++//              parent.appendChild(field);
++            } else {
++                parent.remove_child(field);
++            }
+ 		} else SynErr(103);
+ 		Expect(23);
++        doc->remove_child(tmpParent);
+ }
+ 
+-void Parser::MultiNumber(QString& value) {
++void Parser::MultiNumber(std::string& value) {
+ 		if (la->kind == 2) {
+ 			Get();
+ 		} else if (la->kind == 3) {
+@@ -932,7 +1010,7 @@ void Parser::MultiNumber(QString& value)
+ 		}
+ }
+ 
+-void Parser::MultiString(QString& value) {
++void Parser::MultiString(std::string& value) {
+ 		Expect(4);
+ 		value.append(coco_string_create_char(t->val));
+ 		if (la->kind == 37) {
+@@ -947,7 +1025,7 @@ void Parser::MultiString(QString& value)
+ 		}
+ }
+ 
+-void Parser::MultiBool(QString& value) {
++void Parser::MultiBool(std::string& value) {
+ 		if (la->kind == 82) {
+ 			Get();
+ 		} else if (la->kind == 84) {
+@@ -1173,7 +1251,7 @@ void Errors::Warning(const wchar_t *s) {
+ }
+ 
+ void Errors::Exception(const wchar_t* s) {
+-	wprintf(L"%ls", s); 
++	wprintf(L"%ls", s);
+ 	exit(1);
+ }
+ */
+diff -rupN -x .git autoclone/meshlab_repo-src/src/meshlabplugins/io_x3d/vrml/Parser.h meshlab_repo_patch/src/meshlabplugins/io_x3d/vrml/Parser.h
+--- autoclone/meshlab_repo-src/src/meshlabplugins/io_x3d/vrml/Parser.h	2024-12-06 18:01:12.268963700 -0800
++++ meshlab_repo_patch/src/meshlabplugins/io_x3d/vrml/Parser.h	2024-12-06 18:48:17.891284300 -0800
+@@ -35,9 +35,10 @@
+ #if !defined(VRML_PARSER_H__)
+ #define VRML_PARSER_H__
+ 
+-#include <QtXml>
++#include "contrib/pugixml/src/pugixml.hpp"
++#include <map>
+ #include <set>
+-
++#include <string>
+ 
+ #include "Scanner.h"
+ 
+@@ -48,7 +49,7 @@ class Errors {
+ public:
+ 	int count;			// number of errors detected
+ 	wchar_t* stringError;
+-	
++
+ 	Errors();
+ 	~Errors();
+ 	void SynErr(int line, int col, int n);
+@@ -90,14 +91,15 @@ public:
+ 	Token *t;			// last recognized token
+ 	Token *la;			// lookahead token
+ 
+-QDomDocument *doc;
+-	
+-	std::map<QString, QString> defNode;
+-	
+-	std::set<QString> proto;
+-	
+-	std::set<QString> x3dNode;
+-	
++    pugi::xml_document doc_;
++    pugi::xml_document *doc = &doc_; // IrrXMLReader* createIrrXMLReader(const char* filename);
++
++    std::map<std::string, std::string> defNode;
++
++	std::set<std::string> proto;
++
++	std::set<std::string> x3dNode;
++
+ 	void InitX3dNode()
+ 	{
+ 	  x3dNode.insert("Arc2D"); x3dNode.insert("ArcClose2D"); x3dNode.insert("BallJoint");
+@@ -116,7 +118,7 @@ QDomDocument *doc;
+ 	  x3dNode.insert("EspduTransform"); x3dNode.insert("ExplosionEmitter");
+ 	  x3dNode.insert("FillProperties"); x3dNode.insert("FloatVertexAttribute");
+ 	  x3dNode.insert("FogCoordinate"); x3dNode.insert(" GeneratedCubeMapTexture");
+-	  x3dNode.insert("GeoCoordinate"); x3dNode.insert("GeoElevationGrid"); x3dNode.insert("GeoLocation"); 
++	  x3dNode.insert("GeoCoordinate"); x3dNode.insert("GeoElevationGrid"); x3dNode.insert("GeoLocation");
+ 	  x3dNode.insert("GeoLOD"); x3dNode.insert("GeoMetadata"); x3dNode.insert("GeoOrigin");
+ 	  x3dNode.insert("GeoPositionInterpolator"); x3dNode.insert("GeoProximitySensor");
+ 	  x3dNode.insert("GeoTouchSensor"); x3dNode.insert("GeoViewpoint");	x3dNode.insert("GravityPhysicsModel");
+@@ -128,7 +130,7 @@ QDomDocument *doc;
+ 	  x3dNode.insert("Layer"); x3dNode.insert("LayerSet"); x3dNode.insert("Layout");
+ 	  x3dNode.insert("LayoutGroup"); x3dNode.insert("LayoutLayer"); x3dNode.insert("LinePicker");
+ 	  x3dNode.insert("LineProperties"); x3dNode.insert("LineSet"); x3dNode.insert("LoadSensor");
+-	  x3dNode.insert("LocalFog"); x3dNode.insert("Material"); x3dNode.insert("Matrix3VertexAttribute"); 
++	  x3dNode.insert("LocalFog"); x3dNode.insert("Material"); x3dNode.insert("Matrix3VertexAttribute");
+ 	  x3dNode.insert("Matrix4VertexAttribute"); x3dNode.insert("MetadataDouble");
+ 	  x3dNode.insert("MetadataFloat"); x3dNode.insert("MetadataInteger"); x3dNode.insert("MetadataSet");
+ 	  x3dNode.insert("MetadataString"); x3dNode.insert("MotorJoint"); x3dNode.insert("MultiTexture");
+@@ -162,7 +164,7 @@ QDomDocument *doc;
+ 	  x3dNode.insert(" Viewpoint"); x3dNode.insert("ViewpointGroup"); x3dNode.insert("VolumeEmitter");
+ 	  x3dNode.insert("VolumePicker"); x3dNode.insert("WindPhysicsModel"); x3dNode.insert("Cylinder"); x3dNode.insert("Sphere");
+ 	}
+-	
++
+ 
+ 
+ 	Parser(Scanner *scanner);
+@@ -174,52 +176,52 @@ QDomDocument *doc;
+ 	void ProfileStatement();
+ 	void ComponentStatements();
+ 	void MetaStatements();
+-	void Statements(QDomElement& parent);
++	void Statements(pugi::xml_node& parent);
+ 	void ProfileNameId();
+ 	void ComponentStatement();
+ 	void ComponentNameId();
+ 	void ComponentSupportLevel();
+ 	void ExportStatement();
+-	void NodeNameId(QString& str);
++	void NodeNameId(std::string& str);
+ 	void ExportedNodeNameId();
+ 	void ImportStatement();
+ 	void InlineNodeNameId();
+ 	void MetaStatement();
+ 	void Metakey();
+ 	void Metavalue();
+-	void Statement(QDomElement& parent);
+-	void NodeStatement(QDomElement& parent);
+-	void ProtoStatement(QDomElement& parent);
++	void Statement(pugi::xml_node& parent);
++	void NodeStatement(pugi::xml_node& parent);
++	void ProtoStatement(pugi::xml_node& parent);
+ 	void RouteStatement();
+-	void Node(QDomElement& parent, QString& tagName, const QString defValue);
+-	void RootNodeStatement(QDomElement& parent);
+-	void Proto(QDomElement& parent);
+-	void Externproto(QDomElement& parent);
+-	void ProtoStatements(QDomElement& parent);
+-	void NodeTypeId(QString& str);
+-	void InterfaceDeclarations(QDomElement& parent);
+-	void ProtoBody(QDomElement& parent);
+-	void InterfaceDeclaration(QDomElement& parent);
+-	void RestrictedInterfaceDeclaration(QDomElement& parent);
+-	void FieldType(QString& str);
+-	void InputOnlyId(QString& str);
+-	void OutputOnlyId(QString& str);
+-	void InitializeOnlyId(QString& str);
+-	void FieldValue(QDomElement& parent, QString fieldName, bool flag);
+-	void FieldId(QString& str);
+-	void ExternInterfaceDeclarations(QDomElement& parent);
+-	void URLList(QString& url);
+-	void ExternInterfaceDeclaration(QDomElement& parent);
+-	void NodeBody(QDomElement& parent, bool flag);
++	void Node(pugi::xml_node& parent, std::string& tagName, const std::string defValue);
++	void RootNodeStatement(pugi::xml_node& parent);
++	void Proto(pugi::xml_node& parent);
++	void Externproto(pugi::xml_node& parent);
++	void ProtoStatements(pugi::xml_node& parent);
++	void NodeTypeId(std::string& str);
++	void InterfaceDeclarations(pugi::xml_node& parent);
++	void ProtoBody(pugi::xml_node& parent);
++	void InterfaceDeclaration(pugi::xml_node& parent);
++	void RestrictedInterfaceDeclaration(pugi::xml_node& parent);
++	void FieldType(std::string& str);
++	void InputOnlyId(std::string& str);
++	void OutputOnlyId(std::string& str);
++	void InitializeOnlyId(std::string& str);
++	void FieldValue(pugi::xml_node& parent, std::string fieldName, bool flag);
++	void FieldId(std::string& str);
++	void ExternInterfaceDeclarations(pugi::xml_node& parent);
++	void URLList(std::string& url);
++	void ExternInterfaceDeclaration(pugi::xml_node& parent);
++	void NodeBody(pugi::xml_node& parent, bool flag);
+ 	void ScriptBody();
+-	void NodeBodyElement(QDomElement& parent, bool flag);
++	void NodeBodyElement(pugi::xml_node& parent, bool flag);
+ 	void ScriptBodyElement();
+-	void InputOutputId(QString& str);
+-	void SingleValue(QDomElement& parent, QString fieldName, bool flag);
+-	void MultiValue(QDomElement& parent, QString fieldName, bool flag);
+-	void MultiNumber(QString& value);
+-	void MultiString(QString& value);
+-	void MultiBool(QString& value);
++	void InputOutputId(std::string& str);
++	void SingleValue(pugi::xml_node& parent, std::string fieldName, bool flag);
++	void MultiValue(pugi::xml_node& parent, std::string fieldName, bool flag);
++	void MultiNumber(std::string& value);
++	void MultiString(std::string& value);
++	void MultiBool(std::string& value);
+ 
+ 	void Parse();
+ 
+diff -rupN -x .git autoclone/meshlab_repo-src/src/meshlabplugins/io_x3d/vrml/Scanner.cpp meshlab_repo_patch/src/meshlabplugins/io_x3d/vrml/Scanner.cpp
+--- autoclone/meshlab_repo-src/src/meshlabplugins/io_x3d/vrml/Scanner.cpp	2024-12-06 18:01:12.268963700 -0800
++++ meshlab_repo_patch/src/meshlabplugins/io_x3d/vrml/Scanner.cpp	2024-12-06 14:21:47.920443100 -0800
+@@ -39,7 +39,7 @@
+ 
+ wchar_t* coco_string_create(const wchar_t* value) {
+ 	wchar_t* data;
+-	int len = 0;
++	size_t len = 0;
+ 	if (value) { len = wcslen(value); }
+ 	data = new wchar_t[len + 1];
+ 	wcsncpy(data, value, len);
+@@ -63,7 +63,7 @@ wchar_t* coco_string_create_upper(const
+ 	if (!data) { return NULL; }
+ 
+ 	int dataLen = 0;
+-	if (data) { dataLen = wcslen(data); }
++	if (data) { dataLen = static_cast<int>(wcslen(data)); }
+ 
+ 	wchar_t *newData = new wchar_t[dataLen + 1];
+ 
+@@ -80,7 +80,7 @@ wchar_t* coco_string_create_upper(const
+ 
+ wchar_t* coco_string_create_lower(const wchar_t* data) {
+ 	if (!data) { return NULL; }
+-	int dataLen = wcslen(data);
++	int dataLen = static_cast<int>(wcslen(data));
+ 	return coco_string_create_lower(data, 0, dataLen);
+ }
+ 
+@@ -102,11 +102,11 @@ wchar_t* coco_string_create_lower(const
+ 
+ wchar_t* coco_string_create_append(const wchar_t* data1, const wchar_t* data2) {
+ 	wchar_t* data;
+-	int data1Len = 0;
+-	int data2Len = 0;
++    size_t data1Len = 0;
++    size_t data2Len = 0;
+ 
+ 	if (data1) { data1Len = wcslen(data1); }
+-	if (data2) {data2Len = wcslen(data2); }
++	if (data2) { data2Len = wcslen(data2); }
+ 
+ 	data = new wchar_t[data1Len + data2Len + 1];
+ 
+@@ -133,13 +133,13 @@ void coco_string_delete(wchar_t* &data)
+ }
+ 
+ int coco_string_length(const wchar_t* data) {
+-	if (data) { return wcslen(data); }
++	if (data) { return static_cast<int>(wcslen(data)); }
+ 	return 0;
+ }
+ 
+ bool coco_string_endswith(const wchar_t* data, const wchar_t *end) {
+-	int dataLen = wcslen(data);
+-	int endLen = wcslen(end);
++	size_t dataLen = wcslen(data);
++    size_t endLen = wcslen(end);
+ 	return (endLen <= dataLen) && (wcscmp(data + dataLen - endLen, end) == 0);
+ }
+ 
+@@ -186,8 +186,8 @@ int coco_string_hash(const wchar_t *data
+ // string handling, ascii character
+ 
+ wchar_t* coco_string_create(const char* value) {
+-	int len = 0;
+-	if (value) { len = strlen(value); }
++    int len = 0;
++	if (value) { len = static_cast<int>(strlen(value)); }
+ 	wchar_t* data = new wchar_t[len + 1];
+ 	for (int i = 0; i < len; ++i) { data[i] = (wchar_t) value[i]; }
+ 	data[len] = 0;
+@@ -240,7 +240,7 @@ Buffer::Buffer(FILE* s, bool isUserStrea
+ 		fileLen = bufLen = bufStart = 0;
+ 	}
+ 	bufCapacity = (bufLen>0) ? bufLen : MIN_BUFFER_LENGTH;
+-	buf = new unsigned char[bufCapacity];	
++	buf = new unsigned char[bufCapacity];
+ 	if (fileLen > 0) SetPos(0);          // setup  buffer to position 0 (start)
+ 	else bufPos = 0; // index 0 is already after the file, thus Pos = 0 is invalid
+ 	if (bufLen == fileLen && CanSeek()) Close();
+@@ -270,7 +270,7 @@ Buffer::Buffer(const unsigned char* buf,
+ }
+ 
+ Buffer::~Buffer() {
+-	Close(); 
++	Close();
+ 	if (buf != NULL) {
+ 		delete [] buf;
+ 		buf = NULL;
+@@ -306,12 +306,12 @@ int Buffer::Peek() {
+ 
+ wchar_t* Buffer::GetString(int beg, int end) {
+ 	int len = end - beg;
+-	wchar_t *buf = new wchar_t[len];
++	wchar_t *retBuf = new wchar_t[len];
+ 	int oldPos = GetPos();
+ 	SetPos(beg);
+-	for (int i = 0; i < len; ++i) buf[i] = (wchar_t) Read();
++	for (int i = 0; i < len; ++i) retBuf[i] = (wchar_t) Read();
+ 	SetPos(oldPos);
+-	return buf;
++	return retBuf;
+ }
+ 
+ int Buffer::GetPos() {
+@@ -329,16 +329,16 @@ void Buffer::SetPos(int value) {
+ 
+ 	if ((value < 0) || (value > fileLen)) {
+ 		char msg[50];
+-		sprintf(msg, "Buffer out of bounds access, position: %d", value);
++		snprintf(msg, 50, "Buffer out of bounds access, position: %d", value);
+ 		throw msg;
+-		
++
+ 	}
+ 
+ 	if ((value >= bufStart) && (value < (bufStart + bufLen))) { // already in buffer
+ 		bufPos = value - bufStart;
+ 	} else if (stream != NULL) { // must be swapped in
+ 		fseek(stream, value, SEEK_SET);
+-		bufLen = fread(buf, sizeof(unsigned char), bufCapacity, stream);
++		bufLen = static_cast<int>(fread(buf, sizeof(unsigned char), bufCapacity, stream));
+ 		bufStart = value; bufPos = 0;
+ 	} else {
+ 		bufPos = fileLen - bufStart; // make Pos return fileLen
+@@ -362,7 +362,7 @@ int Buffer::ReadNextStreamChunk() {
+ 		buf = newBuf;
+ 		free = bufLen;
+ 	}
+-	int read = fread(buf + bufLen, sizeof(unsigned char), free, stream);
++    int read = static_cast<int>(fread(buf + bufLen, sizeof(unsigned char), free, stream));
+ 	if (read > 0) {
+ 		fileLen = bufLen = (bufLen + read);
+ 		return read;
+@@ -416,7 +416,7 @@ Scanner::Scanner(const wchar_t* fileName
+ 	char *chFileName = coco_string_create_char(fileName);
+ 	if ((stream = fopen(chFileName, "rb")) == NULL) {
+ 		char msg[50];
+-		sprintf(msg, "Can not open file: %s", chFileName);
++		snprintf(msg, 50, "Can not open file: %s", chFileName);
+ 		coco_string_delete(chFileName);
+ 		throw msg;
+ 	}
+@@ -554,7 +554,7 @@ void Scanner::Init() {
+ 	heapEnd = (void**) (((char*) heap) + HEAP_BLOCK_SIZE);
+ 	*heapEnd = 0;
+ 	heapTop = heap;
+-	if (sizeof(Token) > HEAP_BLOCK_SIZE) {
++	if constexpr (sizeof(Token) > HEAP_BLOCK_SIZE) {
+ 		throw "Too small HEAP_BLOCK_SIZE";
+ 	}
+ 
+@@ -637,18 +637,18 @@ void Scanner::CreateHeapBlock() {
+ }
+ 
+ Token* Scanner::CreateToken() {
+-	Token *t;
++	Token *tkn;
+ 	if (((char*) heapTop + (int) sizeof(Token)) >= (char*) heapEnd) {
+ 		CreateHeapBlock();
+ 	}
+-	t = (Token*) heapTop;
++	tkn = (Token*) heapTop;
+ 	heapTop = (void*) ((char*) heapTop + sizeof(Token));
+-	t->val = NULL;
+-	t->next = NULL;
+-	return t;
++	tkn->val = NULL;
++	tkn->next = NULL;
++	return tkn;
+ }
+ 
+-void Scanner::AppendVal(Token *t) {
++void Scanner::AppendVal(Token *tkn) {
+ 	int reqMem = (tlen + 1) * sizeof(wchar_t);
+ 	if (((char*) heapTop + reqMem) >= (char*) heapEnd) {
+ 		if (reqMem > HEAP_BLOCK_SIZE) {
+@@ -656,11 +656,11 @@ void Scanner::AppendVal(Token *t) {
+ 		}
+ 		CreateHeapBlock();
+ 	}
+-	t->val = (wchar_t*) heapTop;
++	tkn->val = (wchar_t*) heapTop;
+ 	heapTop = (void*) ((char*) heapTop + reqMem);
+ 
+-	wcsncpy(t->val, tval, tlen);
+-	t->val[tlen] = L'\0';
++	wcsncpy(tkn->val, tval, tlen);
++	tkn->val[tlen] = L'\0';
+ }
+ 
+ Token* Scanner::NextToken() {
+diff -rupN -x .git autoclone/meshlab_repo-src/src/meshlabplugins/io_x3d/vrml/Scanner.h meshlab_repo_patch/src/meshlabplugins/io_x3d/vrml/Scanner.h
+--- autoclone/meshlab_repo-src/src/meshlabplugins/io_x3d/vrml/Scanner.h	2024-12-06 18:01:12.268963700 -0800
++++ meshlab_repo_patch/src/meshlabplugins/io_x3d/vrml/Scanner.h	2024-12-06 14:21:59.453748300 -0800
+@@ -52,9 +52,9 @@
+ //#define coco_swprintf _snwprintf
+ //#elif defined __GNUC__
+ //#define coco_swprintf swprintf
+-//#else 
++//#else
+ //#error unknown compiler!
+-//#endif 
++//#endif
+ 
+ #ifdef WIN32
+   #ifndef __MINGW32__
+@@ -62,7 +62,7 @@
+       #define coco_swprintf swprintf_s
+     #elif _MSC_VER >= 1300
+        #define coco_swprintf _snwprintf
+-    #else 
++    #else
+        #error unknown compiler!
+     #endif
+    #else
+@@ -106,7 +106,7 @@ void  coco_string_delete(char* &data);
+ namespace VrmlTranslator {
+ 
+ 
+-class Token  
++class Token
+ {
+ public:
+ 	int kind;     // token kind
+@@ -135,10 +135,10 @@ private:
+ 	int bufPos;         // current position in buffer
+ 	FILE* stream;       // input stream (seekable)
+ 	bool isUserStream;  // was the stream opened by the user?
+-	
++
+ 	int ReadNextStreamChunk();
+ 	bool CanSeek();     // true if stream can seek otherwise false
+-	
++
+ public:
+ 	static const int EoF = COCO_WCHAR_MAX + 1;
+ 
+@@ -146,7 +146,7 @@ public:
+ 	Buffer(const unsigned char* buf, int len);
+ 	Buffer(Buffer *b);
+ 	virtual ~Buffer();
+-	
++
+ 	virtual void Close();
+ 	virtual int Read();
+ 	virtual int Peek();
+@@ -256,7 +256,7 @@ private:
+ 	int eofSym;
+ 	int noSym;
+ 	int maxT;
+-	int charSetSize;
++//	int charSetSize; // unused
+ 	StartStates start;
+ 	KeywordMap keywords;
+ 
+@@ -277,7 +277,7 @@ private:
+ 
+ 	void CreateHeapBlock();
+ 	Token* CreateToken();
+-	void AppendVal(Token *t);
++	void AppendVal(Token *tkn);
+ 
+ 	void Init();
+ 	void NextCh();
+@@ -288,7 +288,7 @@ private:
+ 
+ public:
+ 	Buffer *buffer;   // scanner buffer
+-	
++
+ 	Scanner(const unsigned char* buf, int len);
+ 	Scanner(const wchar_t* fileName);
+ 	Scanner(FILE* s);

+ 1 - 1
contrib/tinyusdz/README.md

@@ -5,7 +5,7 @@
 tinyusdz repo is automatically cloned.  Users who haven't opted-in to USD support
 won't be burdened with the extra download volume.
 
-To update te git commit hash pulled down, modify `TINYUSDZ_GIT_TAG` in file
+To update the git commit hash pulled down, modify `TINYUSDZ_GIT_TAG` in file
     `code/CMakeLists.txt`
 
 ## Notes

+ 23 - 1
include/assimp/XmlParser.h

@@ -50,6 +50,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include "IOStream.hpp"
 
 #include <pugixml.hpp>
+#include <istream>
 #include <utility>
 #include <vector>
 
@@ -128,6 +129,11 @@ public:
     /// @return true, if the parsing was successful, false if not.
     bool parse(IOStream *stream);
 
+    /// @brief  Will parse an xml-file from a stringstream.
+    /// @param[in] str      The input istream (note: not "const" to match pugixml param)
+    /// @return true, if the parsing was successful, false if not.
+    bool parse(std::istream &inStream);
+
     /// @brief  Will return true if a root node is there.
     /// @return true in case of an existing root.
     bool hasRoot() const;
@@ -311,7 +317,23 @@ bool TXmlParser<TNodeType>::parse(IOStream *stream) {
     mDoc = new pugi::xml_document();
     // load_string assumes native encoding (aka always utf-8 per build options)
     //pugi::xml_parse_result parse_result = mDoc->load_string(&mData[0], pugi::parse_full);
-     pugi::xml_parse_result parse_result = mDoc->load_buffer(&mData[0], mData.size(), pugi::parse_full);
+    pugi::xml_parse_result parse_result = mDoc->load_buffer(&mData[0], mData.size(), pugi::parse_full);
+    if (parse_result.status == pugi::status_ok) {
+        return true;
+    }
+
+    ASSIMP_LOG_DEBUG("Error while parse xml.", std::string(parse_result.description()), " @ ", parse_result.offset);
+
+    return false;
+}
+
+template <class TNodeType>
+bool TXmlParser<TNodeType>::parse(std::istream &inStream) {
+    if (hasRoot()) {
+        clear();
+    }
+    mDoc = new pugi::xml_document();
+    pugi::xml_parse_result parse_result = mDoc->load(inStream);
     if (parse_result.status == pugi::status_ok) {
         return true;
     }

+ 87 - 0
test/models/WRL/HelloWorld.wrl

@@ -0,0 +1,87 @@
+#VRML V2.0 utf8
+# X3D-to-VRML-97 XSL translation autogenerated by X3dToVrml97.xslt
+# https://www.web3d.org/x3d/content/X3dToVrml97.xslt
+# Generated using XSLT processor: Saxonica
+
+# [X3D] VRML V3.3 utf8
+# PROFILE Immersive
+# [X3D] version=3.3
+# [X3D] noNamespaceSchemaLocation=https://www.web3d.org/specifications/x3d-3.3.xsd
+# [head]
+
+# Alternate encodings: VRML97, X3D ClassicVRML Encoding, X3D Compressed Binary Encoding (CBE), X3DOM, JSON
+# META "title" "HelloWorld.x3d"
+# META "description" "Simple X3D model example: Hello World!"
+# META "created" "30 October 2000"
+# META "modified" "9 July 2023"
+# META "creator" "Don Brutzman"
+# META "Image" "HelloWorld.tall.png"
+# META "reference" "https://en.wikipedia.org/wiki/Hello_world"
+# META "reference" "https://en.wikipedia.org/wiki/Hello#.22Hello.2C_World.22_computer_program"
+# META "reference" "https://en.wikipedia.org/wiki/\"Hello,_World!\"_program"
+# META "reference" "https://en.wikibooks.org/w/index.php?title=Computer_Programming/Hello_world"
+# META "reference" "https://www.HelloWorldExample.net"
+# META "reference" "https://www.web3d.org"
+# META "reference" "https://www.web3d.org/realtime-3d/news/internationalization-x3d"
+# META "reference" "https://www.web3d.org/x3d/content/examples/HelloWorld.x3d"
+# META "reference" "https://www.web3d.org/x3d/content/examples/X3dForAdvancedModeling/HelloWorldScenes/HelloWorld.x3d"
+# META "identifier" "https://www.web3d.org/x3d/content/examples/X3dForWebAuthors/Chapter01TechnicalOverview/HelloWorld.x3d"
+# META "license" "https://www.web3d.org/x3d/content/examples/license.html"
+# META "generator" "X3D-Edit 4.0, https://savage.nps.edu/X3D-Edit"
+# META "reference" "HelloWorld.wrl"
+# META "reference" "HelloWorld.x3dv"
+# META "reference" "HelloWorld.x3db"
+# META "reference" "HelloWorld.xhtml"
+# META "reference" "HelloWorld.json"
+
+# [Scene] ========== ========== ==========
+
+NavigationInfo { type [ "EXAMINE" "ANY" ] } ###  Default X3D NavigationInfo
+
+# Example scene to illustrate X3D nodes and fields (XML elements and attributes)
+WorldInfo {
+  info [ "Example scene to illustrate a simple X3D model" ]
+  title "Hello World!"
+}
+Group {
+  children [
+      DEF ViewUpClose Viewpoint {
+        centerOfRotation 0 -1 0
+        description "Hello world!"
+        position 0 -1 7
+      }
+      Transform {
+        rotation 0 1 0 3
+        children [
+            Shape {
+              geometry Sphere {
+              }
+              appearance Appearance {
+                material DEF MaterialOffWhite Material {
+                  diffuseColor 0.980392 0.976471 0.964706
+                }
+                texture DEF ImageCloudlessEarth ImageTexture {
+                  url [ "earth-topo.png" "earth-topo.jpg" "earth-topo-small.gif" "https://www.web3d.org/x3d/content/examples/Basic/earth-topo.png" "https://www.web3d.org/x3d/content/examples/Basic/earth-topo.jpg" "https://www.web3d.org/x3d/content/examples/Basic/earth-topo-small.gif" ]
+                }
+              }
+            }
+        ]
+      }
+      Transform {
+        translation 0 -2 0
+        children [
+            Shape {
+              geometry DEF TextMessage Text {
+                string [ "Hello" "world!" ]
+                fontStyle FontStyle {
+                  justify [ "MIDDLE" "MIDDLE"  ] 
+                }
+              }
+              appearance Appearance {
+                material USE MaterialOffWhite
+              }
+            }
+        ]
+      }
+  ]
+}

+ 6 - 0
test/models/WRL/README.md

@@ -0,0 +1,6 @@
+# WRL models
+
+X3D is a modern superset of the old (VRML) WRL format
+
+# HelloWorld.wrl
+Downloaded from [HelloWorld.wrl](http://www.web3d.org/x3d/content/examples/HelloWorld.wrl)

BIN=BIN
test/models/WRL/earth-topo.png


+ 4 - 0
test/models/WRL/ref/README.md

@@ -0,0 +1,4 @@
+# WRL 3D model reference images
+
+## HelloWorld.wrl
+<img alt="HelloWorld.wrl" src="screenshots/HelloWorld_wrl.png" width=180 />

BIN=BIN
test/models/WRL/ref/screenshots/HelloWorld_wrl.png


+ 57 - 0
test/models/X3D/HelloWorld.x3d

@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE X3D PUBLIC "ISO//Web3D//DTD X3D 3.3//EN" "https://www.web3d.org/specifications/x3d-3.3.dtd">
+<X3D profile='Immersive' version='3.3' xmlns:xsd='http://www.w3.org/2001/XMLSchema-instance' xsd:noNamespaceSchemaLocation='https://www.web3d.org/specifications/x3d-3.3.xsd'>
+  <head>
+    <!-- Alternate encodings: VRML97, X3D ClassicVRML Encoding, X3D Compressed Binary Encoding (CBE), X3DOM, JSON -->
+    <meta content='HelloWorld.x3d' name='title'/>
+    <meta content='Simple X3D model example: Hello World!' name='description'/>
+    <meta content='30 October 2000' name='created'/>
+    <meta content='9 July 2023' name='modified'/>
+    <meta content='Don Brutzman' name='creator'/>
+    <meta content='HelloWorld.tall.png' name='Image'/>
+    <meta content='https://en.wikipedia.org/wiki/Hello_world' name='reference'/>
+    <meta content='https://en.wikipedia.org/wiki/Hello#.22Hello.2C_World.22_computer_program' name='reference'/>
+    <meta content='https://en.wikipedia.org/wiki/"Hello,_World!"_program' name='reference'/>
+    <meta content='https://en.wikibooks.org/w/index.php?title=Computer_Programming/Hello_world' name='reference'/>
+    <meta content='https://www.HelloWorldExample.net' name='reference'/>
+    <meta content='https://www.web3d.org' name='reference'/>
+    <meta content='https://www.web3d.org/realtime-3d/news/internationalization-x3d' name='reference'/>
+    <meta content='https://www.web3d.org/x3d/content/examples/HelloWorld.x3d' name='reference'/>
+    <meta content='https://www.web3d.org/x3d/content/examples/X3dForAdvancedModeling/HelloWorldScenes/HelloWorld.x3d' name='reference'/>
+    <meta content='https://www.web3d.org/x3d/content/examples/X3dForWebAuthors/Chapter01TechnicalOverview/HelloWorld.x3d' name='identifier'/>
+    <meta content='https://www.web3d.org/x3d/content/examples/license.html' name='license'/>
+    <meta content='X3D-Edit 4.0, https://savage.nps.edu/X3D-Edit' name='generator'/>
+    <meta content='HelloWorld.wrl' name='reference'/>
+    <meta content='HelloWorld.x3dv' name='reference'/>
+    <meta content='HelloWorld.x3db' name='reference'/>
+    <meta content='HelloWorld.xhtml' name='reference'/>
+    <meta content='HelloWorld.json' name='reference'/>
+  </head>
+  <Scene>
+    <!-- Example scene to illustrate X3D nodes and fields (XML elements and attributes) -->
+    <WorldInfo info='"Example scene to illustrate a simple X3D model"' title='Hello World!'/>
+    <Group>
+      <Viewpoint DEF='ViewUpClose' centerOfRotation='0 -1 0' description='Hello world!' position='0 -1 7'/>
+      <Transform rotation='0 1 0 3'>
+        <Shape>
+          <Sphere/>
+          <Appearance>
+            <!-- https://htmlcolorcodes.com/colors/off-white -->
+            <Material DEF='MaterialOffWhite' diffuseColor='0.980392 0.976471 0.964706'/>
+            <ImageTexture DEF='ImageCloudlessEarth' url='"earth-topo.png" "earth-topo.jpg" "earth-topo-small.gif" "https://www.web3d.org/x3d/content/examples/Basic/earth-topo.png" "https://www.web3d.org/x3d/content/examples/Basic/earth-topo.jpg" "https://www.web3d.org/x3d/content/examples/Basic/earth-topo-small.gif"'/>
+          </Appearance>
+        </Shape>
+      </Transform>
+      <Transform translation='0 -2 0'>
+        <Shape>
+          <Text DEF='TextMessage' string='"Hello" "world!"'>
+            <FontStyle justify='"MIDDLE" "MIDDLE"'/>
+          </Text>
+          <Appearance>
+            <Material USE='MaterialOffWhite'/>
+          </Appearance>
+        </Shape>
+      </Transform>
+    </Group>
+  </Scene>
+</X3D>

+ 4 - 0
test/models/X3D/README.md

@@ -0,0 +1,4 @@
+# X3D models
+
+# HelloWorld.x3d
+Downloaded from [HelloWorld.x3d](http://www.web3d.org/x3d/content/examples/HelloWorld.x3d)

BIN=BIN
test/models/X3D/earth-topo.png


+ 3 - 0
test/models/X3D/ref/README.md

@@ -12,3 +12,6 @@ ComputerKeyboard as of 1 Dec 2021 git commit `1614934`, using pugi xml parsing,
 missing meshes and obvious artifacts:
 
 <img alt="ComputerKeyboard.x3d (pugi xml)" src="screenshots/ComputerKeyboard_x3d_pugi_xml.png" width=320 />
+
+## HelloWorld.x3d
+<img alt="HelloWorld.x3d" src="screenshots/HelloWorld_x3d.png" width=180 />

BIN=BIN
test/models/X3D/ref/screenshots/HelloWorld_x3d.png


BIN=BIN
test/models/X3DB/HelloWorld.x3db


+ 6 - 0
test/models/X3DB/README.md

@@ -0,0 +1,6 @@
+# X3DB models
+
+X3DB models are compressed X3D models
+
+# HelloWorld.x3db
+Downloaded from [HelloWorld.x3db](http://www.web3d.org/x3d/content/examples/HelloWorld.x3db)

BIN=BIN
test/models/X3DB/earth-topo.png


+ 86 - 0
test/models/X3DV/HelloWorld.x3dv

@@ -0,0 +1,86 @@
+#X3D V3.3 utf8
+# X3D-to-ClassicVRML XSL translation autogenerated by X3dToVrml97.xslt
+# https://www.web3d.org/x3d/content/X3dToVrml97.xslt
+# Generated using XSLT processor: Saxonica
+
+PROFILE Immersive
+# [X3D] version=3.3
+# [X3D] noNamespaceSchemaLocation=https://www.web3d.org/specifications/x3d-3.3.xsd
+# [head]
+
+# Alternate encodings: VRML97, X3D ClassicVRML Encoding, X3D Compressed Binary Encoding (CBE), X3DOM, JSON
+META "title" "HelloWorld.x3d"
+META "description" "Simple X3D model example: Hello World!"
+META "created" "30 October 2000"
+META "modified" "9 July 2023"
+META "creator" "Don Brutzman"
+META "Image" "HelloWorld.tall.png"
+META "reference" "https://en.wikipedia.org/wiki/Hello_world"
+META "reference" "https://en.wikipedia.org/wiki/Hello#.22Hello.2C_World.22_computer_program"
+META "reference" "https://en.wikipedia.org/wiki/\"Hello,_World!\"_program"
+META "reference" "https://en.wikibooks.org/w/index.php?title=Computer_Programming/Hello_world"
+META "reference" "https://www.HelloWorldExample.net"
+META "reference" "https://www.web3d.org"
+META "reference" "https://www.web3d.org/realtime-3d/news/internationalization-x3d"
+META "reference" "https://www.web3d.org/x3d/content/examples/HelloWorld.x3d"
+META "reference" "https://www.web3d.org/x3d/content/examples/X3dForAdvancedModeling/HelloWorldScenes/HelloWorld.x3d"
+META "identifier" "https://www.web3d.org/x3d/content/examples/X3dForWebAuthors/Chapter01TechnicalOverview/HelloWorld.x3d"
+META "license" "https://www.web3d.org/x3d/content/examples/license.html"
+META "generator" "X3D-Edit 4.0, https://savage.nps.edu/X3D-Edit"
+META "reference" "HelloWorld.wrl"
+META "reference" "HelloWorld.x3dv"
+META "reference" "HelloWorld.x3db"
+META "reference" "HelloWorld.xhtml"
+META "reference" "HelloWorld.json"
+
+# [Scene] ========== ========== ==========
+
+NavigationInfo { type [ "EXAMINE" "ANY" ] } ###  Default X3D NavigationInfo
+
+# Example scene to illustrate X3D nodes and fields (XML elements and attributes)
+WorldInfo {
+  info [ "Example scene to illustrate a simple X3D model" ]
+  title "Hello World!"
+}
+Group {
+  children [
+      DEF ViewUpClose Viewpoint {
+        centerOfRotation 0 -1 0
+        description "Hello world!"
+        position 0 -1 7
+      }
+      Transform {
+        rotation 0 1 0 3
+        children [
+            Shape {
+              geometry Sphere {
+              }
+              appearance Appearance {
+                material DEF MaterialOffWhite Material {
+                  diffuseColor 0.980392 0.976471 0.964706
+                }
+                texture DEF ImageCloudlessEarth ImageTexture {
+                  url [ "earth-topo.png" "earth-topo.jpg" "earth-topo-small.gif" "https://www.web3d.org/x3d/content/examples/Basic/earth-topo.png" "https://www.web3d.org/x3d/content/examples/Basic/earth-topo.jpg" "https://www.web3d.org/x3d/content/examples/Basic/earth-topo-small.gif" ]
+                }
+              }
+            }
+        ]
+      }
+      Transform {
+        translation 0 -2 0
+        children [
+            Shape {
+              geometry DEF TextMessage Text {
+                string [ "Hello" "world!" ]
+                fontStyle FontStyle {
+                  justify [ "MIDDLE" "MIDDLE"  ] 
+                }
+              }
+              appearance Appearance {
+                material USE MaterialOffWhite
+              }
+            }
+        ]
+      }
+  ]
+}

+ 6 - 0
test/models/X3DV/README.md

@@ -0,0 +1,6 @@
+# X3DV models
+
+X3DV models are as simple WRL (VRML) models with the version number upgraded from `2` to `3`
+
+# HelloWorld.x3dv
+Downloaded from [HelloWorld.x3dv](http://www.web3d.org/x3d/content/examples/HelloWorld.x3dv)

BIN=BIN
test/models/X3DV/earth-topo.png


+ 4 - 0
test/models/X3DV/ref/README.md

@@ -0,0 +1,4 @@
+# X3DV 3D model reference images
+
+## HelloWorld.x3dv
+<img alt="HelloWorld.x3dv" src="screenshots/HelloWorld_x3dv.png" width=180 />

BIN=BIN
test/models/X3DV/ref/screenshots/HelloWorld_x3dv.png