0
$\begingroup$

I try to combine random 3D Models with random Textures. I know the results will probably will look sketchy but that is fine.

But now to my problem: It seems like it doesn't work applying a texture to an object in blender python script. There is some problem with my code. I'm not sure how to apply a texture to an 3D Model/Material.

As example I try to load a texture from a game to a banana 3D Model. I show you both here: banana, 3D Model texture of a game

I load and apply the texture with following code:

def load_texture(file_path, obj, apply=True):
"""
Creates a material, loads a texture and applies it to active material, if apply is true.
"""
# create new material
material = bpy.data.materials.new('My_Awesome_Texture_Material')
material.use_nodes = True

# create texture and apply to material
material_shader = material.node_tree.nodes["Principled BSDF"]
texture = material.node_tree.nodes.new('ShaderNodeTexImage')
texture.image = bpy.data.images.load(file_path)
material.node_tree.links.new(material_shader.inputs['Base Color'], texture.outputs['Color'])

# apply material to object
if apply:
    if len(obj.data.materials) > 0:
        next_material = material
        for i in range(len(obj.data.materials)):
            cache_material = obj.data.materials[i]
            obj.data.materials[i] = next_material
            next_material = cache_material
        obj.data.materials.append(next_material)
    else:
        obj.data.materials.append(material)

return material

And this is the result: result of applying a texture on a 3D model

Maybe the scaling/mapping from texture to Model is not right...Does anyone knows how to fix this?

I tries different texture applying methods, like Reapeat, extend and clip, but all results look the same.

The usage of an new material with random properties works fine, but textures not work for me right now.

Version: Blender 4.1

I Tried:

I hope anyone can help me :)

Here is my full code:

##############
# This is a Blender Python Script
# Run me in Blender
##############



########################
####### Imports ########
########################
import os
import random
import math
import json

import bpy


# load addons
bpy.ops.preferences.addon_enable(module='io_scene_3ds')
bpy.ops.preferences.addon_enable(module='export_mesh_stl')
bpy.ops.preferences.addon_enable(module='io_scene_obj')



########################
######## Tools #########
########################

def load_texture(file_path, obj, apply=True):
    """
    Creates a material, loads a texture and applies it to active material, if apply is true.
    """
    # create new material
    material = bpy.data.materials.new('My_Awesome_Texture_Material')
    material.use_nodes = True

    # create texture and apply to material
    material_shader = material.node_tree.nodes["Principled BSDF"]
    texture = material.node_tree.nodes.new('ShaderNodeTexImage')
    texture.image = bpy.data.images.load(file_path)
    material.node_tree.links.new(material_shader.inputs['Base Color'], texture.outputs['Color'])

    # apply material to object
    if apply:
        if len(obj.data.materials) > 0:
            next_material = material
            for i in range(len(obj.data.materials)):
                cache_material = obj.data.materials[i]
                obj.data.materials[i] = next_material
                next_material = cache_material
            obj.data.materials.append(next_material)
        else:
            obj.data.materials.append(material)

    return material
            
def clear_scene(clear_all=False):
    """
    Deletes all objects, but not the Light and Camera Object.
    """
    scene_objects = bpy.context.scene.objects

    for obj in scene_objects:
        if obj.type != 'LIGHT' and obj.type != 'CAMERA':
            bpy.data.objects.remove(obj)

    if clear_all:
        bpy.ops.object.select_all(action="SELECT")
        bpy.ops.object.delete()

        bpy.ops.outliner.orphans_purge(do_local_ids=True, do_linked_ids=True, do_recursive=True)

def import_model(file_path):
    """
    Imports/loads an 3D Model.

    Supported Formats: obj, stl, glb, fbx
    """
    if file_path.endswith(".obj"):
        imported_object = bpy.ops.wm.obj_import(filepath=file_path)
    elif file_path.endswith(".stl"):
        imported_object = bpy.ops.wm.stl_import(filepath=file_path)
    elif file_path.endswith(".glb") or file_path.endswith(".gltf"):
        imported_object = bpy.ops.wm.stl_import(filepath=file_path)
    elif file_path.endswith(".fbx"):
        imported_object =  bpy.ops.import_scene.fbx(filepath=file_path)
    # elif file_path.endswith(".blend"):
    #     imported_object =  load_blend(filepath=file_path)
    else:
        print(f"Failed loading 3D Model -> unknown/unsupported datatype:'{file_path}'\n-------")
        return "TRIGGER_CONTINUE"

    return imported_object

