awacke1 commited on
Commit
1d5ce9c
·
verified ·
1 Parent(s): c6705d3

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +231 -0
app.py ADDED
@@ -0,0 +1,231 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import os, base64, shutil, random
3
+ from pathlib import Path
4
+
5
+ @st.cache_data
6
+ 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
+
13
+ /* Draggable, bouncing, moving-light components unchanged */
14
+ AFRAME.registerComponent('draggable', {/* … */});
15
+ AFRAME.registerComponent('bouncing', {/* … */});
16
+ AFRAME.registerComponent('moving-light', {/* … */});
17
+
18
+ function moveCamera(direction) {
19
+ var rig = document.querySelector('#rig');
20
+ var pos = rig.getAttribute('position');
21
+ var rot = rig.getAttribute('rotation');
22
+ var speed = 0.5, rSpeed = 5;
23
+ switch(direction) {
24
+ case 'forward': pos.z -= speed; break;
25
+ case 'backward': pos.z += speed; break;
26
+ case 'left': pos.x -= speed; break;
27
+ case 'right': pos.x += speed; break;
28
+ case 'up': pos.y += speed; break;
29
+ case 'down': pos.y -= speed; break;
30
+ case 'reset': pos = {x:0,y:1.6,z:0}; rot = {x:0,y:0,z:0}; break;
31
+ case 'rotateY+': rot.y += rSpeed; break;
32
+ case 'rotateY-': rot.y -= rSpeed; break;
33
+ case 'rotateZ+': rot.z += rSpeed; break;
34
+ case 'rotateZ-': rot.z -= rSpeed; break;
35
+ case 'zoomIn': rig.object3D.scale.multiplyScalar(0.9); break;
36
+ case 'zoomOut': rig.object3D.scale.multiplyScalar(1.1); break;
37
+ }
38
+ rig.setAttribute('position', pos);
39
+ rig.setAttribute('rotation', rot);
40
+ }
41
+
42
+ function fireRaycast() {
43
+ var cam = document.querySelector('[camera]');
44
+ var dir = new THREE.Vector3(); cam.object3D.getWorldDirection(dir);
45
+ var rc = new THREE.Raycaster(cam.object3D.position, dir);
46
+ var hits = rc.intersectObjects(
47
+ Array.from(document.querySelectorAll('.raycastable')).map(e=>e.object3D), true
48
+ );
49
+ if (hits.length>0) {
50
+ var el = hits[0].object.el;
51
+ if (el.components.bouncing) {
52
+ el.components.bouncing.boost();
53
+ score += 10;
54
+ document.getElementById('score').setAttribute('value','Score: '+score);
55
+ }
56
+ }
57
+ }
58
+
59
+ document.addEventListener('keydown', e => {
60
+ switch(e.key.toLowerCase()){
61
+ case 'arrowup': moveCamera('forward'); break;
62
+ case 'arrowdown': moveCamera('backward'); break;
63
+ case 'arrowleft': moveCamera('left'); break;
64
+ case 'arrowright': moveCamera('right'); break;
65
+ case 'r': moveCamera('reset'); break;
66
+ case 'q': moveCamera('rotateY+'); break;
67
+ case 'e': moveCamera('rotateY-'); break;
68
+ case 'z': moveCamera('rotateZ+'); break;
69
+ case 'c': moveCamera('rotateZ-'); break;
70
+ case 'pageup': moveCamera('zoomIn'); break;
71
+ case 'pagedown': moveCamera('zoomOut'); break;
72
+ case ' ': fireRaycast(); break;
73
+ }
74
+ });
75
+ </script>
76
+ """
77
+
78
+ @st.cache_data
79
+ def encode_file(path):
80
+ with open(path,'rb') as f: return base64.b64encode(f.read()).decode()
81
+
82
+ def create_aframe_entity(stem, ext, pos):
83
+ ry = random.uniform(0,360)
84
+ if ext == 'obj':
85
+ return (f'<a-entity obj-model="obj:#{stem}" '
86
+ f'position="{pos}" rotation="0 {ry} 0" scale="1 1 1" '
87
+ 'class="raycastable" draggable></a-entity>')
88
+ if ext in ('glb','gltf'):
89
+ return (f'<a-entity gltf-model="#{stem}" '
90
+ f'position="{pos}" rotation="0 {ry} 0" scale="1 1 1" '
91
+ 'class="raycastable" draggable></a-entity>')
92
+ return ''
93
+
94
+ @st.cache_data
95
+ def generate_tilemap(files, dirpath, gw=8, gh=8):
96
+ img_exts = ['webp','png']
97
+ model_exts = ['obj','glb','gltf']
98
+ vid_exts = ['mp4']
99
+
100
+ imgs = [f for f in files if f.split('.')[-1] in img_exts]
101
+ models = [f for f in files if f.split('.')[-1] in model_exts]
102
+ vids = [f for f in files if f.split('.')[-1] in vid_exts]
103
+
104
+ assets = "<a-assets>"
105
+ for f in files:
106
+ stem = Path(f).stem; ext=f.split('.')[-1]
107
+ data=encode_file(os.path.join(dirpath,f))
108
+ if ext in model_exts:
109
+ assets += (f'<a-asset-item id="{stem}" '
110
+ f'src="data:application/octet-stream;base64,{data}"></a-asset-item>')
111
+ elif ext in img_exts:
112
+ assets += f'<img id="{stem}" src="data:image/{ext};base64,{data}">'
113
+ elif ext in vid_exts:
114
+ assets += (f'<video id="{stem}" '
115
+ f'src="data:video/mp4;base64,{data}" '
116
+ 'loop autoplay muted></video>')
117
+ assets += "</a-assets>"
118
+
119
+ entities = ""
120
+ sx=-gw/2; sz=-gh/2
121
+ for i in range(gw):
122
+ for j in range(gh):
123
+ x,y,z = sx+i,0,sz+j
124
+ if imgs:
125
+ img=random.choice(imgs); s=Path(img).stem
126
+ entities += (f'<a-plane src="#{s}" width="1" height="1" '
127
+ f'rotation="-90 0 0" position="{x} 0.01 {z}"></a-plane>')
128
+ if models:
129
+ m=random.choice(models); ext=m.split('.')[-1]; s=Path(m).stem
130
+ entities += create_aframe_entity(s,ext,f"{x} 0.5 {z}")
131
+ if vids:
132
+ v=random.choice(vids); s=Path(v).stem
133
+ entities += (f'<a-video src="#{s}" width="1" height="1" '
134
+ f'rotation="-90 0 0" position="{x} 0.2 {z}" '
135
+ 'class="raycastable" draggable></a-video>')
136
+ return assets, entities
137
+
138
+ def main():
139
+ st.set_page_config(layout="wide")
140
+ with st.sidebar:
141
+ st.markdown("### 🧭 Navigation Controls")
142
+ # Pan the View
143
+ st.markdown("**Pan the View** ⬅️➡️")
144
+ cols = st.columns(4)
145
+ cols[0].button("⬅️", on_click=lambda: st.session_state.update({'camera_move': 'left'}))
146
+ cols[1].button("➡️", on_click=lambda: st.session_state.update({'camera_move': 'right'}))
147
+ cols[2].button("⬆️", on_click=lambda: st.session_state.update({'camera_move': 'forward'}))
148
+ cols[3].button("⬇️", on_click=lambda: st.session_state.update({'camera_move': 'backward'}))
149
+
150
+ # Rotate the 3D Scene
151
+ st.markdown("**Rotate the 3D Scene** 🔄")
152
+ cols = st.columns(4)
153
+ cols[0].button("⬅️ Rotate Y+", on_click=lambda: st.session_state.update({'camera_move': 'rotateY+'}))
154
+ cols[1].button("➡️ Rotate Y-", on_click=lambda: st.session_state.update({'camera_move': 'rotateY-'}))
155
+ cols[2].button("↖️ Rotate Z+", on_click=lambda: st.session_state.update({'camera_move': 'rotateZ+'}))
156
+ cols[3].button("↘️ Rotate Z-", on_click=lambda: st.session_state.update({'camera_move': 'rotateZ-'}))
157
+
158
+ # Zoom Controls
159
+ st.markdown("**Zoom** 🔎")
160
+ cols = st.columns(2)
161
+ cols[0].button("➕ Zoom In", on_click=lambda: st.session_state.update({'camera_move': 'zoomIn'}))
162
+ cols[1].button("➖ Zoom Out", on_click=lambda: st.session_state.update({'camera_move': 'zoomOut'}))
163
+
164
+ # Reset View
165
+ st.markdown("**Reset View** 🔄")
166
+ st.button("🔄 Reset", on_click=lambda: st.session_state.update({'camera_move': 'reset'}))
167
+
168
+ st.markdown("### ➕ Add Media Files")
169
+ ups = st.file_uploader("Add files (png, obj, glb, etc.):", accept_multiple_files=True)
170
+ st.markdown("### 📋 Uploaded Model Files")
171
+ directory = st.text_input("Path:", ".", key="dir")
172
+ if os.path.isdir(directory):
173
+ files = [f for f in os.listdir(directory) if f.split('.')[-1] in ['obj', 'glb', 'gltf']]
174
+ if files:
175
+ for i, f in enumerate(files, 1):
176
+ st.markdown(f"{i}. {f}")
177
+ else:
178
+ st.markdown("No model files found.")
179
+
180
+ if not os.path.isdir(directory):
181
+ st.sidebar.error("Invalid directory")
182
+ return
183
+
184
+ types = ['obj','glb','gltf','webp','png','mp4']
185
+ if ups:
186
+ for up in ups:
187
+ ext=Path(up.name).suffix.lower()[1:]
188
+ if ext in types:
189
+ with open(os.path.join(directory,up.name),'wb') as f:
190
+ shutil.copyfileobj(up,f)
191
+ st.sidebar.success(f"Uploaded {up.name}")
192
+ else:
193
+ st.sidebar.warning(f"Skipped {up.name}")
194
+
195
+ files = [f for f in os.listdir(directory) if f.split('.')[-1] in types]
196
+
197
+ spot_h = max(8,8)*1.5
198
+
199
+ scene = f"""
200
+ <a-scene embedded style="height:600px; width:100%;">
201
+ <a-entity id="rig" position="0 1.6 0" rotation="0 0 0">
202
+ <a-camera fov="60" look-controls wasd-controls="enabled:true"
203
+ cursor="rayOrigin:mouse" raycaster="objects:.raycastable">
204
+ </a-camera>
205
+ </a-entity>
206
+ <a-sky color="#87CEEB"></a-sky>
207
+ <a-entity moving-light="color:#FFD700; speed:0.07 0.05 0.06; bounds:4 3 4" position="2 2 -2"></a-entity>
208
+ <a-entity moving-light="color:#FF6347; speed:0.06 0.08 0.05; bounds:4 3 4" position="-2 1 2"></a-entity>
209
+ <a-entity moving-light="color:#00CED1; speed:0.05 0.06 0.07; bounds:4 3 4" position="0 3 0"></a-entity>
210
+ <a-entity light="type:spot; color:#FFF; intensity:1; angle:45; penumbra:0.2"
211
+ position="0 {spot_h} 0" rotation="-90 0 0"></a-entity>
212
+ <a-text id="score" value="Score:0" position="-1.5 2 -3" scale="0.5 0.5 0.5" color="white"></a-text>
213
+ """
214
+
215
+ assets, ents = generate_tilemap(files, directory, 8, 8)
216
+ scene += assets + ents + "</a-scene>"
217
+
218
+ cmd = st.session_state.get('camera_move')
219
+ if cmd:
220
+ scene += f"<script>moveCamera('{cmd}');</script>"
221
+ st.session_state.pop('camera_move')
222
+
223
+ loader = '<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/aframe-extras.loaders.min.js"></script>'
224
+
225
+ st.components.v1.html(
226
+ load_aframe_and_extras() + loader + scene,
227
+ height=650
228
+ )
229
+
230
+ if __name__ == "__main__":
231
+ main()