Exemples de Blender Scripting

Exemples de scripting Blender Python pour la modélisation 3D, l'animation, la création d'actifs et l'automatisation des flux de travail

Key Facts

Category
3D Graphics
Items
4
Format Families
text

Sample Overview

Exemples de scripting Blender Python pour la modélisation 3D, l'animation, la création d'actifs et l'automatisation des flux de travail This sample set belongs to 3D Graphics and can be used to test related workflows inside Elysia Tools.

💻 Blender Hello World python

🟢 simple ⭐⭐

Configuration de base du scripting Blender avec création d'objets et fondamentaux de manipulation de scène

⏱️ 15 min 🏷️ blender, python, 3d, scripting
Prerequisites: Basic Python knowledge, Blender interface familiarity
# Blender Hello World - Basic Scripting Setup
# Run this script in Blender's Scripting workspace or from the Text Editor

import bpy
import math

def clear_scene():
    """Clear all objects from the current scene"""
    # Select all objects
    bpy.ops.object.select_all(action='SELECT')
    # Delete selected objects
    bpy.ops.object.delete()
    print("Scene cleared")

def create_basic_objects():
    """Create basic geometric objects"""
    # Create a cube
    bpy.ops.mesh.primitive_cube_add(location=(-2, 0, 0))
    cube = bpy.context.active_object
    cube.name = "HelloCube"
    cube.scale = (1, 1, 1)
    print(f"Created cube: {cube.name}")

    # Create a sphere
    bpy.ops.mesh.primitive_uv_sphere_add(location=(2, 0, 0))
    sphere = bpy.context.active_object
    sphere.name = "HelloSphere"
    sphere.scale = (1, 1, 1)
    print(f"Created sphere: {sphere.name}")

    # Create a plane
    bpy.ops.mesh.primitive_plane_add(location=(0, -2, 0))
    plane = bpy.context.active_object
    plane.name = "HelloPlane"
    plane.scale = (3, 3, 1)
    print(f"Created plane: {plane.name}")

    return cube, sphere, plane

def create_materials():
    """Create basic materials for objects"""
    # Red material for cube
    red_material = bpy.data.materials.new(name="RedMaterial")
    red_material.diffuse_color = (0.8, 0.1, 0.1, 1.0)

    # Green material for sphere
    green_material = bpy.data.materials.new(name="GreenMaterial")
    green_material.diffuse_color = (0.1, 0.8, 0.1, 1.0)

    # Blue material for plane
    blue_material = bpy.data.materials.new(name="BlueMaterial")
    blue_material.diffuse_color = (0.1, 0.1, 0.8, 1.0)

    return red_material, green_material, blue_material

def apply_materials(obj, material):
    """Apply material to object"""
    if obj.data.materials:
        obj.data.materials[0] = material
    else:
        obj.data.materials.append(material)
    print(f"Applied material {material.name} to {obj.name}")

def setup_lighting():
    """Setup basic scene lighting"""
    # Add a sun light
    bpy.ops.object.light_add(type='SUN', location=(5, 5, 10))
    sun = bpy.context.active_object
    sun.name = "SunLight"
    sun.data.energy = 5.0
    print(f"Created sun light: {sun.name}")

    # Add an area light for fill
    bpy.ops.object.light_add(type='AREA', location=(-3, -3, 5))
    area = bpy.context.active_object
    area.name = "FillLight"
    area.data.energy = 2.0
    area.data.size = 2.0
    print(f"Created area light: {area.name}")

def setup_camera():
    """Setup camera for the scene"""
    bpy.ops.object.camera_add(location=(7, -7, 5))
    camera = bpy.context.active_object
    camera.name = "MainCamera"

    # Point camera at origin
    direction = -camera.location
    rot_quat = direction.to_track_quat('-Z', 'Y')
    camera.rotation_euler = rot_quat.to_euler()

    # Set as active camera
    bpy.context.scene.camera = camera
    print(f"Created camera: {camera.name}")

def create_text_object():
    """Create a 3D text object"""
    bpy.ops.object.text_add(location=(0, 0, 2))
    text_obj = bpy.context.active_object
    text_obj.name = "HelloWorldText"

    # Modify text properties
    text_obj.data.body = "Hello Blender!"
    text_obj.data.size = 1.0
    text_obj.data.extrude = 0.1

    # Center the text
    text_obj.data.align_x = 'CENTER'

    # Apply rotation for better visibility
    text_obj.rotation_euler = (0, 0, math.radians(0))

    print(f"Created text object: {text_obj.name}")
    return text_obj

def animate_objects(cube, sphere, text_obj):
    """Create simple animations for objects"""
    # Set timeline range
    bpy.context.scene.frame_start = 1
    bpy.context.scene.frame_end = 120

    # Animate cube rotation
    cube.rotation_euler = (0, 0, 0)
    cube.keyframe_insert(data_path="rotation_euler", frame=1)

    bpy.context.scene.frame_set(60)
    cube.rotation_euler = (0, 0, math.radians(90))
    cube.keyframe_insert(data_path="rotation_euler", frame=60)

    bpy.context.scene.frame_set(120)
    cube.rotation_euler = (0, 0, math.radians(180))
    cube.keyframe_insert(data_path="rotation_euler", frame=120)

    # Animate sphere location
    sphere.location = (2, 0, 0)
    sphere.keyframe_insert(data_path="location", frame=1)

    bpy.context.scene.frame_set(60)
    sphere.location = (2, 0, 2)
    sphere.keyframe_insert(data_path="location", frame=60)

    bpy.context.scene.frame_set(120)
    sphere.location = (2, 0, 0)
    sphere.keyframe_insert(data_path="location", frame=120)

    # Animate text scale
    text_obj.scale = (1, 1, 1)
    text_obj.keyframe_insert(data_path="scale", frame=1)

    bpy.context.scene.frame_set(60)
    text_obj.scale = (1.2, 1.2, 1.2)
    text_obj.keyframe_insert(data_path="scale", frame=60)

    bpy.context.scene.frame_set(120)
    text_obj.scale = (1, 1, 1)
    text_obj.keyframe_insert(data_path="scale", frame=120)

    # Reset to frame 1
    bpy.context.scene.frame_set(1)

    print("Animation keyframes created")

def setup_render_settings():
    """Configure basic render settings"""
    scene = bpy.context.scene

    # Set render engine to Cycles for better quality
    scene.render.engine = 'CYCLES'

    # Set output resolution
    scene.render.resolution_x = 1920
    scene.render.resolution_y = 1080
    scene.render.resolution_percentage = 50  # Render at 50% for faster testing

    # Set output format
    scene.render.image_settings.file_format = 'PNG'
    scene.render.image_settings.color_mode = 'RGBA'

    # Set samples for Cycles
    scene.cycles.samples = 128

    # Set file path
    scene.render.filepath = "//hello_blender_render.png"

    print("Render settings configured")

def main():
    """Main function to execute the Hello World script"""
    print("=== Blender Hello World Script ===")
    print(f"Blender Version: {bpy.app.version}")

    # Clear the scene
    clear_scene()

    # Create objects
    cube, sphere, plane = create_basic_objects()
    text_obj = create_text_object()

    # Create materials
    red_mat, green_mat, blue_mat = create_materials()

    # Apply materials
    apply_materials(cube, red_mat)
    apply_materials(sphere, green_mat)
    apply_materials(plane, blue_mat)

    # Create yellow material for text
    yellow_material = bpy.data.materials.new(name="YellowMaterial")
    yellow_material.diffuse_color = (0.9, 0.9, 0.1, 1.0)
    apply_materials(text_obj, yellow_material)

    # Setup scene
    setup_lighting()
    setup_camera()
    animate_objects(cube, sphere, text_obj)
    setup_render_settings()

    print("=== Hello World scene created successfully! ===")
    print("Press SPACE in the 3D Viewport to play the animation")
    print("Press F12 to render the current frame")

# Run the main function
if __name__ == "__main__":
    main()

💻 Génération de Géométrie Procedurale python

🟡 intermediate ⭐⭐⭐⭐

Modélisation procédurale avancée avec fonctions mathématiques, objets paramétriques et géométrie complexe

⏱️ 35 min 🏷️ blender, python, 3d, procedural, geometry
Prerequisites: Advanced Python, Mathematics for 3D graphics, BMesh module
# Blender Procedural Geometry Generation
# Mathematical functions and parametric modeling

import bpy
import bmesh
import math
import numpy as np
from mathutils import Vector, Matrix

def clear_scene():
    """Clear all objects from the current scene"""
    bpy.ops.object.select_all(action='SELECT')
    bpy.ops.object.delete()

def create_parametric_surface(width=10, height=10, resolution=50):
    """Create a parametric surface using mathematical functions"""
    # Create mesh data
    verts = []
    edges = []
    faces = []

    # Generate vertices using sine and cosine functions
    for i in range(resolution + 1):
        for j in range(resolution + 1):
            u = i / resolution * width - width / 2
            v = j / resolution * height - height / 2

            # Mathematical function for surface
            x = u
            y = v
            z = (math.sin(math.sqrt(u*u + v*v)) * 2 +
                 math.cos(u * 0.5) * math.cos(v * 0.5))

            verts.append((x, y, z))

    # Create edges
    for i in range(resolution):
        for j in range(resolution):
            # Current vertex index
            current = i * (resolution + 1) + j

            # Edge to next vertex in row
            edges.append((current, current + 1))

            # Edge to vertex in next row
            edges.append((current, current + resolution + 1))

    # Create faces
    for i in range(resolution):
        for j in range(resolution):
            current = i * (resolution + 1) + j

            # Two triangles per grid cell
            faces.append((
                current,
                current + 1,
                current + resolution + 2,
                current + resolution + 1
            ))

    # Create mesh object
    mesh = bpy.data.meshes.new("ParametricSurface")
    mesh.from_pydata(verts, edges, faces)
    mesh.update()

    # Create object
    obj = bpy.data.objects.new("ParametricSurface", mesh)
    bpy.context.collection.objects.link(obj)

    return obj

def create_l_system(iterations=4, angle=25, length=1):
    """Create a plant-like structure using L-system"""
    # L-system rules
    rules = {
        'F': 'FF+[+F-F-F]-[-F+F+F]',
        'X': 'F+[[X]-X]-F[-FX]+X'
    }

    # Start with axiom
    current = 'X'

    # Generate L-system string
    for _ in range(iterations):
        next_string = ''
        for char in current:
            next_string += rules.get(char, char)
        current = next_string

    # Convert L-system string to 3D structure
    mesh = bpy.data.meshes.new("LSystem")
    bm = bmesh.new()

    # Create vertices and edges
    position = Vector((0, 0, 0))
    direction = Vector((0, 0, 1))
    angle_rad = math.radians(angle)

    # Stack for turtle graphics
    position_stack = []
    direction_stack = []

    # First vertex
    bm.verts.new(position)

    for char in current:
        if char == 'F':
            # Move forward and create edge
            new_pos = position + direction * length
            bm.verts.new(new_pos)
            bm.edges.new((bm.verts[-2], bm.verts[-1]))
            position = new_pos

        elif char == '+':
            # Turn right
            rotation_matrix = Matrix.Rotation(angle_rad, 4, 'Z')
            direction = rotation_matrix @ direction

        elif char == '-':
            # Turn left
            rotation_matrix = Matrix.Rotation(-angle_rad, 4, 'Z')
            direction = rotation_matrix @ direction

        elif char == '[':
            # Push current state
            position_stack.append(position.copy())
            direction_stack.append(direction.copy())

        elif char == ']':
            # Pop state
            position = position_stack.pop()
            direction = direction_stack.pop()

    bm.to_mesh(mesh)
    bm.free()

    # Create object
    obj = bpy.data.objects.new("LSystem", mesh)
    bpy.context.collection.objects.link(obj)

    return obj

def create_voronoi_pattern(points_count=50, size=10):
    """Create a Voronoi pattern using random points"""
    import random

    # Generate random points
    points = []
    for _ in range(points_count):
        x = random.uniform(-size/2, size/2)
        y = random.uniform(-size/2, size/2)
        points.append((x, y))

    # Create base grid
    resolution = 100
    mesh = bpy.data.meshes.new("VoronoiPattern")
    bm = bmesh.new()

    # Create vertices and find nearest point for each
    for i in range(resolution):
        for j in range(resolution):
            x = (i / resolution * size) - size/2
            y = (j / resolution * size) - size/2

            # Find nearest Voronoi point
            min_dist = float('inf')
            nearest_point = 0
            for idx, (px, py) in enumerate(points):
                dist = math.sqrt((x - px)**2 + (y - py)**2)
                if dist < min_dist:
                    min_dist = dist
                    nearest_point = idx

            # Create vertex with Z based on nearest point
            z = nearest_point * 0.1  # Height variation based on region
            bm.verts.new((x, y, z))

    # Create faces (grid)
    for i in range(resolution - 1):
        for j in range(resolution - 1):
            v1 = i * resolution + j
            v2 = v1 + 1
            v3 = v2 + resolution
            v4 = v1 + resolution

            try:
                bm.faces.new((
                    bm.verts[v1],
                    bm.verts[v2],
                    bm.verts[v3],
                    bm.verts[v4]
                ))
            except:
                pass  # Skip invalid faces

    bm.to_mesh(mesh)
    bm.free()

    # Create object
    obj = bpy.data.objects.new("VoronoiPattern", mesh)
    bpy.context.collection.objects.link(obj)

    return obj

