awacke1 commited on
Commit
bf30843
Β·
verified Β·
1 Parent(s): 4ecdf6e

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +149 -88
app.py CHANGED
@@ -1,11 +1,10 @@
1
- # app.py
2
  import streamlit as st # 🌐 Streamlit magic
3
  import streamlit.components.v1 as components # πŸ–ΌοΈ Embed custom HTML/JS
4
  import os # πŸ“‚ File operations
5
  import json # πŸ”„ JSON encoding/decoding
6
  import pandas as pd # πŸ“Š DataFrame handling
7
  import uuid # πŸ†” Unique IDs
8
- import math # βž— Math utilities
9
  import time # ⏳ Time utilities
10
 
11
  from gamestate import GameState # πŸ’Ό Shared game-state singleton
@@ -23,157 +22,219 @@ CSV_COLUMNS = [
23
  'rot_x', 'rot_y', 'rot_z', 'rot_order'
24
  ]
25
 
26
- # πŸ—‚οΈ Ensure directory for saved plots
27
  os.makedirs(SAVE_DIR, exist_ok=True)
28
 
29
- # πŸ”§ Load prefab kits definitions
30
- @st.cache_data(ttl=3600)
31
- def load_kits():
32
- try:
33
- with open("kits.json", "r", encoding="utf-8") as f:
34
- return json.load(f)
35
- except FileNotFoundError:
36
- st.error("kits.json missing! πŸ“¦")
37
- return {}
38
- except Exception as e:
39
- st.error(f"Error loading kits.json: {e}")
40
- return {}
41
-
42
- kits = load_kits() # πŸ”— name β†’ module list
43
-
44
- # --- Existing load/save/state functions ---
45
- @st.cache_data(ttl=3600)
46
  def load_plot_metadata():
 
47
  try:
48
- files = [f for f in os.listdir(SAVE_DIR) if f.endswith('.csv') and f.startswith('plot_X')]
 
 
 
 
49
  except Exception as e:
50
- st.error(f"Error scanning {SAVE_DIR}: {e}")
51
  return []
 
52
  parsed = []
53
- for fn in files:
54
  try:
55
- parts = fn[:-4].split('_') # remove .csv
56
- gx = int(parts[1][1:]); gz = int(parts[2][1:])
57
- name = ' '.join(parts[3:]) if len(parts)>3 else f"Plot({gx},{gz})"
 
58
  parsed.append({
59
- 'id': fn[:-4], 'filename': fn,
60
- 'grid_x': gx, 'grid_z': gz,
 
 
61
  'name': name,
62
- 'x_offset': gx*PLOT_WIDTH,
63
- 'z_offset': gz*PLOT_DEPTH
64
  })
65
- except:
66
- st.warning(f"Skipping invalid file: {fn}")
67
  parsed.sort(key=lambda p: (p['grid_x'], p['grid_z']))
68
  return parsed
69
 
70
 
71
  def load_plot_objects(filename, x_offset, z_offset):
 
72
  path = os.path.join(SAVE_DIR, filename)
73
  try:
74
  df = pd.read_csv(path)
 
75
  if not all(c in df.columns for c in ['type','pos_x','pos_y','pos_z']):
76
- st.warning(f"Missing cols in {filename}")
77
  return []
 
78
  df['obj_id'] = df.get('obj_id', pd.Series([str(uuid.uuid4()) for _ in df.index]))
 
79
  for col, default in [('rot_x',0.0),('rot_y',0.0),('rot_z',0.0),('rot_order','XYZ')]:
80
- if col not in df.columns: df[col]=default
 
 
81
  objs = []
82
- for _, r in df.iterrows():
83
- o = r.to_dict()
84
- o['pos_x'] += x_offset; o['pos_z'] += z_offset
 
85
  objs.append(o)
86
  return objs
87
- except Exception:
 
 
 
 
 
 
88
  return []
89
 
90
 
91
  def save_plot_data(filename, objects_list, px, pz):
 
92
  path = os.path.join(SAVE_DIR, filename)
93
  if not isinstance(objects_list, list):
94
- st.error("Invalid save data")
95
  return False
96
- rel=[]
 
97
  for o in objects_list:
