Engineer-Areeb commited on
Commit
b49bde7
·
verified ·
1 Parent(s): b461f33

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +516 -0
app.py ADDED
@@ -0,0 +1,516 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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()