Check if a poly is a face

Scripting in Blender with Python, and working on the API

Moderators: jesterKing, stiv

AlphaNow
Posts: 0
Joined: Mon Sep 30, 2013 7:13 pm

Check if a poly is a face

Post by AlphaNow » Mon Sep 30, 2013 7:49 pm

I am writing a custom export script to parse all the objects in a blender file, filter them by name, then check to make sure that they are manifold.

I am using Blender 2.68a. I've created a blender file with some basic 2d and 3d meshes, as well as some that should fail my test criteria.

I've sorted how to iterate through the objects using a for loop and the D.objects iterator, then check for name matches using regular expressions, and then get a mesh from the object using:

mesh = obj.to_mesh(C.scene, True, 'RENDER')
mesh.update(True, True)
then i can get a list of polygons by access mesh.polygons[index] to know if there is a set of vertices with edges that form a polygon

What I can't sort out is how to determine from the python console if a poly is a face or just a poly. Is there a built in function, or what tests can i perform to programmatically determine this?

I apologize if this is a repeat post. Thanks for your help.

CoDEmanX
Posts: 0
Joined: Sun Apr 05, 2009 7:42 pm
Location: Germany

Post by CoDEmanX » Tue Oct 01, 2013 12:05 am

really dunno what you mean, a polygon is a face and vice versa. What's your definition of face? A triangle? A quad? Both of them, so everything which isn't an Ngon?
I'm sitting, waiting, wishing, building Blender in superstition...

AlphaNow
Posts: 0
Joined: Mon Sep 30, 2013 7:13 pm

Post by AlphaNow » Tue Oct 01, 2013 5:57 am

CoDEmanX wrote:really dunno what you mean, a polygon is a face and vice versa. What's your definition of face? A triangle? A quad? Both of them, so everything which isn't an Ngon?
If you start with a new blender file, delete the camera, lamp, and starter cube, and then add a mesh plane.

Then, go down to Mesh->Delete->Face Only.

You will have 4 vertices, 4 edges, and no face. Like, a window frame without any glass in it(except the window frame has no measurable size). But if you access this object, get a mesh from it, and then look at it's children, it will have polygons[0] and it will have 4 edges which connect vertices numbered 0 through 3.

I need to find a way to programatically determine if the bpy.data.object has a face (glass in the window) or if it is only edges and vertices. The polygons structure that it has just seem to be any ngons formed by the edges/vertices, according to what I found in the API reference, and not necessarily a face.

If i am misunderstanding terminology or what the data structure is telling me, please, give me a good smack upside the head and tell me firmly, "no. bad." Then, tell me the right way to do it.

stiv
Posts: 0
Joined: Tue Aug 05, 2003 7:58 am
Location: 45N 86W

Post by stiv » Tue Oct 01, 2013 7:02 am

The data structures involved in a mesh are:

* an array of vertices - X, Y, Z, and maybe a W if we are doing homogenous stuff

* a list of edges - an edge is between two points. Each point is represented as an index into the vertex array, so each element is two numbers

* a list of faces - a face is a list of edges, 3 if you are old school and believe the triangle is the One True polygon, 4 if we are talking quads and N if you are into new-fangled n-gons. Each edge in the list is an index into the edge list for this mesh.

If any of these arrays/lists is empty, then that mesh has none of those elements.

AlphaNow
Posts: 0
Joined: Mon Sep 30, 2013 7:13 pm

Post by AlphaNow » Tue Oct 01, 2013 7:43 am

stiv wrote:The data structures involved in a mesh are:

* an array of vertices - X, Y, Z, and maybe a W if we are doing homogenous stuff

* a list of edges - an edge is between two points. Each point is represented as an index into the vertex array, so each element is two numbers

* a list of faces - a face is a list of edges, 3 if you are old school and believe the triangle is the One True polygon, 4 if we are talking quads and N if you are into new-fangled n-gons. Each edge in the list is an index into the edge list for this mesh.

If any of these arrays/lists is empty, then that mesh has none of those elements.
Okay, well, via http://www.blender.org/documentation/bl ... 4_release/ theres no face access operator in 2.68a which I'm using. Does that mean polygons are faces now, 1:1? every polygon implies that the face is 'filled'? Or is the actual state of the object being modified when one uses the to_mesh() function, as in, does it create "polygons" even when there is no faces?

[/url]

CoDEmanX
Posts: 0
Joined: Sun Apr 05, 2009 7:42 pm
Location: Germany

Post by CoDEmanX » Tue Oct 01, 2013 10:33 am

verts and edges are NOT faces, polygons are (triangles, quads and ngons).

Face access goes via .polygons


See my addon, it counts tris, quads and ngons separately:

https://svn.blender.org/svnroot/bf-exte ... _select.py
I'm sitting, waiting, wishing, building Blender in superstition...

AlphaNow
Posts: 0
Joined: Mon Sep 30, 2013 7:13 pm

Post by AlphaNow » Tue Oct 01, 2013 2:57 pm

CoDEmanX wrote:verts and edges are NOT faces, polygons are (triangles, quads and ngons).

Face access goes via .polygons


See my addon, it counts tris, quads and ngons separately:

https://svn.blender.org/svnroot/bf-exte ... _select.py
I'll give it a try on my test dataset. Looks like counting anything with greater than 2 edges should give me a boolean test like i'm looking for.

I'll let you know how it turns out. Thanks!

AlphaNow
Posts: 0
Joined: Mon Sep 30, 2013 7:13 pm

Post by AlphaNow » Tue Oct 01, 2013 7:56 pm

Okay, so, new problem now. I can select all of the faces with 3 or greater verts. then invert. In order to do this, I'm using the following code (frankensteined together from looking at CoDEmanX's script)

Code: Select all

