awacke1 commited on
Commit
0a0dfe3
·
verified ·
1 Parent(s): bb2022a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +158 -21
app.py CHANGED
@@ -14,8 +14,8 @@ 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',
@@ -25,14 +25,102 @@ CSV_COLUMNS = [
25
  # 🗂️ Ensure directory for plots exists
26
  os.makedirs(SAVE_DIR, exist_ok=True)
27
 
28
- # your existing load_plot_metadata, load_plot_objects, save_plot_data, GameState, etc. …
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
 
30
  # 🧠 Session state defaults
31
- st.session_state.setdefault('selected_object','None')
32
- st.session_state.setdefault('new_plot_name','')
33
- st.session_state.setdefault('js_save_data_result',None)
34
 
35
- # 🔄 Load everything
36
  plots_metadata = load_plot_metadata()
37
  all_initial_objects = []
38
  for p in plots_metadata:
@@ -43,23 +131,21 @@ with st.sidebar:
43
  st.title("🏗️ World Controls")
44
  st.header("📍 Navigate Plots")
45
  cols = st.columns(2)
46
- i = 0
47
- for p in sorted(plots_metadata, key=lambda x:(x['grid_x'],x['grid_z'])):
48
  label = f"➡️ {p['name']} ({p['grid_x']},{p['grid_z']})"
49
- if cols[i].button(label, key=f"nav_{p['id']}"):
 
 
50
  try:
51
- from streamlit_js_eval import streamlit_js_eval
52
- js = f"teleportPlayer({p['x_offset']+PLOT_WIDTH/2},{p['z_offset']+PLOT_DEPTH/2});"
53
  streamlit_js_eval(js_code=js, key=f"tp_{p['id']}")
54
  except Exception as e:
55
- st.error(f"TP fail: {e}")
56
- i = (i+1)%2
57
 
58
  st.markdown("---")
59
  st.header("🌲 Place Objects")
60
 
61
- # ←─── HERE: add your four buildertool options ────→
62
- opts = [
63
  "None",
64
  "Simple House",
65
  "Tree",
@@ -70,16 +156,67 @@ with st.sidebar:
70
  "HEROIC FANTASY CREATURES FULL PACK VOL 1",
71
  "POLYGON - Apocalypse Pack"
72
  ]
73
- idx = opts.index(st.session_state.selected_object) if st.session_state.selected_object in opts else 0
74
- sel = st.selectbox("Select:", opts, index=idx, key="selected_object_widget")
 
 
 
75
  if sel != st.session_state.selected_object:
76
  st.session_state.selected_object = sel
77
 
78
  st.markdown("---")
79
  st.header("💾 Save Work")
80
- if st.button("💾 Save Current Work", key="save_button"):
81
  from streamlit_js_eval import streamlit_js_eval
82
  streamlit_js_eval(js_code="getSaveDataAndPosition();", key="js_save_processor")
83
- st.rerun()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
 
85
- # … the rest of your existing handler for save, and main view injection …
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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',
 
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 existing plot CSVs and parse their grid coords."""
31
+ try:
32
+ files = [f for f in os.listdir(SAVE_DIR) if f.endswith(".csv") and f.startswith("plot_X")]
33
+ except FileNotFoundError:
34
+ return []
35
+ parsed = []
36
+ for fn in files:
37
+ parts = fn[:-4].split('_')
38
+ try:
39
+ gx = int(parts[1][1:])
40
+ gz = int(parts[2][1:])
41
+ name = "_".join(parts[3:]) if len(parts) > 3 else f"Plot({gx},{gz})"
42
+ parsed.append({
43
+ 'id': fn[:-4],
44
+ 'filename': fn,
45
+ 'grid_x': gx,
46
+ 'grid_z': gz,
47
+ 'name': name,
48
+ 'x_offset': gx * PLOT_WIDTH,
49
+ 'z_offset': gz * PLOT_DEPTH
50
+ })
51
+ except:
52
+ continue
53
+ parsed.sort(key=lambda p: (p['grid_x'], p['grid_z']))
54
+ return parsed
55
+
56
+ def load_plot_objects(filename, x_offset, z_offset):
57
+ """Read a plot CSV and shift each object's position by the plot offset."""
58
+ path = os.path.join(SAVE_DIR, filename)
59
+ try:
60
+ df = pd.read_csv(path)
61
+ except (FileNotFoundError, pd.errors.EmptyDataError):
62
+ return []
63
+ # ensure columns
64
+ for col, default in [('rot_x', 0.0), ('rot_y', 0.0), ('rot_z', 0.0), ('rot_order', 'XYZ')]:
65
+ if col not in df.columns:
66
+ df[col] = default
67
+ if 'obj_id' not in df.columns:
68
+ df['obj_id'] = [str(uuid.uuid4()) for _ in df.index]
69
+ objs = []
70
+ for row in df.to_dict(orient='records'):
71
+ objs.append({
72
+ 'obj_id': row['obj_id'],
73
+ 'type': row['type'],
74
+ 'pos_x': row['pos_x'] + x_offset,
75
+ 'pos_y': row['pos_y'],
76
+ 'pos_z': row['pos_z'] + z_offset,
77
+ 'rot_x': row['rot_x'],
78
+ 'rot_y': row['rot_y'],
79
+ 'rot_z': row['rot_z'],
80
+ 'rot_order': row['rot_order']
81
+ })
82
+ return objs
83
+
84
+ def save_plot_data(filename, objects_list, px, pz):
85
+ """Save newly placed objects back to the corresponding plot CSV."""
86
+ path = os.path.join(SAVE_DIR, filename)
87
+ records = []
88
+ for o in objects_list:
89
+ pos = o.get('position', {})
90
+ rot = o.get('rotation', {})
91
+ if 'x' not in pos or 'y' not in pos or 'z' not in pos:
92
+ continue
93
+ records.append({
94
+ 'obj_id': o.get('obj_id', str(uuid.uuid4())),
95
+ 'type': o.get('type', 'Unknown'),
96
+ 'pos_x': pos['x'] - px,
97
+ 'pos_y': pos['y'],
98
+ 'pos_z': pos['z'] - pz,
99
+ 'rot_x': rot.get('_x', 0.0),
100
+ 'rot_y': rot.get('_y', 0.0),
101
+ 'rot_z': rot.get('_z', 0.0),
102
+ 'rot_order': rot.get('_order', 'XYZ')
103
+ })
104
+ try:
105
+ pd.DataFrame(records, columns=CSV_COLUMNS).to_csv(path, index=False)
106
+ st.success(f"🎉 Saved {len(records)} objects to {filename}")
107
+ return True
108
+ except Exception as e:
109
+ st.error(f"Save failed: {e}")
110
+ return False
111
+
112
+ # 🔒 Singleton for global world state
113
+ @st.cache_resource
114
+ def get_game_state():
115
+ return GameState(save_dir=SAVE_DIR, csv_filename="world_state.csv")
116
+
117
+ game_state = get_game_state()
118
 
119
  # 🧠 Session state defaults
120
+ st.session_state.setdefault('selected_object', 'None')
121
+ st.session_state.setdefault('js_save_processor', None)
 
122
 
123
+ # 🔄 Load all saved plots + objects
124
  plots_metadata = load_plot_metadata()
125
  all_initial_objects = []
126
  for p in plots_metadata:
 
131
  st.title("🏗️ World Controls")
132
  st.header("📍 Navigate Plots")
133
  cols = st.columns(2)
134
+ for idx, p in enumerate(plots_metadata):
 
135
  label = f"➡️ {p['name']} ({p['grid_x']},{p['grid_z']})"
136
+ if cols[idx % 2].button(label, key=f"nav_{p['id']}"):
137
+ from streamlit_js_eval import streamlit_js_eval
138
+ js = f"teleportPlayer({p['x_offset']+PLOT_WIDTH/2},{p['z_offset']+PLOT_DEPTH/2});"
139
  try:
 
 
140
  streamlit_js_eval(js_code=js, key=f"tp_{p['id']}")
141
  except Exception as e:
142
+ st.error(f"Teleport failed: {e}")
 
143
 
144
  st.markdown("---")
145
  st.header("🌲 Place Objects")
146
 
147
+ # ←─── four new builder-tool options added here ────→
148
+ options = [
149
  "None",
150
  "Simple House",
151
  "Tree",
 
156
  "HEROIC FANTASY CREATURES FULL PACK VOL 1",
157
  "POLYGON - Apocalypse Pack"
158
  ]
159
+ sel = st.selectbox("Select:", options,
160
+ index=options.index(st.session_state.selected_object)
161
+ if st.session_state.selected_object in options else 0,
162
+ key="selected_object_widget"
163
+ )
164
  if sel != st.session_state.selected_object:
165
  st.session_state.selected_object = sel
166
 
167
  st.markdown("---")
168
  st.header("💾 Save Work")
169
+ if st.button("💾 Save Current Work"):
170
  from streamlit_js_eval import streamlit_js_eval
171
  streamlit_js_eval(js_code="getSaveDataAndPosition();", key="js_save_processor")
172
+ st.experimental_rerun()
173
+
174
+ # 📨 Handle incoming save-data callback
175
+ raw = st.session_state.get("js_save_processor")
176
+ if raw:
177
+ try:
178
+ payload = json.loads(raw) if isinstance(raw, str) else raw
179
+ pos = payload.get('playerPosition')
180
+ objs = payload.get('objectsToSave', [])
181
+ gx = math.floor(pos['x'] / PLOT_WIDTH)
182
+ gz = math.floor(pos['z'] / PLOT_DEPTH)
183
+ fn = f"plot_X{gx}_Z{gz}.csv"
184
+ if save_plot_data(fn, objs, gx*PLOT_WIDTH, gz*PLOT_DEPTH):
185
+ load_plot_metadata.clear()
186
+ st.session_state.js_save_processor = None
187
+ st.experimental_rerun()
188
+ except Exception as e:
189
+ st.error(f"Error saving data: {e}")
190
+
191
+ # 🏠 Main view & HTML injection
192
+ st.header("🌍 Infinite Shared 3D World")
193
+ st.caption("➡️ Explore, click to build, 💾 to save!")
194
+
195
+ state = {
196
+ "ALL_INITIAL_OBJECTS": all_initial_objects,
197
+ "PLOTS_METADATA": plots_metadata,
198
+ "SELECTED_OBJECT_TYPE": st.session_state.selected_object,
199
+ "PLOT_WIDTH": PLOT_WIDTH,
200
+ "PLOT_DEPTH": PLOT_DEPTH,
201
+ "GAME_STATE": game_state.get_state()
202
+ }
203
 
204
+ try:
205
+ with open('index.html', 'r', encoding='utf-8') as f:
206
+ html = f.read()
207
+ inject = f"""
208
+ <script>
209
+ window.ALL_INITIAL_OBJECTS = {json.dumps(state["ALL_INITIAL_OBJECTS"])};
210
+ window.PLOTS_METADATA = {json.dumps(state["PLOTS_METADATA"])};
211
+ window.SELECTED_OBJECT_TYPE = {json.dumps(state["SELECTED_OBJECT_TYPE"])};
212
+ window.PLOT_WIDTH = {json.dumps(state["PLOT_WIDTH"])};
213
+ window.PLOT_DEPTH = {json.dumps(state["PLOT_DEPTH"])};
214
+ window.GAME_STATE = {json.dumps(state["GAME_STATE"])};
215
+ </script>
216
+ """
217
+ html = html.replace("</head>", inject + "\n</head>", 1)
218
+ components.html(html, height=750, scrolling=False)
219
+ except FileNotFoundError:
220
+ st.error("❌ index.html not found!")
221
+ except Exception as e:
222
+ st.error(f"HTML injection failed: {e}")