Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| import pandas as pd | |
| import numpy as np | |
| import matplotlib.pyplot as plt | |
| import matplotlib.patches as patches | |
| from matplotlib.patches import Rectangle, FancyBboxPatch, Circle, Polygon, Wedge, Path, PathPatch | |
| from matplotlib.collections import PatchCollection | |
| from matplotlib import cm | |
| from mpl_toolkits.mplot3d import Axes3D | |
| from mpl_toolkits.mplot3d.art3d import Poly3DCollection | |
| import json | |
| from datetime import datetime | |
| import io | |
| import base64 | |
| import tempfile | |
| class AdvancedGridOptimizer: | |
| def __init__(self): | |
| # Conventional lot widths and their typical depths | |
| self.conventional_lot_specifications = { | |
| 8.5: {"depths": [21, 25, 28], "type": "SLHC", "squares": "11-16"}, | |
| 10.5: {"depths": [21, 25, 28, 32, 35], "type": "SLHC", "squares": "13-21.5"}, | |
| 12.5: {"depths": [21, 25, 28, 30, 32], "type": "Standard", "squares": "16-24"}, | |
| 14.0: {"depths": [21, 25, 28, 30, 32, 34], "type": "Standard", "squares": "17-28"}, | |
| 16.0: {"depths": [28, 30, 32, 34, 36, 40], "type": "Premium", "squares": "24-38"}, | |
| 18.0: {"depths": [32, 34, 36], "type": "Premium", "squares": "32-39"}, | |
| # Traditional corner lots | |
| 11.0: {"depths": [21, 25], "type": "Corner-SLHC", "squares": "13-17"}, | |
| 13.3: {"depths": [25, 28], "type": "Corner-Standard", "squares": "18-22"}, | |
| 14.8: {"depths": [28, 30], "type": "Corner-Standard", "squares": "22-26"}, | |
| 16.8: {"depths": [30, 32], "type": "Corner-Premium", "squares": "26-32"} | |
| } | |
| # Medium Density lot specifications | |
| self.md_rear_loaded_specifications = { | |
| 4.5: {"depths": [19, 25, 28], "type": "MD-Rear Load", "squares": "85.5-126", "build": "2/2/1"}, | |
| 6.0: {"depths": [19, 25, 28], "type": "MD-Rear Load", "squares": "114-168", "build": "3/2/2"}, | |
| 7.5: {"depths": [25, 28], "type": "MD-Rear Load", "squares": "187.5-210", "build": "3-4/2/2"} | |
| } | |
| self.md_front_loaded_specifications = { | |
| 7.0: {"depths": [21], "type": "MD-Front Load", "squares": "147", "build": "3/2/1"}, | |
| 8.0: {"depths": [21], "type": "MD-Front Load", "squares": "168", "build": "3-4/2/2"}, | |
| 8.5: {"depths": [16], "type": "MD-Front Load", "squares": "136", "build": "3/2/1"}, | |
| 10.5: {"depths": [16], "type": "MD-Front Load", "squares": "168", "build": "3-4/2/2"} | |
| } | |
| # Set initial lot specifications to conventional | |
| self.lot_specifications = self.conventional_lot_specifications | |
| self.slhc_widths = [8.5, 10.5] | |
| self.standard_widths = [12.5, 14.0] | |
| self.premium_widths = [16.0, 18.0] | |
| self.corner_specific = [11.0, 13.3, 14.8, 16.8] | |
| # Medium density categories | |
| self.md_rear_widths = [4.5, 6.0, 7.5] | |
| self.md_front_widths = [7.0, 8.0, 8.5, 10.5] | |
| # Define corner_widths as all widths suitable for corners | |
| self.corner_widths = self.corner_specific + [14.0, 16.0, 18.0] | |
| # Enhanced color palette with RPM brand colors | |
| self.color_schemes = { | |
| 'rpm_primary': { | |
| # Conventional colors | |
| 8.5: '#802B2B', # Burgundy for SLHC | |
| 10.5: '#AB3838', # Burgundy 75% | |
| 12.5: '#216767', # Teal | |
| 14.0: '#2E3E2F', # RPM Green (hero color) | |
| 16.0: '#415B6E', # Blue | |
| 18.0: '#FF8E3C', # Yellow | |
| 11.0: '#4F8585', # Teal 75% | |
| 13.3: '#545D51', # RPM Green 75% | |
| 14.8: '#697687', # Blue 75% | |
| 16.8: '#FFCF6D', # Yellow 75% | |
| # Medium Density colors | |
| 4.5: '#6B4C8A', # Purple for MD | |
| 6.0: '#8A6BB3', # Purple 75% | |
| 7.5: '#9F85C7', # Purple 50% | |
| 7.0: '#4A7C7E', # Teal-Blue for MD Front | |
| 8.0: '#5A9A9C' # Teal-Blue 75% | |
| }, | |
| 'rpm_contrast': { | |
| # Conventional colors | |
| 8.5: '#D69C9C', # Burgundy 50% | |
| 10.5: '#E2C1B7', # Burgundy 25% | |
| 12.5: '#95B5B5', # Teal 50% | |
| 14.0: '#80857B', # RPM Green 50% | |
| 16.0: '#99AFC9', # Blue 50% | |
| 18.0: '#FFDF9D', # Yellow 50% | |
| 11.0: '#D6E3E3', # Teal 25% | |
| 13.3: '#B6B8B2', # RPM Green 25% | |
| 14.8: '#CCD7E4', # Blue 25% | |
| 16.8: '#FFEFCE', # Yellow 25% | |
| # Medium Density colors | |
| 4.5: '#B5A6C5', # Purple 50% | |
| 6.0: '#C7BDD6', # Purple 25% | |
| 7.5: '#DDD6E8', # Purple 15% | |
| 7.0: '#8FB8BA', # Teal-Blue 50% | |
| 8.0: '#B3D0D2' # Teal-Blue 25% | |
| }, | |
| 'rpm_monochrome': { | |
| # All widths use grayscale | |
| 8.5: '#2E3E2F', # RPM Green 100% | |
| 10.5: '#545D51', # RPM Green 75% | |
| 12.5: '#80857B', # RPM Green 50% | |
| 14.0: '#B6B8B2', # RPM Green 25% | |
| 16.0: '#636466', # Black 75% | |
| 18.0: '#939598', # Black 50% | |
| 11.0: '#D1D3D4', # Black 25% | |
| 13.3: '#216767', # Teal (accent) | |
| 14.8: '#415B6E', # Blue (accent) | |
| 16.8: '#FF8E3C', # Yellow (accent) | |
| # Medium Density | |
| 4.5: '#4A4B4D', # Dark gray | |
| 6.0: '#6B6C6E', # Medium gray | |
| 7.5: '#8C8D8F', # Light gray | |
| 7.0: '#5C5D5F', # Gray | |
| 8.0: '#7D7E80' # Light gray | |
| } | |
| } | |
| self.current_scheme = 'rpm_primary' | |
| self.current_solution = None # Store current AI solution | |
| self.development_mode = 'conventional' # conventional or medium_density | |
| self.md_load_type = 'front' # front or rear | |
| def set_development_mode(self, mode, load_type=None): | |
| """Set the development mode and update lot specifications""" | |
| self.development_mode = mode | |
| if mode == 'medium_density': | |
| if load_type == 'rear': | |
| self.lot_specifications = self.md_rear_loaded_specifications | |
| self.md_load_type = 'rear' | |
| else: | |
| self.lot_specifications = self.md_front_loaded_specifications | |
| self.md_load_type = 'front' | |
| else: | |
| self.lot_specifications = self.conventional_lot_specifications | |
| self.md_load_type = None | |
| def create_enhanced_visualization(self, solution, stage_width, stage_depth=32, title="Premium Grid Layout", show_variance=None): | |
| """Create a clean 2D visualization with corner splays and optional laneway""" | |
| # Adjust figure size for laneway if needed | |
| fig_height = 14 if self.md_load_type == 'rear' else 12 | |
| fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(18, fig_height), gridspec_kw={'height_ratios': [3, 1]}, | |
| facecolor='#2E3E2F') | |
| # Main visualization | |
| colors = self.color_schemes[self.current_scheme] | |
| x_pos = 0 | |
| for i, (width, lot_type) in enumerate(solution): | |
| # Get base color | |
| if width in colors: | |
| base_color = colors[width] | |
| else: | |
| # Use MD colors if in MD mode | |
| if self.development_mode == 'medium_density': | |
| if width <= 6.0: | |
| base_color = colors.get(4.5, '#6B4C8A') | |
| else: | |
| base_color = colors.get(7.0, '#4A7C7E') | |
| else: | |
| closest_width = min(colors.keys(), key=lambda x: abs(x - width)) | |
| base_color = colors[closest_width] | |
| # Get base color | |
| ax1.set_xlim(-5, stage_width + 5) | |
| # Adjust y-limits for rear laneway | |
| if self.md_load_type == 'rear': | |
| ax1.set_ylim(-10, 60) # Extended for laneway | |
| else: | |
| ax1.set_ylim(-10, 50) | |
| ax1.set_facecolor('#2E3E2F') | |
| # Add title with variance if provided | |
| if show_variance is not None: | |
| variance_color = '#216767' if abs(show_variance) < 0.001 else '#802B2B' | |
| mode_text = "MD " if self.development_mode == 'medium_density' else "" | |
| load_text = f"({self.md_load_type.title()} Loaded) " if self.md_load_type else "" | |
| title_text = f"{mode_text}{load_text}{title}\nGrid Variance: {show_variance:+.1f}m" | |
| ax1.set_title(title_text, fontsize=28, fontweight='bold', pad=25, color='white') | |
| else: | |
| ax1.set_title(title, fontsize=28, fontweight='bold', pad=25, color='white') | |
| # Add subtle gradient background | |
| gradient = np.linspace(0.3, 0.1, 100).reshape(1, -1) | |
| y_max = 60 if self.md_load_type == 'rear' else 50 | |
| ax1.imshow(gradient, extent=[-5, stage_width + 5, -10, y_max], aspect='auto', | |
| cmap='Greys', alpha=0.3, zorder=0) | |
| # Add street with label | |
| street = Rectangle((-5, -8), stage_width + 10, 12, | |
| facecolor='#000000', alpha=0.8, zorder=1, | |
| edgecolor='#636466', linewidth=2) | |
| ax1.add_patch(street) | |
| ax1.text(stage_width/2, -2, 'STREET', ha='center', va='center', | |
| fontsize=20, color='white', fontweight='bold') | |
| # Draw lots with corner splays | |
| splay_size = 3 # 3m corner splay | |
| # Get appropriate depth for current mode | |
| if self.development_mode == 'medium_density': | |
| # Use first available depth for MD lots | |
| lot_height = 28 # Default | |
| for width, _ in solution: | |
| if width in self.lot_specifications: | |
| lot_height = self.lot_specifications[width]['depths'][0] | |
| break | |
| else: | |
| lot_height = 28 # Standard height for conventional | |
| # Add rear laneway if rear loaded MD | |
| if self.md_load_type == 'rear': | |
| laneway_y = 8 + lot_height | |
| laneway = Rectangle((-5, laneway_y), stage_width + 10, 7, | |
| facecolor='#3A3A3A', alpha=0.9, zorder=1, | |
| edgecolor='#FFCF6D', linewidth=2, linestyle='--') | |
| ax1.add_patch(laneway) | |
| ax1.text(stage_width/2, laneway_y + 3.5, 'REAR LANEWAY (7m)', | |
| ha='center', va='center', fontsize=16, color='#FFCF6D', | |
| fontweight='bold', alpha=0.9) | |
| for i, (width, lot_type) in enumerate(solution): | |
| # Get base color | |
| if width in colors: | |
| base_color = colors[width] | |
| else: | |
| # Use MD colors if in MD mode | |
| if self.development_mode == 'medium_density': | |
| if width <= 6.0: | |
| base_color = colors.get(4.5, '#6B4C8A') | |
| else: | |
| base_color = colors.get(7.0, '#4A7C7E') | |
| else: | |
| closest_width = min(colors.keys(), key=lambda x: abs(x - width)) | |
| base_color = colors[closest_width] | |
| # Check position | |
| is_corner = (i == 0 or i == len(solution) - 1) | |
| # Consistent styling for visual alignment | |
| face_color = base_color | |
| edge_color = 'white' | |
| linewidth = 4.0 if is_corner else 3.0 | |
| # Create lot shape with appropriate height | |
| if is_corner: | |
| # Corner lot with splay for both conventional and MD | |
| if i == 0: # First corner | |
| vertices = [ | |
| (x_pos + splay_size, 8), # Start after splay | |
| (x_pos + width, 8), | |
| (x_pos + width, 8 + lot_height), | |
| (x_pos, 8 + lot_height), # Straight rear | |
| (x_pos, 8 + splay_size) # Splay corner | |
| ] | |
| else: # Last corner | |
| vertices = [ | |
| (x_pos, 8), | |
| (x_pos + width - splay_size, 8), | |
| (x_pos + width, 8 + splay_size), # Splay corner | |
| (x_pos + width, 8 + lot_height), | |
| (x_pos, 8 + lot_height) | |
| ] | |
| # Create polygon path | |
| codes = [Path.MOVETO] + [Path.LINETO] * (len(vertices) - 1) + [Path.CLOSEPOLY] | |
| vertices.append(vertices[0]) # Close the path | |
| path = Path(vertices, codes) | |
| lot = PathPatch(path, facecolor=face_color, edgecolor=edge_color, | |
| linewidth=linewidth, zorder=3) | |
| ax1.add_patch(lot) | |
| # Add splay line | |
| if i == 0: | |
| ax1.plot([x_pos, x_pos + splay_size], [8 + splay_size, 8], | |
| 'white', linewidth=2, alpha=0.8) | |
| else: | |
| ax1.plot([x_pos + width - splay_size, x_pos + width], | |
| [8, 8 + splay_size], 'white', linewidth=2, alpha=0.8) | |
| else: | |
| # Regular lot | |
| lot = FancyBboxPatch((x_pos, 8), width, lot_height, | |
| boxstyle="round,pad=0.1", | |
| facecolor=face_color, | |
| edgecolor=edge_color, | |
| linewidth=linewidth, | |
| zorder=3) | |
| ax1.add_patch(lot) | |
| # Add subtle glow | |
| glow = FancyBboxPatch((x_pos - 0.2, 7.8), width + 0.4, lot_height + 0.4, | |
| boxstyle="round,pad=0.15", | |
| facecolor='none', | |
| edgecolor=face_color, | |
| linewidth=1, | |
| alpha=0.5, | |
| zorder=2) | |
| ax1.add_patch(glow) | |
| # Add lot information (positioned inside the lot) | |
| lot_center_y = 8 + lot_height / 2 # Center of the lot | |
| # Just show width in center (like conventional) | |
| ax1.text(x_pos + width/2, lot_center_y, f'{width:.1f}m', | |
| ha='center', va='center', fontsize=16, fontweight='bold', color='white') | |
| # Only show CORNER label for corner lots - positioned lower | |
| if is_corner: | |
| ax1.text(x_pos + width/2, 8 + lot_height/4, "CORNER", | |
| ha='center', va='center', fontsize=12, | |
| bbox=dict(boxstyle="round,pad=0.3", facecolor='#545D51', | |
| edgecolor='white', alpha=0.9, linewidth=1.5), color='white') | |
| # Dimension lines - make more visible | |
| dim_y = 8 + lot_height + 2 # Just above the lot | |
| ax1.plot([x_pos, x_pos + width], [dim_y, dim_y], 'w-', linewidth=1.5, alpha=0.6) | |
| ax1.plot([x_pos, x_pos], [dim_y - 1, dim_y + 1], 'w-', linewidth=1.5, alpha=0.6) | |
| ax1.plot([x_pos + width, x_pos + width], [dim_y - 1, dim_y + 1], 'w-', linewidth=1.5, alpha=0.6) | |
| # Add garage indicators for rear loaded | |
| if self.md_load_type == 'rear': | |
| # Small garage icon at rear | |
| garage_y = 8 + lot_height - 6 | |
| garage = Rectangle((x_pos + width/2 - 1.5, garage_y), 3, 5, | |
| facecolor='#636466', edgecolor='white', | |
| linewidth=1, alpha=0.8, zorder=4) | |
| ax1.add_patch(garage) | |
| x_pos += width | |
| # Add rear alignment line across all lots | |
| rear_y = 8 + lot_height | |
| if self.md_load_type != 'rear': # Don't show if laneway present | |
| ax1.plot([0, stage_width], [rear_y, rear_y], | |
| '#216767', linewidth=2, alpha=0.8, linestyle='-') | |
| ax1.text(stage_width/2, rear_y + 1, 'REAR ALIGNMENT LINE', | |
| ha='center', va='bottom', fontsize=12, color='#216767', alpha=0.8, | |
| bbox=dict(boxstyle="round,pad=0.3", facecolor='#2E3E2F', | |
| edgecolor='#216767', alpha=0.8)) | |
| # Add stage dimensions | |
| arrow_props = dict(arrowstyle='<->', color='white', lw=3) | |
| ax1.annotate('', xy=(0, -6), xytext=(stage_width, -6), arrowprops=arrow_props) | |
| ax1.text(stage_width/2, -7, f'{stage_width}m Γ {stage_depth}m', | |
| ha='center', va='top', fontsize=16, fontweight='bold', color='white') | |
| # Style axes | |
| ax1.set_xticks([]) | |
| ax1.set_yticks([]) | |
| for spine in ax1.spines.values(): | |
| spine.set_visible(False) | |
| # Metrics panel | |
| ax2.axis('off') | |
| ax2.set_facecolor('#2E3E2F') | |
| # Calculate metrics with diversity score | |
| total_lots = len(solution) | |
| unique_widths = len(set(w for w, _ in solution)) | |
| diversity_score = unique_widths / len(set(self.lot_specifications.keys())) | |
| if self.development_mode == 'conventional': | |
| slhc_count = sum(1 for w, _ in solution if w <= 10.5) | |
| standard_count = sum(1 for w, _ in solution if 10.5 < w <= 14) | |
| premium_count = sum(1 for w, _ in solution if w > 14) | |
| # SLHC pairs | |
| slhc_pairs = 0 | |
| for i in range(len(solution) - 1): | |
| if solution[i][0] <= 10.5 and solution[i+1][0] <= 10.5: | |
| slhc_pairs += 1 | |
| # Set MD-specific variables to avoid reference errors | |
| narrow_count = slhc_count | |
| wide_count = premium_count | |
| else: | |
| # MD metrics | |
| narrow_count = sum(1 for w, _ in solution if w <= 6.0) | |
| standard_count = sum(1 for w, _ in solution if 6.0 < w <= 8.0) | |
| wide_count = sum(1 for w, _ in solution if w > 8.0) | |
| slhc_pairs = 0 # Not applicable for MD | |
| slhc_count = narrow_count | |
| premium_count = wide_count | |
| # Calculate actual total width and variance | |
| total_width = sum(w for w, _ in solution) | |
| variance = total_width - stage_width | |
| efficiency = "100%" if abs(variance) < 0.001 else f"{(total_width/stage_width)*100:.1f}%" | |
| # Calculate yield | |
| if self.development_mode == 'medium_density': | |
| # Assume potential for duplex on lots β₯ 7m | |
| potential_dwellings = sum(2 if w >= 7.0 else 1 for w, _ in solution) | |
| yield_text = f"ποΈ Potential Dwellings: {potential_dwellings}" | |
| else: | |
| yield_text = f"π° Revenue: ${total_lots * 0.5:.1f}M - ${total_lots * 1.2:.1f}M" | |
| metrics_lines = [ | |
| f"π TOTAL LOTS: {total_lots}", | |
| f"π LAND EFFICIENCY: {efficiency}", | |
| f"π― DIVERSITY: {diversity_score:.0%} ({unique_widths} types)", | |
| f"π GRID VARIANCE: {variance:+.2f}m", | |
| "", | |
| f"{'Narrow (β€6m)' if self.development_mode == 'medium_density' else 'SLHC (β€10.5m)'}: {narrow_count} lots", | |
| f"{'Standard (6-8m)' if self.development_mode == 'medium_density' else 'Standard (11-14m)'}: {standard_count} lots", | |
| f"{'Wide (>8m)' if self.development_mode == 'medium_density' else 'Premium (>14m)'}: {wide_count} lots", | |
| "", | |
| f"{'π Access: ' + ('Rear Laneway' if self.md_load_type == 'rear' else 'Front Loaded') if self.development_mode == 'medium_density' else f'π SLHC Pairs: {slhc_pairs}'}", | |
| yield_text | |
| ] | |
| col1_text = '\n'.join(metrics_lines[:5]) | |
| col2_text = '\n'.join(metrics_lines[5:]) | |
| ax2.text(0.05, 0.5, col1_text, transform=ax2.transAxes, | |
| fontsize=14, verticalalignment='center', fontweight='bold', | |
| color='white', | |
| bbox=dict(boxstyle="round,pad=0.5", facecolor='#545D51', | |
| edgecolor='#216767', alpha=0.8)) | |
| ax2.text(0.55, 0.5, col2_text, transform=ax2.transAxes, | |
| fontsize=14, verticalalignment='center', fontweight='bold', | |
| color='white', | |
| bbox=dict(boxstyle="round,pad=0.5", facecolor='#545D51', | |
| edgecolor='#216767', alpha=0.8)) | |
| plt.tight_layout() | |
| return fig | |
| def parse_manual_adjustments(self, adjustment_text): | |
| """Parse manual adjustment input into a list of widths""" | |
| try: | |
| if not adjustment_text: | |
| return [] | |
| # Remove any whitespace and split by commas or spaces | |
| adjustment_text = adjustment_text.strip() | |
| # Try parsing as comma-separated values | |
| if ',' in adjustment_text: | |
| widths = [float(w.strip()) for w in adjustment_text.split(',') if w.strip()] | |
| # Try parsing as space-separated values | |
| elif ' ' in adjustment_text: | |
| widths = [float(w.strip()) for w in adjustment_text.split() if w.strip()] | |
| # Try parsing as newline-separated values | |
| elif '\n' in adjustment_text: | |
| widths = [float(w.strip()) for w in adjustment_text.split('\n') if w.strip()] | |
| else: | |
| # Single value | |
| widths = [float(adjustment_text)] | |
| return widths | |
| except Exception as e: | |
| print(f"Error parsing manual adjustments: {e}") | |
| return [] | |
| def validate_manual_solution(self, widths, stage_width): | |
| """Validate and provide feedback on manual solution""" | |
| if not widths: | |
| return None, "No widths provided" | |
| total_width = sum(widths) | |
| variance = total_width - stage_width | |
| # Create solution format | |
| solution = [(w, 'corner' if i in [0, len(widths)-1] else 'standard') | |
| for i, w in enumerate(widths)] | |
| # Provide feedback | |
| if abs(variance) < 0.001: | |
| feedback = "β Perfect fit! Grid is exactly aligned." | |
| elif variance > 0: | |
| feedback = f"β οΈ Grid is {variance:.2f}m too wide. Remove {variance:.2f}m total width." | |
| else: | |
| feedback = f"β οΈ Grid is {-variance:.2f}m too narrow. Add {-variance:.2f}m total width." | |
| # Add suggestions if not perfect | |
| min_width = 4.5 if self.development_mode == 'medium_density' else 8.5 | |
| if abs(variance) > 0.001: | |
| if variance > 0: | |
| # Suggest which lots could be reduced | |
| suggestions = [] | |
| for i, w in enumerate(widths): | |
| if w - variance >= min_width: # Minimum viable width | |
| suggestions.append(f"L{i+1}: reduce from {w:.1f}m to {w-variance:.1f}m") | |
| if suggestions: | |
| feedback += f"\n\nSuggestions:\n" + "\n".join(suggestions[:3]) | |
| else: | |
| # Suggest which lots could be increased | |
| suggestions = [] | |
| add_per_lot = -variance / len(widths) | |
| feedback += f"\n\nSuggestion: Add {add_per_lot:.2f}m to each lot" | |
| return solution, feedback | |
| def solution_to_string(self, solution): | |
| """Convert solution to string format for manual editing""" | |
| if not solution: | |
| return "" | |
| return ", ".join([f"{w:.1f}" for w, _ in solution]) | |
| def find_optimal_custom_corners(self, stage_width, internal_widths, base_corner_width, tolerance=0.5): | |
| """Find optimal corner widths that can vary slightly from base width""" | |
| best_solution = None | |
| best_fitness = -float('inf') | |
| # Ensure corners are at least as wide as smallest internal lot | |
| if self.development_mode == 'medium_density': | |
| min_internal = min(internal_widths) if internal_widths else 4.5 | |
| else: | |
| min_internal = min(internal_widths) if internal_widths else 8.5 | |
| min_corner_width = max(base_corner_width - tolerance, min_internal) | |
| # Try variations of corner widths within tolerance | |
| variations = np.arange(min_corner_width, | |
| base_corner_width + tolerance + 0.1, | |
| 0.1) | |
| for corner1 in variations: | |
| for corner2 in variations: | |
| # Calculate internal space | |
| internal_width = stage_width - corner1 - corner2 | |
| if internal_width <= 0: | |
| continue | |
| # Try to fill internal space exactly | |
| internal_solution = self.find_exact_solution_with_diversity(internal_width, internal_widths) | |
| if internal_solution: | |
| # Verify no internal lot is wider than corners | |
| max_internal = max(internal_solution) if internal_solution else 0 | |
| if max_internal > min(corner1, corner2): | |
| continue | |
| # Build complete solution | |
| solution = [(round(corner1, 1), 'corner')] | |
| solution.extend([(w, 'standard') for w in internal_solution]) | |
| solution.append((round(corner2, 1), 'corner')) | |
| # Evaluate (prefer balanced corners and diversity) | |
| fitness = self.evaluate_solution_with_diversity(solution, stage_width) | |
| if fitness > best_fitness: | |
| best_fitness = fitness | |
| best_solution = solution | |
| return best_solution | |
| def optimize_with_flexible_corners(self, stage_width, enabled_widths, allow_custom_corners=True): | |
| """Enhanced optimization allowing flexible corner sizes with diversity""" | |
| # Separate widths by type | |
| standard_internal = [w for w in enabled_widths if w not in self.corner_specific] | |
| best_solution = None | |
| best_fitness = -float('inf') | |
| # Strategy 1: Try exact widths first with diversity | |
| solution = self.optimize_with_corners_diverse(stage_width, enabled_widths, None) | |
| if solution: | |
| fitness = self.evaluate_solution_with_diversity(solution, stage_width) | |
| if fitness > best_fitness: | |
| best_fitness = fitness | |
| best_solution = solution | |
| # Strategy 2: Try flexible corners if enabled | |
| if allow_custom_corners and standard_internal: | |
| # Use appropriate corner bases for each mode | |
| if self.development_mode == 'medium_density': | |
| # For MD, use the largest available widths as corner bases | |
| corner_bases = sorted(enabled_widths, reverse=True)[:4] | |
| else: | |
| # For conventional, use traditional corner widths | |
| corner_bases = [11.0, 13.3, 14.8, 16.8, 14.0, 16.0] | |
| for base_width in corner_bases: | |
| if any(abs(w - base_width) < 2 for w in enabled_widths): | |
| custom_solution = self.find_optimal_custom_corners( | |
| stage_width, standard_internal, base_width, tolerance=0.5 | |
| ) | |
| if custom_solution: | |
| fitness = self.evaluate_solution_with_diversity(custom_solution, stage_width) | |
| if fitness > best_fitness: | |
| best_fitness = fitness | |
| best_solution = custom_solution | |
| return best_solution | |
| def optimize_with_corners_diverse(self, stage_width, enabled_widths, manual_allocation=None): | |
| """Find lot arrangement with emphasis on diversity and proper corner sizing""" | |
| # Separate widths by size | |
| all_widths = sorted(enabled_widths) | |
| min_internal_width = min(all_widths) if all_widths else 4.5 | |
| # Corner lots must be at least as wide as smallest internal lot | |
| corner_options = [w for w in enabled_widths if w >= max(11.0 if self.development_mode == 'conventional' else min_internal_width, min_internal_width)] | |
| best_solution = None | |
| best_fitness = -float('inf') | |
| # Try different corner combinations | |
| for corner1 in corner_options: | |
| for corner2 in corner_options: | |
| if abs(corner1 - corner2) > 3.0: # Skip very unbalanced | |
| continue | |
| # Calculate internal space | |
| internal_width = stage_width - corner1 - corner2 | |
| if internal_width <= 0: | |
| continue | |
| # Find diverse internal solutions | |
| internal_solutions = self.find_diverse_combinations( | |
| internal_width, all_widths, max_solutions=20 | |
| ) | |
| for internal_widths in internal_solutions: | |
| # Verify no internal lot is wider than corners | |
| max_internal = max(internal_widths) if internal_widths else 0 | |
| if max_internal > min(corner1, corner2): | |
| continue # Skip if internal lots are wider than corners | |
| # Build complete solution | |
| solution = [(corner1, 'corner')] | |
| solution.extend([(w, 'standard') for w in internal_widths]) | |
| solution.append((corner2, 'corner')) | |
| # Optimize arrangement | |
| optimized = self.optimize_lot_grouping(solution) | |
| fitness = self.evaluate_solution_with_diversity(optimized, stage_width) | |
| if fitness > best_fitness: | |
| best_fitness = fitness | |
| best_solution = optimized | |
| # If no good solution, try without strict corner rules but maintain size hierarchy | |
| if not best_solution: | |
| all_solutions = [] | |
| self.find_all_combinations_recursive(stage_width, sorted(enabled_widths), | |
| [], all_solutions, 20) | |
| for widths in all_solutions[:50]: | |
| # Ensure corners are among the largest lots | |
| sorted_widths = sorted(widths) | |
| if len(sorted_widths) >= 2: | |
| # Put two largest widths at corners | |
| solution = [(sorted_widths[-1], 'corner')] # Largest | |
| solution.extend([(w, 'standard') for w in sorted_widths[:-2]]) | |
| solution.append((sorted_widths[-2], 'corner')) # Second largest | |
| else: | |
| solution = [(w, 'standard') for w in widths] | |
| optimized = self.optimize_lot_grouping(solution) | |
| fitness = self.evaluate_solution_with_diversity(optimized, stage_width) | |
| if fitness > best_fitness: | |
| best_fitness = fitness | |
| best_solution = optimized | |
| return best_solution | |
| def optimize_lot_grouping(self, lots): | |
| """Optimize lot arrangement based on development mode""" | |
| if self.development_mode == 'medium_density': | |
| return self.optimize_md_grouping(lots) | |
| else: | |
| return self.optimize_slhc_grouping(lots) | |
| def optimize_md_grouping(self, lots): | |
| """Optimize lot arrangement for medium density""" | |
| if not lots or len(lots) <= 1: | |
| return lots | |
| # Separate lots by width | |
| narrow_lots = [] # 4.5-6m | |
| medium_lots = [] # 7-8m | |
| wide_lots = [] # >8m | |
| for width, lot_type in lots: | |
| if width <= 6.0: | |
| narrow_lots.append((width, lot_type)) | |
| elif width <= 8.0: | |
| medium_lots.append((width, lot_type)) | |
| else: | |
| wide_lots.append((width, lot_type)) | |
| # Build optimized layout | |
| optimized = [] | |
| # For rear loaded, group similar widths for efficient laneway access | |
| if self.md_load_type == 'rear': | |
| # Group narrow lots together | |
| optimized.extend(narrow_lots) | |
| optimized.extend(medium_lots) | |
| optimized.extend(wide_lots) | |
| else: | |
| # For front loaded, alternate sizes for variety | |
| while narrow_lots or medium_lots or wide_lots: | |
| if wide_lots: | |
| optimized.append(wide_lots.pop(0)) | |
| if narrow_lots: | |
| optimized.append(narrow_lots.pop(0)) | |
| if medium_lots: | |
| optimized.append(medium_lots.pop(0)) | |
| return optimized | |
| def find_diverse_combinations(self, target_width, available_widths, max_solutions=20): | |
| """Find combinations that maximize diversity""" | |
| all_solutions = [] | |
| self.find_all_combinations_recursive(target_width, available_widths, | |
| [], all_solutions, 20) | |
| # Sort by diversity (number of unique widths) | |
| diverse_solutions = [] | |
| for sol in all_solutions: | |
| unique_count = len(set(sol)) | |
| diverse_solutions.append((unique_count, sol)) | |
| # Sort by diversity, then by total lots | |
| diverse_solutions.sort(key=lambda x: (x[0], len(x[1])), reverse=True) | |
| # Return the most diverse solutions | |
| return [sol[1] for sol in diverse_solutions[:max_solutions]] | |
| def find_exact_solution_with_diversity(self, target_width, enabled_widths, max_depth=20): | |
| """Find exact solution prioritizing diversity""" | |
| # Try to use multiple different widths | |
| solutions = [] | |
| # Dynamic programming with diversity tracking | |
| dp = {} | |
| dp[0] = ([], set()) # (solution, unique_widths) | |
| for current_target in range(1, int(target_width) + 1): | |
| best_diversity = -1 | |
| best_solution = None | |
| for width in enabled_widths: | |
| if width <= current_target and (current_target - width) in dp: | |
| prev_solution, prev_unique = dp[current_target - width] | |
| if len(prev_solution) < max_depth: | |
| new_solution = prev_solution + [width] | |
| new_unique = prev_unique.copy() | |
| new_unique.add(width) | |
| diversity = len(new_unique) | |
| if diversity > best_diversity: | |
| best_diversity = diversity | |
| best_solution = (new_solution, new_unique) | |
| if best_solution: | |
| dp[current_target] = best_solution | |
| if target_width in dp: | |
| return dp[target_width][0] | |
| # Fallback to regular solution | |
| return self.find_exact_solution(target_width, enabled_widths, max_depth) | |
| def find_exact_solution(self, target_width, enabled_widths, max_depth=20): | |
| """Find exact combination that sums to target_width""" | |
| # Quick check for simple solutions | |
| for width in enabled_widths: | |
| if abs(target_width % width) < 0.001: | |
| count = int(target_width / width) | |
| if count <= max_depth: | |
| return [width] * count | |
| # Dynamic programming solution | |
| dp = {} | |
| dp[0] = [] | |
| for current_target in range(1, int(target_width) + 1): | |
| for width in enabled_widths: | |
| if width <= current_target and (current_target - width) in dp: | |
| prev_solution = dp[current_target - width] | |
| if len(prev_solution) < max_depth: | |
| dp[current_target] = prev_solution + [width] | |
| if target_width in dp: | |
| return dp[target_width] | |
| # Try exhaustive search | |
| all_solutions = [] | |
| self.find_all_combinations_recursive(target_width, sorted(enabled_widths), | |
| [], all_solutions, max_depth) | |
| if all_solutions: | |
| # Return shortest solution | |
| return min(all_solutions, key=len) | |
| return None | |
| def find_all_combinations_recursive(self, remaining, widths, current, all_solutions, max_depth): | |
| """Recursively find all exact combinations""" | |
| if abs(remaining) < 0.001: | |
| all_solutions.append(current[:]) | |
| return | |
| if remaining < 0 or len(current) >= max_depth or len(all_solutions) >= 100: | |
| return | |
| for i, width in enumerate(widths): | |
| if width <= remaining + 0.001: | |
| current.append(width) | |
| self.find_all_combinations_recursive(remaining - width, widths[i:], | |
| current, all_solutions, max_depth) | |
| current.pop() | |
| def optimize_slhc_grouping(self, lots): | |
| """Optimize lot arrangement with sophisticated rules for conventional""" | |
| if not lots or len(lots) <= 1: | |
| return lots | |
| # Separate lots by type | |
| corner_specific = [] | |
| slhc_lots = [] | |
| standard_lots = [] | |
| custom_lots = [] | |
| for width, lot_type in lots: | |
| if width in self.corner_specific: | |
| corner_specific.append((width, lot_type)) | |
| elif width <= 10.5: | |
| slhc_lots.append((width, lot_type)) | |
| elif width in self.standard_widths + self.premium_widths: | |
| standard_lots.append((width, lot_type)) | |
| else: | |
| # Custom width | |
| if width > 10.8 and width < 17: | |
| custom_lots.append((width, lot_type)) | |
| else: | |
| standard_lots.append((width, lot_type)) | |
| # Further separate SLHC by width | |
| slhc_8_5 = [(w, t) for w, t in slhc_lots if abs(w - 8.5) < 0.1] | |
| slhc_10_5 = [(w, t) for w, t in slhc_lots if abs(w - 10.5) < 0.1] | |
| # Determine corner placement | |
| corner_solution = self._determine_best_corners(corner_specific + custom_lots, standard_lots) | |
| # Build optimized layout | |
| optimized = [] | |
| # Place first corner | |
| if corner_solution and corner_solution[0]: | |
| optimized.append((corner_solution[0][0], 'corner')) | |
| # Remove from appropriate list | |
| for lst in [corner_specific, custom_lots, standard_lots]: | |
| if corner_solution[0] in lst: | |
| lst.remove(corner_solution[0]) | |
| break | |
| # Add SLHC groups optimally | |
| optimized.extend(self._arrange_slhc_optimally(slhc_8_5, slhc_10_5)) | |
| # Add remaining lots | |
| optimized.extend(standard_lots) | |
| optimized.extend(custom_lots) | |
| optimized.extend(corner_specific) | |
| # Place second corner | |
| if corner_solution and len(corner_solution) > 1 and corner_solution[1]: | |
| optimized.append((corner_solution[1][0], 'corner')) | |
| return optimized | |
| def _determine_best_corners(self, corner_suitable, standard_lots): | |
| """Determine the best corner placement strategy""" | |
| all_suitable = corner_suitable + [(w, t) for w, t in standard_lots if w >= 12.5] | |
| if len(all_suitable) < 2: | |
| return None | |
| # Find best matching pair | |
| best_pair = None | |
| min_diff = float('inf') | |
| for i in range(len(all_suitable)): | |
| for j in range(i + 1, len(all_suitable)): | |
| diff = abs(all_suitable[i][0] - all_suitable[j][0]) | |
| if diff < min_diff: | |
| min_diff = diff | |
| best_pair = (all_suitable[i], all_suitable[j]) | |
| return best_pair | |
| def _arrange_slhc_optimally(self, slhc_8_5, slhc_10_5): | |
| """Arrange SLHC lots for optimal garage adjacency""" | |
| arranged = [] | |
| # Pair matching widths first | |
| while len(slhc_8_5) >= 2: | |
| arranged.extend(slhc_8_5[:2]) | |
| slhc_8_5 = slhc_8_5[2:] | |
| while len(slhc_10_5) >= 2: | |
| arranged.extend(slhc_10_5[:2]) | |
| slhc_10_5 = slhc_10_5[2:] | |
| # Mixed pairing | |
| while slhc_8_5 and slhc_10_5: | |
| arranged.append(slhc_8_5[0]) | |
| arranged.append(slhc_10_5[0]) | |
| slhc_8_5 = slhc_8_5[1:] | |
| slhc_10_5 = slhc_10_5[1:] | |
| # Add remaining | |
| arranged.extend(slhc_8_5) | |
| arranged.extend(slhc_10_5) | |
| return arranged | |
| def evaluate_solution_with_diversity(self, solution, stage_width): | |
| """Evaluate fitness with strong emphasis on diversity""" | |
| if not solution: | |
| return -float('inf') | |
| total_width = sum(w for w, _ in solution) | |
| waste = stage_width - total_width | |
| # Must have 100% usage | |
| if abs(waste) > 0.001: | |
| return -float('inf') | |
| lot_count = len(solution) | |
| # Calculate diversity metrics | |
| width_counts = {} | |
| for w, _ in solution: | |
| width_counts[w] = width_counts.get(w, 0) + 1 | |
| unique_widths = len(width_counts) | |
| max_repetition = max(width_counts.values()) | |
| diversity_ratio = unique_widths / lot_count if lot_count > 0 else 0 | |
| # Base fitness | |
| fitness = lot_count * 1000 | |
| # STRONG diversity bonus | |
| fitness += unique_widths * 2000 # Big bonus for each unique width | |
| fitness -= max_repetition * 500 # Penalty for too many of same width | |
| fitness += diversity_ratio * 3000 # Bonus for good diversity ratio | |
| # Corner evaluation - apply to both conventional and MD | |
| if len(solution) >= 2: | |
| first_width = solution[0][0] | |
| last_width = solution[-1][0] | |
| # Get max internal width | |
| internal_widths = [w for w, t in solution[1:-1]] | |
| max_internal = max(internal_widths) if internal_widths else 0 | |
| # Penalty if corners are not wider than internals | |
| if first_width <= max_internal: | |
| fitness -= 2000 | |
| if last_width <= max_internal: | |
| fitness -= 2000 | |
| # Bonus for good corners (wider than internals) | |
| if first_width > max_internal: | |
| fitness += 1000 | |
| if last_width > max_internal: | |
| fitness += 1000 | |
| # Balance bonus | |
| corner_diff = abs(first_width - last_width) | |
| if corner_diff < 0.1: | |
| fitness += 1500 # Perfect match | |
| elif corner_diff <= 1.0: | |
| fitness += 1000 # Very good | |
| elif corner_diff <= 2.0: | |
| fitness += 500 # Good | |
| else: | |
| fitness -= 500 # Poor balance | |
| # Mode-specific bonuses | |
| if self.development_mode == 'conventional': | |
| # SLHC grouping bonus | |
| for i in range(len(solution) - 1): | |
| if solution[i][0] <= 10.5 and solution[i+1][0] <= 10.5: | |
| fitness += 300 # Adjacent SLHC bonus | |
| # Penalize corner-specific widths used internally | |
| for i in range(1, len(solution) - 1): | |
| if solution[i][0] in self.corner_specific: | |
| fitness -= 200 | |
| else: | |
| # MD-specific bonuses | |
| if self.md_load_type == 'rear': | |
| # Bonus for grouping similar widths (efficient laneway access) | |
| for i in range(len(solution) - 1): | |
| if abs(solution[i][0] - solution[i+1][0]) < 1.5: | |
| fitness += 200 | |
| # Bonus for potential duplex lots (β₯7m) | |
| duplex_count = sum(1 for w, _ in solution if w >= 7.0) | |
| fitness += duplex_count * 500 | |
| return fitness | |
| def generate_report(self, solution, stage_width, stage_depth, manual_allocation=None): | |
| """Generate a professional report""" | |
| if not solution: | |
| return None | |
| # Check for custom widths | |
| custom_widths = [] | |
| for width, _ in solution: | |
| if width not in self.lot_specifications: | |
| custom_widths.append(f"{width:.1f}m") | |
| # Calculate diversity | |
| unique_widths = len(set(w for w, _ in solution)) | |
| width_counts = {} | |
| for w, _ in solution: | |
| width_counts[w] = width_counts.get(w, 0) + 1 | |
| # Calculate variance | |
| total_width = sum(w for w, _ in solution) | |
| variance = total_width - stage_width | |
| # Mode-specific title | |
| mode_text = "MEDIUM DENSITY " if self.development_mode == 'medium_density' else "" | |
| load_text = f"({self.md_load_type.upper()} LOADED) " if self.md_load_type else "" | |
| report = f""" | |
| # {mode_text}{load_text}SUBDIVISION OPTIMIZATION REPORT | |
| ## Project Analysis for {stage_width}m Γ {stage_depth}m Stage | |
| ### EXECUTIVE SUMMARY | |
| - **Development Type**: {self.development_mode.replace('_', ' ').title()} | |
| - **Total Lots**: {len(solution)} | |
| - **Unique Lot Types**: {unique_widths} | |
| - **Land Efficiency**: {"100%" if abs(variance) < 0.001 else f"{(total_width/stage_width)*100:.1f}%"} | |
| - **Grid Variance**: {variance:+.2f}m | |
| - **Stage Dimensions**: {stage_width}m Γ {stage_depth}m | |
| - **Total Area**: {stage_width * stage_depth}mΒ² | |
| {f"- **Custom Widths Used**: {', '.join(custom_widths)}" if custom_widths else ""} | |
| """ | |
| # Add MD-specific info | |
| if self.development_mode == 'medium_density': | |
| potential_dwellings = sum(2 if w >= 7.0 else 1 for w, _ in solution) | |
| density = potential_dwellings / (stage_width * stage_depth / 10000) # per hectare | |
| report += f"- **Potential Dwellings**: {potential_dwellings} ({density:.0f} dwellings/ha)\n" | |
| report += f"- **Access Type**: {'Rear Laneway (7m)' if self.md_load_type == 'rear' else 'Front Loaded'}\n" | |
| report += f"\n### LOT DIVERSITY ANALYSIS\n" | |
| # Sort by count to show distribution | |
| sorted_widths = sorted(width_counts.items(), key=lambda x: x[1], reverse=True) | |
| for width, count in sorted_widths: | |
| percentage = (count / len(solution)) * 100 | |
| if width in self.lot_specifications: | |
| spec = self.lot_specifications[width] | |
| build_info = f" [{spec.get('build', 'N/A')}]" if 'build' in spec else "" | |
| report += f"- **{width:.1f}m** Γ {count} ({percentage:.1f}%): {spec['type']}{build_info}\n" | |
| else: | |
| report += f"- **{width:.1f}m** Γ {count} ({percentage:.1f}%): Custom Width\n" | |
| # Corner analysis | |
| if len(solution) >= 2: | |
| report += f"\n### CORNER ANALYSIS\n" | |
| report += f"- **Front Corner**: {solution[0][0]:.1f}m with 3m Γ 3m splay\n" | |
| report += f"- **Rear Corner**: {solution[-1][0]:.1f}m with 3m Γ 3m splay\n" | |
| report += f"- **Balance**: {abs(solution[0][0] - solution[-1][0]):.1f}m difference\n" | |
| report += f"\n### DESIGN FEATURES\n" | |
| report += f"- Corner splays provide safe sight lines at intersections\n" | |
| if self.development_mode == 'medium_density': | |
| if self.md_load_type == 'rear': | |
| report += f"- 7m rear laneway provides vehicle access and services\n" | |
| report += f"- Garages positioned at rear for better street presentation\n" | |
| else: | |
| report += f"- Front loaded design with integrated garages\n" | |
| report += f"- Compact lots maximize dwelling yield\n" | |
| report += f"- Potential for duplex/triplex on wider lots (β₯7m)\n" | |
| else: | |
| report += f"- All lots have identical rear alignment for visual consistency\n" | |
| report += f"- Diverse lot mix ensures varied streetscape\n" | |
| report += f"- SLHC lots grouped for efficient garbage collection\n" | |
| report += f"\n---\n*Report generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}*" | |
| return report | |
| def darken_color(self, hex_color, factor=0.8): | |
| """Darken a hex color by a factor""" | |
| try: | |
| hex_color = hex_color.lstrip('#') | |
| rgb = tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4)) | |
| darker_rgb = tuple(int(c * factor) for c in rgb) | |
| return '#' + ''.join(f'{c:02x}' for c in darker_rgb) | |
| except: | |
| return hex_color | |
| def create_advanced_app(): | |
| optimizer = AdvancedGridOptimizer() | |
| def update_available_widths(development_mode, md_load_type): | |
| """Update the available width options based on development mode""" | |
| if development_mode == "Medium Density": | |
| if md_load_type == "Rear Loaded": | |
| # Rear loaded MD widths | |
| return gr.update(visible=False), gr.update(visible=True), gr.update(visible=False) | |
| else: | |
| # Front loaded MD widths | |
| return gr.update(visible=False), gr.update(visible=False), gr.update(visible=True) | |
| else: | |
| # Conventional widths | |
| return gr.update(visible=True), gr.update(visible=False), gr.update(visible=False) | |
| def optimize_grid( | |
| stage_width, | |
| stage_depth, | |
| development_mode, | |
| md_load_type, | |
| # Conventional widths | |
| enable_8_5, enable_10_5, enable_12_5, enable_14, enable_16, enable_18, | |
| enable_corners, enable_11, enable_13_3, enable_14_8, enable_16_8, | |
| # MD rear widths | |
| enable_4_5, enable_6_0, enable_7_5, | |
| # MD front widths | |
| enable_7_0, enable_8_0, enable_md_8_5, enable_md_10_5, | |
| allow_custom_corners, color_scheme | |
| ): | |
| # Update optimizer mode | |
| if development_mode == "Medium Density": | |
| optimizer.set_development_mode('medium_density', 'rear' if md_load_type == "Rear Loaded" else 'front') | |
| else: | |
| optimizer.set_development_mode('conventional') | |
| # Update color scheme | |
| optimizer.current_scheme = color_scheme | |
| # Collect enabled widths based on mode | |
| enabled_widths = [] | |
| if development_mode == "Conventional Land": | |
| if enable_8_5: enabled_widths.append(8.5) | |
| if enable_10_5: enabled_widths.append(10.5) | |
| if enable_12_5: enabled_widths.append(12.5) | |
| if enable_14: enabled_widths.append(14.0) | |
| if enable_16: enabled_widths.append(16.0) | |
| if enable_18: enabled_widths.append(18.0) | |
| if enable_corners: | |
| if enable_11: enabled_widths.append(11.0) | |
| if enable_13_3: enabled_widths.append(13.3) | |
| if enable_14_8: enabled_widths.append(14.8) | |
| if enable_16_8: enabled_widths.append(16.8) | |
| else: | |
| if md_load_type == "Rear Loaded": | |
| if enable_4_5: enabled_widths.append(4.5) | |
| if enable_6_0: enabled_widths.append(6.0) | |
| if enable_7_5: enabled_widths.append(7.5) | |
| else: | |
| if enable_7_0: enabled_widths.append(7.0) | |
| if enable_8_0: enabled_widths.append(8.0) | |
| if enable_md_8_5: enabled_widths.append(8.5) | |
| if enable_md_10_5: enabled_widths.append(10.5) | |
| if not enabled_widths: | |
| return None, None, pd.DataFrame(), "Please select at least one lot width!", "", "" | |
| # Run optimization with diversity focus | |
| optimized_solution = optimizer.optimize_with_flexible_corners( | |
| stage_width, enabled_widths, allow_custom_corners | |
| ) | |
| # Store current solution for manual adjustment | |
| optimizer.current_solution = optimized_solution | |
| # Calculate variance for display | |
| if optimized_solution: | |
| total_width = sum(w for w, _ in optimized_solution) | |
| variance = total_width - stage_width | |
| else: | |
| variance = None | |
| # Verify solution | |
| if not optimized_solution or abs(sum(w for w, _ in optimized_solution) - stage_width) > 0.001: | |
| # Provide mode-specific suggestions | |
| if development_mode == "Medium Density": | |
| width_suggestions = "4.5m, 6m, 7.5m" if md_load_type == "Rear Loaded" else "7m, 8m, 8.5m, 10.5m" | |
| stage_suggestions = "54m, 72m, 90m" | |
| else: | |
| width_suggestions = "8.5m-18m plus corner widths" | |
| stage_suggestions = "84m, 105m, 126m" | |
| return None, pd.DataFrame(), f""" | |
| ### β Cannot achieve 100% usage with selected widths | |
| **Stage Width**: {stage_width}m | |
| **Mode**: {development_mode} {f'({md_load_type})' if development_mode == 'Medium Density' else ''} | |
| **Available Widths**: {', '.join([f"{w}m" for w in sorted(enabled_widths)])} | |
| **Try:** | |
| 1. Enable more lot types for flexibility | |
| 2. Enable "Custom Corners" option | |
| 3. Try common stage widths: {stage_suggestions} | |
| 4. Available widths: {width_suggestions} | |
| """, "", "" | |
| # Create visualizations with variance indicator | |
| title = f"{'MD ' if development_mode == 'Medium Density' else ''}Grid Cut Optimization" | |
| fig_2d = optimizer.create_enhanced_visualization( | |
| optimized_solution, stage_width, stage_depth, | |
| title, | |
| show_variance=variance | |
| ) | |
| # Create results table | |
| width_counts = {} | |
| for width, lot_type in optimized_solution: | |
| key = f"{width:.1f}m" | |
| if key in width_counts: | |
| width_counts[key]['count'] += 1 | |
| else: | |
| # Handle both standard and custom widths | |
| if width in optimizer.lot_specifications: | |
| spec = optimizer.lot_specifications[width] | |
| elif int(width) in optimizer.lot_specifications: | |
| spec = optimizer.lot_specifications[int(width)] | |
| else: | |
| # Custom width - find closest | |
| closest = min(optimizer.lot_specifications.keys(), | |
| key=lambda x: abs(x - width)) | |
| spec = optimizer.lot_specifications[closest] | |
| spec = {**spec, 'type': 'Custom', 'squares': 'Custom'} | |
| width_counts[key] = { | |
| 'count': 1, | |
| 'type': spec.get('type', 'Custom'), | |
| 'squares': spec.get('squares', 'N/A'), | |
| 'area': width * stage_depth, | |
| 'build': spec.get('build', 'N/A') | |
| } | |
| results_data = [] | |
| for width, info in sorted(width_counts.items()): | |
| row_data = { | |
| 'Lot Width': width, | |
| 'Count': info['count'], | |
| 'Type': info['type'], | |
| 'Area Each': f"{info['area']:.0f}mΒ²", | |
| 'Total Width': f"{float(width[:-1]) * info['count']:.1f}m", | |
| 'Total Area': f"{info['area'] * info['count']:.0f}mΒ²" | |
| } | |
| if development_mode == "Medium Density": | |
| row_data['Build Type'] = info['build'] | |
| results_data.append(row_data) | |
| results_df = pd.DataFrame(results_data) | |
| # Generate report | |
| report = optimizer.generate_report(optimized_solution, stage_width, stage_depth, None) | |
| # Create summary | |
| total_lots = len(optimized_solution) | |
| unique_widths = len(set(w for w, _ in optimized_solution)) | |
| if development_mode == "Medium Density": | |
| # MD specific metrics | |
| potential_dwellings = sum(2 if w >= 7.0 else 1 for w, _ in optimized_solution) | |
| density = potential_dwellings / (stage_width * stage_depth / 10000) | |
| summary = f""" | |
| **Stage**: {stage_width}m Γ {stage_depth}m = {stage_width * stage_depth}mΒ² | |
| **Development**: {development_mode} ({md_load_type}) | |
| **Total Lots**: {total_lots} | |
| **Potential Dwellings**: {potential_dwellings} ({density:.0f}/ha) | |
| **Unique Lot Types**: {unique_widths} | |
| **Grid Variance**: {variance:+.2f}m {"β " if abs(variance) < 0.001 else "β οΈ"} | |
| """ | |
| else: | |
| # Conventional metrics | |
| slhc_pairs = sum(1 for i in range(len(optimized_solution) - 1) | |
| if optimized_solution[i][0] <= 10.5 and optimized_solution[i+1][0] <= 10.5) | |
| summary = f""" | |
| **Stage**: {stage_width}m Γ {stage_depth}m = {stage_width * stage_depth}mΒ² | |
| **Total Lots**: {total_lots} | |
| **Unique Lot Types**: {unique_widths} | |
| **SLHC Pairs**: {slhc_pairs} | |
| **Grid Variance**: {variance:+.2f}m {"β " if abs(variance) < 0.001 else "β οΈ"} | |
| """ | |
| # Convert solution to string for manual editing | |
| manual_edit_string = optimizer.solution_to_string(optimized_solution) | |
| return fig_2d, results_df, summary, report, manual_edit_string | |
| def update_manual_adjustment(manual_widths_text, stage_width, stage_depth, development_mode, md_load_type, color_scheme): | |
| """Update visualization based on manual adjustment""" | |
| # Set mode | |
| if development_mode == "Medium Density": | |
| optimizer.set_development_mode('medium_density', 'rear' if md_load_type == "Rear Loaded" else 'front') | |
| else: | |
| optimizer.set_development_mode('conventional') | |
| optimizer.current_scheme = color_scheme | |
| # Parse manual widths | |
| widths = optimizer.parse_manual_adjustments(manual_widths_text) | |
| if not widths: | |
| return None, "Please enter lot widths (e.g., '14.0, 8.5, 10.5, 8.5, 14.0')" | |
| # Validate and get feedback | |
| solution, feedback = optimizer.validate_manual_solution(widths, stage_width) | |
| if not solution: | |
| return None, feedback | |
| # Calculate variance | |
| total_width = sum(widths) | |
| variance = total_width - stage_width | |
| # Create visualization with variance | |
| fig = optimizer.create_enhanced_visualization( | |
| solution, stage_width, stage_depth, | |
| "Manually Adjusted Layout", | |
| show_variance=variance | |
| ) | |
| return fig, feedback | |
| # Create Gradio interface | |
| with gr.Blocks( | |
| title="RPM Grid Cut Optimizer", | |
| theme=gr.themes.Base( | |
| primary_hue="teal", | |
| secondary_hue="green", | |
| neutral_hue="gray", | |
| font=["Arial", "sans-serif"] | |
| ).set( | |
| body_background_fill="#2E3E2F", | |
| body_background_fill_dark="#2E3E2F", | |
| block_background_fill="#2E3E2F", | |
| block_background_fill_dark="#2E3E2F", | |
| panel_background_fill="#545D51", | |
| panel_background_fill_dark="#545D51", | |
| input_background_fill="#545D51", | |
| input_background_fill_dark="#545D51", | |
| button_primary_background_fill="#216767", | |
| button_primary_background_fill_dark="#216767", | |
| block_label_text_color="white", | |
| block_title_text_color="white", | |
| body_text_color="white" | |
| ), | |
| css=""" | |
| .gradio-container { | |
| font-family: 'Arial', sans-serif !important; | |
| background: #2E3E2F !important; | |
| background-color: #2E3E2F !important; | |
| color: white !important; | |
| } | |
| .dark { | |
| --body-background-fill: #2E3E2F !important; | |
| --background-fill-primary: #2E3E2F !important; | |
| --background-fill-secondary: #545D51 !important; | |
| --panel-background-fill: #545D51 !important; | |
| --input-background-fill: #545D51 !important; | |
| --block-background-fill: #2E3E2F !important; | |
| --body-text-color: white !important; | |
| --block-label-text-color: white !important; | |
| --block-title-text-color: white !important; | |
| --text-color: white !important; | |
| } | |
| body { | |
| background-color: #2E3E2F !important; | |
| } | |
| .main { | |
| background-color: #2E3E2F !important; | |
| } | |
| .contain { | |
| background-color: #2E3E2F !important; | |
| } | |
| .app { | |
| background-color: #2E3E2F !important; | |
| } | |
| .gr-button-primary { | |
| background: #216767 !important; | |
| background-color: #216767 !important; | |
| border: none !important; | |
| box-shadow: 0 3px 5px 2px rgba(33, 103, 103, .3) !important; | |
| color: white !important; | |
| } | |
| .gr-button-primary:hover { | |
| background: #4F8585 !important; | |
| background-color: #4F8585 !important; | |
| } | |
| h1, h2, h3, h4, h5, h6 { | |
| color: white !important; | |
| } | |
| h1 { | |
| text-align: center; | |
| font-size: 2.5em; | |
| margin-bottom: 0.5em; | |
| } | |
| h3 { | |
| color: #FFCF6D !important; | |
| } | |
| .gr-form { | |
| background: rgba(84, 93, 81, 0.9) !important; | |
| background-color: rgba(84, 93, 81, 0.9) !important; | |
| border-radius: 10px !important; | |
| padding: 20px !important; | |
| border: 1px solid #216767 !important; | |
| } | |
| .gr-input, input[type="number"], input[type="text"], textarea { | |
| background-color: #545D51 !important; | |
| color: white !important; | |
| border: 1px solid #216767 !important; | |
| } | |
| .gr-input-label, .gr-radio-label { | |
| color: white !important; | |
| } | |
| .gr-check-radio { | |
| background-color: #545D51 !important; | |
| } | |
| .gr-checkbox { | |
| background-color: #545D51 !important; | |
| } | |
| .gr-checkbox input[type="checkbox"] + label { | |
| color: white !important; | |
| } | |
| label { | |
| color: white !important; | |
| } | |
| .gr-panel { | |
| background-color: #545D51 !important; | |
| border: 1px solid #216767 !important; | |
| } | |
| .gr-box { | |
| background-color: rgba(84, 93, 81, 0.5) !important; | |
| border-color: #216767 !important; | |
| } | |
| .gr-padded { | |
| background-color: transparent !important; | |
| } | |
| .gr-compact { | |
| background-color: rgba(84, 93, 81, 0.5) !important; | |
| } | |
| .gr-accordion { | |
| background-color: #545D51 !important; | |
| border-color: #216767 !important; | |
| } | |
| .output-class { | |
| background-color: #2E3E2F !important; | |
| } | |
| .input-container { | |
| background-color: #545D51 !important; | |
| } | |
| .wrap { | |
| background-color: transparent !important; | |
| } | |
| .wrap > div { | |
| background-color: transparent !important; | |
| } | |
| .gr-input-label { | |
| background-color: transparent !important; | |
| } | |
| .gr-group { | |
| background-color: rgba(84, 93, 81, 0.5) !important; | |
| border: 1px solid #216767 !important; | |
| } | |
| .markdown-text { | |
| color: white !important; | |
| } | |
| .markdown-text p { | |
| color: white !important; | |
| } | |
| .markdown-text h1, .markdown-text h2, .markdown-text h3 { | |
| color: white !important; | |
| } | |
| p { | |
| color: white !important; | |
| } | |
| /* Dark mode for radio buttons */ | |
| .dark-radio { | |
| background-color: #545D51 !important; | |
| color: white !important; | |
| } | |
| .dark-radio label { | |
| color: white !important; | |
| } | |
| .dark-radio input[type="radio"] + label { | |
| color: white !important; | |
| } | |
| /* Dark mode for number inputs */ | |
| .dark-input input { | |
| background-color: #545D51 !important; | |
| color: white !important; | |
| border: 1px solid #216767 !important; | |
| } | |
| .dark-input label { | |
| color: white !important; | |
| } | |
| /* Radio button container */ | |
| .gr-radio { | |
| background-color: #545D51 !important; | |
| } | |
| /* Info text */ | |
| .gr-info { | |
| color: #B6B8B2 !important; | |
| } | |
| /* Dark mode for checkboxes */ | |
| .dark-checkbox { | |
| background-color: transparent !important; | |
| } | |
| .dark-checkbox label { | |
| color: white !important; | |
| } | |
| .dark-checkbox input[type="checkbox"] { | |
| background-color: #545D51 !important; | |
| border-color: #216767 !important; | |
| } | |
| .dark-checkbox input[type="checkbox"]:checked { | |
| background-color: #216767 !important; | |
| } | |
| /* Dataframe styling */ | |
| .gr-dataframe { | |
| background-color: #545D51 !important; | |
| color: white !important; | |
| } | |
| .gr-dataframe th { | |
| background-color: #216767 !important; | |
| color: white !important; | |
| } | |
| .gr-dataframe td { | |
| background-color: #545D51 !important; | |
| color: white !important; | |
| border-color: #216767 !important; | |
| } | |
| /* Textbox styling */ | |
| .dark-input textarea { | |
| background-color: #545D51 !important; | |
| color: white !important; | |
| border: 1px solid #216767 !important; | |
| } | |
| /* Secondary button styling */ | |
| .gr-button-secondary { | |
| background: #545D51 !important; | |
| background-color: #545D51 !important; | |
| color: white !important; | |
| border: 1px solid #216767 !important; | |
| } | |
| .gr-button-secondary:hover { | |
| background: #697687 !important; | |
| background-color: #697687 !important; | |
| } | |
| /* Output containers */ | |
| .gr-markdown { | |
| color: white !important; | |
| } | |
| .gr-markdown * { | |
| color: white !important; | |
| } | |
| /* Plot container */ | |
| .gr-plot { | |
| background-color: #2E3E2F !important; | |
| } | |
| /* Fix for light theme bleeding through */ | |
| :root { | |
| --body-background-fill: #2E3E2F !important; | |
| --background-fill-primary: #2E3E2F !important; | |
| --background-fill-secondary: #545D51 !important; | |
| --panel-background-fill: #545D51 !important; | |
| --input-background-fill: #545D51 !important; | |
| --block-background-fill: #2E3E2F !important; | |
| --body-text-color: white !important; | |
| --block-label-text-color: white !important; | |
| --block-title-text-color: white !important; | |
| } | |
| """ | |
| ) as demo: | |
| gr.Markdown(""" | |
| <div style='text-align: center; margin-bottom: 2em;'> | |
| <h1 style='color: white; margin-bottom: 0;'>RPM Grid Cut Optimizer</h1> | |
| <p style='color: #216767; font-size: 1.2em;'>AI-Powered Subdivision Planning</p> | |
| </div> | |
| """) | |
| # Force dark mode | |
| demo.load( | |
| lambda: None, | |
| None, | |
| None, | |
| js=""" | |
| () => { | |
| document.body.classList.add('dark'); | |
| document.documentElement.style.setProperty('--body-background-fill', '#2E3E2F'); | |
| document.documentElement.style.setProperty('--background-fill-primary', '#2E3E2F'); | |
| document.documentElement.style.setProperty('--background-fill-secondary', '#545D51'); | |
| document.documentElement.style.setProperty('--panel-background-fill', '#545D51'); | |
| document.documentElement.style.setProperty('--input-background-fill', '#545D51'); | |
| document.documentElement.style.setProperty('--block-background-fill', '#2E3E2F'); | |
| document.documentElement.style.setProperty('--body-text-color', 'white'); | |
| document.documentElement.style.setProperty('--block-label-text-color', 'white'); | |
| document.documentElement.style.setProperty('--block-title-text-color', 'white'); | |
| } | |
| """ | |
| ) | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| with gr.Group(): | |
| gr.Markdown("<h3 style='color: #FFCF6D'>π Stage Configuration</h3>") | |
| development_mode = gr.Radio( | |
| ["Conventional Land", "Medium Density"], | |
| label="ποΈ Development Mode", | |
| value="Conventional Land", | |
| info="Select the type of development", | |
| elem_classes=["dark-radio"] | |
| ) | |
| md_load_type = gr.Radio( | |
| ["Front Loaded", "Rear Loaded"], | |
| label="π MD Access Type", | |
| value="Front Loaded", | |
| visible=False, | |
| info="Rear loaded includes 7m laneway", | |
| elem_classes=["dark-radio"] | |
| ) | |
| stage_width = gr.Number( | |
| label="Stage Width (m)", | |
| value=105.0, | |
| info="Width along the street", | |
| elem_classes=["dark-input"] | |
| ) | |
| stage_depth = gr.Number( | |
| label="Stage Depth (m)", | |
| value=32.0, | |
| info="Depth of lots (perpendicular to street)", | |
| elem_classes=["dark-input"] | |
| ) | |
| gr.Markdown("<h3 style='color: #FFCF6D'>π Lot Width Options</h3>") | |
| # Conventional widths group | |
| with gr.Group(visible=True) as conventional_group: | |
| gr.Markdown("<p style='color: white; font-weight: bold'>Standard Widths</p>") | |
| with gr.Row(): | |
| enable_8_5 = gr.Checkbox(label="8.5m SLHC", value=True, elem_classes=["dark-checkbox"]) | |
| enable_10_5 = gr.Checkbox(label="10.5m SLHC", value=True, elem_classes=["dark-checkbox"]) | |
| enable_12_5 = gr.Checkbox(label="12.5m", value=True, elem_classes=["dark-checkbox"]) | |
| with gr.Row(): | |
| enable_14 = gr.Checkbox(label="14.0m", value=True, elem_classes=["dark-checkbox"]) | |
| enable_16 = gr.Checkbox(label="16.0m", value=True, elem_classes=["dark-checkbox"]) | |
| enable_18 = gr.Checkbox(label="18.0m", value=False, elem_classes=["dark-checkbox"]) | |
| enable_corners = gr.Checkbox( | |
| label="Enable Corner-Specific Widths", | |
| value=True, | |
| info="Adds variety and helps achieve 100%", | |
| elem_classes=["dark-checkbox"] | |
| ) | |
| with gr.Row(): | |
| enable_11 = gr.Checkbox(label="11.0m", value=True, elem_classes=["dark-checkbox"]) | |
| enable_13_3 = gr.Checkbox(label="13.3m", value=True, elem_classes=["dark-checkbox"]) | |
| with gr.Row(): | |
| enable_14_8 = gr.Checkbox(label="14.8m", value=True, elem_classes=["dark-checkbox"]) | |
| enable_16_8 = gr.Checkbox(label="16.8m", value=True, elem_classes=["dark-checkbox"]) | |
| # MD Rear Loaded widths | |
| with gr.Group(visible=False) as md_rear_group: | |
| gr.Markdown("<p style='color: white; font-weight: bold'>MD Rear Loaded Widths</p>") | |
| enable_4_5 = gr.Checkbox(label="4.5m (2/2/1)", value=True, elem_classes=["dark-checkbox"]) | |
| enable_6_0 = gr.Checkbox(label="6.0m (3/2/2)", value=True, elem_classes=["dark-checkbox"]) | |
| enable_7_5 = gr.Checkbox(label="7.5m (3-4/2/2)", value=True, elem_classes=["dark-checkbox"]) | |
| # MD Front Loaded widths | |
| with gr.Group(visible=False) as md_front_group: | |
| gr.Markdown("<p style='color: white; font-weight: bold'>MD Front Loaded Widths</p>") | |
| enable_7_0 = gr.Checkbox(label="7.0m (3/2/1)", value=True, elem_classes=["dark-checkbox"]) | |
| enable_8_0 = gr.Checkbox(label="8.0m (3-4/2/2)", value=True, elem_classes=["dark-checkbox"]) | |
| enable_md_8_5 = gr.Checkbox(label="8.5m (3/2/1)", value=True, elem_classes=["dark-checkbox"]) | |
| enable_md_10_5 = gr.Checkbox(label="10.5m (3-4/2/2)", value=True, elem_classes=["dark-checkbox"]) | |
| with gr.Column(scale=1): | |
| gr.Markdown("<h3 style='color: #FFCF6D'>βοΈ Settings</h3>") | |
| allow_custom_corners = gr.Checkbox( | |
| label="π― Allow Flexible Corner Widths", | |
| value=True, | |
| info="Enables 13.8m, 13.9m etc. for perfect fits", | |
| elem_classes=["dark-checkbox"] | |
| ) | |
| color_scheme = gr.Radio( | |
| ["rpm_primary", "rpm_contrast", "rpm_monochrome"], | |
| label="π¨ Color Scheme", | |
| value="rpm_primary", | |
| info="RPM brand color palettes", | |
| elem_classes=["dark-radio"] | |
| ) | |
| optimize_btn = gr.Button( | |
| "π Optimize Grid Cut", | |
| variant="primary", | |
| size="lg", | |
| elem_id="optimize-button" | |
| ) | |
| gr.Markdown(""" | |
| <div style='background-color: rgba(84, 93, 81, 0.5); padding: 15px; border-radius: 8px; border: 1px solid #216767;'> | |
| <h3 style='color: #FFCF6D; margin-top: 0;'>π‘ Quick Tips:</h3> | |
| <ul style='color: white; margin-bottom: 0;'> | |
| <li><strong style='color: #FFCF6D'>Conventional</strong>: Traditional lots with corner splays</li> | |
| <li><strong style='color: #FFCF6D'>Medium Density</strong>: Compact lots for higher yield</li> | |
| <li><strong style='color: #FFCF6D'>Rear Loaded</strong>: Includes 7m laneway visualization</li> | |
| <li><strong style='color: #FFCF6D'>Grid Variance</strong>: Shows if layout is perfect (0.0m)</li> | |
| </ul> | |
| </div> | |
| """) | |
| with gr.Row(): | |
| plot_2d = gr.Plot(label="2D Layout Visualization") | |
| # Manual adjustment section | |
| gr.Markdown("<h3 style='color: #FFCF6D'>βοΈ Fine-Tune Result</h3>") | |
| with gr.Row(): | |
| with gr.Column(scale=2): | |
| manual_widths = gr.Textbox( | |
| label="Manually Adjust Lot Widths", | |
| placeholder="Widths will appear here after optimization", | |
| info="Edit the widths (comma-separated) and click 'Update Layout'", | |
| lines=2, | |
| elem_classes=["dark-input"] | |
| ) | |
| with gr.Column(scale=1): | |
| update_btn = gr.Button("π Update Layout", variant="secondary") | |
| adjustment_feedback = gr.Markdown( | |
| value="", | |
| label="Adjustment Feedback" | |
| ) | |
| with gr.Row(): | |
| results_table = gr.DataFrame(label="Lot Distribution Analysis") | |
| with gr.Row(): | |
| with gr.Column(): | |
| summary_output = gr.Markdown(label="Optimization Summary") | |
| with gr.Column(): | |
| report_output = gr.Markdown(label="Professional Report") | |
| # Wire up development mode changes | |
| def handle_mode_change(mode): | |
| if mode == "Medium Density": | |
| return gr.update(visible=True), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False) | |
| else: | |
| return gr.update(visible=False), gr.update(visible=True), gr.update(visible=False), gr.update(visible=False) | |
| def handle_md_type_change(md_type): | |
| if md_type == "Rear Loaded": | |
| return gr.update(visible=True), gr.update(visible=False) | |
| else: | |
| return gr.update(visible=False), gr.update(visible=True) | |
| development_mode.change( | |
| handle_mode_change, | |
| inputs=[development_mode], | |
| outputs=[md_load_type, conventional_group, md_rear_group, md_front_group] | |
| ) | |
| md_load_type.change( | |
| handle_md_type_change, | |
| inputs=[md_load_type], | |
| outputs=[md_rear_group, md_front_group] | |
| ) | |
| # Wire up the optimize button | |
| optimize_btn.click( | |
| optimize_grid, | |
| inputs=[ | |
| stage_width, | |
| stage_depth, | |
| development_mode, | |
| md_load_type, | |
| # Conventional | |
| enable_8_5, enable_10_5, enable_12_5, enable_14, enable_16, enable_18, | |
| enable_corners, enable_11, enable_13_3, enable_14_8, enable_16_8, | |
| # MD Rear | |
| enable_4_5, enable_6_0, enable_7_5, | |
| # MD Front | |
| enable_7_0, enable_8_0, enable_md_8_5, enable_md_10_5, | |
| allow_custom_corners, color_scheme | |
| ], | |
| outputs=[plot_2d, results_table, summary_output, report_output, manual_widths] | |
| ) | |
| update_btn.click( | |
| update_manual_adjustment, | |
| inputs=[manual_widths, stage_width, stage_depth, development_mode, md_load_type, color_scheme], | |
| outputs=[plot_2d, adjustment_feedback] | |
| ) | |
| return demo | |
| # Create and launch | |
| if __name__ == "__main__": | |
| app = create_advanced_app() | |
| app.queue() | |
| app.launch(share=False, inbrowser=True) |