Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
@@ -2,6 +2,7 @@ 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 """
|
@@ -17,6 +18,7 @@ def load_aframe_and_extras():
|
|
17 |
let maxRotSpeed = 2;
|
18 |
let rotAccel = 0.1;
|
19 |
|
|
|
20 |
AFRAME.registerComponent('draggable', {
|
21 |
init: function () {
|
22 |
this.el.addEventListener('mousedown', this.onMouseDown.bind(this));
|
@@ -66,9 +68,11 @@ def load_aframe_and_extras():
|
|
66 |
}
|
67 |
});
|
68 |
|
|
|
69 |
AFRAME.registerComponent('bouncing', {/* โฆ */});
|
70 |
AFRAME.registerComponent('moving-light', {/* โฆ */});
|
71 |
|
|
|
72 |
function moveCamera(view) {
|
73 |
var rig = document.querySelector('#rig');
|
74 |
var gw = 8, gh = 8;
|
@@ -78,7 +82,7 @@ def load_aframe_and_extras():
|
|
78 |
var quadrantOffset = Math.max(gw, gh) / 4; // 2 units to center on quadrants
|
79 |
var pos, rot;
|
80 |
|
81 |
-
rot = {x: -90, y: 0, z: 0}; // Always top-down view
|
82 |
|
83 |
switch(view) {
|
84 |
case 'center':
|
@@ -113,6 +117,7 @@ def load_aframe_and_extras():
|
|
113 |
rig.setAttribute('rotation', rot);
|
114 |
}
|
115 |
|
|
|
116 |
function flyCamera(action) {
|
117 |
var rig = document.querySelector('#rig');
|
118 |
var pos = rig.getAttribute('position');
|
@@ -169,6 +174,7 @@ def load_aframe_and_extras():
|
|
169 |
rig.setAttribute('position', pos);
|
170 |
}
|
171 |
|
|
|
172 |
function updateCameraPosition() {
|
173 |
var rig = document.querySelector('#rig');
|
174 |
var pos = rig.getAttribute('position');
|
@@ -189,6 +195,7 @@ def load_aframe_and_extras():
|
|
189 |
|
190 |
requestAnimationFrame(updateCameraPosition);
|
191 |
|
|
|
192 |
function fireRaycast() {
|
193 |
var cam = document.querySelector('[camera]');
|
194 |
var dir = new THREE.Vector3(); cam.object3D.getWorldDirection(dir);
|
@@ -206,6 +213,7 @@ def load_aframe_and_extras():
|
|
206 |
}
|
207 |
}
|
208 |
|
|
|
209 |
document.addEventListener('keydown', e => {
|
210 |
switch(e.key.toLowerCase()){
|
211 |
case '1': moveCamera('center'); break;
|
@@ -232,10 +240,12 @@ def load_aframe_and_extras():
|
|
232 |
</script>
|
233 |
"""
|
234 |
|
|
|
235 |
@st.cache_data
|
236 |
def encode_file(path):
|
237 |
with open(path,'rb') as f: return base64.b64encode(f.read()).decode()
|
238 |
|
|
|
239 |
def create_aframe_entity(stem, ext, pos):
|
240 |
ry = random.uniform(0,360)
|
241 |
if ext == 'obj':
|
@@ -248,6 +258,7 @@ def create_aframe_entity(stem, ext, pos):
|
|
248 |
'class="raycastable" draggable></a-entity>')
|
249 |
return ''
|
250 |
|
|
|
251 |
@st.cache_data
|
252 |
def generate_tilemap(files, dirpath, gw=8, gh=8):
|
253 |
img_exts = ['webp','png','jpeg','jpg']
|
@@ -258,6 +269,7 @@ def generate_tilemap(files, dirpath, gw=8, gh=8):
|
|
258 |
models = [f for f in files if f.split('.')[-1] in model_exts]
|
259 |
vids = [f for f in files if f.split('.')[-1] in vid_exts]
|
260 |
|
|
|
261 |
assets = "<a-assets>"
|
262 |
for f in files:
|
263 |
stem = Path(f).stem; ext=f.split('.')[-1]
|
@@ -273,7 +285,9 @@ def generate_tilemap(files, dirpath, gw=8, gh=8):
|
|
273 |
'loop autoplay muted></video>')
|
274 |
assets += "</a-assets>"
|
275 |
|
|
|
276 |
entities = ""
|
|
|
277 |
if vids:
|
278 |
v = vids[0]; s = Path(v).stem
|
279 |
entities += (f'<a-video src="#{s}" width="{gw}" height="{gh}" '
|
@@ -295,11 +309,14 @@ def generate_tilemap(files, dirpath, gw=8, gh=8):
|
|
295 |
if models:
|
296 |
m = random.choice(models); ext = m.split('.')[-1]; s = Path(m).stem
|
297 |
entities += create_aframe_entity(s, ext, f"{x} 0 {z}")
|
298 |
-
|
|
|
299 |
|
|
|
300 |
def main():
|
301 |
st.set_page_config(layout="wide")
|
302 |
with st.sidebar:
|
|
|
303 |
st.markdown("### ๐งญ Camera Views")
|
304 |
st.markdown("**Select Quadrant** ๐ท")
|
305 |
cols = st.columns(3)
|
@@ -315,22 +332,32 @@ def main():
|
|
315 |
cols[1].button("โฌ๏ธ Bottom", on_click=lambda: st.session_state.update({'camera_view': 'back'}))
|
316 |
cols[2].button("โ๏ธ Bottom-Right", on_click=lambda: st.session_state.update({'camera_view': 'angle4'}))
|
317 |
|
|
|
318 |
st.markdown("### โ Add Media Files")
|
319 |
-
ups = st.file_uploader(
|
|
|
|
|
|
|
320 |
st.markdown("### ๐ Uploaded Model Files")
|
321 |
directory = st.text_input("Path:", ".", key="dir")
|
322 |
if os.path.isdir(directory):
|
323 |
files = [f for f in os.listdir(directory) if f.split('.')[-1] in ['obj', 'glb', 'gltf']]
|
324 |
if files:
|
325 |
for i, f in enumerate(files, 1):
|
326 |
-
|
|
|
|
|
|
|
|
|
327 |
else:
|
328 |
-
st.markdown("No model files found.")
|
329 |
|
|
|
330 |
if not os.path.isdir(directory):
|
331 |
-
st.sidebar.error("Invalid directory")
|
332 |
return
|
333 |
|
|
|
334 |
types = ['obj','glb','gltf','webp','png','jpeg','jpg','mp4']
|
335 |
if ups:
|
336 |
for up in ups:
|
@@ -338,14 +365,16 @@ def main():
|
|
338 |
if ext in types:
|
339 |
with open(os.path.join(directory,up.name),'wb') as f:
|
340 |
shutil.copyfileobj(up,f)
|
341 |
-
|
|
|
342 |
else:
|
343 |
-
st.sidebar.warning(f"Skipped {up.name}")
|
344 |
|
345 |
files = [f for f in os.listdir(directory) if f.split('.')[-1] in types]
|
346 |
|
347 |
spot_h = max(8,8)*1.5
|
348 |
|
|
|
349 |
scene = f"""
|
350 |
<a-scene embedded style="height:100vh; width:100vw; position:fixed; top:0; left:0;">
|
351 |
<a-entity id="rig" position="0 12 0" rotation="-90 0 0">
|
@@ -362,9 +391,12 @@ def main():
|
|
362 |
<a-text id="score" value="Score:0" position="-1.5 2 -3" scale="0.5 0.5 0.5" color="white"></a-text>
|
363 |
"""
|
364 |
|
365 |
-
|
|
|
|
|
366 |
scene += assets + ents + "</a-scene>"
|
367 |
|
|
|
368 |
view_cmd = st.session_state.get('camera_view', 'center')
|
369 |
if view_cmd:
|
370 |
scene += f"<script>moveCamera('{view_cmd}');</script>"
|
@@ -382,5 +414,6 @@ def main():
|
|
382 |
height=1000
|
383 |
)
|
384 |
|
|
|
385 |
if __name__ == "__main__":
|
386 |
main()
|
|
|
2 |
import os, base64, shutil, random
|
3 |
from pathlib import Path
|
4 |
|
5 |
+
# ๐ 1. Load Aframe and Scripts: Let's get the party started with Aframe and friends! ๐
|
6 |
@st.cache_data
|
7 |
def load_aframe_and_extras():
|
8 |
return """
|
|
|
18 |
let maxRotSpeed = 2;
|
19 |
let rotAccel = 0.1;
|
20 |
|
21 |
+
// ๐ฑ๏ธ 1.1 Draggable Component: Click and drag like a pro! ๐๏ธ
|
22 |
AFRAME.registerComponent('draggable', {
|
23 |
init: function () {
|
24 |
this.el.addEventListener('mousedown', this.onMouseDown.bind(this));
|
|
|
68 |
}
|
69 |
});
|
70 |
|
71 |
+
// โน๏ธ 1.2 Bouncing and Lights: Keep the scene lively! โจ
|
72 |
AFRAME.registerComponent('bouncing', {/* โฆ */});
|
73 |
AFRAME.registerComponent('moving-light', {/* โฆ */});
|
74 |
|
75 |
+
// ๐ธ 1.3 Move Camera: Snap to your favorite quadrant views! ๐ผ๏ธ
|
76 |
function moveCamera(view) {
|
77 |
var rig = document.querySelector('#rig');
|
78 |
var gw = 8, gh = 8;
|
|
|
82 |
var quadrantOffset = Math.max(gw, gh) / 4; // 2 units to center on quadrants
|
83 |
var pos, rot;
|
84 |
|
85 |
+
rot = {x: -90, y: 0, z: 0}; // Always top-down view ๐
|
86 |
|
87 |
switch(view) {
|
88 |
case 'center':
|
|
|
117 |
rig.setAttribute('rotation', rot);
|
118 |
}
|
119 |
|
120 |
+
// โ๏ธ 1.4 Fly Camera: Soar through the scene with style! ๐ฉ๏ธ
|
121 |
function flyCamera(action) {
|
122 |
var rig = document.querySelector('#rig');
|
123 |
var pos = rig.getAttribute('position');
|
|
|
174 |
rig.setAttribute('position', pos);
|
175 |
}
|
176 |
|
177 |
+
// ๐ 1.5 Update Camera: Keep the camera flying smoothly! ๐๏ธ
|
178 |
function updateCameraPosition() {
|
179 |
var rig = document.querySelector('#rig');
|
180 |
var pos = rig.getAttribute('position');
|
|
|
195 |
|
196 |
requestAnimationFrame(updateCameraPosition);
|
197 |
|
198 |
+
// ๐ฏ 1.6 Fire Raycast: Zap objects with a laser beam! โก
|
199 |
function fireRaycast() {
|
200 |
var cam = document.querySelector('[camera]');
|
201 |
var dir = new THREE.Vector3(); cam.object3D.getWorldDirection(dir);
|
|
|
213 |
}
|
214 |
}
|
215 |
|
216 |
+
// โจ๏ธ 1.7 Keyboard Listener: Take control with your keys! ๐ฎ
|
217 |
document.addEventListener('keydown', e => {
|
218 |
switch(e.key.toLowerCase()){
|
219 |
case '1': moveCamera('center'); break;
|
|
|
240 |
</script>
|
241 |
"""
|
242 |
|
243 |
+
# ๐ 2. Encode File: Sneakily encode files into base64! ๐ต๏ธ
|
244 |
@st.cache_data
|
245 |
def encode_file(path):
|
246 |
with open(path,'rb') as f: return base64.b64encode(f.read()).decode()
|
247 |
|
248 |
+
# ๐ฐ 3. Create Aframe Entity: Build magical 3D models! ๐ช
|
249 |
def create_aframe_entity(stem, ext, pos):
|
250 |
ry = random.uniform(0,360)
|
251 |
if ext == 'obj':
|
|
|
258 |
'class="raycastable" draggable></a-entity>')
|
259 |
return ''
|
260 |
|
261 |
+
# ๐บ๏ธ 4. Generate Tilemap: Craft the world one tile at a time! ๐
|
262 |
@st.cache_data
|
263 |
def generate_tilemap(files, dirpath, gw=8, gh=8):
|
264 |
img_exts = ['webp','png','jpeg','jpg']
|
|
|
269 |
models = [f for f in files if f.split('.')[-1] in model_exts]
|
270 |
vids = [f for f in files if f.split('.')[-1] in vid_exts]
|
271 |
|
272 |
+
# ๐ฆ 4.1 Load Assets: Pack up all the goodies! ๐
|
273 |
assets = "<a-assets>"
|
274 |
for f in files:
|
275 |
stem = Path(f).stem; ext=f.split('.')[-1]
|
|
|
285 |
'loop autoplay muted></video>')
|
286 |
assets += "</a-assets>"
|
287 |
|
288 |
+
# ๐๏ธ 4.2 Build the Scene: Lay out the tiles and models! ๐ก
|
289 |
entities = ""
|
290 |
+
model_counts = {f: 0 for f in models} # Track spawned model counts ๐
|
291 |
if vids:
|
292 |
v = vids[0]; s = Path(v).stem
|
293 |
entities += (f'<a-video src="#{s}" width="{gw}" height="{gh}" '
|
|
|
309 |
if models:
|
310 |
m = random.choice(models); ext = m.split('.')[-1]; s = Path(m).stem
|
311 |
entities += create_aframe_entity(s, ext, f"{x} 0 {z}")
|
312 |
+
model_counts[m] += 1 # Increment count for this model ๐
|
313 |
+
return assets, entities, model_counts
|
314 |
|
315 |
+
# ๐จ 5. Main Function: Paint the canvas of your 3D world! ๐๏ธ
|
316 |
def main():
|
317 |
st.set_page_config(layout="wide")
|
318 |
with st.sidebar:
|
319 |
+
# ๐งญ 5.1 Camera Views UI: Pick your perfect angle! ๐
|
320 |
st.markdown("### ๐งญ Camera Views")
|
321 |
st.markdown("**Select Quadrant** ๐ท")
|
322 |
cols = st.columns(3)
|
|
|
332 |
cols[1].button("โฌ๏ธ Bottom", on_click=lambda: st.session_state.update({'camera_view': 'back'}))
|
333 |
cols[2].button("โ๏ธ Bottom-Right", on_click=lambda: st.session_state.update({'camera_view': 'angle4'}))
|
334 |
|
335 |
+
# ๐ 5.2 File Uploader: Drop your media treasures here! ๐
|
336 |
st.markdown("### โ Add Media Files")
|
337 |
+
ups = st.file_uploader(
|
338 |
+
"Add files (๐ผ๏ธ png/jpeg, ๐ฐ obj/glb/gltf, ๐น mp4, etc.):",
|
339 |
+
accept_multiple_files=True
|
340 |
+
)
|
341 |
st.markdown("### ๐ Uploaded Model Files")
|
342 |
directory = st.text_input("Path:", ".", key="dir")
|
343 |
if os.path.isdir(directory):
|
344 |
files = [f for f in os.listdir(directory) if f.split('.')[-1] in ['obj', 'glb', 'gltf']]
|
345 |
if files:
|
346 |
for i, f in enumerate(files, 1):
|
347 |
+
# Add emoji based on file type and show spawn count ๐๏ธ
|
348 |
+
ext = f.split('.')[-1]
|
349 |
+
emoji = "๐ฐ" if ext == 'obj' else "๐ฏ" # ๐ฐ for obj, ๐ฏ for glb/gltf
|
350 |
+
count = st.session_state.get('model_counts', {}).get(f, 0)
|
351 |
+
st.markdown(f"{i}. {emoji} {f} (Spawned: {count})")
|
352 |
else:
|
353 |
+
st.markdown("No model files found. ๐ธ๏ธ")
|
354 |
|
355 |
+
# ๐จ 5.3 Directory Check: Make sure the path exists! ๐ค๏ธ
|
356 |
if not os.path.isdir(directory):
|
357 |
+
st.sidebar.error("Invalid directory ๐ซ")
|
358 |
return
|
359 |
|
360 |
+
# ๐ฅ 5.4 Handle Uploads: Bring in the new files! ๐ฆ
|
361 |
types = ['obj','glb','gltf','webp','png','jpeg','jpg','mp4']
|
362 |
if ups:
|
363 |
for up in ups:
|
|
|
365 |
if ext in types:
|
366 |
with open(os.path.join(directory,up.name),'wb') as f:
|
367 |
shutil.copyfileobj(up,f)
|
368 |
+
emoji = "๐ผ๏ธ" if ext in ['webp', 'png', 'jpeg', 'jpg'] else "๐ฐ" if ext in ['obj', 'glb', 'gltf'] else "๐น"
|
369 |
+
st.sidebar.success(f"Uploaded {emoji} {up.name}")
|
370 |
else:
|
371 |
+
st.sidebar.warning(f"Skipped {up.name} ๐ง")
|
372 |
|
373 |
files = [f for f in os.listdir(directory) if f.split('.')[-1] in types]
|
374 |
|
375 |
spot_h = max(8,8)*1.5
|
376 |
|
377 |
+
# ๐ 5.5 Build the Scene: Create your 3D masterpiece! ๐ฌ
|
378 |
scene = f"""
|
379 |
<a-scene embedded style="height:100vh; width:100vw; position:fixed; top:0; left:0;">
|
380 |
<a-entity id="rig" position="0 12 0" rotation="-90 0 0">
|
|
|
391 |
<a-text id="score" value="Score:0" position="-1.5 2 -3" scale="0.5 0.5 0.5" color="white"></a-text>
|
392 |
"""
|
393 |
|
394 |
+
# ๐๏ธ 5.6 Add Assets and Entities: Fill the world with wonders! ๐
|
395 |
+
assets, ents, model_counts = generate_tilemap(files, directory, 8, 8)
|
396 |
+
st.session_state['model_counts'] = model_counts # Store counts for display ๐
|
397 |
scene += assets + ents + "</a-scene>"
|
398 |
|
399 |
+
# ๐ฅ 5.7 Apply Camera Actions: Lights, camera, action! ๐ฌ
|
400 |
view_cmd = st.session_state.get('camera_view', 'center')
|
401 |
if view_cmd:
|
402 |
scene += f"<script>moveCamera('{view_cmd}');</script>"
|
|
|
414 |
height=1000
|
415 |
)
|
416 |
|
417 |
+
# ๐ 6. Run the Show: Letโs launch this 3D adventure! ๐ญ
|
418 |
if __name__ == "__main__":
|
419 |
main()
|