| 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") |
|
|
| |
| class ELOSystem: |
| def __init__(self): |
| self.ratings = self.load_ratings() |
| self.match_history = [] |
|
|
| def load_ratings(self): |
| |
| 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 |
|
|
| |
| 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 |
|
|
| |
| 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 |
|
|
| |
| 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...") |
| |
| |
| |
| |
| |
| |
| |
|
|
| |
| time.sleep(2) |
| 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() |
|
|
| |
| time.sleep(random.uniform(2.0, 5.0)) |
|
|
| |
| if "bracket" in prompt.lower(): |
| |
| 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(): |
| |
| 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: |
| |
| mesh = trimesh.creation.box([1.5, 1, 0.8]) |
| |
| hole = trimesh.creation.cylinder(radius=0.2, height=1.0) |
| mesh = mesh.difference(hole) |
|
|
| |
| mesh.apply_scale([1.1, 0.9, 1.0]) |
|
|
| |
| 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)""" |
|
|
| |
| 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)) |
|
|
| |
| 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) |
|
|
| |
| 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]) |
|
|
| |
| 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""" |
| |
| 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] |
|
|
| |
| if not model_a.loaded: |
| model_a.load_model() |
| if not model_b.loaded: |
| model_b.load_model() |
|
|
| |
| 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}", "", "", "" |
|
|
| |
| 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') |
|
|
| |
| 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 |
|
|
| |
| def create_interface(): |
| with gr.Blocks(title="CAD Arena - The Battle of CAD AI", theme=gr.themes.Soft()) as demo: |
|
|
| |
| 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("") |
|
|
| |
| 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. |
| """) |
|
|
| |
| 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 |
|
|
| |
| if __name__ == "__main__": |
| demo = create_interface() |
| demo.launch( |
| share=True, |
| server_name="0.0.0.0", |
| server_port=7860 |
| ) |