1
This commit is contained in:
116
render_model.py
Normal file
116
render_model.py
Normal file
@@ -0,0 +1,116 @@
|
||||
import subprocess
|
||||
|
||||
import bpy
|
||||
import os
|
||||
import sys
|
||||
from mathutils import Vector
|
||||
|
||||
|
||||
def clear_scene():
|
||||
bpy.ops.object.select_all(action='SELECT')
|
||||
bpy.ops.object.delete(use_global=False)
|
||||
|
||||
|
||||
def import_model(model_path):
|
||||
ext = os.path.splitext(model_path)[1].lower()
|
||||
if ext == ".obj":
|
||||
bpy.ops.wm.obj_import(filepath=model_path)
|
||||
elif ext in [".glb", ".gltf"]:
|
||||
bpy.ops.import_scene.gltf(filepath=model_path)
|
||||
else:
|
||||
raise ValueError(f"Unsupported format: {ext}")
|
||||
|
||||
|
||||
def get_scene_bbox():
|
||||
objs = [obj for obj in bpy.context.scene.objects if obj.type == 'MESH']
|
||||
if not objs:
|
||||
raise RuntimeError("No mesh objects found")
|
||||
|
||||
min_corner = Vector((float("inf"), float("inf"), float("inf")))
|
||||
max_corner = Vector((float("-inf"), float("-inf"), float("-inf")))
|
||||
|
||||
for obj in objs:
|
||||
for v in obj.bound_box:
|
||||
world_v = obj.matrix_world @ Vector(v)
|
||||
min_corner.x = min(min_corner.x, world_v.x)
|
||||
min_corner.y = min(min_corner.y, world_v.y)
|
||||
min_corner.z = min(min_corner.z, world_v.z)
|
||||
max_corner.x = max(max_corner.x, world_v.x)
|
||||
max_corner.y = max(max_corner.y, world_v.y)
|
||||
max_corner.z = max(max_corner.z, world_v.z)
|
||||
|
||||
return min_corner, max_corner
|
||||
|
||||
|
||||
def normalize_model():
|
||||
min_corner, max_corner = get_scene_bbox()
|
||||
center = (min_corner + max_corner) / 2
|
||||
size = max(max_corner.x - min_corner.x,
|
||||
max(max_corner.y - min_corner.y, max_corner.z - min_corner.z))
|
||||
|
||||
objs = [obj for obj in bpy.context.scene.objects if obj.type == 'MESH']
|
||||
for obj in objs:
|
||||
obj.location -= center
|
||||
|
||||
if size > 0:
|
||||
scale = 2.0 / size
|
||||
for obj in objs:
|
||||
obj.scale *= scale
|
||||
|
||||
bpy.context.view_layer.update()
|
||||
|
||||
|
||||
def add_camera():
|
||||
bpy.ops.object.camera_add(location=(2.5, -2.5, 2.0))
|
||||
cam = bpy.context.active_object
|
||||
direction = Vector((0, 0, 0)) - cam.location
|
||||
cam.rotation_euler = direction.to_track_quat('-Z', 'Y').to_euler()
|
||||
bpy.context.scene.camera = cam
|
||||
|
||||
|
||||
def add_light():
|
||||
bpy.ops.object.light_add(type='SUN', location=(5, -5, 8))
|
||||
bpy.context.active_object.data.energy = 3.0
|
||||
|
||||
bpy.ops.object.light_add(type='AREA', location=(3, -3, 3))
|
||||
area = bpy.context.active_object
|
||||
area.data.energy = 3000
|
||||
area.data.size = 5
|
||||
|
||||
|
||||
def setup_render(output_path, resolution=1024):
|
||||
scene = bpy.context.scene
|
||||
scene.render.engine = 'CYCLES'
|
||||
scene.cycles.samples = 128
|
||||
scene.render.filepath = output_path
|
||||
scene.render.image_settings.file_format = 'PNG'
|
||||
scene.render.resolution_x = resolution
|
||||
scene.render.resolution_y = resolution
|
||||
scene.render.film_transparent = True
|
||||
|
||||
|
||||
def parse_args():
|
||||
argv = sys.argv
|
||||
argv = argv[argv.index("--") + 1:] if "--" in argv else []
|
||||
if len(argv) < 2:
|
||||
raise ValueError("Usage: blender -b -P render_model.py -- model_path output_path")
|
||||
return argv[0], argv[1]
|
||||
|
||||
|
||||
def get_static_model_image():
|
||||
model_path, output_path = parse_args()
|
||||
clear_scene()
|
||||
import_model(model_path)
|
||||
normalize_model()
|
||||
add_camera()
|
||||
add_light()
|
||||
setup_render(output_path)
|
||||
bpy.ops.render.render(write_still=True)
|
||||
print(f"Saved to {output_path}")
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
get_static_model_image()
|
||||
Reference in New Issue
Block a user