def create_fractal_terrain(size=10, resolution=50, octaves=6, persistence=0.5):
    """Create a fractal terrain using Perlin noise"""
    # Use Blender's noise module
    from bpy_extras.node_shader_utils import PrincipledBSDFWrapper

    # Create terrain mesh
    mesh = bpy.data.meshes.new("FractalTerrain")
    bm = bmesh.new()

    # Generate vertices with noise
    verts = {}
    for i in range(resolution + 1):
        for j in range(resolution + 1):
            x = (i / resolution * size) - size/2
            y = (j / resolution * size) - size/2

            # Multi-octave Perlin noise
            z = 0
            amplitude = 1
            frequency = 1

            for octave in range(octaves):
                # Simple noise function (since we can't import external noise libraries)
                noise_val = (
                    math.sin(x * frequency * 0.3) * math.cos(y * frequency * 0.3) +
                    math.sin(x * frequency * 0.7) * math.sin(y * frequency * 0.7) * 0.5
                )
                z += noise_val * amplitude
                amplitude *= persistence
                frequency *= 2

            verts[(i, j)] = bm.verts.new((x, y, z))

    # Create faces
    for i in range(resolution):
        for j in range(resolution):
            try:
                v1 = verts[(i, j)]
                v2 = verts[(i + 1, j)]
                v3 = verts[(i + 1, j + 1)]
                v4 = verts[(i, j + 1)]
                bm.faces.new([v1, v2, v3, v4])
            except:
                pass

    # Recalculate normals
    bmesh.ops.recalc_face_normals(bm, faces=bm.faces)

    bm.to_mesh(mesh)
    bm.free()

    # Create object
    obj = bpy.data.objects.new("FractalTerrain", mesh)
    bpy.context.collection.objects.link(obj)

    return obj

def create_icosphere_subdivisions(subdivisions=3, radius=2):
    """Create an icosphere with custom subdivisions"""
    import math

    # Golden ratio
    phi = (1 + math.sqrt(5)) / 2

    # Initial icosahedron vertices
    vertices = [
        (-1, phi, 0), (1, phi, 0), (-1, -phi, 0), (1, -phi, 0),
        (0, -1, phi), (0, 1, phi), (0, -1, -phi), (0, 1, -phi),
        (phi, 0, -1), (phi, 0, 1), (-phi, 0, -1), (-phi, 0, 1)
    ]

    # Normalize and scale
    vertices = [Vector(v).normalized() * radius for v in vertices]

    # Initial faces (20 triangles of icosahedron)
    faces = [
        (0, 11, 5), (0, 5, 1), (0, 1, 7), (0, 7, 10), (0, 10, 11),
        (1, 5, 9), (5, 11, 4), (11, 10, 2), (10, 7, 6), (7, 1, 8),
        (3, 9, 4), (3, 4, 2), (3, 2, 6), (3, 6, 8), (3, 8, 9),
        (4, 9, 5), (2, 4, 11), (6, 2, 10), (8, 6, 7), (9, 8, 1)
    ]

    # Subdivide faces
    for _ in range(subdivisions):
        new_faces = []
        edge_midpoints = {}

        for face in faces:
            # Get midpoints of edges
            new_vertices = []
            for i in range(3):
                v1, v2 = face[i], face[(i + 1) % 3]
                edge = tuple(sorted([v1, v2]))

                if edge not in edge_midpoints:
                    midpoint = (vertices[v1] + vertices[v2]) / 2
                    midpoint = midpoint.normalized() * radius
                    edge_midpoints[edge] = len(vertices)
                    vertices.append(midpoint)

                new_vertices.append(edge_midpoints[edge])

            # Create 4 new faces
            v0, v1, v2 = face
            m0, m1, m2 = new_vertices

            new_faces.extend([
                (v0, m0, m2),
                (m0, v1, m1),
                (m2, m1, v2),
                (m0, m1, m2)
            ])

        faces = new_faces

    # Create mesh
    mesh = bpy.data.meshes.new("CustomIcosphere")
    mesh.from_pydata(vertices, [], faces)
    mesh.update()

    # Create object
    obj = bpy.data.objects.new("CustomIcosphere", mesh)
    bpy.context.collection.objects.link(obj)

    return obj

def create_sculptural_form():
    """Create an artistic sculptural form using mathematical curves"""
    # Create multiple parametric curves and loft between them

    curves = []
    for i in range(10):
        # Create profile curve
        profile_points = []
        for j in range(20):
            angle = j / 20 * 2 * math.pi

            # Varying radius based on height
            height_factor = i / 10
            radius = 2 + math.sin(angle * 3) * (1 - height_factor)

            x = math.cos(angle) * radius
            y = math.sin(angle) * radius
            z = i * 0.5

            profile_points.append((x, y, z))

        # Create curve object
        curve_data = bpy.data.curves.new(f"Profile_{i}", type='CURVE')
        curve_data.dimensions = '3D'
        curve_data.fill_mode = 'FULL'
        curve_data.bevel_depth = 0.05

        spline = curve_data.splines.new('POLY')
        spline.points.add(len(profile_points) - 1)

        for point, (x, y, z) in enumerate(profile_points):
            spline.points[point].co = (x, y, z, 1)

        curve_obj = bpy.data.objects.new(f"Profile_{i}", curve_data)
        bpy.context.collection.objects.link(curve_obj)
        curves.append(curve_obj)

    return curves

def main():
    """Main function to demonstrate procedural geometry generation"""
    print("=== Procedural Geometry Generation ===")

    # Clear scene
    clear_scene()

    # Create various procedural objects
    surface = create_parametric_surface(width=8, height=8, resolution=30)
    surface.location = (0, 0, 0)

    l_system = create_l_system(iterations=4, angle=25, length=0.3)
    l_system.location = (-5, 0, 0)

    voronoi = create_voronoi_pattern(points_count=30, size=6)
    voronoi.location = (5, 0, 0)

    terrain = create_fractal_terrain(size=8, resolution=40, octaves=4)
    terrain.location = (0, -8, -2)

    icosphere = create_icosphere_subdivisions(subdivisions=2, radius=1.5)
    icosphere.location = (0, 0, 4)

    # Create sculptural forms
    curves = create_sculptural_form()
    for i, curve in enumerate(curves):
        curve.location = (0, 8, i * 0.5)

    # Add materials
    for obj in bpy.context.selected_objects:
        if obj.type == 'MESH':
            mat = bpy.data.materials.new(name=f"Material_{obj.name}")
            mat.diffuse_color = (0.7, 0.7, 0.9, 1.0)
            if obj.data.materials:
                obj.data.materials[0] = mat
            else:
                obj.data.materials.append(mat)

    # Add lighting
    bpy.ops.object.light_add(type='SUN', location=(5, 5, 10))
    sun = bpy.context.active_object
    sun.data.energy = 3.0

    bpy.ops.object.light_add(type='AREA', location=(-3, -3, 5))
    area = bpy.context.active_object
    area.data.energy = 1.5

    # Add camera
    bpy.ops.object.camera_add(location=(10, -10, 8))
    camera = bpy.context.active_object
    direction = -camera.location
    rot_quat = direction.to_track_quat('-Z', 'Y')
    camera.rotation_euler = rot_quat.to_euler()
    bpy.context.scene.camera = camera

    print("Procedural geometry generation complete!")

if __name__ == "__main__":
    main()

💻 Automatisation d'Animation et Rigging python

🟡 intermediate ⭐⭐⭐⭐

Système d'animation automatisé avec création de squelettes, weighting et animations complexes

⏱️ 40 min 🏷️ blender, python, 3d, animation, rigging
Prerequisites: Blender rigging knowledge, Python scripting, Animation principles
# Blender Animation and Rigging Automation
# Automated bone creation, weighting, and animation systems

import bpy
import math
from mathutils import Vector, Matrix, Quaternion

def clear_scene():
    """Clear all objects from the current scene"""
    bpy.ops.object.select_all(action='SELECT')
    bpy.ops.object.delete()

def create_character_mesh():
    """Create a basic humanoid character mesh"""
    # Character proportions
    height = 2.0
    width = 0.8

    # Create body parts as separate meshes
    parts = {}

    # Torso
    bpy.ops.mesh.primitive_cube_add(size=1)
    torso = bpy.context.active_object
    torso.name = "Torso"
    torso.scale = (width, 0.4, 0.8)
    torso.location = (0, 0, height * 0.5)
    parts['torso'] = torso

    # Head
    bpy.ops.mesh.primitive_uv_sphere_add(radius=0.3)
    head = bpy.context.active_object
    head.name = "Head"
    head.location = (0, 0, height * 0.9)
    parts['head'] = head

    # Left arm
    bpy.ops.mesh.primitive_cylinder_add(radius=0.1, depth=0.6)
    left_arm = bpy.context.active_object
    left_arm.name = "LeftArm"
    left_arm.location = (-width * 0.6, 0, height * 0.6)
    left_arm.rotation_euler = (0, math.radians(90), 0)
    parts['left_arm'] = left_arm

    # Right arm
    bpy.ops.mesh.primitive_cylinder_add(radius=0.1, depth=0.6)
    right_arm = bpy.context.active_object
    right_arm.name = "RightArm"
    right_arm.location = (width * 0.6, 0, height * 0.6)
    right_arm.rotation_euler = (0, math.radians(90), 0)
    parts['right_arm'] = right_arm

    # Left leg
    bpy.ops.mesh.primitive_cylinder_add(radius=0.12, depth=0.8)
    left_leg = bpy.context.active_object
    left_leg.name = "LeftLeg"
    left_leg.location = (-width * 0.3, 0, height * 0.2)
    parts['left_leg'] = left_leg

    # Right leg
    bpy.ops.mesh.primitive_cylinder_add(radius=0.12, depth=0.8)
    right_leg = bpy.context.active_object
    right_leg.name = "RightLeg"
    right_leg.location = (width * 0.3, 0, height * 0.2)
    parts['right_leg'] = right_leg

    return parts

def create_armature(parts):
    """Create an armature with bones for the character"""
    # Create armature
    bpy.ops.object.add(type='ARMATURE', enter_editmode=True)
    armature = bpy.context.active_object
    armature.name = "CharacterRig"

    # Clear default bone
    bpy.ops.armature.select_all(action='SELECT')
    bpy.ops.armature.delete()

    # Create bone hierarchy
    bones = {}

    # Root bone
    bpy.ops.armature.bone_add()
    root = bpy.context.active_bone
    root.name = "Root"
    root.head = (0, 0, 0)
    root.tail = (0, 0, 0.2)
    bones['root'] = root

    # Spine
    bpy.ops.armature.bone_add()
    spine = bpy.context.active_bone
    spine.name = "Spine"
    spine.head = root.tail
    spine.tail = (0, 0, parts['torso'].location.z)
    spine.parent = root
    bones['spine'] = spine

    # Neck
    bpy.ops.armature.bone_add()
    neck = bpy.context.active_bone
    neck.name = "Neck"
    neck.head = spine.tail
    neck.tail = (0, 0, parts['head'].location.z - 0.3)
    neck.parent = spine
    bones['neck'] = neck

    # Head
    bpy.ops.armature.bone_add()
    head_bone = bpy.context.active_bone
    head_bone.name = "Head"
    head_bone.head = neck.tail
    head_bone.tail = (0, 0, parts['head'].location.z + 0.3)
    head_bone.parent = neck
    bones['head_bone'] = head_bone

    # Left arm chain
    bpy.ops.armature.bone_add()
    left_shoulder = bpy.context.active_bone
    left_shoulder.name = "LeftShoulder"
    left_shoulder.head = spine.tail
    left_shoulder.tail = parts['left_arm'].location
    left_shoulder.parent = spine
    bones['left_shoulder'] = left_shoulder

    bpy.ops.armature.bone_add()
    left_elbow = bpy.context.active_bone
    left_elbow.name = "LeftElbow"
    left_elbow.head = left_shoulder.tail
    left_elbow.tail = left_shoulder.tail + Vector((-0.6, 0, -0.3))
    left_elbow.parent = left_shoulder
    bones['left_elbow'] = left_elbow

    # Right arm chain
    bpy.ops.armature.bone_add()
    right_shoulder = bpy.context.active_bone
    right_shoulder.name = "RightShoulder"
    right_shoulder.head = spine.tail
    right_shoulder.tail = parts['right_arm'].location
    right_shoulder.parent = spine
    bones['right_shoulder'] = right_shoulder

    bpy.ops.armature.bone_add()
    right_elbow = bpy.context.active_bone
    right_elbow.name = "RightElbow"
    right_elbow.head = right_shoulder.tail
    right_elbow.tail = right_shoulder.tail + Vector((0.6, 0, -0.3))
    right_elbow.parent = right_shoulder
    bones['right_elbow'] = right_elbow

    # Left leg chain
    bpy.ops.armature.bone_add()
    left_hip = bpy.context.active_bone
    left_hip.name = "LeftHip"
    left_hip.head = (0, 0, parts['left_leg'].location.z + 0.4)
    left_hip.tail = parts['left_leg'].location + Vector((0, 0, 0.3))
    left_hip.parent = root
    bones['left_hip'] = left_hip

    bpy.ops.armature.bone_add()
    left_knee = bpy.context.active_bone
    left_knee.name = "LeftKnee"
    left_knee.head = left_hip.tail
    left_knee.tail = (0, 0, 0)
    left_knee.parent = left_hip
    bones['left_knee'] = left_knee

    # Right leg chain
    bpy.ops.armature.bone_add()
    right_hip = bpy.context.active_bone
    right_hip.name = "RightHip"
    right_hip.head = (0, 0, parts['right_leg'].location.z + 0.4)
    right_hip.tail = parts['right_leg'].location + Vector((0, 0, 0.3))
    right_hip.parent = root
    bones['right_hip'] = right_hip

    bpy.ops.armature.bone_add()
    right_knee = bpy.context.active_bone
    right_knee.name = "RightKnee"
    right_knee.head = right_hip.tail
    right_knee.tail = (0, 0, 0)
    right_knee.parent = right_hip
    bones['right_knee'] = right_knee

    # Set bones to pose mode
    bpy.ops.object.mode_set(mode='OBJECT')

    return armature, bones

def setup_ik_constraints(armature, bones):
    """Set up IK constraints for automatic limb positioning"""
    bpy.context.view_layer.objects.active = armature
    bpy.ops.object.mode_set(mode='POSE')

    # Create IK targets
    ik_targets = {}

    # Left hand IK
    bpy.ops.object.empty_add(type='SPHERE', radius=0.1)
    left_hand_target = bpy.context.active_object
    left_hand_target.name = "LeftHand_IK"
    left_hand_target.location = bones['left_elbow'].tail + Vector((-0.3, 0, -0.3))
    ik_targets['left_hand'] = left_hand_target

    # Right hand IK
    bpy.ops.object.empty_add(type='SPHERE', radius=0.1)
    right_hand_target = bpy.context.active_object
    right_hand_target.name = "RightHand_IK"
    right_hand_target.location = bones['right_elbow'].tail + Vector((0.3, 0, -0.3))
    ik_targets['right_hand'] = right_hand_target

    # Left foot IK
    bpy.ops.object.empty_add(type='SPHERE', radius=0.1)
    left_foot_target = bpy.context.active_object
    left_foot_target.name = "LeftFoot_IK"
    left_foot_target.location = bones['left_knee'].tail
    ik_targets['left_foot'] = left_foot_target

    # Right foot IK
    bpy.ops.object.empty_add(type='SPHERE', radius=0.1)
    right_foot_target = bpy.context.active_object
    right_foot_target.name = "RightFoot_IK"
    right_foot_target.location = bones['right_knee'].tail
    ik_targets['right_foot'] = right_foot_target

    # Add IK constraints to bones
    pose_bones = armature.pose.bones

    # Left elbow IK
    left_elbow_constraint = pose_bones['LeftElbow'].constraints.new(type='IK')
    left_elbow_constraint.target = left_hand_target
    left_elbow_constraint.chain_count = 2
    left_elbow_constraint.use_stretch = True

    # Right elbow IK
    right_elbow_constraint = pose_bones['RightElbow'].constraints.new(type='IK')
    right_elbow_constraint.target = right_hand_target
    right_elbow_constraint.chain_count = 2
    right_elbow_constraint.use_stretch = True

    # Left knee IK
    left_knee_constraint = pose_bones['LeftKnee'].constraints.new(type='IK')
    left_knee_constraint.target = left_foot_target
    left_knee_constraint.chain_count = 2
    left_knee_constraint.use_stretch = True

    # Right knee IK
    right_knee_constraint = pose_bones['RightKnee'].constraints.new(type='IK')
    right_knee_constraint.target = right_foot_target
    right_knee_constraint.chain_count = 2
    right_knee_constraint.use_stretch = True

    bpy.ops.object.mode_set(mode='OBJECT')
    return ik_targets

def auto_weight_mesh(mesh_obj, armature):
    """Automatically weight paint a mesh to the armature"""
    # Select mesh and armature
    mesh_obj.select_set(True)
    armature.select_set(True)
    bpy.context.view_layer.objects.active = armature

    # Create armature modifier
    modifier = mesh_obj.modifiers.new(name="Armature", type='ARMATURE')
    modifier.object = armature
    modifier.use_vertex_groups = True

    # Parent mesh to armature with automatic weights
    bpy.ops.object.parent_set(type='ARMATURE_AUTO')

    return modifier

def create_walk_cycle(armature, frames=60):
    """Create a basic walk cycle animation"""
    bpy.context.view_layer.objects.active = armature
    bpy.ops.object.mode_set(mode='POSE')

    # Set timeline
    bpy.context.scene.frame_start = 1
    bpy.context.scene.frame_end = frames

    pose_bones = armature.pose.bones

    # Walk cycle keyframes
    walk_frames = {
        1: {
            'LeftHip': 30, 'RightHip': -30,
            'LeftKnee': 45, 'RightKnee': 0,
            'LeftShoulder': -20, 'RightShoulder': 20,
            'Spine': 5
        },
        15: {
            'LeftHip': 0, 'RightHip': 0,
            'LeftKnee': 20, 'RightKnee': 20,
            'LeftShoulder': 0, 'RightShoulder': 0,
            'Spine': 0
        },
        30: {
            'LeftHip': -30, 'RightHip': 30,
            'LeftKnee': 0, 'RightKnee': 45,
            'LeftShoulder': 20, 'RightShoulder': -20,
            'Spine': -5
        },
        45: {
            'LeftHip': 0, 'RightHip': 0,
            'LeftKnee': 20, 'RightKnee': 20,
            'LeftShoulder': 0, 'RightShoulder': 0,
            'Spine': 0
        },
        60: {
            'LeftHip': 30, 'RightHip': -30,
            'LeftKnee': 45, 'RightKnee': 0,
            'LeftShoulder': -20, 'RightShoulder': 20,
            'Spine': 5
        }
    }

    # Animate the walk cycle
    for frame, rotations in walk_frames.items():
        bpy.context.scene.frame_set(frame)

        for bone_name, rotation in rotations.items():
            if bone_name in pose_bones:
                bone = pose_bones[bone_name]

                if 'Hip' in bone_name or 'Shoulder' in bone_name:
                    # Rotation around X axis
                    bone.rotation_euler = (math.radians(rotation), 0, 0)
                elif 'Knee' in bone_name:
                    # Bend knee forward
                    bone.rotation_euler = (math.radians(rotation), 0, 0)
                elif bone_name == 'Spine':
                    # Spine rotation
                    bone.rotation_euler = (0, 0, math.radians(rotation))

                bone.keyframe_insert(data_path="rotation_euler", frame=frame)

    # Animate root movement (forward motion)
    for frame in range(1, frames + 1, 5):
        bpy.context.scene.frame_set(frame)
        root_bone = pose_bones['Root']
        # Move forward 0.1 units per 5 frames
        root_bone.location.y = (frame - 1) / 5 * 0.1
        root_bone.keyframe_insert(data_path="location", frame=frame)

    bpy.ops.object.mode_set(mode='OBJECT')

def create_facial_expressions(armature, mesh_obj):
    """Create facial expression controls using shape keys"""
    # Add shape keys to head mesh
    head_obj = None
    for obj in bpy.context.scene.objects:
        if obj.name == "Head":
            head_obj = obj
            break

    if not head_obj:
        return None

    # Go to object mode
    bpy.context.view_layer.objects.active = head_obj
    bpy.ops.object.mode_set(mode='OBJECT')

    # Add shape keys
    head_obj.shape_key_add(name='Basis')

    # Smile shape key
    smile_key = head_obj.shape_key_add(name='Smile')
    # Modify vertices for smile (simplified)
    if hasattr(smile_key.data, 'foreach_set'):
        # This would normally involve complex vertex manipulation
        pass

    # Frown shape key
    frown_key = head_obj.shape_key_add(name='Frown')

    # Create driver setup for facial expressions
    # This would normally link bone rotations to shape key values

    return head_obj.shape_keys

def create_animation_system():
    """Create a complete animation system with controls"""
    print("Creating character animation system...")

    # Create character mesh
    parts = create_character_mesh()

    # Create armature
    armature, bones = create_armature(parts)

    # Setup IK constraints
    ik_targets = setup_ik_constraints(armature, bones)

    # Apply automatic weights to all parts
    for part_name, part_obj in parts.items():
        auto_weight_mesh(part_obj, armature)

    # Create walk cycle
    create_walk_cycle(armature, frames=60)

    # Create facial expressions
    shape_keys = create_facial_expressions(armature, parts['head'])

    # Add materials
    for part_obj in parts.values():
        mat = bpy.data.materials.new(name=f"Material_{part_obj.name}")
        mat.diffuse_color = (0.8, 0.6, 0.4, 1.0)  # Skin color
        part_obj.data.materials.append(mat)

    return {
        'armature': armature,
        'parts': parts,
        'ik_targets': ik_targets,
        'shape_keys': shape_keys
    }

def main():
    """Main function to demonstrate animation and rigging automation"""
    print("=== Animation and Rigging Automation ===")

    # Clear scene
    clear_scene()

    # Create animation system
    animation_data = create_animation_system()

    # Add lighting
    bpy.ops.object.light_add(type='SUN', location=(5, 5, 10))
    sun = bpy.context.active_object
    sun.data.energy = 3.0

    bpy.ops.object.light_add(type='AREA', location=(-3, -3, 5))
    area = bpy.context.active_object
    area.data.energy = 1.5

    # Add camera
    bpy.ops.object.camera_add(location=(8, -8, 4))
    camera = bpy.context.active_object
    direction = -camera.location
    rot_quat = direction.to_track_quat('-Z', 'Y')
    camera.rotation_euler = rot_quat.to_euler()
    bpy.context.scene.camera = camera

    print("Animation system created successfully!")
    print("- Play animation to see walk cycle")
    print("- Move IK targets (empty objects) to control limbs")
    print("- Use shape keys for facial expressions")

if __name__ == "__main__":
    main()

💻 Automatisation de Matériaux et Textures python

🟡 intermediate ⭐⭐⭐⭐

Création avancée de matériaux, textures procédurales et automatisation de mapping de textures

⏱️ 30 min 🏷️ blender, python, 3d, materials, texturing
Prerequisites: Blender materials, Node editor, Texture mapping
# Blender Material and Texturing Automation
# Procedural materials, texture generation, and material management

import bpy
import math
import random
from bpy_extras.node_shader_utils import PrincipledBSDFWrapper

def clear_scene():
    """Clear all objects from the current scene"""
    bpy.ops.object.select_all(action='SELECT')
    bpy.ops.object.delete()

def create_test_objects():
    """Create test objects to apply materials to"""
    objects = {}

    # Cube
    bpy.ops.mesh.primitive_cube_add(size=2, location=(-4, 0, 0))
    objects['cube'] = bpy.context.active_object
    objects['cube'].name = "TestCube"

    # Sphere
    bpy.ops.mesh.primitive_uv_sphere_add(radius=1, location=(0, 0, 0))
    objects['sphere'] = bpy.context.active_object
    objects['sphere'].name = "TestSphere"

    # Cylinder
    bpy.ops.mesh.primitive_cylinder_add(radius=1, height=3, location=(4, 0, 0))
    objects['cylinder'] = bpy.context.active_object
    objects['cylinder'].name = "TestCylinder"

    # Plane
    bpy.ops.mesh.primitive_plane_add(size=6, location=(0, -4, 0))
    objects['plane'] = bpy.context.active_object
    objects['plane'].name = "TestPlane"

    return objects

def create_procedural_metal_material(name="ProceduralMetal"):
    """Create a procedural metal material with nodes"""
    # Create material
    mat = bpy.data.materials.new(name=name)
    mat.use_nodes = True

    # Get default nodes
    nodes = mat.node_tree.nodes
    links = mat.node_tree.links

    # Clear default nodes
    nodes.clear()

    # Create output node
    output = nodes.new(type='ShaderNodeOutputMaterial')
    output.location = (400, 0)

    # Create principled BSDF
    principled = nodes.new(type='ShaderNodeBsdfPrincipled')
    principled.location = (200, 0)

    # Create noise texture for scratches
    noise = nodes.new(type='ShaderNodeTexNoise')
    noise.location = (-200, 100)
    noise.inputs['Scale'].default_value = 50.0
    noise.inputs['Detail'].default_value = 8.0
    noise.inputs['Roughness'].default_value = 0.8

    # Create color ramp for scratch variation
    color_ramp = nodes.new(type='ShaderNodeValToRGB')
    color_ramp.location = (0, 100)

    # Create metallic gradient
    metallic_gradient = nodes.new(type='ShaderNodeTexGradient')
    metallic_gradient.location = (-200, -100)
    metallic_gradient.gradient_type = 'LINEAR'

    # Create roughness variation
    roughness_noise = nodes.new(type='ShaderNodeTexNoise')
    roughness_noise.location = (-200, -300)
    roughness_noise.inputs['Scale'].default_value = 10.0
    roughness_noise.inputs['Detail'].default_value = 4.0

    # Create math nodes for combining
    roughness_math = nodes.new(type='ShaderNodeMath')
    roughness_math.location = (0, -300)
    roughness_math.operation = 'MULTIPLY'
    roughness_math.inputs[1].default_value = 0.3

    # Create bump for surface detail
    bump = nodes.new(type='ShaderNodeBump')
    bump.location = (0, -500)
    bump.inputs['Strength'].default_value = 0.05

    # Connect nodes
    links.new(noise.outputs['Fac'], color_ramp.inputs['Fac'])
    links.new(color_ramp.outputs['Color'], principled.inputs['Base Color'])
    links.new(metallic_gradient.outputs['Color'], principled.inputs['Metallic'])
    links.new(roughness_noise.outputs['Fac'], roughness_math.inputs[0])
    links.new(roughness_math.outputs['Value'], principled.inputs['Roughness'])
    links.new(noise.outputs['Fac'], bump.inputs['Height'])
    links.new(bump.outputs['Normal'], principled.inputs['Normal'])
    links.new(principled.outputs['BSDF'], output.inputs['Surface'])

    return mat

