[Solved] Selection of Face of a STL by Face Normal value Threshold


I’m sure there’s a python library to load stl files, but I’ve always just written my own, since the file format is pretty simple (see the Wikipedia article for file format description).

Here is my code to read the stl file:

import numpy as np
import struct

def Unique(inputList):
      """ 
      Given an M x N list, this function gets the unique rows by treating all
      M Ntuples as single objects. This function also returns the indexing
      to convert the unique returned list back to the original non-unique list.
      """

      hashTable=dict()

      indexList=[]
      uniqueList=[]

      indx=0
      for ntuple in inputList:
            if not ntuple in hashTable:
                hashTable[ntuple]=indx
                indexList.append(indx)
                uniqueList.append(ntuple)
                indx+=1
            else:
                indexList.append(hashTable.get(ntuple))      

      return uniqueList, indexList


def IsBinarySTL(filename):
    try:
        with open(filename,'r') as f:
              test=f.readline()
    except UnicodeDecodeError:
        return True

    if len(test) < 5:
        return True
    elif test[0:5].lower() == 'solid':
        return False  # ASCII STL
    else:
        return True

def ReadSTL(filename):
    """ Returns numpy arrays for vertices and facet indexing """
    def GetListFromASCII(filename):
        """ Returns vertex listing from ASCII STL file """
        outputList=[]

        with open(filename,'r') as f:
            lines=[line.split() for line in f.readlines()]
        for line in lines:
            if line[0] == 'vertex':
                    outputList.append(tuple([float(x) for x in line[1:]]))
        return outputList

    def GetListFromBinary(filename):
        """ Returns vertex listing from binary STL file """
        outputList=[]
        with open(filename,'rb') as f:
            f.seek(80) # skip header
            nFacets=struct.unpack('I',f.read(4))[0] # number of facets in piece

            for i in range(nFacets):
                  f.seek(12,1) # skip normal
                  outputList.append(struct.unpack('fff',f.read(12))) # append each vertex triple to list (each facet has 3 vertices)
                  outputList.append(struct.unpack('fff',f.read(12))) 
                  outputList.append(struct.unpack('fff',f.read(12)))
                  f.seek(2,1) # skip attribute
        return outputList

    if IsBinarySTL(filename):
        vertexList = GetListFromBinary(filename)
    else:
        vertexList = GetListFromASCII(filename)

    coords, tempindxs = Unique(vertexList)

    indxs = list()
    templist = list()
    for i in range(len(tempindxs)):
        if (i > 0 ) and not (i % 3):
            indxs.append(templist)
            templist = list()
        templist.append(tempindxs[i])
    indxs.append(templist)

    return np.array(coords), np.array(indxs)

And here is code to compute the facet normals (assuming right-hand-rule)

def GetNormals(vertices, facets):
    """ Returns normals for each facet of mesh """
    u = vertices[facets[:,1],:] - vertices[facets[:,0],:]
    v = vertices[facets[:,2],:] - vertices[facets[:,0],:]
    normals = np.cross(u,v)
    norms = np.sqrt(np.sum(normals*normals, axis=1))
    return normals/norms[:, np.newaxis]

Finally, code to write out the stl file (assuming a list of attributes for each facet):

def WriteSTL(filename, vertices, facets, attributes, header):
    """
    Writes vertices and facets to an stl file. Notes:
    1.) header can not be longer than 80 characters
    2.) length of attributes must be equal to length of facets
    3.) attributes must be integers
    """
    nspaces = 80 - len(header)
    header += nspaces*'\0'

    nFacets = np.shape(facets)[0]
    stl = vertices[facets,:].tolist()

    with open(filename,'wb') as f: # binary
        f.write(struct.pack('80s', header.encode('utf-8'))) # header
        f.write(struct.pack('I',nFacets)) # number of facets
        for i in range(nFacets):
            f.write(struct.pack('fff',0,0,0)) # normals set to 0
            for j in range(3):
                f.write(struct.pack('fff',stl[i][j][0], stl[i][j][1], stl[i][j][2])) # 3 vertices per facet 
            f.write(struct.pack("H", attributes[i])) # 2-byte attribute

Putting this all together, you can do something like the following:

if __name__ == "__main__":
    filename = "bunny.stl"

    vertices, facets = ReadSTL(filename)  # parse stl file
    normals = GetNormals(vertices, facets)  # compute normals

    # Get some value related to normals
    attributes = []
    for i in range(np.shape(normals)[0]):
        attributes.append(int(255*np.sum(normals[i])**2))

    # Write new stl file
    WriteSTL("output.stl", vertices, facets, attributes, "stlheader")

this code snippet reads an stl file, computes the normals, and then assigns an attribute value based on the squared-sum of each normal (note that the attribute must be an integer).

The input and output of this script look like the following:
enter image description here

0

solved Selection of Face of a STL by Face Normal value Threshold