import os import pathlib import sys import bpy PNG_SIGNATURE = b"\x89PNG\r\n\x1a\n" def save_generated_png(path, name, color): image = bpy.data.images.new(name, width=8, height=8, alpha=True) image.generated_color = color image.filepath_raw = str(path) image.file_format = "PNG" image.save() bpy.data.images.remove(image) def material_object(name, image, x): mesh = bpy.data.meshes.new(f"{name}Mesh") mesh.from_pydata([(-1, -1, 0), (1, -1, 0), (0, 1, 0)], [], [(0, 1, 2)]) obj = bpy.data.objects.new(name, mesh) obj.location.x = x bpy.context.scene.collection.objects.link(obj) material = bpy.data.materials.new(f"{name}Material") material.use_nodes = True texture = material.node_tree.nodes.new("ShaderNodeTexImage") texture.image = image material.node_tree.links.new( texture.outputs["Color"], material.node_tree.nodes["Principled BSDF"].inputs["Base Color"]) mesh.materials.append(material) def generate_library(path): bpy.ops.wm.read_factory_settings(use_empty=True) image = bpy.data.images.new("LinkedLibraryTexture", width=16, height=8, alpha=True) image.generated_color = (0.1, 0.7, 0.3, 1.0) image.use_fake_user = True bpy.ops.wm.save_as_mainfile(filepath=str(path), compress=False) def generate_matrix(output_dir, library_path, tile_paths): bpy.ops.wm.read_factory_settings(use_empty=True) generated = bpy.data.images.new("GeneratedTexture", width=12, height=6, alpha=True) generated.generated_type = "COLOR_GRID" generated.generated_color = (0.2, 0.4, 0.8, 1.0) material_object("GeneratedImageObject", generated, -4.5) udim = bpy.data.images.load(str(tile_paths[0]), check_existing=False) udim.name = "UDIMTexture" udim.source = "TILED" udim.filepath = str(tile_paths[0]).replace("1001", "") if udim.tiles.get(1002) is None: udim.tiles.new(1002, label="Second Tile") udim.pack() material_object("UDIMImageObject", udim, -1.5) missing = bpy.data.images.new("MissingExternalTexture", width=1, height=1) missing.source = "FILE" missing.filepath = "//missing/not-present.png" material_object("MissingImageObject", missing, 1.5) with bpy.data.libraries.load(str(library_path), link=True) as (data_from, data_to): if "LinkedLibraryTexture" not in data_from.images: raise RuntimeError("linked resource library image is missing") data_to.images = ["LinkedLibraryTexture"] linked = data_to.images[0] material_object("LinkedImageObject", linked, 4.5) output = output_dir / "image_resource_matrix.blend" bpy.ops.wm.save_as_mainfile(filepath=str(output), compress=False) print( "image-resource-generated " f"fixture={output} udim_tiles={[tile.number for tile in udim.tiles]} " f"udim_packed={len(udim.packed_files)}") def generate_corrupt(output_dir, png_path): bpy.ops.wm.read_factory_settings(use_empty=True) image = bpy.data.images.load(str(png_path), check_existing=False) image.name = "CorruptPackedTexture" image.pack() material_object("CorruptPackedImageObject", image, 0) output = output_dir / "corrupt_packed_image.blend" bpy.ops.wm.save_as_mainfile(filepath=str(output), compress=False) data = output.read_bytes() signature_offset = data.find(PNG_SIGNATURE) if signature_offset < 0 or data.find(PNG_SIGNATURE, signature_offset + 1) >= 0: raise RuntimeError("expected exactly one packed PNG signature in corrupt fixture") output.write_bytes(data[:signature_offset] + b"BROKEN!!" + data[signature_offset + 8:]) print(f"image-resource-corrupted fixture={output} payloadOffset={signature_offset}") def main(output_directory): output_dir = pathlib.Path(output_directory).resolve() support_dir = output_dir / "resources" support_dir.mkdir(parents=True, exist_ok=True) tile_1001 = support_dir / "udim_1001.png" tile_1002 = support_dir / "udim_1002.png" corrupt_png = support_dir / "corrupt_source.png" bpy.ops.wm.read_factory_settings(use_empty=True) save_generated_png(tile_1001, "Tile1001Source", (1.0, 0.1, 0.1, 1.0)) save_generated_png(tile_1002, "Tile1002Source", (0.1, 0.2, 1.0, 1.0)) save_generated_png(corrupt_png, "CorruptSource", (0.8, 0.2, 0.7, 1.0)) library_path = support_dir / "image_resource_library.blend" generate_library(library_path) generate_matrix(output_dir, library_path, (tile_1001, tile_1002)) generate_corrupt(output_dir, corrupt_png) if __name__ == "__main__": arguments = sys.argv[sys.argv.index("--") + 1:] if len(arguments) != 1: raise SystemExit("usage: blender -b --python generate-image-resource-fixtures.py -- output-directory") main(arguments[0])