Every point within a UV face describes a point on the mesh face's surface. Every point outside describes a point in the same plane, but outside the face's surface - usually not very useful.
Therefore, a particular UV face should be provided for the interpolation. If the entire UV map contained no overlaps, then any UV coordinate that lies in any of the UV faces will be mappable to a point on the mesh surface, with no ambiguity - in this case, you wouldn't have to specify which UV face to use for the calculation.
But if there are overlaps, then there will be multiple solutions (different XYZ coordinates for every overlapping UV face at the specified UV coordinate).
Every point outside any UV face will not correspond to a point on the mesh surface (but around or inside the mesh). You may want to clamp UV coordinates to [0, 1], so that the given UV coordinate falls within that range again, if texture repetition is desired.
In order to transform the UV coordinate to a 3D space coordinate based on a certain UV face, use barycentric_transform().
Below you find a script that utilized this function.
Note a couple of requirements:
- the script expects one Image / UV editor to be visible
- a mesh must be selected and in editmode and have a uv map
- UV mesh selection sync needs to be enabled in UV editor
- 2D cursor needs to placed at the location you want to get the 3D coordinate for (in UV editor)
- the UV face around the 2D cursor location needs to be selected (this will also select the corresponding mesh face in 3D View because of selection sync)
Then run the script. It should place the 3D cursor at the 3D coordinate. The coordinate stored in "co" is in object space, but turned into world space to place the 3D cursor correctly by multiplying with the object's matrix_world
.
import bpy
import bmesh
from mathutils.geometry import barycentric_transform, intersect_point_tri_2d
# this will use the 2D cursor of the first UV editor found
for area in bpy.context.screen.areas:
if area.type == 'IMAGE_EDITOR':
space_data = area.spaces.active
loc = space_data.cursor_location
norm_coords = space_data.uv_editor.show_normalized_coords
break
else:
raise Exception("No UV editor found")
# mesh UVs are always normalized, but not the 2D cursor!
def uv_normalize(tex, uv):
if tex.image is None:
x, y = 256, 256
else:
x, y = tex.image.size
return (uv[0] / x, uv[1] / y, 0) # to_3d()
ob = bpy.context.object
assert ob.type == "MESH", "Selected object not a mesh"
me = ob.data
bm = bmesh.new()
bm.from_mesh(me)
# tag selected faces, because triangulate may clear selection state
for f in bm.faces:
if f.select:
f.tag = True
# viewport seems to use fixed / clipping instead of beauty
bmesh.ops.triangulate(bm, faces=bm.faces, quad_method=1, ngon_method=1)
# re-select faces
for f in bm.faces:
if f.tag:
f.select_set(True)
uv_layer = bm.loops.layers.uv.active
tex = bm.faces.layers.tex.active
def find_coord(loc, face, uvs):
uv1, uv2, uv3 = uvs
x, y, z = [v.co for v in face.verts]
co = barycentric_transform(loc, uv1, uv2, uv3, x, y, z)
bpy.context.scene.cursor_location = ob.matrix_world * co
random_face = None
sel_faces = [f for f in bm.faces if f.select]
for face in sel_faces:
uv1, uv2, uv3 = [l[uv_layer].uv.to_3d() for l in face.loops]
if norm_coords:
loc_normalized = loc.to_3d()
else:
loc_normalized = uv_normalize(face[tex], loc)
# remember the first face for possible fallback
if random_face is None:
random_face = loc_normalized, face, (uv1, uv2, uv3)
#print("trying", loc_normalized, "vs", uv1, uv2, uv3)
if intersect_point_tri_2d(loc_normalized, uv1, uv2, uv3):
print("found intersecting triangle")
find_coord(loc_normalized, face, (uv1, uv2, uv3))
break
else:
print("trying random selected face for extrapolation")
find_coord(*random_face)
The mesh does not need to be triangulated, but the script may fail especially on non-planar quads (couldn't figure out why exactly).
Due to the triangulation, we do not know which triangle is the best option to use as input for the transformation - we potentially want a point on the mesh surface, and thus we need to chose the triangle the user-defined input point lies within. Otherwise we would extrapolate a coordinate, which is an issue if the target mesh face is a non-planar polygons. We would end up with a coordinate not on the surface in that case, it should still work for planar target polygons however.
To determine that triangle, the script does triangle-point intersection testing for all triangles the triangulation generated from the input face. If the UV-space coordinate the user wants to calculate the 3d coordinate for isn't within the selected UV face, no intersection test will return true. The only option is to abort, or use a random triangle to extrapolate a (possibly unwanted) coordinate - my script does the latter.
The face selection + sync requirement could be lifted by some code to automatically find all intersecting uv faces for the chosen 2D coordinate. You should probably handle overlapping faces, e.g. return one 3D coordinate for every intersecting face.
For some more details, see http://blenderartists.org/forum/showthread.php?379027-Get-3D-location-of-mesh-surface-point-from-UV-Parameter (which was the base for this answer)