awacke1 commited on
Commit
7825ef7
·
verified ·
1 Parent(s): 41c1a97

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +174 -1
app.py CHANGED
@@ -120,4 +120,177 @@ plots_metadata, next_plot_x_offset = load_plot_metadata()
120
  # --- Load ALL Objects for Rendering ---
121
  # This could be slow with many plots! Consider optimization later.
122
  all_initial_objects = []
123
- for plot in plots_metadata:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
120
  # --- Load ALL Objects for Rendering ---
121
  # This could be slow with many plots! Consider optimization later.
122
  all_initial_objects = []
123
+ for plot in plots_metadata:
124
+ all_initial_objects.extend(load_plot_objects(plot['filename'], plot['x_offset']))
125
+
126
+ # --- Sidebar ---
127
+ with st.sidebar:
128
+ st.title("🏗️ World Controls")
129
+
130
+ st.header("Navigation (Plots)")
131
+ st.caption("Click to teleport player to the start of a plot.")
132
+
133
+ # Use columns for a horizontal button layout if desired
134
+ cols = st.columns(3)
135
+ col_idx = 0
136
+ for plot in plots_metadata:
137
+ # Use an emoji + name for the button
138
+ button_label = f"➡️ {plot.get('name', plot['id'])}"
139
+ if cols[col_idx].button(button_label, key=f"nav_{plot['id']}"):
140
+ # Send command to JS to move the player
141
+ target_x = plot['x_offset']
142
+ streamlit_js_eval(js_code=f"teleportPlayer({target_x});")
143
+ # No rerun needed here, JS handles the move
144
+
145
+ col_idx = (col_idx + 1) % 3 # Cycle through columns
146
+
147
+ st.markdown("---")
148
+
149
+ # --- Object Placement ---
150
+ st.header("Place Objects")
151
+ object_types = ["None", "Simple House", "Tree", "Rock", "Fence Post"]
152
+ current_object_index = 0
153
+ try:
154
+ current_object_index = object_types.index(st.session_state.selected_object)
155
+ except ValueError:
156
+ st.session_state.selected_object = "None" # Reset if invalid
157
+
158
+ selected_object_type_widget = st.selectbox(
159
+ "Select Object:",
160
+ options=object_types,
161
+ index=current_object_index,
162
+ key="selected_object_widget"
163
+ )
164
+ if selected_object_type_widget != st.session_state.selected_object:
165
+ st.session_state.selected_object = selected_object_type_widget
166
+ # No rerun needed just for selection, JS will read state on next placement
167
+
168
+
169
+ st.markdown("---")
170
+
171
+ # --- Saving ---
172
+ st.header("Save New Plot")
173
+ st.session_state.new_plot_name = st.text_input(
174
+ "Name for New Plot:",
175
+ value=st.session_state.new_plot_name,
176
+ placeholder="My Awesome Creation"
177
+ )
178
+
179
+ if st.button("💾 Save Current Work as New Plot"):
180
+ # 1. Trigger JS function to get data
181
+ # This function `getSaveData` should be defined in index.html
182
+ # It should collect data for objects considered "new"
183
+ # and return them as a JSON string.
184
+ # The positions should be WORLD positions.
185
+ js_get_data_code = "getSaveData();"
186
+ # Use streamlit_js_eval to run JS and get data back into session_state
187
+ st.session_state.save_request_data = streamlit_js_eval(
188
+ js_code=js_get_data_code,
189
+ key="get_save_data" # Unique key for this eval call
190
+ # Removed want_output=True, value goes to key if specified
191
+ )
192
+ # Need to trigger a rerun to process the data AFTER it arrives
193
+ # Using sync() might help ensure the value is available immediately after? Let's try without first.
194
+ st.rerun() # Rerun to process the returned data below
195
+
196
+
197
+ # --- Process Save Data (if received from JS via key) ---
198
+ # This runs AFTER the rerun triggered by the save button
199
+ save_data_from_js = st.session_state.get("save_request_data", None)
200
+
201
+ if save_data_from_js:
202
+ st.info("Received save data from client...")
203
+ try:
204
+ objects_to_save = json.loads(save_data_from_js) # Parse JSON string from JS
205
+
206
+ if isinstance(objects_to_save, list) and len(objects_to_save) > 0:
207
+ # Determine filename for the new plot
208
+ new_plot_index = len(plots_metadata) + 1
209
+ plot_name_sanitized = "".join(c for c in st.session_state.new_plot_name if c.isalnum() or c in (' ', '_')).rstrip() or f"Plot_{new_plot_index}"
210
+ new_filename = f"plot_{new_plot_index:03d}_{plot_name_sanitized.replace(' ','_')}.csv"
211
+
212
+ # Save the data, converting world coords to relative coords inside the func
213
+ save_ok = save_plot_data(new_filename, objects_to_save, next_plot_x_offset)
214
+
215
+ if save_ok:
216
+ # IMPORTANT: Clear the plot metadata cache so it reloads with the new file
217
+ load_plot_metadata.clear()
218
+ # Clear the JS data request state
219
+ st.session_state.save_request_data = None
220
+ # Reset the new plot name field
221
+ st.session_state.new_plot_name = ""
222
+ # Reset newly placed objects in JS? Needs another call.
223
+ try:
224
+ streamlit_js_eval(js_code="resetNewlyPlacedObjects();", key="reset_js_state")
225
+ except Exception as js_e:
226
+ st.warning(f"Could not reset JS state after save: {js_e}")
227
+
228
+ st.success(f"New plot '{plot_name_sanitized}' saved!")
229
+ # Rerun AGAIN to reload plots list, redraw minimap, and update JS injection
230
+ st.rerun()
231
+ else:
232
+ st.error("Failed to save plot data to file.")
233
+ # Clear the request data even if save failed to prevent retry loop
234
+ st.session_state.save_request_data = None
235
+
236
+ elif isinstance(objects_to_save, list) and len(objects_to_save) == 0:
237
+ st.warning("Nothing new to save.")
238
+ st.session_state.save_request_data = None # Clear request
239
+ else:
240
+ st.error(f"Received invalid save data format from client: {type(objects_to_save)}")
241
+ st.session_state.save_request_data = None # Clear request
242
+
243
+
244
+ except json.JSONDecodeError:
245
+ st.error("Failed to decode save data from client. Data might be corrupted or empty.")
246
+ print("Received raw data:", save_data_from_js) # Log for debugging
247
+ st.session_state.save_request_data = None # Clear request
248
+ except Exception as e:
249
+ st.error(f"Error processing save: {e}")
250
+ st.exception(e)
251
+ st.session_state.save_request_data = None # Clear request
252
+
253
+
254
+ # --- Main Area ---
255
+ st.header("Shared 3D World")
256
+ st.caption("Build side-by-side with others. Changes load on refresh/save.")
257
+
258
+ # --- Load and Prepare HTML ---
259
+ html_file_path = 'index.html'
260
+ html_content_with_state = None # Initialize
261
+
262
+ try:
263
+ with open(html_file_path, 'r', encoding='utf-8') as f:
264
+ html_template = f.read()
265
+
266
+ # --- Inject Python state into JavaScript ---
267
+ js_injection_script = f"""
268
+ <script>
269
+ // Set global variables BEFORE the main script runs
270
+ window.ALL_INITIAL_OBJECTS = {json.dumps(all_initial_objects)}; // All objects from all plots
271
+ window.SELECTED_OBJECT_TYPE = {json.dumps(st.session_state.selected_object)};
272
+ window.PLOT_WIDTH = {json.dumps(PLOT_WIDTH)};
273
+ window.NEXT_PLOT_X_OFFSET = {json.dumps(next_plot_x_offset)}; // Needed for save calculation
274
+ console.log("Streamlit State Injected:", {{
275
+ selectedObject: window.SELECTED_OBJECT_TYPE,
276
+ initialObjectsCount: window.ALL_INITIAL_OBJECTS ? window.ALL_INITIAL_OBJECTS.length : 0,
277
+ plotWidth: window.PLOT_WIDTH,
278
+ nextPlotX: window.NEXT_PLOT_X_OFFSET
279
+ }});
280
+ </script>
281
+ """
282
+ html_content_with_state = html_template.replace('</head>', js_injection_script + '\n</head>', 1)
283
+
284
+ # --- Embed HTML Component ---
285
+ components.html(
286
+ html_content_with_state,
287
+ height=750,
288
+ scrolling=False
289
+ )
290
+
291
+ except FileNotFoundError:
292
+ st.error(f"CRITICAL ERROR: Could not find the file '{html_file_path}'.")
293
+ st.warning(f"Make sure `{html_file_path}` is in the same directory as `app.py` and `{SAVE_DIR}` exists.")
294
+ except Exception as e:
295
+ st.error(f"An critical error occurred during HTML preparation or component rendering: {e}")
296
+ st.exception(e)