awacke1 commited on
Commit
3d06b33
·
verified ·
1 Parent(s): 9de45be

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +152 -76
app.py CHANGED
@@ -7,8 +7,8 @@ def load_aframe_and_extras():
7
  return """
8
  <script src="https://aframe.io/releases/1.2.0/aframe.min.js"></script>
9
  <script src="https://unpkg.com/[email protected]/dist/aframe-event-set-component.min.js"></script>
 
10
  <script>
11
- let score = 0;
12
  AFRAME.registerComponent('draggable', {
13
  init: function () {
14
  this.el.setAttribute('class', 'raycastable');
@@ -33,97 +33,173 @@ def load_aframe_and_extras():
33
  },
34
  dragMove: function (evt) {
35
  if (!this.isDragging) return;
36
- let mouseX = evt.clientX / window.innerWidth * 2 - 1;
37
- let mouseY = -(evt.clientY / window.innerHeight * 2) + 1;
38
- let position = new THREE.Vector3(mouseX, mouseY, -5);
39
- position.unproject(this.camera.components.camera.camera);
40
- let direction = position.sub(this.camera.object3D.position).normalize();
41
- let distance = -this.camera.object3D.position.z / direction.z;
42
- let newPosition = this.camera.object3D.position.clone().add(direction.multiplyScalar(distance));
43
- this.el.setAttribute('position', {x: newPosition.x, y: newPosition.y, z: this.el.getAttribute('position').z});
 
 
 
 
 
 
 
44
  }
45
  });
46
- AFRAME.registerComponent('cursor-listener', {
 
 
 
 
 
47
  init: function () {
48
- this.el.addEventListener('mouseenter', function (evt) {
49
- this.setAttribute('material', {color: 'red'});
50
- });
51
- this.el.addEventListener('mouseleave', function (evt) {
52
- this.setAttribute('material', {color: 'blue'});
53
- });
54
- this.el.addEventListener('click', function (evt) {
55
- score++;
56
- document.getElementById('score').setAttribute('value', 'Score: ' + score);
57
- });
58
  }
59
  });
60
  </script>
61
  """
62
 
63
- def create_environment(scene):
64
- sky = scene.entity(
65
- geometry="primitive: sphere; radius: 5000",
66
- material="color: #ADD8E6; shader: flat; side: back" # Light blue sky
67
- )
68
- ground = scene.entity(
69
- geometry="primitive: plane; width: 100; height: 100",
70
- material="color: #7CFC00; side: double", # Lawn green ground
71
- rotation="-90 0 0"
72
- )
73
- return sky, ground
74
-
75
- def create_drag_target(scene, x, y, z, color='blue'):
76
- target = scene.entity(
77
- geometry=f"primitive: box; width: 1; height: 1; depth: 1",
78
- material=f"color: {color}",
79
- position=f"{x} {y} {z}",
80
- draggable=""
81
- )
82
- return target
83
 
84
- def main():
85
- st.title("Interactive A-Frame Scene")
86
-
87
- html_string = f"""
88
- <!DOCTYPE html>
89
- <html>
90
- <head>
91
- <meta charset="utf-8">
92
- <title>A-Frame Example</title>
93
- {load_aframe_and_extras()}
94
- </head>
95
- <body>
96
- <a-scene cursor="rayOrigin: mouse" raycaster="objects: .raycastable">
97
- <a-camera position="0 1.6 0"></a-camera>
98
- <a-entity id="environment"></a-entity>
99
- <a-text id="score" value="Score: 0" position="-2 2 -3" color="black"></a-text>
100
- <a-entity id="drag-targets"></a-entity>
101
- </a-scene>
102
- </body>
103
- </html>
104
- """
105
-
106
- st.components.v1.html(html_string, height=600, scrolling=False)
 
 
 
 
 
107
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
108
  with st.sidebar:
109
- st.header("Scene Controls")
110
- num_targets = st.slider("Number of Drag Targets", min_value=1, max_value=10, value=3)
 
 
 
 
 
 
 
 
 
 
 
 
 
111
 
112
- scene = st.components.v1 # This won't work as expected for direct A-Frame manipulation
 
 
 
 
 
 
 
 
 
 
 
 
 
113
 
114
- environment = scene.get("environment") # This also won't work
 
 
 
 
 
 
115
 
116
- drag_targets = scene.get("drag-targets") # And this won't work
 
 
 
 
 
117
 
118
- if environment is not None:
119
- create_environment(environment) # This will likely cause errors
120
 
121
- if drag_targets is not None:
122
- for i in range(num_targets):
123
- x = random.uniform(-5, 5)
124
- y = 1
125
- z = random.uniform(-5, -2)
126
- create_drag_target(drag_targets, x, y, z) # Potential error here too
 
 
 
 
 
 
 
 
 
 
 
 
 
 
127
 
128
  if __name__ == "__main__":
129
  main()
 
7
  return """
8
  <script src="https://aframe.io/releases/1.2.0/aframe.min.js"></script>
9
  <script src="https://unpkg.com/[email protected]/dist/aframe-event-set-component.min.js"></script>
10
+ <script src="https://cdn.jsdelivr.net/npm/aframe-volumetric-fog@latest/dist/aframe-volumetric-fog.min.js"></script>
11
  <script>
 
12
  AFRAME.registerComponent('draggable', {
13
  init: function () {
14
  this.el.setAttribute('class', 'raycastable');
 
33
  },
34
  dragMove: function (evt) {
35
  if (!this.isDragging) return;
36
+ var camera = this.camera.getObject3D('camera');
37
+ var worldPos = new THREE.Vector3();
38
+ camera.getWorldPosition(worldPos);
39
+ var worldDir = new THREE.Vector3();
40
+ camera.getWorldDirection(worldDir);
41
+ var camX = worldPos.x;
42
+ var camY = worldPos.y;
43
+ var camZ = worldPos.z;
44
+ var dirX = worldDir.x;
45
+ var dirY = worldDir.y;
46
+ var dirZ = worldDir.z;
47
+ var t = -camY / dirY;
48
+ var moveX = camX + t * dirX;
49
+ var moveZ = camZ + t * dirZ;
50
+ this.el.setAttribute('position', moveX + " " + 0.0 + " " + moveZ);
51
  }
52
  });
53
+
54
+ AFRAME.registerComponent('undulating-rotation', {
55
+ schema: {
56
+ amplitude: { type: 'number', default: 10 },
57
+ frequency: { type: 'number', default: 0.3 }
58
+ },
59
  init: function () {
60
+ this.initialRotation = Math.random() * 360;
61
+ this.time = 0;
62
+ },
63
+ tick: function (time, deltaTime) {
64
+ this.time += deltaTime / 1000;
65
+ const rotationAmount = Math.sin(this.time * this.data.frequency) * this.data.amplitude;
66
+ this.el.setAttribute('rotation', { x: 0, y: this.initialRotation + rotationAmount, z: 0 });
 
 
 
67
  }
68
  });
69
  </script>
70
  """
71
 
72
+ @st.cache_data
73
+ def encode_file(file_path):
74
+ with open(file_path, "rb") as file:
75
+ return base64.b64encode(file.read()).decode()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76
 