98
- pos=o.get('position',{}); rot=o.get('rotation',{})
99
- typ=o.get('type','Unknown'); oid=o.get('obj_id',str(uuid.uuid4()))
100
- if not all(k in pos for k in ['x','y','z']) or typ=='Unknown': continue
 
 
 
 
101
  rel.append({
102
- 'obj_id':oid,'type':typ,
103
- 'pos_x':pos['x']-px,'pos_y':pos['y'],'pos_z':pos['z']-pz,
104
- 'rot_x':rot.get('_x',0.0),'rot_y':rot.get('_y',0.0),'rot_z':rot.get('_z',0.0),'rot_order':rot.get('_order','XYZ')
 
105
  })
106
  try:
107
- pd.DataFrame(rel,columns=CSV_COLUMNS).to_csv(path,index=False)
108
- st.success(f"Saved {len(rel)} to {filename}")
109
  return True
110
  except Exception as e:
111
  st.error(f"Save failed: {e}")
112
  return False
113
 
 
114
  @st.cache_resource
115
  def get_game_state():
116
- return GameState(save_dir=SAVE_DIR,csv_filename='world_state.csv')
117
 
118
- game_state=get_game_state()
119
 
120
- # 🧠 Session defaults
121
  st.session_state.setdefault('selected_object','None')
 
 
122
 
123
- # πŸ”„ Load metadata & objects
124
- plots_metadata=load_plot_metadata()
125
- all_initial_objects=[]
126
  for p in plots_metadata:
127
- all_initial_objects+=load_plot_objects(p['filename'],p['x_offset'],p['z_offset'])
128
 
129
- # πŸ–₯️ Sidebar UI\ with kits
130
  with st.sidebar:
131
  st.title("πŸ—οΈ World Controls")
132
- st.header("🌟 Place Kits")
133
- options=["None"]+list(kits.keys())
134
- idx=options.index(st.session_state.selected_object) if st.session_state.selected_object in options else 0
135
- sel=st.selectbox("Select Kit:",options,index=idx,key="selected_object_widget")
136
- if sel!=st.session_state.selected_object:
137
- st.session_state.selected_object=sel
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
138
  st.markdown("---")
139
  st.header("πŸ’Ύ Save Work")
140
- if st.button("πŸ’Ύ Save Current Work"):
141
  from streamlit_js_eval import streamlit_js_eval
142
- streamlit_js_eval(js_code="getSaveDataAndPosition();",key="js_save_processor")
143
  st.rerun()
144
 
145
- # 🏠 Main area
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
146
  st.header("🌍 Infinite Shared 3D World")
147
- st.caption("Click to explore & place kits, then πŸ’Ύ to save!")
148
 
149
  # πŸ”Œ Inject state into JS
150
- injected_state={
151
- "ALL_INITIAL_OBJECTS":all_initial_objects,
152
- "PLOTS_METADATA":plots_metadata,
153
- "SELECTED_OBJECT_TYPE":st.session_state.selected_object,
154
- "PLOT_WIDTH":PLOT_WIDTH,
155
- "PLOT_DEPTH":PLOT_DEPTH,
156
- "GAME_STATE":game_state.get_state(),
157
- "KITS":kits
158
  }
159
 
160
  try:
161
- html=open('index.html','r',encoding='utf-8').read()
162
- script=f"""
 
163
  <script>
164
- window.ALL_INITIAL_OBJECTS={json.dumps(injected_state['ALL_INITIAL_OBJECTS'])};
165
- window.PLOTS_METADATA={json.dumps(injected_state['PLOTS_METADATA'])};
166
- window.SELECTED_OBJECT_TYPE={json.dumps(injected_state['SELECTED_OBJECT_TYPE'])};
167
- window.PLOT_WIDTH={json.dumps(injected_state['PLOT_WIDTH'])};
168
- window.PLOT_DEPTH={json.dumps(injected_state['PLOT_DEPTH'])};
169
- window.GAME_STATE={json.dumps(injected_state['GAME_STATE'])};
170
- window.KITS={json.dumps(injected_state['KITS'])};
171
- console.log('State injected',{{objs:window.ALL_INITIAL_OBJECTS.length,plots:window.PLOTS_METADATA.length, kits:Object.keys(window.KITS).length}});
 
 
 
172
  </script>
173
  """
174
- html_mod=html.replace('</head>',script+'\n</head>',1)
175
- components.html(html_mod,height=750,scrolling=False)
176
  except FileNotFoundError:
177
- st.error("index.html missing!")
178
  except Exception as e:
179
- st.error(f"HTML inject error: {e}")
 
 
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
 
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}")