Halloween Costume ideas 2015

PROCEDURAL / Particle Pointilism with Dynamic Paint x PyDriver and PyHandler

This writing is a case study or research note while I am exploring Blender Dynamic Paint, PyDriver and PyHandler to create tool that does Particle Pointilism Art.

It is not a complex task, but does require a little bit of logic and understanding on how data may flow. The end result may not be something that is productive or polished, however, this one is a good example of procedural thinking.

FIRST TEST: Mario Mushroom Pointilism based on Mario Mushroom Pixel Art.

VISUAL DIAGRAM: Particle Pointilism with DP

I started by sketching diagram. The actual sketch is a lot rougher than below.

CONCEPT + IDEA

To be able to convert an IMAGE/PHOTOGRAPH into Pointilism Art that utilizes Blender Dynamic Paint and Particles.

PROBLEM

  • I believe the current Blender Particle has "limited" ability in term of data being carried on per particle basis. Although Blender Particle can emit Particles with RGB Color that seems to correspond to the objet Emitter UV Image Texture RGB Value, we cannot really easily query per Particle RGB color.
  • Dynamic Paint + Particle Color does not directly read UV Image Texture RGB Value. However, the color can be updated during animation and it assign that color to paint on canvas.

SOLUTION

Creating some kind of setup so that we can "sample" Pixel RGB Color (U and V) by referencing object position in 3D (X and Y).

STEP 1: Function to Track Object Position

import bpy
import math as math

ob = bpy.data.objects['ReferenceCube']

# If Reference Cube transform is different to actual pixel
# use ratio multiplication 
ratio = 1

# HELPER FUNCTIONS: Get Cube Position
def getCubePosition():
    x = ob.location[0]
    y = ob.location[1]

    print('X Position:', x)
    print('Y Position:', y)
    print('Pixel X Position:', math.floor(x / ratio))
    print('Pixel Y Position:', math.floor(y / ratio))
    
    return( math.floor(x/ratio),math.floor(y/ratio) )

myPos = getCubePosition()

Test the code and see if it is working:


Here I am tracking the X and Y position of an object called "ReferenceCube", which is a Float (decimal number) value and I know I will need to convert those value into Integer for Image Pixel Color on UV space. The RETURN value of this function is the final Integer value needed to sample Pixel Color.

ON SCREEN TEXT VALUE VIEWER (OPTIONAL)
I think it would be nice if we are able to see and visualize DATA VALUE changing while we are modifying object in real time or during timeline playback (if not real time).

I will attempt to do that using Text Object and Python PyHandler.


Below is an example of script that does it, providing we pass on value into the BODY of Text Object(s). Before running the script below, create a few Text Objects, give it a specific name to reference. I am using Text Object with name "floatXpos", "floatYpos", "xpos_text", and "ypos_text". But the name can be anything. I hard-coded the name of object to track just for simplicity.

# UPDATE TEXT
def updateText(scene):
    ob = bpy.data.objects['ReferenceCube']
    x = ob.location[0]
    y = ob.location[1]

    myPos = getCubePosition()

    fxpost = bpy.data.objects["floatXpos"]
    fxpost.data.body = str(x)
    fypost = bpy.data.objects["floatYpos"]
    fypost.data.body = str(y)
    
    xpost = bpy.data.objects["xpos_text"]
    xpost.data.body = str(myPos[0])
    ypost = bpy.data.objects["ypos_text"]
    ypost.data.body = str(myPos[1])

### SET PYDRIVER
frame = bpy.context.scene.frame_current

### SET PYHANDLER
bpy.app.handlers.frame_change_pre.append(updateText)


The PyHandler code in here is calling that helper function above that updates the Text object BODY value whenever one scrubs on the timeline and/or change the frame number by running the playback. This is totally fine for our purpose. We do not really need to see real-time update the text all the time.

This is the first time I use PyHandler and from my understanding, whatever assigned to PyHandler will get called when certain EVENT is happening. 

Real Time Update Using PyDriver
Alternatively, if we like to see a Real-Time value update, we can use PyDriver.

The simplest way I could think of is to create Custom Properties (set of Key and Value) that gets updated whenever there is changes happening.

Script goes something like below:

import bpy, math

ob = bpy.data.objects['ReferenceCube']

# If Reference Cube transform is different to actual pixel
# use ratio multiplication 
ratio = 1

