diff --git a/api/requirements.txt b/api/requirements.txt index 0462a51b..a6d9199d 100644 --- a/api/requirements.txt +++ b/api/requirements.txt @@ -13,6 +13,9 @@ pymeshlab>=2023.12 # trimesh's graph engine: connected parts, hole loops and vertex merging in # inspect_mesh / repair_mesh. Optional for trimesh, so never pulled in by it. scipy>=1.10 +# trimesh decodes glTF images with Pillow. Optional for trimesh: without it, +# every op that reloads a mesh silently drops its textures. +Pillow>=10.0 # Model downloads (HuggingFace) huggingface_hub>=0.27.0 diff --git a/api/tests/test_requirements.py b/api/tests/test_requirements.py new file mode 100644 index 00000000..bbd0445f --- /dev/null +++ b/api/tests/test_requirements.py @@ -0,0 +1,26 @@ +import re +import unittest +from pathlib import Path + + +REQUIREMENTS = Path(__file__).resolve().parents[1] / "requirements.txt" + + +def declared_packages() -> set[str]: + names = set() + for line in REQUIREMENTS.read_text(encoding="utf-8").splitlines(): + line = line.split("#", 1)[0].strip() + if line: + names.add(re.split(r"[\[<>=!~ ;]", line, maxsplit=1)[0].lower()) + return names + + +class RequirementsTest(unittest.TestCase): + def test_pillow_is_declared(self) -> None: + # Without Pillow, trimesh cannot decode glTF images and silently drops + # every texture when an op reloads and re-exports a mesh. + self.assertIn("pillow", declared_packages()) + + +if __name__ == "__main__": + unittest.main()