Skip to content

Commit

Permalink
Added Unrestricted Material Export
Browse files Browse the repository at this point in the history
  • Loading branch information
Ingenoire authored Jul 18, 2024
1 parent c805a8c commit ab65966
Showing 1 changed file with 164 additions and 4 deletions.
168 changes: 164 additions & 4 deletions vrm_bakin_addon.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
bl_info = {
"name": "Bakin VRM",
"author": "ingenoire",
"version": (2, 0),
"version": (2, 5),
"blender": (2, 80, 0),
"location": "View3D > Tool Shelf > Run Script Button",
"description": "Adds a button that creates itemhook bones and shape keys for both eye and head movement for VRoid VRM characters, for use with RPG Developer Bakin.",
Expand Down Expand Up @@ -167,8 +167,6 @@ def execute(self, context):
return {'FINISHED'}




class ExportFBXButton(bpy.types.Operator):
bl_idname = "object.export_fbx_8"
bl_label = "Export FBX + DEF (8 Materials)"
Expand Down Expand Up @@ -430,6 +428,162 @@ def export_fbx(self, context, material_count):
f.write('UVRotateAnimation 0.000000\n')
f.write('\n') # Double newline to separate materials


class ExportFBXUnrestrictedMaterialsButton(bpy.types.Operator):
bl_idname = "object.export_fbx_unrestricted"
bl_label = "Export FBX + DEF (Unrestricted Materials)"

def invoke(self, context, event):
# Check if the current file has been saved
if bpy.data.is_saved == False:
# Display a pop-up dialog
return context.window_manager.invoke_props_dialog(self)
else:
# If the file has been saved, proceed with the export
return self.execute(context)

def draw(self, context):
layout = self.layout
layout.label(text="You need to save a blend file of this scene first before we can export the FBX and DEF files!")

def execute(self, context):
try:
self.export_fbx(context)
except Exception as e:
print("Failed to export FBX: ", e)

return {'FINISHED'}

def export_fbx(self, context):
# Get the name of the VRM model and replace spaces with underscores
vrm_model_name = bpy.data.objects['Armature'].data.vrm_addon_extension.vrm1.meta['vrm_name'].replace(' ', '_')

# Define the directory for the exported textures
dirpath = bpy.path.abspath("//" + vrm_model_name + " (Bakin Export)")

# Create the directory if it doesn't exist
os.makedirs(dirpath, exist_ok=True)

# Save all the images as PNGs and rename them
for image in bpy.data.images:
if not image.has_data or image.type != 'IMAGE':
continue
new_image_name = vrm_model_name + "_" + image.name
image.save_render(os.path.join(dirpath, new_image_name + ".png"))

# Define the filepath for the exported FBX file
filepath = os.path.join(dirpath, vrm_model_name + ".fbx")

# Export the entire scene as an FBX with the specified parameters
bpy.ops.export_scene.fbx(
filepath=filepath,
use_selection=False,
global_scale=0.01,
use_mesh_modifiers=False,
add_leaf_bones=False,
use_tspace=True # Enable Tangent Space
)

# Create a .def file with the same name as the VRM model
with open(os.path.join(dirpath, vrm_model_name + ".def"), 'w') as f:
# Iterate over the materials and filter them based on assignment to specific meshes
meshes_of_interest = {'Face', 'Body', 'Hair'}
materials_in_use = set()
mesh_materials = {'Face': set(), 'Body': set(), 'Hair': set()}

for obj in bpy.data.objects:
if obj.type == 'MESH' and obj.name in meshes_of_interest:
for mat_slot in obj.material_slots:
if mat_slot.material:
materials_in_use.add(mat_slot.material)
mesh_materials[obj.name].add(mat_slot.material)

for mat in materials_in_use:
f.write(f'mtl {mat.name}\n')
f.write('shader toon c3a93e68844545618e04eb31f52898c8\n')
f.write('emissiveBlink false\n')
f.write('emissiveBlinkSpeed 0.000000\n')
f.write('emissiveLinkBuildingLight false\n')
f.write('uscrollanim false\n')
f.write('vscrollanim false\n')
f.write('scrollanimspeed 0.000000 0.000000\n')
f.write('uvstepanim false\n')
f.write('uvstepanimparam 1 1 0 1.000000\n')
f.write('sortindex 0\n')
f.write('castshadow true\n')
f.write('cull back\n')
f.write('drawOutline false\n')
f.write('outlineWidth 1.000000\n')
f.write('outlineColor 0.000000 0.000000 0.000000 1.000000\n')
f.write('overrideOutlineSetting false\n')
f.write('distanceFade false\n')
f.write('uvofs 0.000000 0.000000\n')
f.write('uvscl 1.000000 1.000000\n')

if "EYE" in mat.name or "CLOTH" in mat.name or "FACE" in mat.name:
f.write('RenderingType TranslucentWithDepth\n')
f.write('cutOffThreshold 0.005000\n')
else:
f.write('RenderingType Cutoff\n')
f.write('cutOffThreshold 0.600000\n')

# Extract the texture names dynamically
lit_map = vrm_model_name + "_" + self.get_litmap_name(mat) + ".png"
shade_map = vrm_model_name + "_" + self.get_shademap_name(mat) + ".png"
normal_map = vrm_model_name + "_" + self.get_normalmap_name(mat) + ".png"
emi_map = vrm_model_name + "_" + self.get_emimap_name(mat) + ".png"

if mat in mesh_materials['Hair'] or mat in mesh_materials['Body']:
f.write(f'LitMap {lit_map}\n')
f.write(f'ShadeMap {shade_map}\n')
f.write(f'NormalMap {normal_map}\n')
f.write(f'EmiMap {emi_map}\n')
elif mat in mesh_materials['Face']:
f.write(f'LitMap {lit_map}\n')
f.write(f'ShadeMap {shade_map}\n')
f.write(f'NormalMap {normal_map}\n')
f.write(f'EmiMap {emi_map}\n')

f.write('LitColor 1.000000 1.000000 1.000000 1.000000\n')
f.write('ShadeColor 0.600000 0.600000 0.600000 1.000000\n')
f.write('normalscl 1.000000\n')
f.write('toony 0.900000\n')
f.write('shift 0.000000\n')
f.write('LitShaderMixTexMult 0.000000\n')
f.write('lightColorAtt 0.000000\n')
f.write('Emission 1.000000 1.000000 1.000000\n')
f.write('EmissionInt 1.000000\n')
f.write('MCMap MatcapWarp.png\n')
f.write('matCapScale 1.000000\n')
f.write('Rim 0.000000 0.000000 0.000000\n')
f.write('RimInt 1.000000\n')
f.write('RimLightingMix 0.000000\n')
f.write('RimFresnelPow 0.000000\n')
f.write('RimLift 0.000000\n')
f.write('outlineType World\n')
f.write('outlineMaxScale 1.000000\n')
f.write('outlineMixLighting 0.000000\n')
f.write('UVRotateAnimation 0.000000\n')
f.write('\n') # Double newline to separate materials


def get_litmap_name(self, mat):
gltf = mat.vrm_addon_extension.mtoon1
return gltf.pbr_metallic_roughness.base_color_texture.index.source.name

def get_shademap_name(self, mat):
gltf = mat.vrm_addon_extension.mtoon1
mtoon = gltf.extensions.vrmc_materials_mtoon
return mtoon.shade_multiply_texture.index.source.name

def get_normalmap_name(self, mat):
gltf = mat.vrm_addon_extension.mtoon1
return gltf.normal_texture.index.source.name

def get_emimap_name(self, mat):
gltf = mat.vrm_addon_extension.mtoon1
return gltf.emissive_texture.index.source.name

class ImportVRMButton(bpy.types.Operator):
bl_idname = "object.import_vrm"
bl_label = "Import VRM"
Expand Down Expand Up @@ -478,11 +632,15 @@ def draw(self, context):
layout.label(text="Export", icon='EXPORT')
layout.operator("object.export_fbx_8")
layout.operator("object.export_fbx_2")
layout.operator("object.export_fbx_unrestricted")

# Wrapped export description labels
export_descriptions = [
"Choose the corresponding export based on the 'Reduce Materials' option you've used in VRoid Studio.",
"8 materials retains more details on the face such as makeup, while 2 materials has less materials, but loses details."
"8 materials retains more details on the face such as makeup, while 2 materials has less materials, but loses details.",
"Unrestricted materials is for VRMs exported without material reduction, retaining the highest texture quality.",
"Be warned, anytime the model is called in the Bakin Editor, major loading lag will happen.",
"In-game performance seems fine, but keep this to a minimum."
]
for export_desc in export_descriptions:
for line in textwrap.wrap(export_desc, width=40):
Expand All @@ -493,13 +651,15 @@ def register():
bpy.utils.register_class(RunScriptButton)
bpy.utils.register_class(ExportFBXButton)
bpy.utils.register_class(ExportFBXTwoMaterialsButton)
bpy.utils.register_class(ExportFBXUnrestrictedMaterialsButton)
bpy.utils.register_class(RunScriptButtonPanel)

def unregister():
bpy.utils.unregister_class(ImportVRMButton)
bpy.utils.unregister_class(RunScriptButton)
bpy.utils.unregister_class(ExportFBXButton)
bpy.utils.unregister_class(ExportFBXTwoMaterialsButton)
bpy.utils.unregister_class(ExportFBXUnrestrictedMaterialsButton)
bpy.utils.unregister_class(RunScriptButtonPanel)

if __name__ == "__main__":
Expand Down

0 comments on commit ab65966

Please sign in to comment.