60 lines
2.6 KiB
Python
60 lines
2.6 KiB
Python
|
|
"""Clay-render each OBJ in a directory, 3 angles, framed on its bbox.
|
||
|
|
Only files matching *_hand_*.obj are picked up, and outdir MUST be absolute — a relative
|
||
|
|
one makes Blender write outside the tree and silently produce nothing.
|
||
|
|
|
||
|
|
usage: blender --background --factory-startup --python render_objs.py -- objdir outdir [res] [dist]
|
||
|
|
res square render resolution in px (default 900)
|
||
|
|
dist camera distance as a multiple of the mesh radius (default 2.6; lower = tighter)"""
|
||
|
|
import bpy, sys, math, glob, os
|
||
|
|
from mathutils import Vector
|
||
|
|
|
||
|
|
argv = sys.argv[sys.argv.index("--") + 1:]
|
||
|
|
objdir, outdir = argv[0], argv[1]
|
||
|
|
RES = int(argv[2]) if len(argv) > 2 else 900
|
||
|
|
DIST = float(argv[3]) if len(argv) > 3 else 2.6
|
||
|
|
|
||
|
|
bpy.ops.wm.read_factory_settings(use_empty=True)
|
||
|
|
scn = bpy.context.scene
|
||
|
|
scn.render.engine = 'BLENDER_EEVEE' if bpy.app.version >= (4, 2) else 'BLENDER_EEVEE_NEXT'
|
||
|
|
scn.render.resolution_x = scn.render.resolution_y = RES
|
||
|
|
|
||
|
|
mat = bpy.data.materials.new("Clay")
|
||
|
|
mat.use_nodes = True
|
||
|
|
bsdf = mat.node_tree.nodes["Principled BSDF"]
|
||
|
|
bsdf.inputs["Base Color"].default_value = (0.72, 0.55, 0.45, 1.0)
|
||
|
|
bsdf.inputs["Roughness"].default_value = 0.65
|
||
|
|
|
||
|
|
for rot, energy in (((50, 0, 30), 3.0), ((-40, 0, -140), 1.2), ((10, 0, 180), 0.8)):
|
||
|
|
sun = bpy.data.objects.new("Sun", bpy.data.lights.new("Sun", 'SUN'))
|
||
|
|
sun.data.energy = energy
|
||
|
|
sun.rotation_euler = tuple(math.radians(a) for a in rot)
|
||
|
|
scn.collection.objects.link(sun)
|
||
|
|
|
||
|
|
cam = bpy.data.objects.new("Cam", bpy.data.cameras.new("Cam"))
|
||
|
|
cam.data.lens = 60
|
||
|
|
scn.collection.objects.link(cam)
|
||
|
|
scn.camera = cam
|
||
|
|
|
||
|
|
for path in sorted(glob.glob(os.path.join(objdir, "*_hand_*.obj"))):
|
||
|
|
bpy.ops.wm.obj_import(filepath=path)
|
||
|
|
obj = bpy.context.selected_objects[0]
|
||
|
|
obj.data.materials.clear()
|
||
|
|
obj.data.materials.append(mat)
|
||
|
|
for p in obj.data.polygons: p.use_smooth = True
|
||
|
|
bb = [obj.matrix_world @ Vector(c) for c in obj.bound_box]
|
||
|
|
ctr = sum(bb, Vector()) / 8
|
||
|
|
rad = max((v - ctr).length for v in bb)
|
||
|
|
tag = os.path.splitext(os.path.basename(path))[0]
|
||
|
|
# OBJ import is -Z forward +Y up by default: gltf Y-up mesh arrives Z-up in Blender
|
||
|
|
for label, direction in (("palm", Vector((0, -1, -0.25))),
|
||
|
|
("back", Vector((0, 1, 0.35))),
|
||
|
|
("side", Vector((-1, -0.3, 0.1)))):
|
||
|
|
d = direction.normalized()
|
||
|
|
cam.location = ctr - d * (rad * DIST)
|
||
|
|
cam.rotation_euler = d.to_track_quat('-Z', 'Y').to_euler()
|
||
|
|
scn.render.filepath = os.path.join(outdir, f"{tag}_{label}.png")
|
||
|
|
bpy.ops.render.render(write_still=True)
|
||
|
|
print("[objr] wrote", scn.render.filepath)
|
||
|
|
bpy.data.objects.remove(obj, do_unlink=True)
|
||
|
|
print("[objr] DONE")
|