8
$\begingroup$

As the title indicates, I'd like, from given UV coordinates, to find their corresponding XYZ coordinates.

I wanted to use the following function (see bellow), to know the UV index (and therefore the corresponding vertices to later do an interpolation) of the UV surrounding triangles before realizing that it may return too many triangles (since more than 1 triangle actually surround a point).

mathutils.geometry.intersect_point_tri_2d(pt, tri_p1, tri_p2, tri_p3)

So... I don't really know if it is possible to do, but if you have any leads on how to find where a random point on the UV texture is going to map on the 3D object please share.

$\endgroup$
  • 1
    $\begingroup$ Do you want to get the corresponding 3D space coordinate for every UV vertex? World or object space? Or also interpolate UV coordinates at which there isn't a UV vertex? $\endgroup$ – CodeManX Sep 3 '15 at 19:17
  • $\begingroup$ @CoDEmanX Thanks for your comment. I'd like to interpolate UV coordinates at which there isn't a UV vertex in order get the corresponding world or object coordinate (it doesn't really matter which one). The problem is that I don't have any known points on the UV texture to do the interpolation. $\endgroup$ – Supercourgette Sep 4 '15 at 6:24
5
$\begingroup$

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)

$\endgroup$
  • $\begingroup$ @CoDEmaxX Thanks you a lot for your answer. :) I made the mistake to think that it wasn't possible to get all the vertex of a same face, and therefore that it wasn't possible to get the 3 corresponding points of a triangle on a UV texture. Your explanation and your code are very easy to understand and to be fair, I wasn't excepting so much. So once again, thanks a lot for your time and your answer. $\endgroup$ – Supercourgette Sep 9 '15 at 9:11

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.