// Code Generator // Generates Node.js (Three.js) or Blender Python code from scene graph export class CodeGenerator { generateNodeJS(sceneGraph, sceneData) { let code = `// GLB Generation Code (Three.js + GLTFExporter) // Generated automatically from scene description // Run: npm install three @gltf-transform/core @gltf-transform/functions import * as THREE from 'three'; import { GLTFExporter } from 'three/examples/jsm/exporters/GLTFExporter.js'; import { Document, NodeIO } from '@gltf-transform/core'; import { dedup, resample, draco } from '@gltf-transform/functions'; import fs from 'fs'; const scene = new THREE.Scene(); const sceneData = ${JSON.stringify(sceneData, null, 2)}; // Environment setup scene.background = new THREE.Color(${this.colorToThreeJS(sceneData.lighting?.color || [1, 1, 1])}); scene.fog = ${sceneData.effects?.fog ? `new THREE.FogExp2(${this.colorToThreeJS([0.8, 0.8, 0.9])}, 0.05)` : 'null'}; // Create ground function createGround() { const groundGeometry = new THREE.PlaneGeometry(${sceneData.ground?.size || 20}, ${sceneData.ground?.size || 20}); const groundMaterial = new THREE.MeshStandardMaterial({ color: ${this.colorToThreeJS(sceneData.ground?.color || [0.5, 0.5, 0.5])}, roughness: ${sceneData.ground?.roughness || 0.8}, metalness: ${sceneData.ground?.metallic || 0.0} }); const ground = new THREE.Mesh(groundGeometry, groundMaterial); ground.rotation.x = -Math.PI / 2; scene.add(ground); return ground; } // Create object helper function createObject(objData) { let geometry, material, mesh; switch(objData.type) { case 'cube': geometry = new THREE.BoxGeometry( objData.size || 1, objData.size || 1, objData.size || 1 ); break; case 'sphere': geometry = new THREE.SphereGeometry( objData.radius || 0.5, 32, 32 ); break; case 'cylinder': geometry = new THREE.CylinderGeometry( objData.radius || 0.5, objData.radius || 0.5, objData.size || 1, 32 ); break; case 'cone': geometry = new THREE.ConeGeometry( objData.radius || 0.5, objData.size || 1, 32 ); break; case 'plane': geometry = new THREE.PlaneGeometry( objData.size || 10, objData.size || 10 ); break; default: geometry = new THREE.BoxGeometry(1, 1, 1); } const color = objData.color || [0.8, 0.8, 0.8, 1]; material = new THREE.MeshStandardMaterial({ color: new THREE.Color(color[0], color[1], color[2]), roughness: objData.roughness || 0.5, metalness: objData.metallic || 0.0, transparent: color[3] < 1, opacity: color[3] || 1 }); if (objData.emission) { material.emissive = new THREE.Color( objData.emission[0], objData.emission[1], objData.emission[2] ); material.emissiveIntensity = objData.emissionStrength || 1.0; } mesh = new THREE.Mesh(geometry, material); mesh.position.set( objData.location[0] || 0, objData.location[1] || 0, objData.location[2] || 0 ); mesh.rotation.set( objData.rotation[0] || 0, objData.rotation[1] || 0, objData.rotation[2] || 0 ); mesh.scale.set( objData.scale[0] || 1, objData.scale[1] || 1, objData.scale[2] || 1 ); mesh.name = objData.name || 'Object'; scene.add(mesh); return mesh; } // Create avatar function createAvatar(avatarData) { const group = new THREE.Group(); group.name = avatarData.name || 'Avatar'; // Simple humanoid from primitives const torso = new THREE.Mesh( new THREE.CylinderGeometry(0.25, 0.25, 0.8, 32), new THREE.MeshStandardMaterial({ color: 0xffccaa }) ); torso.position.y = 1.0; group.add(torso); const head = new THREE.Mesh( new THREE.SphereGeometry(0.18, 32, 32), new THREE.MeshStandardMaterial({ color: 0xffccaa }) ); head.position.y = 1.9; group.add(head); // Arms const leftArm = new THREE.Mesh( new THREE.BoxGeometry(0.2, 0.4, 0.2), new THREE.MeshStandardMaterial({ color: 0xffccaa }) ); leftArm.position.set(-0.45, 1.3, 0); group.add(leftArm); const rightArm = new THREE.Mesh( new THREE.BoxGeometry(0.2, 0.4, 0.2), new THREE.MeshStandardMaterial({ color: 0xffccaa }) ); rightArm.position.set(0.45, 1.3, 0); group.add(rightArm); // Legs const leftLeg = new THREE.Mesh( new THREE.BoxGeometry(0.25, 0.75, 0.25), new THREE.MeshStandardMaterial({ color: 0x4444ff }) ); leftLeg.position.set(-0.18, 0.5, 0); group.add(leftLeg); const rightLeg = new THREE.Mesh( new THREE.BoxGeometry(0.25, 0.75, 0.25), new THREE.MeshStandardMaterial({ color: 0x4444ff }) ); rightLeg.position.set(0.18, 0.5, 0); group.add(rightLeg); group.position.set( avatarData.position[0] || 0, avatarData.position[1] || 0, avatarData.position[2] || 0 ); group.scale.set( avatarData.scale || 1, avatarData.scale || 1, avatarData.scale || 1 ); scene.add(group); return group; } // Create lights function createLights(lightingData) { if (lightingData.lights) { lightingData.lights.forEach(light => { let lightObj; switch(light.type) { case 'SUN': case 'DIRECTIONAL': lightObj = new THREE.DirectionalLight( new THREE.Color(light.color || lightingData.color || [1, 1, 1]), light.energy || 1 ); lightObj.position.set( light.location[0] || 10, light.location[1] || -10, light.location[2] || 10 ); break; case 'POINT': lightObj = new THREE.PointLight( new THREE.Color(light.color || lightingData.color || [1, 1, 1]), light.energy || 50, 100 ); lightObj.position.set( light.location[0] || 0, light.location[1] || 0, light.location[2] || 2 ); break; case 'SPOT': lightObj = new THREE.SpotLight( new THREE.Color(light.color || lightingData.color || [1, 1, 1]), light.energy || 50 ); lightObj.position.set( light.location[0] || 0, light.location[1] || 0, light.location[2] || 2 ); break; default: lightObj = new THREE.AmbientLight( new THREE.Color(lightingData.color || [1, 1, 1]), lightingData.ambient_strength || 0.3 ); } scene.add(lightObj); }); } // Ambient light const ambientLight = new THREE.AmbientLight( new THREE.Color(lightingData.color || [1, 1, 1]), lightingData.ambient_strength || 0.3 ); scene.add(ambientLight); } // Create camera function createCamera(cameraData) { const camera = new THREE.PerspectiveCamera( cameraData.fov || 50, 16 / 9, 0.1, 1000 ); camera.position.set( cameraData.position[0] || 4, cameraData.position[1] || -4, cameraData.position[2] || 2.2 ); camera.rotation.set( cameraData.rotation[0] || 1.05, cameraData.rotation[1] || 0, cameraData.rotation[2] || 0.78 ); return camera; } // Build scene console.log('Building scene...'); createGround(); sceneData.objects?.forEach(obj => createObject(obj)); if (sceneData.avatar?.present) { createAvatar(sceneData.avatar); } createLights(sceneData.lighting); const camera = createCamera(sceneData.camera); // Export to GLB console.log('Exporting to GLB...'); const exporter = new GLTFExporter(); const options = { binary: true, includeCustomExtensions: true }; exporter.parse( scene, (result) => { fs.writeFileSync('scene.glb', Buffer.from(result)); console.log('GLB file saved as scene.glb'); }, (error) => { console.error('Export error:', error); } ); `; return code; } generateBlenderPython(sceneGraph, sceneData) { let code = `# Blender Python Script for GLB Generation # Generated automatically from scene description # Run: blender --background --python this_script.py import bpy import json import mathutils def clear_scene(): bpy.ops.wm.read_factory_settings(use_empty=True) def make_pbr_material(name, base_color=(1,1,1,1), roughness=0.5, metallic=0.0, emission=None, emission_strength=1.0): 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 = roughness principled.inputs['Metallic'].default_value = metallic if emission: principled.inputs['Emission'].default_value = (*emission[:3], 1.0) principled.inputs['Emission Strength'].default_value = emission_strength links.new(principled.outputs['BSDF'], output.inputs['Surface']) return mat def create_object(obj_data): typ = obj_data.get('type', 'cube') location = obj_data.get('location', [0, 0, 0]) rotation = obj_data.get('rotation', [0, 0, 0]) scale = obj_data.get('scale', [1, 1, 1]) name = obj_data.get('name', 'Object') if typ == 'cube': bpy.ops.mesh.primitive_cube_add(size=obj_data.get('size', 1), location=location) elif typ == 'sphere': bpy.ops.mesh.primitive_uv_sphere_add(radius=obj_data.get('radius', 0.5), location=location) elif typ == 'cylinder': bpy.ops.mesh.primitive_cylinder_add(vertices=32, radius=obj_data.get('radius', 0.5), depth=obj_data.get('size', 1), location=location) elif typ == 'cone': bpy.ops.mesh.primitive_cone_add(vertices=32, radius1=obj_data.get('radius', 0.5), depth=obj_data.get('size', 1), location=location) elif typ == 'plane': bpy.ops.mesh.primitive_plane_add(size=obj_data.get('size', 10), location=location) else: bpy.ops.mesh.primitive_cube_add(size=1, location=location) obj = bpy.context.object obj.name = name obj.rotation_euler = rotation obj.scale = scale # Apply material color = obj_data.get('color', [0.8, 0.8, 0.8, 1]) mat = make_pbr_material( f"mat_{name}", base_color=tuple(color), roughness=obj_data.get('roughness', 0.5), metallic=obj_data.get('metallic', 0.0), emission=obj_data.get('emission'), emission_strength=obj_data.get('emissionStrength', 1.0) ) obj.data.materials.append(mat) return obj def create_avatar(avatar_data): # Simple humanoid from primitives bpy.ops.mesh.primitive_cylinder_add(vertices=32, radius=0.25, depth=0.8, location=(0, 0, 1.0)) torso = bpy.context.object torso.name = f"{avatar_data.get('name', 'Avatar')}_Torso" bpy.ops.mesh.primitive_uv_sphere_add(radius=0.18, location=(0, 0, 1.9)) head = bpy.context.object head.name = f"{avatar_data.get('name', 'Avatar')}_Head" # Arms and legs (simplified) bpy.ops.mesh.primitive_cube_add(size=0.2, location=(-0.45, 0, 1.3)) left_arm = bpy.context.object left_arm.scale[1] = 2.0 left_arm.name = f"{avatar_data.get('name', 'Avatar')}_L_Arm" bpy.ops.mesh.primitive_cube_add(size=0.2, location=(0.45, 0, 1.3)) right_arm = bpy.context.object right_arm.scale[1] = 2.0 right_arm.name = f"{avatar_data.get('name', 'Avatar')}_R_Arm" bpy.ops.mesh.primitive_cube_add(size=0.25, location=(-0.18, 0, 0.5)) left_leg = bpy.context.object left_leg.scale[2] = 1.5 left_leg.name = f"{avatar_data.get('name', 'Avatar')}_L_Leg" bpy.ops.mesh.primitive_cube_add(size=0.25, location=(0.18, 0, 0.5)) right_leg = bpy.context.object right_leg.scale[2] = 1.5 right_leg.name = f"{avatar_data.get('name', 'Avatar')}_R_Leg" # Join all parts bpy.ops.object.select_all(action='DESELECT') for part in [torso, head, left_arm, right_arm, left_leg, right_leg]: part.select_set(True) bpy.context.view_layer.objects.active = torso bpy.ops.object.join() avatar = bpy.context.object avatar.name = avatar_data.get('name', 'Avatar') avatar.location = avatar_data.get('position', [0, 0, 0]) avatar.scale = [avatar_data.get('scale', 1.0)] * 3 mat = make_pbr_material("AvatarMat", base_color=(1, 0.8, 0.6, 1)) avatar.data.materials.append(mat) return avatar def create_lights(lighting_data): if lighting_data.get('lights'): for light in lighting_data['lights']: light_type = light.get('type', 'POINT') location = light.get('location', [0, 0, 2]) energy = light.get('energy', 50) color = light.get('color', lighting_data.get('color', [1, 1, 1])) if light_type == 'SUN': bpy.ops.object.light_add(type='SUN', location=location) sun = bpy.context.object sun.data.energy = energy sun.data.color = color elif light_type == 'POINT': bpy.ops.object.light_add(type='POINT', location=location) point = bpy.context.object point.data.energy = energy point.data.color = color elif light_type == 'SPOT': bpy.ops.object.light_add(type='SPOT', location=location) spot = bpy.context.object spot.data.energy = energy spot.data.color = color # Ambient light bpy.context.scene.world.use_nodes = True world_nodes = bpy.context.scene.world.node_tree.nodes world_nodes['Background'].inputs['Strength'].default_value = lighting_data.get('ambient_strength', 0.3) world_nodes['Background'].inputs['Color'].default_value = (*lighting_data.get('color', [1, 1, 1]), 1) def create_camera(camera_data): bpy.ops.object.camera_add(location=camera_data.get('position', [4, -4, 2.2])) cam = bpy.context.object cam.rotation_euler = camera_data.get('rotation', [1.05, 0, 0.78]) bpy.context.scene.camera = cam cam.data.lens = 50 cam.data.sensor_width = 36 # Scene data scene_data = ${JSON.stringify(sceneData, null, 2)} # Clear and build scene clear_scene() # Ground if scene_data.get('ground'): ground = scene_data['ground'] bpy.ops.mesh.primitive_plane_add(size=ground.get('size', 20), location=(0, 0, 0)) ground_obj = bpy.context.object ground_obj.name = "Ground" mat = make_pbr_material( "GroundMat", base_color=tuple(ground.get('color', [0.5, 0.5, 0.5, 1])), roughness=ground.get('roughness', 0.8), metallic=ground.get('metallic', 0.0) ) ground_obj.data.materials.append(mat) # Objects if scene_data.get('objects'): for obj_data in scene_data['objects']: create_object(obj_data) # Avatar if scene_data.get('avatar') and scene_data['avatar'].get('present'): create_avatar(scene_data['avatar']) # Lighting if scene_data.get('lighting'): create_lights(scene_data['lighting']) # Camera if scene_data.get('camera'): create_camera(scene_data['camera']) # Scene settings bpy.context.scene.render.engine = 'CYCLES' bpy.context.scene.cycles.device = 'CPU' # Export GLB output_path = 'scene.glb' export_kwargs = { "filepath": output_path, "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(f"Exported: {output_path}") `; return code; } colorToThreeJS(color) { if (Array.isArray(color) && color.length >= 3) { return `0x${Math.floor(color[0] * 255).toString(16).padStart(2, '0')}${Math.floor(color[1] * 255).toString(16).padStart(2, '0')}${Math.floor(color[2] * 255).toString(16).padStart(2, '0')}`; } return '0xffffff'; } }