4
$\begingroup$

We can show a image (bpy.types.Image) on viewport through the following python code.
But how about a texture (bpy.types.Texture) ?

import bpy
import gpu
from gpu_extras.batch import batch_for_shader

images = tuple(bpy.data.images)
bpy.ops.image.new(name="", generated_type="COLOR_GRID")
image = next(e for e in bpy.data.images  if e not in images)

texture = gpu.texture.from_image(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')

enter image description here


How to display a Texture preview on 3D-Viewport via Gpu module?

enter image description here

enter image description here

enter image description here

$\endgroup$
4
  • $\begingroup$ does it specifically have to be 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$
    – Harry McKenzie
    Commented Mar 28 at 4:05
  • $\begingroup$ @HarryMcKenzie I use it on any type of texture. I want to show it on the viewport, same as the Preview on the panel. $\endgroup$
    – X Y
    Commented Mar 28 at 4:51
  • $\begingroup$ Yeah but procedural textures like MagicTexture and NoiseTexture don't have image data associated with it in the same way as an image texture. Procedural textures generate their patterns algorithmically based on their parameters, rather than storing pre-defined pixel data, so there is no way to get this type of data. It's only possible with the workaround I described. Are you open to such workaround? $\endgroup$
    – Harry McKenzie
    Commented Mar 28 at 4:54
  • 1
    $\begingroup$ If your approach is to render the texture and store it to an image and display it, I'm ok with that. $\endgroup$
    – X Y
    Commented Mar 28 at 5:00

1 Answer 1

4
+500
$\begingroup$

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.

enter image description here

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)
$\endgroup$
1
  • 1
    $\begingroup$ Thanks in advance, I will take a look. $\endgroup$
    – X Y
    Commented Mar 28 at 7:39

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .