|
FhSim
3.1.0
Marine systems simulation
|
| ID | 0009 |
| Class | BUG |
| Severity | 3 |
| Status | blocked |
| Models | — |
| Found | 2026-09-23, first external-host run of a visual FMU built on fhsim_environment (fhsim_fishery's AuvSurvey scenario, fhsim_fmu pipeline) |
| Decision needed | Where the fix belongs. The exporter can make the FMU layout match the RUNPATH the modules were linked with — copy the Ogre plugins flat into binaries/<platform>/, which $ORIGIN/.. already covers, and point PluginFolder there — or patch the copied modules' RUNPATH. Upstream, the Ogre plugins should arguably not be on any link line at all: they exist to be loaded by Ogre::Root from PluginFolder, and a DT_NEEDED on Codec_Assimp.so.14.5 is what makes --with-codec-assimp load-bearing for a model that never imports a mesh. That second fix is in the SimObject libraries' build (and the ogre3d Conan package's link list), not here. Which one is taken decides whether --with-codec-assimp stays optional. |
An fmpy simulate of a visual FMU exported from the pipeline's visual playpen fails at fmi2ExitInitializationMode:
Error opening dll: Codec_Assimp.so.14.5: cannot open shared object file: No such file or directory
That message is FhSim's, from src/engine/license/LicenseManager.cpp:287:
m_pPrint->ReportError("Error opening dll: " + std::string(dlerror()));
and it is reached when the dlopen at LicenseManager.cpp:277 returns null. The name in the message is not what was opened. dllPath there is the SimObject module, SimObjectLibraries/libfhsim_environmentVis.so (built at lines 251-263) — the message passes dlerror() through verbatim, and what dlerror() names after a failed dlopen is the dependency the loader could not resolve, not the file that was requested. So this is one dlopen of the module, failing on the module's own DT_NEEDED entry.
readelf -d on that module, from the visual playpen fhsim_fmu/tools/assemble_playpen.py builds out of the 3.1.0 packages:
$ readelf -d playpen/bin/SimObjectLibraries/libfhsim_environmentVis.so (NEEDED) Shared library: [Codec_Assimp.so.14.5] (NEEDED) Shared library: [Codec_STBI.so.14.5] (NEEDED) Shared library: [libOgreMain.so.14.5] … (RUNPATH) Library runpath: [$ORIGIN/../../lib:$ORIGIN/..:$ORIGIN/../../lib/OGRE]
Two Ogre plugins are link-time dependencies of a SimObject module, and the RUNPATH is the one set in fhsim_environment/src/CMakeLists.txt:31. The plugins are on the link line because the ogre3d Conan package lists them among its libraries — build/Release/generators/Ogre3D-release-x86_64-data.cmake:60 has Codec_Assimp.so and Codec_STBI.so in ogre3d_LIBS_RELEASE, beside OgreMain — and this repository puts that target on every visual SimObject's link chain (src/simobject/CMakeLists.txt:28, OgreDerived::ogrederived as a PUBLIC dependency of simobject). Which edge actually records the two entries was not determined; what is observed is that among the playpen's five visual modules only libfhsim_environmentVis.so has them, and it is the only one that also links marenv::marenv.
The three RUNPATH entries all miss in the FMU. A bundled module sits at binaries/<platform>/SimObjectLibraries/, so $ORIGIN/../../lib is <fmu>/lib (an FMU has no lib), $ORIGIN/.. is binaries/<platform> — where the Ogre libraries are, which is why libOgreMain.so.14.5 resolves — and $ORIGIN/../../lib/OGRE is <fmu>/lib/OGRE, which does not exist either. The plugins are put somewhere else: copyOgreLibraries copies the playpen's lib/OGRE directory to binaries/<platform>/OGRE (ResourceBundler.cpp:298-304, destination at line 281), and patchOgreConfigFiles rewrites PluginFolder to match (ResourceBundler.cpp:427-428). That is correct for Ogre, which reads the path out of plugins.cfg; it is invisible to the dynamic loader, which only has the module's RUNPATH.
Three things follow, and each was checked:
--with-codec-assimp does not help.** Re-exporting with it places Codec_Assimp.so.14.5 in the archive at binaries/linux64/OGRE/, and the error message is byte-for-byte the same. The file is in the FMU and still not on the search path.plugins.cfg is not involved. The exported file has # Plugin=Codec_Assimp commented out, exactly as isOmittedOgrePlugin and the Plugin= rewrite intend (ResourceBundler.cpp:256-259, 436-441). Ogre is never asked to load the codec. The loader wants it anyway, before main in the module gets a chance to run.Codec_STBI.so.14.5 is a DT_NEEDED of the same module and lands in the same OGRE/ subdirectory, and nothing omits it. Codec_Assimp is only named first because it is first in the list.Why the pipeline's own checks pass. MassSpring, the model the FMU tests and FHSIM-0003's verification use, loads libfhsim_baseVis.so, and none of libfhsim_baseVis.so, libfhsim_coriboVis.so, libfhsim_fisheryVis.so or libfhsim_marine_elementsVis.so has a Codec_* NEEDED entry — only the environment module does. So a visual FMU fails exactly when the model uses a fhsim_environment SimObject, which the AUV survey scenario does for its scalar fields, and no model in this repository's tests does. fhsim_fmu/tools/validate_fmus.py:210-216 would not catch it either: its RUNPATH check globs binaries/*/*.so, which is neither the SimObjectLibraries/ subdirectory nor the versioned *.so.14.5 spellings.
A host cannot work around it by turning the renderer off. Since FHSIM-0003 the visual variant exports visualization[0] as variability="constant" start="true", so an importer must refuse the write; and the failure is in the loader, before anything reads the parameter, so even a settable one would not help.
A visual FMU of any model that uses a fhsim_environment SimObject cannot be initialised by an external FMI host. fmpy simulate stops at fmi2ExitInitializationMode with a message naming a file that is inside the FMU, which sends the reader looking for a missing binary instead of a search path. Since the export itself succeeds and every static check passes — archive layout, exported symbols, fmpy validate — the artefact is published and fails only when someone runs it.
The same archive works when it is unpacked next to a playpen-shaped tree, because then $ORIGIN/../../lib/OGRE happens to resolve. That is why the model runs under FhSim in the playpen and not as an FMU.
The headless variant is not expected to be affected: the plain libfhsim_environment.so carries no Codec_* dependency at all (checked with readelf -d on the assembled headless playpen), only the same RUNPATH. It could not be confirmed end to end for the model that found this, because that model's headless export aborts on FHSIM-0008 before an archive exists to run.
lib/OGRE contents into binaries/<platform>/ beside the Ogre libraries rather than into an OGRE/ subdirectory, and set PluginFolder to the platform directory itself. Then $ORIGIN/.. — already the entry that resolves libOgreMain — covers the plugins too. Self-contained in ResourceBundler.cpp (the copy at lines 298-304 and the rewrite at lines 427-428), and it changes the FMU layout, which tests/fmu/CheckFmuArchive.cmake asserts on and doc/user/using-the-api/fmu-export.md:57 documents.patchelf or an equivalent. Keeps the layout but makes the export depend on a tool it does not use today, and it patches binaries produced by other repositories.Ogre::Root from PluginFolder; a DT_NEEDED on one is what makes an unused codec mandatory at load time. Dropping Codec_* (and Plugin_*, RenderSystem_*) from the ogre3d link list, or linking the Ogre target with --as-needed, removes the problem at its source and shrinks the runtime dependency surface of every visual SimObject module. Not a change in this repository, and it needs the SimObject libraries rebuilt and republished.1 is the smallest change that makes today's published archives work. 3 is the one that makes the dependency honest, and without it --with-codec-assimp keeps meaning "the FMU
loads at all" rather than "the FMU can import meshes".
Export a visual FMU of a model that instantiates a fhsim_environment SimObject and run fmpy simulate on it: it fails at fmi2ExitInitializationMode at HEAD. This repository's FMU tests cannot host that directly — they have no fhsim_environment — but they can pin the mechanism without it: extend tests/fmu/CheckBundledRunpath.cmake (or add a check beside it) to walk every *.so* in the archive, read each NEEDED entry, and require that each one resolves against that file's own RUNPATH with $ORIGIN expanded, ignoring the system libraries. Against a visual export of a model using the environment module that fails on Codec_Assimp.so.14.5; it also catches any future module whose dependencies the archive layout does not satisfy, which is the general form of this bug.
fhsim_fmu/tools/validate_fmus.py should get the same walk — its current glob (binaries/*/*.so) sees neither SimObjectLibraries/ nor the versioned Ogre spellings. It has no issues/ tracker of its own, so that is noted here.
Option 1 relocates about a dozen files inside the archive. Nothing in the FMU addresses them by path except plugins.cfg, which the exporter writes itself, so the risk is in what reads the archive rather than in what runs it: CheckFmuArchive.cmake pairs Codec_Assimp in the archive with the Plugin= line in plugins.cfg and would need to look in the new place, and the documented layout table changes. No simulation numerics are involved in any of the three options — either the module loads or it does not.
Option 3 changes the link line of every visual SimObject module in the ecosystem. It removes dependencies rather than adding them, but it is a rebuild of every library and it would be worth confirming that no SimObject actually calls into a codec directly before the entries go.