forked from uimac/mmdbridge
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Optimized the UV of the ABC file, supporting the import of ABC UVs in Blender versions 3.0 to 4.2. The new UVs have only been tested in Blender, Unity, and UE5. Added blender_mmdbridge_import_30_to_42.py, which supports material imports
- Loading branch information
Uqbc9
committed
Oct 10, 2024
1 parent
fa5d2fa
commit 0cda651
Showing
2 changed files
with
247 additions
and
2 deletions.
There are no files selected for viewing
245 changes: 245 additions & 0 deletions
245
Release/x64/alembic_assign_scripts/blender_mmdbridge_import_30_to_42.py
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,245 @@ | ||
import bpy | ||
import os | ||
from bpy.props import StringProperty | ||
from bpy_extras.io_utils import ImportHelper | ||
|
||
bl_info = { | ||
"name": "MMDBridge Alembic and Material Import", | ||
"author": "Kazuma Hatta", | ||
"version": (1, 0), | ||
"blender": (3, 0, 0), | ||
"location": "File > Import > MMDBridge Alembic and Material (.abc, .mtl)", | ||
"description": "Import Alembic files (.abc) and MMDBridge Materials (.mtl)", | ||
"category": "Import-Export", | ||
} | ||
|
||
class Mtl(): | ||
def __init__(self): | ||
self.name = "" | ||
self.textureMap = "" | ||
self.alphaMap = "" | ||
self.diffuse = [0.7, 0.7, 0.7, 1.0] | ||
self.specular = [0.0, 0.0, 0.0] | ||
self.ambient = [0.0, 0.0, 0.0] | ||
self.trans = 1.0 | ||
self.power = 0.0 | ||
self.lum = 1 | ||
self.faceSize = 0 | ||
self.isAccessory = False | ||
|
||
def import_mtl(path, result, relation): | ||
current = None | ||
export_mode = 0 | ||
|
||
with open(path, 'r', encoding="utf-8") as mtl: | ||
for line in mtl.readlines(): | ||
words = line.split() | ||
if len(words) < 2: | ||
continue | ||
if "newmtl" in words[0]: | ||
if current is not None and current.name != "": | ||
result[current.name] = current | ||
current = Mtl() | ||
current.name = str(words[1]) | ||
|
||
nameSplits = current.name.split("_") | ||
if len(nameSplits) >= 3: | ||
try: | ||
objectNumber = int(nameSplits[1]) | ||
materialNumber = int(nameSplits[2]) | ||
|
||
if objectNumber not in relation.keys(): | ||
relation[objectNumber] = [] | ||
|
||
relation[objectNumber].append(materialNumber) | ||
except ValueError: | ||
print(f"Warning: Unable to parse object and material numbers from {current.name}") | ||
continue | ||
|
||
if "Ka" == words[0]: | ||
current.ambient = [float(words[1]), float(words[2]), float(words[3])] | ||
elif "Kd" == words[0]: | ||
current.diffuse = [float(words[1]), float(words[2]), float(words[3]), current.diffuse[3]] | ||
elif "Ks" == words[0]: | ||
current.specular = [float(words[1]), float(words[2]), float(words[3])] | ||
elif "Ns" == words[0]: | ||
current.power = float(words[1]) | ||
elif "d" == words[0]: | ||
current.trans = float(words[1]) | ||
current.diffuse[3] = current.trans | ||
elif "map_Kd" == words[0]: | ||
current.textureMap = line[line.find(words[1]):line.find(".png")+4] | ||
elif "map_d" == words[0]: | ||
current.alphaMap = line[line.find(words[1]):line.find(".png")+4] | ||
elif "#" == words[0]: | ||
if words[1] == "face_size": | ||
current.faceSize = int(words[2]) | ||
elif words[1] == "is_accessory": | ||
current.isAccessory = True | ||
elif words[1] == "mode": | ||
export_mode = int(words[2]) | ||
|
||
if current is not None and current.name != "": | ||
result[current.name] = current | ||
|
||
for rel in relation.values(): | ||
rel.sort() | ||
|
||
return export_mode | ||
|
||
def match_mesh_and_material(mesh_name, mtl_name): | ||
mesh_stripped = mesh_name.replace("xform_", "") | ||
mtl_stripped = mtl_name.replace("mesh_", "") | ||
return mesh_stripped == mtl_stripped | ||
|
||
def assign_material(base_path, obj, mesh, mtlmat, image_dict): | ||
if mtlmat.name in bpy.data.materials: | ||
mat = bpy.data.materials[mtlmat.name] | ||
else: | ||
mat = bpy.data.materials.new(name=mtlmat.name) | ||
|
||
mat.use_nodes = True | ||
|
||
if mat.name not in mesh.materials: | ||
mesh.materials.append(mat) | ||
|
||
nodes = mat.node_tree.nodes | ||
links = mat.node_tree.links | ||
|
||
nodes.clear() | ||
|
||
bsdf = nodes.new(type='ShaderNodeBsdfPrincipled') | ||
material_output = nodes.new(type='ShaderNodeOutputMaterial') | ||
|
||
bsdf.location = (0, 0) | ||
material_output.location = (400, 0) | ||
|
||
links.new(bsdf.outputs['BSDF'], material_output.inputs['Surface']) | ||
|
||
bsdf.inputs['Base Color'].default_value = mtlmat.diffuse | ||
|
||
# Check Blender version and adjust specular input accordingly | ||
if bpy.app.version >= (4, 0, 0): | ||
# For Blender 4.0 and above | ||
if 'Specular IOR Level' in bsdf.inputs: | ||
bsdf.inputs['Specular IOR Level'].default_value = sum(mtlmat.specular) / 3 | ||
else: | ||
# For Blender 3.6 and below | ||
if 'Specular' in bsdf.inputs: | ||
bsdf.inputs['Specular'].default_value = sum(mtlmat.specular) / 3 | ||
|
||
bsdf.inputs['Roughness'].default_value = 1.0 - (mtlmat.power / 100) | ||
|
||
if mtlmat.textureMap: | ||
tex_image_node = nodes.new('ShaderNodeTexImage') | ||
tex_image_node.location = (-300, 0) | ||
|
||
texture_file_path = os.path.normpath(bpy.path.abspath(os.path.join(base_path, mtlmat.textureMap))) | ||
|
||
if texture_file_path in image_dict: | ||
tex_image_node.image = image_dict[texture_file_path] | ||
elif os.path.exists(texture_file_path): | ||
tex_image_node.image = bpy.data.images.load(texture_file_path) | ||
image_dict[texture_file_path] = tex_image_node.image | ||
|
||
if tex_image_node.image: | ||
links.new(tex_image_node.outputs['Color'], bsdf.inputs['Base Color']) | ||
|
||
if mtlmat.trans < 1.0: | ||
bsdf.inputs['Alpha'].default_value = mtlmat.trans | ||
mat.blend_method = 'BLEND' | ||
|
||
def import_mmdbridge_material(filepath, context): | ||
image_dict = {} | ||
mtlDict = {} | ||
relationDict = {} | ||
import_mtl(filepath, mtlDict, relationDict) | ||
|
||
base_path, file_name = os.path.split(filepath) | ||
|
||
for key, mtlmat in mtlDict.items(): | ||
for obj in bpy.data.objects: | ||
if obj.type == "MESH" and obj.data is not None: | ||
if match_mesh_and_material(obj.name, key): | ||
assign_material(base_path, obj, obj.data, mtlmat, image_dict) | ||
print(f"Assigned material {mtlmat.name} to object {obj.name}") | ||
|
||
def import_alembic_and_mtl(filepath, context): | ||
# Create a new collection called "abc" | ||
abc_collection = bpy.data.collections.new("abc") | ||
bpy.context.scene.collection.children.link(abc_collection) | ||
|
||
# Import Alembic file (.abc) | ||
bpy.ops.wm.alembic_import(filepath=filepath) | ||
|
||
# Move all newly imported objects to the "abc" collection | ||
for obj in bpy.context.selected_objects: | ||
for collection in obj.users_collection: | ||
collection.objects.unlink(obj) | ||
abc_collection.objects.link(obj) | ||
|
||
# Record original materials | ||
original_materials = {} | ||
for obj in abc_collection.objects: | ||
if obj.type == 'MESH': | ||
original_materials[obj.name] = [slot.material.name if slot.material else None for slot in obj.material_slots] | ||
|
||
# Clear all materials | ||
for obj in abc_collection.objects: | ||
if obj.type == 'MESH': | ||
obj.data.materials.clear() | ||
|
||
# Find .mtl file in the same directory | ||
base_path, file_name = os.path.split(filepath) | ||
mtl_file = os.path.join(base_path, file_name.replace(".abc", ".mtl")) | ||
|
||
if os.path.exists(mtl_file): | ||
# If corresponding .mtl file is found, import materials | ||
import_mmdbridge_material(mtl_file, context) | ||
else: | ||
print("Warning: .mtl file not found for Alembic file.") | ||
|
||
# Update MTL file | ||
update_mtl_file(mtl_file, original_materials, abc_collection.objects) | ||
|
||
def update_mtl_file(mtl_file, original_materials, objects): | ||
with open(mtl_file, 'a') as f: | ||
f.write("\n# Original material information\n") | ||
for obj_name, materials in original_materials.items(): | ||
f.write(f"# Object: {obj_name}\n") | ||
for i, mat_name in enumerate(materials): | ||
if mat_name: | ||
f.write(f"# Material {i}: {mat_name}\n") | ||
|
||
f.write("\n# New material assignments\n") | ||
for obj in objects: | ||
if obj.type == 'MESH': | ||
f.write(f"# Object: {obj.name}\n") | ||
for i, slot in enumerate(obj.material_slots): | ||
if slot.material: | ||
f.write(f"# Material {i}: {slot.material.name}\n") | ||
|
||
class MMDBridgeAlembicImportOperator(bpy.types.Operator, ImportHelper): | ||
bl_idname = "import_scene.mmdbridge_alembic_material" | ||
bl_label = "MMDBridge Alembic and Material Importer (.abc, .mtl)" | ||
|
||
filename_ext = ".abc" | ||
filter_glob: StringProperty(default="*.abc", options={'HIDDEN'}) | ||
|
||
def execute(self, context): | ||
import_alembic_and_mtl(self.filepath, context) | ||
return {'FINISHED'} | ||
|
||
def menu_func_import(self, context): | ||
self.layout.operator(MMDBridgeAlembicImportOperator.bl_idname, text="MMDBridge Alembic and Material (.abc, .mtl)") | ||
|
||
def register(): | ||
bpy.utils.register_class(MMDBridgeAlembicImportOperator) | ||
bpy.types.TOPBAR_MT_file_import.append(menu_func_import) | ||
|
||
def unregister(): | ||
bpy.utils.unregister_class(MMDBridgeAlembicImportOperator) | ||
bpy.types.TOPBAR_MT_file_import.remove(menu_func_import) | ||
|
||
if __name__ == "__main__": | ||
register() |
This file contains 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