Last active
March 21, 2024 06:18
-
-
Save BigRoy/60883eff23f73a34c4671395b32d858d to your computer and use it in GitHub Desktop.
Query the used UDIM tiles for the UVs of an input mesh
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from maya import cmds | |
def get_bounding_box_tiles(bb): | |
u_minmax, v_minmax = bb | |
# If the max is exactly on the integer boundary we allow it to be | |
# part of the tile of the previos integer so we can subtract one. | |
# But since we'll need to add one to iterate "up to" the maximum for | |
# the `range` function we will instead add one to the opposite cases. | |
# Inputs are tuples so don't assign elements but override tuple | |
if u_minmax[1] != int(u_minmax[1]): | |
u_minmax = (u_minmax[0], u_minmax[1] + 1) | |
if v_minmax[1] != int(v_minmax[1]): | |
v_minmax = (v_minmax[0], v_minmax[1] + 1) | |
tiles = [] | |
for v in range(*map(int, v_minmax)): | |
for u in range(*map(int, u_minmax)): | |
tiles.append((u, v)) | |
return tiles | |
def get_uv_udim_tiles(mesh, uv_set=None): | |
"""Return the UV tiles used by the UVs of the input mesh. | |
Warning: | |
This does not capture the case where a single UV shell | |
might be layout in such a way that all its UV points are | |
around another tile since it uses the UV shell bounding | |
box to compute the used tiles. In the image below imagine | |
the lines being the UVs of a single shell and the `x` being | |
an emtpy UV tile. It will not detect the empty tile. | |
/ - \ | |
| x | | |
Args: | |
mesh (str): Mesh node name. | |
uv_set (str): The UV set to sample. When not | |
provided the current UV map is used. | |
Returns: | |
list: sorted list of uv tiles | |
""" | |
kwargs = {} | |
if uv_set is not None: | |
kwargs["uvSetName"] = uv_set | |
bb = cmds.polyEvaluate(mesh, boundingBox2d=True, **kwargs) | |
tiles = get_bounding_box_tiles(bb) | |
if len(tiles) == 1: | |
# If there's only a single tile for the bounding box | |
# it'll be impossible for there to be empty tiles | |
# in-between so we just return the given tiles | |
return tiles | |
# Get the bounding box per UV shell | |
uv_shells = cmds.polyEvaluate(mesh, uvShell=True, **kwargs) | |
if uv_shells == 1: | |
# If there's only a single UV shell it must span | |
# all the UV tiles | |
return tiles | |
tiles = set() | |
for i in range(uv_shells): | |
shell_uvs = cmds.polyEvaluate(mesh, uvsInShell=i, **kwargs) | |
shell_bb = cmds.polyEvaluate(shell_uvs, boundingBoxComponent2d=True, **kwargs) | |
shell_tiles = get_bounding_box_tiles(shell_bb) | |
tiles.update(shell_tiles) | |
return sorted(tiles, key=uv2udim) | |
def uv2udim(tile): | |
"""UV tile to UDIM number. | |
Note that an input integer of 2 means it's | |
the UV tile range using 2.0-3.0. | |
Examples: | |
>>> uv2udim((0, 0) | |
# 1001 | |
>>> uv2udim((0, 1) | |
# 1011 | |
>>> uv2udim((2, 0) | |
# 1003 | |
>>> uv2udim(8, 899) | |
# 9999 | |
Returns: | |
int: UDIM tile number | |
""" | |
u, v = tile | |
return 1001 + u + 10 * v | |
# Example usage | |
for mesh in cmds.ls(selection=True): | |
tiles = get_uv_udim_tiles(mesh) | |
print mesh | |
print tiles | |
for tile in tiles: | |
print uv2udim(tile) |
Here's one that uses polyUVCoverage
to remove the false positives from the bounding box queries:
from maya import cmds
import math
def get_bounding_box_tiles(bb):
u_minmax, v_minmax = bb
# If the max is exactly on the integer boundary we allow it to be
# part of the tile of the previos integer so we can subtract one.
# But since we'll need to add one to iterate "up to" the maximum for
# the `range` function we will instead add one to the opposite cases.
# Inputs are tuples so don't assign elements but override tuple
if u_minmax[1] != math.floor(u_minmax[1]):
u_minmax = (u_minmax[0], u_minmax[1] + 1)
if v_minmax[1] != math.floor(v_minmax[1]):
v_minmax = (v_minmax[0], v_minmax[1] + 1)
def floor_int(x):
return int(math.floor(x))
tiles = []
for v in range(*map(floor_int, v_minmax)):
for u in range(*map(floor_int, u_minmax)):
tiles.append((u, v))
return tiles
def get_uv_udim_tiles(mesh, uv_set=None):
"""Return the UV tiles used by the UVs of the input mesh.
Warning:
This does not capture the case where a single UV shell
might be layout in such a way that all its UV points are
around another tile since it uses the UV shell bounding
box to compute the used tiles. In the image below imagine
the lines being the UVs of a single shell and the `x` being
an emtpy UV tile. It will not detect the empty tile.
/ - \
| x |
Args:
mesh (str): Mesh node name.
uv_set (str): The UV set to sample. When not
provided the current UV map is used.
Returns:
list: sorted list of uv tiles
"""
def _remove_false_positives(uvs, tiles):
"""Confirm tiles returned from bounding box.
This will remove the false positives.
"""
if len(tiles) < 2:
# A single tile from bounding
# box must always be correct
return tiles
faces = cmds.polyListComponentConversion(uvs,
fromUV=True,
toFace=True,
internal=True)
confirmed_tiles = []
for tile in tiles:
coverage = cmds.polyUVCoverage(faces,
uvRange=[tile[0],
tile[1],
tile[0]+1,
tile[1]+1])[0]
if coverage > 0.0:
confirmed_tiles.append(tile)
return confirmed_tiles
kwargs = {}
if uv_set is not None:
kwargs["uvSetName"] = uv_set
bb = cmds.polyEvaluate(mesh, boundingBox2d=True, **kwargs)
tiles = get_bounding_box_tiles(bb)
if len(tiles) == 1:
# If there's only a single tile for the bounding box
# it'll be impossible for there to be empty tiles
# in-between so we just return the given tiles
return tiles
# Get the bounding box per UV shell
uv_shells = cmds.polyEvaluate(mesh, uvShell=True, **kwargs)
if uv_shells == 1:
# If there's only a single UV shell it must span
# all the UV tiles
return _remove_false_positives(mesh + ".map[*]", tiles)
tiles = set()
for i in range(uv_shells):
shell_uvs = cmds.polyEvaluate(mesh, uvsInShell=i, **kwargs)
shell_bb = cmds.polyEvaluate(shell_uvs, boundingBoxComponent2d=True, **kwargs)
shell_tiles = get_bounding_box_tiles(shell_bb)
shell_tiles = _remove_false_positives(shell_uvs, shell_tiles)
tiles.update(shell_tiles)
return sorted(tiles)
def uv2udim(tile):
"""UV tile to UDIM number.
Note that an input integer of 2 means it's
the UV tile range using 2.0-3.0.
Examples:
>>> uv2udim((0, 0)
# 1001
>>> uv2udim((0, 1)
# 1011
>>> uv2udim((2, 0)
# 1003
>>> uv2udim(8, 899)
# 9999
Returns:
int: UDIM tile number
"""
u, v = tile
return 1001 + u + 10 * v
# Example usage
for mesh in cmds.ls(selection=True):
tiles = get_uv_udim_tiles(mesh)
print mesh
print tiles
for tile in tiles:
print uv2udim(tile)
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
For sake of testing here are some test simple meshes with their expected tiles in Maya.
Example usage with a function called
get_uv_udim_tiles