Hi, everyone.
Here’s my “version 1.0” Pixelation script. Also attached are two images showing the basic functionality.
Of course, Suzanne isn’t the best example - cambo mentioned that it is not a closed mesh because the eyes are non manifold. But the script mostly works for Suzanne. For any fully closed mesh, however, it should work without any errors.
The GUI allows you to choose the “brick” sizing in all three axes, to choose the destination layer for the bricks, and whether or not you want the original object removed.
Note that it can be quite slow. I received a few suggestions from cambo for speed ups that I’ll look into next.
Some of the key parts of this code are from cambo, with minor/tiny modifications.
BEFORE:
AFTER:
#!BPY
"""
Name: 'Pixelate'
Blender: 241
Group: 'Mesh'
Tooltip: '3D Pixelation of Meshes'
"""
__author__ = "David Levine"
__url__ = ("blender", "elysiun")
__version__ = "1.0 02/24/06"
__bpydoc__ = """\
This script takes closed meshes and 'pixelates' them in 3D.
Usage:
Select the mesh and run this script. You can specify the size of
the blocks used. You can specify the destination layer for those
blocks. You can also specify that the original object is removed.
Upon hitting the PIXELATE button, a 'blocky' version of your original
mesh will be created.
Thanks to: cambo, RipSting, JM Soler
"""
from Blender import *
from Blender.BGL import *
from Blender.Draw import *
from Blender.Noise import *
def ptInFaceXYBounds(f, pt):
co= f.v[0].co
xmax= xmin= co.x
ymax= ymin= co.y
co= f.v[1].co
xmax= max(xmax, co.x)
xmin= min(xmin, co.x)
ymax= max(ymax, co.y)
ymin= min(ymin, co.y)
co= f.v[2].co
xmax= max(xmax, co.x)
xmin= min(xmin, co.x)
ymax= max(ymax, co.y)
ymin= min(ymin, co.y)
if len(f.v)==4:
co= f.v[3].co
xmax= max(xmax, co.x)
xmin= min(xmin, co.x)
ymax= max(ymax, co.y)
ymin= min(ymin, co.y)
# Now we have the bounds, see if the point is in it.
if\
pt.x < xmin or\
pt.y < ymin or\
pt.x > xmax or\
pt.y > ymax:
return False # point is outside face bounds
else:
return True # point inside.
def pointInsideMesh(ob, pt):
obMat = Mathutils.Matrix(ob.matrixWorld)
obInvMat = obMat.invert()
obSpaceVec = pt* obInvMat
obSpacePt = Mathutils.Vector(obSpaceVec[0], obSpaceVec[1], obSpaceVec[2])
Intersect = Mathutils.Intersect # 2 less dict lookups.
Vector = Mathutils.Vector
ray = Vector(0,0,-1)
def faceIntersect(f):
isect= Intersect(f.v[0].co, f.v[1].co, f.v[2].co, ray, obSpacePt, 1) # Clipped.
if not isect and len(f.v) == 4:
isect= Intersect(f.v[0].co, f.v[2].co, f.v[3].co, ray, obSpacePt, 1) # Clipped.
if isect and isect.z > obSpacePt.z: # This is so the ray only counts if its above the point.
return True
else:
return False
me= ob.getData(mesh=1)
# Here we find the number on intersecting faces, return true if an odd number (inside), false (outside) if its true.
return len([None for f in me.faces if ptInFaceXYBounds(f, obSpacePt) if faceIntersect(f)]) % 2
def findminmaxxyz(obj):
minx= 999999999
maxx= -999999999
miny= 999999999
maxy= -999999999
minz= 999999999
maxz= -999999999
boundBox= obj.getBoundBox()
for v in boundBox:
minx= min(minx, v.x)
maxx= max(maxx, v.x)
miny= min(miny, v.y)
maxy= max(maxy, v.y)
minz= min(minz, v.z)
maxz= max(maxz, v.z)
minV= Mathutils.Vector(minx,miny,minz)
maxV= Mathutils.Vector(maxx,maxy,maxz)
return [minV,maxV]
def makeblock(name='blockModel',mat=None,min=[-1.0,-1.0,-1.0],max=[1.0,1.0,1.0]):
vertices_list=[
[min[0],min[1],min[2]],
[min[0],max[1],min[2]],
[max[0],max[1],min[2]],
[max[0],min[1],min[2]],
[min[0],min[1],max[2]],
[min[0],max[1],max[2]],
[max[0],max[1],max[2]],
[max[0],min[1],max[2]]
]
faces_list=[
[0,1,2,3],
[4,5,6,7],
[0,4,7,3],
[1,2,6,5],
[0,1,5,4],
[3,7,6,2]
]
blockobj= Object.New('Mesh',name)
Scene.getCurrent().link(blockobj)
blockobj.layers = [NumberL.val]
blockmesh= blockobj.getData()
for v in vertices_list:
vert= NMesh.Vert(v[0],v[1],v[2])
blockmesh.verts.append(vert)
for f in faces_list:
meshface= NMesh.Face()
for vv in f:
meshface.append(blockmesh.verts[vv])
blockmesh.faces.append(meshface)
if mat:
blockmesh.addMaterial(mat)
blockmesh.update()
return blockobj
def faceCent(f):
x= y= z= 0
for v in f.v:
x+= v.co[0]
y+= v.co[1]
z+= v.co[2]
return Mathutils.Vector([x/len(f.v), y/len(f.v), z/len(f.v)])
def nearestFace(ob, pt):
obMat= Mathutils.Matrix(ob.matrixWorld)
obInvMat= obMat.invert()
obSpaceVec= pt* obInvMat
obSpacePt= Mathutils.Vector(obSpaceVec[0], obSpaceVec[1], obSpaceVec[2])
mesh= ob.getData()
closedist= 999999999
for f in mesh.faces:
fc = faceCent(f)
dist = obSpacePt - fc
if (dist.length < closedist):
closedist = dist.length
closeface = f
return closeface
def pix():
editmode= Window.EditMode()
if editmode:
Window.EditMode(0)
Window.WaitCursor(1)
Window.DrawProgressBar(0.0, '')
count= 1
spacingx= NumberX.val
spacingy= NumberY.val
spacingz= NumberZ.val
objectList= Object.GetSelected()
for obj in objectList:
if obj.getType() == 'Mesh':
minmax= findminmaxxyz(obj)
x= minmax[0].x
while x < (minmax[1].x - (spacingx/4.0)):
progress= (x - minmax[0].x) / (minmax[1].x - minmax[0].x)
Window.DrawProgressBar(progress, "Pixelating...")
y= minmax[0].y
while y < (minmax[1].y - (spacingy/4.0)):
z= minmax[0].z
while z < (minmax[1].z - (spacingz/4.0)):
posvec= Mathutils.Vector([x + (spacingx/2.0),y + (spacingy/2.0),z + (spacingz/2.0), 1.0])
if pointInsideMesh(obj, posvec):
nearFaceMaterial= nearestFace(obj, posvec).materialIndex
if len(obj.getData().getMaterials()) > 1:
nextblock= makeblock('block' + str(count),obj.getData().getMaterials()[nearFaceMaterial],Mathutils.Vector([x,y,z]),Mathutils.Vector([x + spacingx,y + spacingy,z + spacingz]))
else:
nextblock= makeblock('block' + str(count),None,Mathutils.Vector([x,y,z]),Mathutils.Vector([x + spacingx,y + spacingy,z + spacingz]))
count= count + 1
z= z + spacingz
y= y + spacingy
x= x + spacingx
if Replace.val == 1:
Scene.getCurrent().unlink(obj)
Window.DrawProgressBar(1.0, '')
Window.WaitCursor(0)
if editmode:
Window.EditMode(1)
Window.RedrawAll()
Replace= Create(0)
NumberZ= Create(0.20)
NumberY= Create(0.20)
NumberX= Create(0.20)
NumberL= Create(1)
EVENT_NOEVENT = 1
EVENT_PIXELATE = 2
def draw():
global Button, Replace, NumberZ, NumberY, NumberX, NumberL
global EVENT_NOEVENT, EVENT_PIXELATE
glClearColor(0.753, 0.753, 0.753, 0.0)
glClear(GL_COLOR_BUFFER_BIT)
glColor3f(0.000, 0.000, 0.000)
glRasterPos2i(108, 54)
Text('Select this toggle to completely replace the original mesh.')
glRasterPos2i(108, 86)
Text('Z Spacing (0.05 to 10.00)')
glRasterPos2i(108, 118)
Text('Y Spacing (0.05 to 10.00)')
glRasterPos2i(108, 150)
Text('X Spacing (0.05 to 10.00)')
glRasterPos2i(108, 182)
Text('Layer for New Pixels (1 to 20)')
Button('Pixelate!', EVENT_PIXELATE, 16, 10, 83, 23, '')
Replace= Toggle('Replace', EVENT_NOEVENT, 16, 42, 83, 23, Replace.val, '')
NumberZ= Number('', EVENT_NOEVENT, 16, 74, 83, 23, NumberZ.val, 0.05, 10.05, '')
NumberY= Number('', EVENT_NOEVENT, 16, 106, 83, 23, NumberY.val, 0.05, 10.05, '')
NumberX= Number('', EVENT_NOEVENT, 16, 138, 83, 23, NumberX.val, 0.05, 10.05, '')
NumberL= Number('', EVENT_NOEVENT, 16, 170, 83, 23, NumberL.val, 1, 20, '')
def event(evt, val):
if (evt == QKEY and not val): Exit()
def bevent(evt):
if evt == EVENT_PIXELATE:
pix()
Redraw()
Register(draw, event, bevent)