An error occurred while executing the following Python script:

Error: Traceback (most recent call last):
  File "D:\Spatial\temp_scripts\temp_script_d8f7c67a3c0f4556ba317ac39f3183ba.py", line 1002, in <module>
    main()
  File "D:\Spatial\temp_scripts\temp_script_d8f7c67a3c0f4556ba317ac39f3183ba.py", line 997, in main
    window = NetworkGUI()
             ^^^^^^^^^^^^
  File "D:\Spatial\temp_scripts\temp_script_d8f7c67a3c0f4556ba317ac39f3183ba.py", line 583, in __init__
    self.components["central_node"].append(self.central_node)
    ~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^
KeyError: 'central_node'


Script:

import sys
import json
from PyQt6 import QtWidgets, QtCore, QtGui

# Define colors for different component types for better visual distinction
COMPONENT_COLORS = {
    "erven": QtGui.QColor(0, 255, 0),             # Green
    "centroids": QtGui.QColor(0, 0, 255),         # Blue
    "boundary_boxes": QtGui.QColor(255, 165, 0),  # Orange
    "muc350": QtGui.QColor(128, 0, 128),          # Purple
    "splitters": QtGui.QColor(255, 0, 255),       # Magenta
    "central_node": QtGui.QColor(200, 200, 200),  # Light Gray
    "road": QtGui.QColor(255, 255, 255),          # White
    "road_crossing": QtGui.QColor(139, 69, 19),   # Brown
    "drop_cable": QtGui.QColor(255, 0, 0),        # Red
    "link_cable": QtGui.QColor(0, 0, 255)         # Blue
}

# Define different pen styles for cables
CABLE_STYLES = {
    "drop_cable": QtCore.Qt.PenStyle.DashLine,
    "link_cable": QtCore.Qt.PenStyle.SolidLine
}

# Path to component icons (ensure these paths are correct or adjust accordingly)
# For demonstration, using solid color rectangles instead of actual icons
# You can replace these with actual icon paths
COMPONENT_ICONS = {
    "erven": None,
    "centroids": None,
    "boundary_boxes": None,
    "muc350": None,
    "splitters": None,
    "central_node": None
}

class NetworkComponent(QtWidgets.QGraphicsObject):
    """Represents a network component with a label, icon, and color."""
    
    # Signal emitted when the component is moved
    moved = QtCore.pyqtSignal()
    
    def __init__(self, component_type, label, x, y, width=80, height=40, icon_path=None):
        super().__init__()
        self.component_type = component_type
        self.label = label
        self.width = width
        self.height = height
        self.icon_path = icon_path or COMPONENT_ICONS.get(component_type, None)
        self.setPos(x, y)
        
        # Initialize brush and pen
        self.brush = QtGui.QBrush(COMPONENT_COLORS.get(component_type, QtGui.QColor(200, 200, 200)))
        self.default_brush = self.brush  # To restore after hover
        self.pen = QtGui.QPen(QtGui.QColor(0, 0, 0))  # Black border
        
        # Load icon if available
        if self.icon_path:
            self.icon = QtGui.QPixmap(self.icon_path).scaled(
                self.width - 10, self.height - 10, QtCore.Qt.AspectRatioMode.KeepAspectRatio, QtCore.Qt.TransformationMode.SmoothTransformation
            )
        else:
            self.icon = None
        
        # Set flags to make the item movable and selectable, and to send geometry change notifications
        self.setFlags(
            QtWidgets.QGraphicsItem.GraphicsItemFlag.ItemIsMovable |
            QtWidgets.QGraphicsItem.GraphicsItemFlag.ItemIsSelectable |
            QtWidgets.QGraphicsItem.GraphicsItemFlag.ItemSendsGeometryChanges
        )
        self.setAcceptHoverEvents(True)
        self.connections = []  # List to store connected cables
    
    def add_connection(self, cable):
        """Add a cable connection to this component."""
        if cable not in self.connections:
            self.connections.append(cable)
    
    def remove_connection(self, cable):
        """Remove a cable connection from this component."""
        if cable in self.connections:
            self.connections.remove(cable)
    
    def hoverEnterEvent(self, event):
        """Change color on hover."""
        self.brush = QtGui.QBrush(QtGui.QColor(255, 255, 0))  # Yellow
        self.update()  # Trigger a repaint
        super().hoverEnterEvent(event)
    
    def hoverLeaveEvent(self, event):
        """Revert color when not hovered."""
        self.brush = self.default_brush
        self.update()  # Trigger a repaint
        super().hoverLeaveEvent(event)
    
    def contextMenuEvent(self, event):
        """Provide a context menu for actions like Delete or Edit Properties."""
        menu = QtWidgets.QMenu()
        delete_action = menu.addAction("Delete")
        edit_action = menu.addAction("Edit Properties")
        action = menu.exec(event.screenPos())
        if action == delete_action:
            self.delete_component()
        elif action == edit_action:
            self.edit_properties()
    
    def delete_component(self):
        """Delete the component and its connected cables."""
        # Remove all connected cables
        for cable in self.connections.copy():
            cable.delete_cable()
        # Remove the component from the scene
        scene = self.scene()
        if scene:
            scene.removeItem(self)
        # Optionally, emit a signal or perform additional cleanup
    
    def edit_properties(self):
        """Open a dialog to edit component properties."""
        dialog = PropertiesDialog(self)
        if dialog.exec() == QtWidgets.QDialog.DialogCode.Accepted:
            self.label = dialog.name_edit.text()
            self.update()  # Refresh the display
    
    def boundingRect(self):
        """Define the bounding rectangle of the component."""
        return QtCore.QRectF(0, 0, self.width, self.height)
    
    def paint(self, painter, option, widget=None):
        """Custom paint to include the label and icon."""
        # Draw the rectangle
        painter.setBrush(self.brush)
        painter.setPen(self.pen)
        painter.drawRect(self.boundingRect())
    
        # Draw the icon
        if self.icon:
            painter.drawPixmap(5, 5, self.icon)
        else:
            # Draw a colored rectangle as a placeholder
            painter.setBrush(QtGui.QBrush(QtGui.QColor(100, 100, 100)))
            painter.drawRect(5, 5, self.width - 10, self.height - 10)
    
        # Draw the label below the icon
        painter.setPen(QtGui.QPen(QtGui.QColor(0, 0, 0)))  # Black text
        font = painter.font()
        font.setPointSize(10)
        painter.setFont(font)
        painter.drawText(
            QtCore.QRectF(0, self.height - 20, self.width, 20),
            QtCore.Qt.AlignmentFlag.AlignCenter,
            self.label
        )
    
    def itemChange(self, change, value):
        """Override to emit signal when moved and handle snapping."""
        if change == QtWidgets.QGraphicsItem.GraphicsItemChange.ItemPositionHasChanged:
            self.moved.emit()
            self.snap_to_center_of_erven()
        return super().itemChange(change, value)
    
    def snap_to_center_of_erven(self, threshold=50):
        """Snap the centroid to the center of the nearest Erf within a threshold."""
        if self.component_type != "centroids":
            return
        nearest_erven = None
        min_distance = float('inf')
        scene = self.scene()
        if not scene:
            return
        for erf in scene.components["erven"]:
            erf_center = erf.scenePos() + erf.boundingRect().center()
            distance = QtCore.QLineF(self.scenePos() + self.boundingRect().center(), erf_center).length()
            if distance < min_distance:
                min_distance = distance
                nearest_erven = erf
        if nearest_erven and min_distance <= threshold:
            erf_center = nearest_erven.scenePos() + nearest_erven.boundingRect().center()
            new_pos = erf_center - QtCore.QPointF(self.boundingRect().width()/2, self.boundingRect().height()/2)
            self.setPos(new_pos)
    
    def mousePressEvent(self, event):
        """Handle initiating cable creation via mouse drag."""
        if event.button() == QtCore.Qt.MouseButton.LeftButton and self.component_type != "road_crossing":
            self.scene().initiate_cable_creation(self, event.pos())
        super().mousePressEvent(event)

class NetworkCable(QtWidgets.QGraphicsPathItem):
    """Represents a cable connecting two network components."""
    
    def __init__(self, start_item, end_item, cable_type='drop_cable'):
        super().__init__()
        self.start_item = start_item
        self.end_item = end_item
        self.cable_type = cable_type
        
        # Set pen style based on cable type
        pen = QtGui.QPen(COMPONENT_COLORS.get(cable_type, QtGui.QColor(0, 0, 0)), 2)
        pen.setStyle(CABLE_STYLES.get(cable_type, QtCore.Qt.PenStyle.SolidLine))
        self.setPen(pen)
        
        # Enable selection
        self.setFlags(
            QtWidgets.QGraphicsItem.GraphicsItemFlag.ItemIsSelectable
        )
        
        self.update_position()
        
        # Connect signals to update position when components move
        self.start_item.moved.connect(self.update_position)
        self.end_item.moved.connect(self.update_position)
        
        # Add this cable to the components' connections
        self.start_item.add_connection(self)
        self.end_item.add_connection(self)
        
        # Ensure the cable is behind components
        self.setZValue(-1)
    
    def update_position(self):
        """Update the cable's position based on connected components."""
        start_center = self.start_item.scenePos() + self.start_item.boundingRect().center()
        end_center = self.end_item.scenePos() + self.end_item.boundingRect().center()
        
        # Retrieve trench lines from the scene
        scene = self.scene()
        if not isinstance(scene, NetworkScene):
            return
        trench_lines = scene.get_trench_lines()
        
        # Simple routing: start -> trench -> end
        path = QtGui.QPainterPath(start_center)
        
        # Find closest points on trench lines
        start_trench_point = scene.closest_point_on_trench(start_center)
        end_trench_point = scene.closest_point_on_trench(end_center)
        
        if start_trench_point and end_trench_point:
            # Connect to trench
            path.lineTo(start_trench_point)
            # Connect along trench (assuming horizontal trench)
            if start_trench_point.y() == end_trench_point.y():
                path.lineTo(end_trench_point)
            else:
                # Add a vertical segment to align with trench
                path.lineTo(start_trench_point.x(), end_trench_point.y())
                path.lineTo(end_trench_point)
            # Connect trench to end
            path.lineTo(end_center)
        else:
            # Fallback to direct line if trench points are not found
            path.lineTo(end_center)
        
        self.setPath(path)
    
    def contextMenuEvent(self, event):
        """Provide a context menu for deleting the cable."""
        menu = QtWidgets.QMenu()
        delete_action = menu.addAction("Delete Cable")
        action = menu.exec(event.screenPos())
        if action == delete_action:
            self.delete_cable()
    
    def delete_cable(self):
        """Delete the cable from the scene and disconnect from components."""
        # Remove connections from components
        self.start_item.remove_connection(self)
        self.end_item.remove_connection(self)
        # Remove the cable from the scene
        scene = self.scene()
        if scene:
            scene.removeItem(self)
        # Optionally, perform additional cleanup

class PropertiesDialog(QtWidgets.QDialog):
    """Dialog to edit properties of a network component."""
    
    def __init__(self, component, parent=None):
        super().__init__(parent)
        self.setWindowTitle("Edit Properties")
        self.component = component
        self.setup_ui()
    
    def setup_ui(self):
        layout = QtWidgets.QFormLayout(self)
        
        # Name Edit
        self.name_edit = QtWidgets.QLineEdit(self)
        self.name_edit.setText(self.component.label)
        layout.addRow("Name:", self.name_edit)
        
        # Component Type (read-only)
        self.type_label = QtWidgets.QLabel(self.component.component_type)
        layout.addRow("Type:", self.type_label)
        
        # Position (read-only)
        pos = self.component.pos()
        self.position_label = QtWidgets.QLabel(f"({pos.x():.1f}, {pos.y():.1f})")
        layout.addRow("Position:", self.position_label)
        
        # Buttons
        button_box = QtWidgets.QDialogButtonBox(
            QtWidgets.QDialogButtonBox.StandardButton.Ok |
            QtWidgets.QDialogButtonBox.StandardButton.Cancel
        )
        button_box.accepted.connect(self.accept)
        button_box.rejected.connect(self.reject)
        layout.addRow(button_box)

class PropertiesPanel(QtWidgets.QDockWidget):
    """Dockable panel to display and edit properties of selected components."""
    
    def __init__(self, parent=None):
        super().__init__("Properties", parent)
        self.setAllowedAreas(QtCore.Qt.DockWidgetArea.LeftDockWidgetArea | QtCore.Qt.DockWidgetArea.RightDockWidgetArea)
        self.widget = QtWidgets.QWidget()
        self.layout = QtWidgets.QFormLayout(self.widget)
        self.setWidget(self.widget)
        
        # Properties fields
        self.name_edit = QtWidgets.QLineEdit()
        self.type_label = QtWidgets.QLabel()
        self.position_label = QtWidgets.QLabel()
        
        self.layout.addRow("Name:", self.name_edit)
        self.layout.addRow("Type:", self.type_label)
        self.layout.addRow("Position:", self.position_label)
        
        # Update button
        self.update_button = QtWidgets.QPushButton("Update")
        self.layout.addRow(self.update_button)
        self.update_button.clicked.connect(self.update_properties)
        
        self.current_component = None
    
    def display_properties(self, component):
        """Display properties of the selected component."""
        self.current_component = component
        self.name_edit.setText(component.label)
        self.type_label.setText(component.component_type)
        pos = component.pos()
        self.position_label.setText(f"({pos.x():.1f}, {pos.y():.1f})")
    
    def update_properties(self):
        """Update the component's properties based on user input."""
        if self.current_component:
            new_name = self.name_edit.text()
            self.current_component.label = new_name
            self.current_component.update()

class HelpDialog(QtWidgets.QDialog):
    """Dialog to provide help and documentation."""
    
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setWindowTitle("Help - Network Configuration GUI")
        self.setMinimumSize(600, 400)
        layout = QtWidgets.QVBoxLayout(self)
        
        help_text = """
        <h2>Network Configuration GUI Help</h2>
        <p>This application allows you to configure and visualize network components such as Erven, Centroids, Boundary Boxes, MUC350s, Splitters, and cables.</p>
        
        <h3>Adding Components</h3>
        <ul>
            <li>Use the toolbox on the left to add different types of components.</li>
            <li>Components can be positioned by dragging them on the canvas.</li>
            <li>Centroids automatically snap to the center of the nearest Erf when moved close enough.</li>
        </ul>
        
        <h3>Creating Cables</h3>
        <ul>
            <li>To create a cable, click and drag from one component to another.</li>
            <li>Select the cable type (Drop Cable or Link Cable) when prompted.</li>
            <li>Drop Cables are represented in red (dashed lines), and Link Cables are in blue (solid lines).</li>
            <li>Drop Cables follow the trech lines for realistic routing.</li>
        </ul>
        
        <h3>Context Menus</h3>
        <ul>
            <li>Right-click on a component to delete it or edit its properties.</li>
            <li>Right-click on a cable to delete it.</li>
        </ul>
        
        <h3>Properties Panel</h3>
        <ul>
            <li>Select a component to view and edit its properties in the Properties panel on the right.</li>
        </ul>
        
        <h3>Saving and Loading</h3>
        <ul>
            <li>Use the File menu to save the current network configuration to a JSON file.</li>
            <li>Load an existing configuration from a JSON file to recreate the network.</li>
        </ul>
        
        <h3>Simulation</h3>
        <ul>
            <li>Use the Simulation menu to test connectivity between two selected components.</li>
            <li>Components will change color based on their connectivity status.</li>
        </ul>
        
        <h3>Performance</h3>
        <ul>
            <li>The application optimizes rendering for smooth performance even with a large number of components.</li>
        </ul>
        
        """
        
        text_browser = QtWidgets.QTextBrowser()
        text_browser.setHtml(help_text)
        layout.addWidget(text_browser)
        
        button_box = QtWidgets.QDialogButtonBox(QtWidgets.QDialogButtonBox.StandardButton.Close)
        button_box.rejected.connect(self.reject)
        layout.addWidget(button_box)

class NetworkScene(QtWidgets.QGraphicsScene):
    """Custom QGraphicsScene to handle cable creation via mouse drag."""
    
    def __init__(self, parent=None):
        super().__init__(parent)
        self.parent_widget = parent
        self.creating_cable = False
        self.start_component = None
        self.temp_line = None

        # Trenched lines for routing cables
        self.trenches = []
        self.create_trench_lines()
    
    def create_trench_lines(self):
        """Create trench lines and add to the scene."""
        trench_pen = QtGui.QPen(QtGui.QColor(160, 82, 45), 2)  # SaddleBrown color

        # Example trench lines (horizontal and vertical)
        trench1 = QtWidgets.QGraphicsLineItem(300, 400, 1100, 400)  # Horizontal trench
        trench1.setPen(trench_pen)
        trench1.setZValue(-3)  # Behind cables
        self.addItem(trench1)
        self.trenches.append(trench1)

        trench2 = QtWidgets.QGraphicsLineItem(700, 200, 700, 600)  # Vertical trench
        trench2.setPen(trench_pen)
        trench2.setZValue(-3)
        self.addItem(trench2)
        self.trenches.append(trench2)
    
    def get_trench_lines(self):
        """Return all trench lines."""
        return self.trenches
    
    def closest_point_on_trench(self, point):
        """Find the closest point on any trench line to the given point."""
        min_distance = float('inf')
        closest_point = None
        for trench in self.trenches:
            line = trench.line()
            proj = self.project_point_onto_line(point, line)
            distance = QtCore.QLineF(point, proj).length()
            if distance < min_distance:
                min_distance = distance
                closest_point = proj
        return closest_point
    
    def project_point_onto_line(self, point, line):
        """Project a point onto a line."""
        line_p1 = line.p1()
        line_p2 = line.p2()
        dx = line_p2.x() - line_p1.x()
        dy = line_p2.y() - line_p1.y()
        if dx == dy == 0:
            return line_p1
        t = ((point.x() - line_p1.x()) * dx + (point.y() - line_p1.y()) * dy) / (dx*dx + dy*dy)
        t = max(0, min(1, t))
        return QtCore.QPointF(line_p1.x() + t * dx, line_p1.y() + t * dy)
    
    def find_nearest_component(self, pos, search_distance=30):
        """Find the nearest NetworkComponent to a given position within a search distance."""
        nearest_component = None
        min_distance = float('inf')
        for category in ["erven", "centroids", "boundary_boxes", "central_node", "muc350", "splitters"]:
            for component in self.parent_widget.components.get(category, []):
                component_center = component.scenePos() + component.boundingRect().center()
                distance = QtCore.QLineF(pos, component_center).length()
                if distance < min_distance and distance <= search_distance:
                    min_distance = distance
                    nearest_component = component
        return nearest_component
    
    def initiate_cable_creation(self, component, pos):
        """Start the process of creating a cable from the given component."""
        self.creating_cable = True
        self.start_component = component
        scene_pos = component.mapToScene(pos)
        self.temp_line = QtWidgets.QGraphicsLineItem(QtCore.QLineF(scene_pos, scene_pos))
        pen = QtGui.QPen(QtGui.QColor(0, 0, 0), 2, QtCore.Qt.PenStyle.DashLine)
        self.temp_line.setPen(pen)
        self.temp_line.setZValue(-2)  # Behind cables
        self.addItem(self.temp_line)
    
    def mouseMoveEvent(self, event):
        """Update the temporary line during cable creation."""
        if self.creating_cable and self.temp_line:
            new_end = event.scenePos()
            line = QtCore.QLineF(self.temp_line.line().p1(), new_end)
            self.temp_line.setLine(line)
        super().mouseMoveEvent(event)
    
    def mouseReleaseEvent(self, event):
        """Finalize the cable creation if a valid end component is targeted."""
        if self.creating_cable and self.temp_line:
            end_items = self.items(event.scenePos())
            # Find the topmost NetworkComponent under the cursor
            end_component = None
            for item in end_items:
                if isinstance(item, NetworkComponent) and item != self.start_component:
                    end_component = item
                    break
            if end_component:
                # Prompt user to select cable type
                cable_type, ok = QtWidgets.QInputDialog.getItem(
                    None, "Select Cable Type", "Cable Type:", ["drop_cable", "link_cable"], 0, False
                )
                if ok:
                    new_cable = NetworkCable(self.start_component, end_component, cable_type=cable_type)
                    self.parent_widget.add_cable_to_scene(new_cable)
            # Remove the temporary line
            self.removeItem(self.temp_line)
            self.temp_line = None
            self.creating_cable = False
        super().mouseReleaseEvent(event)

