168 lines
6.1 KiB
Python
168 lines
6.1 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
import sys
|
|
import tempfile
|
|
import unittest
|
|
from pathlib import Path
|
|
|
|
from PIL import Image
|
|
|
|
|
|
TOOLS_ROOT = Path(__file__).resolve().parents[1]
|
|
sys.path.insert(0, str(TOOLS_ROOT))
|
|
|
|
import combine_substance_textures as combine # noqa: E402
|
|
|
|
|
|
class CombineSubstanceTexturesTest(unittest.TestCase):
|
|
def setUp(self) -> None:
|
|
self.temp_dir = tempfile.TemporaryDirectory(prefix="substance-combine-test-")
|
|
self.root = Path(self.temp_dir.name)
|
|
self.input_dir = self.root / "exports"
|
|
self.output_dir = self.root / "combined"
|
|
self.input_dir.mkdir()
|
|
|
|
self.id_map = self.root / "Material_ID.png"
|
|
id_image = Image.new("RGB", (4, 2), (255, 0, 0))
|
|
for y in range(2):
|
|
for x in range(2, 4):
|
|
id_image.putpixel((x, y), (0, 255, 0))
|
|
id_image.save(self.id_map)
|
|
|
|
self.legend = self.root / "Material_ID.json"
|
|
self.legend.write_text(
|
|
json.dumps(
|
|
{
|
|
"materials": [
|
|
{"slot": 1, "material": "Left Metal", "rgb": [1.0, 0.0, 0.0]},
|
|
{"slot": 2, "material": "Right Paint", "rgb": [0.0, 1.0, 0.0]},
|
|
]
|
|
}
|
|
),
|
|
encoding="utf-8",
|
|
)
|
|
|
|
def tearDown(self) -> None:
|
|
self.temp_dir.cleanup()
|
|
|
|
def write_texture(self, name: str, color: tuple[int, int, int]) -> None:
|
|
Image.new("RGB", (4, 2), color).save(self.input_dir / name)
|
|
|
|
def test_combines_multiple_channels_by_id_color(self) -> None:
|
|
self.write_texture("Robot_Left_Metal_BaseColor.png", (180, 20, 10))
|
|
self.write_texture("Robot_Right_Paint_BaseColor.png", (10, 40, 220))
|
|
self.write_texture("Robot_Left_Metal_Normal.png", (100, 120, 255))
|
|
self.write_texture("Robot_Right_Paint_Normal.png", (140, 125, 250))
|
|
|
|
outputs, unmatched = combine.combine_all(
|
|
id_map_path=self.id_map,
|
|
legend_path=self.legend,
|
|
input_dir=self.input_dir,
|
|
output_dir=self.output_dir,
|
|
)
|
|
|
|
self.assertEqual(unmatched, [])
|
|
self.assertEqual(
|
|
{path.name for path in outputs},
|
|
{"Robot_BaseColor.png", "Robot_Normal.png"},
|
|
)
|
|
with Image.open(self.output_dir / "Robot_BaseColor.png") as result:
|
|
self.assertEqual(result.getpixel((0, 0)), (180, 20, 10))
|
|
self.assertEqual(result.getpixel((3, 1)), (10, 40, 220))
|
|
|
|
def test_aliases_and_missing_materials(self) -> None:
|
|
self.write_texture("asset_left_roughness.png", (32, 32, 32))
|
|
materials = combine.load_legend(
|
|
self.legend,
|
|
{"left metal": "left", "right paint": "right"},
|
|
)
|
|
groups, unmatched = combine.discover_groups(self.input_dir, materials)
|
|
self.assertEqual(unmatched, [])
|
|
self.assertEqual(len(groups), 1)
|
|
self.assertEqual(groups[0].output_name, "asset_roughness.png")
|
|
self.assertEqual(set(groups[0].textures), {"left metal"})
|
|
|
|
def test_rejects_mismatched_resolution(self) -> None:
|
|
Image.new("RGB", (8, 8), (1, 2, 3)).save(
|
|
self.input_dir / "Left_Metal_BaseColor.png"
|
|
)
|
|
with self.assertRaisesRegex(combine.CombineError, "ID map"):
|
|
combine.combine_all(
|
|
id_map_path=self.id_map,
|
|
legend_path=self.legend,
|
|
input_dir=self.input_dir,
|
|
output_dir=self.output_dir,
|
|
)
|
|
|
|
def test_rejects_overlapping_tolerant_masks(self) -> None:
|
|
self.legend.write_text(
|
|
json.dumps(
|
|
{
|
|
"materials": [
|
|
{"slot": 1, "material": "Left", "rgb": [1 / 255, 0.0, 0.0]},
|
|
{"slot": 2, "material": "Right", "rgb": [2 / 255, 0.0, 0.0]},
|
|
]
|
|
}
|
|
),
|
|
encoding="utf-8",
|
|
)
|
|
Image.new("RGB", (4, 2), (1, 0, 0)).save(self.id_map)
|
|
self.write_texture("Left_BaseColor.png", (10, 10, 10))
|
|
self.write_texture("Right_BaseColor.png", (20, 20, 20))
|
|
with self.assertRaisesRegex(combine.CombineError, "overlaps"):
|
|
combine.combine_all(
|
|
id_map_path=self.id_map,
|
|
legend_path=self.legend,
|
|
input_dir=self.input_dir,
|
|
output_dir=self.output_dir,
|
|
tolerance=1,
|
|
)
|
|
|
|
def test_auto_tolerance_handles_float_to_png_rounding(self) -> None:
|
|
self.legend.write_text(
|
|
json.dumps(
|
|
{
|
|
"materials": [
|
|
{"slot": 1, "material": "Left", "rgb": [0.1, 0.2, 1.0]},
|
|
]
|
|
}
|
|
),
|
|
encoding="utf-8",
|
|
)
|
|
# Python rounds 0.1 * 255 to 26, while Blender may store it as 25.
|
|
Image.new("RGB", (4, 2), (25, 51, 255)).save(self.id_map)
|
|
self.write_texture("Left_BaseColor.png", (12, 34, 56))
|
|
outputs, _ = combine.combine_all(
|
|
id_map_path=self.id_map,
|
|
legend_path=self.legend,
|
|
input_dir=self.input_dir,
|
|
output_dir=self.output_dir,
|
|
)
|
|
with Image.open(outputs[0]) as result:
|
|
self.assertEqual(result.getpixel((0, 0)), (12, 34, 56))
|
|
|
|
def test_recursive_material_directories_are_grouped(self) -> None:
|
|
left_dir = self.input_dir / "Robot_Left_Metal"
|
|
right_dir = self.input_dir / "Robot_Right_Paint"
|
|
left_dir.mkdir()
|
|
right_dir.mkdir()
|
|
Image.new("L", (4, 2), 40).save(left_dir / "Roughness.png")
|
|
Image.new("L", (4, 2), 210).save(right_dir / "Roughness.png")
|
|
|
|
outputs, _ = combine.combine_all(
|
|
id_map_path=self.id_map,
|
|
legend_path=self.legend,
|
|
input_dir=self.input_dir,
|
|
output_dir=self.output_dir,
|
|
recursive=True,
|
|
)
|
|
self.assertEqual(outputs[0].relative_to(self.output_dir), Path("Robot/Roughness.png"))
|
|
with Image.open(outputs[0]) as result:
|
|
self.assertEqual(result.getpixel((0, 0)), 40)
|
|
self.assertEqual(result.getpixel((3, 1)), 210)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|