# app.py import streamlit as st # 🌐 Streamlit magic import streamlit.components.v1 as components # 🖼️ Embed custom HTML/JS import os # 📂 File operations import json # 🔄 JSON encoding/decoding import pandas as pd # 📊 DataFrame handling import uuid # 🆔 Unique IDs import math # ➗ Math utilities import time # ⏳ Time utilities from gamestate import GameState # 💼 Shared game-state singleton # 🚀 Page setup st.set_page_config(page_title="Infinite World Builder", layout="wide") # 📏 Constants for world dimensions & CSV schema SAVE_DIR = "saved_worlds" PLOT_WIDTH = 50.0 # ↔️ Plot width in world units PLOT_DEPTH = 50.0 # ↕️ Plot depth in world units CSV_COLUMNS = [ 'obj_id', 'type', 'pos_x', 'pos_y', 'pos_z', 'rot_x', 'rot_y', 'rot_z', 'rot_order' ] # 🗂️ Ensure directory for saved plots os.makedirs(SAVE_DIR, exist_ok=True) # 🔧 Load prefab kits definitions @st.cache_data(ttl=3600) def load_kits(): try: with open("kits.json", "r", encoding="utf-8") as f: return json.load(f) except FileNotFoundError: st.error("kits.json missing! 📦") return {} except Exception as e: st.error(f"Error loading kits.json: {e}") return {} kits = load_kits() # 🔗 name → module list # --- Existing load/save/state functions --- @st.cache_data(ttl=3600) def load_plot_metadata(): try: files = [f for f in os.listdir(SAVE_DIR) if f.endswith('.csv') and f.startswith('plot_X')] except Exception as e: st.error(f"Error scanning {SAVE_DIR}: {e}") return [] parsed = [] for fn in files: try: parts = fn[:-4].split('_') # remove .csv gx = int(parts[1][1:]); gz = int(parts[2][1:]) name = ' '.join(parts[3:]) if len(parts)>3 else f"Plot({gx},{gz})" parsed.append({ 'id': fn[:-4], 'filename': fn, 'grid_x': gx, 'grid_z': gz, 'name': name, 'x_offset': gx*PLOT_WIDTH, 'z_offset': gz*PLOT_DEPTH }) except: st.warning(f"Skipping invalid file: {fn}") parsed.sort(key=lambda p: (p['grid_x'], p['grid_z'])) return parsed def load_plot_objects(filename, x_offset, z_offset): path = os.path.join(SAVE_DIR, filename) try: df = pd.read_csv(path) if not all(c in df.columns for c in ['type','pos_x','pos_y','pos_z']): st.warning(f"Missing cols in {filename}") return [] df['obj_id'] = df.get('obj_id', pd.Series([str(uuid.uuid4()) for _ in df.index])) for col, default in [('rot_x',0.0),('rot_y',0.0),('rot_z',0.0),('rot_order','XYZ')]: if col not in df.columns: df[col]=default objs = [] for _, r in df.iterrows(): o = r.to_dict() o['pos_x'] += x_offset; o['pos_z'] += z_offset objs.append(o) return objs except Exception: return [] def save_plot_data(filename, objects_list, px, pz): path = os.path.join(SAVE_DIR, filename) if not isinstance(objects_list, list): st.error("Invalid save data") return False rel=[] for o in objects_list: pos=o.get('position',{}); rot=o.get('rotation',{}) typ=o.get('type','Unknown'); oid=o.get('obj_id',str(uuid.uuid4())) if not all(k in pos for k in ['x','y','z']) or typ=='Unknown': continue rel.append({ 'obj_id':oid,'type':typ, 'pos_x':pos['x']-px,'pos_y':pos['y'],'pos_z':pos['z']-pz, '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') }) try: pd.DataFrame(rel,columns=CSV_COLUMNS).to_csv(path,index=False) st.success(f"Saved {len(rel)} to {filename}") return True except Exception as e: st.error(f"Save failed: {e}") return False @st.cache_resource def get_game_state(): return GameState(save_dir=SAVE_DIR,csv_filename='world_state.csv') game_state=get_game_state() # 🧠 Session defaults st.session_state.setdefault('selected_object','None') # 🔄 Load metadata & objects plots_metadata=load_plot_metadata() all_initial_objects=[] for p in plots_metadata: all_initial_objects+=load_plot_objects(p['filename'],p['x_offset'],p['z_offset']) # 🖥️ Sidebar UI\ with kits with st.sidebar: st.title("🏗️ World Controls") st.header("🌟 Place Kits") options=["None"]+list(kits.keys()) idx=options.index(st.session_state.selected_object) if st.session_state.selected_object in options else 0 sel=st.selectbox("Select Kit:",options,index=idx,key="selected_object_widget") if sel!=st.session_state.selected_object: st.session_state.selected_object=sel st.markdown("---") st.header("💾 Save Work") if st.button("💾 Save Current Work"): from streamlit_js_eval import streamlit_js_eval streamlit_js_eval(js_code="getSaveDataAndPosition();",key="js_save_processor") st.rerun() # 🏠 Main area st.header("🌍 Infinite Shared 3D World") st.caption("Click to explore & place kits, then 💾 to save!") # 🔌 Inject state into JS injected_state={ "ALL_INITIAL_OBJECTS":all_initial_objects, "PLOTS_METADATA":plots_metadata, "SELECTED_OBJECT_TYPE":st.session_state.selected_object, "PLOT_WIDTH":PLOT_WIDTH, "PLOT_DEPTH":PLOT_DEPTH, "GAME_STATE":game_state.get_state(), "KITS":kits } try: html=open('index.html','r',encoding='utf-8').read() script=f""" """ html_mod=html.replace('',script+'\n',1) components.html(html_mod,height=750,scrolling=False) except FileNotFoundError: st.error("index.html missing!") except Exception as e: st.error(f"HTML inject error: {e}")