Ejemplos de Blender Scripting

Ejemplos de scripting Blender Python para modelado 3D, animación, creación de activos y automatización de flujos de trabajo

💻 Blender Hello World python

🟢 simple ⭐⭐

Configuración básica de scripting Blender con creación de objetos y fundamentos de manipulación de escenas

⏱️ 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.font_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()

💻 Generación de Geometría Procedural python

🟡 intermediate ⭐⭐⭐⭐

Modelado procedural avanzado con funciones matemáticas, objetos paramétricos y geometría compleja

⏱️ 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()

💻 Automatización de Animación y Rigging python

🟡 intermediate ⭐⭐⭐⭐

Sistema de animación automatizado con creación de huesos, weighting y animaciones complejas

⏱️ 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()

💻 Automatización de Materiales y Texturas python

🟡 intermediate ⭐⭐⭐⭐

Creación avanzada de materiales, texturas procedurales y automatización de mapeo de texturas

⏱️ 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()