1
$\begingroup$

I am trying to replicate with scripts a manual procedure that works well.

Using Smart UV project manually, my object stretches the texture too much; I have found that if I resize the UV map (scale up the Y axis), the distortion disappears.

I have taken the python commands from the manual procedure and dropped them into the script. The only problem is that instead of resizing the UV map, the script resizes the object. I think I need to keep the vertices in the UV map selected while deselecting the mesh vertices; but I have not figured out how to do that.

Question: how can I make sure that the following code resizes the UV map instead of the object?

The code should work if cut-and-pasted; the texture can be downloaded here; to run the code you will need to change the pointer to the file in this line 'imgTex = bpy.data.images.load(...'

import bpy

bpy.ops.mesh.primitive_cylinder_add(vertices=32, radius=0.2, depth=30, enter_editmode=False, location=(0,0,0))

materials = bpy.data.materials
material = materials.get('mat')
if not material:
    material = materials.new('mat')
material.use_nodes = True
if material.node_tree:
    material.node_tree.links.clear()
    material.node_tree.nodes.clear()
nodes = material.node_tree.nodes
links = material.node_tree.links
output = nodes.new(type = 'ShaderNodeOutputMaterial' )
diffuse = nodes.new(type = 'ShaderNodeBsdfDiffuse' )
imgTex = bpy.data.images.load('G:\\Blender\\Textures\\wildtextures-zinc-galvanized-metal-sheet.jpg')
node_texture = nodes.new(type='ShaderNodeTexImage')
node_texture.image = imgTex
node_texture.location = 0,200    
tex_coord = nodes.new(type = 'ShaderNodeTexCoord')
links = material.node_tree.links
link=links.new(tex_coord.outputs["UV"], node_texture.inputs["Vector"]) 
link = links.new(node_texture.outputs[0], nodes.get('Diffuse BSDF').inputs[0])
link = links.new( diffuse.outputs['BSDF'], output.inputs['Surface'] )
obj=bpy.data.objects['Cylinder']
obj.active_material=material
bpy.ops.object.select_all(action='DESELECT')
bpy.data.objects['Cylinder'].select_set(True)
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_all(action='SELECT')
bpy.ops.uv.smart_project()

#AT THIS POINT THE TEXTURE HAS BEEN APPLIED
#BUT LOOKS DISTORTED DUE TO CYLINDER'S GEOMETRY;
#THE FOLLOWING ATTEMPTS TO UNDISTORT BY SCALING
#UP THE UVMAP ON THE Y-AXIS; BUT INSTEAD
#IT SIMPLY SCALES UP THE OBJECT INSTEAD
bpy.ops.uv.select_all(action='SELECT')
bpy.ops.transform.resize(value=(1, 30, 1), orient_type='GLOBAL', orient_matrix=((1, 0, 0), (0, 1, 0), (0, 0, 1)), orient_matrix_type='GLOBAL', constraint_axis=(False, True, False), mirror=True, use_proportional_edit=False, proportional_edit_falloff='SMOOTH', proportional_size=100, use_proportional_connected=False, use_proportional_projected=False)
bpy.ops.object.mode_set(mode='OBJECT')

$\endgroup$
  • $\begingroup$ Rather than using operators the best way to edit UVs is using bmesh to directly modify the assigned values in the mesh data. Can you please share a Blender file with the whole object creation and texturing issue? You shouldn't need to scale UV's like this, your material is not set up properly or your object is having non-uniform scale. $\endgroup$ – kheetor Aug 22 at 14:19
  • $\begingroup$ @kheetor I have updated the initial question to provide all the code and the html location of the texture file. It should be possible to run the code and replicate the error (just the pointer to the texture file needs to be changed). Thanks for your help!!! $\endgroup$ – vndep Aug 22 at 17:25
1
$\begingroup$

Use the right context

The issue here, as often with operators, is context. . If Transfrom resize is run in the context of the UV editor it resizes the selected UVs, if run in the dopesheet scales the selected keyframes, ..., otherwise (or if run from 3dview) it defaults back to the object or the mesh.

Depending on how you are going to invoke your code, may need to override the context such that the operator "thinks" it was invoked in, in this case, the UV area. (Like pressing a button in that area)

Some python console test code

Areas in my current screen, area 2 is the image editor.

>>> for i, a in enumerate(C.screen.areas):
...     i, a.type
...     
(0, 'PROPERTIES')
(1, 'OUTLINER')
(2, 'IMAGE_EDITOR')
(3, 'VIEW_3D')
(4, 'CONSOLE')

>>> ie = C.screen.areas[2]

The mesh is in edit mode with all faces selected. Running the operator like so scales my mesh.

>>> bpy.ops.transform.resize(value=(1, 2, 1))
{'FINISHED'}

If I override context.area such that it is the image editor from above, it scales the UV.

>>> bpy.ops.transform.resize({"area" : ie}, value=(1, 2, 1))
{'FINISHED'}

Related

poll() failed, context incorrect? - Example: bpy.ops.view3d.background_image_add()

Without the operator

From the text editor template templates > python > Operator mesh UV can scale the uv coordinates directly.

    uv_layer = bm.loops.layers.uv.verify()

    # adjust uv coordinates

    S = Matrix.Diagonal((1, 2)) # scale matrix

    for face in bm.faces:
        for loop in face.loops:
            loop_uv = loop[uv_layer]
            # use matrix to scale uv by 2 in y
            loop_uv.uv @= S
$\endgroup$
  • $\begingroup$ Thanks for explaining this. In my attempt to put your insights into practice, I replaced my second to last line of code with the following but got an error that "key 'IMAGE_EDITOR' not found" - code is... bpy.ops.transform.resize({"area" : bpy.context.screen.areas['IMAGE_EDITOR']}, value=(1, 30, 1)) $\endgroup$ – vndep Aug 23 at 21:13
  • $\begingroup$ Answer to linked question has an example of finding the context area. Loop areas to find the one with area.type == 'IMAGE_EDITOR'. $\endgroup$ – batFINGER Aug 23 at 21:55
  • $\begingroup$ the only area.type values I get are: PROPERTIES,OUTLINER,INFO,VIEW_3D,TEXT_EDITOR; not sure what to do at this point... $\endgroup$ – vndep Aug 23 at 22:56
  • $\begingroup$ Answer to linked question also shows other ways to change the context area, such as toggling area type. $\endgroup$ – batFINGER Aug 23 at 23:11
  • $\begingroup$ I'm just not getting this. To get to 'IMAGE_EDITOR' I tried enumerating each window but only got 'layout' rather than 'uv editor' for window in bpy.context.window_manager.windows I also tried changing the layout bpy.context.window.screen = bpy.data.screens['UV Editing'] which threw an error ( Couldn't find layout in this workspace: 'Layout' screen: 'UV Editing'. This should not happen!). I tried setting the screen to 'IMAGE_EDITOR' which simply took me to an image editor, but not the one with the texture image. $\endgroup$ – vndep Aug 24 at 0:29

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.