buildinves commited on
Commit
a8cf587
·
verified ·
1 Parent(s): 3a33624

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +215 -677
app.py CHANGED
@@ -14,17 +14,6 @@ import io
14
  import base64
15
  import tempfile
16
 
17
- # Try to import plan reading libraries
18
- try:
19
- import cv2
20
- import pytesseract
21
- from PIL import Image
22
- from pdf2image import convert_from_path
23
- PLAN_READER_AVAILABLE = True
24
- except ImportError:
25
- PLAN_READER_AVAILABLE = False
26
- print("Warning: Plan reader libraries not available. Install opencv-python, pytesseract, pillow, and pdf2image for full functionality.")
27
-
28
  class AdvancedGridOptimizer:
29
  def __init__(self):
30
  # Standard lot widths and their typical depths
@@ -42,6 +31,17 @@ class AdvancedGridOptimizer:
42
  16.8: {"depths": [30, 32], "type": "Corner-Premium", "squares": "26-32"}
43
  }
44
 
 
 
 
 
 
 
 
 
 
 
 
45
  self.slhc_widths = [8.5, 10.5]
46
  self.standard_widths = [12.5, 14.0]
47
  self.premium_widths = [16.0, 18.0]
@@ -93,8 +93,8 @@ class AdvancedGridOptimizer:
93
  self.current_scheme = 'neon'
94
  self.current_solution = None # Store current AI solution
95
 
96
- def create_enhanced_visualization(self, solution, stage_width, stage_depth=32, title="Premium Grid Layout", show_variance=None):
97
- """Create a clean 2D visualization with corner splays and proper alignment"""
98
  fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(18, 12), gridspec_kw={'height_ratios': [3, 1]},
99
  facecolor='#1a1a1a')
100
 
@@ -134,6 +134,9 @@ class AdvancedGridOptimizer:
134
  splay_size = 3 # 3m corner splay
135
  lot_height = 28 # UNIFORM HEIGHT FOR ALL LOTS
136
 
 
 
 
137
  for i, (width, lot_type) in enumerate(solution):
138
  # Get base color
139
  if width in colors:
@@ -205,6 +208,63 @@ class AdvancedGridOptimizer:
205
  zorder=2)
206
  ax1.add_patch(glow)
207
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
208
  # Add rear alignment line to emphasize equal depth
209
  rear_y = 8 + lot_height
210
  ax1.plot([x_pos, x_pos + width], [rear_y, rear_y],
@@ -253,6 +313,21 @@ class AdvancedGridOptimizer:
253
  bbox=dict(boxstyle="round,pad=0.3", facecolor='#1a1a1a',
254
  edgecolor='cyan', alpha=0.8))
255
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
256
  # Add stage dimensions
257
  arrow_props = dict(arrowstyle='<->', color='white', lw=3)
258
  ax1.annotate('', xy=(0, -6), xytext=(stage_width, -6), arrowprops=arrow_props)
@@ -289,11 +364,21 @@ class AdvancedGridOptimizer:
289
  variance = total_width - stage_width
290
  efficiency = "100%" if abs(variance) < 0.001 else f"{(total_width/stage_width)*100:.1f}%"
291
 
 
 
 
 
 
 
 
 
 
292
  metrics_lines = [
293
  f"📊 TOTAL LOTS: {total_lots}",
294
  f"📐 LAND EFFICIENCY: {efficiency}",
295
  f"🎯 DIVERSITY: {diversity_score:.0%} ({unique_widths} types)",
296
  f"📏 GRID VARIANCE: {variance:+.2f}m",
 
297
  "",
298
  f"SLHC (≤10.5m): {slhc_count} lots",
299
  f"Standard (11-14m): {standard_count} lots",
@@ -303,8 +388,8 @@ class AdvancedGridOptimizer:
303
  f"💰 Revenue: ${total_lots * 0.5:.1f}M - ${total_lots * 1.2:.1f}M"
304
  ]
305
 
306
- col1_text = '\n'.join(metrics_lines[:5])
307
- col2_text = '\n'.join(metrics_lines[5:])
308
 