# HELPER FUNCTIONS: Get Cube Position
def getCubePosition():
    
    print('-'*50)
    
    x = ob.location[0]
    y = ob.location[1]

    print('X Position:', x)
    print('Y Position:', y)
    print('Pixel X Position:', math.floor(x / ratio))
    print('Pixel Y Position:', math.floor(y / ratio))
    
    return( math.floor(x/ratio),math.floor(y/ratio) )

myPos = getCubePosition()

def floatXposVal():
    ob = bpy.data.objects['ReferenceCube']
    x = ob.location[0]
    return x

def floatYposVal():
    ob = bpy.data.objects['ReferenceCube']
    y = ob.location[1]
    return y

def xpos_text():
    xpos = myPos[0]
    return xpos

def ypos_text():
    ypos = myPos[0]
    return ypos

bpy.app.driver_namespace['floatXposVal'] = floatXposVal
bpy.app.driver_namespace['floatYposVal'] = floatYposVal
bpy.app.driver_namespace['xpos_text'] = xpos_text
bpy.app.driver_namespace['ypos_text'] = ypos_text


We are using PyDriver here. And the advantage of PyDriver is we can see the value updating in real time, whenever there is a changes. In some occassion, we need to force the update of PyDriver, but in this case, it will work nicely.

STEP 2: Function to Get RGB value based on X and Y

Next, we need another helper function that simply query and get RGB value based on specified Image. We have seen a usage of such function in here:

I will simplify the function so that it will simply query RGB value based on user specified X and Y value. The X and Y value will come from the Reference Cube position in 3D, as we have planned.

The code looks like this:

# HELPER FUNCTIONS: Get RGB value

D = bpy.data

test_file = 'mushroom.png'
img = D.images[test_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)
width = img.size[0]
height = img.size[1]

def getPixelColor(x=0, y=0):
    r = grouped_list[x + y * width][0]
    g = grouped_list[x + y * width][1]
    b = grouped_list[x + y * width][2]
    # a = grouped_list[x + y * width][3]
    print(r,g,b)
    return (r,g,b)

# TEST GET PIXEL FUNCTION
# getPixelColor(4,6) # x,y

STEP 3: Set RGB value to Material Color of Reference Cube

Here, I will create PyDriver setup that query the RGB value in realtime and let it pass on the value into the RGB color. This color will then get pass on into Particle and Dynamic Paint automatically.

# SET DIFFUSE COLOR OF REFERENCE CUBE MATERIAL
name = 'PixelSampling_MAT' # reference cube material name
MAT = bpy.data.materials[name]

def setPixelColor(rgb):
    print ("set color of material to pixel RGB value")
    MAT.diffuse_color = rgb

def valueR():
    myPos = getCubePosition()
    rgb = getPixelColor(myPos[0], myPos[1])
    return rgb[0]

def valueG():
    myPos = getCubePosition()
    rgb = getPixelColor(myPos[0], myPos[1])
    return rgb[1]

def valueB():
    myPos = getCubePosition()
    rgb = getPixelColor(myPos[0], myPos[1])
    return rgb[2]

bpy.app.driver_namespace['valueR'] = valueR
bpy.app.driver_namespace['valueG'] = valueG
bpy.app.driver_namespace['valueB'] = valueB


STEP 4: Setup Dynamic Paint BRUSH and CANVAS

We are done with the coding actually.

All below procedure should happens and updates automatically:
  • When Cube is repositioned in 3D, particularly the X and Y location, we are tracking it.
  • The X and Y will correspond to the Pixel RGB color based on Image Texture we have specified.
  • If Cube is around position X and Y, let say X=1.42343 and Y=3.432523, then the Material Diffuse Color of the Cube will change to match the pixel position X=1 and Y=3.
All we have to do is to setup the Cube as BRUSH and create a Plane or something as Canvas.

Run the simulation and there we go, we have procedural POINTILISM ART machine in Blender. With little tweak, we could actually paint using the REFERENCE CUBE itself, Dot by Dot.

Remember that the Reference Cube can be any object and so we can have any shape of stamp, converting tiny square pixels into other shapes, when everything goes to plan.


All and all, at least you are learning some codes that can help you to drive value with another value. PyDriver and PyHandler are two things you want to explore.

The way I see PyDriver and PyHandler is like this:
  • If the value can be animated and can be assigned with Drivers, you can modify it using PyDriver.
  • If the value is sort of hidden (like Text object BODY data), you can access and modify it using PyHandler. I heard that PyHandler can create Modifier "on-the-fly".