77
+ @st.cache_data
78
+ def generate_tilemap(files, directory, grid_width, grid_height, bump_map_url, max_unique_models=5):
79
+ assets = "<a-assets>"
80
+ entities = ""
81
+ tile_size = 1
82
+ start_x = -(grid_width * tile_size) / 2
83
+ start_z = -(grid_height * tile_size) / 2
84
+ available_models = [f for f in files if f.endswith(('.obj', '.glb'))]
85
+ available_images = [f for f in files if f.endswith(('.webp', '.png'))]
86
+ encoded_files = {}
87
+
88
+ unique_files = random.sample(files, min(len(files), max_unique_models))
89
+
90
+ for file in unique_files:
91
+ file_path = os.path.join(directory, file)
92
+ file_type = file.split('.')[-1]
93
+ encoded_file = encode_file(file_path)
94
+ encoded_files[file] = encoded_file
95
+ if file_type in ['obj', 'glb']:
96
+ assets += f'<a-asset-item id="{Path(file).stem}" src="data:application/octet-stream;base64,{encoded_file}"></a-asset-item>'
97
+ elif file_type in ['webp', 'png']:
98
+ mime_type = f"image/{file_type}"
99
+ assets += f'<img id="{Path(file).stem}" src="data:{mime_type};base64,{encoded_file}" />'
100
+
101
+ assets += f"""
102
+ <a-material id="ground-mat" color="#78815d" normal-map="{bump_map_url}" normal-texture-repeat="{grid_width * 2} {grid_height * 2}"></a-material>
103
+ """
104
+ assets += "</a-assets>"
105
 
106
+ for i in range(grid_width):
107
+ for j in range(grid_height):
108
+ x = start_x + (i * tile_size)
109
+ z = start_z + (j * tile_size)
110
+ position = f"{x} 0 {z}"
111
+
112
+ chosen_file = random.choice(files)
113
+ file_type = chosen_file.split('.')[-1]
114
+ file_stem = Path(chosen_file).stem
115
+ rotation = f"0 0 0" # Initial rotation can be 0 for the undulation to be clearer
116
+ amplitude = random.uniform(5, 15)
117
+ frequency = random.uniform(0.1, 0.5)
118
+ scale = "0.5 0.5 0.5"
119
+
120
+ if file_type == 'obj':
121
+ entities += f'<a-entity position="{position}" rotation="{rotation}" scale="{scale}" obj-model="obj: #{file_stem}" class="raycastable" draggable undulating-rotation="amplitude: {amplitude}; frequency: {frequency}"></a-entity>'
122
+ elif file_type == 'glb':
123
+ entities += f'<a-entity position="{position}" rotation="{rotation}" scale="{scale}" gltf-model="#{file_stem}" class="raycastable" draggable undulating-rotation="amplitude: {amplitude}; frequency: {frequency}"></a-entity>'
124
+ elif file_type in ['webp', 'png']:
125
+ if available_images:
126
+ random_image = random.choice(available_images)
127
+ image_stem = Path(random_image).stem
128
+ entities += f'<a-plane position="{position}" rotation="-90 0 0" width="{tile_size * 0.8}" height="{tile_size * 0.8}" material="src: #{image_stem}" class="raycastable" draggable></a-plane>'
129
+
130
+ return assets, entities
131
+
132
+ def main():
133
+ st.set_page_config(layout="wide")
134
  with st.sidebar:
135
+ st.markdown("### 🤖 3D AI Using Claude 3.5 Sonnet for AI Pair Programming")
136
+ st.markdown("[Open 3D Animation Toolkit](https://huggingface.co/spaces/awacke1/3d_animation_toolkit)", unsafe_allow_html=True)
137
+ st.markdown("### ⬆️ Upload")
138
+ uploaded_files = st.file_uploader("Add files:", accept_multiple_files=True, key="file_uploader")
139
+ st.markdown("### 🗺️ Grid Size")
140
+ grid_width = st.slider("Grid Width", 1, 10, 8)
141
+ grid_height = st.slider("Grid Height", 1, 10, 5)
142
+ st.markdown("### 🖼️ Bump Map")
143
+ bump_map_file = st.file_uploader("Upload Bump Map (png)", type=["png"], key="bump_map_uploader")
144
+ st.markdown("### ℹ️ Instructions")
145
+ st.write("- Click and drag to move objects")
146
+ st.write("- Mouse wheel to zoom")
147
+ st.write("- Right-click and drag to rotate view")
148
+ st.markdown("### 📁 Directory")
149
+ directory = st.text_input("Enter path:", ".", key="directory_input")
150
 
