Python Tools - Session 2A

3D Mosaic Project — Part 2

January 2020

Session Navigation

Overview

Building on the mosaic foundation from Session 1B, we now add color. This session introduces UV space, the colorAtPoint() command for sampling texture colors, and how to map UV coordinates to tile grid positions. By the end, each tile in the mosaic is colored to match the source image.

Key Concepts

  • UV space — a 2D coordinate system on textures where U runs horizontally (0 to 1) and V runs vertically (0 to 1)
  • colorAtPoint() — a PyMEL command that samples the RGB color from a file texture at a given UV coordinate
  • Coordinate mapping — converting tile grid indices to normalized UV values by dividing by the total count
  • Per-vertex color — applying sampled RGB values directly to mesh vertices with polyColorPerVertex
  • Nested loop tracking — maintaining both position (x_pos, z_pos) and UV (u_coord, v_coord) counters simultaneously

UV Coordinate Mapping

As we iterate through the tile grid, we need to calculate where each tile falls in UV space. The key insight is that UV coordinates are normalized (0 to 1), so we increment by 1.0 / count each step:

  • U coordinate increases left-to-right: u_coord += 1.0 / width_count
  • V coordinate decreases top-to-bottom: v_coord -= 1.0 / height_count (V=1 is the top of the image)

Code

The complete mosaic script with color sampling — each tile is created, positioned, and colored to match the source image:

import pymel.core as pm

pm.newFile(force=True)

tile_size = 10
image_path = r"C:\path\to\your\image.jpg"

file_node = pm.createNode('file', name='input_image')
file_node.fileTextureName.set(image_path)

width_count = int(file_node.outSizeX.get() / tile_size)
height_count = int(file_node.outSizeY.get() / tile_size)

x_pos = 0.0
u_coord = 0.0
for x in xrange(0, width_count):
    x_pos += tile_size
    u_coord += 1.0/width_count


    z_pos = 0.0
    v_coord = 1.0
    for z in xrange(0, height_count):
        z_pos += tile_size
        v_coord -= 1.0/height_count

        rgb = pm.colorAtPoint(file_node, u=u_coord, v=v_coord, output='RGB')

        box = pm.polyCube(w=tile_size, h=tile_size, d=tile_size)[0]
        pm.move(box, [x_pos, 0.0, z_pos])
        pm.polyColorPerVertex(box, rgb=rgb, colorDisplayOption=True)
← Prev: Session 1B Next: Session 2B →