178 lines
6.8 KiB
Python
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]])
|