151
+ if not os.path.isdir(directory):
152
+ st.sidebar.error("Invalid directory path")
153
+ return
154
+ file_types = ['obj', 'glb', 'webp', 'png']
155
+ if uploaded_files:
156
+ for uploaded_file in uploaded_files:
157
+ file_extension = Path(uploaded_file.name).suffix.lower()[1:]
158
+ if file_extension in file_types:
159
+ with open(os.path.join(directory, uploaded_file.name), "wb") as f:
160
+ shutil.copyfileobj(uploaded_file, f)
161
+ st.sidebar.success(f"Uploaded: {uploaded_file.name}")
162
+ else:
163
+ st.sidebar.warning(f"Skipped unsupported file: {uploaded_file.name}")
164
+ files = [f for f in os.listdir(directory) if f.split('.')[-1] in file_types]
165
 
166
+ bump_map_url = ""
167
+ if bump_map_file:
168
+ bump_map_bytes = bump_map_file.read()
169
+ bump_map_base64 = base64.b64encode(bump_map_bytes).decode()
170
+ bump_map_url = f"data:image/png;base64,{bump_map_base64}"
171
+ elif os.path.exists(os.path.join(directory, "bump_map.png")): # Optional: Check for a default bump map
172
+ bump_map_url = "bump_map.png"
173
 
174
+ aframe_scene = f"""
175
+ <a-scene embedded style="height: 600px; width: 100%;">
176
+ {load_aframe_and_extras()}
177
+ <a-entity id="rig" position="0 {max(grid_width, grid_height) * 0.8} {max(grid_width, grid_height) * 0.8}" rotation="-45 0 0">
178
+ <a-camera id="camera" look-controls wasd-controls="enabled: false" cursor="rayOrigin: mouse"></a-camera>
179
+ </a-entity>
180
 
181
+ <a-entity volumetric-fog="density: 0.02; color: #cccccc; near: 1; far: 50; animation: property: volumetric-fog.density; to: 0.04; dur: 8000; loop: true; dir: alternate"></a-entity>
 
182
 
183
+ <a-entity id="skybox">
184
+ <a-plane position="0 0 -50" rotation="0 0 0" width="100" height="100" material="color: #87CEEB"></a-plane>
185
+ <a-plane position="0 0 50" rotation="0 180 0" width="100" height="100" material="color: #ADD8E6"></a-plane>
186
+ <a-plane position="50 0 0" rotation="0 90 0" width="100" height="100" material="color: #B0E0E6"></a-plane>
187
+ <a-plane position="-50 0 0" rotation="0 -90 0" width="100" height="100" material="color: #E0FFFF"></a-plane>
188
+ <a-plane position="0 50 0" rotation="-90 0 0" width="100" height="100" material="color: #ADD8E6"></a-plane>
189
+ <a-plane position="0 -50 0" rotation="90 0 0" width="100" height="100" material="color: #F0F8FF"></a-plane>
190
+ </a-entity>
191
+
192
+ <a-plane id="ground" position="0 -0.5 0" rotation="-90 0 0" width="{grid_width}" height="{grid_height}" material="src: #ground-mat"></a-plane>
193
+
194
+ <a-entity id="tilemap">
195
+ {''.join(generate_tilemap(files, directory, grid_width, grid_height, bump_map_url))}
196
+ </a-entity>
197
+
198
+ <a-entity light="type: ambient; color: #445451"></a-entity>
199
+ <a-entity light="type: directional; color: #FFF; intensity: 0.8" position="-1 1 2"></a-entity>
200
+ </a-scene>
201
+ """
202
+ st.components.v1.html(aframe_scene, height=600)
203
 
204
  if __name__ == "__main__":
205
  main()