def export_model(filepath, target_format, only_active=False):
    """
    Exports an 3D Model.

    Supported Formats: obj, stl, glb, fbx, blend
    """
    if target_format.lower() == "stl":
        bpy.ops.wm.stl_export(filepath=filepath, check_existing=True, use_selection=only_active)
    elif target_format.lower() == "obj":
        bpy.ops.export_scene.obj(filepath=filepath, check_existing=True, use_selection=only_active)
    elif target_format.lower() == "glb" or target_format.lower() == "gltf":
        bpy.ops.export_scene.gltf(filepath=filepath, check_existing=True, use_selection=only_active)
    elif target_format.lower() == "fbx":
        bpy.ops.export_scene.fbx(filepath=filepath, check_existing=True, use_selection=only_active)
    elif target_format.lower() == "blend":
        bpy.ops.wm.save_mainfile(filepath=filepath, check_existing=True, use_selection=only_active)
    else:
        print(f"Unsupported datatype for convertation: '{target_format}'")



########################
###### Functions #######
########################

def load_model(model_path, texture_path):
    """
    Tries to load a model and a material/texture file and puts them together.
    """
    clear_scene()
    
    print(f"Tries to load '{model_path}'...")
    
    # load object
    import_model(file_path=model_path)
    object = bpy.context.selected_objects[0]
    
    # create and add material
    print(f"Tries to load texture: '{texture_path}'...")
    texture = load_texture(texture_path, object, apply=True)
    
    # render scene
    print(f"Tries to render the scene...")
    bpy.ops.render.render(write_still=True)


########################
#Here we run the script# 
########################

load_model(model_path="/home/tobia/src/res/stl_models/Banana.stl", 
           texture_path="/home/tobia/src/res/textures/texture_00166.png")

$\endgroup$
1
  • $\begingroup$ You probably need to unwrap the mesh. $\endgroup$
    – scurest
    Commented Apr 20 at 19:32

1 Answer 1

1
$\begingroup$

The UV from my model was missing to successfull apply a texture to an object/material, the model first have to mapped to 2D.

Following code does this wrapping:

def apply_uv(use_smart=True):
    # Select the object
    obj = bpy.context.object
    bpy.context.view_layer.objects.active = obj
    obj.select_set(True)

    # Switch to edit mode to perform mesh operations
    bpy.ops.object.mode_set(mode='EDIT')

    # Select all mesh elements
    bpy.ops.mesh.select_all(action='SELECT')
    
    if use_smart:
        # Smart UV unwrap the mesh
        bpy.ops.uv.smart_project(angle_limit=1.15192, margin_method='SCALED', island_margin=0, area_weight=0, correct_aspect=True, scale_to_bounds=False)

    else:
        # Unwrap the mesh
        bpy.ops.uv.unwrap(method='ANGLE_BASED', margin=0.001)
        
    # Switch back to object mode
    bpy.ops.object.mode_set(mode='OBJECT')

    # Access the UV loop layer
    bm = bmesh.new()
    bm.from_mesh(obj.data)
    bm.faces.ensure_lookup_table()
    uv_layer = bm.loops.layers.uv.verify()

    # Update the mesh with the new UV coordinates
    bm.to_mesh(obj.data)
    bm.free()

And than it looks like I wanted: successfull result

Here is the complete working code:

##############
# This is a Blender Python Script
# Run me in Blender
##############



########################
####### Imports ########
########################
import os
import random
import math
import json

import bpy
import bmesh


# load addons
bpy.ops.preferences.addon_enable(module='io_scene_3ds')
bpy.ops.preferences.addon_enable(module='export_mesh_stl')
bpy.ops.preferences.addon_enable(module='io_scene_obj')



########################
######## Tools #########
########################

def load_texture(file_path, obj, apply=True):
    """
    Creates a material, loads a texture and applies it to active material, if apply is true.
    """
    # create new material
    material = bpy.data.materials.new('My_Awesome_Texture_Material')
    material.use_nodes = True

    # create texture and apply to material
    material_shader = material.node_tree.nodes["Principled BSDF"]
    texture = material.node_tree.nodes.new('ShaderNodeTexImage')
    texture.image = bpy.data.images.load(file_path)
    material.node_tree.links.new(material_shader.inputs['Base Color'], texture.outputs['Color'])

    # apply material to object
    if apply:
        if len(obj.data.materials) > 0:
            next_material = material
            for i in range(len(obj.data.materials)):
                cache_material = obj.data.materials[i]
                obj.data.materials[i] = next_material
                next_material = cache_material
            obj.data.materials.append(next_material)
        else:
            obj.data.materials.append(material)

    return material
            
