from __future__ import annotations import json import struct import sys from pathlib import Path import bpy BLENDER_ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(BLENDER_ROOT)) import material_id_baker # noqa: E402 def make_test_object() -> bpy.types.Object: mesh = bpy.data.meshes.new("MaterialIDSmokeMesh") mesh.from_pydata( [ (-1.0, -1.0, 0.0), (0.0, -1.0, 0.0), (0.0, 1.0, 0.0), (-1.0, 1.0, 0.0), (0.0, -1.0, 0.0), (1.0, -1.0, 0.0), (1.0, 1.0, 0.0), (0.0, 1.0, 0.0), ], [], [(0, 1, 2, 3), (4, 5, 6, 7)], ) mesh.update() left = bpy.data.materials.new("Left Material") right = bpy.data.materials.new("Right Material") mesh.materials.append(left) mesh.materials.append(right) mesh.polygons[0].material_index = 0 mesh.polygons[1].material_index = 1 uv_layer = mesh.uv_layers.new(name="ID UV") uv_coordinates = ( (0.05, 0.05), (0.45, 0.05), (0.45, 0.95), (0.05, 0.95), (0.55, 0.05), (0.95, 0.05), (0.95, 0.95), (0.55, 0.95), ) for loop, uv in zip(mesh.loops, uv_coordinates, strict=True): uv_layer.uv[loop.index].vector = uv obj = bpy.data.objects.new("MaterialIDSmokeObject", mesh) bpy.context.scene.collection.objects.link(obj) obj.modifiers.new(name="Triangulate for evaluated-mesh test", type="TRIANGULATE") obj.select_set(True) bpy.context.view_layer.objects.active = obj return obj def pixel(image: bpy.types.Image, x: int, y: int) -> tuple[float, float, float, float]: offset = (y * image.size[0] + x) * 4 return tuple(image.pixels[offset : offset + 4]) def close_rgb(actual: tuple[float, ...], expected: list[float], tolerance: float = 0.03) -> bool: return all(abs(actual[index] - expected[index]) <= tolerance for index in range(3)) def main() -> None: material_id_baker.register() source = make_test_object() original_engine = bpy.context.scene.render.engine bpy.context.scene.cycles.samples = 37 original_cycles_samples = bpy.context.scene.cycles.samples settings = bpy.context.scene.material_id_baker settings.resolution = "256" settings.margin = 4 settings.palette_mode = "DISTINCT" settings.apply_modifiers = True settings.create_export_copy = True settings.quick_export_glb = True settings.glb_filepath = "/tmp/material_id_baker_smoke_export" settings.image_name = "Material_ID_Smoke" settings.save_to_disk = True settings.filepath = "/tmp/material_id_baker_smoke.png" settings.write_legend = True result = bpy.ops.object.bake_material_id() assert result == {"FINISHED"}, result assert bpy.context.scene.render.engine == original_engine assert bpy.context.scene.cycles.samples == original_cycles_samples assert not any(item.name.startswith("__MID_BAKER_") for item in bpy.data.objects) assert not any(item.name.startswith("__MID_BAKER_") for item in bpy.data.materials) export_copy = bpy.context.active_object assert export_copy is not None assert export_copy.name == "MaterialIDSmokeObject_Baked_IDs" assert export_copy != source assert export_copy.select_get() assert not source.select_get() assert len(export_copy.data.materials) == 1 baked_material = export_copy.data.materials[0] assert baked_material.name == "baked ids" assert all(polygon.material_index == 0 for polygon in export_copy.data.polygons) assert len(export_copy.data.polygons) == 4 assert len(export_copy.modifiers) == 0 image = bpy.data.images[settings.last_image_name] image_nodes = [ node for node in baked_material.node_tree.nodes if node.bl_idname == "ShaderNodeTexImage" ] assert len(image_nodes) == 1 assert image_nodes[0].image == image palette = json.loads(image["material_id_palette"]) assert len(palette) == 2 assert palette[0]["material"] == "Left Material" assert palette[1]["material"] == "Right Material" left_pixel = pixel(image, 64, 128) right_pixel = pixel(image, 192, 128) assert close_rgb(left_pixel, palette[0]["rgb"]), (left_pixel, palette[0]) assert close_rgb(right_pixel, palette[1]["rgb"]), (right_pixel, palette[1]) assert Path("/tmp/material_id_baker_smoke.png").is_file() assert Path("/tmp/material_id_baker_smoke.json").is_file() glb_path = Path("/tmp/material_id_baker_smoke_export.glb") assert glb_path.is_file() glb_data = glb_path.read_bytes() magic, version, total_length = struct.unpack_from("<4sII", glb_data, 0) assert magic == b"glTF" assert version == 2 assert total_length == len(glb_data) json_length, json_type = struct.unpack_from("