Closed Mesh Area and Volume

I am trying to slice a closed mesh and get the new surface area and volume.

HEMesh has a mesh.getArea() in the Ref_HEM_Soapfilm example, but the function does not exist in the library and I can’t find documentation for the HEMesh. Am I missing something?

Is there another library I should try for mesh splitting and analysis? I only see a way to split mesh by planes. Is there a way to split a mesh with another mesh?

Thanks for the help in advance. - J

1 Like

Can you say more about what you mean? Like, given a sphere mesh and cube mesh, split the sphere into between one and seven parts, depending on how it intersects with the cube…?

After doing more research, I found CSG.
I found a java library (JCSG) that I tried to use for mesh boolean operations, but I didn’t have any luck.

This is correct. I want to take two solids and either remove one from the other (difference), add one to the other (union), or get the overlap of the two (intersect). These are the typical boolean operations that I have in most 3d modeling environments.

1 Like

Hi @jNew jNew,

I’m afraid JCSG doesn’t work well with Processing 3.

I gave it a try a few months ago and came to the conclusion that the library is not compatible. If I remember correctly, you can do some boolean operations with the bundled primitives (Cube().toCSG, Sphere().toCSG, …) and convert them to PShapes (as kindly described by GeorgeProfenza here) but cannot do the opposite: converting a custom PShape to CSG format for transformation.

# Convert a CSG primitive to PShape (based on George Profenza example code)
def CSGToPShape(mesh, scl):
    
    pshape = createShape(GROUP)
    for p in mesh.getPolygons():
        face = createShape()
        face.beginShape()
        for v in p.vertices:
            face.vertex(v.pos.getX() * scl, v.pos.getY() * scl, v.pos.getZ() * scl)
        face.endShape()
        pshape.addChild(face)
    
    return pshape


# !! Doesn't work: tries to convert a PShape to CSG format for boolean operations
def PShapeToCSG(shp):
    
    polys = []
    for ic in xrange(shp.getChildCount()):
        c = shp.getChild(ic)
        verts = []
        for iv in xrange(c.getVertexCount()):
            v = c.getVertex(iv)
            verts.append(Vector3d(v.x, v.y, v.z))
        polys.append(Polygon.fromPoints(verts))
        
    return CSG.fromPolygons(polys)

Regarding Hemesh, Frederik Vanhoutte (the author) told me once that his library doesn’t support boolean operations.

1 Like

Thank you solub, this is what I experienced as well. I also found that JCSG uses a newer version of Java that Processing3 does not support, so I couldn’t use newer releases.

Perhaps the pre-Processing 4 private release, which is Java 11 compatible, would work for those recent JCSG releases…

2 Likes

@jNew a couple of questions:

  • Are you working in 2D or in 3D ?
  • Is there any particular reason why you want to do the boolean operation in Processing ? (instead of using a third-party software)

It’s probably unlikely but in case you work in 2D, the Geomerative library has a RPolygon class that allows boolean operations like union() or diff().

If you have 3D meshes and your workflow requires to stick to Processing, there is also the possibility to communicate with a third-party software directly from your sketch. From what I understand you are into Computational Design so you might be familiar with Maya or Grasshopper. If so, you could:

  • send you meshes to the software of your choice via UDP or TCP
  • do the boolean operation
  • save the output as an ‘.obj’ file and import it back to your sketch

Here below an example sketch using the OscP5 library to communicate with Grasshopper through the gHowl component.

Please note that:

  • the meshes should intersect the way you want before sending
  • only low-poly meshes (<1000 vertices) can be sent via UDP. If your meshes are denser, I would suggest to use FTP instead but unfortunately I can’t help you with this.
add_library('peasycam')
add_library('oscP5')


def setup():
    perspective(60 * DEG_TO_RAD, width/float(height), 2, 6000)
    size(1400, 800, P3D)
    smooth(8)
    
    global osc, shp3
    
    #Instantiate the PeasyCam/OscP5 libraries
    cam = PeasyCam(this, 80)
    osc = OscP5(this, 12000) 

    #the two shapes to be used for Boolean Operation
    shp1 = #YourFirstMesh 
    shp2 = #YourSecondMesh
    
    #computes the 'difference' mesh between shp1 and shp2
    mesh = transform([shp1, shp2])
    
    #convert the resulting mesh to a PShape object
    shp3 = meshToPShape(mesh)
    
    
    
def draw():
    background('#FFFFFF')
    
    shape(shp3)
    
    

    