def clear_scene(clear_all=False):
    """
    Deletes all objects, but not the Light and Camera Object.
    """
    scene_objects = bpy.context.scene.objects

    for obj in scene_objects:
        if obj.type != 'LIGHT' and obj.type != 'CAMERA':
            bpy.data.objects.remove(obj)

    if clear_all:
        bpy.ops.object.select_all(action="SELECT")
        bpy.ops.object.delete()

        bpy.ops.outliner.orphans_purge(do_local_ids=True, do_linked_ids=True, do_recursive=True)

def import_model(file_path):
    """
    Imports/loads an 3D Model.

    Supported Formats: obj, stl, glb, fbx
    """
    if file_path.endswith(".obj"):
        imported_object = bpy.ops.wm.obj_import(filepath=file_path)
    elif file_path.endswith(".stl"):
        imported_object = bpy.ops.wm.stl_import(filepath=file_path)
    elif file_path.endswith(".glb") or file_path.endswith(".gltf"):
        imported_object = bpy.ops.wm.stl_import(filepath=file_path)
    elif file_path.endswith(".fbx"):
        imported_object =  bpy.ops.import_scene.fbx(filepath=file_path)
    # elif file_path.endswith(".blend"):
    #     imported_object =  load_blend(filepath=file_path)
    else:
        print(f"Failed loading 3D Model -> unknown/unsupported datatype:'{file_path}'\n-------")
        return "TRIGGER_CONTINUE"

    return imported_object

def export_model(filepath, target_format, only_active=False):
    """
    Exports an 3D Model.

    Supported Formats: obj, stl, glb, fbx, blend
    """
    if target_format.lower() == "stl":
        bpy.ops.wm.stl_export(filepath=filepath, check_existing=True, use_selection=only_active)
    elif target_format.lower() == "obj":
        bpy.ops.export_scene.obj(filepath=filepath, check_existing=True, use_selection=only_active)
    elif target_format.lower() == "glb" or target_format.lower() == "gltf":
        bpy.ops.export_scene.gltf(filepath=filepath, check_existing=True, use_selection=only_active)
    elif target_format.lower() == "fbx":
        bpy.ops.export_scene.fbx(filepath=filepath, check_existing=True, use_selection=only_active)
    elif target_format.lower() == "blend":
        bpy.ops.wm.save_mainfile(filepath=filepath, check_existing=True, use_selection=only_active)
    else:
        print(f"Unsupported datatype for convertation: '{target_format}'")

# new method
def apply_uv(use_smart=True):
    # Select the object
    obj = bpy.context.object
    bpy.context.view_layer.objects.active = obj
    obj.select_set(True)

    # Switch to edit mode to perform mesh operations
    bpy.ops.object.mode_set(mode='EDIT')

    # Select all mesh elements
    bpy.ops.mesh.select_all(action='SELECT')
    
    if use_smart:
        # Smart UV unwrap the mesh
        bpy.ops.uv.smart_project(angle_limit=1.15192, margin_method='SCALED', island_margin=0, area_weight=0, correct_aspect=True, scale_to_bounds=False)

    else:
        # Unwrap the mesh
        bpy.ops.uv.unwrap(method='ANGLE_BASED', margin=0.001)
        
    # Switch back to object mode
    bpy.ops.object.mode_set(mode='OBJECT')

    # Access the UV loop layer
    bm = bmesh.new()
    bm.from_mesh(obj.data)
    bm.faces.ensure_lookup_table()
    uv_layer = bm.loops.layers.uv.verify()

    # Update the mesh with the new UV coordinates
    bm.to_mesh(obj.data)
    bm.free()



########################
###### Functions #######
########################

def load_model(model_path, texture_path):
    """
    Tries to load a model and a material/texture file and puts them together.
    """
    clear_scene()
    
    print(f"Tries to load '{model_path}'...")
    
    # load object
    import_model(file_path=model_path)
    object = bpy.context.selected_objects[0]
    
    # apply uv
    apply_uv()
    
    # create and add material
    print(f"Tries to load texture: '{texture_path}'...")
    texture = load_texture(texture_path, object, apply=True)
    
    # render scene
    print(f"Tries to render the scene...")
    bpy.ops.render.render(write_still=True)


########################
#Here we run the script# 
########################

load_model(model_path="/home/tobia/src/res/stl_models/Banana.stl", 
           texture_path="/home/tobia/src/res/textures/texture_00166.png")
           

Thanks to scurest! And maybe someone has a similiar problem and this helps someone :)

$\endgroup$

You must log in to answer this question.

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