Basically, I have generated tree roots with UV mapping along the root shape. Something that’s impossible to recreate once I apply the GN modifier. I can not use the Shader Editor attribute workaround as it’s a model intended to be exported outside of Blender.
So I am curious if there’s at least any python workaround to do so? I’ve read somewhere that MLoopUV is not accessible from Python, but maybe that changed recently?
So I actually did it, but it’s a horrible noob python code:
class OBJECT_OT_ConvertAttributeUVMap(bpy.types.Operator):
"""Converts the mesh attribute called "UVMap" to an actual UV Map of the same name"""
bl_idname = "object.convert_attribute_uvmap"
bl_label = "Convert UVMap attribute to UV Map"
@classmethod
def poll(cls, context):
return len(bpy.context.selected_objects) > 0
def execute(self, context):
new_uv = bpy.context.active_object.data.uv_layers.new(name='NewUV')
uv_coords = []
for v_attrib in bpy.context.active_object.data.attributes['UVMap'].data:
uv_coords.append(v_attrib.vector)
for loop in bpy.context.active_object.data.loops:
new_uv.data[loop.index].uv[0] = uv_coords[loop.index][0]
new_uv.data[loop.index].uv[1] = uv_coords[loop.index][1]
return {'FINISHED'}
I wonder if someone who is not python-disabled like me could actually help me figure out how to do this properly using foreach_get and set.
This could be really useful for not just me, but all the people who want to use GN to generate models with texture maps usable outside of Blender.
So here’s slightly more sophisticated (but still a bit crappy) version:
class OBJECT_OT_ConvertAttributeUVMap(bpy.types.Operator):
"""Converst first found face corner attribuet to UVMap"""
bl_idname = "object.convert_attribute_uvmap"
bl_label = "Convert fist face corner attribute to UV Map"
@classmethod
def poll(cls, context):
return context.active_object is not None
def execute(self, context):
source_name = context.active_object.name
# Duplicate active object
bpy.ops.object.duplicate()
# Collapse existing modifiers on the mesh object
bpy.ops.object.convert(target='MESH')
# Store and name the duplicated object
obj = context.active_object
obj.name = source_name + "_Baked"
# Create UVMap UV layer if there's none present
if obj.data.uv_layers.find("UVMap") == -1:
uvmap = obj.data.uv_layers.new(name="UVMap")
else:
uvmap_index = obj.data.uv_layers.find("UVMap")
uvmap = obj.data.uv_layers[uvmap_index]
# UV attribute to store
uv_attrib = None
# Try to find valid face corner attribute
for attribute in obj.data.attributes:
if attribute.domain == 'CORNER':
uv_attrib = attribute
# Check if face corner attribute was found
if uv_attrib is None:
if context.active_object is obj:
bpy.ops.object.delete()
self.report({'ERROR'}, 'No Face Corner attribute found!')
return {'CANCELLED'}
for loop in obj.data.loops:
uvmap.data[loop.index].uv[0] = uv_attrib.data[loop.index].vector[0]
uvmap.data[loop.index].uv[1] = uv_attrib.data[loop.index].vector[1]
obj.data.attributes.remove(uv_attrib)
return {'FINISHED'}
It will find first face corner attribute and turn it into a UVMap, either creating or overriding UVMap channel. This is still bad as it assumes that there is only one Attribute Capture node set to Face Corner domain in the GN network, and that it’s used for UVs, so if someone needed to use Attribute Capture set to face corner multiple times for something else, it’d fail.
I am still curious if there’s a way to gather the attribute vector values using foreach_get and then dump them into the UV map using foreach_get. The face corner attribute is 3D vector while the UV map channel seems to expect 2D vector, so IDK how to do that.
Here’s an example in practice. I’ve made quasi cube UV projection in GN and then used the script to create a copy of the object which converts the projection from attribute into an actual UVMap:
The script isn’t working for me either. No button is created or sth similiar to trigger it?
(no not correct, I was just not willing enough to read and invest the five minutes of effort to figure out how to call the operator, see below, I am sorry it was late and I was discouraged a lot by the broken UVs. Thanks Ludvik for fixing this).
good to know
for anyone else living in the past, this is the same as Ludviks script above but ready for copy paste execute.
import bpy
class OBJECT_OT_ConvertAttributeUVMap(bpy.types.Operator):
bl_idname = "object.convert_attribute_uvmap"
bl_label = "Convert fist face corner attribute to UV Map"
@classmethod
def poll(cls, context):
return context.active_object is not None
def execute(self, context):
source_name = context.active_object.name
# Duplicate active object
bpy.ops.object.duplicate()
# Collapse existing modifiers on the mesh object
bpy.ops.object.convert(target='MESH')
# Store and name the duplicated object
obj = context.active_object
obj.name = source_name + "_Baked"
# Create UVMap UV layer if there's none present
if obj.data.uv_layers.find("UVMap") == -1:
uvmap = obj.data.uv_layers.new(name="UVMap")
else:
uvmap_index = obj.data.uv_layers.find("UVMap")
uvmap = obj.data.uv_layers[uvmap_index]
# UV attribute to store
uv_attrib = None
# Try to find valid face corner attribute
for attribute in obj.data.attributes:
if attribute.domain == 'CORNER':
uv_attrib = attribute
# Check if face corner attribute was found
if uv_attrib is None:
if context.active_object is obj:
bpy.ops.object.delete()
self.report({'ERROR'}, 'No Face Corner attribute found!')
return {'CANCELLED'}
for loop in obj.data.loops:
uvmap.data[loop.index].uv[0] = uv_attrib.data[loop.index].vector[0]
uvmap.data[loop.index].uv[1] = uv_attrib.data[loop.index].vector[1]
obj.data.attributes.remove(uv_attrib)
return {'FINISHED'}
bpy.ops.object.convert_attribute_uvmap()
Hey, thanks for doing the legwork on this, unfortunately I haven’t been able to get it to work as written. Indentation errors abound. Tried everything I could think of to fix, but I’m not familiar enough with Python’s syntax to fix.
Sorry for bugging you further, but I’m still dumb. Found attribute convert in the manual, but doesn’t show up in any editor that I can find. And the post two posts above mine doesn’t say where it’s located.
I don’t understand your problem… but did you try to convert this attribute by inputting it into your node tree and then output it as a vector face corner overwriting an UVMap already created? I’m doing it by converting vertex colors into vertex groups and it works… not sure if it will work for your case…
for loop in obj.data.loops:
uvmap.data[loop.index].uv[0] = uv_attrib.data[loop.index].vector[0]
uvmap.data[loop.index].uv[1] = uv_attrib.data[loop.index].vector[1]
Bummer foreach_get/set do not work here uh?
We can now use bpy.ops.geometry.attribute_convert(mode="UV_MAP")
which is faster than looping millions of loops via python
Btw, that should be fixed in cpp, special case when applying geonode modifiers perhaps?