File size: 6,106 Bytes
1c6109f
 
bca6fdd
 
 
b163a43
1c6109f
 
 
bca6fdd
 
 
 
 
1c6109f
bca6fdd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1c6109f
bca6fdd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1c6109f
bca6fdd
 
 
 
 
1c6109f
 
bca6fdd
 
 
 
1c6109f
bca6fdd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1c6109f
bca6fdd
 
 
 
 
 
 
 
 
 
 
 
 
1c6109f
bca6fdd
1c6109f
bca6fdd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
import gradio as gr
import networkx as nx
import matplotlib.pyplot as plt
import random
import time
from graphGen7 import NetworkGenerator



def get_preset_dims(preset_mode, topology):
    """Calculates dimensions based on preset and topology."""
    if preset_mode == "Custom":
        # Enable inputs, keep current values (passed via state in UI, but here we just return interaction update)
        return gr.update(interactive=True), gr.update(interactive=True)
    
    # Preset Logic
    if topology == "linear":
        if preset_mode == "Small": dims = (4, 4)
        elif preset_mode == "Medium": dims = (6, 11)
        else: dims = (10, 26)
    else: # Highly Connected / Bottlenecks
        if preset_mode == "Small": dims = (4, 4)
        elif preset_mode == "Medium": dims = (8, 8)
        else: dims = (16, 16)
        
    return gr.update(value=dims[0], interactive=False), gr.update(value=dims[1], interactive=False)

def update_void_settings(variant, width, height):
    """Calculates void fraction based on variant logic."""
    if variant == "Custom":
        return gr.update(interactive=True)
    
    # Fixed Logic
    area = width * height
    val = 0.60 if area <= 20 else 0.35
    return gr.update(value=val, interactive=False)

def generate_network_viz(topology, width, height, variant, void_frac):
    try:
        variant_code = "F" if variant == "Fixed" else "R"
        
        generator = NetworkGenerator(
            width=width,
            height=height,
            variant=variant_code,
            topology=topology,
            node_drop_fraction=void_frac
        )
        
        start = time.time()
        graph = generator.generate()
        end = time.time()
        
        # Plotting
        fig, ax = plt.subplots(figsize=(8, 8))
        pos = {node: (node[0], node[1]) for node in graph.nodes()}
        
        nx.draw_networkx_edges(graph, pos, ax=ax, width=2, alpha=0.6, edge_color="#333")
        nx.draw_networkx_nodes(graph, pos, ax=ax, node_size=350, node_color="#4F46E5", edgecolors="white", linewidths=1.5)
        nx.draw_networkx_labels(graph, pos, ax=ax, font_size=7, font_color="white", font_weight="bold")
        
        ax.set_xlim(-1, width + 1)
        ax.set_ylim(-1, height + 1)
        ax.invert_yaxis() # Camera looks top-down (0 at top)
        ax.grid(True, linestyle=':', alpha=0.3)
        ax.set_axis_on()
        ax.tick_params(left=True, bottom=True, labelleft=False, labelbottom=False)
        ax.set_title(f"{topology.upper()} | {width}x{height} | Voids: {int(void_frac*100)}%")
        
        # Metrics
        info_text = (
            f"**Nodes:** {len(graph.nodes())} | "
            f"**Edges:** {len(graph.edges())} | "
            f"**Density:** {nx.density(graph):.2f} | "
            f"**Time:** {end - start:.3f}s"
        )
        
        return fig, info_text
    
    except Exception as e:
        return None, f"Error: {str(e)}"


with gr.Blocks(title="Spatial Network Generator") as demo:
    gr.Markdown("# Spatial Network Generator")
    gr.Markdown("Generate procedural room-like graphs with topological constraints.")
    
    with gr.Row():
        # --- LEFT COLUMN: CONTROLS ---
        with gr.Column(scale=1):
            topology_dd = gr.Dropdown(
                choices=["highly_connected", "bottlenecks", "linear"], 
                value="highly_connected", 
                label="Topology"
            )
            
            preset_radio = gr.Radio(
                choices=["Small", "Medium", "Large", "Custom"], 
                value="Medium", 
                label="Preset Size"
            )
            
            with gr.Row():
                width_num = gr.Number(value=8, label="Width (X)", precision=0, interactive=False)
                height_num = gr.Number(value=8, label="Height (Y)", precision=0, interactive=False)

            gr.Markdown("---")
            
            variant_dd = gr.Dropdown(
                choices=["Fixed", "Custom"], 
                value="Fixed", 
                label="Variant",
                info="Fixed = Standard density. Custom = Random density."
            )
            
            void_slider = gr.Slider(
                minimum=0.0, maximum=0.9, value=0.35, step=0.05, 
                label="Void Fraction", 
                interactive=False,
                info="Percentage of grid to leave empty."
            )
            
            gen_btn = gr.Button("Generate Network", variant="primary")

        # --- RIGHT COLUMN: VISUALIZATION ---
        with gr.Column(scale=2):
            metrics_out = gr.Markdown("Click Generate to see metrics...")
            plot_out = gr.Plot(label="Network Visualization")

    # ==========================================
    # EVENT LISTENERS
    # ==========================================
    
    # 1. Update Dimensions when Preset or Topology changes
    #    Note: We pass inputs to calculate new dims, and output to width/height inputs
    input_group_dims = [preset_radio, topology_dd]
    output_group_dims = [width_num, height_num]
    
    preset_radio.change(fn=get_preset_dims, inputs=input_group_dims, outputs=output_group_dims)
    topology_dd.change(fn=get_preset_dims, inputs=input_group_dims, outputs=output_group_dims)

    # 2. Update Void Settings when Variant or Dimensions change
    #    (If user switches to Fixed, we lock slider. If dimensions change while Fixed, we recalc 0.60 vs 0.35)
    input_group_void = [variant_dd, width_num, height_num]
    
    variant_dd.change(fn=update_void_settings, inputs=input_group_void, outputs=void_slider)
    width_num.change(fn=update_void_settings, inputs=input_group_void, outputs=void_slider)
    height_num.change(fn=update_void_settings, inputs=input_group_void, outputs=void_slider)
    
    # 3. Main Generation
    input_group_gen = [topology_dd, width_num, height_num, variant_dd, void_slider]
    output_group_gen = [plot_out, metrics_out]
    
    gen_btn.click(fn=generate_network_viz, inputs=input_group_gen, outputs=output_group_gen)

# Launch
if __name__ == "__main__":
    demo.launch()