117 lines
3.2 KiB
Python
Executable File
117 lines
3.2 KiB
Python
Executable File
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()
|