309
  ax2.text(0.05, 0.5, col1_text, transform=ax2.transAxes,
310
  fontsize=14, verticalalignment='center', fontweight='bold',
@@ -922,337 +1007,12 @@ class AdvancedGridOptimizer:
922
  report += f"- All lots have identical rear alignment for visual consistency\n"
923
  report += f"- Diverse lot mix ensures varied streetscape\n"
924
  report += f"- SLHC lots grouped for efficient garbage collection\n"
 
925
 
926
  report += f"\n---\n*Report generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}*"
927
 
928
  return report
929
 
930
- def process_plan_image(self, image_path, scale=1000, auto_detect_scale=True, confidence=0.75):
931
- """Process a plan image to extract lot information"""
932
- if not PLAN_READER_AVAILABLE:
933
- # Return mock data for demonstration
934
- mock_lots = []
935
- for i in range(8):
936
- frontage = np.random.choice([8.5, 10.5, 12.5, 14.0, 16.0])
937
- mock_lots.append({
938
- 'lot_number': f'L{i+1}',
939
- 'frontage': frontage,
940
- 'depth': 32,
941
- 'area': frontage * 32,
942
- 'type': 'SLHC' if frontage <= 10.5 else 'Standard' if frontage <= 14 else 'Premium'
943
- })
944
-
945
- # Create a simple preview image
946
- fig, ax = plt.subplots(figsize=(10, 8))
947
- ax.text(0.5, 0.5, 'Plan Reader Demo Mode\n(Install required libraries for actual functionality)',
948
- ha='center', va='center', fontsize=16, transform=ax.transAxes)
949
- ax.set_xlim(0, 1)
950
- ax.set_ylim(0, 1)
951
- ax.axis('off')
952
-
953
- # Convert plot to numpy array
954
- fig.canvas.draw()
955
- preview_img = np.frombuffer(fig.canvas.tostring_rgb(), dtype=np.uint8)
956
- preview_img = preview_img.reshape(fig.canvas.get_width_height()[::-1] + (3,))
957
- plt.close()
958
-
959
- summary = """
960
- ### Demo Mode Active
961
- Plan reader libraries not installed. Showing sample data.
962
-
963
- **To enable full functionality, install:**
964
- ```
965
- pip install opencv-python pytesseract pillow pdf2image
966
- ```
967
-
968
- **Sample lots generated for demonstration.**
969
- """
970
- return preview_img, mock_lots, summary
971
-
972
- try:
973
- # Load image
974
- if image_path.endswith('.pdf'):
975
- # Convert PDF to image
976
- with tempfile.TemporaryDirectory() as temp_dir:
977
- images = convert_from_path(image_path, dpi=300)
978
- if images:
979
- # Convert PIL image to numpy array
980
- img = np.array(images[0])
981
- else:
982
- return None, None, "Failed to convert PDF"
983
- else:
984
- img = cv2.imread(image_path)
985
- if img is None:
986
- return None, None, "Failed to load image"
987
-
988
- # Convert to RGB if needed
989
- if len(img.shape) == 2:
990
- img_rgb = cv2.cvtColor(img, cv2.COLOR_GRAY2RGB)
991
- elif img.shape[2] == 4:
992
- img_rgb = cv2.cvtColor(img, cv2.COLOR_BGRA2RGB)
993
- else:
994
- img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
995
-
996
- # Process image for lot detection
997
- gray = cv2.cvtColor(img_rgb, cv2.COLOR_RGB2GRAY)
998
-
999
- # Detect lot boundaries
1000
- lots_detected = self.detect_lot_boundaries(gray, img_rgb, confidence)
1001
-
1002
- # Extract text using OCR
1003
- text_data = self.extract_text_from_plan(gray)
1004
-
1005
- # Match lots with dimensions
1006
- lot_data = self.match_lots_with_dimensions(lots_detected, text_data, scale, auto_detect_scale)
1007
-
1008
- # Create annotated preview
1009
- preview_img = self.create_annotated_preview(img_rgb, lot_data)
1010
-
1011
- # Create summary
1012
- summary = f"""
1013
- ### Analysis Complete!
1014
- - **Lots Detected**: {len(lot_data)}
1015
- - **Scale Used**: 1:{scale if not auto_detect_scale else 'Auto-detected'}
1016
- - **Confidence**: {confidence:.0%}
1017
-
1018
- **Next Steps:**
1019
- 1. Review detected lots in the table below
1020
- 2. Make any necessary corrections
1021
- 3. Click "Send to Optimizer" to analyze the layout
1022
- """
1023
-
1024
- return preview_img, lot_data, summary
1025
-
1026
- except Exception as e:
1027
- return None, None, f"Error processing plan: {str(e)}"
1028
-
1029
- def detect_lot_boundaries(self, gray_img, rgb_img, confidence):
1030
- """Detect lot boundaries in the plan"""
1031
- if not PLAN_READER_AVAILABLE:
1032
- return []
1033
-
1034
- lots = []
1035
-
1036
- # Apply edge detection
1037
- edges = cv2.Canny(gray_img, 50, 150)
1038
-
1039
- # Find contours
1040
- contours, _ = cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
1041
-
1042
- # Filter and process contours
1043
- for contour in contours:
1044
- area = cv2.contourArea(contour)
1045
- if area > 1000: # Minimum area threshold
1046
- # Approximate polygon
1047
- epsilon = 0.02 * cv2.arcLength(contour, True)
1048
- approx = cv2.approxPolyDP(contour, epsilon, True)
1049
-
1050
- # Check if shape is roughly rectangular (4-6 vertices)
1051
- if 4 <= len(approx) <= 6:
1052
- x, y, w, h = cv2.boundingRect(contour)
1053
- aspect_ratio = float(w) / h
1054
-
1055
- # Lots typically have aspect ratios between 0.3 and 3.0
1056
- if 0.3 <= aspect_ratio <= 3.0:
1057
- lots.append({
1058
- 'contour': approx,
1059
- 'bbox': (x, y, w, h),
1060
- 'area': area,
1061
- 'confidence': confidence
1062
- })
1063
-
1064
- return lots
1065
-
1066
- def extract_text_from_plan(self, gray_img):
1067
- """Extract text from plan using OCR"""
1068
- if not PLAN_READER_AVAILABLE:
1069
- return []
1070
-
1071
- # Preprocess for better OCR
1072
- _, thresh = cv2.threshold(gray_img, 150, 255, cv2.THRESH_BINARY)
1073
-
1074
- # Use Tesseract OCR
1075
- try:
1076
- text = pytesseract.image_to_string(thresh)
1077
- data = pytesseract.image_to_data(thresh, output_type=pytesseract.Output.DICT)
1078
-
1079
- # Extract numbers and dimensions
1080
- text_elements = []
1081
- for i in range(len(data['text'])):
1082
- if int(data['conf'][i]) > 0:
1083
- text_val = data['text'][i].strip()
1084
- # Look for lot numbers (L followed by digits) and dimensions (numbers possibly with 'm')
1085
- is_lot_number = text_val.startswith('L') and text_val[1:].isdigit()
1086
- is_dimension = text_val.replace('.', '').replace('m', '').isdigit()
1087
-
1088
- if is_lot_number or is_dimension:
1089
- text_elements.append({
1090
- 'text': text_val,
1091
- 'x': data['left'][i],
1092
- 'y': data['top'][i],
1093
- 'w': data['width'][i],
1094
- 'h': data['height'][i]
1095
- })
1096
-
1097
- return text_elements
1098
- except:
1099
- return []
1100
-
1101
- def match_lots_with_dimensions(self, lots, text_data, scale, auto_detect_scale):
1102
- """Match detected lots with their dimensions and numbers"""
1103
- lot_info = []
1104
-
1105
- # Simple matching based on proximity
1106
- for i, lot in enumerate(lots):
1107
- x, y, w, h = lot['bbox']
1108
- lot_center = (x + w/2, y + h/2)
1109
-
1110
- # Find nearby text
1111
- lot_number = None
1112
- frontage = None
1113
- depth = None
1114
-
1115
- for text in text_data:
1116
- text_center = (text['x'] + text['w']/2, text['y'] + text['h']/2)
1117
- distance = np.sqrt((lot_center[0] - text_center[0])**2 +
1118
- (lot_center[1] - text_center[1])**2)
1119
-
1120
- # If text is close to lot
1121
- if distance < max(w, h) * 0.5:
1122
- text_val = text['text']
1123
-
1124
- # Check if it's a lot number or dimension
1125
- if text_val.startswith('L') and text_val[1:].isdigit():
1126
- lot_number = text_val
1127
- # Check if it's a dimension (number possibly followed by 'm')
1128
- elif text_val.replace('.', '').replace('m', '').isdigit():
1129
- dim_val = float(text_val.replace('m', ''))
1130
- # Assign to frontage or depth based on position
1131
- if abs(text_center[1] - lot_center[1]) < h * 0.3:
1132
- frontage = dim_val
1133
- else:
1134
- depth = dim_val
1135
-
1136
- # If no lot number found, assign sequential
1137
- if not lot_number:
1138
- lot_number = f"L{i+1}"
1139
-
1140
- # If no dimensions found, estimate from pixel measurements
1141
- if not frontage:
1142
- frontage = round(w / scale * 1000, 1) # Convert to meters
1143
- if not depth:
1144
- depth = round(h / scale * 1000, 1) # Convert to meters
1145
-
1146
- # Determine lot type based on frontage
1147
- if frontage <= 10.5:
1148
- lot_type = "SLHC"
1149
- elif frontage <= 14:
1150
- lot_type = "Standard"
1151
- else:
1152
- lot_type = "Premium"
1153
-
1154
- lot_info.append({
1155
- 'lot_number': lot_number,
1156
- 'frontage': frontage,
1157
- 'depth': depth,
1158
- 'area': frontage * depth,
1159
- 'type': lot_type,
1160
- 'bbox': lot['bbox']
1161
- })
1162
-
1163
- # Sort by lot number if possible
1164
- try:
1165
- def get_lot_number(lot_info):
1166
- lot_num = lot_info['lot_number']
1167
- if lot_num.startswith('L'):
1168
- return int(lot_num[1:])
1169
- return 999999 # Put non-standard lot numbers at the end
1170
-
1171
- lot_info.sort(key=get_lot_number)
1172
- except:
1173
- pass
1174
-
1175
- return lot_info
1176
-
1177
- def create_annotated_preview(self, img, lot_data):
1178
- """Create preview image with annotations"""
1179
- if not PLAN_READER_AVAILABLE:
1180
- return img
1181
-
1182
- annotated = img.copy()
1183
-
1184
- # Define colors for different lot types
1185
- colors = {
1186
- 'SLHC': (255, 0, 0), # Red
1187
- 'Standard': (0, 255, 0), # Green
1188
- 'Premium': (0, 0, 255) # Blue
1189
- }
1190
-
1191
- # Draw lot boundaries and labels
1192
- for lot in lot_data:
1193
- if 'bbox' in lot:
1194
- x, y, w, h = lot['bbox']
1195
- color = colors.get(lot['type'], (128, 128, 128))
1196
-
1197
- # Draw rectangle
1198
- cv2.rectangle(annotated, (x, y), (x + w, y + h), color, 2)
1199
-
1200
- # Draw lot number
1201
- label = f"{lot['lot_number']}: {lot['frontage']}m"
1202
- cv2.putText(annotated, label, (x + 5, y + 20),
1203
- cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 2)
1204
-
1205
- return annotated
1206
-
1207
- def lot_data_to_dataframe(self, lot_data):
1208
- """Convert lot data to DataFrame format"""
1209
- if not lot_data:
1210
- return pd.DataFrame(columns=["Lot #", "Frontage (m)", "Depth (m)", "Area (m²)", "Type"])
1211
-
1212
- df_data = []
1213
- for lot in lot_data:
1214
- df_data.append({
1215
- "Lot #": lot['lot_number'],
1216
- "Frontage (m)": lot['frontage'],
1217
- "Depth (m)": lot['depth'],
1218
- "Area (m²)": round(lot['area'], 1),
1219
- "Type": lot['type']
1220
- })
1221
-
1222
- return pd.DataFrame(df_data)
1223
-
1224
- def export_lot_data_to_csv(self, df):
1225
- """Export lot data to CSV format"""
1226
- if df is None or df.empty:
1227
- return None
1228
-
1229
- csv_buffer = io.StringIO()
1230
- df.to_csv(csv_buffer, index=False)
1231
- return csv_buffer.getvalue()
1232
-
1233
- def convert_lot_data_to_stage_format(self, df):
1234
- """Convert lot data to format suitable for optimizer"""
1235
- if df is None or df.empty:
1236
- return None, None
1237
-
1238
- # Group by frontage and count
1239
- frontage_counts = {}
1240
- for _, row in df.iterrows():
1241
- frontage = float(row['Frontage (m)'])
1242
- if frontage in frontage_counts:
1243
- frontage_counts[frontage] += 1
1244
- else:
1245
- frontage_counts[frontage] = 1
1246
-
1247
- # Calculate total width
1248
- total_width = sum(f * c for f, c in frontage_counts.items())
1249
-
1250
- # Find common depth (mode)
1251
- depths = df['Depth (m)'].mode()
1252
- common_depth = depths[0] if len(depths) > 0 else 32
1253
-
1254
- return total_width, common_depth
1255
-
1256
  def darken_color(self, hex_color, factor=0.8):
1257
  """Darken a hex color by a factor"""
1258
  try:
@@ -1271,7 +1031,7 @@ def create_advanced_app():
1271
  stage_depth,
1272
  enable_8_5, enable_10_5, enable_12_5, enable_14, enable_16, enable_18,
1273
  enable_corners, enable_11, enable_13_3, enable_14_8, enable_16_8,
1274
- allow_custom_corners, optimization_strategy, color_scheme
1275
  ):