def create_wood_material(name="ProceduralWood"):
    """Create a procedural wood material with grain patterns"""
    mat = bpy.data.materials.new(name=name)
    mat.use_nodes = True

    nodes = mat.node_tree.nodes
    links = mat.node_tree.links
    nodes.clear()

    # Output node
    output = nodes.new(type='ShaderNodeOutputMaterial')
    output.location = (600, 0)

    # Principled BSDF
    principled = nodes.new(type='ShaderNodeBsdfPrincipled')
    principled.location = (400, 0)

    # Wood ring texture
    rings = nodes.new(type='ShaderNodeTexVoronoi')
    rings.location = (-200, 100)
    rings.voronoi_dimensions = '1D'
    rings.feature = 'DISTANCE_TO_EDGE'
    rings.scale = 0.2

    # Color ramp for wood color
    wood_colors = nodes.new(type='ShaderNodeValToRGB')
    wood_colors.location = (0, 100)
    # Light brown to dark brown
    wood_colors.color_ramp.elements[0].color = (0.6, 0.4, 0.2, 1.0)
    wood_colors.color_ramp.elements[1].color = (0.3, 0.2, 0.1, 1.0)

    # Wood grain texture
    grain = nodes.new(type='ShaderNodeTexNoise')
    grain.location = (-200, -100)
    grain.scale = 50.0
    grain.detail = 2.0
    grain.distortion = 0.1

    # Combine wood patterns
    grain_strength = nodes.new(type='ShaderNodeMath')
    grain_strength.location = (0, -100)
    grain_strength.operation = 'MULTIPLY'
    grain_strength.inputs[1].default_value = 0.2

    # Combine rings and grain
    wood_mix = nodes.new(type='ShaderNodeMixRGB')
    wood_mix.location = (200, 0)
    wood_mix.blend_type = 'OVERLAY'

    # Roughness variation
    roughness = nodes.new(type='ShaderNodeMath')
    roughness.location = (200, -200)
    roughness.operation = 'ADD'
    roughness.inputs[1].default_value = 0.8

    # Bump for grain texture
    wood_bump = nodes.new(type='ShaderNodeBump')
    wood_bump.location = (200, -400)
    wood_bump.inputs['Strength'].default_value = 0.02

    # Connect nodes
    links.new(rings.outputs['Distance'], wood_colors.inputs['Fac'])
    links.new(grain.outputs['Fac'], grain_strength.inputs[0])
    links.new(wood_colors.outputs['Color'], wood_mix.inputs['Color1'])
    links.new(grain_strength.outputs['Value'], wood_mix.inputs['Color2'])
    links.new(wood_mix.outputs['Color'], principled.inputs['Base Color'])
    links.new(grain.outputs['Fac'], roughness.inputs[0])
    links.new(roughness.outputs['Value'], principled.inputs['Roughness'])
    links.new(grain.outputs['Fac'], wood_bump.inputs['Height'])
    links.new(wood_bump.outputs['Normal'], principled.inputs['Normal'])
    links.new(principled.outputs['BSDF'], output.inputs['Surface'])

    return mat

def create_fabric_material(name="ProceduralFabric"):
    """Create a procedural fabric material with weave patterns"""
    mat = bpy.data.materials.new(name=name)
    mat.use_nodes = True

    nodes = mat.node_tree.nodes
    links = mat.node_tree.links
    nodes.clear()

    # Output node
    output = nodes.new(type='ShaderNodeOutputMaterial')
    output.location = (600, 0)

    # Principled BSDF
    principled = nodes.new(type='ShaderNodeBsdfPrincipled')
    principled.location = (400, 0)

    # Fabric weave pattern using wave texture
    weave_x = nodes.new(type='ShaderNodeTexWave')
    weave_x.location = (-400, 100)
    weave_x.wave_type = 'SAW'
    weave_x.scale = 20.0
    weave_x.direction = 'X'

    weave_y = nodes.new(type='ShaderNodeTexWave')
    weave_y.location = (-400, 0)
    weave_y.wave_type = 'SAW'
    weave_y.scale = 20.0
    weave_y.direction = 'Y'

    # Combine weave patterns
    weave_combine = nodes.new(type='ShaderNodeMath')
    weave_combine.location = (-200, 50)
    weave_combine.operation = 'MULTIPLY'

    # Add noise for fabric texture
    fabric_noise = nodes.new(type='ShaderNodeTexNoise')
    fabric_noise.location = (-200, -100)
    fabric_noise.scale = 100.0
    fabric_noise.detail = 1.0

    # Color ramp for fabric color
    fabric_colors = nodes.new(type='ShaderNodeValToRGB')
    fabric_colors.location = (0, 0)
    fabric_colors.color_ramp.elements[0].color = (0.4, 0.3, 0.6, 1.0)  # Purple
    fabric_colors.color_ramp.elements[1].color = (0.6, 0.5, 0.8, 1.0)  # Light purple

    # Subsurface scattering for fabric look
    sss = nodes.new(type='ShaderNodeSubsurfaceScattering')
    sss.location = (200, -200)

    # Combine for final material
    fabric_mix = nodes.new(type='ShaderNodeMixShader')
    fabric_mix.location = (400, 0)

    # Connect nodes
    links.new(weave_x.outputs['Fac'], weave_combine.inputs[0])
    links.new(weave_y.outputs['Fac'], weave_combine.inputs[1])
    links.new(weave_combine.outputs['Value'], fabric_colors.inputs['Fac'])
    links.new(fabric_colors.outputs['Color'], principled.inputs['Base Color'])
    links.new(fabric_noise.outputs['Fac'], principled.inputs['Roughness'])
    links.new(principled.outputs['BSDF'], fabric_mix.inputs[1])
    links.new(sss.outputs['BSSRDF'], fabric_mix.inputs[2])
    links.new(fabric_mix.outputs['Shader'], output.inputs['Surface'])

    return mat

def create_marble_material(name="ProceduralMarble"):
    """Create a procedural marble material with veins"""
    mat = bpy.data.materials.new(name=name)
    mat.use_nodes = True

    nodes = mat.node_tree.nodes
    links = mat.node_tree.links
    nodes.clear()

    # Output node
    output = nodes.new(type='ShaderNodeOutputMaterial')
    output.location = (600, 0)

    # Principled BSDF
    principled = nodes.new(type='ShaderNodeBsdfPrincipled')
    principled.location = (400, 0)

    # Marble veins using musgrave texture
    marble = nodes.new(type='ShaderNodeTexMusgrave')
    marble.location = (-200, 0)
    marble.musgrave_type = 'RIDGED_MULTIFRACTAL'
    marble.scale = 2.0
    marble.detail = 8.0
    marble.dimension = 1.5

    # Wave texture for additional variation
    marble_wave = nodes.new(type='ShaderNodeTexWave')
    marble_wave.location = (-400, 100)
    marble_wave.scale = 1.0
    marble_wave.detail = 0.0
    marble_wave.distortion = 0.5

    # Combine marble patterns
    marble_mix = nodes.new(type='ShaderNodeMath')
    marble_mix.location = (0, 0)
    marble_mix.operation = 'ADD'

    # Color ramp for marble colors
    marble_colors = nodes.new(type='ShaderNodeValToRGB')
    marble_colors.location = (200, 0)
    marble_colors.color_ramp.elements[0].color = (0.95, 0.95, 0.95, 1.0)  # White
    marble_colors.color_ramp.elements[1].color = (0.1, 0.1, 0.1, 1.0)    # Black

    # Glass shader for transparency
    glass = nodes.new(type='ShaderNodeBsdfGlass')
    glass.location = (200, -200)
    glass.inputs['IOR'].default_value = 1.5
    glass.inputs['Roughness'].default_value = 0.1

    # Mix shaders
    shader_mix = nodes.new(type='ShaderNodeMixShader')
    shader_mix.location = (400, 0)

    # Connect nodes
    links.new(marble_wave.outputs['Color'], marble.inputs['Vector'])
    links.new(marble.outputs['Fac'], marble_mix.inputs[0])
    links.new(marble_mix.outputs['Value'], marble_colors.inputs['Fac'])
    links.new(marble_colors.outputs['Color'], principled.inputs['Base Color'])
    links.new(principled.outputs['BSDF'], shader_mix.inputs[1])
    links.new(glass.outputs['BSDF'], shader_mix.inputs[2])
    links.new(marble.outputs['Fac'], shader_mix.inputs['Fac'])
    links.new(shader_mix.outputs['Shader'], output.inputs['Surface'])

    return mat