def transform(shpList):
    
    '''Sends the meshes vertices to Grasshopper via UDP using the OscP5 library & gHowl component.
       The Grasshopper definition will then reconstruct the 2 meshes and perform the Boolean Operation of your choice (here "Mesh Difference").
       The resulting mesh is then converted to an .obj file through a C# script and exported to the location of your choice.
       Finally, Processing will import that mesh from the chosen location and return it for PShape conversion. '''
    
    net1 = NetAddress("127.0.0.1", 12001) #remote location for the 1st mesh
    net2 = NetAddress("127.0.0.2", 12002) #remote location for the 2nd mesh
    
    for i, net in enumerate([net1, net2]):
        message = OscMessage("/gHowlTest") 
        
        for child in shpList[i].getChildren():
            for iv in xrange(child.getVertexCount()):
                vert = child.getVertex(iv)
    
                message.add(str(vert.x))
                message.add(str(vert.y))
                message.add(str(vert.z))
                    
        osc.send(message, net)

    #Pausing the sketch for a moment because the '.obj' conversion in Grasshopper may take some time
    delay(1000) 
    
    return loadShape("C:/Select/Your/Path/TheFileName.obj")



def meshToPShape(m):
    
    '''Traverse the selected mesh and construct a PShape object from its faces/vertices'''
    
    shp = createShape(GROUP)
    for c in m.getChildren():
        face = createShape()
        face.beginShape(TRIANGLE)
        face.fill(int(random(30, 50)), int(random(180, 205)), int(random(170, 190)))
        for iv in xrange(c.getVertexCount()):
            v = c.getVertex(iv)
            face.vertex(v.x, v.y, v.z)
        face.endShape()
        shp.addChild(face)
    
    return shp

GH file

3 Likes

Edit: Giving it a second thought, it would make more sense to send the location of your meshes (as OBJ) instead of transmitting their vertices one by one. This way we can overcome the UDP transmission limitation and have a cleaner workflow.

The example sketch below will:

  • convert the 2 base shapes to meshes (both triangular and quadrangular accepted)
  • save them as OBJ in \data directory with the HE_Mesh exporter
  • send the Boolean instruction to Grasshopper via UDP (difference or union or intersection)
  • load the Grasshopper output as a PShape

__

Processing Sketch
import os

add_library('peasycam')
add_library('hemesh')
add_library('oscP5')


def setup():
    perspective(60 * DEG_TO_RAD, width/float(height), 2, 6000)
    size(1600, 1000, P3D)
    smooth(8)
    
    global shp3
        
    cam = PeasyCam(this, 30)
    osc = OscP5(this, 6001)

    shp1 = #Mesh1  
    shp2 = #Mesh2 
    
    #Location of the future OBJ files
    directory = sketchPath("data")
    
    #Convert meshes to OBJ and place them in directory
    toObj(shp1, "objName1", directory)
    toObj(shp2, "objName2", directory)
    
    #Select Boolean Operation of your choice + send instruction via UDP
    operation = "difference" # -----> 'difference' or 'union' or 'intersection'
    filename = "meshDifference"
    osc.flush(directory + '\ ' + operation + ' ' + filename, "127.0.0.1", 6002)
    
    #Wait for Rhino/Grasshopper to compute the transformation
    delay(8000)
    
    #Load output + convert to PShape (not mandatory)
    shp = loadShape(sketchPath(filename + '.obj'))
    shp3 = toPShape(shp)
        

    
    
def draw():
    background('#FFFFFF')
    shape(shp3)

    


    
def toObj(shp, filename, directory):
    
    '''Traverse the selected mesh and construct a HE_Mesh from its faces.
       The output will be saved as an OBJ in the chosen directory. '''


    #Create 'data' directory if not existent
    if not os.path.isdir(directory):
        os.mkdir(directory)
        
    #Detect face type (triangular vs quandragular faces)
    type = 'triangular' if shp.getChild(0).getVertexCount() == 3 else 'quadrangular'
    
    #Reconstruct mesh from faces
    temp = []
    for child in shp.getChildren():
        face = []
        for i in xrange(child.getVertexCount()):
            v = child.getVertex(i)
            face.append(WB_Point(v.x, v.y, v.z))
            
        if type == 'triangular':
            a, b, c = face
            tri = WB_Triangle(a, b, c)
            temp.append(tri)
        else:
            a, b, c, d = face
            qua = WB_Quad(a, b, c, d)
            temp.append(qua)
            
    #Convert to HE_Mesh mesh format      
    if type == 'triangular':
        mesh = HE_Mesh(HEC_FromTriangles().setTriangles(temp))
    else:
        mesh = HE_Mesh(HEC_FromQuads().setQuads(temp))
        
    #Export to OBJ in 'data' directory
    HET_Export.saveToOBJ(mesh, directory, filename)
    
    
    
