blender version 2.66
import bpy def createMeshFromData(name, origin, verts, faces): # Create mesh and object me = bpy.data.meshes.new(name+'Mesh') ob = bpy.data.objects.new(name, me) ob.location = origin ob.show_name = True # Link object to scene and make active scn = bpy.context.scene scn.objects.link(ob) scn.objects.active = ob ob.select = True # Create mesh from given verts, faces. me.from_pydata(verts, [], faces) # Update mesh with new data me.update() return ob verts = ( (1,0, 2), (0,1, 2), (-1,0, 2), (0,-1, 2), # top (1,0,-2), (0,1,-2), (-1,0,-2), (0,-1,-2), # bottom ( 2,1,0), ( 2,0,1), ( 2,-1,0), ( 2,0,-1), #x+ (-2,1,0), (-2,0,1), (-2,-1,0), (-2,0,-1), #x- (1, 2,0), (0, 2,1), (-1, 2,0), (0, 2,-1), #y+ (1,-2,0), (0,-2,1), (-1,-2,0), (0,-2,-1) #y- ) faces=( (0,1,2,3), (4,7,6,5), (8,9,10,11), (12,15,14,13), (16,19,18,17), (20,21,22,23), (16,17,1,0,9,8), (8,11,4,5,19, 16), (11,10, 20,23,7,4), (0,3,21,20,10,9), (3,2, 13,14, 22,21), (23,22, 14,15, 6,7), (2,1, 17,18, 12,13), (18,19, 5,6, 15, 12), ) to = createMeshFromData('truncOct', (0,0,0), verts, faces) to.data.materials.append(bpy.data.materials['stork']) to.data.uv_textures.new("hippo")the createMeshFromData() function I borrowed from examples on the internet. The uv_textures.new() call is important if you want the object to have a UV mesh. The default mesh gives each ngon its own uv coordinates that fill the square. It's a convenient default for polyhedra that don't have a wrap-around texture.
def popUpMaterial( c, t0, t1, t2, t3): rval = bpy.data.materials.new('fadeIn') rval.use_transparency=1 rval.diffuse_color=(1,1,1) rval.alpha=0 rval.emit=1 rval.diffuse_intensity=0 rval.keyframe_insert(data_path='alpha', frame=t0) rval.alpha=1 rval.keyframe_insert(data_path='alpha', frame=t1) rval.keyframe_insert(data_path='diffuse_color', frame=t1) rval.keyframe_insert(data_path='diffuse_intensity', frame=t1) rval.keyframe_insert(data_path='emit', frame=t1) rval.diffuse_color=c rval.emit=0.2 rval.diffuse_intensity=0.7 rval.keyframe_insert(data_path='diffuse_color', frame=t2) rval.keyframe_insert(data_path='diffuse_intensity', frame=t2) rval.keyframe_insert(data_path='emit', frame=t2) for curve in rval.animation_data.action.fcurves.items(): #print (curve[1].data_path ) if 'diffuse_color'==curve[1].data_path: curve[1].keyframe_points.items()[1][1].interpolation = 'LINEAR' rval.diffuse_color=(0.5,0.5,0.5) rval.keyframe_insert(data_path='diffuse_color', frame=t3) return rvalThis python function creates a material that starts out white (diffuse_color=(1,1,1), but completely transparent (alpha=0). We put a keyframe at t0 to mark the start of the animation.
Then the material fades to solid (alpha=1) at t1. We add some keyframes to the 'diffuse_color' and 'diffuse_intensity' data_paths because from t1 to t2 the material fades from solid white to solid color c. We also fiddle with the 'emit' and 'diffuse_intensity' to let the sunlight and color take over from a bright unshaded white.
Finally we fade to grey at t3.
Also, since keyframes like to default to bezier curves, I had to set them to linear. I'm not sure if there's a way to set that at the time you perform keyframe_insert()
Another important note is that data_path is slightly undocumented. The best way to figure out what it should be is to mouse over the UI element in the Material tab of the Properties panel. A tooltip will appear with stuff like bpy.data.materials["flashIn.025"].alpha, that 'alpha' is what you use for data_path.
addLump(0.0, 0.0, 0.0, 1) addLump(-2.0, -2.0, 2.0, 2) addLump(0.0, 0.0, 4.0, 3) addLump(0.0, -4.0, 0.0, 4) addLump(2.0, 2.0, 6.0, 5) addLump(4.0, 4.0, 4.0, 6)Now I just have to write the addLump function.
# The following function is adapted from # Nick Keeline "Cloud Generator" addNewObject # from object_cloud_gen.py (an addon that comes with the Blender 2.6 package) # def duplicateObject(scene, name, copyobj): # Create new mesh mesh = bpy.data.meshes.new(name) # Create new object associated with the mesh ob_new = bpy.data.objects.new(name, mesh) # Copy data block from the old object into the new object ob_new.data = copyobj.data.copy() ob_new.scale = copyobj.scale ob_new.location = copyobj.location while 0<len(ob_new.data.materials.items()): ob_new.data.materials.pop(0,1) # Link new object to the given scene and select it scene.objects.link(ob_new) ob_new.select = True return ob_new def putInGroup(o, gn): if gn in bpy.data.groups: group = bpy.data.groups[gn] else: group = bpy.data.groups.new(gn) group.objects.link(o) def addLump(x,y,z, t): o2 = duplicateObject(bpy.context.scene, "lump", bpy.context.object) o2.location = (x,y,z) o2.hide_render=1 o2.hide = 1 fr = t*10 o2.keyframe_insert(data_path="hide_render", frame=1) o2.keyframe_insert(data_path="hide", frame=1) o2.hide_render = 0 o2.hide = 0 o2.keyframe_insert(data_path="hide_render", frame=fr) o2.keyframe_insert(data_path="hide", frame=fr) m1 = popUpMaterial( (0,0.8,0) , fr, fr+7, fr+20, fr+600) o2.data.materials.append(m1) o2.data.uv_textures.new("hippo") putInGroup(o2, "lumps")This function, combined with the output of the simulator will copy the currently selected object (bpy.context.object) and create lumps that pop-in over time.
Putting all the lumps into a single group makes it easy to select (blender shift-g) and delete them when I decide to change any of my tech.
I wrote a java app that generates SVG of celtic knotwork. I then had to figure out how to work this into the pop-in textures. I had a vision that I'd keep the fade-in to bright unshaded (emit=1) white, and then fade down to the green with knotwork overlay.
To accomplish this I needed a node-based material. The biggest
roadblock was that blender's material node system didn't have an
AlphaOverlay node, only a Mix node. (The compositor has AlphaOverlay,
but evidently the need for translucent node materials wasn't high on
the busy coders' list of things to do). With help from the #blender
channel I was able to come up with a scheme to fade from the
translucent white to translucent white over green+knots.
We'll start with the material that "flashes in". It is pure white, with emit=1 and diffuse_intensity=0 (so it's unaffected by lights and bright. But we animate the alpha channel so it fades in and then out.
def findCurve(animation_data, data_path): for c in animation_data.action.fcurves.values(): if c.data_path == data_path: return c def flashIn(t0, t1, t2): rval = bpy.data.materials.new('flashIn') rval.use_transparency=1 rval.diffuse_color=(1,1,1) rval.alpha=0 rval.emit=1 rval.diffuse_intensity=0 rval.specular_intensity=0 rval.keyframe_insert(data_path='alpha', frame=t0) rval.alpha=1 rval.keyframe_insert(data_path='alpha', frame=t1) rval.alpha=0 rval.keyframe_insert(data_path='alpha', frame=t2) kp = findCurve(rval.animation_data, 'alpha').keyframe_points.values() kp[0].interpolation='LINEAR' kp[1].interpolation= 'LINEAR' return rvalWe also need a material that starts invisible, pops in solid, and then fades to grey (to make the lump old)
def fadeToGrey(c, c2, t0, t1, t2): mat = bpy.data.materials.new('fadeToGrey') mat.use_transparency=1 mat.diffuse_color=c mat.specular_intensity=0.1 mat.alpha=0 mat.emit=0.2 mat.keyframe_insert(data_path='alpha', frame =1) mat.alpha=1 mat.keyframe_insert(data_path='alpha', frame=t0) mat.keyframe_insert(data_path='diffuse_color', frame=t1) mat.diffuse_color=c2 mat.keyframe_insert(data_path='diffuse_color', frame=t2) fc = findCurve(mat.animation_data, 'alpha') kp = fc.keyframe_points.values() kp[0].interpolation = 'CONSTANT' kp[1].interpolation = 'LINEAR' return mat
def materialNodeMix(mat, solid, flash, texture): mat.use_nodes = 1 for node in mat.node_tree.nodes.values() : mat.node_tree.nodes.remove(node) l= mat.node_tree.links nm1 = mat.node_tree.nodes.new('MATERIAL') nm1.location = (0,200) nm1.material = solid nm2 = mat.node_tree.nodes.new('MATERIAL') nm2.location = (300,0) nm2.material = flash nx1 = mat.node_tree.nodes.new('MIX_RGB') nx1.location = (300,200) nm3 = mat.node_tree.nodes.new('TEXTURE') nm3.location = (100,0) nm3.texture = texture ng = mat.node_tree.nodes.new("GEOMETRY") ng.location = (-150,0) l.new(ng.outputs['UV'], nm3.inputs['Vector']) l.new(nm1.outputs['Color'], nx1.inputs['Color1']) l.new(nm3.outputs['Color'], nx1.inputs['Color2']) l.new(nm3.outputs['Value'], nx1.inputs['Fac']) nx2 = mat.node_tree.nodes.new('MATH') nx2.operation = 'MAXIMUM' nx2.location = (450,400) l.new(nm1.outputs['Alpha'], nx2.inputs[0]) l.new(nm2.outputs['Alpha'], nx2.inputs[1]) nx4 = mat.node_tree.nodes.new('MIX_RGB') nx4.location = (450,200) l.new(nm1.outputs['Alpha'], nx4.inputs['Fac']) l.new(nx1.outputs[0], nx4.inputs['Color2']) l.new(nm2.outputs['Color'], nx4.inputs['Color1']) nx3 = mat.node_tree.nodes.new('MIX_RGB') nx3.location = (600,100) l.new(nx4.outputs[0], nx3.inputs['Color1']) l.new(nm2.outputs['Color'], nx3.inputs['Color2']) l.new(nm2.outputs['Alpha'], nx3.inputs['Fac']) no = mat.node_tree.nodes.new('OUTPUT') no.location = (750,200) l.new(nx3.outputs['Color'], no.inputs['Color'] ) l.new(nx2.outputs[0], no.inputs['Alpha']) def knotworkMaterial(c, m2, t0, t1, t2, t3): mat = bpy.data.materials.new("popIn") materialNodeMix(mat, fadeToGrey( (0,0.8,0), (0.5,0.5,0.5), t1, t2, t3), m2, bpy.data.textures['knot 2'] ) #mat.use_face_texture = 1 #mat.use_face_texture_alpha=1 mat.use_transparency = 1 mat.specular_intensity = 0.1 return mat mfi = flashIn(fr, fr+7, fr+17) m1 = knotworkMaterial( (0,0.8,0), mfi, fr, fr+7, fr+20, fr+600)
import bpy import bmesh def createMeshFromData(name, origin, verts, faces): # Create mesh and object me = bpy.data.meshes.new(name+'Mesh') ob = bpy.data.objects.new(name, me) ob.location = origin ob.show_name = True # Link object to scene and make active scn = bpy.context.scene scn.objects.link(ob) scn.objects.active = ob ob.select = True # Create mesh from given verts, faces. me.from_pydata(verts, [], faces) # Update mesh with new data me.update() return ob verts = ( (1,0, 2), (0,1, 2), (-1,0, 2), (0,-1, 2), # top (1,0,-2), (0,1,-2), (-1,0,-2), (0,-1,-2), # bottom ( 2,1,0), ( 2,0,1), ( 2,-1,0), ( 2,0,-1), #x+ (-2,1,0), (-2,0,1), (-2,-1,0), (-2,0,-1), #x- (1, 2,0), (0, 2,1), (-1, 2,0), (0, 2,-1), #y+ (1,-2,0), (0,-2,1), (-1,-2,0), (0,-2,-1), #y- ) faces=[ (0,1,2,3), (4,7,6,5), (8,9,10,11), (12,15,14,13), (16,19,18,17), (20,21,22,23), ] f2 = ((16,17,1,0,9,8), (8,11,4,5,19, 16), (11,10, 20,23,7,4), (0,3,21,20,10,9), (3,2, 13,14, 22,21), (23,22, 14,15, 6,7), (2,1, 17,18, 12,13), (18,19, 5,6, 15, 12) ) for f in f2: i0 = len(verts) v0 =verts[f[0]] v3 = verts[f[3]] v7 = ((v0[0]+v3[0])/2 , (v0[1]+v3[1])/2 , (v0[2]+v3[2])/2 ) verts = verts + (v7,) newF = [ (i0, f[j], f[(j+1)%6]) for j in range(len(f)) ] faces .extend(newF) to = createMeshFromData('truncOct', (0,0,0), verts, faces) to.data.materials.append(bpy.data.materials['stork']) def truncOctUV(to): to.data.uv_textures.new("hippo") bm = bmesh.new() bm.from_mesh(to.data) uv_layer = bm.loops.layers.uv[0] for fi in range(6, len(bm.faces)): print(bm.faces[fi]) bm.faces[fi].loops[0][uv_layer].uv = (0.5,0.5) bm.to_mesh(to.data) truncOctUV(to)
inappropriate![]() |
better![]() |
The default UV map constructed by blender has one of the coordinates wrong for our particular application. That's why we use a bmesh to edit them in the truncOctUV() function. We programmatically move the UV coordinate of the center vertex (loops[0]) from its default position to 0.5,0.5, the center of the knotwork texture.
The bits involving the bmesh caused me a lot of headache. A lot of the examples on the internet are for older versions of blender. Also, I kept forgetting to "import bmesh" and blender doesn't have a good UI for showing you the python errors.
def knotworkMaterial2(c, m2, t0, t1, t2, t3): mat = bpy.data.materials.new("popIn") materialNodeMix(mat, fadeToGrey( c, (0.5,0.5,0.5), t1, t2, t3), m2, bpy.data.textures['knot 3'] ) #mat.use_face_texture = 1 #mat.use_face_texture_alpha=1 mat.use_transparency = 1 mat.specular_intensity = 0.1 return mat
I still haven't figured out how I want to pace the pop-in of lumps, so I have a function I can fiddle with
def frameForNode(nodeNum): nodeCount=1000 x2 = 1-(nodeNum/nodeCount) return 3600*(1-x2*x2)And it's kind of handy to have nodes that aren't rendered to also disappear from the 3D view. (it can be a pain in the butt at other times).
def animateVisibility(o2, fr): o2.hide_render=1 o2.hide = 1 o2.keyframe_insert(data_path="hide_render", frame=1) o2.keyframe_insert(data_path="hide", frame=1) o2.hide_render = 0 o2.hide = 0 o2.keyframe_insert(data_path="hide_render", frame=fr) o2.keyframe_insert(data_path="hide", frame=fr)And finally, a new version of addLump that glues it all together
def addLump(x,y,z, t): o2 = duplicateObject(bpy.context.scene, "lump", bpy.context.object) o2.location = (x,y,z) fr = frameForNode(t) animateVisibility(o2, fr) mfi = flashIn(fr, fr+7, fr+17) m1 = knotworkMaterial( (0,0.8,0), mfi, fr, fr+7, fr+20, fr+600) m3 = knotworkMaterial2((0,0,1), mfi, fr, fr+7, fr+20, fr+600) o2.data.materials.append(m1) o2.data.materials.append(m3) for fi in range(6, len(o2.data.polygons)): o2.data.polygons[fi].material_index=1 o2.data.uv_textures.new("hippo") putInGroup(o2, "lumps")The bit of magic at the end with 'material_index' is how we get different materials on different faces. The faces default to the green and gold knotwork for the squares (m1), but that's only appropriate for faces 0..5 (the squares). For the triangles of the hexagonal faces (6 and up) we want them to use the blue and gold knotwork, so we adjust the material_index to refer to m3.