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: