As an aside to developing my nuke photogrammetry pipeline (to be documented shortly) I was annoyed at the lack of .ply file support in nuke. Since a lot of photogrammetry and LIDAR tools rely export point clouds in the .ply format, it seemed useful to allow that data to be brought into nuke somehow.
The obvious way of doing this would be to subclass the geoReader class that comes with nuke and write an actual node that could parse and display ply files…but getting involved with compiled plugins means sacrificing portability and as a freelancer, I’d rather take a pythonic solution that I can use anywhere.
I remembered that in nuke 7, dense point clouds can be baked into “BakedPointCloud” groups which are just a collection of points in space. Intrigued, I copy/pasted the node into a text editor, and low and behold, it contained the same information as my .ply file! Point locations, normal directions, and a color! If I could get this to work, it’d be huge for 2 reasons…First, LIDAR scans and any .ply data could be easily imported and manipulated in nuke, as stated. But more excitingly, since the input to a Poisson Mesh node can be a BakedPointCloud group isolated from a dense point cloud, perhaps the points could be meshed interactively in nuke. Turns out they can.
This looks like a mesh, but is actually a point cloud viewed from a ways off.
Meshed point cloud! Thanks PoissonMesh node! In truth, meshlab has more control over meshing and I’m not sure there’s any reason to do it in nuke itself (perhaps I need to write a BakedPointCloud –> .ply file exporter to have a better chance of making nice meshes from camera solves, but I digress) but it’s still cool in principle!
Simply copy/paste this code to your script editor and execute it with Command + Enter(Mac) or Ctrl + Enter(Linux). There’s a GUI! Select an ASCII .ply file with verts, normals, and color, and the maximum number of points you want created, and hit okay! I’ll try to post this on nukepedia when I get the chance (and they start accepting uploads again)!
UPDATE! Better code!
# TODO: # detect .ply files written with a different order of pos / normals / colors # more efficiently access the file in question! # probably won't work on windows due to /tmp import math import time import nuke import random import math # GLOBALS singleLineInput = 300000 filenameSearch = None output = "/tmp/plyConversion_%s.nk" % str(int(time.mktime(time.gmtime()))) points = "" # long string representing all the point positions normals = "" # long string representing all the point normals colors = "" # long string representing all the point colors numVerts = "" total = 0 data = False # whether we're in the "data" section of the ply file vertEntries = [] # lines of the file pertaining to verts # randomly select n items from a list # inspired by a discussion here: http://www/gregable.com/2007/10/reservoir-sampling.html def randomSelection(numToPick,items): shuffledItems = items random.shuffle(shuffledItems) return shuffledItems[0:numToPick] # GUI! p = nuke.Panel(".ply importer") p.addFilenameSearch(".ply file", filenameSearch) p.addSingleLineInput("Maximum points to create", singleLineInput) p.addButton("Cancel") p.addButton("OK") result = p.show() # Parse choices path = p.value(".ply file") MAX_POINTS = int(p.value("Maximum points to create")) # Processing! r = open(path,"r") lines = r.readlines() r.close # parse .ply file line by line! # this is hideously inefficient...we should bomb out when we hit the last vert line... for i,line in enumerate(lines): # if we've hit the data section, add only the lines that contain vertex info if data and total < numVerts: vertEntries.append(line) total += 1 # calculate the number of vertices we're going to be loading! if "element vertex" in line: numVerts = int(line.split()[2]) print "%s points detected! " % numVerts # if the header is over, toggle the data boolean to true: now we're in the payload of the file if "end_header" in line: data = True # process every point, or a fixed, random selection if MAX_POINTS > numVerts: lines = vertEntries else: # Thanks Mike Lang for suggesting an optimization could be used here! print "selecting only %s points at random! " % MAX_POINTS lines = randomSelection(MAX_POINTS,vertEntries) # now that we have our selection, build up our template strings! for line in lines: datasplit = line.split() points += "%s " % " ".join(datasplit[0:3]) normals += "%s " % " ".join(datasplit[3:6]) colors += "%s " % " ".join([str(float(v)/255.0) for v in datasplit[6:9]]) # ignore alpha value # Nuke node specifics: name = path.split("/")[-1] label = "%s points" % (len(lines)) template = """ set cut_paste_input [stack 0] version 7.0 v4 BakedPointCloud { inputs 0 pointSize 2 serializeKnob "" serializePoints "%s %s " serializeNormals "%s %s " serializeColors "%s %s " name %s label "%s" xpos 510 ypos 140 }""" % (total, points, total, normals, total, colors,name,label) # Write out .nk file to output location (somewhere in /tmp) w = open(output,"w") w.write(template) w.close() nuke.nodePaste(output)