venkat_glb_creation / scripts /generate_scene.py
Antigravity AI
Initial HF commit
37ed7e4
Raw
History Blame Contribute Delete
13.8 kB
# scripts/generate_scene.py
import bpy
import json
import sys
import os
import mathutils
def clear_scene():
bpy.ops.wm.read_factory_settings(use_empty=True)
def make_material(name, base_color=(1,1,1,1), texture_path=None):
mat = bpy.data.materials.new(name)
mat.use_nodes = True
nodes = mat.node_tree.nodes
links = mat.node_tree.links
nodes.clear()
output = nodes.new(type="ShaderNodeOutputMaterial")
principled = nodes.new(type="ShaderNodeBsdfPrincipled")
principled.inputs['Base Color'].default_value = base_color
principled.inputs['Roughness'].default_value = 0.4
principled.inputs['Metallic'].default_value = 0.0
links.new(principled.outputs['BSDF'], output.inputs['Surface'])
if texture_path and os.path.exists(texture_path):
tex = nodes.new(type="ShaderNodeTexImage")
tex.image = bpy.data.images.load(texture_path)
links.new(tex.outputs['Color'], principled.inputs['Base Color'])
return mat
def add_mesh_object(mesh_obj, name, location=(0,0,0), material=None):
"""Helper to configure mesh object - objects from bpy.ops are already linked"""
mesh_obj.name = name
mesh_obj.location = location
if material:
if len(mesh_obj.data.materials) == 0:
mesh_obj.data.materials.append(material)
else:
mesh_obj.data.materials[0] = material
return mesh_obj
def primitive_humanoid(name_prefix="Avatar", scale=1.0, material=None):
# Create simple primitives for torso, head, limbs and join them
parts = []
# Torso - cylinder
bpy.ops.mesh.primitive_cylinder_add(vertices=32, radius=0.25*scale, depth=0.8*scale, location=(0,0,1.0*scale))
torso = bpy.context.object
torso.name = f"{name_prefix}_Torso"
parts.append(torso)
# Head - sphere
bpy.ops.mesh.primitive_uv_sphere_add(radius=0.18*scale, location=(0,0,1.9*scale))
head = bpy.context.object
head.name = f"{name_prefix}_Head"
parts.append(head)
# Upper arms - cubes scaled
bpy.ops.mesh.primitive_cube_add(size=0.2*scale, location=(0.45*scale,0,1.3*scale))
r_arm = bpy.context.object
r_arm.scale[0] = 2.0
r_arm.name = f"{name_prefix}_R_UpperArm"
parts.append(r_arm)
bpy.ops.mesh.primitive_cube_add(size=0.2*scale, location=(-0.45*scale,0,1.3*scale))
l_arm = bpy.context.object
l_arm.scale[0] = 2.0
l_arm.name = f"{name_prefix}_L_UpperArm"
parts.append(l_arm)
# Upper legs
bpy.ops.mesh.primitive_cube_add(size=0.25*scale, location=(0.18*scale,0,0.5*scale))
r_leg = bpy.context.object
r_leg.scale[2] = 1.5
r_leg.name = f"{name_prefix}_R_UpperLeg"
parts.append(r_leg)
bpy.ops.mesh.primitive_cube_add(size=0.25*scale, location=(-0.18*scale,0,0.5*scale))
l_leg = bpy.context.object
l_leg.scale[2] = 1.5
l_leg.name = f"{name_prefix}_L_UpperLeg"
parts.append(l_leg)
# Join into single mesh (optional)
bpy.ops.object.select_all(action='DESELECT')
for p in parts:
p.select_set(True)
bpy.context.view_layer.objects.active = p
bpy.ops.object.join() # join into active
combined = bpy.context.object
combined.name = f"{name_prefix}_Mesh"
# Apply material
if material:
if len(combined.data.materials) == 0:
combined.data.materials.append(material)
else:
combined.data.materials[0] = material
return combined
def create_armature_for_avatar(avatar_obj, name="AvatarArmature"):
bpy.ops.object.select_all(action='DESELECT')
# create armature
bpy.ops.object.armature_add(enter_editmode=True, location=(0,0,0))
arm = bpy.context.object
arm.name = name
arm_data = arm.data
arm_data.name = name + "_Data"
# edit mode bones: root, spine, head, left_arm, right_arm, left_leg, right_leg
eb = arm.data.edit_bones
eb.remove(eb[0]) # remove default bone to start clean
def add_bone(bname, head, tail, parent=None):
b = eb.new(bname)
b.head = head
b.tail = tail
if parent:
b.parent = eb[parent]
return b
add_bone("root", (0,0,0), (0,0,0.5))
add_bone("spine", (0,0,0.5), (0,0,1.2), parent="root")
add_bone("head", (0,0,1.2), (0,0,1.9), parent="spine")
add_bone("left_arm", (-0.45,0,1.3), (-0.9,0,1.3), parent="spine")
add_bone("right_arm", (0.45,0,1.3), (0.9,0,1.3), parent="spine")
add_bone("left_leg", (-0.18,0,0.5), (-0.18,0,-0.5), parent="root")
add_bone("right_leg", (0.18,0,0.5), (0.18,0,-0.5), parent="root")
bpy.ops.object.mode_set(mode='OBJECT')
# Parent mesh to armature with automatic weights
bpy.context.view_layer.objects.active = avatar_obj
avatar_obj.select_set(True)
arm.select_set(True)
bpy.ops.object.parent_set(type='ARMATURE_AUTO')
return arm
def add_ground(scene_desc):
"""Add ground plane"""
if scene_desc.get('ground'):
ground_data = scene_desc.get('ground')
size = ground_data.get('size', 20)
color = ground_data.get('color', [0.4, 0.3, 0.2, 1])
else:
size = 20
color = [0.4, 0.3, 0.2, 1]
bpy.ops.mesh.primitive_plane_add(size=size, location=(0,0,0))
ground = bpy.context.object
ground.name = "Ground"
mat_ground = make_material("GroundMat", base_color=tuple(color), texture_path=scene_desc.get('ground_texture'))
# Object is already linked by primitive_plane_add, just set material
if len(ground.data.materials) == 0:
ground.data.materials.append(mat_ground)
else:
ground.data.materials[0] = mat_ground
def add_objects(scene_desc):
"""Add scene objects"""
for i, obj in enumerate(scene_desc.get('objects', [])):
typ = obj.get('type','cube')
pos = obj.get('location', [i*1.5, 0.0, 0.5])
obj_name = obj.get('name', f"EnvObj_{i}")
if typ == 'cube':
bpy.ops.mesh.primitive_cube_add(size=obj.get('size', 0.8), location=pos)
o = bpy.context.object
elif typ == 'sphere':
bpy.ops.mesh.primitive_uv_sphere_add(radius=obj.get('radius',0.5), location=pos)
o = bpy.context.object
elif typ == 'cylinder':
bpy.ops.mesh.primitive_cylinder_add(vertices=32, radius=obj.get('radius', 0.5), depth=obj.get('size', 1), location=pos)
o = bpy.context.object
elif typ == 'cone':
bpy.ops.mesh.primitive_cone_add(vertices=32, radius1=obj.get('radius', 0.5), depth=obj.get('size', 1), location=pos)
o = bpy.context.object
elif typ == 'plane':
bpy.ops.mesh.primitive_plane_add(size=obj.get('size', 10), location=pos)
o = bpy.context.object
else:
bpy.ops.mesh.primitive_cube_add(size=0.5, location=pos)
o = bpy.context.object
# Object is already linked by bpy.ops, just set name, material, and transform
o.name = obj_name
o.location = tuple(pos)
if obj.get('rotation'):
o.rotation_euler = tuple(obj.get('rotation', [0, 0, 0]))
if obj.get('scale'):
o.scale = tuple(obj.get('scale', [1, 1, 1]))
# Handle PBR material properties
roughness = obj.get('roughness', 0.5)
metallic = obj.get('metallic', 0.0)
emission = obj.get('emission')
emission_strength = obj.get('emissionStrength', 1.0)
mat = make_material(f"objmat_{i}", base_color=tuple(obj.get('color', [1,1,1,1])), texture_path=obj.get('texture'))
# Update material properties
nodes = mat.node_tree.nodes
principled = nodes.get('Principled BSDF')
if principled:
principled.inputs['Roughness'].default_value = roughness
principled.inputs['Metallic'].default_value = metallic
if emission:
# Try different emission input names for different Blender versions
try:
if 'Emission Color' in principled.inputs:
principled.inputs['Emission Color'].default_value = (*emission[:3], 1.0)
elif 'Emission' in principled.inputs:
principled.inputs['Emission'].default_value = (*emission[:3], 1.0)
if 'Emission Strength' in principled.inputs:
principled.inputs['Emission Strength'].default_value = emission_strength
except KeyError:
# Skip emission if not supported
pass
if len(o.data.materials) == 0:
o.data.materials.append(mat)
else:
o.data.materials[0] = mat
def add_lighting(scene_desc):
"""Add default lighting"""
bpy.ops.object.light_add(type='SUN', location=(10, -10, 10))
sun = bpy.context.object
sun.data.energy = scene_desc.get('sun_energy', 3.0)
sun.data.angle = 0.5
# Optional point lights from scene_desc
for i, L in enumerate(scene_desc.get('lights', [])):
typ = L.get('type','POINT')
loc = L.get('location', [0,0,2])
bpy.ops.object.light_add(type=typ, location=loc)
light = bpy.context.object
light.data.energy = L.get('energy', 50.0)
def apply_camera():
# Camera
bpy.ops.object.camera_add(location=(4.0, -4.0, 2.2))
cam = bpy.context.object
cam.rotation_euler = (1.05, 0.0, 0.78)
bpy.context.scene.camera = cam
def main():
# CLI args: script.py -- <input_json> <output_glb>
argv = sys.argv
if "--" in argv:
idx = argv.index("--")
args = argv[idx+1:]
else:
args = []
if len(args) < 2:
print("Usage: blender --background --python generate_scene.py -- <input.json> <output.glb>")
return
input_json = args[0]
output_glb = args[1]
with open(input_json, 'r') as f:
scene_desc = json.load(f)
clear_scene()
# Ground
add_ground(scene_desc)
# Objects
add_objects(scene_desc)
# Avatar creation (if present)
if scene_desc.get('avatar') and scene_desc.get('avatar', {}).get('present'):
tex_file = scene_desc.get('avatar_texture') # optional
mat_avatar = make_material("AvatarMat", base_color=(1,0.8,0.6,1), texture_path=tex_file)
avatar = primitive_humanoid(name_prefix="Hero", scale=1.0, material=mat_avatar)
arm = create_armature_for_avatar(avatar, name="HeroArmature")
# Set avatar position if specified
avatar_pos = scene_desc.get('avatar', {}).get('position', [0, 0, 0])
avatar.location = tuple(avatar_pos)
avatar_scale = scene_desc.get('avatar', {}).get('scale', 1.0)
avatar.scale = (avatar_scale, avatar_scale, avatar_scale)
# Lighting
if scene_desc.get('lighting'):
lighting_data = scene_desc.get('lighting')
# Add ambient light via world
bpy.context.scene.world.use_nodes = True
world_nodes = bpy.context.scene.world.node_tree.nodes
if 'Background' in world_nodes:
world_nodes['Background'].inputs['Strength'].default_value = lighting_data.get('ambient_strength', 0.3)
light_color = lighting_data.get('color', [1, 1, 1])
world_nodes['Background'].inputs['Color'].default_value = (*light_color[:3], 1.0)
# Add lights from lighting.lights array
if lighting_data.get('lights'):
for i, L in enumerate(lighting_data.get('lights', [])):
typ = L.get('type','POINT')
loc = L.get('location', [0,0,2])
if typ == 'SUN':
bpy.ops.object.light_add(type='SUN', location=loc)
sun = bpy.context.object
sun.data.energy = L.get('energy', 3.0)
sun.data.angle = L.get('angle', 0.5)
if L.get('color'):
sun.data.color = tuple(L.get('color', [1,1,1])[:3])
elif typ == 'POINT':
bpy.ops.object.light_add(type='POINT', location=loc)
light = bpy.context.object
light.data.energy = L.get('energy', 50.0)
if L.get('color'):
light.data.color = tuple(L.get('color', [1,1,1])[:3])
elif typ == 'SPOT':
bpy.ops.object.light_add(type='SPOT', location=loc)
light = bpy.context.object
light.data.energy = L.get('energy', 50.0)
if L.get('color'):
light.data.color = tuple(L.get('color', [1,1,1])[:3])
else:
# Default sun if no lights specified
bpy.ops.object.light_add(type='SUN', location=(10, -10, 10))
sun = bpy.context.object
sun.data.energy = lighting_data.get('sun_energy', 3.0)
sun.data.angle = 0.5
else:
# Default lighting
add_lighting(scene_desc)
# Camera
if scene_desc.get('camera'):
cam_data = scene_desc.get('camera')
bpy.ops.object.camera_add(location=tuple(cam_data.get('position', [4.0, -4.0, 2.2])))
cam = bpy.context.object
cam.rotation_euler = tuple(cam_data.get('rotation', [1.05, 0.0, 0.78]))
bpy.context.scene.camera = cam
else:
apply_camera()
# Scene settings (cycles or eevee)
bpy.context.scene.render.engine = 'CYCLES'
bpy.context.scene.cycles.device = 'CPU'
# Export glb
export_kwargs = {
"filepath": output_glb,
"export_format": "GLB",
"export_apply": True,
"export_texcoords": True,
"export_normals": True,
"export_materials": "EXPORT",
"export_colors": True,
"export_extras": True,
}
bpy.ops.export_scene.gltf(**export_kwargs)
print("Exported:", output_glb)
if __name__ == "__main__":
main()