Files
2026-08-14 15:56:39 -04:00

178 lines
6.8 KiB
Python

from __future__ import annotations
import importlib.util
import sys
import tempfile
import types
import unittest
from pathlib import Path
SCRIPT_DIR = Path(__file__).resolve().parent
FIXTURE_DIR = SCRIPT_DIR / "fixtures" / "mtl-space-reference"
def load_convert_module():
sys.modules.setdefault("bpy", types.ModuleType("bpy"))
sys.modules.setdefault("bmesh", types.ModuleType("bmesh"))
mathutils = sys.modules.setdefault("mathutils", types.ModuleType("mathutils"))
mathutils.Vector = object
spec = importlib.util.spec_from_file_location(
"healer_man_convert_dungeon",
SCRIPT_DIR / "convert-dungeon.py",
)
if spec is None or spec.loader is None:
raise RuntimeError("Could not load convert-dungeon.py")
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
class MtlTexturePathCorrectionTests(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.convert = load_convert_module()
def test_preserves_the_full_map_remainder_for_the_importer(self):
corrections = self.convert.mtl_texture_path_corrections(FIXTURE_DIR)
self.assertEqual(corrections, [{
"mtl": "scene.mtl",
"line": "3",
"splitReference": "CFD_METAL",
"exactReference": "CFD_METAL FLOOR.png",
"splitPath": "CFD_METAL",
"exactPath": "CFD_METAL FLOOR.png",
}])
importer = types.SimpleNamespace()
with self.convert.corrected_wowobj_texture_loader(importer, FIXTURE_DIR):
with importer.open(FIXTURE_DIR / "scene.mtl", "r", encoding="utf-8") as stream:
texture_lines = [
line.split()[1]
for line in stream
if line.strip().startswith("map_Kd")
]
self.assertEqual(texture_lines, ["CFD_METAL FLOOR.png"])
self.assertFalse(hasattr(importer, "open"))
def test_resolves_two_materials_with_the_same_truncated_token_by_line_context(self):
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
(root / "scene.mtl").write_text(
"newmtl library_trim_02\n"
"map_Kd WD_LIBRARYTRIM 02.png\n"
"newmtl library_trim_03\n"
"map_Kd WD_LIBRARYTRIM 03.png\n",
encoding="utf-8",
)
(root / "WD_LIBRARYTRIM 02.png").write_bytes(b"02")
(root / "WD_LIBRARYTRIM 03.png").write_bytes(b"03")
importer = types.SimpleNamespace()
materials = {}
material_name = ""
with self.convert.corrected_wowobj_texture_loader(importer, root) as corrections:
with importer.open(root / "scene.mtl", "r", encoding="utf-8") as stream:
for line in stream:
fields = line.split()
if not fields:
continue
if fields[0] == "newmtl":
material_name = fields[1]
elif fields[0] == "map_Kd":
materials[material_name] = fields[1]
self.assertEqual(materials, {
"library_trim_02": "WD_LIBRARYTRIM 02.png",
"library_trim_03": "WD_LIBRARYTRIM 03.png",
})
self.assertEqual(
[correction["exactReference"] for correction in corrections],
["WD_LIBRARYTRIM 02.png", "WD_LIBRARYTRIM 03.png"],
)
def test_rejects_ambiguous_candidates_when_the_exact_reference_is_absent(self):
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
(root / "scene.mtl").write_text(
"map_Kd WD_LIBRARYTRIM missing.png\n",
encoding="utf-8",
)
(root / "WD_LIBRARYTRIM 02.png").write_bytes(b"02")
(root / "WD_LIBRARYTRIM 03.png").write_bytes(b"03")
with self.assertRaisesRegex(RuntimeError, "ambiguous whitespace-truncated texture"):
self.convert.mtl_texture_path_corrections(root)
class ObjNonFiniteFaceCorrectionTests(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.convert = load_convert_module()
def test_omits_only_a_terminal_incomplete_wow_export_triangle(self):
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
obj = root / "scene.obj"
obj.write_text(
"# Exported using wow.export v0.2.19\n"
"g gates0\n"
"f 1/1/1 2/2/2 3/3/3\n"
"f 4/4/4 5/5/5 NaN/NaN/NaN\n"
"g gates1\n"
"f 6/6/6 7/7/7 8/8/8\n",
encoding="utf-8",
)
corrections = self.convert.obj_nonfinite_face_corrections(root)
self.assertEqual(corrections, [{
"obj": "scene.obj",
"line": "4",
"group": "gates0",
"sourceRecord": "f 4/4/4 5/5/5 NaN/NaN/NaN",
"validVertexIndices": [4, 5],
"danglingIndexCount": 2,
"repair": "omit-terminal-incomplete-triangle",
}])
importer = types.SimpleNamespace()
with self.convert.corrected_wowobj_texture_loader(
importer,
root,
corrections,
):
with importer.open(obj, "rb") as stream:
imported_lines = list(stream)
imported = b"".join(imported_lines)
self.assertNotIn(b"NaN", imported)
self.assertIn(b"f 1/1/1 2/2/2 3/3/3", imported)
self.assertIn(b"f 6/6/6 7/7/7 8/8/8", imported)
def test_rejects_a_nonterminal_nonfinite_face(self):
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
(root / "scene.obj").write_text(
"# Exported using wow.export v0.2.19\n"
"g gates0\n"
"f 1/1/1 2/2/2 NaN/NaN/NaN\n"
"f 1/1/1 2/2/2 3/3/3\n",
encoding="utf-8",
)
with self.assertRaisesRegex(RuntimeError, "not the terminal"):
self.convert.obj_nonfinite_face_corrections(root)
def test_rejects_nonfinite_vertex_data(self):
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
(root / "scene.obj").write_text(
"# Exported using wow.export v0.2.19\n"
"v NaN 0 0\n",
encoding="utf-8",
)
with self.assertRaisesRegex(RuntimeError, "cannot be reconstructed"):
self.convert.obj_nonfinite_face_corrections(root)
if __name__ == "__main__":
unittest.main(argv=[sys.argv[0]])