Today I created an addon for Blender version 4.3.2 that allows me to select two folders to render 3D objects from the first folder and add 512px samples with these renderings to the second folder.
This is what the addon installed in Blender 3D looks like:
Here's what the source code of this addon looks like:
bl_info = {
"name": "3D File Renderer by catafest",
"blender": (4, 3, 2),
"category": "Object",
"author": "Catalin George Festila\n"
"nicknames: catafest and mythcat\n"
"country: Romania\n"
"mail: catafest [at] yahoo.com",
"version": (1, 0),
"blender": (2, 80, 0),
"location": "View3D > UI > 3D File Renderer",
"description": "Addon for rendering 3D files",
"warning": "",
"doc_url": "https://github.com/catafest",
"tracker_url": "https://github.com/catafest/issues",
"support": "COMMUNITY",
}
import bpy
import os
class FileRendererProperties(bpy.types.PropertyGroup):
input_directory: bpy.props.StringProperty(
name="Input Directory",
description="Directory containing 3D files",
default="",
maxlen=1024,
subtype='DIR_PATH'
)
output_directory: bpy.props.StringProperty(
name="Output Directory",
description="Directory to save rendered images",
default="",
maxlen=1024,
subtype='DIR_PATH'
)
class RENDER_OT_files(bpy.types.Operator):
bl_idname = "render.files"
bl_label = "Start render 3D files for all files"
def execute(self, context):
input_directory = context.scene.file_renderer_props.input_directory
output_directory = context.scene.file_renderer_props.output_directory
if not input_directory or not output_directory:
self.report({'ERROR'}, "Input and Output directories must be set.")
return {'CANCELLED'}
if not os.path.exists(output_directory):
os.makedirs(output_directory)
def render_file(file_path, output_path):
try:
bpy.ops.wm.read_factory_settings(use_empty=True)
ext = os.path.splitext(file_path)[1].lower()
if ext == ".glb":
bpy.ops.import_scene.gltf(filepath=file_path)
elif ext == ".obj":
bpy.ops.import_scene.obj(filepath=file_path)
elif ext == ".fbx":
bpy.ops.import_scene.fbx(filepath=file_path)
else:
raise ValueError("Unsupported file format")
bpy.ops.object.camera_add(location=(0, -3, 1.5), rotation=(1.1, 0, 0))
camera = bpy.context.scene.objects['Camera']
bpy.context.scene.camera = camera
bpy.ops.object.light_add(type='POINT', location=(0, -3, 3))
light = bpy.context.view_layer.objects.active
light.data.energy = 1000
bpy.context.scene.render.resolution_x = 512
bpy.context.scene.render.resolution_y = 512
bpy.context.scene.render.filepath = output_path
bpy.ops.render.render(write_still=True)
except Exception as e:
# Generate a red image with "BAD FILE" text using Blender
bpy.ops.wm.read_factory_settings(use_empty=True)
bpy.ops.mesh.primitive_plane_add(size=2)
plane = bpy.context.active_object
mat = bpy.data.materials.new(name="BadFileMaterial")
mat.diffuse_color = (1, 0, 0, 1) # Red
plane.data.materials.append(mat)
# Add "BAD FILE" text
bpy.ops.object.text_add(location=(0, 0, 0.1))
text_obj = bpy.context.active_object
text_obj.data.body = "BAD FILE"
text_obj.data.size = 0.5
text_obj.data.align_x = 'CENTER'
text_obj.data.align_y = 'CENTER'
text_obj.rotation_euler = (1.5708, 0, 0)
# Set camera and light
bpy.ops.object.camera_add(location=(0, -3, 1.5), rotation=(1.1, 0, 0))
camera = bpy.context.scene.objects['Camera']
bpy.context.scene.camera = camera
bpy.ops.object.light_add(type='POINT', location=(0, -3, 3))
light = bpy.context.view_layer.objects.active
light.data.energy = 1000
bpy.context.scene.render.resolution_x = 512
bpy.context.scene.render.resolution_y = 512
bpy.context.scene.render.filepath = output_path
bpy.ops.render.render(write_still=True)
for filename in os.listdir(input_directory):
if filename.lower().endswith((".glb", ".obj", ".fbx")):
file_path = os.path.join(input_directory, filename)
output_path = os.path.join(output_directory, os.path.splitext(filename)[0] + ".png")
render_file(file_path, output_path)
self.report({'INFO'}, "Rendering of files is complete.")
return {'FINISHED'}
class ABOUT_OT_dialog(bpy.types.Operator):
bl_idname = "wm.about_dialog"
bl_label = "About this addon"
def execute(self, context):
return context.window_manager.invoke_props_dialog(self)
def draw(self, context):
layout = self.layout
layout.label(text="3D File Renderer by catafest")
layout.label(text="Author: Catalin George Festila")
layout.label(text="Nicknames: catafest and mythcat")
layout.label(text="Country: Romania")
layout.label(text="Email: catafest [at] yahoo.com")
layout.operator("wm.url_open", text="LinkedIn").url = "https://www.linkedin.com/in/c%C4%83t%C4%83lin-george-fe%C8%99til%C4%83-05780a67"
layout.operator("wm.url_open", text="Author Site").url = "https://sites.google.com/view/festila-george-catalin"
layout.operator("wm.url_open", text="catafest GitHub").url = "https://github.com/catafest"
layout.operator("wm.url_open", text="catafest-work GitHub").url = "https://github.com/catafest-work"
class FileRendererPanel(bpy.types.Panel):
bl_label = "3D File Renderer by catafest"
bl_idname = "OBJECT_PT_file_renderer"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = 'File Renderer'
def draw(self, context):
layout = self.layout
scene = context.scene
file_renderer_props = scene.file_renderer_props
layout.prop(file_renderer_props, "input_directory")
layout.prop(file_renderer_props, "output_directory")
# Styling the render button
render_button = layout.operator("render.files", text="Start render 3D files for all files")
layout.separator()
layout.operator("wm.about_dialog", text="About this addon")
def register():
bpy.utils.register_class(FileRendererProperties)
bpy.utils.register_class(RENDER_OT_files)
bpy.utils.register_class(ABOUT_OT_dialog)
bpy.utils.register_class(FileRendererPanel)
bpy.types.Scene.file_renderer_props = bpy.props.PointerProperty(type=FileRendererProperties)
def unregister():
bpy.utils.unregister_class(FileRendererProperties)
bpy.utils.unregister_class(RENDER_OT_files)
bpy.utils.unregister_class(ABOUT_OT_dialog)
bpy.utils.unregister_class(FileRendererPanel)
del bpy.types.Scene.file_renderer_props
if __name__ == "__main__":
register()