From 50463fe20028bb2323ee9774a94fa22d75b3d37a Mon Sep 17 00:00:00 2001 From: Lightning Pixel Date: Thu, 12 Nov 2026 11:19:45 +0100 Subject: [PATCH] fix(deps): declare Pillow so mesh ops keep their textures trimesh decodes glTF images with Pillow but does not depend on it. The setup venv therefore had no Pillow, and every op that reloads and re-exports a mesh (presets, transforms, normals) silently wrote it back without its textures. The dev .venv happened to have it, which hid the bug. Changing requirements.txt also changes its hash, so existing installs re-run setup and get Pillow on the next launch. --- api/requirements.txt | 3 +++ api/tests/test_requirements.py | 26 ++++++++++++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 api/tests/test_requirements.py 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()