Spaces:
Running
Running
Delete app.py
Browse files
app.py
DELETED
|
@@ -1,516 +0,0 @@
|
|
| 1 |
-
# app.py
|
| 2 |
-
# Combined Engineering Suite for Hugging Face Deployment
|
| 3 |
-
|
| 4 |
-
# --- 1. IMPORTS ---
|
| 5 |
-
# Standard library imports
|
| 6 |
-
import re
|
| 7 |
-
import os
|
| 8 |
-
import io
|
| 9 |
-
import warnings
|
| 10 |
-
|
| 11 |
-
# Third-party imports
|
| 12 |
-
import gradio as gr
|
| 13 |
-
import numpy as np
|
| 14 |
-
import matplotlib
|
| 15 |
-
matplotlib.use('Agg') # Use a non-interactive backend
|
| 16 |
-
import matplotlib.pyplot as plt
|
| 17 |
-
from matplotlib import cm
|
| 18 |
-
from PIL import Image, ImageDraw, ImageFont
|
| 19 |
-
import plotly.graph_objects as go
|
| 20 |
-
from shapely.geometry import box, Point
|
| 21 |
-
from shapely.affinity import scale
|
| 22 |
-
import trimesh
|
| 23 |
-
import pyvista as pv
|
| 24 |
-
|
| 25 |
-
# Suppress warnings for a cleaner output
|
| 26 |
-
warnings.filterwarnings('ignore')
|
| 27 |
-
|
| 28 |
-
# --- 2. CORE LOGIC FROM PDFS ---
|
| 29 |
-
|
| 30 |
-
# === MODULE 1: Text to CAD & G-Code (from g-code.pdf) ===
|
| 31 |
-
|
| 32 |
-
def parse_simple_description(description):
|
| 33 |
-
"""Parses a textual description to extract geometric parameters for a plate."""
|
| 34 |
-
width, height = 100, 100
|
| 35 |
-
holes, slots, ovals = [], [], []
|
| 36 |
-
|
| 37 |
-
# Dimensions
|
| 38 |
-
dim_match = re.search(r'(\d+)\s*mm\s*[xX×]\s*(\d+)\s*mm', description)
|
| 39 |
-
if dim_match:
|
| 40 |
-
width, height = int(dim_match.group(1)), int(dim_match.group(2))
|
| 41 |
-
|
| 42 |
-
# Holes
|
| 43 |
-
for hole_match in re.finditer(r'(hole|circle|circular cutout)[^\d]*(\d+)\s*mm', description):
|
| 44 |
-
holes.append({'x': width / 2, 'y': height / 2, 'diameter': int(hole_match.group(2))})
|
| 45 |
-
|
| 46 |
-
# Slots
|
| 47 |
-
for slot_match in re.finditer(r'(\d+)\s*mm\s+long\s+and\s+(\d+)\s*mm\s+wide\s+slot', description):
|
| 48 |
-
slots.append({'x': width / 2, 'y': height / 2, 'length': int(slot_match.group(1)), 'width': int(slot_match.group(2))})
|
| 49 |
-
|
| 50 |
-
# Ovals
|
| 51 |
-
for oval_match in re.finditer(r'oval\s+hole\s+(\d+)\s*mm\s+long\s+and\s+(\d+)\s*mm\s+wide', description):
|
| 52 |
-
ovals.append({'x': width / 2, 'y': height / 2, 'length': int(oval_match.group(1)), 'width': int(oval_match.group(2))})
|
| 53 |
-
|
| 54 |
-
return {"width": width, "height": height, "holes": holes, "slots": slots, "ovals": ovals}
|
| 55 |
-
|
| 56 |
-
def generate_simple_3_view_drawing(description):
|
| 57 |
-
"""Generates a 3-view engineering drawing from a simple description."""
|
| 58 |
-
parsed = parse_simple_description(description)
|
| 59 |
-
width, height, depth = parsed["width"], parsed["height"], 5
|
| 60 |
-
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
|
| 61 |
-
plt.style.use('grayscale')
|
| 62 |
-
|
| 63 |
-
views = ['Top View', 'Front View', 'Side View']
|
| 64 |
-
for ax, view in zip(axes, views):
|
| 65 |
-
ax.set_title(view)
|
| 66 |
-
ax.grid(True, linestyle='--', linewidth=0.5)
|
| 67 |
-
ax.set_aspect('equal', adjustable='box')
|
| 68 |
-
|
| 69 |
-
if view == "Top View":
|
| 70 |
-
shape = box(0, 0, width, height)
|
| 71 |
-
x, y = shape.exterior.xy
|
| 72 |
-
ax.plot(x, y, color='black')
|
| 73 |
-
for hole in parsed["holes"]:
|
| 74 |
-
ax.plot(*Point(hole['x'], hole['y']).buffer(hole['diameter'] / 2).exterior.xy, color='black')
|
| 75 |
-
for slot in parsed["slots"]:
|
| 76 |
-
ax.plot(*scale(Point(slot['x'], slot['y']).buffer(1), slot['length']/2, slot['width']/2).exterior.xy, color='black')
|
| 77 |
-
for oval in parsed["ovals"]:
|
| 78 |
-
ax.plot(*scale(Point(oval['x'], oval['y']).buffer(1), oval['length']/2, oval['width']/2).exterior.xy, color='black')
|
| 79 |
-
ax.set_xlim(-10, width + 10)
|
| 80 |
-
ax.set_ylim(-10, height + 10)
|
| 81 |
-
elif view == "Front View":
|
| 82 |
-
ax.plot(*box(0, 0, width, depth).exterior.xy, color='black')
|
| 83 |
-
ax.set_xlim(-10, width + 10)
|
| 84 |
-
ax.set_ylim(-10, depth + 10)
|
| 85 |
-
elif view == "Side View":
|
| 86 |
-
ax.plot(*box(0, 0, height, depth).exterior.xy, color='black')
|
| 87 |
-
ax.set_xlim(-10, height + 10)
|
| 88 |
-
ax.set_ylim(-10, depth + 10)
|
| 89 |
-
|
| 90 |
-
plt.tight_layout()
|
| 91 |
-
drawing_path = "simple_3_view_drawing.png"
|
| 92 |
-
fig.savefig(drawing_path)
|
| 93 |
-
plt.close(fig)
|
| 94 |
-
return drawing_path
|
| 95 |
-
|
| 96 |
-
def generate_gcode(description):
|
| 97 |
-
"""Generates G-code for milling the part based on the description."""
|
| 98 |
-
parsed = parse_simple_description(description)
|
| 99 |
-
w, h = parsed['width'], parsed['height']
|
| 100 |
-
gcode = [
|
| 101 |
-
"G21 ; Set units to mm", "G90 ; Use absolute positioning", "G17 ; Select XY plane",
|
| 102 |
-
"M3 S1000 ; Start spindle", "G0 Z5 ; Lift Z to a safe height",
|
| 103 |
-
"\n; --- Mill Outer Profile ---",
|
| 104 |
-
"G0 X0 Y0", "G1 Z-1 F100", f"G1 X{w} F300", f"G1 Y{h}", f"G1 X0", "G1 Y0", "G0 Z5"
|
| 105 |
-
]
|
| 106 |
-
for hole in parsed['holes']:
|
| 107 |
-
x, y, r = hole['x'], hole['y'], hole['diameter'] / 2
|
| 108 |
-
gcode.extend([f"\n; --- Mill Hole at X{x}, Y{y}, D{hole['diameter']} ---", f"G0 X{x - r} Y{y}", "G1 Z-1 F100", f"G2 I{r} J0 F200", "G0 Z5"])
|
| 109 |
-
for slot in parsed['slots']:
|
| 110 |
-
x, y, l, w_slot = slot['x'], slot['y'], slot['length'], slot['width']
|
| 111 |
-
r = w_slot / 2
|
| 112 |
-
x_start, x_end = x - (l - w_slot) / 2, x + (l - w_slot) / 2
|
| 113 |
-
gcode.extend([f"\n; --- Mill Slot at center X{x}, Y{y} ---", f"G0 X{x_start} Y{y - r}", "G1 Z-1 F100", f"G1 X{x_end} F200", f"G2 I0 J{r}", f"G1 X{x_start}", f"G2 I0 J{r}", "G0 Z5"])
|
| 114 |
-
for oval in parsed['ovals']:
|
| 115 |
-
gcode.append(f"\n; --- Oval hole at X{oval['x']}, Y{oval['y']} (manual operation needed) ---")
|
| 116 |
-
gcode.extend(["\nM5 ; Stop spindle", "G0 X0 Y0 ; Return to home", "M30 ; End of program"])
|
| 117 |
-
return "\n".join(gcode)
|
| 118 |
-
|
| 119 |
-
def export_svg(description):
|
| 120 |
-
"""Exports a 2D drawing of the part top-view as an SVG file."""
|
| 121 |
-
parsed = parse_simple_description(description)
|
| 122 |
-
width, height = parsed["width"], parsed["height"]
|
| 123 |
-
fig, ax = plt.subplots(figsize=(width/25.4, height/25.4)) # Inches
|
| 124 |
-
ax.set_aspect('equal')
|
| 125 |
-
ax.plot(*box(0, 0, width, height).exterior.xy, color='black', linewidth=2)
|
| 126 |
-
for hole in parsed["holes"]:
|
| 127 |
-
ax.plot(*Point(hole['x'], hole['y']).buffer(hole['diameter'] / 2).exterior.xy, color='black', linewidth=1.5)
|
| 128 |
-
ax.set_xlim(-10, width + 10)
|
| 129 |
-
ax.set_ylim(-10, height + 10)
|
| 130 |
-
ax.axis('off')
|
| 131 |
-
svg_path = "drawing.svg"
|
| 132 |
-
fig.savefig(svg_path, format="svg", bbox_inches='tight', pad_inches=0.1)
|
| 133 |
-
plt.close(fig)
|
| 134 |
-
return svg_path
|
| 135 |
-
|
| 136 |
-
def process_simple_cad(description):
|
| 137 |
-
"""Top-level function for the simple CAD generator."""
|
| 138 |
-
if not description.strip():
|
| 139 |
-
return None, "Please enter a description.", None
|
| 140 |
-
try:
|
| 141 |
-
drawing_path = generate_simple_3_view_drawing(description)
|
| 142 |
-
gcode = generate_gcode(description)
|
| 143 |
-
svg_path = export_svg(description)
|
| 144 |
-
return drawing_path, gcode, svg_path
|
| 145 |
-
except Exception as e:
|
| 146 |
-
return None, f"An error occurred: {e}", None
|
| 147 |
-
|
| 148 |
-
# === MODULE 2: Advanced Text-to-CAD (from text to cad.pdf) ===
|
| 149 |
-
class TextToCADGenerator:
|
| 150 |
-
"""Text-to-CAD generator using procedural geometry."""
|
| 151 |
-
def __init__(self):
|
| 152 |
-
self.shapes_library = {
|
| 153 |
-
'cube': self._create_cube, 'box': self._create_cube,
|
| 154 |
-
'sphere': self._create_sphere, 'ball': self._create_sphere,
|
| 155 |
-
'cylinder': self._create_cylinder, 'tube': self._create_cylinder,
|
| 156 |
-
'cone': self._create_cone, 'plate': self._create_plate,
|
| 157 |
-
'washer': self._create_washer, 'bracket': self._create_bracket,
|
| 158 |
-
}
|
| 159 |
-
|
| 160 |
-
def parse_prompt(self, prompt: str) -> dict:
|
| 161 |
-
prompt = prompt.lower().strip()
|
| 162 |
-
dimensions = self._extract_dimensions(prompt)
|
| 163 |
-
shape_type = next((shape for shape in self.shapes_library if shape in prompt), 'cube')
|
| 164 |
-
color = next((c for c in ['red', 'blue', 'green', 'yellow', 'gray'] if c in prompt), 'lightblue')
|
| 165 |
-
return {'shape': shape_type, 'dimensions': dimensions, 'color': color, 'prompt': prompt}
|
| 166 |
-
|
| 167 |
-
def _extract_dimensions(self, prompt: str) -> dict:
|
| 168 |
-
dims = {'length': 10, 'width': 10, 'height': 10, 'radius': 5, 'diameter': 10}
|
| 169 |
-
patterns = {
|
| 170 |
-
'length': r'length\s*[:=]?\s*(\d+\.?\d*)', 'width': r'width\s*[:=]?\s*(\d+\.?\d*)',
|
| 171 |
-
'height': r'height\s*[:=]?\s*(\d+\.?\d*)', 'radius': r'radius\s*[:=]?\s*(\d+\.?\d*)',
|
| 172 |
-
'diameter': r'diameter\s*[:=]?\s*(\d+\.?\d*)', 'thick': r'thick\w*\s*[:=]?\s*(\d+\.?\d*)'
|
| 173 |
-
}
|
| 174 |
-
for key, pattern in patterns.items():
|
| 175 |
-
match = re.search(pattern, prompt, re.IGNORECASE)
|
| 176 |
-
if match:
|
| 177 |
-
dims[key] = float(match.group(1))
|
| 178 |
-
return dims
|
| 179 |
-
|
| 180 |
-
def generate_3d_model(self, params: dict) -> trimesh.Trimesh:
|
| 181 |
-
shape_func = self.shapes_library.get(params['shape'], self._create_cube)
|
| 182 |
-
return shape_func(params['dimensions'])
|
| 183 |
-
|
| 184 |
-
def generate_2d_drawing(self, params: dict) -> Image.Image:
|
| 185 |
-
fig, (ax1, ax2, ax3) = plt.subplots(1, 3, figsize=(15, 5))
|
| 186 |
-
fig.suptitle(f"Technical Drawing: {params['shape'].title()}", fontsize=16)
|
| 187 |
-
dims = params['dimensions']
|
| 188 |
-
# Simplified drawing logic
|
| 189 |
-
self._draw_rectangular_part(ax1, ax2, ax3, dims, params['shape'])
|
| 190 |
-
plt.tight_layout(rect=[0, 0.03, 1, 0.95])
|
| 191 |
-
buf = io.BytesIO()
|
| 192 |
-
plt.savefig(buf, format='png', dpi=150)
|
| 193 |
-
buf.seek(0)
|
| 194 |
-
img = Image.open(buf)
|
| 195 |
-
plt.close(fig)
|
| 196 |
-
return img
|
| 197 |
-
|
| 198 |
-
def _draw_rectangular_part(self, ax1, ax2, ax3, dims, shape):
|
| 199 |
-
l, w, h = dims['length'], dims['width'], dims['height']
|
| 200 |
-
views = {"Front View": (w, h), "Top View": (w, l), "Side View": (l, h)}
|
| 201 |
-
axes = {"Front View": ax1, "Top View": ax2, "Side View": ax3}
|
| 202 |
-
for title, (dim1, dim2) in views.items():
|
| 203 |
-
ax = axes[title]
|
| 204 |
-
ax.set_title(title)
|
| 205 |
-
ax.add_patch(plt.Rectangle((0, 0), dim1, dim2, fill=False, edgecolor='black', linewidth=2))
|
| 206 |
-
ax.set_xlim(-dim1*0.1, dim1*1.1)
|
| 207 |
-
ax.set_ylim(-dim2*0.1, dim2*1.1)
|
| 208 |
-
ax.set_aspect('equal')
|
| 209 |
-
ax.grid(True, alpha=0.3)
|
| 210 |
-
ax.set_xlabel(f"Dim 1: {dim1:.2f}")
|
| 211 |
-
ax.set_ylabel(f"Dim 2: {dim2:.2f}")
|
| 212 |
-
|
| 213 |
-
def generate_3d_visualization(self, mesh: trimesh.Trimesh, color: str = 'lightblue') -> go.Figure:
|
| 214 |
-
fig = go.Figure(data=[go.Mesh3d(
|
| 215 |
-
x=mesh.vertices[:, 0], y=mesh.vertices[:, 1], z=mesh.vertices[:, 2],
|
| 216 |
-
i=mesh.faces[:, 0], j=mesh.faces[:, 1], k=mesh.faces[:, 2],
|
| 217 |
-
color=color, opacity=0.9
|
| 218 |
-
)])
|
| 219 |
-
fig.update_layout(title="3D CAD Model", scene=dict(xaxis_title="X", yaxis_title="Y", zaxis_title="Z"))
|
| 220 |
-
return fig
|
| 221 |
-
|
| 222 |
-
# Shape creation methods
|
| 223 |
-
def _create_cube(self, dims: dict) -> trimesh.Trimesh:
|
| 224 |
-
return trimesh.creation.box(extents=[dims['length'], dims['width'], dims['height']])
|
| 225 |
-
def _create_sphere(self, dims: dict) -> trimesh.Trimesh:
|
| 226 |
-
return trimesh.creation.icosphere(subdivisions=3, radius=dims.get('radius', dims.get('diameter', 10) / 2))
|
| 227 |
-
def _create_cylinder(self, dims: dict) -> trimesh.Trimesh:
|
| 228 |
-
return trimesh.creation.cylinder(radius=dims.get('radius', 5), height=dims.get('height', 10))
|
| 229 |
-
def _create_cone(self, dims: dict) -> trimesh.Trimesh:
|
| 230 |
-
return trimesh.creation.cone(radius=dims.get('radius', 5), height=dims.get('height', 10))
|
| 231 |
-
def _create_plate(self, dims: dict) -> trimesh.Trimesh:
|
| 232 |
-
return trimesh.creation.box(extents=[dims['length'], dims['width'], dims.get('height', 2)])
|
| 233 |
-
def _create_washer(self, dims: dict) -> trimesh.Trimesh:
|
| 234 |
-
outer = trimesh.creation.cylinder(radius=dims.get('radius', 10), height=dims.get('height', 2))
|
| 235 |
-
inner = trimesh.creation.cylinder(radius=dims.get('radius', 10) * 0.5, height=dims.get('height', 2) * 1.1)
|
| 236 |
-
return outer.difference(inner)
|
| 237 |
-
def _create_bracket(self, dims: dict) -> trimesh.Trimesh:
|
| 238 |
-
l, w, h, t = dims.get('length', 20), dims.get('width', 15), dims.get('height', 20), dims.get('thick', 3)
|
| 239 |
-
base = trimesh.creation.box(extents=[l, w, t])
|
| 240 |
-
base.apply_translation([l/2, w/2, t/2])
|
| 241 |
-
wall = trimesh.creation.box(extents=[t, w, h])
|
| 242 |
-
wall.apply_translation([t/2, w/2, h/2])
|
| 243 |
-
return base.union(wall)
|
| 244 |
-
|
| 245 |
-
cad_generator = TextToCADGenerator()
|
| 246 |
-
|
| 247 |
-
def process_advanced_text_to_cad(prompt: str):
|
| 248 |
-
"""Main function for the advanced CAD generator."""
|
| 249 |
-
try:
|
| 250 |
-
params = cad_generator.parse_prompt(prompt)
|
| 251 |
-
mesh_3d = cad_generator.generate_3d_model(params)
|
| 252 |
-
drawing_2d = cad_generator.generate_2d_drawing(params)
|
| 253 |
-
fig_3d = cad_generator.generate_3d_visualization(mesh_3d, params['color'])
|
| 254 |
-
summary = f"*Shape:* {params['shape'].title()}\n*Dimensions:* {params['dimensions']}"
|
| 255 |
-
return drawing_2d, fig_3d, summary
|
| 256 |
-
except Exception as e:
|
| 257 |
-
return None, go.Figure().add_annotation(text=f"Error: {e}", showarrow=False), f"Error: {e}"
|
| 258 |
-
|
| 259 |
-
|
| 260 |
-
# === MODULE 3: 3D to Orthographic View (from cad to ortho.pdf) ===
|
| 261 |
-
|
| 262 |
-
def generate_ortho_views(uploaded_file):
|
| 263 |
-
"""Generates orthographic views from an uploaded 3D model file."""
|
| 264 |
-
if uploaded_file is None:
|
| 265 |
-
return None, "Please upload a 3D model file."
|
| 266 |
-
try:
|
| 267 |
-
# PyVista reads from a file path
|
| 268 |
-
mesh = pv.read(uploaded_file.name)
|
| 269 |
-
|
| 270 |
-
# Generate base views with PyVista
|
| 271 |
-
views, filenames = ['xy', 'xz', 'yz'], ['front.png', 'top.png', 'side.png']
|
| 272 |
-
plotter = pv.Plotter(off_screen=True, window_size=[800, 800])
|
| 273 |
-
plotter.background_color = 'white'
|
| 274 |
-
plotter.enable_parallel_projection()
|
| 275 |
-
for i, view in enumerate(views):
|
| 276 |
-
plotter.clear()
|
| 277 |
-
plotter.add_mesh(mesh.extract_feature_edges(), color='black', line_width=2)
|
| 278 |
-
plotter.camera_position = view
|
| 279 |
-
plotter.reset_camera()
|
| 280 |
-
plotter.camera.zoom(1.2)
|
| 281 |
-
plotter.screenshot(filenames[i])
|
| 282 |
-
|
| 283 |
-
# Combine and add dimensions with Matplotlib
|
| 284 |
-
fig, axes = plt.subplots(1, 3, figsize=(18, 6))
|
| 285 |
-
fig.patch.set_facecolor('white')
|
| 286 |
-
bounds = mesh.bounds
|
| 287 |
-
x_dim, y_dim, z_dim = bounds[1]-bounds[0], bounds[3]-bounds[2], bounds[5]-bounds[4]
|
| 288 |
-
dims = [(x_dim, y_dim), (x_dim, z_dim), (y_dim, z_dim)]
|
| 289 |
-
view_titles = ["Front View (XY)", "Top View (XZ)", "Side View (YZ)"]
|
| 290 |
-
|
| 291 |
-
for i, ax in enumerate(axes):
|
| 292 |
-
img = plt.imread(filenames[i])
|
| 293 |
-
ax.imshow(img)
|
| 294 |
-
ax.set_title(view_titles[i], fontsize=12, pad=10)
|
| 295 |
-
ax.axis('off')
|
| 296 |
-
dim1, dim2 = dims[i]
|
| 297 |
-
# Add annotations
|
| 298 |
-
ax.text(0.5, 0.05, f"Width: {dim1:.2f}", transform=ax.transAxes, ha='center', va='bottom', fontsize=10)
|
| 299 |
-
ax.text(0.05, 0.5, f"Height: {dim2:.2f}", transform=ax.transAxes, ha='left', va='center', rotation=90, fontsize=10)
|
| 300 |
-
|
| 301 |
-
final_path = 'orthographic_views_with_dimensions.png'
|
| 302 |
-
plt.tight_layout()
|
| 303 |
-
plt.savefig(final_path, dpi=150)
|
| 304 |
-
plt.close(fig)
|
| 305 |
-
return final_path, f"Successfully generated views for {os.path.basename(uploaded_file.name)}."
|
| 306 |
-
|
| 307 |
-
except Exception as e:
|
| 308 |
-
return None, f"An error occurred: {e}"
|
| 309 |
-
|
| 310 |
-
|
| 311 |
-
# === MODULE 4: 2D CFD Simulation (from cfd.pdf) ===
|
| 312 |
-
|
| 313 |
-
def run_cfd_simulation(Lx, Ly, Nx, Ny, inlet_velocity_u, rho, mu, ox1, oy1, ox2, oy2, max_iter, p_iter):
|
| 314 |
-
"""Runs a 2D CFD simulation and returns the result plot."""
|
| 315 |
-
try:
|
| 316 |
-
# Grid and Initialization
|
| 317 |
-
dx, dy = Lx / (Nx - 1), Ly / (Ny - 1)
|
| 318 |
-
x, y = np.linspace(0, Lx, Nx), np.linspace(0, Ly, Ny)
|
| 319 |
-
X, Y = np.meshgrid(x, y)
|
| 320 |
-
u, v, p = np.zeros((Ny, Nx)), np.zeros((Ny, Nx)), np.zeros((Ny, Nx))
|
| 321 |
-
|
| 322 |
-
# Obstacle
|
| 323 |
-
obstacle_mask = np.zeros((Ny, Nx), dtype=bool)
|
| 324 |
-
if ox1 < ox2 and oy1 < oy2:
|
| 325 |
-
i1, i2 = int(ox1 / dx), int(ox2 / dx)
|
| 326 |
-
j1, j2 = int(oy1 / dy), int(oy2 / dy)
|
| 327 |
-
obstacle_mask[j1:j2, i1:i2] = True
|
| 328 |
-
|
| 329 |
-
# Main Loop (SIMPLE algorithm simplified)
|
| 330 |
-
nu = mu / rho
|
| 331 |
-
udiff, stepcount = 1.0, 0
|
| 332 |
-
alpha_p, alpha_uv = 0.1, 0.5 # Relaxation factors
|
| 333 |
-
|
| 334 |
-
for it in range(max_iter):
|
| 335 |
-
un, vn = u.copy(), v.copy()
|
| 336 |
-
|
| 337 |
-
# Momentum predictor (simplified)
|
| 338 |
-
# Pressure correction (simplified Poisson equation)
|
| 339 |
-
p_prime = np.zeros_like(p)
|
| 340 |
-
for _ in range(p_iter):
|
| 341 |
-
pn_prime = p_prime.copy()
|
| 342 |
-
p_prime[1:-1, 1:-1] = ((pn_prime[1:-1, 2:] + pn_prime[1:-1, :-2]) * dy**2 +
|
| 343 |
-
(pn_prime[2:, 1:-1] + pn_prime[:-2, 1:-1]) * dx**2) / \
|
| 344 |
-
(2 * (dx**2 + dy**2))
|
| 345 |
-
# BCs for p_prime
|
| 346 |
-
p_prime[:, -1] = 0 # Outlet
|
| 347 |
-
p_prime[:, 0] = p_prime[:, 1]
|
| 348 |
-
p_prime[0, :] = p_prime[1, :]
|
| 349 |
-
p_prime[-1, :] = p_prime[-2, :]
|
| 350 |
-
|
| 351 |
-
p += alpha_p * p_prime
|
| 352 |
-
|
| 353 |
-
# Velocity corrector
|
| 354 |
-
u[1:-1, 1:-1] -= alpha_uv * (p_prime[1:-1, 1:-1] - p_prime[1:-1, :-2]) / dx
|
| 355 |
-
v[1:-1, 1:-1] -= alpha_uv * (p_prime[1:-1, 1:-1] - p_prime[:-2, 1:-1]) / dy
|
| 356 |
-
|
| 357 |
-
# Boundary Conditions
|
| 358 |
-
u[:, 0], v[:, 0] = inlet_velocity_u, 0
|
| 359 |
-
u[:, -1], v[:, -1] = u[:, -2], v[:, -2]
|
| 360 |
-
u[0, :], v[0, :] = 0, 0
|
| 361 |
-
u[-1, :], v[-1, :] = 0, 0
|
| 362 |
-
u[obstacle_mask], v[obstacle_mask] = 0, 0
|
| 363 |
-
|
| 364 |
-
udiff = np.linalg.norm(u - un) / (np.linalg.norm(un) + 1e-6)
|
| 365 |
-
if udiff < 1e-6: break
|
| 366 |
-
|
| 367 |
-
# Visualization
|
| 368 |
-
fig, ax = plt.subplots(figsize=(10, 5))
|
| 369 |
-
velocity_magnitude = np.sqrt(u**2 + v**2)
|
| 370 |
-
velocity_magnitude[obstacle_mask] = np.nan
|
| 371 |
-
|
| 372 |
-
cf = ax.contourf(X, Y, velocity_magnitude, levels=50, cmap=cm.jet)
|
| 373 |
-
fig.colorbar(cf, label='Velocity Magnitude (m/s)')
|
| 374 |
-
ax.streamplot(X, Y, u, v, color='black', linewidth=0.7, density=1.5)
|
| 375 |
-
if np.any(obstacle_mask):
|
| 376 |
-
ax.contour(X, Y, obstacle_mask, levels=[0.5], colors='grey', linewidths=3)
|
| 377 |
-
|
| 378 |
-
ax.set_title(f'CFD Simulation Results after {it+1} iterations')
|
| 379 |
-
ax.set_xlabel('X (m)')
|
| 380 |
-
ax.set_ylabel('Y (m)')
|
| 381 |
-
ax.set_aspect('equal')
|
| 382 |
-
plt.tight_layout()
|
| 383 |
-
|
| 384 |
-
return fig
|
| 385 |
-
except Exception as e:
|
| 386 |
-
fig, ax = plt.subplots()
|
| 387 |
-
ax.text(0.5, 0.5, f"Error during CFD simulation:\n{e}", ha='center', va='center')
|
| 388 |
-
return fig
|
| 389 |
-
|
| 390 |
-
# --- 3. GRADIO UI ---
|
| 391 |
-
|
| 392 |
-
def create_gradio_interface():
|
| 393 |
-
"""Create and launch the main Gradio interface."""
|
| 394 |
-
|
| 395 |
-
css = """
|
| 396 |
-
.gradio-container { max-width: 1280px !important; margin: auto; }
|
| 397 |
-
.gr-tabs { border: 1px solid #E0E0E0; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.05); }
|
| 398 |
-
footer { display: none !important; }
|
| 399 |
-
"""
|
| 400 |
-
|
| 401 |
-
with gr.Blocks(theme=gr.themes.Soft(), css=css, title="Engineering Suite") as demo:
|
| 402 |
-
gr.Markdown("# 🛠️ Engineering Suite")
|
| 403 |
-
gr.Markdown("A collection of tools for CAD generation, G-Code, and simulation. Created by combining multiple code sources.")
|
| 404 |
-
|
| 405 |
-
with gr.Tabs():
|
| 406 |
-
# --- Tab 1: Simple Text to CAD & G-Code ---
|
| 407 |
-
with gr.TabItem("Text to CAD & G-Code"):
|
| 408 |
-
gr.Markdown("### Describe a simple 2D part to generate drawings and G-Code.")
|
| 409 |
-
with gr.Row():
|
| 410 |
-
with gr.Column(scale=1):
|
| 411 |
-
simple_cad_input = gr.Textbox(
|
| 412 |
-
lines=4,
|
| 413 |
-
label="Part Description",
|
| 414 |
-
placeholder="e.g., A 100mm x 50mm plate with a 20mm diameter hole and a 30mm long and 10mm wide slot"
|
| 415 |
-
)
|
| 416 |
-
simple_cad_button = gr.Button("Generate", variant="primary")
|
| 417 |
-
with gr.Column(scale=2):
|
| 418 |
-
simple_cad_img_output = gr.Image(label="3-View Drawing")
|
| 419 |
-
with gr.Row():
|
| 420 |
-
simple_cad_gcode_output = gr.Code(label="Generated G-Code", language="gcode")
|
| 421 |
-
simple_cad_svg_output = gr.File(label="Download SVG Drawing")
|
| 422 |
-
|
| 423 |
-
gr.Examples(
|
| 424 |
-
examples=[
|
| 425 |
-
["A 150mm x 100mm plate with a 25mm diameter circular cutout"],
|
| 426 |
-
["A 100mm x 100mm plate with a 50mm long and 10mm wide slot"],
|
| 427 |
-
],
|
| 428 |
-
inputs=simple_cad_input
|
| 429 |
-
)
|
| 430 |
-
|
| 431 |
-
# --- Tab 2: Advanced Text-to-CAD ---
|
| 432 |
-
with gr.TabItem("Advanced Text-to-CAD"):
|
| 433 |
-
gr.Markdown("### Generate a 3D model and technical drawing from a more detailed description.")
|
| 434 |
-
with gr.Row():
|
| 435 |
-
with gr.Column(scale=1):
|
| 436 |
-
adv_cad_input = gr.Textbox(lines=4, label="Design Prompt", placeholder="e.g., Create a blue bracket with length 50, width 30, height 40 and thickness 5")
|
| 437 |
-
adv_cad_button = gr.Button("Generate 3D Model", variant="primary")
|
| 438 |
-
adv_cad_summary = gr.Markdown(label="Generation Summary")
|
| 439 |
-
with gr.Column(scale=2):
|
| 440 |
-
adv_cad_img_output = gr.Image(label="2D Technical Drawing")
|
| 441 |
-
adv_cad_3d_output = gr.Plot(label="Interactive 3D Model")
|
| 442 |
-
|
| 443 |
-
gr.Examples(
|
| 444 |
-
examples=[
|
| 445 |
-
["A red cube with length 20, width 30, height 15"],
|
| 446 |
-
["A green cylinder with radius 10 and height 40"],
|
| 447 |
-
["A gray washer with radius 15 and height 3"],
|
| 448 |
-
],
|
| 449 |
-
inputs=adv_cad_input
|
| 450 |
-
)
|
| 451 |
-
|
| 452 |
-
# --- Tab 3: 3D to Orthographic View ---
|
| 453 |
-
with gr.TabItem("3D to Orthographic View"):
|
| 454 |
-
gr.Markdown("### Upload a 3D model file (.stl, .obj) to generate its dimensioned orthographic views.")
|
| 455 |
-
with gr.Row():
|
| 456 |
-
with gr.Column(scale=1):
|
| 457 |
-
ortho_input = gr.File(label="Upload 3D Model (.stl, .obj, .ply)")
|
| 458 |
-
ortho_button = gr.Button("Generate Ortho Views", variant="primary")
|
| 459 |
-
ortho_status = gr.Textbox(label="Status", interactive=False)
|
| 460 |
-
with gr.Column(scale=2):
|
| 461 |
-
ortho_output = gr.Image(label="Orthographic Views with Dimensions")
|
| 462 |
-
|
| 463 |
-
# --- Tab 4: 2D CFD Simulation ---
|
| 464 |
-
with gr.TabItem("2D CFD Simulation"):
|
| 465 |
-
gr.Markdown("### Configure and run a 2D channel flow simulation.")
|
| 466 |
-
with gr.Row():
|
| 467 |
-
with gr.Column():
|
| 468 |
-
gr.Markdown("**Domain & Grid**")
|
| 469 |
-
cfd_Lx = gr.Slider(1.0, 5.0, value=2.0, label="Channel Length (Lx)")
|
| 470 |
-
cfd_Ly = gr.Slider(0.5, 2.0, value=1.0, label="Channel Height (Ly)")
|
| 471 |
-
cfd_Nx = gr.Slider(31, 101, value=61, step=10, label="Grid Points X (Nx)")
|
| 472 |
-
cfd_Ny = gr.Slider(21, 81, value=41, step=10, label="Grid Points Y (Ny)")
|
| 473 |
-
gr.Markdown("**Fluid Properties**")
|
| 474 |
-
cfd_vel = gr.Slider(0.001, 0.1, value=0.01, label="Inlet Velocity (m/s)")
|
| 475 |
-
cfd_rho = gr.Slider(1.0, 1000.0, value=1.0, label="Density (kg/m^3)")
|
| 476 |
-
cfd_mu = gr.Slider(0.001, 0.1, value=0.02, label="Viscosity (Pa.s)")
|
| 477 |
-
with gr.Column():
|
| 478 |
-
gr.Markdown("**Obstacle (relative to domain)**")
|
| 479 |
-
cfd_ox1 = gr.Slider(0.0, 1.0, value=0.4, label="Obstacle X1")
|
| 480 |
-
cfd_oy1 = gr.Slider(0.0, 1.0, value=0.4, label="Obstacle Y1")
|
| 481 |
-
cfd_ox2 = gr.Slider(0.0, 1.0, value=0.6, label="Obstacle X2")
|
| 482 |
-
cfd_oy2 = gr.Slider(0.0, 1.0, value=0.6, label="Obstacle Y2")
|
| 483 |
-
gr.Markdown("**Solver Settings**")
|
| 484 |
-
cfd_max_iter = gr.Slider(100, 5000, value=500, step=100, label="Max Iterations")
|
| 485 |
-
cfd_p_iter = gr.Slider(20, 200, value=50, step=10, label="Pressure Solver Iterations")
|
| 486 |
-
cfd_button = gr.Button("Run Simulation", variant="primary")
|
| 487 |
-
cfd_output = gr.Plot(label="CFD Result")
|
| 488 |
-
|
| 489 |
-
# --- Event Handlers ---
|
| 490 |
-
simple_cad_button.click(
|
| 491 |
-
fn=process_simple_cad,
|
| 492 |
-
inputs=[simple_cad_input],
|
| 493 |
-
outputs=[simple_cad_img_output, simple_cad_gcode_output, simple_cad_svg_output]
|
| 494 |
-
)
|
| 495 |
-
adv_cad_button.click(
|
| 496 |
-
fn=process_advanced_text_to_cad,
|
| 497 |
-
inputs=[adv_cad_input],
|
| 498 |
-
outputs=[adv_cad_img_output, adv_cad_3d_output, adv_cad_summary]
|
| 499 |
-
)
|
| 500 |
-
ortho_button.click(
|
| 501 |
-
fn=generate_ortho_views,
|
| 502 |
-
inputs=[ortho_input],
|
| 503 |
-
outputs=[ortho_output, ortho_status]
|
| 504 |
-
)
|
| 505 |
-
cfd_button.click(
|
| 506 |
-
fn=run_cfd_simulation,
|
| 507 |
-
inputs=[cfd_Lx, cfd_Ly, cfd_Nx, cfd_Ny, cfd_vel, cfd_rho, cfd_mu, cfd_ox1, cfd_oy1, cfd_ox2, cfd_oy2, cfd_max_iter, cfd_p_iter],
|
| 508 |
-
outputs=[cfd_output]
|
| 509 |
-
)
|
| 510 |
-
|
| 511 |
-
return demo
|
| 512 |
-
|
| 513 |
-
# --- 4. LAUNCH THE APP ---
|
| 514 |
-
if __name__ == "__main__":
|
| 515 |
-
app = create_gradio_interface()
|
| 516 |
-
app.launch()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|