1276
  # Update color scheme
1277
  optimizer.current_scheme = color_scheme
@@ -1329,11 +1089,12 @@ def create_advanced_app():
1329
  3. Try common stage widths: 84m, 105m, 126m
1330
  """, "", ""
1331
 
1332
- # Create visualizations with variance indicator
1333
  fig_2d = optimizer.create_enhanced_visualization(
1334
  optimized_solution, stage_width, stage_depth,
1335
  "AI-Optimized Diverse Subdivision Layout",
1336
- show_variance=variance
 
1337
  )
1338
 
1339
  # Create results table
@@ -1414,7 +1175,7 @@ def create_advanced_app():
1414
 
1415
  return fig_2d, results_df, summary, report, manual_edit_string
1416
 
1417
- def update_manual_adjustment(manual_widths_text, stage_width, stage_depth, color_scheme):
1418
  """Update visualization based on manual adjustment"""
1419
  optimizer.current_scheme = color_scheme
1420
 
@@ -1438,7 +1199,8 @@ def create_advanced_app():
1438
  fig = optimizer.create_enhanced_visualization(
1439
  solution, stage_width, stage_depth,
1440
  "Manually Adjusted Layout",
1441
- show_variance=variance
 
1442
  )
1443
 
1444
  return fig, feedback
@@ -1483,239 +1245,122 @@ def create_advanced_app():
1483
  ) as demo:
1484
  gr.Markdown("""
