Help with applying texture to material and getting user input values

I’m making an addon which will allow you to add various layers (color, bump, specular) while in Texture Paint mode. I’m aware there is an addon already like this, but I wish to customize my own further. I’ve only got so far as the first button (add color). This button successfully adds a material and names it if the object doesn’t already have a material and I can create a texture slot for it but I can’t add an actual image to the texture slot to be used.


import bpy


#First function for the button "Add Color"
class betterPaintColor(bpy.types.Operator):  
    bl_idname = "object.better_paint_color"  
    bl_label = "Add Color"
    bl_options = {'REGISTER', 'UNDO'}


    def execute(self, context):
        #Store name for convenience
        obj = bpy.context.scene.objects.active 
        
        #Test if object has material or not
        if len(obj.data.materials) == 0:
            #Create a new material and give it the name of the object
            mat = bpy.data.materials.new(obj.name)
            #Assign it some properties
            mat.diffuse_shader = 'LAMBERT'
            mat.darkness = 0.8
            
            #Assign Material to the active object
            obj.active_material = bpy.data.materials[obj.name]


         <i><b>(THIS IS THE AREA WHERE I NEED TO HAVE A TEXTURE APPLIED TO THE MATERIAL)</b></i>

        #Switch to texture viewport shading
        bpy.context.space_data.viewport_shade = 'TEXTURED'
                  
        return {'FINISHED'}
    


#Draw the addon into the Tool Shelf    
class betterPaint(bpy.types.Panel):
    bl_label = "Layers"
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"
    
    #Make addon specific to Texture Paint mode
    @classmethod
    def poll(cls, context):
        return (context.image_paint_object)
    
    def draw(self, context):
        
        #Store names for convenience
        layout = self.layout
        row = layout.row()
        
        row.operator("object.better_paint_color", icon='BRUSH_DATA')
        row = layout.row()
        row.label(text="Add Bump", icon='BRUSH_DATA')
        row = layout.row()
        row.label(text="Add Specular", icon='BRUSH_DATA')


#register and unregister all classes for use with 
#addon by running them through a loop.  
classes = [betterPaint, betterPaintColor]
    
def register():
    for c in classes:
        bpy.utils.register_class(c)


def unregister():
    for c in classes:
        bpy.utils.unregister_class(c)


if __name__ == "__main__":
    register()

I’d also really like to be able to place two text inputs like this:


(with default settings) for the user to change the size of the image map they are creating but also be able to just press the button if they don’t wish to first give it a value. Thanks for any help or leads.

Quick example:

mat = bpy.data.materials.new("New Material")
ts = mat.texture_slots.add()
tex = bpy.data.textures.new("New Texture", 'IMAGE')
img = bpy.data.images.load("PATH/TO/FILE.EXT")
tex.image = img
ts.texture = tex

Thanks for your response. Is it required to load an image from a file source to add it to a texture? This wouldn’t be good as an addon. Can I just load it internally into Blender or tell Blender to just pack the file?

I know that you can create new textures, edit and use them without ever saving them I just can’t figure out how to command this via script.

Just wanted to wrap this thread up by presenting my final solution for applying a texture to a material which involves using an RNA method to create a generated image, then using a for loop to apply that image to every face in the object.


        #Test if object has material or not       
        if len(obj.data.materials) == 0:
            
            #Create a new material and give it the name of the object
            mat = bpy.data.materials.new(obj.name)
            #Assign it some properties
            mat.diffuse_shader = 'LAMBERT'
            mat.darkness = 0.8
            
            #Assign Material to the active object
            obj.active_material = mat
        
        #Create transparent image to be used as texture with RNA method
        xAndY = 1024
        img = bpy.data.images.new(obj.name + 'Color', xAndY, xAndY, alpha=True)
        #Set alpha and color to none
        img.pixels[:] = (0.0, 0.0, 0.0, 0.0) * xAndY * xAndY
        #Create A New texture and store it in cTex
        cTex = bpy.data.textures.new( name = obj.name + 'Color', type = 'IMAGE')
        
        #Add a texture slot and assign it the texture just created
        mTex = mat.texture_slots.add()
        mTex.texture = cTex
        
        #Change mapping to UV now that we have a texture in place
        mTex.texture_coords = 'UV'
        
        #Assign the image we created to the texture we created
        cTex.image = img
        
        #Enter edit mode to select all faces
        bpy.ops.object.mode_set(mode = 'EDIT')
        bpy.ops.mesh.select_all(action='SELECT')
        #Use Smart UV project to create a UV Map
        bpy.ops.uv.smart_project(island_margin=0.03, angle_limit=40)
        #Average the scale of UVs.
        bpy.ops.uv.average_islands_scale()
        
        #Use loop to link texture to UV Map of all faces
        for uv_face in obj.data.uv_textures.active.data:
            uv_face.image = img

any image datablock is okay of course, no matter if loaded from a file or generated internally.

If you want to set the same value (actually reference) on every element in an array, you can do:

uv_tex = obj.data.uv_textures.active.data
uv_tex.foreach_set(“image”, [img]*len(uv_tex))

foreach_set() passes an attribute and a flat data sequence to the C code, and that should able to perform the operation faster than a python for … in loop.

Cool. Thanks for the tip. I’m always down with faster.