UV cube projection preserving vertex coordinates

When I apply "UV > Cube projection" it centers all the islands somewhere around (0.5,0.5) UV coordinates. I'd like, however, to preserve the the local vertex coordinates. For example if the vertex is located at (1,2,3), and is projected along the Z axis, then I want the UV coordinate of that vertex to become (1,2) rather than some arbitrarily translated value. Is it possible?

Rationale: I want the textures of different islands to be aligned with respect to each other.

EDIT: Example of cube projection (left) and desired result (right):

The center of the leftmost cube is located at (0,0,0) in this example.

• 'Project from View' from an orthogonal view will preserve proportions.. but if you need object-space coordinates, see answer. Aug 17 '21 at 9:50
• @RobinBetts: 'project from view' is what I did so far to work-around this issue. However it required manually selecting each set of faces (X, Y or Z-dominant), which is impractical for larger models. I actually don't care about object vs world space (could always bring the object to origin for that). Just need something more automatic. Aug 17 '21 at 14:31
• Btw, I also tried the UV projection modifier with three orthogonal projectors, but it gave somewhat glitchy results. Aug 17 '21 at 14:47
• Could you please clarify, eg edit in your comment on removed answer What is a Y dominant face, one with normal (0, 1, 0) ? Perhaps an image of desired result. Aug 17 '21 at 14:53
• @batFINGER I don't have enough rep to see the deleted answer. I added examples of the desired result (and my own solution in an answer). Aug 19 '21 at 0:42

Cube Projection.

In the python script templates there is a UV operator that sets the UV to the xy vertex coordinates.

Here is a minor edit to that

• Made a dictionary of the three axes eg axes["x"] = (1, 0, 0)
• For each face sort its keys by absolute value face normal dot axis.
• The most aligned is last
• Use first two coordinates to get coordinate
• Note may want to test and flip for faces pointing opposite.

Template script with edits.

import bpy
import bmesh
from mathutils import Matrix

def main(context):
obj = context.active_object
me = obj.data
bm = bmesh.from_edit_mesh(me)

uv_layer = bm.loops.layers.uv.verify()
axes = {a : vec for a, vec in zip("xyz", Matrix.Identity(3))}

for face in bm.faces:
axis = sorted(
"xyz",
key=lambda a : abs(face.normal.dot(axes[a]))
).pop()

for loop in face.loops:
loop_uv = loop[uv_layer]
# use xy position of the vertex as a uv coordinate
loop_uv.uv = getattr(loop.vert.co, "xyz".replace(axis, ""))

bmesh.update_edit_mesh(me)

class UvOperator(bpy.types.Operator):
"""UV Operator description"""
bl_idname = "uv.simple_operator"
bl_label = "Simple UV Operator"

@classmethod
def poll(cls, context):
obj = context.active_object
return obj and obj.type == 'MESH' and obj.mode == 'EDIT'

def execute(self, context):
main(context)
return {'FINISHED'}

def register():
bpy.utils.register_class(UvOperator)

def unregister():
bpy.utils.unregister_class(UvOperator)

if __name__ == "__main__":
register()

# test call (requires edit mode)
bpy.ops.uv.simple_operator()


Instead of building a UV map, I can calculate the UV with an OSL node with the following script:

shader cubemap(vector Position = P, vector Normal = N, output vector UV = 0.0) {
vector p = Position;
vector n = abs(Normal);
if(n.x > n.y && n.x > n.z)
UV = vector(p.y,p.z,0);
else if(n.y > n.x && n.y > n.z)
UV = vector(p.z,p.x,0);
else
UV = vector(p.x,p.y,0);
}


Position and Normal can be connected to the corresponding inputs in the Texture Coordinate node to get local coordinates (or else it defaults to world coordinates).