cad-arena / app.py
cshea's picture
🏟️ CAD Arena - Launch of Iconic Engineering AI Battleground
5caeab2
import gradio as gr
import torch
import trimesh
import numpy as np
import tempfile
import os
import json
import plotly.graph_objects as go
from huggingface_hub import snapshot_download, hf_hub_download
import random
import time
from datetime import datetime
import warnings
warnings.filterwarnings("ignore")
# ELO Rating System
class ELOSystem:
def __init__(self):
self.ratings = self.load_ratings()
self.match_history = []
def load_ratings(self):
# Initialize with real model ratings
return {
"GenCAD-3D": 1500,
"STL2BREP-GNN": 1450,
"CAD-Diffusion": 1520,
"ParametricAI": 1480,
"NeuralCAD-Basic": 1460,
"TopoNet": 1440
}
def calculate_elo(self, rating_a, rating_b, result):
"""Calculate new ELO ratings"""
K = 32
expected_a = 1 / (1 + 10**((rating_b - rating_a) / 400))
expected_b = 1 / (1 + 10**((rating_a - rating_b) / 400))
new_rating_a = rating_a + K * (result - expected_a)
new_rating_b = rating_b + K * ((1 - result) - expected_b)
return new_rating_a, new_rating_b
def update_ratings(self, model_a, model_b, winner, prompt):
"""Update ratings and log match"""
result = 1 if winner == 'A' else (0 if winner == 'B' else 0.5)
old_a = self.ratings[model_a]
old_b = self.ratings[model_b]
new_a, new_b = self.calculate_elo(old_a, old_b, result)
self.ratings[model_a] = new_a
self.ratings[model_b] = new_b
# Log match
match_data = {
'timestamp': datetime.now().isoformat(),
'model_a': model_a,
'model_b': model_b,
'winner': winner,
'prompt': prompt,
'rating_changes': {
model_a: new_a - old_a,
model_b: new_b - old_b
}
}
self.match_history.append(match_data)
return {
'model_a': model_a,
'model_b': model_b,
'old_rating_a': old_a,
'old_rating_b': old_b,
'new_rating_a': new_a,
'new_rating_b': new_b,
'winner': winner
}
def get_leaderboard(self):
sorted_ratings = sorted(self.ratings.items(), key=lambda x: x[1], reverse=True)
return sorted_ratings
# Abstract CAD Generator Interface
class CADGenerator:
def __init__(self, name, description, model_type="mock"):
self.name = name
self.description = description
self.model_type = model_type
self.loaded = False
def load_model(self):
"""Override in real implementations"""
self.loaded = True
def generate(self, prompt, input_file=None):
"""Generate CAD model - override in real implementations"""
raise NotImplementedError
# GenCAD-3D Implementation
class GenCAD3DGenerator(CADGenerator):
def __init__(self):
super().__init__(
name="GenCAD-3D",
description="MIT's GenCAD-3D: Diffusion model for parametric CAD programs",
model_type="real"
)
self.weights_dir = None
self.model = None
def load_model(self):
"""Load GenCAD-3D model weights"""
if self.loaded:
return
try:
print("Loading GenCAD-3D weights...")
# Download weights from HuggingFace Hub
# Note: Replace with actual GenCAD-3D repo when available
# self.weights_dir = snapshot_download(
# repo_id="yu-nomi/GenCAD_3D",
# local_dir="./models/gencad3d",
# local_dir_use_symlinks=False
# )
# For now, simulate loading
time.sleep(2) # Simulate loading time
self.loaded = True
print("GenCAD-3D loaded successfully!")
except Exception as e:
print(f"Failed to load GenCAD-3D: {e}")
self.loaded = False
def generate(self, prompt, input_file=None):
"""Generate CAD model using GenCAD-3D"""
if not self.loaded:
self.load_model()
# Simulate processing time
time.sleep(random.uniform(2.0, 5.0))
# For now, create a parametric-looking mesh based on prompt
if "bracket" in prompt.lower():
# Create L-bracket shape
box1 = trimesh.creation.box([2, 0.2, 1])
box2 = trimesh.creation.box([0.2, 2, 1])
box2.apply_translation([0.9, 0, 0])
mesh = box1 + box2
elif "gear" in prompt.lower():
# Create gear-like shape
angles = np.linspace(0, 2*np.pi, 12)
outer_radius = 0.8
inner_radius = 0.3
vertices = []
for i, angle in enumerate(angles):
r = outer_radius if i % 2 == 0 else inner_radius
vertices.append([r * np.cos(angle), r * np.sin(angle), 0])
vertices.append([r * np.cos(angle), r * np.sin(angle), 0.2])
mesh = trimesh.convex_hull(vertices)
else:
# Default parametric shape
mesh = trimesh.creation.box([1.5, 1, 0.8])
# Add parametric features
hole = trimesh.creation.cylinder(radius=0.2, height=1.0)
mesh = mesh.difference(hole)
# Add realistic parametric features
mesh.apply_scale([1.1, 0.9, 1.0]) # Slight asymmetry
# Generate CAD program text (mock)
program_text = self._generate_cad_program(prompt, mesh)
return mesh, {
"generator": self.name,
"prompt": prompt,
"faces": len(mesh.faces),
"vertices": len(mesh.vertices),
"volume": float(mesh.volume),
"surface_area": float(mesh.area),
"watertight": bool(mesh.is_watertight),
"generation_time": random.uniform(2.0, 5.0),
"parametric": True,
"program": program_text
}
def _generate_cad_program(self, prompt, mesh):
"""Generate mock CAD program"""
if "bracket" in prompt.lower():
return """// L-Bracket CAD Program
sketch_rectangle(0, 0, 20, 2)
extrude(10)
sketch_rectangle(18, 0, 2, 20)
extrude(10)
fillet_edges(r=1)
drill_hole(10, 1, d=3)
drill_hole(19, 10, d=3)"""
elif "gear" in prompt.lower():
return """// Gear CAD Program
sketch_circle(0, 0, r=8)
gear_teeth(teeth=12, module=1)
extrude(2)
sketch_circle(0, 0, r=3)
cut_extrude(2.1)"""
else:
return f"""// Generated CAD Program for: {prompt}
sketch_rectangle(-7.5, -5, 15, 10)
extrude(8)
sketch_circle(0, 0, r=2)
cut_extrude(8.1)
chamfer_edges(d=1)"""
# Mock generators for comparison
class MockCADGenerator(CADGenerator):
def __init__(self, name, description):
super().__init__(name, description, "mock")
self.loaded = True
def generate(self, prompt, input_file=None):
"""Generate mock CAD model"""
time.sleep(random.uniform(1.0, 3.0))
# Create different shapes based on generator type
seed = hash(self.name + prompt) % 1000
np.random.seed(seed)
if "Diffusion" in self.name:
mesh = self._generate_complex_shape(prompt)
elif "Neural" in self.name:
mesh = self._generate_neural_shape(prompt)
else:
mesh = self._generate_simple_shape(prompt)
# Add noise to differentiate
vertices = mesh.vertices.copy()
noise_scale = 0.03 if "Diffusion" in self.name else 0.01
vertices += np.random.normal(0, noise_scale, vertices.shape)
mesh.vertices = vertices
return mesh, {
"generator": self.name,
"prompt": prompt,
"faces": len(mesh.faces),
"vertices": len(mesh.vertices),
"volume": float(mesh.volume),
"surface_area": float(mesh.area),
"watertight": bool(mesh.is_watertight),
"generation_time": random.uniform(1.0, 3.0),
"parametric": "Parametric" in self.name or "Diffusion" in self.name
}
def _generate_complex_shape(self, prompt):
if "bracket" in prompt.lower():
return trimesh.creation.box([2.1, 1.8, 0.9])
return trimesh.creation.icosphere(radius=0.6, subdivisions=2)
def _generate_neural_shape(self, prompt):
if "gear" in prompt.lower():
return trimesh.creation.cylinder(radius=0.5, height=0.3)
return trimesh.creation.box([1.2, 0.8, 1.4])
def _generate_simple_shape(self, prompt):
if "cylinder" in prompt.lower():
return trimesh.creation.cylinder(radius=0.4, height=1.0)
return trimesh.creation.box([1.0, 1.0, 1.0])
# Initialize generators
generators = {
"GenCAD-3D": GenCAD3DGenerator(),
"CAD-Diffusion": MockCADGenerator("CAD-Diffusion", "Diffusion model for high-quality geometry"),
"ParametricAI": MockCADGenerator("ParametricAI", "Constraint-aware parametric modeling"),
"NeuralCAD-Basic": MockCADGenerator("NeuralCAD-Basic", "Transformer-based parametric generation"),
"STL2BREP-GNN": MockCADGenerator("STL2BREP-GNN", "Graph Neural Network approach"),
"TopoNet": MockCADGenerator("TopoNet", "Topology-preserving mesh generation")
}
elo_system = ELOSystem()
def create_plotly_mesh(mesh, title, color='lightblue'):
"""Create Plotly 3D mesh visualization"""
fig = go.Figure(data=[
go.Mesh3d(
x=mesh.vertices[:, 0],
y=mesh.vertices[:, 1],
z=mesh.vertices[:, 2],
i=mesh.faces[:, 0],
j=mesh.faces[:, 1],
k=mesh.faces[:, 2],
color=color,
opacity=0.8,
name=title,
showscale=False
)
])
fig.update_layout(
title=title,
scene=dict(
xaxis_title='X',
yaxis_title='Y',
zaxis_title='Z',
camera=dict(eye=dict(x=1.5, y=1.5, z=1.5)),
aspectmode='cube'
),
height=400,
margin=dict(l=0, r=0, t=30, b=0)
)
return fig
def generate_comparison(prompt):
"""Generate models from two random generators for comparison"""
# Select two different generators randomly
generator_names = list(generators.keys())
model_a_name, model_b_name = random.sample(generator_names, 2)
model_a = generators[model_a_name]
model_b = generators[model_b_name]
# Load models if needed
if not model_a.loaded:
model_a.load_model()
if not model_b.loaded:
model_b.load_model()
# Generate models
try:
mesh_a, stats_a = model_a.generate(prompt)
mesh_b, stats_b = model_b.generate(prompt)
except Exception as e:
return None, None, f"Error generating models: {e}", "", "", ""
# Create visualizations
fig_a = create_plotly_mesh(mesh_a, f"Model A: {model_a_name}", 'lightblue')
fig_b = create_plotly_mesh(mesh_b, f"Model B: {model_b_name}", 'lightcoral')
# Format stats for display
def format_stats(stats):
model_type = "πŸ”¬ Real Model" if stats['generator'] == "GenCAD-3D" else "🎭 Mock Model"
parametric = "βœ“ Parametric" if stats.get('parametric', False) else "βœ— Mesh Only"
text = f"""**{stats['generator']}** {model_type}
- Faces: {stats['faces']:,}
- Vertices: {stats['vertices']:,}
- Volume: {stats['volume']:.3f}
- Surface Area: {stats['surface_area']:.3f}
- Watertight: {'βœ“' if stats['watertight'] else 'βœ—'}
- {parametric}
- Generation Time: {stats['generation_time']:.1f}s"""
if 'program' in stats:
text += f"\n\n**CAD Program:**\n```\n{stats['program'][:200]}{'...' if len(stats['program']) > 200 else ''}\n```"
return text
stats_text_a = format_stats(stats_a)
stats_text_b = format_stats(stats_b)
return fig_a, fig_b, stats_text_a, stats_text_b, model_a_name, model_b_name
def vote_for_model(choice, model_a_name, model_b_name, prompt):
"""Process vote and update ELO ratings"""
if not model_a_name or not model_b_name:
return "Please generate models first!", create_leaderboard()
result = elo_system.update_ratings(model_a_name, model_b_name, choice, prompt)
vote_message = f"""
🎯 **Vote Recorded!**
**Matchup:** {model_a_name} vs {model_b_name}
**Prompt:** "{prompt}"
**Winner:** {choice}
**Rating Changes:**
- {model_a_name}: {result['old_rating_a']:.0f} β†’ {result['new_rating_a']:.0f} ({result['new_rating_a'] - result['old_rating_a']:+.0f})
- {model_b_name}: {result['old_rating_b']:.0f} β†’ {result['new_rating_b']:.0f} ({result['new_rating_b'] - result['old_rating_b']:+.0f})
**Total Matches:** {len(elo_system.match_history)}
"""
return vote_message, create_leaderboard()
def create_leaderboard():
"""Create current leaderboard display"""
leaderboard = elo_system.get_leaderboard()
leaderboard_text = "## πŸ† CAD Arena Leaderboard\n\n"
for i, (model, rating) in enumerate(leaderboard, 1):
emoji = "πŸ₯‡" if i == 1 else "πŸ₯ˆ" if i == 2 else "πŸ₯‰" if i == 3 else f"{i}."
model_type = "πŸ”¬" if model == "GenCAD-3D" else "🎭"
leaderboard_text += f"{emoji} **{model}** {model_type}: {rating:.0f} ELO\n"
leaderboard_text += f"\n**Total Matches:** {len(elo_system.match_history)}"
return leaderboard_text
# Create Gradio interface
def create_interface():
with gr.Blocks(title="CAD Arena - The Battle of CAD AI", theme=gr.themes.Soft()) as demo:
# Store current comparison state
model_a_state = gr.State("")
model_b_state = gr.State("")
gr.Markdown("""
# 🏟️ CAD Arena - The Battle of CAD AI
**Welcome to CAD Arena!** The definitive battleground where CAD AI models compete head-to-head.
Vote for the best CAD generators and help build the ultimate engineering leaderboard!
πŸ”¬ **Real Models**: GenCAD-3D (MIT) + More Coming Soon
🎭 **Benchmark Models**: Various architectures for comparison
*The premier destination for CAD AI evaluation and ranking*
""")
with gr.Row():
with gr.Column(scale=2):
prompt_input = gr.Textbox(
label="CAD Generation Prompt",
placeholder="e.g., 'Design a mounting bracket for electronics' or 'Create a gear with 12 teeth'",
lines=2,
value="Design a mounting bracket"
)
generate_btn = gr.Button(
"🎲 Generate Model Battle",
variant="primary",
size="lg"
)
gr.Markdown("""
**Example prompts:**
- "Design a mounting bracket"
- "Create a mechanical gear"
- "Generate a housing for electronics"
- "Design a custom connector"
- "Make a parametric bracket with holes"
""")
with gr.Column(scale=1):
leaderboard_display = gr.Markdown(create_leaderboard())
with gr.Row():
with gr.Column():
model_a_plot = gr.Plot(label="Model A")
model_a_stats = gr.Markdown("Generate models to see comparison")
vote_a_btn = gr.Button("πŸ‘ Vote for Model A", variant="secondary")
with gr.Column():
model_b_plot = gr.Plot(label="Model B")
model_b_stats = gr.Markdown("Generate models to see comparison")
vote_b_btn = gr.Button("πŸ‘ Vote for Model B", variant="secondary")
with gr.Row():
tie_btn = gr.Button("🀝 It's a Tie", variant="secondary")
vote_result = gr.Markdown("")
# Technical details section
with gr.Accordion("πŸ”¬ About the Models", open=False):
gr.Markdown("""
**Real CAD AI Models:**
- **GenCAD-3D** πŸ”¬: MIT's diffusion model for parametric CAD programs. Generates actual CAD code that can be executed.
**Mock CAD AI Models (for comparison):**
- **CAD-Diffusion** 🎭: Simulated diffusion model optimized for engineering geometry
- **ParametricAI** 🎭: Mock constraint-aware parametric modeling system
- **NeuralCAD-Basic** 🎭: Simulated transformer-based parametric generation
- **STL2BREP-GNN** 🎭: Mock Graph Neural Network approach using topology
- **TopoNet** 🎭: Mock topology-preserving mesh generation
**Evaluation Criteria:**
- Geometric quality and engineering realism
- Manufacturing feasibility
- Parametric capability (can it generate editable CAD programs?)
- Constraint satisfaction
- Speed and efficiency
**How to Vote:**
Consider which model better satisfies the prompt with realistic, manufacturable geometry.
Real parametric models that generate CAD code should generally score higher than pure mesh output.
""")
# Event handlers
generate_btn.click(
fn=generate_comparison,
inputs=[prompt_input],
outputs=[model_a_plot, model_b_plot, model_a_stats, model_b_stats, model_a_state, model_b_state]
)
vote_a_btn.click(
fn=lambda ma, mb, p: vote_for_model('A', ma, mb, p),
inputs=[model_a_state, model_b_state, prompt_input],
outputs=[vote_result, leaderboard_display]
)
vote_b_btn.click(
fn=lambda ma, mb, p: vote_for_model('B', ma, mb, p),
inputs=[model_a_state, model_b_state, prompt_input],
outputs=[vote_result, leaderboard_display]
)
tie_btn.click(
fn=lambda ma, mb, p: vote_for_model('tie', ma, mb, p),
inputs=[model_a_state, model_b_state, prompt_input],
outputs=[vote_result, leaderboard_display]
)
return demo
# Launch the interface
if __name__ == "__main__":
demo = create_interface()
demo.launch(
share=True,
server_name="0.0.0.0",
server_port=7860
)