BLENDER SUSHI SCRIPT: Pointillism Art (rough script with lots of hardcoded thing, needs clean up)


import bpy
import math as math

print('-' * 40)

ob = bpy.data.objects['SamplerCube']

# If Cube Transform is different to actual pixel
ratio = 1

# HELPER FUNCTIONS: Get Cube Position
def getCubePosition():
x = ob.location[0]
y = ob.location[1]

print('X Position:', x)
print('Y Position:', y)
print('Pixel X Position:', math.floor(x / ratio))
print('Pixel Y Position:', math.floor(y / ratio))

return( math.floor(x/ratio),math.floor(y/ratio) )

myPos = getCubePosition()

# HELPER FUNCTIONS: Get RGB value

D = bpy.data

test_file = 'mushroom.png'
img = D.images[test_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)
width = img.size[0]
height = img.size[1]

def getPixelColor(x=0, y=0):
r = grouped_list[x + y * width][0]
g = grouped_list[x + y * width][1]
b = grouped_list[x + y * width][2]
# a = grouped_list[x + y * width][3]
print(r,g,b)
return (r,g,b)

# TEST GET PIXEL FUNCTION
# getPixelColor(4,6) # x,y

# SET DIFFUSE COLOR OF REFERENCE CUBE MATERIAL
name = 'PixelSampling_MAT' #material name
MAT = bpy.data.materials[name]

def setPixelColor(rgb):
print ("set color of material to pixel RGB value")
MAT.diffuse_color = rgb


def valueR():
myPos = getCubePosition()
rgb = getPixelColor(myPos[0], myPos[1])
return rgb[0]

def valueG():
myPos = getCubePosition()
rgb = getPixelColor(myPos[0], myPos[1])
return rgb[1]

def valueB():
myPos = getCubePosition()
rgb = getPixelColor(myPos[0], myPos[1])
return rgb[2]


# UPDATE TEXT
def updateText(scene):
ob = bpy.data.objects['SamplerCube']
x = ob.location[0]
y = ob.location[1]
myPos = getCubePosition()
fxpost = bpy.data.objects["floatXpos"]
fxpost.data.body = str(x)

fypost = bpy.data.objects["floatYpos"]
fypost.data.body = str(y)

xpost = bpy.data.objects["xpos_text"]
xpost.data.body = str(myPos[0])
ypost = bpy.data.objects["ypos_text"]
ypost.data.body = str(myPos[1])

rgb = getPixelColor(myPos[0], myPos[1])
rgbt = bpy.data.objects["rgbValue_text"]
rgbt.data.body = str(rgb)

### SET PYDRIVER
frame = bpy.context.scene.frame_current

bpy.app.driver_namespace['valueR'] = valueR
bpy.app.driver_namespace['valueG'] = valueG
bpy.app.driver_namespace['valueB'] = valueB

### SET PYHANDLER
bpy.app.handlers.frame_change_pre.append(updateText)





NOTE:
I do not know how Blender would handle large image. So far, I tested with 16 x 16 Mario Mushroom Pixel Art. I will give it a try with a larger image after I post this blog. Maybe it will crash or simply running really slowly on the machine.

However, all goes well and sometimes you got lucky, and Blender does magic one again.

TON (2013)

1024x1024 Pixels, 2500 Samples, 3 hours calculation, 3 x DP Brush in Torus Shape.

*Original photo of Ton is from the Internet, which is cropped and resized to 512x512 pixels. Copyright belong to photographer.













STEVE (2013)

1024x1024 Pixels, 2800 Samples, 3 hours calculation, 3 x DP Brush in Torus Shape.

STEVE JOB (2013).
Based from that famous photograph by ....
Below is a more detailed version, using Apple logo as dynamic Ink Stamping, instead of regular spherical dots. The point of every dots are procedurally spread across the image.

2048x2048, 4400 Samples, over 10 hours.







INSPIRATION: William Betts Surveillance Art (pixel art using CCTV image)
As part of the Creator Project, this artist is recycling CCTV image and create a setup that actually paint a giant canvas, dot by dot (like the old dot matrix printer). Quite clever.
http://thecreatorsproject.vice.com/blog/cctv-footage-as-art-the-work-of-william-betts

INSPIRATION: Street Art in Germany
Was Blender ever made for this kind of Art creation? Don't know, keep experimenting.
http://youtu.be/fhL3XgkMiMk

Post a Comment

MKRdezign

Contact Form

Name

Email *

Message *

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