Home Coding, Scripting, Shaders

Maya to Unreal ISM export/import tool

grand marshal polycounter
Offline / Send Message
Alex_J grand marshal polycounter
Development of a tool to send static mesh and ISMs from Maya to Unreal.  Once it's complete I'll move the finished code up top for easy answer in future.


Maya side python code so far (by the way, code tags don't seem to format properly for me):
import maya.cmds as cmds
import csv



selection = cmds.ls(sl=True)



col = ['','actorName','tx', 'ty', 'tz', 'rx', 'ry', 'rz', 'sx', 'sy', 'sz']


with open("C:\ISMExporter\data.csv", 'w') as f:
        writer = csv.writer(f)
        writer.writerow(col)


        for idx, obj in enumerate(selection):
            data = []
           
            data.append(idx)    
            data.append(idx)      
           
            actorName = obj.split("_")[0]
            data.append(actorName)

           
            data.append(cmds.getAttr(obj + '.translateX'))
            data.append(cmds.getAttr(obj + '.translateY'))
            data.append(cmds.getAttr(obj + '.translateZ'))

            data.append(cmds.getAttr(obj + '.rotateX'))
            data.append(cmds.getAttr(obj + '.rotateY'))
            data.append(cmds.getAttr(obj + '.rotateZ'))

            data.append(cmds.getAttr(obj + '.scaleX'))
            data.append(cmds.getAttr(obj + '.scaleY'))
            data.append(cmds.getAttr(obj + '.scaleZ'))



            writer.writerow(data)
            del data

Mostly good but results with a blank row every other row:


Trying to figure out where the mystery rows come from.


Unreal Side code so far:


This is only basic test to figure out how we can get .uassets from content browser by name. This proves that it can be done and the asset loaded from construction script. Next I will need to parse the data table, but is easy. The part I'm still unsure about is how to handle reimport, but just going one problem at a time.


Replies

  • Alex_J
    Options
    Offline / Send Message
    Alex_J grand marshal polycounter
    Okay, is working, but now will need to think about workflow and what it needs to make things efficient and simple.

    Here is breakdown of the basic code though:

    For maya python script, same as before but newline is needed to prevent that empty row issue (I don't know why, it just works):
    import maya.cmds as cmds

    import csv



    selection = cmds.ls(sl=True)



    col = ['','actorName','tx', 'ty', 'tz', 'rx', 'ry', 'rz', 'sx', 'sy', 'sz']



    with open("C:\ISMExporter\data.csv", 'w', newline='') as f:

    writer = csv.writer(f)

    writer.writerow(col)



    for idx, obj in enumerate(selection):

    data = []

    data.append(idx)

    actorName = obj.split("_")[0]

    data.append(actorName)

    data.append(cmds.getAttr(obj + '.translateX'))

    data.append(cmds.getAttr(obj + '.translateY'))

    data.append(cmds.getAttr(obj + '.translateZ'))



    data.append(cmds.getAttr(obj + '.rotateX'))

    data.append(cmds.getAttr(obj + '.rotateY'))

    data.append(cmds.getAttr(obj + '.rotateZ'))



    data.append(cmds.getAttr(obj + '.scaleX'))

    data.append(cmds.getAttr(obj + '.scaleY'))

    data.append(cmds.getAttr(obj + '.scaleZ'))



    writer.writerow(data)

    del data

    note that you have to select instances directly, it won't work by selecting a group. I'm sure this could be adjust in code to make things more user friendly. May extend later if it becomes annoying.

    In unreal, a few things are needed:
    Struct that matches the columns in the csv:

    Note that blueprint structs are brittle. If you change the contents of one that already had things referencing it, unreal may crash and sometimes can corrupt assets. Anytime you mess with these back shit up like you are save scumming for your life. If its not too big a hassle, usually better to just make a fresh struct instead of edit an existing one. C++ structs seem more stable.

    Then you can make a data table from this, and link the csv that maya outputs.

    Then make a blueprint actor. In construction script:


    Now the actor will be updated on construction time from the data table. So you can just reimport the data table.

    For workflow this means that you must have static meshes in the directory with a matching substring. There is room here for some better fail safes.

    I also need to look into a way to "save" an individual data set. This could be like a single building, or a group of buildings... doesn't really matter, just need a way to identify some sort of arbitrary grouping and be able to access it / update it.

    I wonder if perhaps making a unique csv with name might be best way to do that? Then the importer actor can just select a csv per instance. Will think on it more and update after testing.


    note: one thing about the content browser search is that it has to be forward slash and rather than /content/ it has to be /Game/. Otherwise it just does nothing. So you can't just right click and save a content path, you have to type it manually like this. 
    No idea why because nobody ever writes this shit down. I think programmers all hate each other or something.



    I'm only making quick notes for reference here. If you want to learn a little more about why things work, here is tutorial I based most of the code from:




  • Alex_J
    Options
    Offline / Send Message
    Alex_J grand marshal polycounter
    Okay, here is what I call a production ready version. Just a tiny bit more code but now we have an easy way to iterate between maya and unreal with minimal clicking.

    Extension to the python makes it so that we select a group in maya, and that group name becomes the csv file name. So that way you name a building, or a group of buildings, that you want to export and it gets its own data table. Then we look within the group and do same as before with the children objects.
    import maya.cmds as cmds
    import csv


    selection = cmds.ls(sl=True)

    # Ensure that a group is selected
    if not selection:
        cmds.warning("Please select a group.")
    else:
        group_name = selection[0]  # Assuming only one group is selected, use the first one

    # Validate that the selected object is a group
        if not cmds.nodeType(group_name) == 'transform':
            cmds.warning("Selected object is not a group. Please select a group.")


        else:
            col = ['','actorName','tx', 'ty', 'tz', 'rx', 'ry', 'rz', 'sx', 'sy', 'sz']

     # Derive CSV filename from group name
            csv_filename = "C:/ISMExporter/{}.csv".format(group_name)



        with open(csv_filename, 'w', newline='') as f:
            writer = csv.writer(f)
            writer.writerow(col)

     # Get the children objects within the group
            objects_in_group = cmds.listRelatives(group_name, ad=True, type='transform')

            for idx, obj in enumerate(objects_in_group):
                data = []
               
                data.append(idx)      
               
               # Extract the actor name from the object name
                actorName = obj.split("_")[0]
                data.append(actorName)
               
                data.append(cmds.getAttr(obj + '.translateX'))
                data.append(cmds.getAttr(obj + '.translateY'))
                data.append(cmds.getAttr(obj + '.translateZ'))


                data.append(cmds.getAttr(obj + '.rotateX'))
                data.append(cmds.getAttr(obj + '.rotateY'))
                data.append(cmds.getAttr(obj + '.rotateZ'))


                data.append(cmds.getAttr(obj + '.scaleX'))
                data.append(cmds.getAttr(obj + '.scaleY'))
                data.append(cmds.getAttr(obj + '.scaleZ'))


                writer.writerow(data)
                del data
           
       
       


    In unreal, the only additional thing to do is make the Data Table an instance editable variable, this way you can have each one of these "ISM Loader" actors represent some export group. Again, that can be an individual model, or a whole neighborhood, or even an entire scene if it wasn't too large. It's just a balance of how many individual exports you want to make, versus how granular your iterative changes are likely to be.



    So the workflow is pretty simple:

    • first export you static mesh kit to unreal as regular static meshes
    • Group some group of these that you want to export and fire the script (they actually dont have to be instances, can just be duplicates. The only thing that's actually exported is the transform info)
    • In unreal duplicate the data table and using Reimport Using New File to make link to any new csv's
    • make a new actor in the level for each csv and link the data table using instance editable variable

    Now for iterations you can just overwrite the csv's or create new versions by changing the group name if you want.


  • Alex_J
    Options
    Offline / Send Message
    Alex_J grand marshal polycounter
    One weird issue:

    Values in the data table are good. Some instances have correct orientation, others are off. I can't figure out any pattern to this. It can't be a simple right / left handed conversion because a lot of instances are correct... others just arent.

    I have no clue.

    The base static mesh orientation is good.

    The rotation values in the data table match what exist in maya.

    Somehow when those same values are applied in unreal, they dont work... sometimes. Other times, they do.

    Issue seems to only exist when there is rotation of more than one axis. If just one axis is rotated, it works. But if X, Y, and Z have values, then it ends up wrong. So I think that they don't get added correctly somehow.

Sign In or Register to comment.