Check if a poly is a face

Scripting in Blender with Python, and working on the API

Moderators: jesterKing, stiv

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

Post by CoDEmanX » Thu Oct 03, 2013 12:27 am

Hm no, you should make it an operator and call like seen in the last line:

Code: Select all

import bpy, bmesh


def main(context):

    count_good = 0
    count_bad = 0

    for ob in context.scene.objects:
        if ob.type != 'MESH':
            continue
        bm = bmesh.new()
        # Note: only use from_object if you wanna test 
        # the mesh with modifiers etc. applied, otherwise
        # from_mesh() to save performance (I think?)
        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")
            count_good += 1
        else:
            print(ob.name, "has errors") 
            count_bad += 1
            
    return count_good, count_bad


class SimpleOperator(bpy.types.Operator):
    """Tooltip"""
    bl_idname = "object.meshes_validate"
    bl_label = "Validate Meshes"

    @classmethod
    def poll(cls, context):
        return context.active_object is not None

    def execute(self, context):
        count_good, count_bad = main(context)
        self.report({'INFO'}, "%i good, %i bad." % (count_good, count_bad))
        return {'FINISHED'}


def register():
    bpy.utils.register_class(SimpleOperator)


def unregister():
    bpy.utils.unregister_class(SimpleOperator)


if __name__ == "__main__":
    register()

    # test call
    bpy.ops.object.meshes_validate()
Run the operator in viewport by pressing SpaceBar while mouse cursor is over View 3D, to bring up the operator search dialog and type in "validate meshes", then press Enter. It will give you a summary in the info header, but also prints to the system console.
I'm sitting, waiting, wishing, building Blender in superstition...

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

Post by AlphaNow » Thu Oct 03, 2013 12:32 am

Well, this is for an exporter. I've written the code as an addon so that it generates a menu item to do a one-click validate and selective export. I just wanted to see if I could test it from the console to make sure my functions that I wrote were right before I started trying to troubleshoot why it won't import as an addon.

Let me know.

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

Post by CoDEmanX » Thu Oct 03, 2013 3:21 am

