• Which the release of FS2020 we see an explosition of activity on the forun and of course we are very happy to see this. But having all questions about FS2020 in one forum becomes a bit messy. So therefore we would like to ask you all to use the following guidelines when posting your questions:

    • Tag FS2020 specific questions with the MSFS2020 tag.
    • Questions about making 3D assets can be posted in the 3D asset design forum. Either post them in the subforum of the modelling tool you use or in the general forum if they are general.
    • Questions about aircraft design can be posted in the Aircraft design forum
    • Questions about airport design can be posted in the FS2020 airport design forum. Once airport development tools have been updated for FS2020 you can post tool speciifc questions in the subforums of those tools as well of course.
    • Questions about terrain design can be posted in the FS2020 terrain design forum.
    • Questions about SimConnect can be posted in the SimConnect forum.

    Any other question that is not specific to an aspect of development or tool can be posted in the General chat forum.

    By following these guidelines we make sure that the forums remain easy to read for everybody and also that the right people can find your post to answer it.

MSFS Blender script to create meshes with a single material (for Armor Paint, clean UV maps, etc.)

Messages
94
While creating textures for a MSFS aircraft, you may encounter some difficulty when there are multiple materials assigned to a mesh. Since you can only have one texture sheet per material, if you were to export a mesh like this to use in 3D texturing software such as Armor Paint or Quixel Mixer, you may find the results less than satisfying.

I needed an easy way to separate all the parts of a mesh that are assigned to a material to a new mesh. You can do this easily enough in edit mode using the materials property panel to click the material and hit select then separate the faces. But what if you needed to do this for more than one mesh? That is a lot of manual work.

So here is a Blender Python script that can find all the faces assigned to each material and duplicate them out to new meshes each using a single material. Then, all you need to do is export the meshes you need and work on the texture sheets with ease. The script is tested with Blender 2.93 but should work from 2.8 to 3.0.

DISCLAIMER!
This script while it duplicates the existing meshes, does alter the Blender file, so only work on a copy. Also, there may be bugs.

To use the script, switch to the scripting workspace or open a text editor pane. Click the new text data-block and paste in all of the script code. Then just run the script.

If you get errors, it may be due to objects that are hidden. It may be more reliable to select all the objects you want to affect before running the script. Avoid running multiple times. Just revert the file and try again.

This could be made into a plugin, but I think it's nice to see the code and edit if you need different behavior. Python is not super difficult to pick up and Blender's API is very powerful, so you may think of other ideas.

Feel free to describe any special cases you may have that could benefit from some automation and I can try to assist.

Code:
import bpy
import bmesh

# Blender script to create new objects that contain only a single material
# This is useful for 3D texturing software such as Armor Paint which only supports one texture sheet
# The new objects will be created in a new collection
# The new objects will not have any parenting or animations, just the meshes
# Avoid running the script multiple times as it can duplicate the new objects as well

# ** SETTINGS:
# Set join_objects = True if you need the new objects to be combined into a single mesh per material
join_objects = False

# duplicate only selected objects or all objects in the scene
objs = bpy.context.selected_objects
if(len(objs) == 0):
    objs = bpy.context.scene.objects


collections = bpy.context.scene.collection.children
# create a new collection - all new objects will be added   
new_collection = bpy.data.collections.get("New Objects")
if( new_collection is None ):
    new_collection = bpy.data.collections.new("New Objects")
    collections.link(new_collection)

mat_collections = {}
            
for ob in objs:
    if ob.type == 'MESH':

        # for each material on the mesh, create a new duplicate
        for mat in enumerate(ob.data.materials):
            # get a ref to the collection for this material
            key = mat[1].name + '_mat'
            #collection = bpy.data.collections.get(mat[1].name + '_mat')
            if(key in mat_collections):
                collection = mat_collections[key]
            else :
                # this is the first mesh with this material, so create a new collection
                collection = bpy.data.collections.new(key)
                mat_collections[collection.name] = collection
                new_collection.children.link(collection)

            # make the duplicate
            ob_temp = ob.copy()
            ob_temp.data = ob.data.copy()


            bm = bmesh.new()
            bm.from_mesh( ob_temp.data )
            bm.faces.ensure_lookup_table()
            # find all faces not set to this material 
            faces = [f for f in bm.faces if f.material_index != mat[0]] 

            if(len(faces)):
                # delete the faces
                bmesh.ops.delete( bm, geom = faces, context = 'FACES' )

            # rewrite the new mesh back to the object
            bm.to_mesh( ob_temp.data )

            # clear all materials and append only the target material
            ob_temp.data.materials.clear()
            ob_temp.data.materials.append(mat[1])

            # populate the duplicate object in the collection for this material
            collection.objects.link(ob_temp)

if(join_objects):
    bpy.ops.object.select_all(action='DESELECT')
    for key in mat_collections:
        objs = mat_collections[key].all_objects
        if(len(objs)):
            for ob in objs:
                ob.select_set(True)
            bpy.context.view_layer.objects.active = objs[0]
            bpy.ops.object.join()
            objs[0].name = key
            bpy.ops.object.select_all(action='DESELECT')
 
Last edited:
Here are some screenshots in action. Prior to running the script, I have the Asobo C152 LOD1 in the Collection. After running, a new collection (New Objects) is created and within that, a collection for each material used in the original meshes. By default, it makes copies of each object, but you can set join_objects=True to have it join the meshes into a single mesh per material.
mesh_script_1.png
mesh_script_2.png


Using join_objects = True
mesh_script_3.png
 
Back
Top