class NetworkGUI(QtWidgets.QMainWindow):
    """Main window for the network configuration GUI."""
    
    def __init__(self):
        super().__init__()
        
        self.setWindowTitle("Network Configuration")
        self.setGeometry(100, 100, 1400, 900)
        
        self.central_widget = QtWidgets.QWidget()
        self.setCentralWidget(self.central_widget)
        self.layout = QtWidgets.QHBoxLayout(self.central_widget)
        
        # Initialize Properties Panel
        self.properties_panel = PropertiesPanel(self)
        self.addDockWidget(QtCore.Qt.DockWidgetArea.RightDockWidgetArea, self.properties_panel)
        
        # Initialize Help Menu
        self.init_help_menu()
        
        # Initialize Simulation Menu
        self.init_simulation_menu()
        
        # Initialize File Menu
        self.init_file_menu()
        
        # Initialize Graphics View and Scene
        self.scene = NetworkScene(self)
        self.view = QtWidgets.QGraphicsView(self.scene)
        self.view.setRenderHint(QtGui.QPainter.RenderHint.Antialiasing)
        self.layout.addWidget(self.view)
        
        # Connect selection changes
        self.scene.selectionChanged.connect(self.update_properties_panel)
        
        # Initialize component storage
        self.components = {
            "erven": [],
            "centroids": [],
            "boundary_boxes": [],
            "drop_cables": [],
            "link_cables": [],
            "muc350": [],
            "splitters": [],
            "road_crossings": []
        }
        
        # Initialize the central node and intersection line
        self.central_node = NetworkComponent("central_node", "Central Node", 700, 350, 100, 50, COMPONENT_ICONS.get("central_node"))
        self.scene.addItem(self.central_node)
        self.components["central_node"].append(self.central_node)
        
        # Create the road and trench lines are already handled in NetworkScene
        self.create_road()
        
        # Create UI elements
        self.create_toolbox()
        self.create_initial_components()
        
    def init_help_menu(self):
        """Initialize the Help menu in the menu bar."""
        help_action = QtGui.QAction("Help", self)
        help_action.triggered.connect(self.show_help)
        menu = self.menuBar().addMenu("Help")
        menu.addAction(help_action)
    
    def show_help(self):
        """Display the help dialog."""
        help_dialog = HelpDialog(self)
        help_dialog.exec()
    
    def init_simulation_menu(self):
        """Initialize the Simulation menu with connectivity testing."""
        simulation_menu = self.menuBar().addMenu("Simulation")
        
        test_connectivity_action = QtGui.QAction("Test Connectivity", self)
        test_connectivity_action.triggered.connect(self.test_connectivity)
        
        simulation_menu.addAction(test_connectivity_action)
    
    def init_file_menu(self):
        """Initialize the File menu in the menu bar with Save and Load options."""
        file_menu = self.menuBar().addMenu("File")
        
        save_action = QtGui.QAction("Save Configuration", self)
        save_action.triggered.connect(self.save_configuration)
        
        load_action = QtGui.QAction("Load Configuration", self)
        load_action.triggered.connect(self.load_configuration)
        
        file_menu.addAction(save_action)
        file_menu.addAction(load_action)
    
    def create_toolbox(self):
        """Create a dockable toolbox for adding components."""
        toolbox = QtWidgets.QDockWidget("Components", self)
        self.addDockWidget(QtCore.Qt.DockWidgetArea.LeftDockWidgetArea, toolbox)
        
        toolbox_widget = QtWidgets.QWidget()
        toolbox_layout = QtWidgets.QVBoxLayout(toolbox_widget)
        
        # Add buttons for different components
        self.add_component_button(toolbox_layout, "Add Erf", self.add_erven)
        self.add_component_button(toolbox_layout, "Add Centroid", self.add_centroid)
        self.add_component_button(toolbox_layout, "Add Boundary Box", self.add_boundary_box)
        self.add_component_button(toolbox_layout, "Add Drop Cable", self.add_drop_cable)
        self.add_component_button(toolbox_layout, "Add Link Cable", self.add_link_cable)
        self.add_component_button(toolbox_layout, "Add MUC350", self.add_muc350)
        self.add_component_button(toolbox_layout, "Add 1:16-way Splitter", self.add_splitter)
        self.add_component_button(toolbox_layout, "Add Road-Crossing", self.add_road_crossing)
        
        # Spacer to push buttons to the top
        toolbox_layout.addStretch()
        toolbox.setWidget(toolbox_widget)
    
    def add_component_button(self, layout, label, callback):
        """Helper method to add a button to the toolbox."""
        button = QtWidgets.QPushButton(label)
        button.clicked.connect(callback)
        layout.addWidget(button)
    
    def create_road(self):
        """Create the road with two T-sections forming a U-shape."""
        # Main road
        road = QtWidgets.QGraphicsLineItem(300, 350, 1100, 350)
        road.setPen(QtGui.QPen(COMPONENT_COLORS["road"], 4))  # Width of 4 pixels
        road.setZValue(-4)  # Behind trench lines
        self.scene.addItem(road)
        
        # First T-section
        t_section1 = QtWidgets.QGraphicsLineItem(700, 350, 700, 400)
        t_section1.setPen(QtGui.QPen(COMPONENT_COLORS["road"], 4))
        t_section1.setZValue(-4)
        self.scene.addItem(t_section1)
        
        # Second T-section
        t_section2 = QtWidgets.QGraphicsLineItem(700, 350, 700, 300)
        t_section2.setPen(QtGui.QPen(COMPONENT_COLORS["road"], 4))
        t_section2.setZValue(-4)
        self.scene.addItem(t_section2)
    
    def create_initial_components(self):
        """Create initial network components with specified layout."""
        # Create Erven on both sides of the road
        erven_spacing = 150
        erven_start_x_left = 150
        erven_start_y = 300
        erven_width = 80
        erven_height = 40

        for i in range(4):
            x = erven_start_x_left + i * erven_spacing
            y = erven_start_y - 100
            erf = NetworkComponent("erven", f"Erf {i+1}", x, y, erven_width, erven_height, COMPONENT_ICONS.get("erven"))
            self.scene.addItem(erf)
            self.components["erven"].append(erf)
        
        erven_start_x_right = 1000
        for i in range(4):
            x = erven_start_x_right - i * erven_spacing
            y = erven_start_y - 100
            erf = NetworkComponent("erven", f"Erf {i+5}", x, y, erven_width, erven_height, COMPONENT_ICONS.get("erven"))
            self.scene.addItem(erf)
            self.components["erven"].append(erf)
        
        # Create initial centroids (half the size of Erven)
        centroid_width = 40  # Half of 80
        centroid_height = 20  # Half of 40
        centroid_spacing = 150
        centroid_start_x_left = 150
        centroid_start_y = 300

        for i in range(4):
            x = centroid_start_x_left + i * centroid_spacing
            y = centroid_start_y
            centroid = NetworkComponent("centroids", f"Centroid {i+1}", x, y, centroid_width, centroid_height, COMPONENT_ICONS.get("centroids"))
            self.scene.addItem(centroid)
            self.components["centroids"].append(centroid)
        
        centroid_start_x_right = 1000
        for i in range(4):
            x = centroid_start_x_right - i * centroid_spacing
            y = centroid_start_y
            centroid = NetworkComponent("centroids", f"Centroid {i+5}", x, y, centroid_width, centroid_height, COMPONENT_ICONS.get("centroids"))
            self.scene.addItem(centroid)
            self.components["centroids"].append(centroid)
        
        # Create boundary boxes
        boundary_box_spacing = 200
        boundary_box_start_x = 300
        boundary_box_start_y = 200
        for i in range(3):
            x = boundary_box_start_x + i * boundary_box_spacing
            y = boundary_box_start_y
            boundary_box = NetworkComponent("boundary_boxes", f"BB {i+1}", x, y, 80, 40, COMPONENT_ICONS.get("boundary_boxes"))
            self.scene.addItem(boundary_box)
            self.components["boundary_boxes"].append(boundary_box)
    
        # Optionally, add initial cables
        # Connect the first and last Erven to the central node
        if self.components["erven"]:
            first_erf = self.components["erven"][0]
            last_erf = self.components["erven"][-1]
            drop_cable1 = NetworkCable(first_erf, self.central_node, cable_type='drop_cable')
            self.add_cable_to_scene(drop_cable1)
    
            drop_cable2 = NetworkCable(last_erf, self.central_node, cable_type='drop_cable')
            self.add_cable_to_scene(drop_cable2)
    
    def add_cable_to_scene(self, cable):
        """Add a cable to the scene and store it."""
        self.scene.addItem(cable)
        if cable.cable_type == 'drop_cable':
            self.components["drop_cables"].append(cable)
        elif cable.cable_type == 'link_cable':
            self.components["link_cables"].append(cable)
    
    def add_erven(self):
        """Add an Erf component."""
        index = len(self.components["erven"]) + 1
        # Alternate side based on position
        if index % 2 == 1:
            x = 150 + ((index - 1) // 2) * 150
            y = 200
        else:
            x = 1000 - ((index // 2 - 1) * 150)
            y = 200
        erf = NetworkComponent("erven", f"Erf {index}", x, y, 80, 40, COMPONENT_ICONS.get("erven"))
        self.scene.addItem(erf)
        self.components["erven"].append(erf)
        self.statusBar().showMessage(f"Added Erf {index}", 2000)
    
    def add_centroid(self):
        """Add a Centroid component."""
        index = len(self.components["centroids"]) + 1
        # Alternate side based on position
        if index % 2 == 1:
            x = 150 + ((index - 1) // 2) * 150
            y = 350
        else:
            x = 1000 - ((index // 2 - 1) * 150)
            y = 350
        centroid = NetworkComponent("centroids", f"Centroid {index}", x, y, 40, 20, COMPONENT_ICONS.get("centroids"))  # Half size
        self.scene.addItem(centroid)
        self.components["centroids"].append(centroid)
        self.statusBar().showMessage(f"Added Centroid {index}", 2000)
    
    def add_boundary_box(self):
        """Add a Boundary Box component."""
        index = len(self.components["boundary_boxes"]) + 1
        x = 300 + (index - 1) * 200
        y = 200
        boundary_box = NetworkComponent("boundary_boxes", f"BB {index}", x, y, 80, 40, COMPONENT_ICONS.get("boundary_boxes"))
        self.scene.addItem(boundary_box)
        self.components["boundary_boxes"].append(boundary_box)
        self.statusBar().showMessage(f"Added Boundary Box {index}", 2000)
    
    def add_drop_cable(self):
        """Add a Drop Cable by connecting the last Erf to the central node."""
        if not self.components["erven"]:
            QtWidgets.QMessageBox.warning(self, "No Erven", "Please add an Erf first.")
            return
        # For simplicity, connect the last Erf to the central node
        start_item = self.components["erven"][-1]
        end_item = self.central_node
        drop_cable = NetworkCable(start_item, end_item, cable_type='drop_cable')
        self.add_cable_to_scene(drop_cable)
        self.statusBar().showMessage("Added Drop Cable", 2000)
    
    def add_link_cable(self):
        """Add a Link Cable by connecting the central node to the last Centroid."""
        if not self.components["centroids"]:
            QtWidgets.QMessageBox.warning(self, "No Centroids", "Please add a Centroid first.")
            return
        # For simplicity, connect the central node to the last Centroid
        start_item = self.central_node
        end_item = self.components["centroids"][-1]
        link_cable = NetworkCable(start_item, end_item, cable_type='link_cable')
        self.add_cable_to_scene(link_cable)
        self.statusBar().showMessage("Added Link Cable", 2000)
    
    def add_muc350(self):
        """Add a MUC350 component."""
        index = len(self.components["muc350"]) + 1
        x = 700
        y = 100 + (index - 1) * 100
        muc350 = NetworkComponent("muc350", f"MUC350 {index}", x, y, 80, 40, COMPONENT_ICONS.get("muc350"))
        self.scene.addItem(muc350)
        self.components["muc350"].append(muc350)
        self.statusBar().showMessage(f"Added MUC350 {index}", 2000)
    
    def add_splitter(self):
        """Add a 1:16-way Splitter component."""
        index = len(self.components["splitters"]) + 1
        x = 800
        y = 150 + (index - 1) * 100
        splitter = NetworkComponent("splitters", f"Splitter {index}", x, y, 80, 40, COMPONENT_ICONS.get("splitters"))
        self.scene.addItem(splitter)
        self.components["splitters"].append(splitter)
        self.statusBar().showMessage(f"Added Splitter {index}", 2000)
    
    def add_road_crossing(self):
        """Add a Road-Crossing component."""
        index = len(self.components["road_crossings"]) + 1
        x = 500  # Starting x position for Road-Crossing
        y = 350  # Vertical position aligned with trench
        road_crossing_length = 200
        road_crossing = QtWidgets.QGraphicsLineItem(x, y, x + road_crossing_length, y)
        road_crossing.setPen(QtGui.QPen(COMPONENT_COLORS["road_crossing"], 8))  # Twice the road width (4*2)
        road_crossing.setZValue(-2)  # Behind road lines but above cables
        self.scene.addItem(road_crossing)
        self.components["road_crossings"].append(road_crossing)
        self.statusBar().showMessage(f"Added Road-Crossing {index}", 2000)
    
    def update_properties_panel(self):
        """Update the properties panel based on the selected item."""
        selected_items = self.scene.selectedItems()
        if selected_items:
            item = selected_items[0]
            if isinstance(item, NetworkComponent):
                self.properties_panel.display_properties(item)
        else:
            # Clear the properties panel if nothing is selected
            self.properties_panel.current_component = None
            self.properties_panel.name_edit.clear()
            self.properties_panel.type_label.clear()
            self.properties_panel.position_label.clear()
    
    def save_configuration(self):
        """Save the current network layout to a JSON file."""
        options = QtWidgets.QFileDialog.Options()
        file_path, _ = QtWidgets.QFileDialog.getSaveFileName(self, "Save Configuration", "", "JSON Files (*.json)", options=options)
        if file_path:
            data = {
                "components": [],
                "cables": []
            }
            # Save components
            for category in ["erven", "centroids", "boundary_boxes", "central_node", "muc350", "splitters"]:
                for component in self.components.get(category, []):
                    component_data = {
                        "type": component.component_type,
                        "label": component.label,
                        "x": component.scenePos().x(),
                        "y": component.scenePos().y(),
                        "width": component.width,
                        "height": component.height
                        # Add more properties as needed
                    }
                    data["components"].append(component_data)
            # Save cables
            for cable in self.components.get("drop_cables", []) + self.components.get("link_cables", []):
                cable_data = {
                    "start_label": cable.start_item.label,
                    "end_label": cable.end_item.label,
                    "type": cable.cable_type
                }
                data["cables"].append(cable_data)
            # Write to file
            try:
                with open(file_path, 'w') as f:
                    json.dump(data, f, indent=4)
                self.statusBar().showMessage(f"Configuration saved to {file_path}", 5000)
            except Exception as e:
                QtWidgets.QMessageBox.critical(self, "Save Error", f"Failed to save configuration:\n{e}")
    
    def load_configuration(self):
        """Load a network layout from a JSON file."""
        options = QtWidgets.QFileDialog.Options()
        file_path, _ = QtWidgets.QFileDialog.getOpenFileName(self, "Load Configuration", "", "JSON Files (*.json)", options=options)
        if file_path:
            try:
                with open(file_path, 'r') as f:
                    data = json.load(f)
                # Clear existing scene
                self.clear_scene()
                # Load components
                label_to_component = {}
                for component_data in data.get("components", []):
                    component_type = component_data["type"]
                    label = component_data["label"]
                    x = component_data["x"]
                    y = component_data["y"]
                    width = component_data.get("width", 80)
                    height = component_data.get("height", 40)
                    icon = COMPONENT_ICONS.get(component_type, None)
                    component = NetworkComponent(component_type, label, x, y, width, height, icon)
                    self.scene.addItem(component)
                    self.components.setdefault(component_type, []).append(component)
                    label_to_component[label] = component
                # Load cables
                for cable_data in data.get("cables", []):
                    start_label = cable_data["start_label"]
                    end_label = cable_data["end_label"]
                    cable_type = cable_data.get("type", "drop_cable")
                    start_item = label_to_component.get(start_label, None)
                    end_item = label_to_component.get(end_label, None)
                    if start_item and end_item:
                        cable = NetworkCable(start_item, end_item, cable_type=cable_type)
                        self.add_cable_to_scene(cable)
                self.statusBar().showMessage(f"Configuration loaded from {file_path}", 5000)
            except Exception as e:
                QtWidgets.QMessageBox.critical(self, "Load Error", f"Failed to load configuration:\n{e}")
    
    def clear_scene(self):
        """Clear all items from the scene and reset component storage."""
        self.scene.clear()
        for key in self.components:
            self.components[key] = []
        # Recreate trench lines
        self.scene = NetworkScene(self)
        self.view.setScene(self.scene)
        # Re-add central node and road
        self.central_node = NetworkComponent("central_node", "Central Node", 700, 350, 100, 50, COMPONENT_ICONS.get("central_node"))
        self.scene.addItem(self.central_node)
        self.components["central_node"].append(self.central_node)
        self.create_road()
    
    def test_connectivity(self):
        """Test connectivity between two selected components."""
        selected_items = self.scene.selectedItems()
        components = [item for item in selected_items if isinstance(item, NetworkComponent)]
        if len(components) != 2:
            QtWidgets.QMessageBox.warning(self, "Selection Error", "Please select exactly two components to test connectivity.")
            return
        start = components[0]
        end = components[1]
        # Perform BFS to check connectivity
        visited = set()
        queue = [start]
        found = False
        while queue:
            current = queue.pop(0)
            if current == end:
                found = True
                break
            visited.add(current)
            for cable in current.connections:
                neighbor = cable.start_item if cable.end_item == current else cable.end_item
                if neighbor not in visited and neighbor not in queue:
                    queue.append(neighbor)
        # Update visual indicators
        if found:
            start.pen.setColor(QtGui.QColor(0, 255, 0))  # Green
            end.pen.setColor(QtGui.QColor(0, 255, 0))
            start.update()
            end.update()
            QtWidgets.QMessageBox.information(self, "Connectivity Test", f"{start.label} and {end.label} are connected.")
        else:
            start.pen.setColor(QtGui.QColor(255, 0, 0))  # Red
            end.pen.setColor(QtGui.QColor(255, 0, 0))
            start.update()
            end.update()
            QtWidgets.QMessageBox.information(self, "Connectivity Test", f"{start.label} and {end.label} are NOT connected.")
    
    def show_help(self):
        """Display the help dialog."""
        help_dialog = HelpDialog(self)
        help_dialog.exec()
    

def main():
    """Main function to run the application."""
    app = QtWidgets.QApplication(sys.argv)
    window = NetworkGUI()
    window.show()
    sys.exit(app.exec())

if __name__ == "__main__":
    main()


Please suggest a fix for the error.