well you could actually use my code as it is, just change the main() parameter from context to scene, or even objects (but you'll have to use bpy.context.scene in the from_object() call).

That way, you get a nice, standalone function to validate (e.g. import it into another script which does the export). But you can also wrap it as operator.

What does it say in the system console as you try to import it as addon?
I'm sitting, waiting, wishing, building Blender in superstition...

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

Post by AlphaNow » Thu Oct 03, 2013 6:59 pm

CoDEmanX wrote:well you could actually use my code as it is, just change the main() parameter from context to scene, or even objects (but you'll have to use bpy.context.scene in the from_object() call).

That way, you get a nice, standalone function to validate (e.g. import it into another script which does the export). But you can also wrap it as operator.

What does it say in the system console as you try to import it as addon?
I tried to import it from the GUI interface in User Preferences / Addons / From File

When i right-click on the module, which it shows as loaded, it says(minorly edited for confidentiality):
Traceback(most recent call last):
File: "C:\Program Files\Blender Foundation\Blender\2.68\scripts\modules\addon_utils.py", line 294, in enable_mod=__import__(module_name)
ImportError: No module named '*******_exporter_0'

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

Post by AlphaNow » Thu Oct 03, 2013 7:04 pm

I'm now getting the feeling that the file name has to match some information in the bl_info structure. Any resources with the correct procedure out there?

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

Post by AlphaNow » Thu Oct 03, 2013 8:03 pm

AlphaNow wrote:I'm now getting the feeling that the file name has to match some information in the bl_info structure. Any resources with the correct procedure out there?

After re-reading Bruce Sutherland's blog posts about creating a blender export addon, I realized that the script needed to be in its own folder in my addons folder, and be named __init__.py

I got the addon to import, but now it's giving me an error:

Code: Select all

reloading addon: io_mesh_xxx 1380819239.0650904 1380822045.6446173 C:\Program Files\Blender Foundation\Blender\2.68\scripts\addons\io_mesh_xxx\__init__.py 
trying to save userpref at C:\Users\*******\AppData\Roaming\Blender Foundation\Blender\2.68\config\userpref.blend ok 
<!> event has invalid window 
trying to save userpref at C:\Users\*******\AppData\Roaming\Blender Foundation\Blender\2.68\config\userpref.blend ok 
Traceback (most recent call last): 
  File "C:\Program Files\Blender Foundation\Blender\2.68\scripts\addons\io_mesh_ 
xxx\__init__.py", line 23, in execute 
    if enumerate(self, context): 
TypeError: 'Context' object cannot be interpreted as an integer 

location: <unknown location>:-1 

location: <unknown location>:-1 
execute is defined as

Code: Select all

def execute(self, context): 
   if enumerate(self, context): 
      return('Finished'); 
   return('The file could not be opened for writing.')

enumerate is defined as:

Code: Select all

def enumerate(self, context): 
      import re#import regular expressions to match name patterns 
      filename = bpy.data.filepath.rstrip('.blend') + <extension of export_file> 
      #remove .blend from end of current filename 
      file = open(filename,'w') 
      #opening and then closing with w mode clears current file contents. file will be appended to within the export function. 
      file.close() 
      for obj in D.objects: 
         match = re.match(<match string here>, obj.name) 
         if(match != None) : 
            log(obj.name + ' meets naming criteria.') 
            if(verify_object(obj, context)): 
               log('begin object export', obj.name) 
               export(obj, filename) 
               log('finish object export', obj.name) 
            else: 
               log.write('object failed to meet face requirements.') 
      print('done checking & exporting objects.') 
      return True

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

Post by CoDEmanX » Fri Oct 04, 2013 6:26 am

enumerate is a built-in function, better not call your own functions like that!
I'm sitting, waiting, wishing, building Blender in superstition...

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

Post by AlphaNow » Fri Oct 04, 2013 10:13 pm

CoDEmanX wrote:enumerate is a built-in function, better not call your own functions like that!
Heh, that makes sense. Changed the function name, and all seems to be well. Much appreciated.

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

Post by AlphaNow » Fri Oct 04, 2013 10:59 pm

AlphaNow wrote:
CoDEmanX wrote:enumerate is a built-in function, better not call your own functions like that!
Heh, that makes sense. Changed the function name, and all seems to be well. Much appreciated.
So, I'm seeing some unneeded functionality and I'm not sure how to get rid of it.

I wrote my exporter, following a tutorial i found, and the exporter part of things work, as does all the object/mesh verification. But when I select File->Import/Export-> <MyFileType> it opens a file browser. I've coded the exporter to just work against the currently opened .blend file, always. How can I get rid of the file browser if I'm not using that functionality? I understand that I could access self.filename and get the filename that the user selects, but I don't want it to even show up. Let me know. Thanks.

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

Post by CoDEmanX » Sat Oct 05, 2013 12:02 am

your exporter is actually part of a modal operator. It has invoke() and execute(), the modal() part is derived from the ExportHelper (i assume you use it?).

In the export helper code, a file selector is added when operator invoked(!). The file selector is a modal operator. When target is chosen, modal() ends and operator goes on with execute(). So it goes invoke() -> modal() -> execute().

For menu entries, it is the default to invoke operators. In other contexts, they are rather executed.

You could skip the invoke() and modal() (=file selector), and directly go on to the execute() which does the export, and pass it a filename or whatever params are required. (This would have been done by the file selector otherwise).

To make a menu entry execute an operator instead of invoking, you need to set .operator_context before adding the operator using .operator() - but both are elements of a UILayout instance! If you set layout.operator_context = ... , it would apply to a couple entries, and not just yours.

So better add a new ui element:

col = layout.column()
col.operator_context = 'EXEC_DEFAULT'
col.operator("export.your_operators_name")

you could even hardcode a filepath into the menu:

props = col.operator("export.your_operators_name")
props.filename = "..." # assuming your operator has a StringProperty "filename"
I'm sitting, waiting, wishing, building Blender in superstition...

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

Post by AlphaNow » Sat Oct 05, 2013 4:52 am

CoDEmanX wrote:your exporter is actually part of a modal operator. It has invoke() and execute(), the modal() part is derived from the ExportHelper (i assume you use it?).

In the export helper code, a file selector is added when operator invoked(!). The file selector is a modal operator. When target is chosen, modal() ends and operator goes on with execute(). So it goes invoke() -> modal() -> execute().

For menu entries, it is the default to invoke operators. In other contexts, they are rather executed.

You could skip the invoke() and modal() (=file selector), and directly go on to the execute() which does the export, and pass it a filename or whatever params are required. (This would have been done by the file selector otherwise).

To make a menu entry execute an operator instead of invoking, you need to set .operator_context before adding the operator using .operator() - but both are elements of a UILayout instance! If you set layout.operator_context = ... , it would apply to a couple entries, and not just yours.

So better add a new ui element:

col = layout.column()
col.operator_context = 'EXEC_DEFAULT'
col.operator("export.your_operators_name")

you could even hardcode a filepath into the menu:

props = col.operator("export.your_operators_name")
props.filename = "..." # assuming your operator has a StringProperty "filename"
So, if I didn't use ExportHelper, it wouldn't invoke the file selector?

I want to export to the same directory where the currently open file is located. Currently, the export function already determines what file is open, and then does a little string manipulation to change the extension, then goes ahead with the export.

I also ran into a new problem which probably has something to do with my understanding(or lack thereof) of how python deals with lists.

In order to minimize the amount of information I need to manipulate in the recipient program, I am going through the set of edge_keys for each polygon. The program seems to randomly choose the order in which it represents the endpoints of edges, but does store the edges in the correct order. I am trying to take a copy of the edge_keys, then swap the edge endpoints at each index if adjacent endpoints in the edge_keys list are not equal.

example:

[(1,2),(24,1),(2,24)] -> [(2,1),(1,24),(24,2)] through use of a loop of comparisons, if statements, and endpoint swaps. Then, when adjacent (and wrapped) endpoints are equal, i can just export 1,24,2 and it will properly represent the poly in question.

But it seems when I try to assign values to the individual sub-elements, i get an error about assigning tuples(not in front of my workstation, cant recall the exact language).

this is an example of the kind of thing that I am trying to do in this endpoint

Code: Select all

edges = bpy.data.objects[0].data.polygons[0].edge_keys
if test_condition:
     temp = edges[0][0]
     edges[0][0]=edges[0][1]#this is where the problem happens.
     edges[0][1]=temp
I dont have to do it this way, if there's an easier way to do the sort, i'd be interested to see it.

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

Post by AlphaNow » Sat Oct 05, 2013 5:10 am

CoDEmanX wrote:your exporter is actually part of a modal operator. It has invoke() and execute(), the modal() part is derived from the ExportHelper (i assume you use it?).

In the export helper code, a file selector is added when operator invoked(!). The file selector is a modal operator. When target is chosen, modal() ends and operator goes on with execute(). So it goes invoke() -> modal() -> execute().

For menu entries, it is the default to invoke operators. In other contexts, they are rather executed.

You could skip the invoke() and modal() (=file selector), and directly go on to the execute() which does the export, and pass it a filename or whatever params are required. (This would have been done by the file selector otherwise).

To make a menu entry execute an operator instead of invoking, you need to set .operator_context before adding the operator using .operator() - but both are elements of a UILayout instance! If you set layout.operator_context = ... , it would apply to a couple entries, and not just yours.

So better add a new ui element:

col = layout.column()
col.operator_context = 'EXEC_DEFAULT'
col.operator("export.your_operators_name")

you could even hardcode a filepath into the menu:

props = col.operator("export.your_operators_name")
props.filename = "..." # assuming your operator has a StringProperty "filename"
So, if I didn't use ExportHelper, it wouldn't invoke the file selector?

I want to export to the same directory where the currently open file is located. Currently, the export function already determines what file is open, and then does a little string manipulation to change the extension, then goes ahead with the export.

I also ran into a new problem which probably has something to do with my understanding(or lack thereof) of how python deals with lists.

In order to minimize the amount of information I need to manipulate in the recipient program, I am going through the set of edge_keys for each polygon. The program seems to randomly choose the order in which it represents the endpoints of edges, but does store the edges in the correct order. I am trying to take a copy of the edge_keys, then swap the edge endpoints at each index if adjacent endpoints in the edge_keys list are not equal.

example:

[(1,2),(24,1),(2,24)] -> [(2,1),(1,24),(24,2)] through use of a loop of comparisons, if statements, and endpoint swaps. Then, when adjacent (and wrapped) endpoints are equal, i can just export 1,24,2 and it will properly represent the poly in question.

But it seems when I try to assign values to the individual sub-elements, i get an error about assigning tuples(not in front of my workstation, cant recall the exact language).

this is an example of the kind of thing that I am trying to do in this endpoint

Code: Select all

edges = bpy.data.objects[0].data.polygons[0].edge_keys
if test_condition:
     temp = edges[0][0]
     edges[0][0]=edges[0][1]#this is where the problem happens.
     edges[0][1]=temp
I dont have to do it this way, if there's an easier way to do the sort, i'd be interested to see it.

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

Post by AlphaNow » Sat Oct 05, 2013 5:13 am

CoDEmanX wrote:your exporter is actually part of a modal operator. It has invoke() and execute(), the modal() part is derived from the ExportHelper (i assume you use it?).

In the export helper code, a file selector is added when operator invoked(!). The file selector is a modal operator. When target is chosen, modal() ends and operator goes on with execute(). So it goes invoke() -> modal() -> execute().

For menu entries, it is the default to invoke operators. In other contexts, they are rather executed.

You could skip the invoke() and modal() (=file selector), and directly go on to the execute() which does the export, and pass it a filename or whatever params are required. (This would have been done by the file selector otherwise).

To make a menu entry execute an operator instead of invoking, you need to set .operator_context before adding the operator using .operator() - but both are elements of a UILayout instance! If you set layout.operator_context = ... , it would apply to a couple entries, and not just yours.

So better add a new ui element:

col = layout.column()
col.operator_context = 'EXEC_DEFAULT'
col.operator("export.your_operators_name")

you could even hardcode a filepath into the menu:

props = col.operator("export.your_operators_name")
props.filename = "..." # assuming your operator has a StringProperty "filename"
So, if I didn't use ExportHelper, it wouldn't invoke the file selector?

I want to export to the same directory where the currently open file is located. Currently, the export function already determines what file is open, and then does a little string manipulation to change the extension, then goes ahead with the export.

I also ran into a new problem which probably has something to do with my understanding(or lack thereof) of how python deals with lists.

In order to minimize the amount of information I need to manipulate in the recipient program, I am going through the set of edge_keys for each polygon. The program seems to randomly choose the order in which it represents the endpoints of edges, but does store the edges in the correct order. I am trying to take a copy of the edge_keys, then swap the edge endpoints at each index if adjacent endpoints in the edge_keys list are not equal.

example:

[(1,2),(24,1),(2,24)] -> [(2,1),(1,24),(24,2)] through use of a loop of comparisons, if statements, and endpoint swaps. Then, when adjacent (and wrapped) endpoints are equal, i can just export 1,24,2 and it will properly represent the poly in question.

But it seems when I try to assign values to the individual sub-elements, i get an error about assigning tuples(not in front of my workstation, cant recall the exact language).

this is an example of the kind of thing that I am trying to do in this endpoint

Code: Select all

edges = bpy.data.objects[0].data.polygons[0].edge_keys
if test_condition:
     temp = edges[0][0]
     edges[0][0]=edges[0][1]#this is where the problem happens.
     edges[0][1]=temp
I dont have to do it this way, if there's an easier way to do the sort, i'd be interested to see it.

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

Post by CoDEmanX » Sat Oct 05, 2013 6:24 am

So, if I didn't use ExportHelper, it wouldn't invoke the file selector?
Yes, but it wouldn't export either, as execute() would never be called (remember: operators in menus are invoked be default, not executed).

ExportHelper is just a convenience class, it calls bpy.context.window_manager.fileselect_add(). You could do that yourself as well.
I want to export to the same directory where the currently open file is located. Currently, the export function already determines what file is open, and then does a little string manipulation to change the extension, then goes ahead with the export.
Well you can do what i described above (overwrite operator_context) and ignore the file selector / ExportHelper. That should just work fine. It will give you a menu entry that will directly execute the operator and not call the invoke() method which would bring up the file selector.

But it seems when I try to assign values to the individual sub-elements, i get an error about assigning tuples
It's a list of tuples, and tuples are immutable. You can't change them once created, you can only replace the entries in the (mutable) list by new tuples.

This works:

Code: Select all

for i in range(len(edge_keys)):
    edge_keys[i] = edge_keys[i][::-1]
(reverses all tuples)

BTW: you can swap values between variables without temp var in python

a = 1
b = 2
a, b = b, a
I'm sitting, waiting, wishing, building Blender in superstition...

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

Post by AlphaNow » Mon Oct 07, 2013 11:44 pm

CoDEmanX wrote:
So, if I didn't use ExportHelper, it wouldn't invoke the file selector?
Yes, but it wouldn't export either, as execute() would never be called (remember: operators in menus are invoked be default, not executed).

ExportHelper is just a convenience class, it calls bpy.context.window_manager.fileselect_add(). You could do that yourself as well.
I want to export to the same directory where the currently open file is located. Currently, the export function already determines what file is open, and then does a little string manipulation to change the extension, then goes ahead with the export.
Well you can do what i described above (overwrite operator_context) and ignore the file selector / ExportHelper. That should just work fine. It will give you a menu entry that will directly execute the operator and not call the invoke() method which would bring up the file selector.
So, I started with this tutorial on writing an export script, and it included a starter script file that was already laid out.

Code: Select all

bl_info = {
    "name":         "My Data Format",
    "author":       "Bruce Sutherland",
    "blender":      (2,6,2),
    "version":      (0,0,1),
    "location":     "File > Import-Export",
    "description":  "Export custom data format",
    "category":     "Import-Export"
}
        
import bpy
from bpy_extras.io_utils import ExportHelper

class ExportMyFormat(bpy.types.Operator, ExportHelper):
    bl_idname       = "export_my_format.fmt";
    bl_label        = "My Data Exporter";
    bl_options      = {'PRESET'};
    
    filename_ext    = ".fmt";
    
    def execute(self, context):
        return {'Finished'};

def menu_func(self, context):
    self.layout.operator(ExportMyFormat.bl_idname, text="My Model Format(.fmt)");

def register():
    bpy.utils.register_module(__name__);
    bpy.types.INFO_MT_file_export.append(menu_func);
    
def unregister():
    bpy.utils.unregister_module(__name__);
    bpy.types.INFO_MT_file_export.remove(menu_func);

if __name__ == "__main__":
    register()
then, below that if statement, I declared several functions which call each other at the appropriate moments. As per your previous post, i renamed my parent function to main.

Since I want the operator to be listed in by.ops.scene.export_xxx(), I set:

Code: Select all

bl_idname = "scene.export_xxx"
though honestly not sure if this matters AT ALL beyond calling the operator from the command line.

I also removed the ExportHelper from

Code: Select all

class ExportMyFormat( , <here>)
and added

Code: Select all

@classmethod
def poll(cls, context):
return context.active_object is not None

def execute(self, context):
if main(bpy.context):
return{'FINISHED'}
else:
return{'ERROR'}
what other changes do I need to make to be able to have this listed in the Import/Export menu and call execute() but not show the file picker?

As always, thanks for your input.

Post Reply