OBJ Export Options



  • I just switched to R23 and am fixing some my python scripts. I'm running into an issue setting an OBJ Export Option. In particular it's something that I think is new - OBJEXPORTOPTIONS_EXPORT_UVS. In the documentation it looks like I should be setting it to a number.

    Parameter: UVs

    Parameter ID: c4d.OBJEXPORTOPTIONS_EXPORT_UVS
    
    Parameter Type: int
    
    Cycle Values:
    
            None (c4d.OBJEXPORTOPTIONS_UV_NONE)
    
            Original (c4d.OBJEXPORTOPTIONS_UV_ORIGINAL)
    
            Flip U (c4d.OBJEXPORTOPTIONS_FLIP_U)
    
            Flip V (c4d.OBJEXPORTOPTIONS_FLIP_V)
    
            Flip UV (c4d.OBJEXPORTOPTIONS_FLIP_UV)
    

    Based on the documentation I think I should have something like this - objExport[c4d.OBJEXPORTOPTIONS_EXPORT_UVS] = 1 but it's throwing an error in the console 'AttributeError: parameter set failed'

    Here's my block of code for this -

        #set up obj exporter
    
        plug = plugins.FindPlugin(1030178, c4d.PLUGINTYPE_SCENESAVER)
        if plug is None:
            print ("noPlug")
    
        op = {} # create dictionary to store key
        # Send MSG_RETRIEVEPRIVATEDATA to fbx export plugin
        if plug.Message(c4d.MSG_RETRIEVEPRIVATEDATA, op):
            # BaseList2D object stored in "imexporter" key hold the settings
            objExport = op["imexporter"]
            # Define the settings
            objExport[c4d.OBJEXPORTOPTIONS_SCALE] = 1
            objExport[c4d.OBJEXPORTOPTIONS_MATERIAL] =  2
            objExport[c4d.OBJEXPORTOPTIONS_INVERT_TRANSPARENCY] = False
            objExport[c4d.OBJEXPORTOPTIONS_POINTTRANSFORM_FLIPX] = False
            objExport[c4d.OBJEXPORTOPTIONS_POINTTRANSFORM_FLIPY] = False
            objExport[c4d.OBJEXPORTOPTIONS_POINTTRANSFORM_FLIPZ] = True
            objExport[c4d.OBJEXPORTOPTIONS_FLIPFACES] = False
            objExport[c4d.OBJEXPORTOPTIONS_POINTTRANSFORM_SWAPXY] = False
            objExport[c4d.OBJEXPORTOPTIONS_POINTTRANSFORM_SWAPXZ] = False
            objExport[c4d.OBJEXPORTOPTIONS_POINTTRANSFORM_SWAPYZ] = False
            objExport[c4d.OBJEXPORTOPTIONS_EXPORT_UVS] = 1
            objExport[c4d.OBJEXPORTOPTIONS_OBJECTS_AS_GROUPS] = False
            objExport[c4d.OBJEXPORTOPTIONS_TRIANGULATE_NGONS] = True
    

    Is anybody else running into this or am I missing something on how to set the UV parameter of the export?

    Any help is appreciated.



  • @del said in OBJ Export Options:

    objExport[c4d.OBJEXPORTOPTIONS_EXPORT_UVS] = 1

    Why 1? A quick look at the numeric value of the five options shows that they are between 4092 and 4096; none of them is 1. And you should use the profferred "constants" anyway, so it would be

    objExport[c4d.OBJEXPORTOPTIONS_EXPORT_UVS] = c4d.OBJEXPORTOPTIONS_UV_ORIGINAL
    

    etc.



  • I'm using a 1 because it's the second value in the list much like I'm using for for Scale and Material where the numbers represent their positions in the list.

    I'll give it a try with the value instead.



  • Thanks for your help, that worked. I swear that the documentation and what the SDK expects changes every couple of years. Several years ago I'd try words and I'd be told to use numbers and now it seams as if numbers work for some things but words are required for others. It can be a bit confusing for me at times but at least I have a working script again.

    Just to clarify, I always thought when the documentation showed 'Parameter Type: int' that it meant a number and the number was representative of the numerical position in the list. You mentioned "numeric value of the five options shows that they are between 4092 and 4096". Where did you find those numbers? I don't see that in the Python documentation.

    thanks,
    .del



  • Well it looks like exporting an .obj isn't working for me at the moment anyway. The full scripts exports everything except the obj and when I try to use C4D's menu to export an .obj nothing happens either. No error message....nothing....no file...no lockup......nothing. I've submitted a support ticket. I'm not sure if it's script related or just a bug in R23. I'll have to fall back to R21 again so I can keep working.



  • @del said in OBJ Export Options:

    Thanks for your help, that worked. I swear that the documentation and what the SDK expects changes every couple of years. Several years ago I'd try words and I'd be told to use numbers and now it seams as if numbers work for some things but words are required for others. It can be a bit confusing for me at times but at least I have a working script again.

    There are actually some values for which no constants exist, so you have to use numbers. Nevertheless - those "constants" (I write it in quote marks because Python doesn't have real constants, these are actually variables but you must never change them) are representing only numbers, they're not strings.

    Constants are a common method to define numberic values because 1. if they change internally, you don't need to change your code because the constant will now refer the new value, and 2. it's more readable. So I understand why Maxon offers these constants. If you really need to you could use the numeric value instead (= 4093 in this case), but that is not recommended and only done when there really is no constant.

    (In the latter case, you normally define your own constant just in case you need to change it later - then you have one change point instead of many, and an easy readable way to identify the purpose of that number.)

    Just to clarify, I always thought when the documentation showed 'Parameter Type: int' that it meant a number and the number was representative of the numerical position in the list. You mentioned "numeric value of the five options shows that they are between 4092 and 4096". Where did you find those numbers? I don't see that in the Python documentation.

    Well, the list positions are not necessarily the corresponding numbers. Have you ever created a list field in user data? You will notice then that for every entry you state an id and a text, the position in the list is not enough to define the ID. In many cases the ID and the position in the list field are the same, so you define text 0, text 1, text 2 etc. just as you expect.

    But that's not needed (line index and ID may be vastly different), and in fact there are dropdowns in the C4D GUI where this is not the case! (I'm not going into detail why.)

    If the API documentation lists constants, you're better off using these constants, for the reasons I just mentioned. If you want the numbers for the constants, just type them into the Python Console and press return, so Python shows you the value. If you get no value, prefix it with "print ", although that should only happen if the value of that constant is None.

    If you find no constants in the docs, you can still get the numeric value: Select the value in the GUI dropdown where it is used. Then drag the name label of the parameter into the console and press return. Python should then give you the current value of the parameter (sadly, not the name of the constant as Python has no information about these constants).



  • hi,

    after giving it a try, it's working as expected. Of course you have to check c4d version to define some option or not.
    As in the script you found on github

    If after launching your script the export option doesn't do anything, that mean your script have send bad parameter to the exporter. (and you have to relaunch c4d)

    Another thing, now we have a resource parser witch help to retrieve those ids. For example

    import c4d
    
    
    def main():
        # Retrieves a path to save the exported file
        filePath = c4d.storage.LoadDialog(title="Save File for OBJ Export", flags=c4d.FILESELECT_SAVE, force_suffix="obj")
        if not filePath:
            return
    
        # Retrieves Obj export plugin, defined in R17 as FORMAT_OBJ2EXPORT and below R17 as FORMAT_OBJEXPORT
        objExportId = c4d.FORMAT_OBJEXPORT if c4d.GetC4DVersion() < 17000 else c4d.FORMAT_OBJ2EXPORT
        plug = c4d.plugins.FindPlugin(objExportId, c4d.PLUGINTYPE_SCENESAVER)
        if plug is None:
            raise RuntimeError("Failed to retrieve the OBJ exporter.")
    
        data = dict()
        # Sends MSG_RETRIEVEPRIVATEDATA to OBJ export plugin
        if not plug.Message(c4d.MSG_RETRIEVEPRIVATEDATA, data):
            raise RuntimeError("Failed to retrieve private data.")
    
        # BaseList2D object stored in "imexporter" key hold the settings
        objExport = data.get("imexporter", None)
        if objExport is None:
            raise RuntimeError("Failed to retrieve BaseContainer private data.")
    
        # Defines OBJ export settings
        # Check c4d version
        if c4d.GetC4DVersion() > 22600:
            objExport[c4d.OBJEXPORTOPTIONS_EXPORT_UVS] = c4d.OBJEXPORTOPTIONS_UV_ORIGINAL
        else:
            objExport[c4d.OBJEXPORTOPTIONS_TEXTURECOORDINATES] = True
        objExport[c4d.OBJEXPORTOPTIONS_MATERIAL] = c4d.OBJEXPORTOPTIONS_MATERIAL_MATERIAL
    
        objExport[c4d.OBJEXPORTOPTIONS_SCALE] = 1
        objExport[c4d.OBJEXPORTOPTIONS_MATERIAL] =  2
        objExport[c4d.OBJEXPORTOPTIONS_INVERT_TRANSPARENCY] = False
        objExport[c4d.OBJEXPORTOPTIONS_POINTTRANSFORM_FLIPX] = False
        objExport[c4d.OBJEXPORTOPTIONS_POINTTRANSFORM_FLIPY] = False
        objExport[c4d.OBJEXPORTOPTIONS_POINTTRANSFORM_FLIPZ] = True
        objExport[c4d.OBJEXPORTOPTIONS_FLIPFACES] = False
        objExport[c4d.OBJEXPORTOPTIONS_POINTTRANSFORM_SWAPXY] = False
        objExport[c4d.OBJEXPORTOPTIONS_POINTTRANSFORM_SWAPXZ] = False
        objExport[c4d.OBJEXPORTOPTIONS_POINTTRANSFORM_SWAPYZ] = False
        objExport[c4d.OBJEXPORTOPTIONS_OBJECTS_AS_GROUPS] = False
        objExport[c4d.OBJEXPORTOPTIONS_TRIANGULATE_NGONS] = True
    
    
    
        # Finally export the document
        if not c4d.documents.SaveDocument(doc, filePath, c4d.SAVEDOCUMENTFLAGS_DONTADDTORECENTLIST, objExportId):
            raise RuntimeError("Failed to save the document.")
    
        print("Document successfully exported to:", filePath)
    
    
    if __name__ == '__main__':
        main()
    

    Cheers,
    Manuel



  • I was able to get it working once I knew that I was supposed to be passing text and you were right that once I re-laucnhed R23 it cleared up the problem with the export working from the main menu. I wish I knew about the GitHub file sooner as it would have saved me some time. My script is an adaptation of an fbx exporter I had been using. At the time I created I couldn't find much on .obj exporting. These things are two years old and I'm just circling back to them as I start using R23.

    Unfortunately I've come to find that the new exporter uses a file path in the .mtl file for the image whereas the previous versions did not. I'm not sure why the change was made but having a file path in there is throwing off the software that imports the .obj files. I don't see a way for me to set set a preference for it. I find myself falling back to R21 again until I can dig into the possibility of writing something to alter the .mtl file.



  • hi,

    you are talking about the texture, well if the texture path reflect the path you have on the shader using the texture.
    if i'm using a relative path, in the mtl i got : map_Kd ./tex/stock-photo-pair-222411175.jpg

    Or i will have an absolute path. That allow you to retrieve the texture, and make sense to me.

    If it really matter, you can simply parse the .mtl file once you have exported it and change the path. It's pretty easy to parse file with python.

    Cheers,
    Manuel



  • Thanks for looking at it Manuel. Sounds like your getting the same behavior I'm seeing.

    I'll add something to my script to parse it out.

    Thanks Manuel and Cairyn for taking time to help.

    .del