Procedural textures like bpy.types.MagicTexture
and bpy.types.NoiseTexture
are generated algorithmically within Blender and do not have direct image data associated with them. Unlike bpy.types.ImageTexture
, which has an image
attribute of type bpy.types.Image
, procedural textures cannot be accessed or manipulated directly.
To work with procedural textures programmatically, you'll need to create a workaround by scripting a material node setup that utilizes the procedural texture, renders the scene to produce the texture's result, and then saves the resulting image. This image can then be loaded and displayed in the 3D View for further manipulation or visualization.
I've created a sample script that automates the creation of a temporary camera, a plane, and material using a MagicTexture
. The script then renders the scene to generate the texture's output and utilizes the gpu module to draw and display the rendered texture. Then deletes the temporary objects. So it doesn't matter what is on your scene, it will make sure not to interfere with anything.
import bpy
import os
import tempfile
import shutil
from mathutils import Euler
BASEDIR=tempfile.mkdtemp()
FILENAME="img.png"
FILETYPE="PNG"
RESOLUTION=300
C = bpy.context
D = bpy.data
previous_selection = C.selected_objects[:]
previous_active_object = C.active_object
mat = D.materials.new(name="temp_material")
mat.use_nodes = True
nodes = mat.node_tree.nodes
# create procedural texture
texture_node = nodes.new(type='ShaderNodeTexMagic')
# or get a regular image texture from an image:
#texture_node = mat.node_tree.nodes.new(type='ShaderNodeTexImage')
#texture_node.image = D.textures['Texture'].image
mat.node_tree.links.new(texture_node.outputs['Color'], nodes.get('Material Output').inputs['Surface'])
bpy.ops.mesh.primitive_plane_add(size=2)
offset = 1000
plane = C.active_object
plane.data.materials.clear()
plane.data.materials.append(mat)
plane.location = (0, 0, 0 + offset)
plane.name = "temp_plane"
prev_cam = C.scene.camera
bpy.ops.object.camera_add()
cam = C.active_object
cam.name = "temp_camera"
cam.rotation_euler = Euler((0, 0, 0), 'XYZ')
cam.data.type = 'ORTHO'
camera_distance = 2
cam.location = (0, 0, camera_distance + offset)
cam.select_set(False)
C.scene.render.resolution_x = RESOLUTION
C.scene.render.resolution_y = RESOLUTION
C.scene.camera = cam
cam.data.ortho_scale = (plane.dimensions[0] / 2) * camera_distance
for obj in previous_selection:
obj.select_set(True)
if previous_active_object:
C.view_layer.objects.active = previous_active_object
os.makedirs(BASEDIR, exist_ok=True)
C.scene.render.filepath = os.path.join(BASEDIR, FILENAME)
C.scene.render.image_settings.file_format = FILETYPE
bpy.ops.render.render(animation=False, write_still=True)
C.scene.camera = prev_cam
D.materials.remove(mat)
D.objects.remove(plane, do_unlink=True)
D.objects.remove(cam, do_unlink=True)
import gpu
from gpu_extras.batch import batch_for_shader
print("Load image from: ", C.scene.render.filepath)
render_result_image = D.images.load(C.scene.render.filepath)
texture = gpu.texture.from_image(render_result_image)
shader = gpu.shader.from_builtin('IMAGE')
batch = batch_for_shader(
shader, 'TRI_FAN',
{
"pos": ((100, 100), (200, 100), (200, 200), (100, 200)),
"texCoord": ((0, 0), (1, 0), (1, 1), (0, 1)),
},
)
def draw():
shader.bind()
shader.uniform_sampler("image", texture)
batch.draw(shader)
bpy.types.SpaceView3D.draw_handler_add(draw, (), 'WINDOW', 'POST_PIXEL')
shutil.rmtree(BASEDIR)
bpy.types.MagicTexture
? or is it to be an external image to be loaded as texture? because it will not work for procedural textures, you will have to do a workaround by scripting a material nodesetup which gets rendered out and then resulting image gets loaded and drawn on 3D View. it works in my case. $\endgroup$