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.
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: