Python – Some useful list-of-tuples operations

Language:
Python 3.7

Sometimes we need to sort a list of tuples (or other list-type containers) according to their internal values.
For instance, you might have a list of tuples representing X,Y coordinates and need to sort that list according to the Y coordinate of the locations.
Using the sorted function with a lambda function as the supplied key argument can do that.
In this case, each tuple element is fed to the lambda function as the argument x and the function simply returns x[1], which is the second (Y) value within the tuple as the key for the sorting comparison:

sorted(my_tuple_list, key=lambda x: x[1])

Again, in a list of tuples (or other list-type containers), Sometimes we need to find the index of the first occurrence of a tuple that has a specific value as one of its elements.
This example will return the index of the first occurrence in the list of a tuple with the value 8 as its second element:

list(zip(*my_tuple_list))[1].index(8)

The zip function “decouples” the tuples into two lists one containing the tuples first element values and the second containing the tuples second element values.
The zip object is fed into a list function to be converted into a subscript-able list containing the 2 new lists, so now we can use the [1] index to access the list containing only the original tuples’s second elements, and use the index function to get the index of first occurrence of value 8.
> Note that the * operator isn’t a C like “pointer” operator..
Its a Python unpack operator needed to unpack the list of tuples to separate tuple arguments for the zip function.

Python for 3ds max – Loading an image file and reading pixel values

Software:
3ds max 2020

An example of loading and displaying an image file using Python for 3ds max:
* The EXR image file is located in in the same directory with the 3ds max file in this case.
Annotation 2020-05-12 123229
from MaxPlus import BitmapManager
image_file_path = r'BG_park_A.exr'
bmp_storage = MaxPlus.Factory.CreateStorage(17)
bmp_info = bmp_storage.GetBitmapInfo()
bmp_info.SetName(image_file_path)
bmp = BitmapManager.Load(bmp_info)
bmp.Display()

Script explanation line by line:
1. Import the BitmapManager class needed to load image files.
2. Set a variable containing the path to the image file
3. Call the MaxPlus.Factory class’s CreateStorage method to initiate a BitmapStorage object.
This is embarrassing IMO..
And it may very well be that I simply didn’t find the correct way it should be done..
I couldn’t find any other way to independently initiate the BitmapInfo object needed for loading the image, other than Initiating a BitmapStorage object and getting referece to its BitmapInfo object. (the BitmapInfo class has no constructor..)
* If you know a better method to do this I’ll be very grateful if you take the time to comment.
Note:
that the 17 integer argument we supply sets the storage to be compatible with:
32-bit floating-point color depth format (without an alpha channel).
See list of other color format options in this example here:
https://help.autodesk.com/view/3DSMAX/2020/ENU/?guid=__developer_using_maxplus_creating_a_bitmap_html
* They wrote a class containing convenient named constants of the integer arguments (see example code below).
* In this example of creating the BitmapStorage just as a way to generate a BitmapInfo object the actual format you’l supply doesn’t matter, but you can’t use a format that can’t be written to like 8 for example (see list below)
4. Get a reference to the BitmapInfo object contained in the BitmapStorage object.
5. Setting the name property (full file path) of the BitmapInfo object.
6. Loading the image.
7. Displaying the image in 3ds max‘s image viewer window.

Example code for a BitmapStorage format constants container class:

