Halloween Costume ideas 2015

PYTHON / The Inception of List Comprehension

During my course of research and exploration of Python and Blender Python (BPY), I heard a strange term coined few times: LIST COMPREHENSION. This term was heard by me the first time via Taisuke Tanimura. He said, this is what makes Python pretty "awesome".

Well, thanks to him, I actually started using it too.

LIST COMPREHENSION
List Comprehension is something crazy (I reckon) that you can do in Python. A little bit like inception, where you can put Loop and Expression and Iteration, within another Loop and Expression, and so on, where the end result is just a LIST that you need.

We can take analogy of a Basket Full of Fruits:
1. We want to filter the basket and get type of Apple, Banana, and Oranges.
2. Continue to filter those fruits are already ripe and certain size (big).
3. Then we take those fruits and separate them into 3 different basket, for each type.

Basically the List Comprehension is the shortening of FOR, IF, etc LOOPS into a single [LONG SENTENCES or STATEMENT] that will form a usable LIST for further processing. The List itself is still LIVE, meaning it will always process what happens previously.

As long, we don't go too crazy with List Comprehension, the code will be still readable and the compactness of the code might be preferable.

I mentioned how LISTING OBJECTS in 3D is the first important step to do something useful. I will write further about it.

Just follow along, hopefully you will get some kind of understanding of it by the end. We will go deeper and deeper by the end of this writing.

BLENDER PYTHON (BPY) AND PYTHON PROGRAMMING LANGUAGE
I am still relatively new to Python and Blender Python.

The way I can understand those two together (excuse me if I am wrong):
"Blender Python (BPY) is a collection of Library and Dictionary of Components and Operations, etc, that can be used and put together to create YOUR own functions and tools, using Python language."
Currently, I won't be touching Blender Game Python Programming yet, because it is another whole different world. I still have a list of beginner Python books to read to understand the language a little better. But I don't want to just learn the Python language, I want to apply it inside Blender or other 3D programs.

I am sometimes having this question: "Am I really learning Python programming concept or Blender Python?". I guess the answer is both.

For non-programmer background like me, to start to understand programming and Python language, it is a lot fun to do it inside Blender.

For me, the really cool thing both with Blender & Python Combo of learning is that ANYONE can just go to a Public Library, use the available computer that has decent Internet, download Blender ZIP then right away I can do both 3D Programming in Blender and do some 3D things.

I actually encourage such way of thinking.

Enough babbling, let's power up Blender and do some Python.

LIST AND SELECT CERTAIN TYPE *
I created 6 boring primitive objects:


In previous post, most of my Python script doodles work by expecting user to select some objects. We cannot always do this.

Often we want the script to just select objects via script as further specified by user, it could be:

  • Select By Name or List of Names
  • Select By Type
  • Select By Other Criteria
I will select those 6 objects in 3D scene and do below:

# Create Variable named selected objects and add bpy.context.selected_objects into it

import bpy

selected_objects = bpy.context.selected_objects
print(selected_objects)


Fine, nothing new, right?

Now, I actually just want Blender to spit out the NAME of every object I selected, not list starting with bpy.data.objects.

LIST THE NAMES OF SELECTED OBJECTS*

https://gist.github.com/enzyme69/5229868

# NORMAL STYLE
import bpy

selected_objects = bpy.context.selected_objects
obNameList = []

for item in selected_objects:
    obNameList.append(item.name) #append each name into list

print(obNameList)

# LIST COMPREHENSION STYLE
print([item.name for item in selected_objects])


# RESULT
# ['Icosphere', 'Cylinder', 'Cone', 'Torus', 'Suzanne', 'Cube']



I will now deselect everything in the scene using Python:

bpy.ops.object.select_all(action='DESELECT')


Alright, now that I have just the name of objects in the scene, I want to be able to select them using their names. How would we do that?

SELECT BY NAME*

import bpy

obNameList = ['Icosphere', 'Cylinder', 'Cone', 'Torus', 'Suzanne', 'Cube']

objects = bpy.data.objects

for name in obNameList:
    objects[name].select = True

That script above select each of 6 boring primitive objects by their names. Fantastic. We can also specify few names to be selected and it will work.

From what we know in Blender and in 3D packages in general, we usually have "selected objects" and a single "active object". Active object is usually the last object being selected and highlighted by yellow color, while the rest will be orange color.

While we did give a bunch list of objects for Blender to select, we do not always get the correct "active object". How do we do that?



SPECIFY "AN ACTIVE OBJECT" FOR SELECTED OBJECTS*

import bpy

obNameList = ['Icosphere', 'Cylinder', 'Cone', 'Torus', 'Suzanne', 'Cube']

objects = bpy.data.objects

for name in obNameList:
    objects[name].select = True
    
myActive = 'Suzanne'

bpy.context.scene.objects.active = bpy.data.objects[myActive]




UPDATE 2013.03.29
I don't know if the Blender Python Built-In  function below is still exist or not, but just now I checked it, the function is not there. Maybe we can just use Select By Pattern (see down below).
bpy.ops.object.select_name

Read:
http://blenderartists.org/forum/showthread.php?187565-bpy-ops-object-select_name-doesn-t-work


SPECIAL: QUICK SELECT INPUT AND OUTPUT

I read a little bit about Python File Open and File Write. We basically can easily tell Python to output whatever we can print as readable TXT file.

Let say we have some objects in the scene and we like to save it out as "Quick Select Set" for Blender. We can have script like below that can Save Out the Name of Selected Objects as a file.




Selected Objects --> Write Data Out


# NORMAL STYLE
import bpy

selected_objects = bpy.context.selected_objects
obNameList = []

for item in selected_objects:
    obNameList.append(item.name) #append each name into list


### WRITING FILE ###

outfile = open('selectMe.txt', 'w')

outfile.write("\n".join(obNameList))

outfile.close



On the other hand, we can now reuse the script so that we can select Object by Name specified from file outside Blender.

Open Data In --> Select Objects

### READING FILE ###

infile = open('selectMe.txt', 'r')

listAll = infile.read()

infile.close()

# Split lines back into normal flat List
mySelectList = listAll.split()


### BLENDER SELECT FROM LIST ###

import bpy

# Deselect any selected objects first

bpy.ops.object.select_all(action='DESELECT')

obNameList = mySelectList

objects = bpy.data.objects

for name in obNameList:
    objects[name].select = True


My scripts above are pretty rough, but I guess I will refine it further in the future. For now, you can quickly save out LIST of Selected Objects and import back the list and tell Blender to do quick select.




SELECT BY SPECIFIC LIST OF NAMES, USING A CUSTOM FUNCTION*
Ok, we now should know how to select object by names, but there seems to be a lot of work, don't you think? We cannot expect user to write all above every time they wanted to select objects.

We need to simplify the script above and turn it into FUNCTION.

Python function is defined like this:

def (argument):
    {do something with argument provided}
    return {output}

With that knowledge, we can simply create a Python function for "selecting objects by name" so that users can simply run the function whenever they want to select object or objects.

An example for such function is below:

import bpy

def select(objects, replace = True, active=''):
    '''
    Blender select objects by name ala Maya
    '''

    if isinstance(objects, str):
       # convert a string to list
       objects=list([objects])

    else:
       print('It is a list already')

    if replace:
       bpy.ops.object.select_all(action='DESELECT')

  
    for item in objects:

       ob = bpy.data.objects.get(item)
       ob.select = True
       
    if (active==''):
        active = objects[-1]
    else:
        active = active

    bpy.context.scene.objects.active = bpy.data.objects[active]
            

Once we declare a function, we can use it like below and re-use it multiple time as we like.

# EXAMPLE 1:
select('Torus', replace=True)

# EXAMPLE 2:
select(['Suzanne', 'Cube'], replace = False)

# EXAMPLE 3:
select(['Torus', 'Icosphere', 'Suzanne'], replace = True, active = 'Icosphere')


Notice that you need to specify List of String or A String to be selected.

The function I created above is pretty rough and it does not have 100% error checking, but it does the job and I think it is okey as quick example.

I will have the final version later on.

SELECT BY FILTERED NAME (using Python 'fnmatch' module) *
Imagine if we have the 6 boring primitive objects above and then we have actually some useful objects named:
- Sphere.001
- Sphere.002
- Sphere.003
- Sphere.004
- Sphere.005
- and so on.

How do we able to select things like that? Would not it be nice if we can tell Blender to: "Select objects that with this pattern "Sphere.*".

Can we do that? Yes, we can. Python can do it. Python has a special module called "fnmatch" that we can import and use, this allowing us to do wildcard filter by name.
How to select those Sphere using Python?


import bpy
import fnmatch

# Get All Meshes Objects
objects = bpy.data.objects

## NORMAL STYLE

# Get Names of All Meshes as a List of Strings
myList = [objects[i].name for i in range(len(objects))]

# Use fnmatch to filter the name
newList = fnmatch.filter(myList, 'Cube.*')

print(newList)

for item in newList:
    bpy.data.objects[item].select = True


## LIST COMPREHENSION STYLE
myListX = fnmatch.filter( [objects [i].name for i in range(len(objects ))] , 'Sphere.*')

for item in myListX:
    bpy.data.objects[item].select = True


UPDATE 2013.03.29
SELECT BY PATTERN (Built In Blender Python Function)
I found out that apparently you can already do select object with filter, by using Select By Pattern:

bpy.ops.object.select_pattern(extend=False, pattern="Cube*", case_sensitive=False)


FILTER THE LIST FURTHER USING SLICE, ENUMERATE, ETC*

I want to push it a little further with this by adding extra layer of filtering. For example, based on the "Filtered List Using Wildcard" above, what if we like to further select just sliced of them?

We can use the SLICE method for LIST.

import bpy
import fnmatch

# Get All Meshes Objects
objects = bpy.data.objects

### LIST COMPREHENSION STYLE
myListX = fnmatch.filter( [objects [i].name for i in range(len(objects ))] , 'Sphere.*')

myListXSlice = myListX[0:5]

for item in myListXSlice:
    bpy.data.objects[item].select = True

Further LIST processing can be done to the List of Strings to order and re-order them.


Anyways, long story short, the FINAL INCEPTION may look something like below:

import bpy
import fnmatch

objects = bpy.data.objects

test = [ item for item in list(enumerate( [x for x in fnmatch.filter( [objects[i].name for i in range(len(objects))] , 'Sphere*')] )) if item[0]<5 ]

print(test)

Try changing 'Sphere*' with other wildcard search and then the expression at the very back with other type of expression. I enumerate the LIST so that it is possible to randomize the list order etc. Just in case we want to do more with it.

THE WHOLE "INCEPTION" LIST COMPREHENSION THING:
All above subheading with (*) asterisk is important to understand the whole thing.

1. List objects in the scene (can be filtered further if it is a mesh or camera, etc)
2. Further filter the name list using wildcard, if matches, continue pass it on to next filter.
3. Turn the Filtered List into a Sortable List
4. Do something with the order of the list.

All packed into a single line to represent LIST.

For example of list above:
[ item for item in list(enumerate( [x for x in fnmatch.filter( [objects[i].name for i in range(len(objects))] , 'Sphere*')] )) if item[0]<5 ]

[ Get every item in list [ For every item that matches word 'Sphere*' [Get the name of objects in the scene with particular type ] if that item is less than 5]

When packed like that, it feels complex at a glance. We probably don't ever need to do that kind of complex List Comprehension, but we can do that. It's like a layer within layer within another layer and so on.


NOW THE INCEPTION PUZZLE:
Imagine a bottle full of hundreds of objects, and we need to filter and select a bunch of Monkeys in Red, Green, Blue colored Material only.

And what if we want to put Red Monkeys inside Green Monkeys inside Blue Monkeys?

Can we do that using Python?


BLENDER SUSHI SCRIPT: Arrange Selected Objects In XY Flat 2D Grid Layout


BEFORE


AFTER



import bpy
'''
Arrange selected objects in specified grid space

'''

def arrangeIn2DGrid(width=10, height=10, spread=2):

# Get List of Selected Objects
myObjects = bpy.context.selected_objects

if(myObjects):
print('Please select some objects')

totalGrid = width * height

if(len(myObjects) > totalGrid):
print('You need to specify bigger grid for your objects')

coords = []

for x in range(0,width):
for z in range(0,height):
coords.append([x,z])

# For every object in list, place each one in order into provided coordinates
for number, object in enumerate(myObjects):
object.location = coords[number][0] * spread, coords[number][1] * spread , 0.0


arrangeIn2DGrid(spread = 9)








BONUS UPDATE: Script to Import Pixel Art as Vertex Color Cubes

Since you manage to read up to here, below is an updated version to Pixel Art Script from the other day.

The script is still not fully optimized in term of processing large data, but below script will indeed color each Pixel Cube with Vertex Color 'Col' layer and will link every Pixel Cube into a Single Material only.

So, this is very useful if you want to change the Material to Cycle Render and you want to turn all into Glossy or Glass.

Remember to apply the script only to small Pixel Art image, under 128 x 128 pixel size. Any bigger and the script will take too long time. Wait until I optimize it further.

NOTE: If the script is taking too long time, you simply CTRL+C to halt the script.

BLENDER SUSHI SCRIPT: Pixel Art Import as 3D Cubes With Vertex Color



import bpy

D = bpy.data

# Specify your image here, open it inside Blender Image Editor panel
image_file = 'suzanne_pixel.png'
img = D.images[image_file]
pixels = list(img.pixels)
grouped_list = [pixels[ipx:ipx+4] for ipx in range(0, len(pixels), 4)]

# Get width and height of image (in pixel)
w = width = img.size[0]
h = height = img.size[1]
print("------LENGTH------")
print( len(img.pixels)//4)
print("------PIXEL------")
print(pixels)
print("---GROUPED-PIXEL---")
print(len(grouped_list))

# Create LIST of GRID XY value
rowColList=[]
for i in range(height):
for j in range(width):
rowColList.append(i)
rowColList.append(j)

# SPLIT the LIST of XY
origList = rowColList
splitList = [origList[i:i+2] for i in range(0,len(origList),2)]

# Create a single material that respect Vertex Color
mat = bpy.data.materials.new('PixelVertexColorMat')
mat.use_vertex_color_paint = True
mat.use_vertex_color_light = True

for number in range(len(grouped_list)):
# separate RGBA into each own variables
r = grouped_list[number][0]
g = grouped_list[number][1]
b = grouped_list[number][2]
a = grouped_list[number][3]
# separate XY coordinate
x=splitList[number][0]
y=splitList[number][1]
# create cube at location XY
bpy.ops.mesh.primitive_cube_add(location=(x-width/2, y-width/2, 0))
selectedObject = bpy.context.selected_objects
selectedObject[0].name = 'pixelcube.%s' % number
selectedObject[0].scale = 0.45, 0.45, 0.45
mesh = selectedObject[0]
bpy.context.object.data.materials.append(mat)
# ADD VERTEX COLOR SNIPPET
# Create Vertex Color Layer for each selected meshes
# bpy.context.selected_objects[0].data.vertex_colors
mesh.data.vertex_colors.new()
# Get Total Length of Vertex Colors for each selected meshes
# len(bpy.context.selected_objects[0].data.vertex_colors[0].data)
totalVertCol = len(mesh.data.vertex_colors[0].data)
# Iterate over every mesh vertex color and give it a single colour
for i in range(totalVertCol):
mesh.data.vertex_colors[0].data[i].color = r, g, b
# Print the progress
print ('PixelCube {number} created at:'.format(number=number), x, y, ', with RGB color', r, g, b)



Hatsune Miku Pixel Art



Post a Comment

MKRdezign

Contact Form

Name

Email *

Message *

Powered by Blogger.
Javascript DisablePlease Enable Javascript To See All Widget