Knife II

After losing some work due to HDD problems I ran into a lot of issues with the previously posted Knife SOP
So I tried again using the normal Knife and got the same issues as it does not work iteratively and then I bugfixed (or rewrote largely) the previous knife I made, hopefully more functional this time. It retains face order, it does now calculate point attributes for new points and it transfters custom prim attributes (the split prims duplicate the attribute vaues). Consider this a snippet dump…

node = hou.pwd()
geo = node.geometry()


#parse parameters
target = node.evalParm("target")
origin = hou.Vector3( node.evalParm("originx"), node.evalParm("originy"), node.evalParm("originz") )
distance = node.evalParm("dist")
direction = hou.Vector3( node.evalParm("dirx"), node.evalParm("diry"), node.evalParm("dirz") ).normalized()
#distance really just moves the origin
origin += direction*distance


def rayPlaneIntersect(rayorigin, in_raydirection, planeorigin, in_planenormal):  
    ''''' 
    @returns: Vector3, intersectionPoint-rayOrigin 
    '''  
    raydirection = in_raydirection.normalized()  
    planenormal = in_planenormal.normalized()  
    distanceToPlane = (rayorigin-planeorigin).dot(planenormal)  
    triangleHeight = raydirection.dot(-planenormal)  
    if not distanceToPlane: #ray origin lies in the plane
        return rayorigin-planeorigin  
    if not triangleHeight: #ray is parallel to plane
        return None
    return raydirection * distanceToPlane * (1.0/triangleHeight)


def getPolygonsWithEdge(geom, edgeids):
    '''''
    @param geo: hou.Geometry, geometry to search
    @param edgeids: tuple of 2 ints, point numbers
    describing the edge to find shared faces for
    
    @returns: list of hou.Prim, all primitives sharing this edge
    
    if the points are not connected by an edge
    (adjacent in the vertex list of any primitive)
    the result is an empty list
    '''
    out = []
    for poly in geom.prims():  
        verts = poly.vertices()
        for i in range(poly.numVertices()):
            if verts[i].point().number() in edgeids and\
               verts[(i+1)%poly.numVertices()].point().number() in edgeids:
                out.append(poly)
    return out


#stub primitive, I use this for the cut faces so I can actually add the polygons in the end to not disturb primitive order / numbers
class notPrim():
    def __init__(self):
        self.points = []

    def addVertex( self, inPoint ):
        self.points.append( inPoint )

    def addToGeo( self, inGeo ):
        poly = inGeo.createPolygon()
        for point in self.points:
            poly.addVertex(point)
        return poly


### Cut target ###  
verts = geo.iterPrims()[target].vertices()  
nverts = len(verts)  
cutEdges = []
adjFaces = []
#foreach edge
for i in range(nverts):  
    pt0 = verts[i].point()
    pt1 = verts[(i+1)%nverts].point()
    edgedirection = pt1.position()-pt0.position()
    #find interseciton on edge
    intersectpt = rayPlaneIntersect(pt0.position(), edgedirection, origin, direction)  
    if not intersectpt: #edge is parallel to cutting plane  
        continue
    #check if intersection is on the edge (line-segment)
    param = intersectpt.dot(edgedirection.normalized())
    if param > 0 and param < edgedirection.length():  
        #store the cut
        pt = geo.createPoint()

        #propagate point attribs
        for attrib in geo.pointAttribs():
            val0 = pt0.attribValue(attrib)
            val1 = pt1.attribValue(attrib)
            if type(val0) in (int, float):
                pt.setAttribValue( attrib, val0*(1-param)+val1*param )
            if type(val0) == tuple:
                val = []
                for i in range(len(val0)):
                    if type(val0[i]) in (int, float):
                        val.append( val0[i]*(1-param)+val1[i]*param )
                pt.setAttribValue( attrib, val )

        pt.setPosition( intersectpt+pt0.position() )  
        cutEdges.append( [pt0, pt1, pt] )

        #store the face(s) influenced by this cut
        adjFaces.extend( getPolygonsWithEdge( geo, (pt0.number(), pt1.number()) ) )


### Rebuild geometry ###
delete = []
polys = []
#rebuild all prims
for i in range(len(geo.iterPrims())):
    prim = geo.iterPrims()[i]
    delete.append(prim) #remove all old prims

    poly = geo.createPolygon() #create new prim

    #duplicate prim attribs
    for attrib in geo.primAttribs():
        val = prim.attribValue(attrib)
        poly.setAttribValue( attrib, val )

    #build cut faces
    if i == target:
        ### Create cut face ###
        #iterate over edges to build new polygons  
        cuts = 0
        wrap = False
        polys.append( poly ) #the first split primitive keeps the original primitive nr
        for j in range(prim.numVertices()):
            cut = None
            vtx = prim.vertices()[j]
            #all cuts added, finish the first polygon
            if wrap:
                polys[0].addVertex(vtx.point())  
                continue

            #find edge points
            nxtvtx = prim.vertices()[(j+1)%prim.numVertices()]
            polys[-1].addVertex(vtx.point())

            for edge in cutEdges:
                if vtx.point() in edge and nxtvtx.point() in edge:
                    cut=edge

            #if edge is a cut edge
            if cut:
                polys[-1].addVertex(cut[2])  
                cuts += 1
                if cuts == len(cutEdges): #wrap to first polygon at last cut  
                    wrap = True  
                    polys[0].addVertex(cut[2])
                    continue
                polys.append(notPrim()) #add stub primitive and start building vertex list for it
                polys[-1].addVertex(cut[2])
    else: #or just build the primitive again
        for j in range(prim.numVertices()):
            vtx = prim.vertices()[j]
            poly.addVertex(vtx.point())
            if prim in adjFaces: #if influenced by cut, then cut the right edge
                for edge in cutEdges:
                    #when at start of the split edge, known because next vertex is it's end point
                    if vtx.point() in edge:
                        nxtvtx = prim.vertices()[(j+1)%prim.numVertices()]
                        if nxtvtx.point() in edge: 
                            #add the intersection point
                            poly.addVertex(edge[2])
                            break


### append stub split faces at the very end###
for i in range(1,len(polys),1):
    poly = polys[i].addToGeo( geo )
    src = geo.iterPrims()[target]
    #duplicate prim attribs
    for attrib in geo.primAttribs():
        val = src.attribValue(attrib)
        poly.setAttribValue( attrib, val )

#remove all old prims
geo.deletePrims(delete, True)

One thought on “Knife II

Leave a Reply

Your email address will not be published. Required fields are marked *