def toPShape(s):
    
    '''Traverse the selected mesh and construct a PShape object from its faces/vertices'''
    
    shp = createShape(GROUP)
    for c in s.getChildren():
        face = createShape()
        face.beginShape(TRIANGLE)
        face.fill(int(random(30, 50)), int(random(180, 205)), int(random(170, 190)))
        for iv in xrange(c.getVertexCount()):
            v = c.getVertex(iv)
            face.vertex(v.x, v.y, v.z)
        face.endShape()
        shp.addChild(face)
    
    return shp

¯¯

On the Grasshopper side, as soon as the Boolean Operation instruction is received, the GhPython component will automatically:

  • find the saved OBJs
  • convert them to Rhino meshes
  • compute the desired transformation (difference or union or intersection)
  • save the output as an OBJ (with triangular faces)

__

GhPython script
import os
import Rhino.Geometry as rg

directory, operation, filename = iMessage.split()
meshes = [rg.Mesh(), rg.Mesh()]


def toMesh(path, i):
    
    with open(path) as f:
        for l in f:
            if l.startswith('v '):
                x, y, z = map(float, l.split()[1:])
                meshes[i].Vertices.Add(rg.Point3d(x, y, z))
                
            elif l.startswith('f '):
                face = [int(e.split('/')[0])-1 for e in l.split()[1:]]
                if len(face) == 3:
                    meshes[i].Faces.AddFace(rg.MeshFace(face[0], face[1], face[2]))
                else:
                    meshes[i].Faces.AddFace(rg.MeshFace(face[0], face[1], face[2], face[3]))
                    
    meshes[i].Normals.ComputeNormals()
    meshes[i].Compact()
    rg.Collections.MeshFaceList.ConvertQuadsToTriangles(meshes[i].Faces)

def toObj(mesh):
    
    newDirectory = directory.split('data')[0]
    print newDirectory
    
    if not os.path.isdir(newDirectory):
        os.mkdir(newDirectory)
    
    filepath = newDirectory + "%s" % filename + ".obj"
    
    with open(filepath, 'w') as f:
        f.write("# OBJ file\n")
        for vert in mesh.Vertices:
            f.write('v ' + "%s %s %s\n" % tuple([vert.X, vert.Y, vert.Z]))
            
        for face in mesh.Faces:
            if face.IsTriangle:
                ids = map(lambda x: str(x+1) + '//' + str(x+1), [face.A, face.B, face.C])
                f.write('f ' + ("%s %s %s\n") % tuple(ids))
            elif face.IsQuad:
                ids = map(lambda x: str(x+1) + '//' + str(x+1), [face.A, face.B, face.C, face.D])
                f.write('f ' + ("%s %s %s %s\n") % tuple(ids))

def run():
    global oMesh
    
    objs = [file for file in os.listdir(directory) if file.endswith('.obj')]
    for i, obj in enumerate(objs):
        path = directory + obj
        toMesh(path, i)
        
    if operation == 'difference':
        oMesh = rg.Mesh.CreateBooleanDifference([meshes[0]], [meshes[1]])
    elif operation == 'intersection':
        oMesh = rg.Mesh.CreateBooleanIntersection([meshes[0]], [meshes[1]])
    elif operation == 'union':
        oMesh = rg.Mesh.CreateBooleanUnion(meshes)
    
    toObj(oMesh[0])

run()

¯¯

Definition + Gh file


Gh file

Below an example with 2 meshes (woman face + roman bust) converted to OBJ in Processing and retrieved by Grasshopper after UDP transmission of their locations. The instruction sent for boolean operation is: difference

The output loaded back in Processing:

1 Like

I appreciate all the input, but I am trying to make a stand alone app. If I was making the app for myself, I would just develop it as a plugin for Rhino.

It looks like I will not be using processing for the development anymore… back to C++.

I am considering C++ because it has VTK. Upon further investigation, VTK actually has a java wrapper. I might try using this with processing. VTK has some really nice 3D tools.

I see. Don’t know if that’s relevant in your case but it seems the CGAL library also has a SWIG interface for Java.

1 Like