|
|
import gradio as gr |
|
|
import matplotlib.pyplot as plt |
|
|
import numpy as np |
|
|
import random |
|
|
|
|
|
|
|
|
plt.style.use('ggplot') |
|
|
plt.rcParams['figure.figsize'] = [10, 10] |
|
|
|
|
|
|
|
|
class Circle: |
|
|
"""Represents a circle in the drawing and its state""" |
|
|
def __init__(self, x, y, radius, color): |
|
|
self.x = x |
|
|
self.y = y |
|
|
self.radius = radius |
|
|
self.color = color |
|
|
|
|
|
def __repr__(self): |
|
|
return f"Circle(Center:({self.x:.2f}, {self.y:.2f}), R:{self.radius}, Color:{self.color})" |
|
|
|
|
|
|
|
|
def run_shape_grammar(N_iterations): |
|
|
""" |
|
|
Runs the shape grammar iterative process N times and yields the state after each iteration. |
|
|
""" |
|
|
|
|
|
circles = [Circle(0, 0, 2, 'blue')] |
|
|
lines = [] |
|
|
|
|
|
current_iteration = 0 |
|
|
print(f"--- Starting Shape Grammar: Target Iterations N = {N_iterations} ---") |
|
|
|
|
|
|
|
|
yield circles, lines |
|
|
|
|
|
while current_iteration < N_iterations: |
|
|
|
|
|
active_circles = [c for c in circles if c.color != 'red'] |
|
|
|
|
|
|
|
|
if not active_circles: |
|
|
print(f"\n✅ Stop: All circles have turned red. Total iterations: {current_iteration}") |
|
|
break |
|
|
|
|
|
|
|
|
C_original = random.choice(active_circles) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
angle_deg = random.choice([0, 45, 90, 135, 180, 225, 270, 315]) |
|
|
angle_rad = np.deg2rad(angle_deg) |
|
|
|
|
|
|
|
|
L_length = random.choice([6, 8, 10]) |
|
|
|
|
|
|
|
|
start_from_center = random.choice([True, False]) |
|
|
|
|
|
if start_from_center: |
|
|
|
|
|
P_start_x, P_start_y = C_original.x, C_original.y |
|
|
else: |
|
|
|
|
|
|
|
|
P_start_x = C_original.x + C_original.radius * np.cos(angle_rad + np.pi) |
|
|
P_start_y = C_original.y + C_original.radius * np.sin(angle_rad + np.pi) |
|
|
|
|
|
|
|
|
P_end_x = P_start_x + L_length * np.cos(angle_rad) |
|
|
P_end_y = P_start_y + L_length * np.sin(angle_rad) |
|
|
|
|
|
|
|
|
lines.append(((P_start_x, P_start_y), (P_end_x, P_end_y))) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
R_new = random.choice([1, 2, 3, 4]) |
|
|
|
|
|
colors = ['blue'] * 9 + ['green'] * 9 + ['red'] * 2 |
|
|
Color_new = random.choice(colors) |
|
|
|
|
|
|
|
|
placement_option = random.choice(['tangential_left', 'tangential_right', 'centered']) |
|
|
|
|
|
if placement_option == 'centered': |
|
|
|
|
|
C_new_x, C_new_y = P_end_x, P_end_y |
|
|
else: |
|
|
|
|
|
|
|
|
|
|
|
normal_angle = angle_rad + (np.pi/2 if placement_option == 'tangential_left' else -np.pi/2) |
|
|
|
|
|
|
|
|
C_new_x = P_end_x + R_new * np.cos(normal_angle) |
|
|
|
|
|
C_new_y = P_end_y + R_new * np.sin(normal_angle) |
|
|
|
|
|
C_new = Circle(C_new_x, C_new_y, R_new, Color_new) |
|
|
circles.append(C_new) |
|
|
|
|
|
|
|
|
if C_original.color == 'blue': |
|
|
C_original.color = 'green' |
|
|
elif C_original.color == 'green': |
|
|
|
|
|
if random.random() < 0.5: |
|
|
C_original.color = 'red' |
|
|
|
|
|
|
|
|
current_iteration += 1 |
|
|
print(f"Iteration {current_iteration}/{N_iterations}: Original circle turned {C_original.color}, New circle {C_new.color}, Total circles: {len(circles)}") |
|
|
|
|
|
|
|
|
yield circles, lines |
|
|
|
|
|
|
|
|
if current_iteration == N_iterations: |
|
|
print(f"\n✅ Stop: Maximum number of iterations N = {N_iterations} reached.") |
|
|
|
|
|
|
|
|
|
|
|
def plot_grammar_result(circles, lines): |
|
|
""" |
|
|
Plots the generated shape using Matplotlib and returns the figure. |
|
|
""" |
|
|
fig, ax = plt.subplots() |
|
|
|
|
|
|
|
|
for c in circles: |
|
|
|
|
|
circle_patch = plt.Circle((c.x, c.y), c.radius, |
|
|
color=c.color, |
|
|
alpha=0.6, |
|
|
fill=True, |
|
|
linewidth=1, |
|
|
edgecolor='black') |
|
|
ax.add_patch(circle_patch) |
|
|
|
|
|
|
|
|
for (start, end) in lines: |
|
|
ax.plot([start[0], end[0]], [start[1], end[1]], |
|
|
color='gray', |
|
|
linestyle='-', |
|
|
linewidth=0.5, |
|
|
zorder=-1) |
|
|
|
|
|
|
|
|
if circles: |
|
|
all_x = [c.x for c in circles] |
|
|
all_y = [c.y for c in circles] |
|
|
|
|
|
x_min, x_max = min(all_x), max(all_x) |
|
|
y_min, y_max = min(all_y), max(all_y) |
|
|
|
|
|
padding = 10 |
|
|
|
|
|
if x_min == x_max: |
|
|
x_min -= padding |
|
|
x_max += padding |
|
|
if y_min == y_max: |
|
|
y_min -= padding |
|
|
y_max += padding |
|
|
|
|
|
ax.set_xlim(x_min - padding, x_max + padding) |
|
|
ax.set_ylim(y_min - padding, y_max + padding) |
|
|
|
|
|
ax.set_aspect('equal', adjustable='box') |
|
|
ax.set_title(f"Shape Grammar Generation Result (Total {len(circles)} circles)") |
|
|
ax.set_xlabel("X Coordinate") |
|
|
ax.set_ylabel("Y Coordinate") |
|
|
|
|
|
|
|
|
return fig |
|
|
|
|
|
|
|
|
generated_plots = [] |
|
|
|
|
|
def run_shape_grammar_and_generate_plots(num_iterations): |
|
|
""" |
|
|
Runs the shape grammar and generates plots for each iteration. |
|
|
Returns a list of Matplotlib figure objects. |
|
|
""" |
|
|
global generated_plots |
|
|
generated_plots = [] |
|
|
print(f"run_shape_grammar_and_generate_plots called with {num_iterations} iterations") |
|
|
|
|
|
|
|
|
if num_iterations is None or num_iterations <= 0: |
|
|
num_iterations = 50 |
|
|
|
|
|
|
|
|
for i, (circles, lines) in enumerate(run_shape_grammar(int(num_iterations))): |
|
|
|
|
|
fig = plot_grammar_result(circles, lines) |
|
|
generated_plots.append(fig) |
|
|
|
|
|
plt.close(fig) |
|
|
|
|
|
print(f"Generated {len(generated_plots)} plots.") |
|
|
return generated_plots |
|
|
|
|
|
def display_iteration(iteration_index): |
|
|
""" |
|
|
Displays the plot for a specific iteration based on the slider value. |
|
|
""" |
|
|
global generated_plots |
|
|
print(f"display_iteration called with index {iteration_index}") |
|
|
if generated_plots and 0 <= iteration_index < len(generated_plots): |
|
|
print(f"Displaying plot for iteration {iteration_index}") |
|
|
return generated_plots[int(iteration_index)] |
|
|
else: |
|
|
print("No plot available or index out of range.") |
|
|
|
|
|
fig, ax = plt.subplots() |
|
|
ax.text(0.5, 0.5, "No plot available", horizontalalignment='center', verticalalignment='center') |
|
|
ax.set_title("Error") |
|
|
return fig |
|
|
|
|
|
|
|
|
|
|
|
with gr.Blocks() as demo: |
|
|
gr.Markdown("## Shape Grammar Generator with Iteration Slider") |
|
|
gr.Markdown("Generate abstract shapes using a simple shape grammar and explore each step of the process.") |
|
|
|
|
|
with gr.Row(): |
|
|
num_iterations_input = gr.Number(label="Number of Iterations", value=50, precision=0) |
|
|
generate_button = gr.Button("Generate") |
|
|
|
|
|
|
|
|
plot_output = gr.Plot() |
|
|
|
|
|
|
|
|
iteration_slider = gr.Slider(minimum=0, maximum=0, step=1, label="Iteration") |
|
|
|
|
|
|
|
|
|
|
|
generate_button.click( |
|
|
fn=run_shape_grammar_and_generate_plots, |
|
|
inputs=num_iterations_input, |
|
|
outputs=None |
|
|
).then( |
|
|
fn=lambda: gr.update(maximum=len(generated_plots)-1, value=0), |
|
|
inputs=None, |
|
|
outputs=iteration_slider |
|
|
).then( |
|
|
fn=lambda: generated_plots[0] if generated_plots else None, |
|
|
inputs=None, |
|
|
outputs=plot_output |
|
|
) |
|
|
|
|
|
|
|
|
iteration_slider.change( |
|
|
fn=display_iteration, |
|
|
inputs=iteration_slider, |
|
|
outputs=plot_output |
|
|
) |
|
|
|
|
|
|
|
|
if __name__ == "__main__": |
|
|
|
|
|
demo.launch(share=True) |