Created
July 6, 2024 20:43
-
-
Save DaSalba/87dff887d88f6abc4bb7c22a902ba369 to your computer and use it in GitHub Desktop.
Blender script that imports an OBJ (3D model) exported from Alone in the Dark room viewer application (https://github.com/tigrouind/AITD-roomviewer), and fixes the materials so it can be properly rendered/edited.
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
# Blender script that imports an OBJ (3D model) exported from Alone in the Dark | |
# room viewer application (https://github.com/tigrouind/AITD-roomviewer), and | |
# fixes the materials so it can be properly rendered/edited. | |
# NOTE: the file "noise.png" is needed (get it from the Assets/Materials/Model/ | |
# directory of the mentioned application). Put it in the same folder as this | |
# script so Blender can find it. It will also have to be present in the same | |
# folder as the output .blend file, unless you manually change the path or pack | |
# (embed) it into the .blend. | |
# Usage: | |
# blender --background --python <script.py> -- -f <file.obj> [-o <file.blend>] | |
import bpy | |
import sys | |
import os | |
import argparse | |
USAGE_TEXT = "Usage:\n\tblender --background --python " + __file__ + " -- -f <file.obj> [-o <file.blend>]" | |
SUPPORTED_MATERIALS = {"shadeless", "noise", "glass", "metal_horizontal", "metal_vertical"} | |
def main(): | |
argv = sys.argv | |
# "--" (with spaces before and after) separates the Blender arguments from | |
# the Python script ones. We recover these arguments (if there are any). | |
if "--" not in argv: | |
print(USAGE_TEXT) | |
return | |
else: | |
argv = argv[argv.index("--") + 1:] | |
# Use the handy ArgumentParser to do the job for us. | |
argParser = argparse.ArgumentParser(description=USAGE_TEXT) | |
argParser.add_argument("-f", "--file", dest="inFile", type=str, required=True, help="The .obj file that will be read.") | |
argParser.add_argument("-o", "--output", dest="outFile", type=str, required=False, help="The .blend file that will be saved (optional).", default="untitled.blend") | |
args = argParser.parse_args(argv) | |
if not args.inFile: | |
print(USAGE_TEXT) | |
return | |
# Part 1: clear the scene (remove all objects). | |
bpy.ops.wm.read_factory_settings(use_empty=True) | |
# Part 2: try to import the .obj file into Blender. | |
# Optional interesting arguments for obj_import(): | |
# global_scale (default 1.0) | |
# clamp_size (default 0.0) | |
# forward_axis (default "NEGATIVE_Z") | |
# up_axis (default "Y") | |
# Check reference at https://docs.blender.org/api/current/bpy.ops.wm.html#bpy.ops.wm.obj_import | |
result = str(bpy.ops.wm.obj_import(filepath=args.inFile, forward_axis="Z")) | |
result = result.strip() # Remove leading and trailing spaces. | |
if result != "{'FINISHED'}": | |
print("There was an error trying to import the chosen OBJ. Check that it is a valid file.") | |
return | |
# Part 3: fix the materials. | |
# Check reference at https://github.com/tigrouind/AITD-roomviewer/blob/master/README.md#obj-export | |
for material in bpy.data.materials: | |
# We will only fix these materials we're interested in. We will avoid | |
# messing with others (in case there's any). | |
if material.name not in SUPPORTED_MATERIALS: | |
continue | |
material.use_nodes = True | |
material.node_tree.nodes.clear() | |
# SHADELESS (standard, plain color with no effects). | |
if material.name == "shadeless": | |
nodeVertexColor = material.node_tree.nodes.new("ShaderNodeVertexColor") | |
nodeVertexColor.location = (0, 0) | |
nodeOutput = material.node_tree.nodes.new("ShaderNodeOutputMaterial") | |
nodeOutput.location = (200, 0) | |
material.node_tree.links.new(nodeOutput.inputs["Surface"], nodeVertexColor.outputs["Color"]) | |
# NOISE (noisy pattern mixed with the plain color). | |
elif material.name == "noise": | |
nodeVertexColor = material.node_tree.nodes.new("ShaderNodeVertexColor") | |
nodeVertexColor.location = (100, 150) | |
nodeImageTexture = material.node_tree.nodes.new("ShaderNodeTexImage") | |
bpy.ops.image.open(directory=os.getcwd(), files=[{"name": "noise.png"}], relative_path=True) | |
noiseImage = bpy.data.images["noise.png"] | |
nodeImageTexture.image = noiseImage | |
nodeImageTexture.location = (0, 0) | |
nodeMix = material.node_tree.nodes.new("ShaderNodeMix") | |
nodeMix.data_type = "RGBA" | |
nodeMix.blend_type = "MULTIPLY" | |
nodeMix.clamp_factor = True | |
nodeMix.inputs["Factor"].default_value = 0.5 | |
nodeMix.location = (350, 150) | |
nodeOutput = material.node_tree.nodes.new("ShaderNodeOutputMaterial") | |
nodeOutput.location = (550, 150) | |
material.node_tree.links.new(nodeMix.inputs["A"], nodeVertexColor.outputs["Color"]) | |
material.node_tree.links.new(nodeMix.inputs["B"], nodeImageTexture.outputs["Color"]) | |
material.node_tree.links.new(nodeOutput.inputs["Surface"], nodeMix.outputs["Result"]) | |
# GLASS (surfaces with simple transparency and no refraction, where | |
# white -> fully transparent | |
# black -> fully opaque | |
# and all other colors having an intermediate degree of transparency). | |
elif material.name == "glass": | |
nodeVertexColor = material.node_tree.nodes.new("ShaderNodeVertexColor") | |
nodeVertexColor.location = (0, 0) | |
nodeShader = material.node_tree.nodes.new("ShaderNodeBsdfTransparent") | |
nodeShader.location = (200, 0) | |
nodeOutput = material.node_tree.nodes.new("ShaderNodeOutputMaterial") | |
nodeOutput.location = (400, 0) | |
material.node_tree.links.new(nodeShader.inputs["Color"], nodeVertexColor.outputs["Color"]) | |
material.node_tree.links.new(nodeOutput.inputs["Surface"], nodeShader.outputs["BSDF"]) | |
material.blend_method = "BLEND" | |
# METAL_HORIZONTAL / METAL_VERTICAL (metallic surfaces, with a gradient). | |
elif material.name == "metal_horizontal" or material.name == "metal_vertical": | |
nodeTexCoord = material.node_tree.nodes.new("ShaderNodeTexCoord") | |
nodeTexCoord.location = (0, 0) | |
nodeSeparateXYZ = material.node_tree.nodes.new("ShaderNodeSeparateXYZ") | |
nodeSeparateXYZ.location = (200, 0) | |
nodeMultiply = material.node_tree.nodes.new("ShaderNodeMath") | |
nodeMultiply.operation = "MULTIPLY" | |
nodeMultiply.inputs[1].default_value = 5.0 | |
nodeMultiply.location = (400, 0) | |
nodePingPong = material.node_tree.nodes.new("ShaderNodeMath") | |
nodePingPong.operation = "PINGPONG" | |
nodePingPong.inputs[1].default_value = 1.0 | |
nodePingPong.location = (600, 0) | |
nodeAdd = material.node_tree.nodes.new("ShaderNodeMath") | |
nodeAdd.operation = "ADD" | |
nodeAdd.inputs[1].default_value = 0.5 | |
nodeAdd.location = (800, 0) | |
nodeVertexColor = material.node_tree.nodes.new("ShaderNodeVertexColor") | |
nodeVertexColor.location = (800, 150) | |
nodeGamma = material.node_tree.nodes.new("ShaderNodeGamma") | |
nodeGamma.location = (1000, 100) | |
nodeOutput = material.node_tree.nodes.new("ShaderNodeOutputMaterial") | |
nodeOutput.location = (1200, 100) | |
material.node_tree.links.new(nodeSeparateXYZ.inputs["Vector"], nodeTexCoord.outputs["Window"]) | |
if material.name == "metal_horizontal": | |
material.node_tree.links.new(nodeMultiply.inputs[0], nodeSeparateXYZ.outputs["X"]) | |
elif material.name == "metal_vertical": | |
material.node_tree.links.new(nodeMultiply.inputs[0], nodeSeparateXYZ.outputs["Y"]) | |
material.node_tree.links.new(nodePingPong.inputs[0], nodeMultiply.outputs["Value"]) | |
material.node_tree.links.new(nodeAdd.inputs[0], nodePingPong.outputs["Value"]) | |
material.node_tree.links.new(nodeGamma.inputs["Color"], nodeVertexColor.outputs["Color"]) | |
material.node_tree.links.new(nodeGamma.inputs["Gamma"], nodeAdd.outputs["Value"]) | |
material.node_tree.links.new(nodeOutput.inputs["Surface"], nodeGamma.outputs["Color"]) | |
# Part 4: set the 3D viewport shading mode to "material preview", to allow | |
# a quick check upon opening the .blend file without having to do anything. | |
for screenArea in bpy.context.screen.areas: | |
if screenArea.type == "VIEW_3D": | |
screenArea.spaces[0].shading.type = "MATERIAL" | |
# Part 5: save the .blend result file. | |
bpy.ops.wm.save_mainfile(filepath=args.outFile) | |
# Blender does not allow to set relative paths before saving the file. To | |
# prevent errors with "noise.png" not being found, we do it now and resave. | |
bpy.ops.file.make_paths_relative() | |
bpy.ops.wm.save_mainfile() | |
if __name__ == "__main__": | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
In order to batch convert many files, you can use the following PowerShell script (e.g. save it as
convert_aitd_obj.ps1
):