1485
  # 🏗️ Advanced AI Grid Cut Optimizer Pro
1486
- ### AI-Powered Subdivision Planning with Manual Fine-Tuning
1487
  """)
1488
 
1489
- with gr.Tabs() as main_tabs:
1490
- with gr.TabItem("🤖 AI Optimization"):
1491
- with gr.Row():
1492
- with gr.Column(scale=1):
1493
- with gr.Group():
1494
- gr.Markdown("### 📐 Stage Dimensions")
1495
- stage_width = gr.Number(
1496
- label="Stage Width (m)",
1497
- value=105.0,
1498
- info="Width along the street"
1499
- )
1500
- stage_depth = gr.Number(
1501
- label="Stage Depth (m)",
1502
- value=32.0,
1503
- info="Depth of lots (perpendicular to street)"
1504
- )
1505
-
1506
- gr.Markdown("### 📏 Lot Width Options")
1507
-
1508
- with gr.Group():
1509
- gr.Markdown("**Standard Widths**")
1510
- with gr.Row():
1511
- enable_8_5 = gr.Checkbox(label="8.5m SLHC", value=True)
1512
- enable_10_5 = gr.Checkbox(label="10.5m SLHC", value=True)
1513
- enable_12_5 = gr.Checkbox(label="12.5m", value=True)
1514
- with gr.Row():
1515
- enable_14 = gr.Checkbox(label="14.0m", value=True)
1516
- enable_16 = gr.Checkbox(label="16.0m", value=True)
1517
- enable_18 = gr.Checkbox(label="18.0m", value=False)
1518
-
1519
- with gr.Group():
1520
- enable_corners = gr.Checkbox(
1521
- label="Enable Corner-Specific Widths",
1522
- value=True,
1523
- info="Adds variety and helps achieve 100%"
1524
- )
1525
- with gr.Row():
1526
- enable_11 = gr.Checkbox(label="11.0m", value=True)
1527
- enable_13_3 = gr.Checkbox(label="13.3m", value=True)
1528
- with gr.Row():
1529
- enable_14_8 = gr.Checkbox(label="14.8m", value=True)
1530
- enable_16_8 = gr.Checkbox(label="16.8m", value=True)
1531
-
1532
- with gr.Column(scale=1):
1533
- gr.Markdown("### ⚙️ Advanced Settings")
1534
-
1535
- allow_custom_corners = gr.Checkbox(
1536
- label="🎯 Allow Flexible Corner Widths",
1537
- value=True,
1538
- info="Enables 13.8m, 13.9m etc. for perfect fits"
1539
- )
1540
-
1541
- optimization_strategy = gr.Radio(
1542
- ["diversity_focus", "balanced"],
1543
- label="Optimization Strategy",
1544
- value="diversity_focus",
1545
- info="Diversity creates more interesting layouts"
1546
- )
1547
-
1548
- color_scheme = gr.Radio(
1549
- ["modern", "professional", "neon"],
1550
- label="🎨 Color Scheme",
1551
- value="neon",
1552
- info="Neon colors work best with dark background"
1553
- )
1554
-
1555
- optimize_btn = gr.Button(
1556
- "🚀 Optimize with AI",
1557
- variant="primary",
1558
- size="lg",
1559
- elem_id="optimize-button"
1560
- )
1561
-
1562
- gr.Markdown("""
1563
- ### 💡 Quick Tips:
1564
- - **Visual Fix**: All lots now align at rear boundary
1565
- - **Corner Lots**: Always wider than internals
1566
- - **Grid Variance**: Shows if layout is perfect (0.0m)
1567
- - **Manual Adjust**: Edit the result below after optimization
1568
- """)
1569
-
1570
- with gr.Row():
1571
- plot_2d = gr.Plot(label="2D Layout with Corner Splays")
1572
 
1573
- # Manual adjustment section
1574
- gr.Markdown("### ✏️ Fine-Tune AI Result")
1575
- with gr.Row():
1576
- with gr.Column(scale=2):
1577
- manual_widths = gr.Textbox(
1578
- label="Manually Adjust Lot Widths",
1579
- placeholder="Widths will appear here after optimization",
1580
- info="Edit the widths (comma-separated) and click 'Update Layout'",
1581
- lines=2
1582
- )
1583
- with gr.Column(scale=1):
1584
- update_btn = gr.Button("🔄 Update Layout", variant="secondary")
1585
- adjustment_feedback = gr.Markdown(
1586
- value="",
1587
- label="Adjustment Feedback"
1588
- )
1589
 
1590
- with gr.Row():
1591
- results_table = gr.DataFrame(label="Lot Distribution Analysis")
 
 
 
 
 
 
 
 
1592
 
1593
- with gr.Row():
1594
- with gr.Column():
1595
- summary_output = gr.Markdown(label="Optimization Summary")
1596
- with gr.Column():
1597
- report_output = gr.Markdown(label="Professional Report")
 
 
 
 
 
 
 
1598
 
1599
- with gr.TabItem("📊 Plan Reader"):
1600
- gr.Markdown("""
1601
- ## 🏢 AI Plan Reader
1602
- ### Upload your subdivision plan to automatically extract lot information
1603
 
1604
- **Workflow:**
1605
- 1. Upload plan 2. Review/edit extracted data → 3. Send to optimizer → 4. Optimize layout
1606
- """)
 
 
1607
 
1608
- with gr.Row():
1609
- with gr.Column(scale=1):
1610
- plan_upload = gr.File(
1611
- label="Upload Subdivision Plan",
1612
- file_types=["image", "pdf"],
1613
- type="filepath"
1614
- )
1615
-
1616
- gr.Markdown("""
1617
- **Supported Formats:**
1618
- - PDF plans
1619
- - PNG/JPG images
1620
- - CAD exports
1621
-
1622
- **Best Results:**
1623
- - High resolution (300+ DPI)
1624
- - Clear lot numbers
1625
- - Visible frontage dimensions
1626
- - North arrow included
1627
- """)
1628
-
1629
- process_plan_btn = gr.Button(
1630
- "🔍 Analyze Plan",
1631
- variant="primary",
1632
- size="lg"
1633
- )
1634
-
1635
- # Analysis options
1636
- with gr.Group():
1637
- gr.Markdown("**Analysis Settings**")
1638
- scale_input = gr.Number(
1639
- label="Scale (1:X)",
1640
- value=1000,
1641
- info="Drawing scale ratio"
1642
- )
1643
-
1644
- auto_detect_scale = gr.Checkbox(
1645
- label="Auto-detect scale from plan",
1646
- value=True
1647
- )
1648
-
1649
- confidence_threshold = gr.Slider(
1650
- label="Detection Confidence",
1651
- minimum=0.5,
1652
- maximum=0.95,
1653
- value=0.75,
1654
- step=0.05,
1655
- info="Higher = more accurate but may miss some lots"
1656
- )
1657
-
1658
- with gr.Column(scale=2):
1659
- # Preview with annotations
1660
- plan_preview = gr.Image(
1661
- label="Analyzed Plan Preview",
1662
- type="numpy"
1663
- )
1664
-
1665
- analysis_status = gr.Markdown(
1666
- value="Upload a plan to begin analysis",
1667
- label="Analysis Status"
1668
- )
1669
 
1670
- # Results section
1671
- gr.Markdown("### 📊 Extracted Lot Data")
 
 
 
 
1672
 
1673
- with gr.Row():
1674
- extracted_data = gr.DataFrame(
1675
- headers=["Lot #", "Frontage (m)", "Depth (m)", "Area (m²)", "Type"],
1676
- label="Detected Lots",
1677
- interactive=True
1678
- )
1679
-
1680
- with gr.Column():
1681
- extraction_summary = gr.Markdown(
1682
- label="Extraction Summary"
1683
- )
1684
-
1685
- export_btn = gr.Button(
1686
- "📥 Export to CSV",
1687
- variant="secondary"
1688
- )
1689
-
1690
- send_to_optimizer_btn = gr.Button(
1691
- "➡️ Send to Optimizer",
1692
- variant="primary"
1693
- )
1694
 
1695
- # Manual correction section
1696
- gr.Markdown("### ✏️ Manual Corrections")
1697
- with gr.Row():
1698
- with gr.Column():
1699
- gr.Markdown("""
1700
- **Quick Edit Tools:**
1701
- - Double-click cells to edit
1702
- - Add missing lots manually
1703
- - Correct misread numbers
1704
- - Adjust frontages
1705
- """)
1706
-
1707
- add_lot_btn = gr.Button("➕ Add Lot", size="sm")
1708
-
1709
- with gr.Column():
1710
- validation_result = gr.Markdown(
1711
- label="Data Validation"
1712
- )
1713
-
1714
- export_output = gr.Textbox(
1715
- label="CSV Export (Copy and save as .csv file)",
1716
- lines=10,
1717
- visible=False
1718
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1719
 
1720
  # Wire up the buttons
1721
  optimize_btn.click(
@@ -1725,124 +1370,17 @@ def create_advanced_app():
1725
  stage_depth,
1726
  enable_8_5, enable_10_5, enable_12_5, enable_14, enable_16, enable_18,
1727
  enable_corners, enable_11, enable_13_3, enable_14_8, enable_16_8,
1728
- allow_custom_corners, optimization_strategy, color_scheme
1729
  ],
1730
  outputs=[plot_2d, results_table, summary_output, report_output, manual_widths]
1731
  )
1732
 
1733
  update_btn.click(
1734
  update_manual_adjustment,
1735
- inputs=[manual_widths, stage_width, stage_depth, color_scheme],
1736
  outputs=[plot_2d, adjustment_feedback]
1737
  )
1738
 
1739
- # Plan reader functions
1740
- def process_uploaded_plan(file_path, scale, auto_detect, confidence):
1741
- if not file_path:
1742
- return None, pd.DataFrame(), "Please upload a plan file"
1743
-
1744
- preview, lot_data, status = optimizer.process_plan_image(
1745
- file_path, scale, auto_detect, confidence
1746
- )
1747
-
1748
- if lot_data:
1749
- df = optimizer.lot_data_to_dataframe(lot_data)
1750
- return preview, df, status
1751
- else:
1752
- return preview, pd.DataFrame(), status
1753
-
1754
- def export_to_csv(df):
1755
- if df is None or df.empty:
1756
- return gr.update(visible=False), "No data to export"
1757
-
1758
- csv_content = optimizer.export_lot_data_to_csv(df)
1759
- return gr.update(value=csv_content, visible=True), "✅ CSV data ready - copy and save as .csv file"
1760
-
1761
- def send_to_optimizer(df):
1762
- if df is None or df.empty:
1763
- return 0, 32, "No data to send"
1764
-
1765
- width, depth = optimizer.convert_lot_data_to_stage_format(df)
1766
- return width, depth, f"✅ Stage dimensions set to {width:.1f}m × {depth:.1f}m\nSwitch to 'AI Optimization' tab to continue"
1767
-
1768
- def validate_lot_data(df):
1769
- if df is None or df.empty:
1770
- return "No data to validate"
1771
-
1772
- # Check for common issues
1773
- issues = []
1774
-
1775
- # Check for missing values
1776
- if df.isnull().any().any():
1777
- issues.append("⚠️ Missing values detected")
1778
-
1779
- # Check for unrealistic dimensions
1780
- if (df['Frontage (m)'] < 6).any():
1781
- issues.append("⚠️ Some lots have frontage < 6m")
1782
- if (df['Frontage (m)'] > 30).any():
1783
- issues.append("⚠️ Some lots have frontage > 30m")
1784
-
1785
- # Check total lots
1786
- total_lots = len(df)
1787
- if total_lots < 5:
1788
- issues.append("ℹ️ Few lots detected - check if all were found")
1789
-
1790
- if not issues:
1791
- return f"✅ Data looks good! {total_lots} lots ready for optimization"
1792
- else:
1793
- return "\n".join(issues)
1794
-
1795
- def add_lot_row(df):
1796
- if df is None or df.empty:
1797
- new_row = pd.DataFrame({
1798
- "Lot #": ["L1"],
1799
- "Frontage (m)": [12.5],
1800
- "Depth (m)": [32.0],
1801
- "Area (m²)": [400.0],
1802
- "Type": ["Standard"]
1803
- })
1804
- return new_row
1805
- else:
1806
- last_lot_num = len(df) + 1
1807
- new_row = pd.DataFrame({
1808
- "Lot #": [f"L{last_lot_num}"],
1809
- "Frontage (m)": [12.5],
1810
- "Depth (m)": [32.0],
1811
- "Area (m²)": [400.0],
1812
- "Type": ["Standard"]
1813
- })
1814
- return pd.concat([df, new_row], ignore_index=True)
1815
-
1816
- process_plan_btn.click(
1817
- process_uploaded_plan,
1818
- inputs=[plan_upload, scale_input, auto_detect_scale, confidence_threshold],
1819
- outputs=[plan_preview, extracted_data, analysis_status]
1820
- )
1821
-
1822
- export_btn.click(
1823
- export_to_csv,
1824
- inputs=[extracted_data],
1825
- outputs=[export_output, extraction_summary]
1826
- )
1827
-
1828
- send_to_optimizer_btn.click(
1829
- send_to_optimizer,
1830
- inputs=[extracted_data],
1831
- outputs=[stage_width, stage_depth, extraction_summary]
1832
- )
1833
-
1834
- extracted_data.change(
1835
- validate_lot_data,
1836
- inputs=[extracted_data],
1837
- outputs=[validation_result]
1838
- )
1839
-
1840
- add_lot_btn.click(
1841
- add_lot_row,
1842
- inputs=[extracted_data],
1843
- outputs=[extracted_data]
1844
- )
1845
-
1846
  return demo
1847
 
1848
  # Create and launch
 
14
  import base64
15
  import tempfile
16
 
 
 
 
 
 
 
 
 
 
 
 
17
  class AdvancedGridOptimizer:
18
  def __init__(self):
19
  # Standard lot widths and their typical depths
 
31
  16.8: {"depths": [30, 32], "type": "Corner-Premium", "squares": "26-32"}
32
  }
33
 
34
+ # Rescode setback requirements
35
+ self.setback_rules = {
36
+ 'front': 6.0, # 6m front setback
37
+ 'rear': 6.0, # 6m rear setback
38
+ 'side': 1.0, # 1m side setback (single story)
39
+ 'side_2story': 1.5, # 1.5m side setback (two story)
40
+ 'corner_secondary': 3.0, # 3m setback on secondary street for corners
41
+ 'garage_front': 5.5, # 5.5m garage setback from front
42
+ 'alfresco_rear': 3.0 # 3m minimum for alfresco area
43
+ }
44
+
45
  self.slhc_widths = [8.5, 10.5]
46
  self.standard_widths = [12.5, 14.0]
47
  self.premium_widths = [16.0, 18.0]
 
93
  self.current_scheme = 'neon'
94
  self.current_solution = None # Store current AI solution
95
 
96
+ def create_enhanced_visualization(self, solution, stage_width, stage_depth=32, title="Premium Grid Layout", show_variance=None, show_setbacks=True):
97
+ """Create a clean 2D visualization with corner splays and buildable boundaries"""
98
  fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(18, 12), gridspec_kw={'height_ratios': [3, 1]},
99
  facecolor='#1a1a1a')
100
 
 
134
  splay_size = 3 # 3m corner splay
135
  lot_height = 28 # UNIFORM HEIGHT FOR ALL LOTS
136
 
137
+ # Track maximum buildable area info
138
+ max_buildable_areas = []
139
+
140
  for i, (width, lot_type) in enumerate(solution):
141
  # Get base color
142
  if width in colors:
 
208
  zorder=2)
209
  ax1.add_patch(glow)
210
 
211
+ # Add buildable boundaries if enabled
212
+ if show_setbacks:
213
+ # Determine if lot likely has 2 stories
214
+ two_story = width > 14 # Premium lots likely 2 story
215
+ side_setback = self.setback_rules['side_2story'] if two_story else self.setback_rules['side']
216
+
217
+ if is_corner:
218
+ # Corner lot buildable area
219
+ if i == 0: # First corner
220
+ buildable_x = x_pos + max(splay_size + self.setback_rules['corner_secondary'], side_setback)
221
+ buildable_y = 8 + self.setback_rules['front']
222
+ buildable_width = width - max(splay_size + self.setback_rules['corner_secondary'], side_setback) - side_setback
223
+ buildable_height = lot_height - self.setback_rules['front'] - self.setback_rules['rear']
224
+ else: # Last corner
225
+ buildable_x = x_pos + side_setback
226
+ buildable_y = 8 + self.setback_rules['front']
227
+ buildable_width = width - side_setback - max(splay_size + self.setback_rules['corner_secondary'], side_setback)
228
+ buildable_height = lot_height - self.setback_rules['front'] - self.setback_rules['rear']
229
+ else:
230
+ # Regular lot buildable area
231
+ buildable_x = x_pos + side_setback
232
+ buildable_y = 8 + self.setback_rules['front']
233
+ buildable_width = width - 2 * side_setback
234
+ buildable_height = lot_height - self.setback_rules['front'] - self.setback_rules['rear']
235
+
236
+ # Draw buildable boundary with dotted lines
237
+ buildable_rect = Rectangle(
238
+ (buildable_x, buildable_y),
239
+ buildable_width,
240
+ buildable_height,
241
+ fill=False,
242
+ edgecolor='white',
243
+ linewidth=1.5,
244
+ linestyle='--',
245
+ alpha=0.7,
246
+ zorder=4
247
+ )
248
+ ax1.add_patch(buildable_rect)
249
+
250
+ # Add small label for max house size
251
+ max_house_area = buildable_width * buildable_height
252
+ max_buildable_areas.append(max_house_area)
253
+
254
+ # Show dimensions on hover-like annotation
255
+ ax1.text(buildable_x + buildable_width/2, buildable_y + buildable_height/2,
256
+ f'{buildable_width:.1f}×{buildable_height:.1f}m\n({max_house_area:.0f}m²)',
257
+ ha='center', va='center', fontsize=8, color='white', alpha=0.5,
258
+ bbox=dict(boxstyle="round,pad=0.2", facecolor='black', alpha=0.5))
259
+
260
+ # Add setback dimension indicators
261
+ # Front setback
262
+ ax1.plot([x_pos + width*0.1, x_pos + width*0.1], [8, buildable_y],
263
+ 'gray', linewidth=0.5, alpha=0.3)
264
+ ax1.text(x_pos + width*0.1, 8 + self.setback_rules['front']/2,
265
+ f"{self.setback_rules['front']}m",
266
+ ha='right', va='center', fontsize=6, color='gray', alpha=0.5)
267
+
268
  # Add rear alignment line to emphasize equal depth
269
  rear_y = 8 + lot_height
270
  ax1.plot([x_pos, x_pos + width], [rear_y, rear_y],
 
313
  bbox=dict(boxstyle="round,pad=0.3", facecolor='#1a1a1a',
314
  edgecolor='cyan', alpha=0.8))
315
 
316
+ # Add setback legend
317
+ if show_setbacks:
318
+ legend_text = (
319
+ "SETBACK REQUIREMENTS:\n"
320
+ f"Front: {self.setback_rules['front']}m | Rear: {self.setback_rules['rear']}m\n"
321
+ f"Side (1 story): {self.setback_rules['side']}m | Side (2 story): {self.setback_rules['side_2story']}m\n"
322
+ f"Corner secondary street: {self.setback_rules['corner_secondary']}m"
323
+ )
324
+ ax1.text(0.02, 0.98, legend_text,
325
+ transform=ax1.transAxes,
326
+ fontsize=10, color='white', alpha=0.7,
327
+ bbox=dict(boxstyle="round,pad=0.5", facecolor='#2a2a2a',
328
+ edgecolor='white', alpha=0.7),
329
+ verticalalignment='top')
330
+
331
  # Add stage dimensions
332
  arrow_props = dict(arrowstyle='<->', color='white', lw=3)
333
  ax1.annotate('', xy=(0, -6), xytext=(stage_width, -6), arrowprops=arrow_props)
 
364
  variance = total_width - stage_width
365
  efficiency = "100%" if abs(variance) < 0.001 else f"{(total_width/stage_width)*100:.1f}%"
366
 
367
+ # Calculate buildable area stats
368
+ if max_buildable_areas:
369
+ avg_buildable = np.mean(max_buildable_areas)
370
+ min_buildable = min(max_buildable_areas)
371
+ max_buildable = max(max_buildable_areas)
372
+ buildable_stats = f"Buildable: {min_buildable:.0f}-{max_buildable:.0f}m² (avg: {avg_buildable:.0f}m²)"
373
+ else:
374
+ buildable_stats = "Buildable area calculation pending"
375
+
376
  metrics_lines = [
377
  f"📊 TOTAL LOTS: {total_lots}",
378
  f"📐 LAND EFFICIENCY: {efficiency}",
379
  f"🎯 DIVERSITY: {diversity_score:.0%} ({unique_widths} types)",
380
  f"📏 GRID VARIANCE: {variance:+.2f}m",
381
+ f"🏠 {buildable_stats}",
382
  "",
383
  f"SLHC (≤10.5m): {slhc_count} lots",
384
  f"Standard (11-14m): {standard_count} lots",
 
388
  f"💰 Revenue: ${total_lots * 0.5:.1f}M - ${total_lots * 1.2:.1f}M"
389
  ]
390
 
391
+ col1_text = '\n'.join(metrics_lines[:6])
392
+ col2_text = '\n'.join(metrics_lines[6:])
393
 
394
  ax2.text(0.05, 0.5, col1_text, transform=ax2.transAxes,
395
  fontsize=14, verticalalignment='center', fontweight='bold',
 
1007
  report += f"- All lots have identical rear alignment for visual consistency\n"
1008
  report += f"- Diverse lot mix ensures varied streetscape\n"
1009
  report += f"- SLHC lots grouped for efficient garbage collection\n"
1010
+ report += f"- Buildable areas shown with rescode-compliant setbacks\n"
1011
 
1012
  report += f"\n---\n*Report generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}*"
1013
 
1014
  return report
1015
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1016
  def darken_color(self, hex_color, factor=0.8):
1017
  """Darken a hex color by a factor"""
1018
  try:
 
1031
  stage_depth,
1032
  enable_8_5, enable_10_5, enable_12_5, enable_14, enable_16, enable_18,
1033
  enable_corners, enable_11, enable_13_3, enable_14_8, enable_16_8,
1034
+ allow_custom_corners, optimization_strategy, color_scheme, show_setbacks
1035
  ):
1036
  # Update color scheme
1037
  optimizer.current_scheme = color_scheme
 
1089
  3. Try common stage widths: 84m, 105m, 126m
1090
  """, "", ""