* This example’s source is in the 3ds max help Python examples:
https://help.autodesk.com/view/3DSMAX/2020/ENU/?guid=__developer_using_maxplus_creating_a_bitmap_html
class BitmapTypes(object):
     BMM_NO_TYPE = 0 # Not allocated yet
     BMM_LINE_ART = 1 # 1-bit monochrome image
     BMM_PALETTED = 2 # 8-bit paletted image. Each pixel value is an index into the color table.
     BMM_GRAY_8 = 3 # 8-bit grayscale bitmap.
     BMM_GRAY_16 = 4 # 16-bit grayscale bitmap.
     BMM_TRUE_16 = 5 # 16-bit true color image.
     BMM_TRUE_32 = 6 # 32-bit color: 8 bits each for Red, Green, Blue, and Alpha.
     BMM_TRUE_64 = 7 # 64-bit color: 16 bits each for Red, Green, Blue, and Alpha.
     BMM_TRUE_24 = 8 # 24-bit color: 8 bits each for Red, Green, and Blue. Cannot be written to.
     BMM_TRUE_48 = 9 # 48-bit color: 16 bits each for Red, Green, and Blue. Cannot be written to.
     BMM_YUV_422 = 10 # This is the YUV format - CCIR 601. Cannot be written to.
     BMM_BMP_4 = 11 # Windows BMP 16-bit color bitmap. Cannot be written to.
     BMM_PAD_24 = 12 # Padded 24-bit (in a 32 bit register). Cannot be written to.
     BMM_LOGLUV_32 = 13 BMM_LOGLUV_24 = 14
     BMM_LOGLUV_24A = 15 BMM_REALPIX_32 = 16 # The 'Real Pixel' format.
     BMM_FLOAT_RGBA_32 = 17 # 32-bit floating-point per component (non-compressed),
     RGB with or without alpha
     BMM_FLOAT_GRAY_32 = 18 # 32-bit floating-point (non-compressed), monochrome/grayscale
     BMM_FLOAT_RGB_32 = 19
     BMM_FLOAT_A_32 = 20

Reading pixel values from the image:
Annotation 2020-05-12 143904
bmp_storage = bmp.GetStorage()
hdr_pixel = bmp_storage.GetHDRPixel(3000,200)
print(hdr_pixel)
1. Get reference to the Bitmap‘s BitmapStorage object.
* in this case, writing over the BitmapStorage object we created earlier just to get the BitmapInfo object..
2. Read the pixel value.

Note:

When copying and pasting a script from this example, the indentation may not be pasted correctly.

Related:

 

Python for Blender – Batch renaming objects

Software:
Blender 2.8 | Python 3.7

Some useful short Python snippets for batch re-naming objects in Blender:

  1. Remove last 4 characters from selected object names:
    import bpy
    objects = bpy.context.selected_objects
    for o in objects:
        o.name = o.name[:-4]

    Annotation 2020-04-23 202028

  2. Rename all selected objects to a set base name followed by a 3 digit numeric suffix:
    import bpy
    objects = bpy.context.selected_objects
    for (i,o) in enumerate(objects):
         o.name = "some_base_name_{:03d}".format(i)

    Annotation 2020-04-23 202718

  3. Prefix all selected objects name with their Blender data type like renaming “SomeModel” to “MESH_SomeModel” or for example:
    import bpy
    objects = bpy.context.selected_objects
    for (i,o) in enumerate(objects):
         o.name = "{}_{}".format(o.type,o.name)

    Annotation 2020-04-23 203345

 

* note that when copying and pasting a script from this example, the indentation may not be pasted correctly.

Note:
Blender 2.8 has a robust batch renaming utility that is invoked by pressing Ctrl + F2 so we don’t have to write scripts to do batch renaming of objects:

Annotation 2020-04-24 174503


Related:

Python for Blender – Accessing Mesh Triangles

Python for 3ds max – Select objects of type

Software:
3ds max 2020

Continuing this example,
If you need to select objects (nodes) of a certain type i.e. lights, cameras etc.
You can use the INode class’s GetObject() method to get a reference to the node’s object, or “modified object” in 3ds max terms, and use the object’s GetSuperClassID() method to get the integer id representing the object’s superclass.

In addition, the MaxPlus SuperClassIds class contains convenient constants that can be used to avoid having to check and remember the superclasses numeric ids.
See reference here:
https://help.autodesk.com/view/3DSMAX/2017/ENU/?guid=__py_ref_class_max_plus_1_1_super_class_ids_html

An example of a script that selects all light objects in the scene:

from MaxPlus import SuperClassIds
from MaxPlus import SelectionManager

def scene_objects():
   def list_children(node):
      list = []
      for c in node.Children:
         list.append(c)
         list = list + list_children(c)
      return list
   return list_children(MaxPlus.Core.GetRootNode())

for o in scene_objects():
   obj = o.GetObject()
   if obj.GetSuperClassID() == SuperClassIds.Light:
      SelectionManager.SelectNode(o, False)

* note that when copying and pasting a script from this example, the indentation may not be pasted correctly.

UE4 – Python – Placing level actors bottom at Z 0.0

Software:
Unreal Engine 4.22

This simple Unreal Editor Python example sets the Z axis location of all actors with names beginning with ‘Sphere_’ in a way that their bottom (minimum Z bound) is at height 0.0.

