Iterating over mesh faces, to check for various conditions

I am trying to write a Python script that iterates over all the faces of a mesh, checks various conditions, and selects only a few faces. I was successful in determining the orientation of the surface, which excluded most of the surfaces. Now, I would like to further narrow down my selection of faces, but I’m stuck. Most rhinoscriptsyntax functions require a GUID, which I apparently lose once I coerce the geometry. As far as I know, I can’t access the GUID with a function like face.guid() or similar. Is there a better way of doing this? Could I take note of the GUID at the start?
(It seems, that the guid from the start is not iterable.)
Thanks in advance for any assistance!


import Rhino.Geometry as rg
import rhinoscriptsyntax as rs
import math
import Rhino

angle_threshold_degrees = 40
mesh = rs.coercemesh(mesh)

#mesh = rs.ExplodeMeshes(mesh)
faces = rs.MeshFaces(mesh)
normals = rs.MeshFaceNormals(mesh)

filtered_faces = []

for i in range(0, len(normals)):
    normal = normals [i]
    angle = rg.Vector3d.VectorAngle(-normal, rg.Vector3d.ZAxis)
    angle_degrees = math.degrees(angle)

    if -angle_threshold_degrees < angle_degrees < angle_threshold_degrees:
        face = faces[i]
        filtered_faces.append(face)
    else:
        continue
        
    direction = -rg.Vector3d.ZAxis
    area = rs.MeshArea(face)

	#detect if a vector starting at the face center (to direction -z) would collide with the mesh
	#I would use the face centre as available above. However, how do I figure out the intersection with the mesh & in code?
	
	#calculate the area of the face, exclude if smaller than treshold
	#I was thinking of using the vertices and calculating it myself
	
	#check if the remaining faces are neighbours
	#could omit this if to difficult
	
	#if close enough merge them
	#I was thinking of using cull duplicates here

For almost any Mesh related query (Faces, Edges, Vertices and explicit/implicit Properties - and/or Connectivity - related with these 3 Classes) there’s RC Methods available [you don’t need a GUID].

Elaborate more: what kind of query you want?

PS: The best way to do that (provited that you can deal with P/LINQ Syntax) is to define a Class with suitable Properties, create a List and then perform any imaginable simple or nested (i.e. GroupBy) Query:

Say … find Faces that have a Normal (VS some Vector) angle within some Interval while their edges have this min/max Length/Angle Ratio while their edges are less/greater than a given value … blah, blah.

PS: Unless you really want a collection of Type MeshFace … avoid sampling Faces (i.e. avoid reinventing the wheel later on).

Well, I would like to filter out the most promising surfaces, to pass on to another gh-python-component, which will attach other geometry to those surfaces. To save on some computation, I started with the one condition, (normal) which removes most of the surfaces.

I do not actually need the face later on. The centroid and normal will suffice.

How / Where is P/LINQ implemented? I’ve never heared of it. When googling it says something about .NET-Framework, in which I sadly do not have much experience.

Assume that you define a Class with some Properties for some Scope.

Say a Brep related Custom Type where Idx is the index, A is the Area, NF is the N of Faces, a bool Array IsPlanar tells you if a given Face is planar etc etc. For each Brep in some Collection (List/Tree) you define a Custom Object that “carries” over Idx,A, NF, IsPlanar … (add any other Property imaginable). Then you add that Object in a List (say myList).

Then a LINQ Query (out of a million possible) could be:

var Q = myList.OrderBy(x=>x.A),Where(x=>x.NF > 5).ToList();

blah, blah.

Anyway if that type of stuff means nothing to you (is paramout for any type of data mining) forget it.

So: describe IN DEPTH what exactly you want (as a demo, that is) and provide (in Rhino 5 Format) a test Mesh.

Alright - is forgotten :grinning:

The class does makes sense. However, how would I access the Id of such a face?
before coercing? As soon I have it, I can add it to classes to my hearts content :slight_smile:

Classes are the A to Z for any kind of coding worth the name.

A Class can (in his Properties) define/contain ANY imaginable Type (that makes sense) in any imaginable Collection (ditto): a long, an object, a Tree of cats, an Array of dogs, an IndexPair of something, a List of Aliens, a double for the speed of light … etc etc.

Say that we name a Class FINFO (i.e. Face info). The Properties could be the Face Idx, the Face Connectivity (i.e. 3 Arrays for FV, FE, FF), the Face Normal, the Face Area, the Face Angles, the Adjacent Vertices Normals … plus a cat and/or a dog. Then … well … you can imagine what happens next.

Agreed. especially since it’s so easy to implement in python.

But I am confused about getting the Id, which I later could add to the class.
If there’s a way to access more details about the faces, such as it’s vertices, without knowing its guid, I’m all ears.

Er … hmm … Faces (as Edges and Vertices) are contained in a Collection:

var MF = mesh.Faces; (or var MTV = mesh.TopologyVertices;)

Then (C# - I hate P)

for (int i = 0; i < MF.Count; i++){ // i is the index of the Face
MeshFace face = MF[i];

}

Thanks for the input. In the above code, I already use:

faces = rs.MeshFaces(mesh)

which in my mind does the same as your var MF = mesh.Faces();

In later functions such as when I try to access the vertices of the face:

    verts = rs.MeshFaceVertices(face)

it fails because of:

Runtime error (TypeErrorException): Parameter must be a Guid or string representing a Guid

But I cannot figure a way to access said Guid :confused:

Your query related with Face Normals is that simple (spot the LINQ):

I could obviously add more “filters” in that single LINQ line (more Where, that is)

I hear you: but that’s C#. Well … as I said I hate P like my sins.

Forget that sort of thing.

Here’s a C# (as simple as possible) that cuts the mustard by the book. I hear you: why the bazooka (the Class, that is) VS the mosquito?

Well … the demo Query provited is very simple (NOT using all the Properties available like connectivity related ones etc etc) …

… but ANY other imaginable Query - flat/nested/you name it - would be as simple as that.

Kinda spending some time to fine tune your suspension in your superbike (I do hope some Ducati) > after that > WOT etc etc.

Mesh_LINQ_FaceNormals_V1.gh (168.6 KB)

For instace just by adding another Where … I could exclude (from rated as valid) Faces that are isolated (like the ones shown) etc etc.

Thanks. I will give it a try!

A must for anyone with pro aspirations:

BTW: Added (just for fun) a PLANAR bool (obviously related only to Quads). See change in the Class Properties (what is Face planarity ? you tell me) and the simple mod in the LINQ that excludes (or not) non planar Faces (is that easy - as I said anything is a piece of cake).

Mesh_LINQ_FaceNormals_V1A.gh (173.1 KB)

I gave it a try. and altough impressive, I couldn’t quite follow.
I will have to dig into this. I profiled it and it’s really fast. Thanks for pointing me in this direction.

To “quickly” fix my problem I disregarded the guid completely, and did the necessary calculations “on the fly”. by extracting the mesh-faces vertices before starting the iteration.

For any curious reader, this looks like this:

vertices = rs.MeshVertices(mesh)
vertices_index = rs.MeshFaceVertices(mesh)

for loop:
    #[...]
    verts = [vertices[k] for k in vertices_index[i][0:3]]

Coding is kinda martial arts: for the first 10 years it’s hard to get the point.

1 Like

Hi
Just with reading the title and without reading the context or comments,
in such situations i was successful using networkx for python.
Define a network of faces, store face data that will needed , define functions and Bobs your uncle!
Using networkx you can do many network processing.commonly i start with simple networkx operation and develop them step by step.

Hi @Philip7, below is a simple example which iterates over mesh faces, prints out the face index, face type and the vertex indices of each mesh face. Mesh faces and vertices do not have ids, they are accessed by indices. The script demonstrates how to perform a persistent mesh face (sub-object) selection. I did not include any special condition, it just stores every second face index and finally selects the faces.

import Rhino
import scriptcontext
import rhinoscriptsyntax as rs

def DoSomething():
    mesh_id = rs.GetObject("Mesh", rs.filter.mesh, True, False)
    if not mesh_id: return
    
    mesh_obj = rs.coercerhinoobject(mesh_id, True, True)
    face_indices = []
    
    for index, face in enumerate(mesh_obj.Geometry.Faces):
        if face.IsTriangle:
            a,b,c = face.A, face.B, face.C
            print "Face: {} -> Triangle [{},{},{}]".format(index, a, b, c)
        elif face.IsQuad:
            a,b,c,d = face.A, face.B, face.C, face.D
            print "Face: {} -> Quadrangle [{},{},{}]".format(index, a, b, c, d)
            
        if index %2 == 0: face_indices.append(index)
    
    comp_type = Rhino.Geometry.ComponentIndexType.MeshFace
    for index in face_indices:
        comp_index = Rhino.Geometry.ComponentIndex(comp_type, index)
        mesh_obj.SelectSubObject(comp_index, True, True, True) 
    
    scriptcontext.doc.Views.Redraw()
    
DoSomething()

Note: since it prints out information for each mesh face, try it with a simple mesh first.
You can then add other conditions on your own.

_
c.

That was the important part for me. thanks for the code example!