1091
 
1092
+ # Create visualizations with variance indicator and setbacks
1093
  fig_2d = optimizer.create_enhanced_visualization(
1094
  optimized_solution, stage_width, stage_depth,
1095
  "AI-Optimized Diverse Subdivision Layout",
1096
+ show_variance=variance,
1097
+ show_setbacks=show_setbacks
1098
  )
1099
 
1100
  # Create results table
 
1175
 
1176
  return fig_2d, results_df, summary, report, manual_edit_string
1177
 
1178
+ def update_manual_adjustment(manual_widths_text, stage_width, stage_depth, color_scheme, show_setbacks):
1179
  """Update visualization based on manual adjustment"""
1180
  optimizer.current_scheme = color_scheme
1181
 
 
1199
  fig = optimizer.create_enhanced_visualization(
1200
  solution, stage_width, stage_depth,
1201
  "Manually Adjusted Layout",
1202
+ show_variance=variance,
1203
+ show_setbacks=show_setbacks
1204
  )
1205
 
1206
  return fig, feedback
 
1245
  ) as demo:
1246
  gr.Markdown("""
1247
  # 🏗️ Advanced AI Grid Cut Optimizer Pro
1248
+ ### AI-Powered Subdivision Planning with Buildable Boundaries
1249
  """)
