1
# Maya Texel Density Checker
# Select mesh transforms or mesh shapes, then run.
# Density result = pixels per Maya scene unit.
# Example: if scene unit is cm, result is px/cm.

import math
import maya.cmds as cmds
import maya.api.OpenMaya as om


# ---------------- SETTINGS ----------------

TEXTURE_SIZE = 2048          # 1024, 2048, 4096, etc.
UV_SET = None                # None = current UV set, or set e.g. "map1"

TARGET_TEXEL_DENSITY = None  # Example: 10.24 for target px/unit. None disables target check.
TOLERANCE_PERCENT = 10.0     # Only used if TARGET_TEXEL_DENSITY is set.

# ------------------------------------------


def polygon_area_2d(us, vs):
    """Shoelace area in UV space."""
    count = len(us)
    if count < 3:
        return 0.0

    area = 0.0
    for i in range(count):
        j = (i + 1) % count
        area += us[i] * vs[j] - us[j] * vs[i]

    return abs(area) * 0.5


def get_selected_mesh_shapes():
    selection = cmds.ls(sl=True, long=True, flatten=True) or []
    if not selection:
        return []

    objects = cmds.ls(selection, objectsOnly=True, long=True) or []

    shapes = []
    seen = set()

    for obj in objects:
        if not cmds.objExists(obj):
            continue

        if cmds.nodeType(obj) == "mesh":
            candidates = [obj]
        else:
            candidates = cmds.ls(
                obj,
                dag=True,
                type="mesh",
                long=True,
                noIntermediate=True
            ) or []

        for shape in candidates:
            if not cmds.objExists(shape):
                continue

            try:
                if cmds.getAttr(shape + ".intermediateObject"):
                    continue
            except Exception:
                pass

            if shape not in seen:
                seen.add(shape)
                shapes.append(shape)

    return shapes


def get_dag_path(node):
    sel = om.MSelectionList()
    sel.add(node)
    return sel.getDagPath(0)


def get_current_uv_set(shape):
    current = cmds.polyUVSet(shape, query=True, currentUVSet=True)
    if current:
        return current[0]
    return None


def get_texel_density_data(shape, texture_size, uv_set=None):
    dag = get_dag_path(shape)
    mesh_fn = om.MFnMesh(dag)

    uv_sets = mesh_fn.getUVSetNames()
    if not uv_sets:
        return {
            "shape": shape,
            "uv_set": None,
            "face_count": 0,
            "world_area": 0.0,
            "uv_area": 0.0,
            "texel_density": 0.0,
            "missing_uv_faces": 0,
            "zero_area_faces": 0,
            "error": "Mesh has no UV sets."
        }

    used_uv_set = uv_set or get_current_uv_set(shape) or uv_sets[0]

    if used_uv_set not in uv_sets:
        return {
            "shape": shape,
            "uv_set": used_uv_set,
            "face_count": 0,
            "world_area": 0.0,
            "uv_area": 0.0,
            "texel_density": 0.0,
            "missing_uv_faces": 0,
            "zero_area_faces": 0,
            "error": "UV set does not exist on mesh."
        }

    face_count = 0
    world_area = 0.0
    uv_area = 0.0
    missing_uv_faces = 0
    zero_area_faces = 0

    it = om.MItMeshPolygon(dag)

    while not it.isDone():
        face_count += 1

        try:
            face_world_area = it.getArea(om.MSpace.kWorld)
        except Exception:
            face_world_area = 0.0

        if face_world_area <= 0.000000000001:
            zero_area_faces += 1
        else:
            world_area += face_world_area

        try:
            us, vs = it.getUVs(used_uv_set)
            face_uv_area = polygon_area_2d(us, vs)

            if face_uv_area <= 0.000000000001:
                missing_uv_faces += 1
            else:
                uv_area += face_uv_area

        except Exception:
            missing_uv_faces += 1

        it.next()

    if world_area > 0.0 and uv_area > 0.0:
        texel_density = texture_size * math.sqrt(uv_area / world_area)
    else:
        texel_density = 0.0

    return {
        "shape": shape,
        "uv_set": used_uv_set,
        "face_count": face_count,
        "world_area": world_area,
        "uv_area": uv_area,
        "texel_density": texel_density,
        "missing_uv_faces": missing_uv_faces,
        "zero_area_faces": zero_area_faces,
        "error": None
    }


def target_status(texel_density):
    if TARGET_TEXEL_DENSITY is None or TARGET_TEXEL_DENSITY <= 0:
        return ""

    diff_percent = ((texel_density - TARGET_TEXEL_DENSITY) / TARGET_TEXEL_DENSITY) * 100.0

    if abs(diff_percent) <= TOLERANCE_PERCENT:
        return "OK ({:+.2f}%)".format(diff_percent)

    if diff_percent < 0:
        return "LOW ({:+.2f}%)".format(diff_percent)

    return "HIGH ({:+.2f}%)".format(diff_percent)


def run_texel_density_check():
    shapes = get_selected_mesh_shapes()

    if not shapes:
        cmds.warning("Select one or more polygon meshes first.")
        return

    print("\n" + "=" * 80)
    print("TEXEL DENSITY CHECK")
    print("Texture size: {0}px".format(TEXTURE_SIZE))

    if TARGET_TEXEL_DENSITY is not None:
        print("Target texel density: {0:.4f} px/unit".format(TARGET_TEXEL_DENSITY))
        print("Tolerance: +/- {0:.2f}%".format(TOLERANCE_PERCENT))

    print("=" * 80)

    total_world_area = 0.0
    total_uv_area = 0.0

    for shape in shapes:
        data = get_texel_density_data(shape, TEXTURE_SIZE, UV_SET)

        print("\nMesh: {0}".format(shape))

        if data["error"]:
            print("  ERROR: {0}".format(data["error"]))
            continue

        print("  UV set:            {0}".format(data["uv_set"]))
        print("  Faces:             {0}".format(data["face_count"]))
        print("  World area:        {0:.6f}".format(data["world_area"]))
        print("  UV area:           {0:.6f}".format(data["uv_area"]))
        print("  Texel density:     {0:.4f} px/unit".format(data["texel_density"]))

        status = target_status(data["texel_density"])
        if status:
            print("  Target status:     {0}".format(status))

        if data["missing_uv_faces"] > 0:
            print("  Warning:           {0} faces have missing/zero UV area".format(data["missing_uv_faces"]))

        if data["zero_area_faces"] > 0:
            print("  Warning:           {0} faces have zero world area".format(data["zero_area_faces"]))

        total_world_area += data["world_area"]
        total_uv_area += data["uv_area"]

    if total_world_area > 0.0 and total_uv_area > 0.0:
        combined_density = TEXTURE_SIZE * math.sqrt(total_uv_area / total_world_area)

        print("\n" + "-" * 80)
        print("Combined selected meshes:")
        print("  Total world area:  {0:.6f}".format(total_world_area))
        print("  Total UV area:     {0:.6f}".format(total_uv_area))
        print("  Texel density:     {0:.4f} px/unit".format(combined_density))

        status = target_status(combined_density)
        if status:
            print("  Target status:     {0}".format(status))

    print("=" * 80 + "\n")


run_texel_density_check()

For immediate assistance, please email our customer support: [email protected]

Download RAW File