def check_face(obj):
	C.scene.objects.active = obj
	if(obj.type == 'MESH'):
		bpy.ops.object.editmode_toggle()
		bpy.ops.mesh.select_all(action='DESELECT')
		#bpy.ops.mesh.select_face_by_sides(number=3, type='EQUAL', extend=False)
		bpy.ops.mesh.select_face_by_sides(number=3, type='GREATER', extend=True)
		bpy.ops.mesh.select_mode(False, False, type='VERT', action='ENABLE')
		bpy.ops.mesh.select_all(action='INVERT')
		if(len(C.selected_objects)==0):
			return true
	return false
I encountered the problem that C.select_objects is always returning 1, even when the active object has faces, and no verts, edges, or faces are selected when it tries to compare C.selected_objects == 0.

So, I researched some, and found that I can in fact just iterate through every edge / vert in each mesh to determine if anything is selected.

This requires code similar to this:

Code: Select all

def test_meshes()
	selectededges = 0
	selectedverts = 0
	for thismesh in bpy.data.meshes:
		for thisedge in thismesh.edges:
			print(thisedge.select)
			if(thisedge.select):
				print("\tan edge was selected.")
				selectededges += 1
		print("Done testing edges.
		for thisvert in thismesh.vertices
			print(thisvert.select)
			if(thisvert.select):
				print("\ta vert was selected.")
				selectedverts += 1
		print("\tThere were " + str(selectededges) + " edges selected in mesh " + thismesh.name )
		print("\tThere were " + str(selectedverts) + " verts selected in mesh " + thismesh.name )
		selectededges = 0
		selectedverts = 0
My problem is, when I do this, I havent sorted out how to determine which meshes belong to which bpy.data.objects.

CoDEmanX
Posts: 0
Joined: Sun Apr 05, 2009 7:42 pm
Location: Germany

Post by CoDEmanX » Wed Oct 02, 2013 1:46 am

hm maybe you actually mean to analyse these two cases?

Image

For the left cube, test should return True, for the right (no faces) False?

Code: Select all

[print(ob.name, len(ob.data.polygons) > 0) for ob in bpy.context.scene.objects]
I'm sitting, waiting, wishing, building Blender in superstition...

AlphaNow
Posts: 0
Joined: Mon Sep 30, 2013 7:13 pm

Post by AlphaNow » Wed Oct 02, 2013 6:23 am

CoDEmanX wrote:hm maybe you actually mean to analyse these two cases?

Image

For the left cube, test should return True, for the right (no faces) False?

Code: Select all

[print(ob.name, len(ob.data.polygons) > 0) for ob in bpy.context.scene.objects]
That sounds promising. I will have to test it against my data set tomorrow.

You are correct in your understanding of my desired behavior for those two test cubes. But i need it to also fail if there are edges that do not belong to any polygons. I guess i'll just have to iterate through every edge and make sure they belong to a poly.

Thanks!

CoDEmanX
Posts: 0
Joined: Sun Apr 05, 2009 7:42 pm
Location: Germany

Post by CoDEmanX » Wed Oct 02, 2013 8:28 am

Hm dunno if there's a faster builtin way (other than using select loose operator), but should work:

Code: Select all

import bpy, bmesh

for ob in bpy.context.scene.objects:
    if ob.type != 'MESH':
        continue
    bm = bmesh.new()
    bm.from_object(ob, bpy.context.scene)

    if len(bm.faces) > 0 and 0 not in (len(e.link_faces) for e in bm.edges):
        print(ob.name, "is valid")
    else:
        print(ob.name, "has errors")
I'm sitting, waiting, wishing, building Blender in superstition...

AlphaNow
Posts: 0
Joined: Mon Sep 30, 2013 7:13 pm

Post by AlphaNow » Wed Oct 02, 2013 5:03 pm

CoDEmanX wrote:Hm dunno if there's a faster builtin way (other than using select loose operator), but should work:

Code: Select all

import bpy, bmesh

for ob in bpy.context.scene.objects:
    if ob.type != 'MESH':
        continue
    bm = bmesh.new()
    bm.from_object(ob, bpy.context.scene)

    if len(bm.faces) > 0 and 0 not in (len(e.link_faces) for e in bm.edges):
        print(ob.name, "is valid")
    else:
        print(ob.name, "has errors")
So far, looks perfect against my minimal test dataset. I've got to look at some of my more complex datasets to verify there arent any other gotchas that I need to handle, but you've saved me a lot of time, and i've learned a fair bit. Thanks a bunch!

AlphaNow
Posts: 0
Joined: Mon Sep 30, 2013 7:13 pm

Post by AlphaNow » Wed Oct 02, 2013 6:13 pm

Is there an easy way to access the name of the currently open file? I don't need the path, but I wanted to have my exporter default to a name based off the name of the currently open file. I've dived all through the API and haven't found anything to let me access that.

CoDEmanX
Posts: 0
Joined: Sun Apr 05, 2009 7:42 pm
Location: Germany

Post by CoDEmanX » Wed Oct 02, 2013 6:51 pm

it is easily overlooked, yet simple:

bpy.data.filepath

As long as you work with an unsaved blend, it will be a blank string
I'm sitting, waiting, wishing, building Blender in superstition...

AlphaNow
Posts: 0
Joined: Mon Sep 30, 2013 7:13 pm

Post by AlphaNow » Wed Oct 02, 2013 10:46 pm

CoDEmanX wrote:it is easily overlooked, yet simple:

bpy.data.filepath

As long as you work with an unsaved blend, it will be a blank string
Thanks so much. I'm plowing through this now.

I followed a tutorial that uses the self, context arguments (for it to be an addon). I wondered, can I call my functions with self from the python console?

i.e. function_name(bpy.some.data.substructure.self, bpy.context)

Post Reply