Download the script

> learn how to run Python scripts in the UE4 Editor

import unreal
from unreal import Vector

lst_actors = unreal.EditorLevelLibrary.get_all_level_actors()
print('place actors at 0 z')
for act in lst_actors:
    act_label = act.get_actor_label()
    if 'Sphere_' in act_label:
        print('placing: {}'.format(act_label))
        act_location = act.get_actor_location()
        act_bounds = act.get_actor_bounds(False)
        act_min_z = act_bounds[0].z - act_bounds[1].z
        location_offset = Vector(act_location.x, act_location.y, act_location.z - act_min_z)
        act.set_actor_location(location_offset, False, False)

* note that when copying and pasting a script from this example, the indentation may not be pasted correctly.

Note:
The get_actor_bounds unreal.Actor class method returns a tuple object containing 2 unreal.Vector objects, the first being world space location of the actor geometric center, and the second is the corner of the bounding box relative to the center.

‘Sphere_*’ actors before running the script:

Annotation 2019-12-08 225356.jpg

‘Sphere_*’ actors after running the script:

Annotation 2019-12-08 225503.jpg

 

Related:
Get started with Python for Unreal Editor
UE4 – Python – Importing assets

Blender Python – Accessing mesh triangles

Software:
Blender 2.81 | Python 3.74

By default, mesh triangles are not accessible to Python in Blender.
When accessing the mesh triangles is needed, they must be calculated first using the ‘calc_loop_triangles’ Mesh object method.
Before the calc_loop_triangle method has been called, the loop_triangles property of the Mesh object will reference a empty collection.
After calling the calc_loop_triangles method, the loop_triangles property will reference a collection of MeshLoopTriangle objects, in which the vertices property will hold an array of 3 integers that are the indices of the triangle vertices.

The following example script creates and places sphere objects at the centers of the cube’s triangles:

import bpy
mesh = bpy.data.objects['Cube'].data
mesh.calc_loop_triangles()
for tri in mesh.loop_triangles:
     tri_center = (mesh.vertices[tri.vertices[0]].co * 0.333) +\
                  (mesh.vertices[tri.vertices[1]].co * 0.333) +\
                  (mesh.vertices[tri.vertices[2]].co * 0.333)
     bpy.ops.mesh.primitive_uv_sphere_add(radius=0.1,
                                          enter_editmode=False, 
                                          location=tri_center)

* note that when copying and pasting a script from this example, the indentation may not be pasted correctly.

Download and example file here

Annotation 2019-11-23 224800.jpg
Related:
Python for Blender – Batch Rename Objects

PyCharm – Enabling Scroll Zoom

Software:
PyCharm Community 2019.1

Unlike many popular text/programming editors and IDE’s, PyCharm doesn’t by default let you change the font size by pressing Ctrl + Scrolling the mouse wheel.

To enable scroll-zoom in PyCharm:

  1. Go to File > Settings to open the Settings dialog
  2. In the Settings dialog, navigate to Editor > General
  3. Check the Change font size (Zoom) with Ctrl + Mouse Wheel option.

Untitled-1

Python for 3ds max – Mesh manipulation

Software:
3ds max 2019

An example of creating a mesh ripple deformation using Python for 3ds max:

mesh_manipulation.gif

Script steps:

  1. Define the effect intensity and a helper point object that will serve as the effect center.
  2. Collapse the object to an Editable-Mesh so its vertices will be accessible by Python.
    Note that the Node‘s Object has to be cast as a TriObject, to access the object’s Mesh data.
  3.  Loop through the Mesh’s vertices, get their world position, and set a new Z position as a sine function of the distance from the effect center.
import math
from MaxPlus import Factory
from MaxPlus import ClassIds
from MaxPlus import INode
from MaxPlus import TriObject
from MaxPlus import Matrix3
from MaxPlus import Point3

# Intensity:
effecr_mult = 1.0

# Effect center:
effector = INode.GetINodeByName('Point001')
effect_pos = effector.GetWorldPosition()

# Prepare object and eccess it's mesh data:
node = INode.GetINodeByName('Teapot001')
new_edit_mesh_mod = Factory.CreateObjectModifier(ClassIds.Edit_Mesh)
node.AddModifier(new_edit_mesh_mod)
node.Collapse(True)
node_tm = node.GetWorldTM()
node_pos = node.GetWorldPosition()
obj = node.GetObject()
triobj = TriObject._CastFrom(obj)
mesh = triobj.GetMesh()

# Process the object's vertices:
for i in range(mesh.GetNumVertices()):
     # Get vertex in world space
     vert_pos = mesh.GetVertex(i)
     vert_world_pos = node_tm.VectorTransform(vert_pos)
     vert_world_pos = vert_world_pos + node_pos
     # Get vertex distance from effect center:
     diff_vec = vert_world_pos - effect_pos 
     diff_vec.Z = 0
     dist = diff_vec.GetLength()
     # Set new vertex position:
     mesh.SetVert(i,vert_pos.X,vert_pos.Y,vert_pos.Z + math.sin(dist)*effecr_mult)

* note that when copying and pasting a script from this example, the indentation may not be pasted correctly.

Related Post:
Python for 3ds max – Animated Mesh

Python for 3ds max – replace selected objects with a merged object

Software:
3ds max 2020

merge

The following Python for 3ds max snippet replaces all selected objects with an object merged from an external 3ds max file.

Get this code on gist

Notes:

  1. The ‘model_path’ string variable should be set to the path of the 3ds max file containing the replacement model.
  2. This example takes into account that the merged file contains only a single object, otherwise the first of the merged objects will be used.
  3. The script actually replaces the Object node of the selected objects with the Object node of the merged model (creating instances), assigning them the merged material giving them the merged object’s name appended with numbers.
from MaxPlus import FileManager
from MaxPlus import SelectionManager

model_path = r"D:\Models\Bar_Stool_A.max"

place_holders = []
for o in SelectionManager.Nodes:
     place_holders.append(o)

FileManager.Merge(model_path,True,True)

model = SelectionManager.GetNode(0)

for i in range(len(place_holders)):
     place_holders[i].SetObject(model.GetObject())
     place_holders[i].SetMaterial(model.GetMaterial())
     place_holders[i].SetName(model.Name + "_" + str(i))

model.Delete()

* note that when copying and pasting a script from this example, the indentation may not be pasted correctly.

UE4 – Python – Importing assets

Software:
Unreal Engine 4.20

Untitled-1

Importing assets into a project is done using the import_asset_tasks() function which is a member of the unreal.AssetTools class.
A reference to the AssetTools class is created by calling the get_asset_tools() function which is a member of the unreal.AssetToolHelpers class.
The import_asset_tasks() function requires a list of unreal.AssetImportTask objects as an argument, each unreal.AssetImportTask object in the supplied list represents the import action of a single asset, and contains properties needed for the import operation.
Asset import properties are set using the set_editor_property() function which is called through the AssetImportTask object.
Available asset import properties are listed here:
https://api.unrealengine.com/INT/PythonAPI/class/AssetImportTask.html

In the following example a specified texture file is imported into the project and stored in the Content(Game) > Textures folder.
* If the folder doesn’t exist it will be created.

import unreal
AssetTools = unreal.AssetToolsHelpers.get_asset_tools()
AssetImportTask = unreal.AssetImportTask()
AssetImportTask.set_editor_property('filename', "D:\Wood_Red_A.jpg")
AssetImportTask.set_editor_property('destination_path', '/Game/Textures')
AssetTools.import_asset_tasks([AssetImportTask])

The following example imports all the JPG files from folder: D:\ into the project, stores the new assets in Content(Game)\Textures folder and saves them:

from os import listdir
from os.path import isfile, join
import unreal
dir = "D:\\"
files = [f for f in listdir(dir) if isfile(join(dir, f)) and f[-3:]=='jpg']
AssetTools = unreal.AssetToolsHelpers.get_asset_tools()

import_tasks = []
for f in files:
     print join(dir, f)
     AssetImportTask = unreal.AssetImportTask()
     AssetImportTask.set_editor_property('filename', join(dir, f))
     AssetImportTask.set_editor_property('destination_path', '/Game/Textures')
     AssetImportTask.set_editor_property('save', True)
     import_tasks.append(AssetImportTask)

AssetTools.import_asset_tasks(import_tasks)

* note that when copying and pasting a script from this example, the indentation may not be pasted correctly.

Related:

  1. Get started with Python in UE4
  2. Setting actors locations