For certain reasons, which are hard to explain I needed a way to deform a given mesh to match the surface of another mesh, while its internal topology is maintained. I found the best way for me to do this, is define the mapping via the overlap in UV. So here is the type of problem I faced
How to bring the right mesh into the form of the left mesh? Maybe, there is already a function for that but I found it easier to simply unwrap both meshes and define where the uv-area should overlap
So the uv-map of the right mesh is somewhere within the uv-map fo the left mesh. Then hit “Map via UV” and the result is
Here is the add-on. I hope it is useful for some of you.
bl_info = {
"name": "Map via UV",
"description": "Maps a mesh on the form of another mesh via UV-overlap",
"author": "Martin Pyka",
"version": (1, 0),
"blender": (2, 70, 0),
"location": "View3D > Add > Mesh",
"warning": "", # used for warning icon and text in addons panel
"wiki_url": "http://www.martinpyka.de",
"category": "Mesh"}
import bpy
import mathutils
def main(context):
for ob in context.scene.objects:
print(ob)
class MapUVOperator(bpy.types.Operator):
"""Tooltip"""
bl_idname = "object.map_via_uv"
bl_label = "Map via UV overlap"
bl_description = "Maps a mesh on the form of another mesh via UV-overlap"
@classmethod
def poll(cls, context):
active_obj = context.active_object
if active_obj is not None:
return active_obj.type == "MESH"
else:
return False
def execute(self, context):
target_obj = context.active_object
if (bpy.context.selected_objects.index(target_obj) == 0):
origin_obj = bpy.context.selected_objects[1]
else:
origin_obj = bpy.context.selected_objects[0]
map_via_uv(origin_obj, target_obj)
return {'FINISHED'}
def invoke(self, context, event):
return self.execute(context)
def register():
bpy.utils.register_class(MapUVOperator)
def unregister():
bpy.utils.unregister_class(MapUVOperator)
if __name__ == "__main__":
register()
# test call
# bpy.ops.object.simple_operator()
# TODO(SK): Quads into triangles (indices)
def map3dPointToUV(object, object_uv, point, normal=None):
"""Converts a given 3d-point into uv-coordinates,
object for the 3d point and object_uv must have the same topology
if normal is not None, the normal is used to detect the point on object, otherwise
the closest_point_on_mesh operation is used
"""
# if normal is None, we don't worry about orthogonal projections
if normal is None:
# get point, normal and face of closest point to a given point
p, n, f = object.closest_point_on_mesh(point)
else:
p, n, f = object.ray_cast(point + normal * config.ray_fac, point - normal * config.ray_fac)
# if no collision could be detected, return None
if f == -1:
return None
# get the uv-coordinate of the first triangle of the polygon
A = object.data.vertices[object.data.polygons[f].vertices[0]].co
B = object.data.vertices[object.data.polygons[f].vertices[1]].co
C = object.data.vertices[object.data.polygons[f].vertices[2]].co
# and the uv-coordinates of the first triangle
uvs = [object_uv.data.uv_layers.active.data[li] for li in object_uv.data.polygons[f].loop_indices]
U = uvs[0].uv.to_3d()
V = uvs[1].uv.to_3d()
W = uvs[2].uv.to_3d()
# convert 3d-coordinates of point p to uv-coordinates
p_uv = mathutils.geometry.barycentric_transform(p, A, B, C, U, V, W)
# if the point is not within the first triangle, we have to repeat the calculation
# for the second triangle
if (mathutils.geometry.intersect_point_tri_2d(p_uv.to_2d(), uvs[0].uv, uvs[1].uv, uvs[2].uv) == 0) & (len(uvs)==4):
A = object.data.vertices[object.data.polygons[f].vertices[0]].co
B = object.data.vertices[object.data.polygons[f].vertices[2]].co
C = object.data.vertices[object.data.polygons[f].vertices[3]].co
U = uvs[0].uv.to_3d()
V = uvs[2].uv.to_3d()
W = uvs[3].uv.to_3d()
p_uv = mathutils.geometry.barycentric_transform(p, A, B, C, U, V, W)
return p_uv.to_2d()
# TODO(SK): Quads into triangles (indices)
def mapUVPointTo3d(object_uv, uv_list, cleanup=True):
""" Converts a list of uv-points into 3d. This function is mostly
used by interpolateUVTrackIn3D. Note, that therefore, not all points
can and have to be converted to 3d points. The return list can therefore
have less points than the uv-list. This cleanup can be deactivated
by setting cleanup = False. Then, the return-list may contain
some [] elements.
"""
uv_polygons = []
points_3d = [[] for j in range(len(uv_list))]
to_find = [i for i in range(len(uv_list))]
for p in uv_polygons:
uvs = [object_uv.data.uv_layers.active.data[li] for li in p.loop_indices]
for i in to_find:
result = mathutils.geometry.intersect_point_tri_2d(
uv_list[i],
uvs[0].uv,
uvs[1].uv,
uvs[2].uv
)
if (result != 0):
U = uvs[0].uv.to_3d()
V = uvs[1].uv.to_3d()
W = uvs[2].uv.to_3d()
A = object_uv.data.vertices[p.vertices[0]].co
B = object_uv.data.vertices[p.vertices[1]].co
C = object_uv.data.vertices[p.vertices[2]].co
points_3d[i] = mathutils.geometry.barycentric_transform(uv_list[i].to_3d(), U, V, W, A, B, C)
to_find.remove(i)
else:
result = mathutils.geometry.intersect_point_tri_2d(
uv_list[i],
uvs[0].uv,
uvs[2].uv,
uvs[3].uv
)
if (result != 0):
U = uvs[0].uv.to_3d()
V = uvs[2].uv.to_3d()
W = uvs[3].uv.to_3d()
A = object_uv.data.vertices[p.vertices[0]].co
B = object_uv.data.vertices[p.vertices[2]].co
C = object_uv.data.vertices[p.vertices[3]].co
points_3d[i] = mathutils.geometry.barycentric_transform(uv_list[i].to_3d(), U, V, W, A, B, C)
to_find.remove(i)
if len(to_find) == 0:
return points_3d
for p in object_uv.data.polygons:
uvs = [object_uv.data.uv_layers.active.data[li] for li in p.loop_indices]
to_delete = []
for i in to_find:
result = mathutils.geometry.intersect_point_tri_2d(
uv_list[i],
uvs[0].uv,
uvs[1].uv,
uvs[2].uv
)
if (result != 0):
U = uvs[0].uv.to_3d()
V = uvs[1].uv.to_3d()
W = uvs[2].uv.to_3d()
A = object_uv.data.vertices[p.vertices[0]].co
B = object_uv.data.vertices[p.vertices[1]].co
C = object_uv.data.vertices[p.vertices[2]].co
points_3d[i] = mathutils.geometry.barycentric_transform(uv_list[i].to_3d(), U, V, W, A, B, C)
to_delete.append(i)
uv_polygons.append(p)
else:
result = mathutils.geometry.intersect_point_tri_2d(
uv_list[i],
uvs[0].uv,
uvs[2].uv,
uvs[3].uv
)
if (result != 0):
U = uvs[0].uv.to_3d()
V = uvs[2].uv.to_3d()
W = uvs[3].uv.to_3d()
A = object_uv.data.vertices[p.vertices[0]].co
B = object_uv.data.vertices[p.vertices[2]].co
C = object_uv.data.vertices[p.vertices[3]].co
points_3d[i] = mathutils.geometry.barycentric_transform(uv_list[i].to_3d(), U, V, W, A, B, C)
to_delete.append(i)
uv_polygons.append(p)
if len(to_find) == 0:
return points_3d
for d in to_delete:
to_find.remove(d)
if cleanup:
points_3d = [p for p in points_3d if p != []]
return points_3d
def map_via_uv(origin, target):
""" Maps a mesh on the surface of another mesh while preserving
its topology. The mapping uses the uv-space
origin : mesh to map
target : mesh-form that is targeted
"""
if not origin.type == "MESH":
raise Exception("Origin object is not a mesh")
if not target.type == "MESH":
raise Exception("Target object is not a mesh")
o_2d = []
for v in origin.data.vertices:
o_2d.append(
map3dPointToUV(origin, origin, v.co)
)
t_3d = mapUVPointTo3d(target, o_2d)
if len(t_3d) != len(o_2d):
raise Exception("One of the points is not in the UV-grid of the " +
"target object")
for i in range(len(t_3d)):
origin.data.vertices[i].co = t_3d[i]