Toolkit for validating the reference integrity of OBJ bundles (face indices, mtllib, usemtl, map_* references). Part of the 3D Preservation Research project.
The toolkit ships in three forms:
validate_obj_refs.py- command-line script. Stdlib-only Python.validate_obj_gui.py- desktop GUI on top of the same checks. Tkinter, multilingual (NL/FR/EN).- Standalone executable built from the GUI with PyInstaller. No Python install required for end users.
Download the executable for your platform from the project release folder:
- Windows:
validate_obj_gui.exe- double-click to run. - macOS:
validate_obj_gui.app- double-click to run. On first launch you may need to right-click and choose "Open" to bypass Gatekeeper. - Linux:
validate_obj_gui- right-click, Properties, open "Permissions" tab and check "Allow executing file as program". Or mark executable withchmod +x validate_obj_gui, and double-click to open.
On first launch you are asked to pick an interface language (NL / FR / EN). The choice is saved in ~/.validate_obj_gui_settings.json and can be changed later via Options > Language.
- Add file / Add folder (recursive) to load OBJ files into the table. Folders are scanned for
.objrecursively. - Remove selection (or press Delete / Backspace on the selected rows) takes individual files out of the list without wiping the rest. Use Ctrl/Shift to select multiple rows.
- Output folder (required): pick a folder via Browse. The combined report files land here. There is no per-file output mode; one report per run covers the whole batch.
- Report formats (1 file per run): tick Text (.txt) (default on) and/or CSV (.csv) (default off). With one tick you get one file; with both you get a
.txtand a.csvside by side. Filenames arevalidation_batch_<date>_<time>.txtand.csv. - Validate starts the run. Progress streams to the log panel: stage messages plus a tick every 5 million lines for large files.
- Cancel stops the run cleanly between files. The currently-processing file finishes naturally so its data is included in the combined report. Remaining files stay as
Waiting; click Validate again to resume.
Requires Python 3.10 or newer with tkinter available.
git clone <repo-url>
cd tools/
python3 validate_obj_refs.py path/to/MODEL.obj # CLI
python3 validate_obj_gui.py # GUIIf python3 complains about missing _tkinter:
- Fedora / RHEL:
sudo dnf install python3-tkinter - Debian / Ubuntu:
sudo apt install python3-tk - macOS (python.org installer): tkinter is bundled
- macOS (Homebrew Python):
brew install python-tk@3.13(match your Python version) - Windows (python.org installer): tkinter is bundled
CLI exit codes: 0 all checks passed, 1 one or more failed, 2 usage or I/O error.
PyInstaller cannot cross-compile. To produce a Windows .exe you need a Windows machine; for a macOS .app you need a Mac; for a Linux binary you need Linux. The same build.py script works on all three.
Each platform follows the same three steps: copy the source folder to the target machine, set up a Python venv with PyInstaller, run python build.py. Concrete commands per platform below.
Copy the source folder to the Linux machine, e.g. ~/scripts/build-validator/. Then in a terminal:
cd ~/scripts/build-validator
python3 -m venv .venv-build
source .venv-build/bin/activate
pip install pyinstaller
python build.pyOutput: dist/validate_obj_gui (~16 MB ELF binary).
If python3 -m venv fails on Fedora with a missing ensurepip error: sudo dnf install python3-tkinter covers most missing pieces in one go.
Copy the source folder to the Windows machine, e.g. C:\Users\<you>\scripts\build-validator\. Open PowerShell:
cd C:\Users\<you>\scripts\build-validator
python -m venv .venv-build
.venv-build\Scripts\activate
pip install pyinstaller
python build.pyOutput: dist\validate_obj_gui.exe (~25 MB single-file executable).
Prerequisite: Python 3.10+ installed via the python.org installer with "Add Python to PATH" ticked. Tkinter is bundled.
Copy the source folder to the Mac, e.g. ~/scripts/build-validator/. Transfer methods that work: AirDrop, USB, NAS share, or RustDesk file-transfer. Open Terminal:
cd ~/scripts/build-validator
python3 -m venv .venv-build
source .venv-build/bin/activate
pip install pyinstaller
python build.pyOutput in dist/:
validate_obj_gui.app(~25 MB app bundle, double-clickable in Finder)validate_obj_gui(standalone binary for terminal use)
Prerequisites: Python 3.10+ from the python.org installer (the .pkg), not Homebrew. The python.org installer brings Tkinter along; Homebrew Python often needs python-tk@3.x separately.
python build.py accepts two optional flags:
--cleanremovesbuild/anddist/before rebuilding. Use after a script update to avoid stale caches.--debugkeeps the console window visible on Windows/macOS and enables verbose PyInstaller output. Useful when the GUI silently crashes on launch and you need to see the traceback.
Windows: the build is silent (no console window pops up next to the GUI). For debugging during development use python build.py --debug to keep a visible console, which prints uncaught Python errors. Antivirus tools sometimes flag fresh PyInstaller binaries as suspicious. If you see a SmartScreen warning ("Windows protected your PC"), they can click "More info" then "Run anyway".
macOS: Apple requires that .app bundles distributed outside the App Store be either notarised or run with a manual right-click > Open the first time. Without notarisation, double-clicking will show "cannot be opened because the developer cannot be verified". Notarisation requires an Apple Developer account ($99/year). When and how this blocks depends on the Macos version. it was tested it on Tahoe, the app always opens after the second time.
Linux: the binary is a statically-bundled ELF that includes its own Python interpreter and tkinter. It runs on most modern distributions without further dependencies. Tested on Fedora 43. On older glibc versions the binary may not start; in that case build on the oldest target distribution.
Five reference-integrity and spec-compliance checks per OBJ file:
- Face indices (
f) fall within the range ofv/vt/vncounts in the file. Index 0 is invalid (OBJ uses 1-based indexing) and is reported. - Every
mtllibreference points to an existing.mtlfile in the same folder. - Every
usemtlin the OBJ matches anewmtldefinition in one of the loaded MTLs. - Every
map_*directive in the MTLs points to an existing texture file. - Every
usemtlandnewmtlname uses only allowed characters: alphanumeric, underscore, hyphen and dot. Embedded spaces, quotes, brackets, parentheses, slashes and other punctuation are flagged as spec violations. (Strict spec is alphanumeric + underscore only; hyphen and dot are accepted as common-practice exceptions: hyphen for human-readable names likewood-grain, dot for Blender auto-numbering likeMaterial.001.)
These checks complement (not replace) GUI viewers like MeshLab/Blender and format identifiers like DROID/Siegfried. Viewers catch missing textures visually, but silently ignore other reference issues. Identifiers verify PRONOM signatures but not internal consistency. This tool fills that gap with deterministic, scriptable checks suitable for preservation workflows and bulk validation.
The validator never modifies the OBJ, MTL, or texture files it inspects. All source-file access is read-only:
validate_obj_refs.pyopens OBJ and MTL files with mode"r"(read-only) and only ever callsis_file()/resolve()(metadata) on textures.- The GUI worker thread uses the same code path and adds no writes.
- The only files the tool does create are its own report outputs:
validation_batch_<date>_<time>.txtand/orvalidation_batch_<date>_<time>.csv. These land in the user-chosen output folder, never next to the OBJ files. Reports are written via atomic.tmp+ rename so a crash mid-write leaves no truncated file.
This matches preservation requirements where the archived bundle must remain bit-identical before and after inspection. If you need a stronger guarantee, hash the source files before and after a validation run and compare; the hashes will be identical.
Path resolution and matching:
- MTL and texture filenames in the source files are resolved relative to the OBJ's parent folder (the same convention MeshLab and Blender use).
- Filename existence is checked via the host filesystem, so case-sensitivity follows the host: case-sensitive on Linux and macOS (default APFS configuration), case-insensitive on Windows (NTFS default). A mismatch in casing (e.g.
texture.TIFin the MTL vs.texture.tifon disk) will surface as missing on Linux but pass silently on Windows. usemtlandnewmtlmatching is strict literal string comparison. The OBJ specification describes material names as alphanumeric tokens with underscores; the spec does not define a quote-stripping or escape mechanism. The validator therefore treats"material_1"(with quotes) andmaterial_1(without) as different names, which mirrors the behaviour of MeshLab and other parsers. A separate spec-compliance check (number 5 above) flags any name containing characters outside[A-Za-z0-9_], so non-spec names typically fail twice: once on the lookup mismatch (check 3) and once on the character-set violation (check 5). Both failures point at the same root cause.- Negative (relative) face indices like
f -1 -2 -3are recognised but not validated. They are legal per the OBJ spec but rare in modern exports. - Line continuations (
\at end of line) are not interpreted; the next line is treated as a separate (likely unrecognised) line. Also rare in modern exports.
What the tool does NOT check:
- Geometry quality (degenerate faces, non-manifold edges, normal direction).
- Semantic correctness of materials (whether the colour values are sensible).
- Whether the OBJ describes a coherent 3D model rather than random triangles.
- Image content of texture files (whether they are valid PNG/JPEG/TIFF, sufficient resolution, etc.).
These belong to the broader validation workflow, where tools like trimesh, Assimp, and viewers like MeshLab take over.
The validator and the GUI are written against the Python standard library only. No third-party packages are imported at runtime. This keeps the tool installable without pip, easy to audit, and unaffected by upstream package churn.
Runtime imports:
argparse,pathlib,dataclasses,sys,typing,re- CLI parsing, path handling, structured data, name-pattern matching.csv- writing the combined CSV report.tkinter(incl.tkinter.ttk,tkinter.filedialog,tkinter.messagebox) - desktop GUI. Bundled with most Python installs; on Fedora available viapython3-tkinter, on Debian/Ubuntu viapython3-tk.threading,queue- background validation worker so the GUI stays responsive on large OBJ files.json,datetime- persistent language preference and timestamped log entries.
Build-time dependency:
- PyInstaller - only needed if you want to produce a standalone executable. Installed inside the build venv (
pip install pyinstaller). Not used at runtime: the produced executable bundles its own Python interpreter and standard library, so end users have nothing to install.
Validators we deliberately do NOT depend on:
- trimesh and Assimp - both are excellent OBJ parsers. We avoid them by design: they would parse the OBJ a second time, which defeats the purpose of an independent integrity check. The broader validation workflow uses them in parallel as cross-checks; this tool fills the gap they leave open (reference integrity).