1250
 
1251
+ with gr.Row():
1252
+ with gr.Column(scale=1):
1253
+ with gr.Group():
1254
+ gr.Markdown("### 📐 Stage Dimensions")
1255
+ stage_width = gr.Number(
1256
+ label="Stage Width (m)",
1257
+ value=105.0,
1258
+ info="Width along the street"
1259
+ )
1260
+ stage_depth = gr.Number(
1261
+ label="Stage Depth (m)",
1262
+ value=32.0,
1263
+ info="Depth of lots (perpendicular to street)"
1264
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1265
 
1266
+ gr.Markdown("### 📏 Lot Width Options")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1267
 
1268
+ with gr.Group():
1269
+ gr.Markdown("**Standard Widths**")
1270
+ with gr.Row():
1271
+ enable_8_5 = gr.Checkbox(label="8.5m SLHC", value=True)
1272
+ enable_10_5 = gr.Checkbox(label="10.5m SLHC", value=True)
1273
+ enable_12_5 = gr.Checkbox(label="12.5m", value=True)
1274
+ with gr.Row():
1275
+ enable_14 = gr.Checkbox(label="14.0m", value=True)
1276
+ enable_16 = gr.Checkbox(label="16.0m", value=True)
1277
+ enable_18 = gr.Checkbox(label="18.0m", value=False)
1278
 
1279
+ with gr.Group():
1280
+ enable_corners = gr.Checkbox(
1281
+ label="Enable Corner-Specific Widths",
1282
+ value=True,
1283
+ info="Adds variety and helps achieve 100%"
1284
+ )
1285
+ with gr.Row():
1286
+ enable_11 = gr.Checkbox(label="11.0m", value=True)
1287
+ enable_13_3 = gr.Checkbox(label="13.3m", value=True)
1288
+ with gr.Row():
1289
+ enable_14_8 = gr.Checkbox(label="14.8m", value=True)
1290
+ enable_16_8 = gr.Checkbox(label="16.8m", value=True)
1291
 
1292
+ with gr.Column(scale=1):
1293
+ gr.Markdown("### ⚙️ Advanced Settings")
 
 
1294
 
1295
+ allow_custom_corners = gr.Checkbox(
1296
+ label="🎯 Allow Flexible Corner Widths",
1297
+ value=True,
1298
+ info="Enables 13.8m, 13.9m etc. for perfect fits"
1299
+ )
1300
 
1301
+ show_setbacks = gr.Checkbox(
1302
+ label="🏠 Show Buildable Boundaries",
1303
+ value=True,
1304
+ info="Display maximum house envelope with rescode setbacks"
1305
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1306
 
1307
+ optimization_strategy = gr.Radio(
1308
+ ["diversity_focus", "balanced"],
1309
+ label="Optimization Strategy",
1310
+ value="diversity_focus",
1311
+ info="Diversity creates more interesting layouts"
1312
+ )
1313
 
1314
+ color_scheme = gr.Radio(
1315
+ ["modern", "professional", "neon"],
1316
+ label="🎨 Color Scheme",
1317
+ value="neon",
1318
+ info="Neon colors work best with dark background"
1319
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1320
 
1321
+ optimize_btn = gr.Button(
1322
+ "🚀 Optimize with AI",
1323
+ variant="primary",
1324
+ size="lg",
1325
+ elem_id="optimize-button"
1326
+ )
1327
+
1328
+ gr.Markdown("""
1329
+ ### 💡 Quick Tips:
1330
+ - **Buildable Areas**: Dotted lines show max house size
1331
+ - **Setbacks**: Front/Rear: 6m, Side: 1m (1.5m for 2-story)
1332
+ - **Corner Lots**: Extra 3m setback on secondary street
1333
+ - **Manual Adjust**: Edit the result below after optimization
1334
+ """)
1335
+
1336
+ with gr.Row():
1337
+ plot_2d = gr.Plot(label="2D Layout with Buildable Boundaries")
1338
+
1339
+ # Manual adjustment section
1340
+ gr.Markdown("### ✏️ Fine-Tune AI Result")
1341
+ with gr.Row():
1342
+ with gr.Column(scale=2):
1343
+ manual_widths = gr.Textbox(
1344
+ label="Manually Adjust Lot Widths",
1345
+ placeholder="Widths will appear here after optimization",
1346
+ info="Edit the widths (comma-separated) and click 'Update Layout'",
1347
+ lines=2
1348
+ )
1349
+ with gr.Column(scale=1):
1350
+ update_btn = gr.Button("🔄 Update Layout", variant="secondary")
1351
+ adjustment_feedback = gr.Markdown(
1352
+ value="",
1353
+ label="Adjustment Feedback"
1354
+ )
1355
+
1356
+ with gr.Row():
1357
+ results_table = gr.DataFrame(label="Lot Distribution Analysis")
1358
+
1359
+ with gr.Row():
1360
+ with gr.Column():
1361
+ summary_output = gr.Markdown(label="Optimization Summary")
1362
+ with gr.Column():
1363
+ report_output = gr.Markdown(label="Professional Report")
1364
 
1365
  # Wire up the buttons
1366
  optimize_btn.click(
 
1370
  stage_depth,
1371
  enable_8_5, enable_10_5, enable_12_5, enable_14, enable_16, enable_18,
1372
  enable_corners, enable_11, enable_13_3, enable_14_8, enable_16_8,
1373
+ allow_custom_corners, optimization_strategy, color_scheme, show_setbacks
1374
  ],
1375
  outputs=[plot_2d, results_table, summary_output, report_output, manual_widths]
1376
  )
1377
 
1378
  update_btn.click(
1379
  update_manual_adjustment,
1380
+ inputs=[manual_widths, stage_width, stage_depth, color_scheme, show_setbacks],
1381
  outputs=[plot_2d, adjustment_feedback]
1382
  )
1383
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1384
  return demo
1385
 
1386
  # Create and launch