awacke1 commited on
Commit
c0f5b84
ยท
verified ยท
1 Parent(s): c084cb6

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +240 -0
app.py ADDED
@@ -0,0 +1,240 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st # ๐ŸŒ Streamlit magic
2
+ import streamlit.components.v1 as components # ๐Ÿ–ผ๏ธ Embed custom HTML/JS
3
+ import os # ๐Ÿ“‚ File operations
4
+ import json # ๐Ÿ”„ JSON encoding/decoding
5
+ import pandas as pd # ๐Ÿ“Š DataFrame handling
6
+ import uuid # ๐Ÿ†” Unique IDs
7
+ import math # โž— Math utils
8
+ import time # โณ Time utilities
9
+
10
+ from gamestate import GameState # ๐Ÿ’ผ Shared game-state singleton
11
+
12
+ # ๐Ÿš€ Page setup
13
+ st.set_page_config(page_title="Infinite World Builder", layout="wide")
14
+
15
+ # ๐Ÿ“ Constants for world dimensions & CSV schema
16
+ SAVE_DIR = "saved_worlds"
17
+ PLOT_WIDTH = 50.0 # โ†”๏ธ Plot width in world units
18
+ PLOT_DEPTH = 50.0 # โ†•๏ธ Plot depth in world units
19
+ CSV_COLUMNS = [
20
+ 'obj_id', 'type',
21
+ 'pos_x', 'pos_y', 'pos_z',
22
+ 'rot_x', 'rot_y', 'rot_z', 'rot_order'
23
+ ]
24
+
25
+ # ๐Ÿ—‚๏ธ Ensure directory for plots exists
26
+ os.makedirs(SAVE_DIR, exist_ok=True)
27
+
28
+ @st.cache_data(ttl=3600) # ๐Ÿ•’ Cache for 1h
29
+ def load_plot_metadata():
30
+ # ๐Ÿ” Scan SAVE_DIR for plot CSVs
31
+ try:
32
+ plot_files = [f for f in os.listdir(SAVE_DIR)
33
+ if f.endswith(".csv") and f.startswith("plot_X")]
34
+ except FileNotFoundError:
35
+ st.error(f"Folder '{SAVE_DIR}' missing! ๐Ÿšจ")
36
+ return []
37
+ except Exception as e:
38
+ st.error(f"Error reading '{SAVE_DIR}': {e}")
39
+ return []
40
+
41
+ parsed = []
42
+ for fn in plot_files:
43
+ try:
44
+ parts = fn[:-4].split('_') # strip .csv
45
+ gx = int(parts[1][1:]) # X index
46
+ gz = int(parts[2][1:]) # Z index
47
+ name = " ".join(parts[3:]) if len(parts)>3 else f"Plot({gx},{gz})"
48
+ parsed.append({
49
+ 'id': fn[:-4],
50
+ 'filename': fn,
51
+ 'grid_x': gx,
52
+ 'grid_z': gz,
53
+ 'name': name,
54
+ 'x_offset': gx * PLOT_WIDTH,
55
+ 'z_offset': gz * PLOT_DEPTH
56
+ })
57
+ except Exception:
58
+ st.warning(f"Skip invalid file: {fn}")
59
+ parsed.sort(key=lambda p: (p['grid_x'], p['grid_z']))
60
+ return parsed
61
+
62
+
63
+ def load_plot_objects(filename, x_offset, z_offset):
64
+ # ๐Ÿ“ฅ Load objects from a plot CSV and shift by offsets
65
+ path = os.path.join(SAVE_DIR, filename)
66
+ try:
67
+ df = pd.read_csv(path)
68
+ # ๐Ÿ›ก๏ธ Ensure essentials exist
69
+ if not all(c in df.columns for c in ['type','pos_x','pos_y','pos_z']):
70
+ st.warning(f"Missing cols in {filename} ๐Ÿง")
71
+ return []
72
+ # ๐Ÿ†” Guarantee obj_id
73
+ df['obj_id'] = df.get('obj_id', pd.Series([str(uuid.uuid4()) for _ in df.index]))
74
+ # ๐Ÿ”„ Fill missing rotation
75
+ for col, default in [('rot_x',0.0),('rot_y',0.0),('rot_z',0.0),('rot_order','XYZ')]:
76
+ if col not in df.columns:
77
+ df[col] = default
78
+
79
+ objs = []
80
+ for _, row in df.iterrows():
81
+ o = row.to_dict()
82
+ o['pos_x'] += x_offset
83
+ o['pos_z'] += z_offset
84
+ objs.append(o)
85
+ return objs
86
+ except FileNotFoundError:
87
+ st.error(f"CSV not found: {filename}")
88
+ return []
89
+ except pd.errors.EmptyDataError:
90
+ return []
91
+ except Exception as e:
92
+ st.error(f"Load error {filename}: {e}")
93
+ return []
94
+
95
+
96
+ def save_plot_data(filename, objects_list, px, pz):
97
+ # ๐Ÿ’พ Save list of new objects relative to plot origin
98
+ path = os.path.join(SAVE_DIR, filename)
99
+ if not isinstance(objects_list, list):
100
+ st.error("๐Ÿ‘Ž Invalid data format for save")
101
+ return False
102
+
103
+ rel = []
104
+ for o in objects_list:
105
+ pos = o.get('position', {})
106
+ rot = o.get('rotation', {})
107
+ typ = o.get('type','Unknown')
108
+ oid = o.get('obj_id', str(uuid.uuid4()))
109
+ # ๐Ÿ›‘ Skip bad objects
110
+ if not all(k in pos for k in ['x','y','z']) or typ=='Unknown':
111
+ continue
112
+ rel.append({
113
+ 'obj_id': oid, 'type': typ,
114
+ 'pos_x': pos['x']-px, 'pos_y': pos['y'], 'pos_z': pos['z']-pz,
115
+ 'rot_x': rot.get('_x',0.0), 'rot_y': rot.get('_y',0.0),
116
+ 'rot_z': rot.get('_z',0.0), 'rot_order': rot.get('_order','XYZ')
117
+ })
118
+ try:
119
+ pd.DataFrame(rel, columns=CSV_COLUMNS).to_csv(path, index=False)
120
+ st.success(f"๐ŸŽ‰ Saved {len(rel)} to {filename}")
121
+ return True
122
+ except Exception as e:
123
+ st.error(f"Save failed: {e}")
124
+ return False
125
+
126
+ # ๐Ÿ”’ Singleton for global world state
127
+ @st.cache_resource
128
+ def get_game_state():
129
+ return GameState(save_dir=SAVE_DIR, csv_filename="world_state.csv")
130
+
131
+ game_state = get_game_state()
132
+
133
+ # ๐Ÿง  Session state defaults
134
+ st.session_state.setdefault('selected_object','None')
135
+ st.session_state.setdefault('new_plot_name','')
136
+ st.session_state.setdefault('js_save_data_result',None)
137
+
138
+ # ๐Ÿ”„ Load everything
139
+ plots_metadata = load_plot_metadata()
140
+ all_initial_objects = []
141
+ for p in plots_metadata:
142
+ all_initial_objects += load_plot_objects(p['filename'], p['x_offset'], p['z_offset'])
143
+
144
+ # ๐Ÿ–ฅ๏ธ Sidebar UI
145
+ with st.sidebar:
146
+ st.title("๐Ÿ—๏ธ World Controls")
147
+ st.header("๐Ÿ“ Navigate Plots")
148
+ cols = st.columns(2)
149
+ i = 0
150
+ for p in sorted(plots_metadata, key=lambda x:(x['grid_x'],x['grid_z'])):
151
+ label = f"โžก๏ธ {p['name']} ({p['grid_x']},{p['grid_z']})"
152
+ if cols[i].button(label, key=f"nav_{p['id']}"):
153
+ try:
154
+ from streamlit_js_eval import streamlit_js_eval
155
+ js = f"teleportPlayer({p['x_offset']+PLOT_WIDTH/2},{p['z_offset']+PLOT_DEPTH/2});"
156
+ streamlit_js_eval(js_code=js, key=f"tp_{p['id']}")
157
+ except Exception as e:
158
+ st.error(f"TP fail: {e}")
159
+ i = (i+1)%2
160
+
161
+ st.markdown("---")
162
+ st.header("๐ŸŒฒ Place Objects")
163
+ opts = ["None","Simple House","Tree","Rock","Fence Post"]
164
+ idx = opts.index(st.session_state.selected_object) if st.session_state.selected_object in opts else 0
165
+ sel = st.selectbox("Select:", opts, index=idx, key="selected_object_widget")
166
+ if sel != st.session_state.selected_object:
167
+ st.session_state.selected_object = sel
168
+
169
+ st.markdown("---")
170
+ st.header("๐Ÿ’พ Save Work")
171
+ if st.button("๐Ÿ’พ Save Current Work", key="save_button"):
172
+ from streamlit_js_eval import streamlit_js_eval
173
+ streamlit_js_eval(js_code="getSaveDataAndPosition();", key="js_save_processor")
174
+ st.rerun()
175
+
176
+ # ๐Ÿ“จ Handle incoming save data
177
+ raw = st.session_state.get("js_save_processor")
178
+ if raw:
179
+ st.info("๐Ÿ“ฌ Got save data!")
180
+ ok=False
181
+ try:
182
+ pay = json.loads(raw) if isinstance(raw,str) else raw
183
+ pos, objs = pay.get('playerPosition'), pay.get('objectsToSave')
184
+ if isinstance(objs,list) and pos:
185
+ gx, gz = math.floor(pos['x']/PLOT_WIDTH), math.floor(pos['z']/PLOT_DEPTH)
186
+ fn = f"plot_X{gx}_Z{gz}.csv"
187
+ if save_plot_data(fn, objs, gx*PLOT_WIDTH, gz*PLOT_DEPTH):
188
+ load_plot_metadata.clear()
189
+ try:
190
+ from streamlit_js_eval import streamlit_js_eval
191
+ streamlit_js_eval(js_code="resetNewlyPlacedObjects();", key="reset_js")
192
+ except:
193
+ pass
194
+ game_state.update_state(objs)
195
+ ok=True
196
+ if not ok:
197
+ st.error("โŒ Save error")
198
+ except Exception as e:
199
+ st.error(f"Err: {e}")
200
+ st.session_state.js_save_processor=None
201
+ if ok: st.rerun()
202
+
203
+ # ๐Ÿ  Main view
204
+ st.header("๐ŸŒ Infinite Shared 3D World")
205
+ st.caption("โžก๏ธ Explore, click to build, ๐Ÿ’พ to save!")
206
+
207
+ # ๐Ÿ”Œ Inject state into JS
208
+ state = {
209
+ "ALL_INITIAL_OBJECTS": all_initial_objects,
210
+ "PLOTS_METADATA": plots_metadata,
211
+ "SELECTED_OBJECT_TYPE": st.session_state.selected_object,
212
+ "PLOT_WIDTH": PLOT_WIDTH,
213
+ "PLOT_DEPTH": PLOT_DEPTH,
214
+ "GAME_STATE": game_state.get_state()
215
+ }
216
+
217
+ try:
218
+ with open('index.html','r',encoding='utf-8') as f:
219
+ html = f.read()
220
+ script = f"""
221
+ <script>
222
+ window.ALL_INITIAL_OBJECTS = {json.dumps(state['ALL_INITIAL_OBJECTS'])};
223
+ window.PLOTS_METADATA = {json.dumps(state['PLOTS_METADATA'])};
224
+ window.SELECTED_OBJECT_TYPE = {json.dumps(state['SELECTED_OBJECT_TYPE'])};
225
+ window.PLOT_WIDTH = {json.dumps(state['PLOT_WIDTH'])};
226
+ window.PLOT_DEPTH = {json.dumps(state['PLOT_DEPTH'])};
227
+ window.GAME_STATE = {json.dumps(state['GAME_STATE'])};
228
+ console.log('๐Ÿ‘ State injected!', {{
229
+ objs: window.ALL_INITIAL_OBJECTS.length,
230
+ plots: window.PLOTS_METADATA.length,
231
+ gs: window.GAME_STATE.length
232
+ }});
233
+ </script>
234
+ """
235
+ html = html.replace('</head>', script + '\n</head>', 1)
236
+ components.html(html, height=750, scrolling=False)
237
+ except FileNotFoundError:
238
+ st.error("โŒ index.html missing!")
239
+ except Exception as e:
240
+ st.error(f"๐Ÿ˜ฑ HTML inject failed: {e}")