Spaces:
Runtime error
Runtime error
| // 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'; | |
| } | |
| } | |