def create_pbr_texture_material(image_path, name="PBRMaterial"):
    """Create a PBR material from texture images"""
    # This would normally load from actual image files
    # For demonstration, we'll create placeholder nodes

    mat = bpy.data.materials.new(name=name)
    mat.use_nodes = True

    nodes = mat.node_tree.nodes
    links = mat.node_tree.links

    # Get existing principled BSDF
    principled = nodes.get('Principled BSDF')

    # Create texture nodes (these would normally load actual images)
    base_color_tex = nodes.new(type='ShaderNodeTexImage')
    base_color_tex.name = "BaseColorTexture"
    base_color_tex.location = (-300, 100)
    # base_color_tex.image = bpy.data.images.load(f"{image_path}_base_color.png")

    normal_tex = nodes.new(type='ShaderNodeTexImage')
    normal_tex.name = "NormalTexture"
    normal_tex.location = (-300, 0)
    # normal_tex.image = bpy.data.images.load(f"{image_path}_normal.png")

    roughness_tex = nodes.new(type='ShaderNodeTexImage')
    roughness_tex.name = "RoughnessTexture"
    roughness_tex.location = (-300, -100)
    # roughness_tex.image = bpy.data.images.load(f"{image_path}_roughness.png")

    metallic_tex = nodes.new(type='ShaderNodeTexImage')
    metallic_tex.name = "MetallicTexture"
    metallic_tex.location = (-300, -200)
    # metallic_tex.image = bpy.data.images.load(f"{image_path}_metallic.png")

    # Normal map node
    normal_map = nodes.new(type='ShaderNodeNormalMap')
    normal_map.location = (-100, 0)

    # Separate color channels
    separate_roughness = nodes.new(type='ShaderNodeSeparateColor')
    separate_roughness.location = (-100, -100)
    separate_roughness.mode = 'RGB'

    separate_metallic = nodes.new(type='ShaderNodeSeparateColor')
    separate_metallic.location = (-100, -200)
    separate_metallic.mode = 'RGB'

    # Connect nodes
    links.new(base_color_tex.outputs['Color'], principled.inputs['Base Color'])
    links.new(normal_tex.outputs['Color'], normal_map.inputs['Color'])
    links.new(normal_map.outputs['Normal'], principled.inputs['Normal'])
    links.new(roughness_tex.outputs['Color'], separate_roughness.inputs['Color'])
    links.new(separate_roughness.outputs['Blue'], principled.inputs['Roughness'])
    links.new(metallic_tex.outputs['Color'], separate_metallic.inputs['Color'])
    links.new(separate_metallic.outputs['Blue'], principled.inputs['Metallic'])

    return mat

def apply_uv_mapping(obj, projection_type='CUBE'):
    """Apply UV mapping to an object"""
    bpy.context.view_layer.objects.active = obj
    bpy.ops.object.mode_set(mode='EDIT')

    # Select all faces
    bpy.ops.mesh.select_all(action='SELECT')

    # Apply UV mapping
    if projection_type == 'CUBE':
        bpy.ops.uv.cube_project()
    elif projection_type == 'SPHERE':
        bpy.ops.uv.sphere_project()
    elif projection_type == 'CYLINDER':
        bpy.ops.uv.cylinder_project()
    else:  # SMART
        bpy.ops.uv.smart_project(angle_limit=66.0, island_margin=0.0)

    bpy.ops.object.mode_set(mode='OBJECT')

    return obj.data.uv_layers.active

def create_material_library():
    """Create a library of procedural materials"""
    materials = {}

    # Create different material types
    materials['metal'] = create_procedural_metal_material("SteelMetal")
    materials['wood'] = create_wood_material("OakWood")
    materials['fabric'] = create_fabric_material("VelvetFabric")
    materials['marble'] = create_marble_material("WhiteMarble")

    # Add variations
    materials['gold_metal'] = create_procedural_metal_material("GoldMetal")
    materials['dark_wood'] = create_wood_material("DarkWood")
    materials['denim'] = create_fabric_material("DenimFabric")

    return materials

def apply_materials_to_objects(objects, materials):
    """Apply materials to objects with appropriate UV mapping"""
    # Apply UV mapping
    apply_uv_mapping(objects['cube'], 'CUBE')
    apply_uv_mapping(objects['sphere'], 'SPHERE')
    apply_uv_mapping(objects['cylinder'], 'CYLINDER')
    apply_uv_mapping(objects['plane'], 'SMART')

    # Apply materials
    objects['cube'].data.materials.append(materials['metal'])
    objects['sphere'].data.materials.append(materials['marble'])
    objects['cylinder'].data.materials.append(materials['wood'])
    objects['plane'].data.materials.append(materials['fabric'])

    print("Materials applied to objects with UV mapping")

def create_material_preview():
    """Create a material preview scene with proper lighting"""
    # Create HDRI lighting setup
    bpy.ops.object.light_add(type='SUN', location=(5, 5, 10))
    sun = bpy.context.active_object
    sun.data.energy = 5.0

    bpy.ops.object.light_add(type='AREA', location=(-3, -3, 5))
    area = bpy.context.active_object
    area.data.energy = 2.0
    area.data.size = 3.0

    bpy.ops.object.light_add(type='AREA', location=(0, 3, 3))
    fill = bpy.context.active_object
    fill.data.energy = 1.0
    fill.data.size = 2.0

    # Setup camera
    bpy.ops.object.camera_add(location=(8, -8, 4))
    camera = bpy.context.active_object
    direction = -camera.location
    rot_quat = direction.to_track_quat('-Z', 'Y')
    camera.rotation_euler = rot_quat.to_euler()
    bpy.context.scene.camera = camera

    # Setup render settings
    scene = bpy.context.scene
    scene.render.engine = 'CYCLES'
    scene.cycles.samples = 128
    scene.render.resolution_x = 1920
    scene.render.resolution_y = 1080

def main():
    """Main function to demonstrate material and texturing automation"""
    print("=== Material and Texturing Automation ===")

    # Clear scene
    clear_scene()

    # Create test objects
    objects = create_test_objects()

    # Create material library
    materials = create_material_library()
    print(f"Created {len(materials)} procedural materials")

    # Apply materials to objects
    apply_materials_to_objects(objects, materials)

    # Create preview setup
    create_material_preview()

    print("Material system setup complete!")
    print("- Materials: Metal, Wood, Fabric, Marble")
    print("- Each material uses procedural nodes")
    print("- Objects have UV mapping applied")
    print("- Render with F12 to preview materials")

if __name__ == "__main__":
    main()