Created
April 6, 2024 22:52
-
-
Save SuperFromND/c909b99a3f0c61dd4b096cbed732042b to your computer and use it in GitHub Desktop.
Blender Python: Cleanup Modnao Model Imports
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Script by SuperFromND. Last updated 4/6/2024 | |
# This was written to clean up exported model data from ModNao, a Sega Dreamcast model ripping website. | |
# License: CC0/WTFPL/Unlicense/whatever else you prefer to mean "public domain". | |
# No warranty or liability, do whatever you want, yada yada etc. | |
import bpy | |
# ======================================================== | |
# Change this value if you're getting weird results from the merge-by-distance. | |
# Higher values = more aggressive merging. | |
threshold_value = 0.01 | |
# ======================================================== | |
# holds names of image textures, used to check dupe materials | |
image_mats = [] | |
# reset selected objects | |
bpy.ops.object.select_all(action="DESELECT") | |
print("Cleaning up materials...") | |
for obj in bpy.data.objects: | |
if obj.type == "MESH" and not obj.active_material == None: | |
for item in obj.material_slots: | |
mat = bpy.data.materials[item.name] | |
if mat.use_nodes: | |
mat.blend_method = "CLIP" | |
# first pass to just remove anything that's not necessary, i.e. we keep images and material out ONLY | |
for node in mat.node_tree.nodes: | |
if node.type == "TEX_IMAGE" or node.type == "OUTPUT_MATERIAL": | |
# do nothing, but we keep this node | |
pass | |
else: | |
mat.node_tree.nodes.remove(node) | |
# second pass to re-assemble the nodes into a normal material | |
pbsdf = mat.node_tree.nodes.new("ShaderNodeBsdfPrincipled") | |
matoutput = mat.node_tree.nodes.get("Material Output") | |
for node in mat.node_tree.nodes: | |
if node.type == "TEX_IMAGE": | |
links = mat.node_tree.links | |
links.new(node.outputs[0], pbsdf.inputs["Base Color"]) | |
links.new(node.outputs[1], pbsdf.inputs["Alpha"]) | |
links.new(pbsdf.outputs["BSDF"], matoutput.inputs["Surface"]) | |
# checks for duplicate materials based on image texture name | |
if node.image.name in image_mats: | |
for mat2 in bpy.data.materials: | |
if mat2.name == node.image.name: | |
obj.data.materials[0] = mat2 | |
else: | |
print( | |
"Unique image detected, creating material: " | |
+ node.image.name | |
) | |
mat.name = node.image.name | |
image_mats.append(node.image.name) | |
# now it's time to cleanup the mesh data | |
# loop over top-level objects | |
print("Cleaning up meshes...") | |
root_objs = (obj for obj in bpy.context.scene.objects if not obj.parent) | |
for obj in root_objs: | |
if obj.children == None: | |
continue | |
else: | |
for child in obj.children: | |
# this should be all the "object" nodes, which contain more empties and meshes respectively | |
obj_name = child.name | |
mesh_list = [] | |
# fetch all meshes (assuming structure is consistent PLEASE BE CONSISTENT AAAAA) | |
for obj_empty in child.children: | |
for obj_mesh in obj_empty.children: | |
mesh_list.append(obj_mesh) | |
for objz in mesh_list: | |
objz.select_set(True) | |
# clear their parents but keep transforms | |
bpy.ops.object.parent_clear(type="CLEAR_KEEP_TRANSFORM") | |
# check to see if there's meshes | |
if len(mesh_list) == 0: | |
continue | |
else: | |
print("Merging " + obj_name + "'s meshes...") | |
# select first object in the mesh list and then join to that | |
bpy.context.view_layer.objects.active = mesh_list[0] | |
bpy.ops.object.join() | |
# deselect all selected objects so it doesnt just make one huge mesh | |
bpy.ops.object.select_all(action="DESELECT") | |
# final bit of cleanup: apply a merge-by-distance on every object and delete any remaining empties | |
for obj in bpy.data.objects: | |
if obj.type == "EMPTY": | |
bpy.data.objects.remove(obj) | |
elif obj.type == "MESH": | |
print("Applying merge-by-distance to " + obj.name + "...") | |
obj.select_set(True) | |
bpy.ops.object.mode_set(mode="EDIT") | |
bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type="VERT") | |
bpy.ops.mesh.select_all(action="SELECT") | |
bpy.ops.mesh.remove_doubles( | |
threshold=threshold_value, | |
use_unselected=False, | |
use_sharp_edge_from_normals=False, | |
) | |
obj.select_set(False) | |
# set back to object mode | |
bpy.ops.object.mode_set(mode="OBJECT") | |
print("Done. Have fun!") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment