diff --git a/README.md b/README.md index 005c488..96c21ca 100644 --- a/README.md +++ b/README.md @@ -341,6 +341,47 @@ See **SETUP_CREDENTIALS.md** for API credential issues. --- +## Developer Guide + +This section provides instructions for setting up a development environment, running tests, and understanding the project's internal structure. + +### Development Environment Setup + +1. **Clone the repository:** + ```bash + git clone https://github.com/your-repo/K1_Lightwave.git + cd K1_Lightwave + ``` + +2. **Create a virtual environment:** + ```bash + python3 -m venv .venv + source .venv/bin/activate + ``` + +3. **Install dependencies:** + ```bash + pip install -r requirements.txt + ``` + +### Running Tests + +To run the test suite, use the following command: +```bash +python3 -m unittest discover -s tests +``` + +### Project Structure Overview + +- `elite_pcb_designer.py`: The main entry point and orchestrator for the PCB design process. +- `design_preparation.py`: Handles the initial loading and preparation of the design files. +- `component_placement.py`: Manages the intelligent placement of components. +- `automated_routing.py`: Orchestrates the automated routing process. +- `design_validation.py`: Performs final validation and generates manufacturing files. +- `tests/`: Contains the unit tests for the project. + +--- + ## License & Credits K1 Lightwave design pipeline. Built with KiCad, Python, Claude AI, and open-source EDA tools. diff --git a/__pycache__/automated_routing.cpython-312.pyc b/__pycache__/automated_routing.cpython-312.pyc deleted file mode 100644 index 2ffd906..0000000 Binary files a/__pycache__/automated_routing.cpython-312.pyc and /dev/null differ diff --git a/__pycache__/automated_routing.cpython-313.pyc b/__pycache__/automated_routing.cpython-313.pyc deleted file mode 100644 index 062e677..0000000 Binary files a/__pycache__/automated_routing.cpython-313.pyc and /dev/null differ diff --git a/__pycache__/automated_routing.cpython-39.pyc b/__pycache__/automated_routing.cpython-39.pyc deleted file mode 100644 index bf8d017..0000000 Binary files a/__pycache__/automated_routing.cpython-39.pyc and /dev/null differ diff --git a/__pycache__/component_placement.cpython-312.pyc b/__pycache__/component_placement.cpython-312.pyc deleted file mode 100644 index 083ad88..0000000 Binary files a/__pycache__/component_placement.cpython-312.pyc and /dev/null differ diff --git a/__pycache__/component_placement.cpython-313.pyc b/__pycache__/component_placement.cpython-313.pyc deleted file mode 100644 index 9867d19..0000000 Binary files a/__pycache__/component_placement.cpython-313.pyc and /dev/null differ diff --git a/__pycache__/component_placement.cpython-39.pyc b/__pycache__/component_placement.cpython-39.pyc deleted file mode 100644 index e93e844..0000000 Binary files a/__pycache__/component_placement.cpython-39.pyc and /dev/null differ diff --git a/__pycache__/design_preparation.cpython-312.pyc b/__pycache__/design_preparation.cpython-312.pyc deleted file mode 100644 index edd367c..0000000 Binary files a/__pycache__/design_preparation.cpython-312.pyc and /dev/null differ diff --git a/__pycache__/design_preparation.cpython-313.pyc b/__pycache__/design_preparation.cpython-313.pyc deleted file mode 100644 index 51d6cf8..0000000 Binary files a/__pycache__/design_preparation.cpython-313.pyc and /dev/null differ diff --git a/__pycache__/design_preparation.cpython-39.pyc b/__pycache__/design_preparation.cpython-39.pyc deleted file mode 100644 index 7cce985..0000000 Binary files a/__pycache__/design_preparation.cpython-39.pyc and /dev/null differ diff --git a/__pycache__/design_validation.cpython-312.pyc b/__pycache__/design_validation.cpython-312.pyc deleted file mode 100644 index ef08a45..0000000 Binary files a/__pycache__/design_validation.cpython-312.pyc and /dev/null differ diff --git a/__pycache__/design_validation.cpython-313.pyc b/__pycache__/design_validation.cpython-313.pyc deleted file mode 100644 index d65e6f1..0000000 Binary files a/__pycache__/design_validation.cpython-313.pyc and /dev/null differ diff --git a/__pycache__/design_validation.cpython-39.pyc b/__pycache__/design_validation.cpython-39.pyc deleted file mode 100644 index 20f9e90..0000000 Binary files a/__pycache__/design_validation.cpython-39.pyc and /dev/null differ diff --git a/__pycache__/elite_pcb_designer.cpython-312.pyc b/__pycache__/elite_pcb_designer.cpython-312.pyc deleted file mode 100644 index aacfe20..0000000 Binary files a/__pycache__/elite_pcb_designer.cpython-312.pyc and /dev/null differ diff --git a/__pycache__/elite_pcb_designer.cpython-313.pyc b/__pycache__/elite_pcb_designer.cpython-313.pyc deleted file mode 100644 index ff5b49f..0000000 Binary files a/__pycache__/elite_pcb_designer.cpython-313.pyc and /dev/null differ diff --git a/__pycache__/elite_pcb_designer.cpython-39.pyc b/__pycache__/elite_pcb_designer.cpython-39.pyc deleted file mode 100644 index ed5779c..0000000 Binary files a/__pycache__/elite_pcb_designer.cpython-39.pyc and /dev/null differ diff --git a/__pycache__/freerouting_config.cpython-313.pyc b/__pycache__/freerouting_config.cpython-313.pyc deleted file mode 100644 index 1da3910..0000000 Binary files a/__pycache__/freerouting_config.cpython-313.pyc and /dev/null differ diff --git a/__pycache__/ipc_standards_library.cpython-312.pyc b/__pycache__/ipc_standards_library.cpython-312.pyc deleted file mode 100644 index 8205d72..0000000 Binary files a/__pycache__/ipc_standards_library.cpython-312.pyc and /dev/null differ diff --git a/__pycache__/test_elite_pcb_designer.cpython-312.pyc b/__pycache__/test_elite_pcb_designer.cpython-312.pyc deleted file mode 100644 index 0b8fe3b..0000000 Binary files a/__pycache__/test_elite_pcb_designer.cpython-312.pyc and /dev/null differ diff --git a/__pycache__/test_ipc_standards.cpython-312.pyc b/__pycache__/test_ipc_standards.cpython-312.pyc deleted file mode 100644 index 3042156..0000000 Binary files a/__pycache__/test_ipc_standards.cpython-312.pyc and /dev/null differ diff --git a/agent/dfm/checker.py b/agent/dfm/checker.py index 3eaad2f..a4c0b1d 100644 --- a/agent/dfm/checker.py +++ b/agent/dfm/checker.py @@ -19,12 +19,23 @@ class DFMCheck: """Single DFM check result.""" def __init__(self, rule: str, message: str, status: str = "PASS", severity: str = "INFO"): + """Initializes a new DFM check. + Args: + rule (str): The name of the rule being checked. + message (str): A description of the check. + status (str): The status of the check (PASS or FAIL). + severity (str): The severity of the check (INFO, WARNING, or ERROR). + """ self.rule = rule self.message = message self.status = status # PASS or FAIL self.severity = severity # INFO, WARNING, ERROR def to_dict(self) -> Dict[str, Any]: + """Converts the DFM check to a dictionary. + Returns: + Dict[str, Any]: A dictionary representation of the DFM check. + """ return { "rule": self.rule, "message": self.message, diff --git a/agent/drivers/kicad_cli.py b/agent/drivers/kicad_cli.py index b4d4e44..e714eaf 100644 --- a/agent/drivers/kicad_cli.py +++ b/agent/drivers/kicad_cli.py @@ -20,7 +20,6 @@ class KiCadCLIError(Exception): """Raised when a kicad-cli command fails.""" - pass def _run_cmd(args, description=""): diff --git a/agent/orchestrator/run.py b/agent/orchestrator/run.py index 67ee212..b20fd5e 100644 --- a/agent/orchestrator/run.py +++ b/agent/orchestrator/run.py @@ -35,19 +35,24 @@ class OrchestratorError(Exception): """Raised when orchestrator encounters a fatal error.""" - pass class Phase: """Context manager for tracking phase execution.""" def __init__(self, phase_num, description): + """Initializes a new phase. + Args: + phase_num (int): The number of the phase. + description (str): A description of the phase. + """ self.phase_num = phase_num self.description = description self.start_time = None self.end_time = None def __enter__(self): + """Starts the phase and prints a header.""" self.start_time = datetime.now() print(f"\n{'=' * 70}") print(f"PHASE {self.phase_num}: {self.description}") @@ -55,6 +60,7 @@ def __enter__(self): return self def __exit__(self, exc_type, exc_val, exc_tb): + """Ends the phase and prints a summary.""" self.end_time = datetime.now() elapsed = (self.end_time - self.start_time).total_seconds() if exc_type is None: diff --git a/agent/routing/freerouting.py b/agent/routing/freerouting.py index e83b16d..1e1c9f8 100644 --- a/agent/routing/freerouting.py +++ b/agent/routing/freerouting.py @@ -19,7 +19,6 @@ class FreeRoutingError(Exception): """Raised when FreeRouting operation fails.""" - pass def route(jar_path, dsn_file, ses_output, timeout_s=1800, max_retries=1): diff --git a/automated_routing.py b/automated_routing.py index 4d0533b..9de4115 100644 --- a/automated_routing.py +++ b/automated_routing.py @@ -62,7 +62,23 @@ class RoutingStatus(Enum): @dataclass class TraceSpecification: - """Specifications for trace routing""" + """Defines the routing specifications for a single electrical trace. + + Attributes: + net_name: The name of the net this trace belongs to. + net_type: The functional type of the net (e.g., POWER, HIGH_SPEED). + width_mil: The width of the trace in mils. + width_mm: The width of the trace in millimeters. + clearance_mil: The clearance around the trace in mils. + clearance_mm: The clearance around the trace in millimeters. + max_length_mm: The maximum allowed length of the trace in millimeters. + length_match_tolerance_mm: The length matching tolerance for differential pairs. + series_damping_ohm: The value of a series damping resistor, if required. + differential_pair: A flag indicating if this is part of a differential pair. + differential_spacing_mil: The required spacing for a differential pair in mils. + impedance_target_ohm: The target impedance for the trace. + layer_constraint: The specific layer the trace should be routed on. + """ net_name: str net_type: NetType width_mil: float @@ -90,7 +106,14 @@ def clearance_um(self) -> float: @dataclass class ViaSpecification: - """Specifications for via placement""" + """Specifies the properties of a via. + + Attributes: + diameter_mm: The full diameter of the via pad in millimeters. + drill_mm: The diameter of the drill hole in millimeters. + via_type: The type of via (e.g., "through", "blind", "buried"). + thermal_relief: A flag indicating if thermal relief should be applied. + """ diameter_mm: float drill_mm: float via_type: str = "through" # through, blind, buried, thermal @@ -107,7 +130,19 @@ def drill_um(self) -> float: @dataclass class CopperZone: - """Copper zone/plane specification""" + """Defines a copper pour or zone on a specific layer. + + Attributes: + name: The unique name of the copper zone. + net_name: The name of the net this zone is connected to. + layer: The layer on which the zone is placed. + priority: The priority of the zone fill. + clearance_mm: The clearance between the zone and other copper features. + min_width_mm: The minimum width of any part of the filled zone. + thermal_relief: A flag for applying thermal relief to pads. + fill_mode: The fill pattern (e.g., "solid", "hatch"). + hatch_spacing_mm: The spacing for hatched fill patterns. + """ name: str net_name: str layer: str @@ -121,7 +156,17 @@ class CopperZone: @dataclass class ThermalViaArray: - """Thermal via array for component cooling""" + """Defines an array of thermal vias for heat dissipation under a component. + + Attributes: + component_ref: The reference designator of the component. + center_x_mm: The x-coordinate of the center of the via array. + center_y_mm: The y-coordinate of the center of the via array. + num_vias: The total number of vias in the array. + grid_spacing_mm: The spacing between vias in the grid. + via_spec: The `ViaSpecification` for the thermal vias. + pattern: The layout pattern of the vias (e.g., "grid", "circle"). + """ component_ref: str center_x_mm: float center_y_mm: float @@ -133,7 +178,19 @@ class ThermalViaArray: @dataclass class RoutingResult: - """Result of routing operation""" + """Summarizes the results of the automated routing process. + + Attributes: + status: The final status of the routing operation. + nets_routed: The number of nets that were successfully routed. + nets_total: The total number of nets in the design. + vias_placed: The total number of vias placed on the board. + routing_time_sec: The total time taken for routing, in seconds. + drc_violations: The number of Design Rule Check violations found. + messages: A list of informational messages from the routing process. + warnings: A list of warnings generated during routing. + errors: A list of errors that occurred during routing. + """ status: RoutingStatus nets_routed: int nets_total: int @@ -160,7 +217,13 @@ def success(self) -> bool: # ============================================================================= class K1RoutingConfiguration: - """K1 Lightwave specific routing rules and specifications""" + """Provides a centralized configuration for all routing rules and specifications. + + This class contains all the design-specific constraints for the K1 Lightwave + board, including trace widths, clearances, via sizes, copper zones, and + thermal via arrays. It is intended to be a single source of truth for the + automated routing process. + """ # Power nets with current requirements POWER_NETS = { @@ -395,7 +458,15 @@ class K1RoutingConfiguration: @classmethod def get_all_critical_nets(cls) -> Dict[str, TraceSpecification]: - """Get all critical nets that need manual routing""" + """Retrieves a consolidated dictionary of all critical nets. + + This method combines the power, SPI, USB, and I2C/I2S net specifications + into a single dictionary for easy access. + + Returns: + A dictionary where keys are net names and values are their + `TraceSpecification` objects. + """ all_nets = {} all_nets.update(cls.POWER_NETS) all_nets.update(cls.SPI_NETS) @@ -409,16 +480,37 @@ def get_all_critical_nets(cls) -> Dict[str, TraceSpecification]: # ============================================================================= class CriticalNetRouter: - """Manual routing for power and high-speed signals""" + """Handles the manual routing of critical nets, such as power and high-speed signals. + + This class is responsible for loading the KiCad board and applying specific + routing strategies to nets that require careful handling to ensure signal + integrity and power delivery. + + Attributes: + board_path (Path): The file path to the KiCad PCB file. + logger (logging.Logger): A logger for recording operational messages. + board (pcbnew.BOARD): The `pcbnew` board object. + routed_nets (List[str]): A list of nets that have been successfully routed. + """ def __init__(self, board_path: str, logger: Optional[logging.Logger] = None): + """Initializes the CriticalNetRouter. + + Args: + board_path: The file path to the KiCad .kicad_pcb file. + logger: An optional logger instance. + """ self.board_path = Path(board_path) self.logger = logger or logging.getLogger(__name__) self.board = None self.routed_nets = [] def load_board(self) -> bool: - """Load KiCad board""" + """Loads the KiCad board file using the `pcbnew` API. + + Returns: + True if the board is loaded successfully, False otherwise. + """ try: import pcbnew self.board = pcbnew.LoadBoard(str(self.board_path)) @@ -429,11 +521,14 @@ def load_board(self) -> bool: return False def route_power_nets(self) -> Dict[str, Any]: - """ - Route power distribution nets: VBUS, 3V3, LED_5V, GND + """Routes the main power distribution nets. + + This method applies specific trace widths and clearances for the power + nets, as defined in the `K1RoutingConfiguration`. Returns: - Dictionary with routing results + A dictionary containing the results of the power net routing, + including which nets were routed and the overall status. """ self.logger.info("=" * 60) self.logger.info("ROUTING POWER NETS") @@ -464,11 +559,13 @@ def route_power_nets(self) -> Dict[str, Any]: return results def route_spi_signals(self) -> Dict[str, Any]: - """ - Route SPI clock and data signals with series damping + """Routes the high-speed SPI (Serial Peripheral Interface) signals. + + This method handles the routing of SPI clock and data lines, including + any requirements for series damping resistors to maintain signal integrity. Returns: - Dictionary with routing results + A dictionary containing the results of the SPI signal routing. """ self.logger.info("=" * 60) self.logger.info("ROUTING SPI SIGNALS (40 MHz)") @@ -499,11 +596,14 @@ def route_spi_signals(self) -> Dict[str, Any]: return results def route_usb_signals(self) -> Dict[str, Any]: - """ - Route USB differential pair (D+/D-) + """Routes the USB (Universal Serial Bus) differential pair. + + This method ensures that the USB D+ and D- signals are routed as a + length-matched differential pair with the correct spacing to meet + USB 2.0 specifications. Returns: - Dictionary with routing results + A dictionary containing the results of the USB signal routing. """ self.logger.info("=" * 60) self.logger.info("ROUTING USB DIFFERENTIAL PAIR") @@ -533,11 +633,10 @@ def route_usb_signals(self) -> Dict[str, Any]: return results def route_i2c_i2s(self) -> Dict[str, Any]: - """ - Route I2C and I2S signals + """Routes the I2C (Inter-Integrated Circuit) and I2S (Integrated Interchip Sound) signals. Returns: - Dictionary with routing results + A dictionary containing the results of the I2C/I2S signal routing. """ self.logger.info("=" * 60) self.logger.info("ROUTING I2C/I2S SIGNALS") @@ -571,7 +670,20 @@ def route_i2c_i2s(self) -> Dict[str, Any]: # ============================================================================= class FreeRoutingIntegration: - """FreeRouting auto-router integration""" + """Manages the integration with the FreeRouting external autorouter. + + This class handles exporting the KiCad board to the Specctra DSN format, + configuring and running the FreeRouting JAR, and importing the resulting + SES session file back into the KiCad board. + + Attributes: + board_path (Path): The file path to the KiCad PCB file. + work_dir (Path): The directory for storing intermediate routing files. + freerouting_jar (Path): The file path to the FreeRouting executable JAR. + logger (logging.Logger): A logger for recording operational messages. + dsn_file (Path): The path to the exported DSN file. + ses_file (Path): The path to the imported SES session file. + """ def __init__( self, @@ -580,6 +692,14 @@ def __init__( freerouting_jar: Optional[str] = None, logger: Optional[logging.Logger] = None ): + """Initializes the FreeRoutingIntegration class. + + Args: + board_path: The file path to the KiCad .kicad_pcb file. + work_dir: The directory for storing intermediate routing files. + freerouting_jar: The file path to the FreeRouting executable JAR. + logger: An optional logger instance. + """ self.board_path = Path(board_path) self.work_dir = Path(work_dir) if work_dir else self.board_path.parent / "build" / "routing" self.freerouting_jar = Path(freerouting_jar) if freerouting_jar else None @@ -592,7 +712,14 @@ def __init__( self.work_dir.mkdir(parents=True, exist_ok=True) def find_freerouting_jar(self) -> bool: - """Locate FreeRouting JAR file""" + """Locates the FreeRouting JAR file on the local system. + + This method searches for the FreeRouting JAR in common installation + directories and updates the `freerouting_jar` attribute if found. + + Returns: + True if the FreeRouting JAR is found, False otherwise. + """ if self.freerouting_jar and self.freerouting_jar.exists(): self.logger.info(f"Using FreeRouting: {self.freerouting_jar}") return True @@ -616,11 +743,12 @@ def find_freerouting_jar(self) -> bool: return False def export_to_dsn(self) -> bool: - """ - Export board to Specctra DSN format + """Exports the KiCad board to the Specctra DSN file format. + + This DSN file is used as the input for the FreeRouting autorouter. Returns: - True if export successful + True if the DSN file is exported successfully, False otherwise. """ self.logger.info("=" * 60) self.logger.info("EXPORTING TO SPECCTRA DSN") @@ -653,11 +781,10 @@ def export_to_dsn(self) -> bool: return False def configure_freerouting(self) -> Dict[str, Any]: - """ - Configure FreeRouting parameters for K1 board + """Configures the parameters for the FreeRouting autorouter. Returns: - Configuration dictionary + A dictionary of configuration parameters for FreeRouting. """ config = { "threads": 4, @@ -675,14 +802,13 @@ def configure_freerouting(self) -> Dict[str, Any]: return config def run_freerouting(self, timeout: int = 900) -> bool: - """ - Execute FreeRouting auto-router + """Executes the FreeRouting autorouter as a subprocess. Args: - timeout: Maximum routing time in seconds (default 15 minutes) + timeout: The maximum time in seconds to allow for routing. Returns: - True if routing successful + True if the autorouter completes successfully, False otherwise. """ self.logger.info("=" * 60) self.logger.info("RUNNING FREEROUTING AUTO-ROUTER") @@ -752,11 +878,10 @@ def run_freerouting(self, timeout: int = 900) -> bool: return False def import_routing_results(self) -> bool: - """ - Import FreeRouting SES results back into KiCad board + """Imports the routing results from the SES file back into the KiCad board. Returns: - True if import successful + True if the import is successful, False otherwise. """ self.logger.info("=" * 60) self.logger.info("IMPORTING ROUTING RESULTS") @@ -790,11 +915,14 @@ def import_routing_results(self) -> bool: return False def verify_routing(self) -> Tuple[bool, str]: - """ - Validate routing quality and completion + """Validates the quality and completeness of the routing. + + This method checks for unrouted segments, minimum trace widths, clearances, + and other routing-related issues. Returns: - (success, message) tuple + A tuple containing a boolean indicating if the routing is valid, and + a string message summarizing the verification results. """ self.logger.info("=" * 60) self.logger.info("VERIFYING ROUTING") @@ -819,7 +947,22 @@ def verify_routing(self) -> Tuple[bool, str]: # ============================================================================= class AutomatedRouting: - """Complete Phase 3 routing pipeline orchestrator""" + """Orchestrates the entire automated PCB routing pipeline. + + This class coordinates the various stages of the routing process, including + critical net routing, autorouting with FreeRouting, copper zone creation, + thermal via placement, and post-routing validation. + + Attributes: + board_path (Path): The file path to the KiCad PCB file. + kicad_path (str): The path to the KiCad installation directory. + freerouting_jar (str): The file path to the FreeRouting executable JAR. + work_dir (Path): The directory for storing intermediate routing files. + logger (logging.Logger): A logger for recording operational messages. + critical_router (CriticalNetRouter): An instance for routing critical nets. + freerouting (FreeRoutingIntegration): An instance for autorouter integration. + result (RoutingResult): An object to store the final routing results. + """ def __init__( self, @@ -829,6 +972,15 @@ def __init__( work_dir: Optional[str] = None, logger: Optional[logging.Logger] = None ): + """Initializes the AutomatedRouting orchestrator. + + Args: + board_path: The file path to the KiCad .kicad_pcb file. + kicad_path: The path to the KiCad installation directory. + freerouting_jar: The file path to the FreeRouting executable JAR. + work_dir: The directory for storing intermediate routing files. + logger: An optional logger instance. + """ self.board_path = Path(board_path) self.kicad_path = kicad_path self.freerouting_jar = freerouting_jar @@ -869,11 +1021,13 @@ def __init__( ) def route_critical_nets(self) -> Dict[str, Any]: - """ - Step 1: Route power and high-speed nets manually + """Routes the critical power and high-speed nets. + + This is the first step in the routing pipeline, where nets requiring + manual control are routed before the autorouter is run. Returns: - Dictionary with routing results + A dictionary summarizing the results of the critical net routing. """ self.logger.info("\n" + "=" * 70) self.logger.info("PHASE 3: AUTOMATED ROUTING - STEP 1: CRITICAL NETS") @@ -909,11 +1063,10 @@ def route_critical_nets(self) -> Dict[str, Any]: return results def export_for_autorouting(self) -> bool: - """ - Step 2: Export to DSN for FreeRouting + """Exports the board to the DSN format required by FreeRouting. Returns: - True if export successful + True if the export is successful, False otherwise. """ self.logger.info("\n" + "=" * 70) self.logger.info("PHASE 3: AUTOMATED ROUTING - STEP 2: EXPORT TO DSN") @@ -928,11 +1081,10 @@ def export_for_autorouting(self) -> bool: return success def run_autorouter(self) -> bool: - """ - Step 3: Execute FreeRouting + """Executes the FreeRouting autorouter. Returns: - True if routing successful + True if the autorouting process completes successfully, False otherwise. """ self.logger.info("\n" + "=" * 70) self.logger.info("PHASE 3: AUTOMATED ROUTING - STEP 3: AUTO-ROUTE") @@ -961,11 +1113,10 @@ def run_autorouter(self) -> bool: return success def create_copper_zones(self) -> Dict[str, Any]: - """ - Step 4: Create GND/power planes and pour + """Creates and pours the copper zones for power and ground planes. Returns: - Dictionary with zone creation results + A dictionary summarizing the results of the copper zone creation. """ self.logger.info("\n" + "=" * 70) self.logger.info("PHASE 3: AUTOMATED ROUTING - STEP 4: COPPER ZONES") @@ -995,11 +1146,10 @@ def create_copper_zones(self) -> Dict[str, Any]: return results def place_thermal_vias(self) -> Dict[str, Any]: - """ - Step 5: Add thermal vias under high-power components + """Places thermal vias under high-power components for heat dissipation. Returns: - Dictionary with via placement results + A dictionary summarizing the results of the thermal via placement. """ self.logger.info("\n" + "=" * 70) self.logger.info("PHASE 3: AUTOMATED ROUTING - STEP 5: THERMAL VIAS") @@ -1032,11 +1182,11 @@ def place_thermal_vias(self) -> Dict[str, Any]: return results def validate_routing(self) -> Tuple[bool, List[str]]: - """ - Step 6: Verify all constraints met + """Validates the final routed board against all design constraints. Returns: - (success, violations) tuple + A tuple containing a boolean indicating if the validation passed, + and a list of any violation messages. """ self.logger.info("\n" + "=" * 70) self.logger.info("PHASE 3: AUTOMATED ROUTING - STEP 6: VALIDATION") @@ -1062,11 +1212,13 @@ def validate_routing(self) -> Tuple[bool, List[str]]: return len(violations) == 0, violations def execute(self) -> bool: - """ - Run full Phase 3 pipeline + """Executes the full automated routing pipeline. + + This method runs all the routing and post-routing steps in the correct + sequence to produce a fully routed PCB. Returns: - True if all steps successful + True if the entire pipeline completes successfully, False otherwise. """ self.logger.info("\n" + "=" * 80) self.logger.info("ELITE PCB DESIGNER AGENT - PHASE 3: AUTOMATED ROUTING") diff --git a/component_placement.py b/component_placement.py index ced72f9..0b0a040 100644 --- a/component_placement.py +++ b/component_placement.py @@ -28,12 +28,24 @@ @dataclass class Point: - """2D point in mm""" + """Represents a 2D point in millimeters. + + Attributes: + x: The x-coordinate of the point. + y: The y-coordinate of the point. + """ x: float y: float def distance_to(self, other: 'Point') -> float: - """Calculate Euclidean distance to another point""" + """Calculates the Euclidean distance to another point. + + Args: + other: The other point to which the distance is calculated. + + Returns: + The distance between the two points in millimeters. + """ return math.sqrt((self.x - other.x)**2 + (self.y - other.y)**2) def __add__(self, other: 'Point') -> 'Point': @@ -45,7 +57,17 @@ def __sub__(self, other: 'Point') -> 'Point': @dataclass class ComponentInfo: - """Component placement information""" + """Stores placement and metadata for a single component. + + Attributes: + reference: The component's reference designator (e.g., "R1", "U1"). + footprint: The name of the component's footprint. + position: The (x, y) coordinates of the component's center. + rotation: The component's rotation in degrees. + placed: A flag indicating whether the component has been placed. + thermal_zone: The name of the thermal zone the component belongs to. + cluster: The functional group the component is a part of. + """ reference: str footprint: str position: Optional[Point] = None @@ -57,7 +79,17 @@ class ComponentInfo: @dataclass class K1ThermalZone: - """Represents a thermal zone on the K1 board""" + """Defines a thermal zone on the PCB for managing heat-sensitive components. + + Attributes: + name: The unique name of the thermal zone. + center: The center point of the zone. + radius: The radius of the zone in millimeters. + priority: The placement priority for this zone (1 is highest). + max_temp_rise: The maximum allowable temperature rise in degrees Celsius. + power_dissipation: The estimated power dissipation within the zone in milliwatts. + components: A list of components placed within this zone. + """ name: str center: Point radius: float # mm @@ -67,24 +99,54 @@ class K1ThermalZone: components: List[ComponentInfo] = field(default_factory=list) def contains_point(self, point: Point) -> bool: - """Check if point is within thermal zone""" + """Checks if a given point is inside the thermal zone. + + Args: + point: The point to check. + + Returns: + True if the point is within the zone's radius, False otherwise. + """ return self.center.distance_to(point) <= self.radius def add_component(self, comp: ComponentInfo) -> None: - """Add component to this thermal zone""" + """Adds a component to the thermal zone. + + Args: + comp: The component to add. + """ self.components.append(comp) comp.thermal_zone = self.name class ComponentPlacement: - """ - Intelligent component placement engine for K1 Lightwave board. - - Implements multi-phase placement algorithm: - - Phase 2A: Fixed components (board edge connectors) - - Phase 2B: Primary components (MCUs, power) - - Phase 2C: Supporting components (passives) - - Phase 2D: Remaining components + """Orchestrates the automated placement of components on a PCB. + + This class implements a multi-phase placement strategy to intelligently + position components based on thermal zones, functional clusters, and + manufacturing constraints. It is designed to handle the placement process + for the K1 Lightwave board, but can be adapted for other designs. + + The placement process is divided into four main phases: + 1. **Fixed Components**: Placement of connectors and other components with + fixed positions. + 2. **Primary Components**: Placement of core components like MCUs and power + modules within their designated thermal zones. + 3. **Supporting Components**: Placement of passives and other components + that support the primary components. + 4. **Remaining Components**: Placement of all other components in the + remaining available space. + + Attributes: + board_path (Path): The file path to the input KiCad PCB file. + output_path (Path): The destination file path for the modified PCB file. + pcbnew_available (bool): A flag indicating if the `pcbnew` API is available. + board (pcbnew.BOARD): The `pcbnew` board object. + components (Dict[str, ComponentInfo]): A dictionary of all components on the board. + thermal_zones (List[K1ThermalZone]): A list of defined thermal zones. + clusters (Dict[str, List[ComponentInfo]]): A dictionary of component functional clusters. + placement_results (Dict[str, Point]): A dictionary of final component placements. + violations (List[str]): A list of any placement violations found. """ # K1 Board dimensions (mm) @@ -97,12 +159,12 @@ class ComponentPlacement: MIN_EDGE_DISTANCE = 2.0 # mm from board edge def __init__(self, board_path: str, output_path: Optional[str] = None): - """ - Initialize component placement engine. + """Initializes the ComponentPlacement engine. Args: - board_path: Path to KiCad .kicad_pcb file - output_path: Optional output path for modified board + board_path: The file path to the KiCad .kicad_pcb file. + output_path: The optional destination path for the modified board file. + If not provided, the original board file will be overwritten. """ self.board_path = Path(board_path) if output_path: @@ -141,7 +203,13 @@ def __init__(self, board_path: str, output_path: Optional[str] = None): self._load_components() def _load_components(self) -> None: - """Load all components from board""" + """Loads all components from the KiCad board file. + + This method populates the `self.components` dictionary with `ComponentInfo` + objects for each component found on the board. If the `pcbnew` API is not + available, it falls back to creating a dummy set of components for + simulation purposes. + """ if not self.pcbnew_available or self.board is None: # Fallback: Create dummy components for simulation logging.info("Generating simulated component inventory (52 components)") @@ -192,11 +260,14 @@ def _load_components(self) -> None: self.components[ref] = comp def define_thermal_zones(self) -> List[K1ThermalZone]: - """ - Define K1 Lightwave thermal zones. + """Defines the thermal zones for the K1 Lightwave board. + + This method creates a list of `K1ThermalZone` objects, each representing + a specific area on the PCB where heat-generating components should be + placed. Returns: - List of thermal zones with priorities + A list of `K1ThermalZone` objects. """ self.thermal_zones = [ K1ThermalZone( @@ -236,11 +307,14 @@ def define_thermal_zones(self) -> List[K1ThermalZone]: return self.thermal_zones def cluster_components(self) -> Dict[str, List[ComponentInfo]]: - """ - Group components by functional clusters. + """Groups components into functional clusters. + + This method categorizes components based on their function (e.g., power, + USB interface, decoupling) to facilitate logical placement. Returns: - Dictionary of cluster names to component lists + A dictionary where keys are cluster names and values are lists of + `ComponentInfo` objects belonging to that cluster. """ self.clusters = { 'power': [], @@ -307,11 +381,15 @@ def cluster_components(self) -> Dict[str, List[ComponentInfo]]: return self.clusters def place_fixed_components(self) -> Dict[str, Point]: - """ - Phase 2A: Place fixed components at board edges. + """Places components with fixed positions, such as connectors. + + This is the first phase of the placement process, where components + that must be in specific locations (e.g., along the board edges) + are positioned. Returns: - Dictionary of reference to position + A dictionary of the placed components, where keys are component + references and values are their `Point` positions. """ placements = {} @@ -370,11 +448,15 @@ def place_fixed_components(self) -> Dict[str, Point]: return placements def place_primary_components(self) -> Dict[str, Point]: - """ - Phase 2B: Place primary components in thermal zones. + """Places the primary components within their designated thermal zones. + + This is the second phase of placement, focusing on positioning the main + integrated circuits (e.g., MCUs, power management ICs) in thermally + managed areas. Returns: - Dictionary of reference to position + A dictionary of the placed components, where keys are component + references and values are their `Point` positions. """ placements = {} @@ -452,11 +534,15 @@ def place_primary_components(self) -> Dict[str, Point]: return placements def place_supporting_components(self) -> Dict[str, Point]: - """ - Phase 2C: Place supporting components along signal paths. + """Places supporting components, such as passives, along signal paths. + + This third phase of placement positions components like resistors, + capacitors, and diodes in close proximity to the primary components + they support. Returns: - Dictionary of reference to position + A dictionary of the placed components, where keys are component + references and values are their `Point` positions. """ placements = {} @@ -511,11 +597,14 @@ def place_supporting_components(self) -> Dict[str, Point]: return placements def place_remaining_components(self) -> Dict[str, Point]: - """ - Phase 2D: Place remaining components in available space. + """Places all remaining components in the available board space. + + This final placement phase uses a grid-based approach to position any + components that have not yet been placed. Returns: - Dictionary of reference to position + A dictionary of the placed components, where keys are component + references and values are their `Point` positions. """ placements = {} @@ -548,11 +637,14 @@ def place_remaining_components(self) -> Dict[str, Point]: return placements def verify_spacing(self) -> Tuple[bool, List[str]]: - """ - Validate all spacing constraints. + """Validates that all components meet the required spacing constraints. + + This method checks for minimum spacing between components and from the + board edge to ensure manufacturability. Returns: - Tuple of (is_valid, list of violations) + A tuple containing a boolean indicating if all spacing constraints + are met, and a list of violation messages. """ violations = [] @@ -589,11 +681,15 @@ def verify_spacing(self) -> Tuple[bool, List[str]]: return len(violations) == 0, violations def optimize_routing_accessibility(self) -> Dict[str, float]: - """ - Calculate routing accessibility score for each component. + """Calculates a routing accessibility score for each component. + + This score is determined by factors such as the component's distance + from the board edge and the density of nearby components. A higher score + indicates that the component is easier to route to. Returns: - Dictionary of reference to accessibility score (0-1) + A dictionary where keys are component references and values are their + routing accessibility scores (from 0 to 1). """ scores = {} @@ -628,11 +724,13 @@ def optimize_routing_accessibility(self) -> Dict[str, float]: return scores def generate_placement_report(self) -> str: - """ - Generate comprehensive placement report. + """Generates a comprehensive report of the component placement results. + + The report includes information about thermal zones, component clusters, + spacing validation, and routing accessibility. Returns: - Multi-line report string + A formatted string containing the placement report. """ lines = [] lines.append("=" * 80) @@ -715,11 +813,13 @@ def generate_placement_report(self) -> str: return '\n'.join(lines) def generate_ascii_visualization(self) -> str: - """ - Generate ASCII art visualization of component placement. + """Generates an ASCII art visualization of the component placement. + + This method provides a simple, text-based view of the board layout, + showing the positions of components and thermal zones. Returns: - ASCII diagram string + A string containing the ASCII visualization. """ # Create grid (2mm per character) scale = 2.0 # mm per character @@ -791,7 +891,12 @@ def generate_ascii_visualization(self) -> str: return '\n'.join(lines + legend) def apply_placement_to_board(self) -> None: - """Apply calculated placements to KiCad board""" + """Applies the calculated component placements to the KiCad board file. + + This method updates the positions and rotations of all components in the + `pcbnew` board object based on the results of the placement algorithm. + It requires the `pcbnew` API to be available. + """ if not self.pcbnew_available or self.board is None: logging.info("Skipping board update (pcbnew not available)") return @@ -813,11 +918,14 @@ def apply_placement_to_board(self) -> None: ) def execute(self) -> bool: - """ - Execute full Phase 2 placement pipeline. + """Executes the full component placement pipeline. + + This method runs all the placement phases in sequence, from defining + thermal zones to placing all components and verifying the results. Returns: - True if successful, False otherwise + True if the placement is successful and passes all verification + checks, False otherwise. """ try: print("Starting Phase 2: Component Placement") diff --git a/design_preparation.py b/design_preparation.py index 1d22521..3390d5e 100644 --- a/design_preparation.py +++ b/design_preparation.py @@ -42,8 +42,21 @@ class FootprintAssignment: class DesignPreparation: """ - Phase 1: Design Preparation - Load netlist, assign footprints, validate design + Handles the initial design preparation phase for a PCB project. + + This class automates loading a KiCad netlist into a PCB file, assigning + component footprints based on predefined rules, documenting necessary IC + replacements, validating net connectivity, and running Electrical Rule + Checks (ERC). It is designed to prepare a raw schematic and netlist for + the component placement phase. + + Attributes: + netlist_path (Path): The file path to the KiCad netlist (.net). + board_path (Path): The file path to the KiCad PCB file (.kicad_pcb). + output_path (Path): The destination file path for the modified PCB file. + kicad_cli (str): The command-line executable for KiCad. + logger (logging.Logger): A logger for recording operational messages. + results (Dict[str, any]): A dictionary to store the outcomes of the preparation steps. """ # Footprint mapping rules for K1 Lightwave @@ -158,14 +171,14 @@ def __init__( output_path: Optional[Path] = None, kicad_cli: str = '/opt/homebrew/bin/kicad-cli' ): - """ - Initialize Design Preparation + """Initializes the DesignPreparation class. Args: - netlist_path: Path to KiCad netlist (.net) - board_path: Path to KiCad PCB file (.kicad_pcb) - output_path: Optional output path (defaults to board_path) - kicad_cli: Path to kicad-cli executable + netlist_path: The file path to the KiCad netlist (.net). + board_path: The file path to the KiCad PCB file (.kicad_pcb). + output_path: The optional destination path for the modified PCB file. + If not provided, the original board file will be overwritten. + kicad_cli: The path to the KiCad command-line interface executable. """ self.netlist_path = Path(netlist_path) self.board_path = Path(board_path) @@ -196,14 +209,15 @@ def __init__( } def load_netlist(self) -> bool: - """ - Import netlist into KiCad board + """Imports a netlist into a KiCad board file. - Parses netlist, applies footprint rules, and populates board with footprints - using KiCad Python API (pcbnew) + This method reads component information from the netlist file, matches + each component to a predefined footprint, and adds the footprints to the + KiCad board file. It requires the `pcbnew` Python API to be available. Returns: - True if successful + True if the netlist is loaded and at least one component is added + successfully, False otherwise. """ self.logger.info(f"Loading netlist: {self.netlist_path}") @@ -337,14 +351,15 @@ def load_netlist(self) -> bool: return False def assign_footprints(self) -> Dict[str, str]: - """ - Assign missing footprints to Device library components + """Assigns footprints to components that are missing them. - Reads netlist, matches components by reference pattern, - generates footprint assignments. + This method reads the netlist to identify components without an assigned + footprint. It then attempts to match these components against a set of + predefined rules to determine the correct footprint. Returns: - Dictionary of {component_ref: footprint} + A dictionary where keys are component references and values are the + assigned footprint names. """ self.logger.info("Analyzing footprint assignments...") @@ -407,11 +422,15 @@ def assign_footprints(self) -> Dict[str, str]: return {} def replace_ic_placeholders(self) -> Dict[str, Dict]: - """ - Document IC replacements needed + """Documents the necessary integrated circuit (IC) replacements. + + This method logs the ICs that are currently using placeholder symbols + in the schematic and need to be replaced with the correct components. + It serves as a reminder for manual corrections in the schematic. Returns: - Dictionary of IC replacement specifications + A dictionary detailing the required IC replacements, including the + current placeholder, the target component, and the correct footprint. """ self.logger.info("Analyzing IC placeholder replacements...") @@ -435,16 +454,14 @@ def replace_ic_placeholders(self) -> Dict[str, Dict]: return self.IC_REPLACEMENTS def validate_nets(self) -> Tuple[bool, str]: - """ - Validate all nets are properly connected + """Validates the electrical connectivity of nets in the design. - Checks for: - - Floating pins - - Unconnected nets - - Net integrity + This method checks for common net-related issues, such as floating pins + and unconnected nets, by analyzing the netlist file. Returns: - (success, message) + A tuple containing a boolean indicating if the nets are valid and a + string message summarizing the validation results. """ self.logger.info("Validating net connectivity...") @@ -502,17 +519,15 @@ def validate_nets(self) -> Tuple[bool, str]: return False, error def run_erc(self) -> Tuple[bool, str]: - """ - Run Electrical Rule Check - - Uses: kicad-cli erc + """Runs the Electrical Rule Check (ERC) on the schematic. - Acceptance criteria: - - Errors: 0 (must pass) - - Warnings: <= 100 (acceptable for placeholders) + This method uses the `kicad-cli` tool to perform an ERC on the schematic + file associated with the netlist. It checks for electrical errors and + warnings based on KiCad's rules. Returns: - (success, message) + A tuple containing a boolean indicating if the ERC passed and a + string message summarizing the results. """ self.logger.info("Running Electrical Rule Check (ERC)...") @@ -606,11 +621,14 @@ def run_erc(self) -> Tuple[bool, str]: return False, error def execute(self) -> bool: - """ - Run full Phase 1 pipeline + """Executes the full design preparation pipeline. + + This method runs all the steps in the design preparation phase in the + correct order: loading the netlist, assigning footprints, documenting + IC replacements, validating nets, and running the ERC. Returns: - True if all steps successful + True if all steps in the pipeline complete successfully, False otherwise. """ self.logger.info("="*60) self.logger.info("PHASE 1: DESIGN PREPARATION") @@ -682,11 +700,10 @@ def execute(self) -> bool: return overall_success def save_report(self, report_path: Path) -> None: - """ - Save detailed report to JSON + """Saves a detailed report of the design preparation results to a JSON file. Args: - report_path: Path to save report + report_path: The path to the file where the report will be saved. """ with open(report_path, 'w') as f: json.dump(self.results, f, indent=2) diff --git a/design_validation.py b/design_validation.py index 56bb991..f98fd87 100644 --- a/design_validation.py +++ b/design_validation.py @@ -42,7 +42,15 @@ class ValidationSeverity(Enum): @dataclass class ValidationResult: - """Result of a validation check""" + """Represents the outcome of a single validation check. + + Attributes: + check_name: The name of the validation check performed. + severity: The severity level of the result (e.g., PASS, WARNING, ERROR). + passed: A boolean indicating whether the check passed. + message: A human-readable summary of the validation result. + details: A dictionary containing detailed information about the check. + """ check_name: str severity: ValidationSeverity passed: bool @@ -56,7 +64,22 @@ def __str__(self) -> str: @dataclass class DRCRules: - """Design Rule Check constraints (JLCPCB standard 4-layer)""" + """Defines the Design Rule Check (DRC) constraints for the PCB. + + This class centralizes the DRC rules, which are based on the JLCPCB + standard for 4-layer boards. All dimensions are in millimeters unless + otherwise specified. + + Attributes: + trace_width_min: The minimum allowed trace width. + trace_width_max: The maximum allowed trace width. + trace_spacing_min: The minimum allowed spacing between traces. + pad_to_trace_min: The minimum allowed spacing from a pad to a trace. + via_drill_min: The minimum allowed via drill diameter. + via_pad_size_min: The minimum allowed via pad diameter. + annular_ring_min: The minimum allowed annular ring width. + copper_to_edge_min: The minimum allowed distance from copper to the board edge. + """ # All dimensions in mm unless specified trace_width_min: float = 0.1016 # 4 mil trace_width_max: float = 2.54 # 100 mil @@ -68,7 +91,11 @@ class DRCRules: copper_to_edge_min: float = 0.3 # mm (JLCPCB safety margin) def to_dict(self) -> dict: - """Convert rules to dictionary""" + """Converts the DRC rules to a dictionary. + + Returns: + A dictionary representation of the DRC rules. + """ return { 'trace_width': ('min', 4, 'mil', f'{self.trace_width_min}mm'), 'trace_width_max': ('max', 100, 'mil', f'{self.trace_width_max}mm'), @@ -82,13 +109,23 @@ def to_dict(self) -> dict: class DRCValidator: - """Design Rule Check validation using KiCad DRC engine""" + """Performs Design Rule Checks (DRC) using the KiCad DRC engine. + + This class provides methods to execute KiCad's DRC tool, parse the results, + and verify the design against a set of predefined electrical and physical + constraints. + + Attributes: + board_path (Path): The file path to the KiCad PCB file. + board (pcbnew.BOARD): The `pcbnew` board object. + rules (DRCRules): The set of DRC rules to be applied. + """ def __init__(self, board_path: Path): - """Initialize DRC validator + """Initializes the DRCValidator. Args: - board_path: Path to .kicad_pcb file + board_path: The file path to the .kicad_pcb file to be validated. """ self.board_path = Path(board_path) if not self.board_path.exists(): @@ -98,10 +135,15 @@ def __init__(self, board_path: Path): self.rules = DRCRules() def run_kicad_drc(self) -> tuple[int, str]: - """Execute KiCad DRC command line tool + """Executes the KiCad DRC command-line tool. + + This method attempts to use the `kicad-cli` tool to perform a DRC and + generate a JSON report. If `kicad-cli` is not available, it falls back + to a simpler Python-based DRC. Returns: - Tuple of (error_count, report_text) + A tuple containing the number of DRC errors found and the full + text of the DRC report. """ try: # Use kicad-cli for DRC if available (KiCad 7+) @@ -136,10 +178,14 @@ def run_kicad_drc(self) -> tuple[int, str]: return -1, f"DRC execution failed: {str(e)}" def _run_python_drc(self) -> tuple[int, str]: - """Fallback DRC using Python API + """Performs a fallback DRC using the `pcbnew` Python API. + + This method provides a basic DRC implementation for environments where + `kicad-cli` is not available. Returns: - Tuple of (error_count, report_text) + A tuple containing the number of DRC violations found and a + string report of the violations. """ violations = [] @@ -165,10 +211,13 @@ def _run_python_drc(self) -> tuple[int, str]: return len(violations), report def verify_constraints(self) -> ValidationResult: - """Check custom K1 Lightwave constraints + """Verifies the board against the defined DRC constraints. + + This method runs the DRC and returns a `ValidationResult` object + summarizing the outcome. Returns: - ValidationResult with constraint check status + A `ValidationResult` object with the status of the constraint check. """ error_count, report = self.run_kicad_drc() @@ -198,22 +247,31 @@ def verify_constraints(self) -> ValidationResult: class DFMValidator: - """Design for Manufacturing validation (JLCPCB specific)""" + """Performs Design for Manufacturing (DFM) validation, specific to JLCPCB. + + This class checks for common manufacturability issues, such as incorrect + layer stackup, component spacing, and edge clearances, to ensure the design + is suitable for production at JLCPCB. + + Attributes: + board_path (Path): The file path to the KiCad PCB file. + board (pcbnew.BOARD): The `pcbnew` board object. + """ def __init__(self, board_path: Path): - """Initialize DFM validator + """Initializes the DFMValidator. Args: - board_path: Path to .kicad_pcb file + board_path: The file path to the .kicad_pcb file to be validated. """ self.board_path = Path(board_path) self.board = pcbnew.LoadBoard(str(self.board_path)) def validate_layer_stack(self) -> ValidationResult: - """Verify 4-layer configuration for JLCPCB + """Verifies that the board has the correct 4-layer stackup for JLCPCB. Returns: - ValidationResult for layer stack validation + A `ValidationResult` indicating if the layer stack is correct. """ # Get layer count layer_count = self.board.GetCopperLayerCount() @@ -230,10 +288,10 @@ def validate_layer_stack(self) -> ValidationResult: ) def validate_assembly(self) -> ValidationResult: - """Check assembly constraints (component spacing, clearances) + """Checks for assembly constraints, such as component spacing. Returns: - ValidationResult for assembly validation + A `ValidationResult` indicating if assembly constraints are met. """ violations = [] @@ -265,10 +323,10 @@ def validate_assembly(self) -> ValidationResult: ) def validate_manufacturing(self) -> ValidationResult: - """Check fabrication constraints (copper to edge, isolated copper, etc) + """Checks for fabrication constraints, like copper-to-edge clearance. Returns: - ValidationResult for manufacturing validation + A `ValidationResult` indicating if fabrication constraints are met. """ violations = [] @@ -309,10 +367,10 @@ def validate_manufacturing(self) -> ValidationResult: ) def validate_fiducials(self) -> ValidationResult: - """Verify 3 fiducials with diagonal placement + """Verifies that at least three fiducial markers are present. Returns: - ValidationResult for fiducial validation + A `ValidationResult` indicating if the fiducial requirement is met. """ fiducials = [] @@ -347,25 +405,34 @@ def validate_fiducials(self) -> ValidationResult: class SignalIntegrityValidator: - """High-speed signal routing validation""" + """Performs validation of high-speed signal routing. + + This class checks for common signal integrity issues in high-speed interfaces + like SPI, USB, I2C, and I2S, ensuring that the routing meets the required + specifications for reliable operation. + + Attributes: + board_path (Path): The file path to the KiCad PCB file. + board (pcbnew.BOARD): The `pcbnew` board object. + """ def __init__(self, board_path: Path): - """Initialize signal integrity validator + """Initializes the SignalIntegrityValidator. Args: - board_path: Path to .kicad_pcb file + board_path: The file path to the .kicad_pcb file to be validated. """ self.board_path = Path(board_path) self.board = pcbnew.LoadBoard(str(self.board_path)) def _find_net_by_name(self, net_name_pattern: str) -> list[pcbnew.NETINFO_ITEM]: - """Find nets matching pattern + """Finds all nets that match a given name pattern. Args: - net_name_pattern: Net name or pattern to search for + net_name_pattern: The pattern to search for in the net names. Returns: - List of matching nets + A list of `pcbnew.NETINFO_ITEM` objects for the matching nets. """ matching_nets = [] for net_code in range(self.board.GetNetCount()): @@ -375,13 +442,13 @@ def _find_net_by_name(self, net_name_pattern: str) -> list[pcbnew.NETINFO_ITEM]: return matching_nets def _get_track_length_mm(self, net: pcbnew.NETINFO_ITEM) -> float: - """Calculate total track length for a net + """Calculates the total length of all tracks belonging to a specific net. Args: - net: Net to measure + net: The `pcbnew.NETINFO_ITEM` object for the net. Returns: - Total track length in mm + The total length of the net's tracks in millimeters. """ total_length = 0.0 for track in self.board.GetTracks(): @@ -390,10 +457,13 @@ def _get_track_length_mm(self, net: pcbnew.NETINFO_ITEM) -> float: return total_length def validate_spi_routing(self) -> ValidationResult: - """Check 40 MHz SPI traces (SCK, MOSI, MISO) + """Validates the routing of the 40 MHz SPI traces. + + This method checks for the presence of the main SPI signals (SCK, MOSI, MISO) + and provides recommendations for ensuring signal integrity. Returns: - ValidationResult for SPI routing validation + A `ValidationResult` for the SPI routing validation. """ spi_nets = ['SCK', 'MOSI', 'MISO', 'SPI_SCK', 'SPI_MOSI', 'SPI_MISO'] found_nets = {} @@ -436,10 +506,13 @@ def validate_spi_routing(self) -> ValidationResult: ) def validate_usb_routing(self) -> ValidationResult: - """Check USB 2.0 Full-Speed differential pairs + """Validates the routing of the USB 2.0 Full-Speed differential pair. + + This method checks for the presence of the D+ and D- signals and verifies + that their lengths are matched within the required tolerance. Returns: - ValidationResult for USB routing validation + A `ValidationResult` for the USB routing validation. """ usb_dp = self._find_net_by_name('USB_D+') or self._find_net_by_name('D+') usb_dm = self._find_net_by_name('USB_D-') or self._find_net_by_name('D-') @@ -488,10 +561,13 @@ def validate_usb_routing(self) -> ValidationResult: ) def validate_i2c_i2s_routing(self) -> ValidationResult: - """Check I2C/I2S signals (pull-ups, series damping) + """Validates the routing of I2C and I2S signals. + + This method checks for the presence of I2C and I2S signals and provides + recommendations for pull-up resistors and series damping. Returns: - ValidationResult for I2C/I2S routing validation + A `ValidationResult` for the I2C/I2S routing validation. """ i2c_nets = self._find_net_by_name('SDA') + self._find_net_by_name('SCL') i2s_nets = self._find_net_by_name('I2S') @@ -535,7 +611,18 @@ def validate_i2c_i2s_routing(self) -> ValidationResult: @dataclass class ThermalParameters: - """K1 Lightwave thermal parameters""" + """Defines the thermal parameters for the K1 Lightwave board. + + Attributes: + ambient_temp_c: The ambient operating temperature in degrees Celsius. + power_mcu_a_w: The power dissipation of the primary MCU in watts. + power_mcu_b_w: The power dissipation of the secondary MCU in watts. + power_converter_w: The power dissipation of the power converter in watts. + r_thermal_mcu_to_gnd: The thermal resistance from the MCU to the ground plane. + r_thermal_gnd_to_ambient: The thermal resistance from the ground plane to ambient. + thermal_via_benefit_pct: The estimated thermal benefit from thermal vias. + max_junction_temp_c: The maximum allowable junction temperature for the ICs. + """ ambient_temp_c: float = 25.0 power_mcu_a_w: float = 0.3 power_mcu_b_w: float = 0.5 @@ -547,31 +634,47 @@ class ThermalParameters: @property def total_power_w(self) -> float: - """Calculate total power dissipation""" + """Calculates the total power dissipation of the board. + + Returns: + The total power dissipation in watts. + """ return self.power_mcu_a_w + self.power_mcu_b_w + self.power_converter_w class ThermalValidator: - """Thermal analysis and validation""" + """Performs thermal analysis and validation of the PCB design. + + This class estimates the junction temperature of the main components to + ensure they operate within safe limits. It considers factors like power + dissipation, thermal resistance, and the presence of thermal vias. + + Attributes: + board_path (Path): The file path to the KiCad PCB file. + board (pcbnew.BOARD): The `pcbnew` board object. + params (ThermalParameters): The thermal parameters used for the analysis. + """ def __init__(self, board_path: Path, params: ThermalParameters | None = None): - """Initialize thermal validator + """Initializes the ThermalValidator. Args: - board_path: Path to .kicad_pcb file - params: Thermal parameters (uses K1 defaults if None) + board_path: The file path to the .kicad_pcb file. + params: Optional `ThermalParameters` object. If not provided, + defaults for the K1 Lightwave board are used. """ self.board_path = Path(board_path) self.board = pcbnew.LoadBoard(str(self.board_path)) self.params = params or ThermalParameters() def calculate_temperature_rise(self) -> float: - """Estimate maximum junction temperature rise + """Estimates the maximum junction temperature rise of the components. - Formula: T_rise = P_total × (R_thermal_mcu + R_thermal_gnd) × (1 - via_benefit) + This calculation is based on the total power dissipation, thermal + resistance, and the estimated benefit from thermal vias. Returns: - Temperature rise in °C + The estimated temperature rise in degrees Celsius. """ r_total = self.params.r_thermal_mcu_to_gnd + self.params.r_thermal_gnd_to_ambient temp_rise_no_vias = self.params.total_power_w * r_total @@ -580,10 +683,10 @@ def calculate_temperature_rise(self) -> float: return temp_rise def calculate_via_effectiveness(self) -> float: - """Estimate thermal via benefit + """Estimates the temperature reduction benefit provided by thermal vias. Returns: - Temperature reduction in °C from thermal vias + The estimated temperature reduction in degrees Celsius. """ r_total = self.params.r_thermal_mcu_to_gnd + self.params.r_thermal_gnd_to_ambient temp_rise_no_vias = self.params.total_power_w * r_total @@ -592,10 +695,13 @@ def calculate_via_effectiveness(self) -> float: return benefit_temp def _count_thermal_vias(self) -> int: - """Count thermal vias on board + """Counts the number of thermal vias on the board. + + This method identifies vias connected to the ground net, which are + assumed to be thermal vias. Returns: - Number of thermal vias detected + The total number of detected thermal vias. """ thermal_via_count = 0 @@ -615,10 +721,13 @@ def _count_thermal_vias(self) -> int: return thermal_via_count def validate_thermal_design(self) -> ValidationResult: - """Check T_junction < 80°C with >10°C margin + """Validates the thermal design of the board. + + This method checks if the estimated junction temperature is below the + maximum allowable limit with a sufficient margin. Returns: - ValidationResult for thermal validation + A `ValidationResult` for the thermal design validation. """ temp_rise = self.calculate_temperature_rise() t_junction = self.params.ambient_temp_c + temp_rise @@ -650,14 +759,31 @@ def validate_thermal_design(self) -> ValidationResult: class DesignValidation: - """Comprehensive PCB design validation suite""" + """Orchestrates the comprehensive design validation process. + + This class runs a suite of validation checks, including DRC, DFM, signal + integrity, and thermal analysis. It also generates a manufacturing + readiness checklist, exports Gerber and drill files, and produces a + detailed validation report. + + Attributes: + board_path (Path): The file path to the KiCad PCB file. + output_dir (Path): The directory for storing validation outputs. + pcbnew_available (bool): A flag indicating if the `pcbnew` API is available. + drc (DRCValidator): The DRC validator instance. + dfm (DFMValidator): The DFM validator instance. + si (SignalIntegrityValidator): The signal integrity validator instance. + thermal (ThermalValidator): The thermal validator instance. + results (list[ValidationResult]): A list of results from all validation checks. + """ def __init__(self, board_path: Path | str, output_dir: Path | str | None = None): - """Initialize validation suite + """Initializes the DesignValidation suite. Args: - board_path: Path to .kicad_pcb file - output_dir: Directory for validation outputs (defaults to board directory) + board_path: The file path to the .kicad_pcb file to be validated. + output_dir: The directory for storing validation outputs. If not + provided, the board's directory is used. """ self.board_path = Path(board_path) if not self.board_path.exists(): @@ -690,10 +816,13 @@ def __init__(self, board_path: Path | str, output_dir: Path | str | None = None) self.results: list[ValidationResult] = [] def run_all_validations(self) -> dict[str, Any]: - """Execute all validation steps + """Executes all validation checks in the suite. + + This method runs DRC, DFM, signal integrity, and thermal validations + and compiles the results into a summary dictionary. Returns: - Dictionary with all validation results + A dictionary containing the aggregated results of all validation checks. """ self.results = [] @@ -790,10 +919,11 @@ def run_all_validations(self) -> dict[str, Any]: } def manufacturing_readiness_check(self) -> tuple[bool, list[str]]: - """Pre-manufacturing checklist validation + """Performs a final check against a manufacturing readiness checklist. Returns: - Tuple of (ready, issues_list) + A tuple containing a boolean indicating if the board is ready for + manufacturing, and a list of any outstanding issues. """ print("\n[5/6] Manufacturing Readiness Checklist...") @@ -840,10 +970,11 @@ def manufacturing_readiness_check(self) -> tuple[bool, list[str]]: return ready, issues def export_manufacturing_files(self) -> dict[str, Any]: - """Generate all Gerber and drill files + """Generates all necessary manufacturing files, including Gerbers and drill files. Returns: - Dictionary with file generation status + A dictionary containing the status of the file generation and a list + of the generated files. """ print("\n[6/6] Exporting Manufacturing Files...") @@ -921,10 +1052,10 @@ def export_manufacturing_files(self) -> dict[str, Any]: } def generate_validation_report(self) -> str: - """Create comprehensive validation report + """Creates a comprehensive, human-readable report of all validation results. Returns: - Formatted report text + A formatted string containing the validation report. """ report_lines = [] report_lines.append("=" * 80) @@ -961,10 +1092,14 @@ def generate_validation_report(self) -> str: return "\n".join(report_lines) def execute(self) -> bool: - """Run full Phase 4 validation pipeline + """Executes the full design validation pipeline. + + This method runs all validation checks, generates manufacturing files, + and produces a final report. Returns: - True if all validations pass, False otherwise + True if all validations pass and all files are generated + successfully, False otherwise. """ # Run all validations summary = self.run_all_validations() diff --git a/elite_pcb_designer.py b/elite_pcb_designer.py index e60258c..4c87e37 100755 --- a/elite_pcb_designer.py +++ b/elite_pcb_designer.py @@ -17,7 +17,7 @@ # Phase status enumerations class PhaseStatus(Enum): - """Phase execution status""" + """Enumeration for the execution status of a design phase.""" PENDING = "pending" IN_PROGRESS = "in_progress" PASSED = "passed" @@ -26,7 +26,16 @@ class PhaseStatus(Enum): @dataclass class PhaseResult: - """Result from a phase execution""" + """Holds the results from a single phase of the PCB design process. + + Attributes: + phase_name: The name of the phase. + status: The execution status of the phase. + duration: The time taken for the phase to complete, in seconds. + component_count: The number of components processed in the phase. + message: A summary message of the phase's outcome. + errors: A list of any errors that occurred during the phase. + """ phase_name: str status: PhaseStatus duration: float @@ -47,12 +56,33 @@ class PhaseResult: @dataclass class ElitePCBConfig: + """Configuration settings for the Elite PCB Designer. + + Attributes: + netlist_path: The file path to the KiCad netlist (.net). + board_path: The file path to the KiCad PCB file (.kicad_pcb). + output_dir: The directory where all output files will be saved. + verbose: A flag for enabling verbose logging. + """ netlist_path: str board_path: str output_dir: str = "k1_design_output" verbose: bool = False def setup_logging(verbose=False, log_file=None): + """Configures the logging for the application. + + This function sets up a logger that can write to both the console and a + log file, with a verbosity level controlled by the `verbose` parameter. + + Args: + verbose: If True, the logging level is set to DEBUG; otherwise, it is + set to INFO. + log_file: The optional path to a file where logs should be saved. + + Returns: + The configured logger instance. + """ level = logging.DEBUG if verbose else logging.INFO formatter = logging.Formatter('[%(asctime)s] %(levelname)s: %(message)s', datefmt='%H:%M:%S') @@ -76,7 +106,33 @@ def setup_logging(verbose=False, log_file=None): return logger class ElitePCBDesigner: + """Orchestrates the entire PCB design process from netlist to manufacturing. + + This class manages the four main phases of the PCB design workflow: + 1. **Design Preparation**: Loads the netlist and prepares the design. + 2. **Component Placement**: Intelligently places components on the board. + 3. **Automated Routing**: Routes the electrical connections. + 4. **Design Validation**: Verifies the design and generates manufacturing files. + + It can operate in a real mode, using the actual design modules, or in a + simulation mode if the modules are not available. + + Attributes: + config (ElitePCBConfig): The configuration settings for the design process. + logger (logging.Logger): A logger for recording operational messages. + start_time (float): The timestamp when the process started. + end_time (float): The timestamp when the process finished. + all_results (dict): A dictionary to store detailed results from each phase. + phase_results (dict): A dictionary to track the success or failure of each phase. + overall_success (bool): A flag indicating the overall success of the process. + """ def __init__(self, config): + """Initializes the ElitePCBDesigner. + + Args: + config: An `ElitePCBConfig` object containing the configuration + settings for the design process. + """ self.config = config os.makedirs(config.output_dir, exist_ok=True) @@ -89,6 +145,14 @@ def __init__(self, config): self.overall_success = True # Track if all phases passed def execute(self): + """Executes the full PCB design pipeline. + + This method runs each of the four design phases in sequence, records + the results, and generates a final summary and manufacturing files. + + Returns: + True if all phases complete successfully, False otherwise. + """ self.logger.info("\n") self.logger.info("╔════════════════════════════════════════════════════════════╗") self.logger.info("║ ELITE PCB DESIGNER AGENT - K1 LIGHTWAVE ║") @@ -134,7 +198,14 @@ def execute(self): return self.overall_success def _execute_phase_1(self): - """Phase 1: Design Preparation""" + """Executes the design preparation phase. + + This method runs the `DesignPreparation` module to load the netlist, + assign footprints, and perform initial design validation. + + Returns: + True if the phase completes successfully, False otherwise. + """ self.logger.info("=" * 60) self.logger.info("PHASE 1: DESIGN PREPARATION") self.logger.info("=" * 60) @@ -195,7 +266,14 @@ def _execute_phase_1(self): return False def _execute_phase_2(self): - """Phase 2: Component Placement""" + """Executes the component placement phase. + + This method runs the `ComponentPlacement` module to intelligently + position components on the PCB. + + Returns: + True if the phase completes successfully, False otherwise. + """ self.logger.info("=" * 60) self.logger.info("PHASE 2: COMPONENT PLACEMENT") self.logger.info("=" * 60) @@ -252,7 +330,14 @@ def _execute_phase_2(self): return False def _execute_phase_3(self): - """Phase 3: Automated Routing""" + """Executes the automated routing phase. + + This method runs the `AutomatedRouting` module to route the electrical + connections between components. + + Returns: + True if the phase completes successfully, False otherwise. + """ self.logger.info("=" * 60) self.logger.info("PHASE 3: AUTOMATED ROUTING") self.logger.info("=" * 60) @@ -307,7 +392,14 @@ def _execute_phase_3(self): return False def _execute_phase_4(self): - """Phase 4: Design Validation""" + """Executes the design validation phase. + + This method runs the `DesignValidation` module to perform final checks + on the design and generate manufacturing files. + + Returns: + True if the phase completes successfully, False otherwise. + """ self.logger.info("=" * 60) self.logger.info("PHASE 4: DESIGN VALIDATION") self.logger.info("=" * 60) @@ -364,7 +456,12 @@ def _execute_phase_4(self): return False def _save_results(self): - """Save all results to JSON""" + """Saves a master report and all manufacturing files. + + This method generates a comprehensive JSON report of the entire design + process and creates a set of mock manufacturing files, including + Gerbers, a drill file, and a Bill of Materials (BOM). + """ elapsed = self.end_time - self.start_time report = { @@ -441,6 +538,7 @@ def _save_results(self): self.logger.info(f"✓ Manufacturing files: {mfg_dir}/ (8 Gerber + 1 drill + 1 BOM)") def _print_summary(self): + """Prints a summary of the design process to the console.""" elapsed = self.end_time - self.start_time self.logger.info("=" * 60) @@ -499,6 +597,12 @@ def _print_summary(self): self.logger.info("\n") def _count_components(self): + """Counts the number of components in the netlist file. + + Returns: + The number of components found, or a default value of 52 if the + file cannot be read. + """ try: with open(self.config.netlist_path, 'r') as f: return f.read().count('(comp') diff --git a/hardware/k1-lightwave/skidl/__pycache__/k1_motherboard_revA.cpython-312.pyc b/hardware/k1-lightwave/skidl/__pycache__/k1_motherboard_revA.cpython-312.pyc deleted file mode 100644 index 185c3a0..0000000 Binary files a/hardware/k1-lightwave/skidl/__pycache__/k1_motherboard_revA.cpython-312.pyc and /dev/null differ diff --git a/mcp/mcp-rag/__pycache__/server.cpython-312.pyc b/mcp/mcp-rag/__pycache__/server.cpython-312.pyc deleted file mode 100644 index f2caf16..0000000 Binary files a/mcp/mcp-rag/__pycache__/server.cpython-312.pyc and /dev/null differ diff --git a/test_automated_routing.py b/test_automated_routing.py index 9034ae8..b026056 100644 --- a/test_automated_routing.py +++ b/test_automated_routing.py @@ -38,10 +38,10 @@ class TestTraceSpecification(unittest.TestCase): - """Test TraceSpecification dataclass""" + """Tests for the TraceSpecification dataclass.""" def test_trace_spec_creation(self): - """Test creating a trace specification""" + """Verify that a TraceSpecification object is created with the correct attributes.""" spec = TraceSpecification( net_name="VCC", net_type=NetType.POWER, @@ -59,7 +59,7 @@ def test_trace_spec_creation(self): self.assertEqual(spec.clearance_um, 200) def test_differential_pair_spec(self): - """Test differential pair specification""" + """Verify that a TraceSpecification for a differential pair is created correctly.""" spec = TraceSpecification( net_name="USB_D+", net_type=NetType.DIFFERENTIAL, @@ -78,10 +78,10 @@ def test_differential_pair_spec(self): class TestViaSpecification(unittest.TestCase): - """Test ViaSpecification dataclass""" + """Tests for the ViaSpecification dataclass.""" def test_standard_via(self): - """Test standard via specification""" + """Verify that a standard ViaSpecification object is created correctly.""" via = ViaSpecification( diameter_mm=0.6, drill_mm=0.3, @@ -96,7 +96,7 @@ def test_standard_via(self): self.assertFalse(via.thermal_relief) def test_thermal_via(self): - """Test thermal via specification""" + """Verify that a thermal ViaSpecification object is created correctly.""" via = ViaSpecification( diameter_mm=0.3, drill_mm=0.15, @@ -109,10 +109,10 @@ def test_thermal_via(self): class TestCopperZone(unittest.TestCase): - """Test CopperZone dataclass""" + """Tests for the CopperZone dataclass.""" def test_copper_zone_creation(self): - """Test creating a copper zone""" + """Verify that a CopperZone object is created with the correct attributes.""" zone = CopperZone( name="GND_L2", net_name="GND", @@ -133,10 +133,10 @@ def test_copper_zone_creation(self): class TestRoutingResult(unittest.TestCase): - """Test RoutingResult dataclass""" + """Tests for the RoutingResult dataclass.""" def test_routing_result_success(self): - """Test successful routing result""" + """Verify that a successful RoutingResult is correctly evaluated.""" result = RoutingResult( status=RoutingStatus.COMPLETED, nets_routed=50, @@ -150,7 +150,7 @@ def test_routing_result_success(self): self.assertEqual(result.completion_percentage, 100.0) def test_routing_result_partial(self): - """Test partial routing result""" + """Verify that a partially successful RoutingResult is correctly evaluated.""" result = RoutingResult( status=RoutingStatus.COMPLETED, nets_routed=45, @@ -164,7 +164,7 @@ def test_routing_result_partial(self): self.assertEqual(result.completion_percentage, 90.0) def test_routing_result_failed(self): - """Test failed routing result""" + """Verify that a failed RoutingResult is correctly evaluated.""" result = RoutingResult( status=RoutingStatus.FAILED, nets_routed=0, @@ -179,10 +179,10 @@ def test_routing_result_failed(self): class TestK1RoutingConfiguration(unittest.TestCase): - """Test K1 Lightwave routing configuration""" + """Tests for the K1RoutingConfiguration class.""" def test_power_nets(self): - """Test power net specifications""" + """Verify the correctness of the power net specifications.""" power_nets = K1RoutingConfiguration.POWER_NETS self.assertIn("VBUS_USB_5V", power_nets) @@ -196,7 +196,7 @@ def test_power_nets(self): self.assertEqual(led_5v.width_mm, 4.06) def test_spi_nets(self): - """Test SPI net specifications""" + """Verify the correctness of the SPI net specifications.""" spi_nets = K1RoutingConfiguration.SPI_NETS self.assertIn("SPI_SCK_A2B", spi_nets) @@ -208,7 +208,7 @@ def test_spi_nets(self): self.assertEqual(spi_nets[net].series_damping_ohm, 33) def test_usb_nets(self): - """Test USB differential pair specifications""" + """Verify the correctness of the USB differential pair specifications.""" usb_nets = K1RoutingConfiguration.USB_NETS self.assertIn("USB_D+", usb_nets) @@ -222,7 +222,7 @@ def test_usb_nets(self): self.assertEqual(spec.length_match_tolerance_mm, 0.5) def test_copper_zones(self): - """Test copper zone specifications""" + """Verify the correctness of the copper zone specifications.""" zones = K1RoutingConfiguration.COPPER_ZONES self.assertEqual(len(zones), 3) @@ -234,7 +234,7 @@ def test_copper_zones(self): self.assertEqual(gnd_zone.priority, 1) def test_thermal_vias(self): - """Test thermal via array specifications""" + """Verify the correctness of the thermal via array specifications.""" thermal_vias = K1RoutingConfiguration.THERMAL_VIAS self.assertEqual(len(thermal_vias), 3) @@ -246,7 +246,7 @@ def test_thermal_vias(self): self.assertEqual(mcu_a.grid_spacing_mm, 1.27) def test_get_all_critical_nets(self): - """Test getting all critical nets""" + """Verify that all critical nets are correctly aggregated.""" all_nets = K1RoutingConfiguration.get_all_critical_nets() self.assertGreater(len(all_nets), 0) @@ -257,21 +257,21 @@ def test_get_all_critical_nets(self): class TestCriticalNetRouter(unittest.TestCase): - """Test CriticalNetRouter class""" + """Tests for the CriticalNetRouter class.""" def setUp(self): - """Set up test fixtures""" + """Set up a temporary directory and a dummy board file for testing.""" self.temp_dir = tempfile.mkdtemp() self.board_path = Path(self.temp_dir) / "test_board.kicad_pcb" self.board_path.touch() def tearDown(self): - """Clean up test fixtures""" + """Remove the temporary directory and its contents after testing.""" shutil.rmtree(self.temp_dir) @patch('automated_routing.pcbnew') def test_load_board(self, mock_pcbnew): - """Test loading KiCad board""" + """Verify that the KiCad board is loaded correctly.""" mock_board = Mock() mock_pcbnew.LoadBoard.return_value = mock_board @@ -282,7 +282,7 @@ def test_load_board(self, mock_pcbnew): self.assertEqual(router.board, mock_board) def test_route_power_nets(self): - """Test routing power nets""" + """Verify that power nets are routed as expected.""" router = CriticalNetRouter(str(self.board_path)) router.board = Mock() @@ -294,7 +294,7 @@ def test_route_power_nets(self): self.assertIn("LED_5V", results["nets_routed"]) def test_route_spi_signals(self): - """Test routing SPI signals""" + """Verify that SPI signals are routed as expected.""" router = CriticalNetRouter(str(self.board_path)) router.board = Mock() @@ -305,7 +305,7 @@ def test_route_spi_signals(self): self.assertIn("SPI_MOSI_A2B", results["nets_routed"]) def test_route_usb_signals(self): - """Test routing USB differential pair""" + """Verify that USB differential pairs are routed as expected.""" router = CriticalNetRouter(str(self.board_path)) router.board = Mock() @@ -316,7 +316,7 @@ def test_route_usb_signals(self): self.assertIn("USB_D-", results["nets_routed"]) def test_route_i2c_i2s(self): - """Test routing I2C and I2S signals""" + """Verify that I2C and I2S signals are routed as expected.""" router = CriticalNetRouter(str(self.board_path)) router.board = Mock() @@ -328,27 +328,27 @@ def test_route_i2c_i2s(self): class TestFreeRoutingIntegration(unittest.TestCase): - """Test FreeRoutingIntegration class""" + """Tests for the FreeRoutingIntegration class.""" def setUp(self): - """Set up test fixtures""" + """Set up a temporary directory and a dummy board file for testing.""" self.temp_dir = tempfile.mkdtemp() self.board_path = Path(self.temp_dir) / "test_board.kicad_pcb" self.board_path.touch() def tearDown(self): - """Clean up test fixtures""" + """Remove the temporary directory and its contents after testing.""" shutil.rmtree(self.temp_dir) def test_initialization(self): - """Test FreeRouting integration initialization""" + """Verify that the FreeRoutingIntegration class is initialized correctly.""" integration = FreeRoutingIntegration(str(self.board_path)) self.assertEqual(integration.board_path, self.board_path) self.assertTrue(integration.work_dir.exists()) def test_find_freerouting_jar_custom(self): - """Test finding custom FreeRouting JAR""" + """Verify that a custom FreeRouting JAR path is correctly identified.""" jar_path = Path(self.temp_dir) / "freerouting.jar" jar_path.touch() @@ -362,7 +362,7 @@ def test_find_freerouting_jar_custom(self): @patch('automated_routing.DSN') def test_export_to_dsn(self, mock_dsn_module): - """Test DSN export""" + """Verify that the board is correctly exported to a DSN file.""" mock_db = Mock() mock_dsn_module.SPECCTRA_DB.return_value = mock_db @@ -378,7 +378,7 @@ def test_export_to_dsn(self, mock_dsn_module): self.assertTrue(success) def test_configure_freerouting(self): - """Test FreeRouting configuration""" + """Verify that the FreeRouting configuration is generated correctly.""" integration = FreeRoutingIntegration(str(self.board_path)) config = integration.configure_freerouting() @@ -389,7 +389,7 @@ def test_configure_freerouting(self): @patch('automated_routing.subprocess.run') def test_run_freerouting_success(self, mock_run): - """Test successful FreeRouting execution""" + """Verify that the FreeRouting process is executed successfully.""" # Mock successful subprocess mock_result = Mock() mock_result.returncode = 0 @@ -420,7 +420,7 @@ def test_run_freerouting_success(self, mock_run): mock_run.assert_called_once() def test_verify_routing(self): - """Test routing verification""" + """Verify that the routing verification method runs without errors.""" integration = FreeRoutingIntegration(str(self.board_path)) success, message = integration.verify_routing() @@ -430,20 +430,20 @@ def test_verify_routing(self): class TestAutomatedRouting(unittest.TestCase): - """Test AutomatedRouting orchestrator""" + """Tests for the AutomatedRouting orchestrator class.""" def setUp(self): - """Set up test fixtures""" + """Set up a temporary directory and a dummy board file for testing.""" self.temp_dir = tempfile.mkdtemp() self.board_path = Path(self.temp_dir) / "test_board.kicad_pcb" self.board_path.touch() def tearDown(self): - """Clean up test fixtures""" + """Remove the temporary directory and its contents after testing.""" shutil.rmtree(self.temp_dir) def test_initialization(self): - """Test AutomatedRouting initialization""" + """Verify that the AutomatedRouting class is initialized correctly.""" router = AutomatedRouting(str(self.board_path)) self.assertEqual(router.board_path, self.board_path) @@ -457,7 +457,7 @@ def test_initialization(self): @patch.object(CriticalNetRouter, 'route_usb_signals') @patch.object(CriticalNetRouter, 'route_i2c_i2s') def test_route_critical_nets(self, mock_i2c, mock_usb, mock_spi, mock_power, mock_load): - """Test critical net routing orchestration""" + """Verify that the critical net routing orchestration works as expected.""" mock_load.return_value = True mock_power.return_value = {"nets_routed": ["VCC"], "status": "completed"} mock_spi.return_value = {"nets_routed": ["SPI_SCK"], "status": "completed"} @@ -474,7 +474,7 @@ def test_route_critical_nets(self, mock_i2c, mock_usb, mock_spi, mock_power, moc self.assertIn("i2c_i2s", results) def test_create_copper_zones(self): - """Test copper zone creation""" + """Verify that the copper zone creation process runs without errors.""" router = AutomatedRouting(str(self.board_path)) results = router.create_copper_zones() @@ -482,7 +482,7 @@ def test_create_copper_zones(self): self.assertGreater(len(results["zones_created"]), 0) def test_place_thermal_vias(self): - """Test thermal via placement""" + """Verify that the thermal via placement process runs without errors.""" router = AutomatedRouting(str(self.board_path)) results = router.place_thermal_vias() @@ -492,16 +492,16 @@ def test_place_thermal_vias(self): class TestIntegration(unittest.TestCase): - """Integration tests for full routing pipeline""" + """Integration tests for the full automated routing pipeline.""" def setUp(self): - """Set up test fixtures""" + """Set up a temporary directory and a dummy board file for testing.""" self.temp_dir = tempfile.mkdtemp() self.board_path = Path(self.temp_dir) / "k1_test.kicad_pcb" self.board_path.touch() def tearDown(self): - """Clean up test fixtures""" + """Remove the temporary directory and its contents after testing.""" shutil.rmtree(self.temp_dir) @patch.object(CriticalNetRouter, 'load_board') @@ -510,7 +510,7 @@ def tearDown(self): @patch.object(FreeRoutingIntegration, 'import_routing_results') @patch.object(FreeRoutingIntegration, 'verify_routing') def test_full_pipeline_mock(self, mock_verify, mock_import, mock_run, mock_export, mock_load): - """Test full routing pipeline with mocked external dependencies""" + """Verify that the full routing pipeline executes with mocked external dependencies.""" mock_load.return_value = True mock_export.return_value = True mock_run.return_value = True diff --git a/test_component_placement.py b/test_component_placement.py index 911159f..da9ab19 100644 --- a/test_component_placement.py +++ b/test_component_placement.py @@ -17,16 +17,16 @@ class TestPoint(unittest.TestCase): - """Test Point class""" + """Tests for the Point class.""" def test_distance_calculation(self): - """Test distance calculation between points""" + """Verify that the distance between two points is calculated correctly.""" p1 = Point(0, 0) p2 = Point(3, 4) self.assertAlmostEqual(p1.distance_to(p2), 5.0, places=2) def test_point_addition(self): - """Test point addition""" + """Verify that two points can be added together.""" p1 = Point(1, 2) p2 = Point(3, 4) p3 = p1 + p2 @@ -34,7 +34,7 @@ def test_point_addition(self): self.assertEqual(p3.y, 6) def test_point_subtraction(self): - """Test point subtraction""" + """Verify that one point can be subtracted from another.""" p1 = Point(5, 7) p2 = Point(2, 3) p3 = p1 - p2 @@ -43,10 +43,10 @@ def test_point_subtraction(self): class TestK1ThermalZone(unittest.TestCase): - """Test K1ThermalZone class""" + """Tests for the K1ThermalZone class.""" def setUp(self): - """Set up test thermal zone""" + """Set up a test thermal zone for use in the test cases.""" self.zone = K1ThermalZone( name="Test Zone", center=Point(25, 40), @@ -57,7 +57,7 @@ def setUp(self): ) def test_zone_contains_point_inside(self): - """Test point inside zone""" + """Verify that a point inside the zone is correctly identified.""" point = Point(25, 40) # Center self.assertTrue(self.zone.contains_point(point)) @@ -65,17 +65,17 @@ def test_zone_contains_point_inside(self): self.assertTrue(self.zone.contains_point(point)) def test_zone_contains_point_outside(self): - """Test point outside zone""" + """Verify that a point outside the zone is correctly identified.""" point = Point(40, 40) # 15mm from center self.assertFalse(self.zone.contains_point(point)) def test_zone_contains_point_boundary(self): - """Test point on zone boundary""" + """Verify that a point on the zone's boundary is correctly identified.""" point = Point(35, 40) # Exactly 10mm from center self.assertTrue(self.zone.contains_point(point)) def test_add_component(self): - """Test adding component to zone""" + """Verify that a component can be successfully added to the zone.""" comp = ComponentInfo( reference="U1", footprint="Package_DFN_QFN:QFN-56-1EP_7x7mm", @@ -88,23 +88,23 @@ def test_add_component(self): class TestComponentPlacementInitialization(unittest.TestCase): - """Test ComponentPlacement initialization and loading""" + """Tests for the initialization and component loading of the ComponentPlacement class.""" @classmethod def setUpClass(cls): - """Set up test board path""" + """Set up the path to the test board file.""" cls.board_path = Path("hardware/k1-lightwave/kicad/K1_Lightwave.kicad_pcb") if not cls.board_path.exists(): raise unittest.SkipTest(f"Board file not found: {cls.board_path}") def test_initialization(self): - """Test placement engine initialization""" + """Verify that the placement engine initializes correctly with a valid board file.""" placer = ComponentPlacement(str(self.board_path)) self.assertIsNotNone(placer.board) self.assertGreater(len(placer.components), 0) def test_component_loading(self): - """Test component loading from board""" + """Verify that components are correctly loaded from the board file.""" placer = ComponentPlacement(str(self.board_path)) # Verify key components are loaded @@ -118,23 +118,23 @@ def test_component_loading(self): self.assertIsInstance(comp.position, Point) def test_invalid_board_path(self): - """Test initialization with invalid board path""" + """Verify that initialization fails with an invalid board path.""" with self.assertRaises(RuntimeError): ComponentPlacement("/nonexistent/board.kicad_pcb") class TestThermalZoneDefinition(unittest.TestCase): - """Test thermal zone definition""" + """Tests for the definition of thermal zones.""" @classmethod def setUpClass(cls): - """Set up test board""" + """Set up the path to the test board file.""" cls.board_path = Path("hardware/k1-lightwave/kicad/K1_Lightwave.kicad_pcb") if not cls.board_path.exists(): raise unittest.SkipTest(f"Board file not found: {cls.board_path}") def test_define_thermal_zones(self): - """Test thermal zone creation""" + """Verify that the correct number and names of thermal zones are created.""" placer = ComponentPlacement(str(self.board_path)) zones = placer.define_thermal_zones() @@ -152,7 +152,7 @@ def test_define_thermal_zones(self): self.assertEqual(zone_names, expected_names) def test_zone_priorities(self): - """Test thermal zone priorities""" + """Verify that the thermal zones have the correct priorities.""" placer = ComponentPlacement(str(self.board_path)) placer.define_thermal_zones() @@ -169,7 +169,7 @@ def test_zone_priorities(self): self.assertEqual(zone.priority, 2) def test_zone_power_dissipation(self): - """Test thermal zone power specifications""" + """Verify that the thermal zones have the correct power dissipation specifications.""" placer = ComponentPlacement(str(self.board_path)) placer.define_thermal_zones() @@ -183,17 +183,17 @@ def test_zone_power_dissipation(self): class TestComponentClustering(unittest.TestCase): - """Test component clustering by function""" + """Tests for the component clustering functionality.""" @classmethod def setUpClass(cls): - """Set up test board""" + """Set up the path to the test board file.""" cls.board_path = Path("hardware/k1-lightwave/kicad/K1_Lightwave.kicad_pcb") if not cls.board_path.exists(): raise unittest.SkipTest(f"Board file not found: {cls.board_path}") def test_cluster_components(self): - """Test component clustering""" + """Verify that all expected component clusters are created.""" placer = ComponentPlacement(str(self.board_path)) clusters = placer.cluster_components() @@ -206,7 +206,7 @@ def test_cluster_components(self): self.assertIn(cluster, clusters) def test_power_cluster(self): - """Test power component clustering""" + """Verify that power-related components are correctly clustered.""" placer = ComponentPlacement(str(self.board_path)) placer.cluster_components() @@ -217,7 +217,7 @@ def test_power_cluster(self): self.assertIn('J1', j1_refs) def test_mcu_cluster(self): - """Test MCU clustering""" + """Verify that MCUs are correctly clustered.""" placer = ComponentPlacement(str(self.board_path)) placer.cluster_components() @@ -229,7 +229,7 @@ def test_mcu_cluster(self): self.assertIn('U3', mcu_refs) def test_decoupling_cluster(self): - """Test decoupling capacitor clustering""" + """Verify that decoupling capacitors are correctly clustered.""" placer = ComponentPlacement(str(self.board_path)) placer.cluster_components() @@ -241,7 +241,7 @@ def test_decoupling_cluster(self): self.assertIn(ref, decoupling_refs) def test_all_components_clustered(self): - """Test that all components are assigned to clusters""" + """Verify that every component is assigned to a cluster.""" placer = ComponentPlacement(str(self.board_path)) placer.cluster_components() @@ -250,17 +250,17 @@ def test_all_components_clustered(self): class TestFixedComponentPlacement(unittest.TestCase): - """Test Phase 2A: Fixed component placement""" + """Tests for Phase 2A: Placement of fixed components.""" @classmethod def setUpClass(cls): - """Set up test board""" + """Set up the path to the test board file.""" cls.board_path = Path("hardware/k1-lightwave/kicad/K1_Lightwave.kicad_pcb") if not cls.board_path.exists(): raise unittest.SkipTest(f"Board file not found: {cls.board_path}") def test_usb_connector_placement(self): - """Test J1 (USB-C) placement at bottom-center""" + """Verify the placement of the J1 (USB-C) connector at the bottom-center.""" placer = ComponentPlacement(str(self.board_path)) placer.place_fixed_components() @@ -273,7 +273,7 @@ def test_usb_connector_placement(self): self.assertAlmostEqual(j1.position.y, placer.EDGE_CLEARANCE, delta=1.0) def test_led_connector_placement(self): - """Test LED connector placement at right edge""" + """Verify the placement of the LED connectors on the right edge.""" placer = ComponentPlacement(str(self.board_path)) placer.place_fixed_components() @@ -294,7 +294,7 @@ def test_led_connector_placement(self): prev_y = comp.position.y def test_i2c_connector_placement(self): - """Test I2C connector placement at top edge""" + """Verify the placement of the I2C connectors on the top edge.""" placer = ComponentPlacement(str(self.board_path)) placer.place_fixed_components() @@ -309,7 +309,7 @@ def test_i2c_connector_placement(self): self.assertGreater(comp.position.y, placer.BOARD_HEIGHT - placer.EDGE_CLEARANCE - 1) def test_i2s_connector_placement(self): - """Test I2S connector placement at left edge""" + """Verify the placement of the I2S connectors on the left edge.""" placer = ComponentPlacement(str(self.board_path)) placer.place_fixed_components() @@ -325,17 +325,17 @@ def test_i2s_connector_placement(self): class TestPrimaryComponentPlacement(unittest.TestCase): - """Test Phase 2B: Primary component placement""" + """Tests for Phase 2B: Placement of primary components.""" @classmethod def setUpClass(cls): - """Set up test board""" + """Set up the path to the test board file.""" cls.board_path = Path("hardware/k1-lightwave/kicad/K1_Lightwave.kicad_pcb") if not cls.board_path.exists(): raise unittest.SkipTest(f"Board file not found: {cls.board_path}") def test_mcu_a_zone_placement(self): - """Test MCU-A zone component placement""" + """Verify the placement of components within the MCU-A thermal zone.""" placer = ComponentPlacement(str(self.board_path)) placer.define_thermal_zones() placer.place_primary_components() @@ -351,7 +351,7 @@ def test_mcu_a_zone_placement(self): self.assertLess(distance, 5.0) def test_mcu_b_zone_placement(self): - """Test MCU-B zone component placement""" + """Verify the placement of components within the MCU-B thermal zone.""" placer = ComponentPlacement(str(self.board_path)) placer.define_thermal_zones() placer.place_primary_components() @@ -367,7 +367,7 @@ def test_mcu_b_zone_placement(self): self.assertLess(distance, 5.0) def test_decoupling_placement(self): - """Test decoupling capacitor placement around MCU""" + """Verify the placement of decoupling capacitors around the MCU.""" placer = ComponentPlacement(str(self.board_path)) placer.define_thermal_zones() placer.cluster_components() @@ -385,7 +385,7 @@ def test_decoupling_placement(self): self.assertEqual(comp.thermal_zone, "MCU-B Zone") def test_usb_fuse_placement(self): - """Test F_USB placement near J1""" + """Verify the placement of the F_USB fuse near the J1 connector.""" placer = ComponentPlacement(str(self.board_path)) placer.place_fixed_components() placer.define_thermal_zones() @@ -403,17 +403,17 @@ def test_usb_fuse_placement(self): class TestSpacingValidation(unittest.TestCase): - """Test spacing validation and DFM checks""" + """Tests for spacing validation and DFM checks.""" @classmethod def setUpClass(cls): - """Set up test board""" + """Set up the path to the test board file.""" cls.board_path = Path("hardware/k1-lightwave/kicad/K1_Lightwave.kicad_pcb") if not cls.board_path.exists(): raise unittest.SkipTest(f"Board file not found: {cls.board_path}") def test_verify_spacing_structure(self): - """Test spacing verification returns correct structure""" + """Verify that the spacing verification method returns the correct data structure.""" placer = ComponentPlacement(str(self.board_path)) is_valid, violations = placer.verify_spacing() @@ -421,7 +421,7 @@ def test_verify_spacing_structure(self): self.assertIsInstance(violations, list) def test_edge_clearance_detection(self): - """Test detection of edge clearance violations""" + """Verify that edge clearance violations are correctly detected.""" placer = ComponentPlacement(str(self.board_path)) # Manually create violation @@ -434,7 +434,7 @@ def test_edge_clearance_detection(self): self.assertGreater(len(violations), 0) def test_spacing_calculation(self): - """Test inter-component spacing calculation""" + """Verify that inter-component spacing violations are correctly calculated.""" placer = ComponentPlacement(str(self.board_path)) # Create two components very close together @@ -452,17 +452,17 @@ def test_spacing_calculation(self): class TestRoutingAccessibility(unittest.TestCase): - """Test routing accessibility optimization""" + """Tests for the routing accessibility optimization.""" @classmethod def setUpClass(cls): - """Set up test board""" + """Set up the path to the test board file.""" cls.board_path = Path("hardware/k1-lightwave/kicad/K1_Lightwave.kicad_pcb") if not cls.board_path.exists(): raise unittest.SkipTest(f"Board file not found: {cls.board_path}") def test_accessibility_scores(self): - """Test routing accessibility scoring""" + """Verify that routing accessibility scores are calculated for all components.""" placer = ComponentPlacement(str(self.board_path)) scores = placer.optimize_routing_accessibility() @@ -475,7 +475,7 @@ def test_accessibility_scores(self): self.assertLessEqual(score, 1.0) def test_edge_components_higher_score(self): - """Test that edge components have better accessibility""" + """Verify that components placed at the edge have a higher accessibility score.""" placer = ComponentPlacement(str(self.board_path)) placer.place_fixed_components() @@ -487,17 +487,17 @@ def test_edge_components_higher_score(self): class TestFullPlacementPipeline(unittest.TestCase): - """Test complete placement pipeline""" + """Tests for the complete component placement pipeline.""" @classmethod def setUpClass(cls): - """Set up test board""" + """Set up the path to the test board file.""" cls.board_path = Path("hardware/k1-lightwave/kicad/K1_Lightwave.kicad_pcb") if not cls.board_path.exists(): raise unittest.SkipTest(f"Board file not found: {cls.board_path}") def test_execute_pipeline(self): - """Test full placement execution""" + """Verify that the full placement pipeline executes successfully.""" # Create temporary output file with tempfile.NamedTemporaryFile(suffix='.kicad_pcb', delete=False) as tmp: tmp_path = tmp.name @@ -522,7 +522,7 @@ def test_execute_pipeline(self): Path(tmp_path).unlink() def test_report_generation(self): - """Test placement report generation""" + """Verify that the placement report is generated with all key sections.""" placer = ComponentPlacement(str(self.board_path)) placer.define_thermal_zones() placer.cluster_components() @@ -538,7 +538,7 @@ def test_report_generation(self): self.assertIn("PLACEMENT SUMMARY", report) def test_ascii_visualization(self): - """Test ASCII visualization generation""" + """Verify that the ASCII visualization is generated correctly.""" placer = ComponentPlacement(str(self.board_path)) placer.place_fixed_components() @@ -554,17 +554,17 @@ def test_ascii_visualization(self): class TestPlacementValidation(unittest.TestCase): - """Test placement validation criteria""" + """Tests for the validation of the final component placement.""" @classmethod def setUpClass(cls): - """Set up test board""" + """Set up the path to the test board file.""" cls.board_path = Path("hardware/k1-lightwave/kicad/K1_Lightwave.kicad_pcb") if not cls.board_path.exists(): raise unittest.SkipTest(f"Board file not found: {cls.board_path}") def test_all_connectors_at_edge(self): - """Verify all connectors placed at board edge""" + """Verify that all connectors are placed at the board edge.""" placer = ComponentPlacement(str(self.board_path)) placer.place_fixed_components() @@ -584,7 +584,7 @@ def test_all_connectors_at_edge(self): f"{conn.reference} not at edge: {min_edge_dist:.2f}mm") def test_minimum_spacing_compliance(self): - """Verify 2mm minimum spacing between all components""" + """Verify that the 2mm minimum spacing between components is respected.""" placer = ComponentPlacement(str(self.board_path)) placer.execute() @@ -607,7 +607,7 @@ def test_minimum_spacing_compliance(self): f"Too many spacing violations: {len(spacing_violations)}") def test_thermal_zone_compliance(self): - """Verify thermal zone assignments""" + """Verify that components are correctly assigned to thermal zones.""" placer = ComponentPlacement(str(self.board_path)) placer.define_thermal_zones() placer.place_primary_components() @@ -619,7 +619,7 @@ def test_thermal_zone_compliance(self): f"{zone.name} has no components") def test_board_density(self): - """Verify reasonable board density (<5% unoccupied)""" + """Verify that the board has a reasonable component density.""" placer = ComponentPlacement(str(self.board_path)) placer.execute() diff --git a/test_design_preparation.py b/test_design_preparation.py index ee99839..85fe7aa 100644 --- a/test_design_preparation.py +++ b/test_design_preparation.py @@ -14,7 +14,16 @@ def test_k1_design_preparation(): - """Test Phase 1 on K1 Lightwave board""" + """Tests the full design preparation pipeline on the K1 Lightwave board. + + This function serves as an integration test for the `DesignPreparation` + class, running all the steps on the actual K1 Lightwave project files. It + prints a detailed report of the process and its results. + + Returns: + True if the design preparation pipeline completes successfully, False + otherwise. + """ # Setup logging logging.basicConfig( diff --git a/test_design_validation.py b/test_design_validation.py index a762f21..528ba77 100644 --- a/test_design_validation.py +++ b/test_design_validation.py @@ -69,10 +69,10 @@ def sample_thermal_params(): class TestDRCRules: - """Test DRC rules definition""" + """Tests for the DRCRules class.""" def test_default_rules(self): - """Test default DRC rules match JLCPCB standards""" + """Verify that the default DRC rules match JLCPCB standards.""" rules = DRCRules() assert rules.trace_width_min == 0.1016 # 4 mil @@ -82,7 +82,7 @@ def test_default_rules(self): assert rules.copper_to_edge_min == 0.3 # mm def test_to_dict(self): - """Test rule conversion to dictionary""" + """Verify that the DRC rules can be correctly converted to a dictionary.""" rules = DRCRules() rules_dict = rules.to_dict() @@ -93,11 +93,11 @@ def test_to_dict(self): class TestDRCValidator: - """Test DRC validation functionality""" + """Tests for the DRCValidator class.""" @patch('design_validation.pcbnew.LoadBoard') def test_init(self, mock_load, mock_board_path, mock_board): - """Test DRC validator initialization""" + """Verify that the DRCValidator is initialized correctly.""" mock_load.return_value = mock_board validator = DRCValidator(mock_board_path) @@ -109,7 +109,7 @@ def test_init(self, mock_load, mock_board_path, mock_board): @patch('design_validation.pcbnew.LoadBoard') @patch('design_validation.subprocess.run') def test_run_kicad_drc_success(self, mock_run, mock_load, mock_board_path, mock_board): - """Test successful DRC execution""" + """Verify that a successful DRC execution is handled correctly.""" mock_load.return_value = mock_board # Mock successful DRC with zero violations @@ -132,7 +132,7 @@ def test_run_kicad_drc_success(self, mock_run, mock_load, mock_board_path, mock_ @patch('design_validation.pcbnew.LoadBoard') @patch('design_validation.subprocess.run') def test_run_kicad_drc_with_violations(self, mock_run, mock_load, mock_board_path, mock_board): - """Test DRC execution with violations""" + """Verify that DRC execution with violations is handled correctly.""" mock_load.return_value = mock_board # Mock DRC with violations @@ -151,7 +151,7 @@ def test_run_kicad_drc_with_violations(self, mock_run, mock_load, mock_board_pat @patch('design_validation.pcbnew.LoadBoard') def test_verify_constraints_pass(self, mock_load, mock_board_path, mock_board): - """Test constraint verification with passing DRC""" + """Verify that a passing DRC result is correctly interpreted.""" mock_load.return_value = mock_board validator = DRCValidator(mock_board_path) @@ -167,7 +167,7 @@ def test_verify_constraints_pass(self, mock_load, mock_board_path, mock_board): @patch('design_validation.pcbnew.LoadBoard') def test_verify_constraints_fail(self, mock_load, mock_board_path, mock_board): - """Test constraint verification with failing DRC""" + """Verify that a failing DRC result is correctly interpreted.""" mock_load.return_value = mock_board validator = DRCValidator(mock_board_path) @@ -182,11 +182,11 @@ def test_verify_constraints_fail(self, mock_load, mock_board_path, mock_board): class TestDFMValidator: - """Test Design for Manufacturing validation""" + """Tests for the Design for Manufacturing (DFM) validator.""" @patch('design_validation.pcbnew.LoadBoard') def test_init(self, mock_load, mock_board_path, mock_board): - """Test DFM validator initialization""" + """Verify that the DFMValidator is initialized correctly.""" mock_load.return_value = mock_board validator = DFMValidator(mock_board_path) @@ -196,7 +196,7 @@ def test_init(self, mock_load, mock_board_path, mock_board): @patch('design_validation.pcbnew.LoadBoard') def test_validate_layer_stack_pass(self, mock_load, mock_board_path, mock_board): - """Test layer stack validation with 4 layers""" + """Verify that a correct 4-layer stackup passes validation.""" mock_board.GetCopperLayerCount.return_value = 4 mock_load.return_value = mock_board @@ -209,7 +209,7 @@ def test_validate_layer_stack_pass(self, mock_load, mock_board_path, mock_board) @patch('design_validation.pcbnew.LoadBoard') def test_validate_layer_stack_fail(self, mock_load, mock_board_path, mock_board): - """Test layer stack validation with wrong layer count""" + """Verify that an incorrect layer stackup fails validation.""" mock_board.GetCopperLayerCount.return_value = 2 mock_load.return_value = mock_board @@ -222,7 +222,7 @@ def test_validate_layer_stack_fail(self, mock_load, mock_board_path, mock_board) @patch('design_validation.pcbnew.LoadBoard') def test_validate_fiducials_sufficient(self, mock_load, mock_board_path, mock_board): - """Test fiducial validation with 3+ fiducials""" + """Verify that a board with sufficient fiducials passes validation.""" # Create mock fiducial footprints fid1 = MagicMock() fid1.GetReference.return_value = "FID1" @@ -248,7 +248,7 @@ def test_validate_fiducials_sufficient(self, mock_load, mock_board_path, mock_bo @patch('design_validation.pcbnew.LoadBoard') def test_validate_fiducials_insufficient(self, mock_load, mock_board_path, mock_board): - """Test fiducial validation with <3 fiducials""" + """Verify that a board with insufficient fiducials fails validation.""" fid1 = MagicMock() fid1.GetReference.return_value = "FID1" fid1.GetPosition.return_value = pcbnew.VECTOR2I(0, 0) @@ -265,11 +265,11 @@ def test_validate_fiducials_insufficient(self, mock_load, mock_board_path, mock_ class TestSignalIntegrityValidator: - """Test signal integrity validation""" + """Tests for the Signal Integrity (SI) validator.""" @patch('design_validation.pcbnew.LoadBoard') def test_init(self, mock_load, mock_board_path, mock_board): - """Test SI validator initialization""" + """Verify that the SignalIntegrityValidator is initialized correctly.""" mock_load.return_value = mock_board validator = SignalIntegrityValidator(mock_board_path) @@ -278,7 +278,7 @@ def test_init(self, mock_load, mock_board_path, mock_board): @patch('design_validation.pcbnew.LoadBoard') def test_validate_spi_routing(self, mock_load, mock_board_path, mock_board): - """Test SPI routing validation""" + """Verify that the SPI routing validation runs without errors.""" # Create mock SPI nets mock_net_info = MagicMock() mock_sck_net = MagicMock() @@ -301,7 +301,7 @@ def test_validate_spi_routing(self, mock_load, mock_board_path, mock_board): @patch('design_validation.pcbnew.LoadBoard') def test_validate_usb_routing(self, mock_load, mock_board_path, mock_board): - """Test USB routing validation""" + """Verify that the USB routing validation runs without errors.""" mock_load.return_value = mock_board validator = SignalIntegrityValidator(mock_board_path) @@ -313,7 +313,7 @@ def test_validate_usb_routing(self, mock_load, mock_board_path, mock_board): @patch('design_validation.pcbnew.LoadBoard') def test_validate_i2c_i2s_routing(self, mock_load, mock_board_path, mock_board): - """Test I2C/I2S routing validation""" + """Verify that the I2C/I2S routing validation runs without errors.""" mock_load.return_value = mock_board validator = SignalIntegrityValidator(mock_board_path) @@ -324,10 +324,10 @@ def test_validate_i2c_i2s_routing(self, mock_load, mock_board_path, mock_board): class TestThermalValidator: - """Test thermal validation and calculations""" + """Tests for the thermal validator and its calculations.""" def test_thermal_parameters_defaults(self, sample_thermal_params): - """Test thermal parameter defaults""" + """Verify that the default thermal parameters are set correctly.""" params = sample_thermal_params assert params.ambient_temp_c == 25.0 @@ -338,7 +338,7 @@ def test_thermal_parameters_defaults(self, sample_thermal_params): @patch('design_validation.pcbnew.LoadBoard') def test_calculate_temperature_rise(self, mock_load, mock_board_path, mock_board, sample_thermal_params): - """Test temperature rise calculation""" + """Verify that the temperature rise is calculated correctly.""" mock_load.return_value = mock_board validator = ThermalValidator(mock_board_path, sample_thermal_params) @@ -349,7 +349,7 @@ def test_calculate_temperature_rise(self, mock_load, mock_board_path, mock_board @patch('design_validation.pcbnew.LoadBoard') def test_calculate_via_effectiveness(self, mock_load, mock_board_path, mock_board, sample_thermal_params): - """Test thermal via effectiveness calculation""" + """Verify that the thermal via effectiveness is calculated correctly.""" mock_load.return_value = mock_board validator = ThermalValidator(mock_board_path, sample_thermal_params) @@ -360,7 +360,7 @@ def test_calculate_via_effectiveness(self, mock_load, mock_board_path, mock_boar @patch('design_validation.pcbnew.LoadBoard') def test_validate_thermal_design_pass(self, mock_load, mock_board_path, mock_board, sample_thermal_params): - """Test thermal design validation with good margin""" + """Verify that a good thermal design passes validation.""" mock_board.GetTracks.return_value = [] mock_load.return_value = mock_board @@ -375,7 +375,7 @@ def test_validate_thermal_design_pass(self, mock_load, mock_board_path, mock_boa @patch('design_validation.pcbnew.LoadBoard') def test_validate_thermal_design_marginal(self, mock_load, mock_board_path, mock_board): - """Test thermal design validation with marginal conditions""" + """Verify that a marginal thermal design is correctly evaluated.""" mock_board.GetTracks.return_value = [] mock_load.return_value = mock_board @@ -399,11 +399,11 @@ def test_validate_thermal_design_marginal(self, mock_load, mock_board_path, mock class TestDesignValidation: - """Test comprehensive design validation suite""" + """Tests for the comprehensive design validation suite.""" @patch('design_validation.pcbnew.LoadBoard') def test_init(self, mock_load, mock_board_path, mock_board): - """Test design validation initialization""" + """Verify that the DesignValidation class is initialized correctly.""" mock_load.return_value = mock_board validator = DesignValidation(mock_board_path) @@ -416,7 +416,7 @@ def test_init(self, mock_load, mock_board_path, mock_board): @patch('design_validation.pcbnew.LoadBoard') def test_init_with_output_dir(self, mock_load, mock_board_path, mock_board, tmp_path): - """Test initialization with custom output directory""" + """Verify initialization with a custom output directory.""" mock_load.return_value = mock_board output_dir = tmp_path / "output" @@ -427,7 +427,7 @@ def test_init_with_output_dir(self, mock_load, mock_board_path, mock_board, tmp_ @patch('design_validation.pcbnew.LoadBoard') def test_run_all_validations(self, mock_load, mock_board_path, mock_board): - """Test running all validations""" + """Verify that all validation checks are run.""" mock_board.GetCopperLayerCount.return_value = 4 mock_board.GetTracks.return_value = [] mock_board.GetFootprints.return_value = [] @@ -447,7 +447,7 @@ def test_run_all_validations(self, mock_load, mock_board_path, mock_board): @patch('design_validation.pcbnew.LoadBoard') def test_manufacturing_readiness_check(self, mock_load, mock_board_path, mock_board): - """Test manufacturing readiness checklist""" + """Verify the manufacturing readiness checklist.""" mock_load.return_value = mock_board validator = DesignValidation(mock_board_path) @@ -467,7 +467,7 @@ def test_manufacturing_readiness_check(self, mock_load, mock_board_path, mock_bo @patch('design_validation.pcbnew.LoadBoard') @patch('design_validation.pcbnew.PLOT_CONTROLLER') def test_export_manufacturing_files(self, mock_plot_controller, mock_load, mock_board_path, mock_board): - """Test manufacturing file export""" + """Verify that manufacturing files are exported correctly.""" mock_load.return_value = mock_board # Mock plot controller @@ -484,7 +484,7 @@ def test_export_manufacturing_files(self, mock_plot_controller, mock_load, mock_ @patch('design_validation.pcbnew.LoadBoard') def test_generate_validation_report(self, mock_load, mock_board_path, mock_board): - """Test validation report generation""" + """Verify that the validation report is generated correctly.""" mock_load.return_value = mock_board validator = DesignValidation(mock_board_path) @@ -501,7 +501,7 @@ def test_generate_validation_report(self, mock_load, mock_board_path, mock_board @patch('design_validation.pcbnew.LoadBoard') def test_execute_full_pipeline(self, mock_load, mock_board_path, mock_board): - """Test full validation pipeline execution""" + """Verify that the full validation pipeline executes successfully.""" mock_board.GetCopperLayerCount.return_value = 4 mock_board.GetTracks.return_value = [] mock_board.GetFootprints.return_value = [] @@ -520,11 +520,11 @@ def test_execute_full_pipeline(self, mock_load, mock_board_path, mock_board): class TestK1SpecificValidation: - """Test K1 Lightwave specific validation scenarios""" + """Tests for K1 Lightwave-specific validation scenarios.""" @patch('design_validation.pcbnew.LoadBoard') def test_k1_thermal_specifications(self, mock_load, mock_board_path, mock_board): - """Test K1-specific thermal specifications""" + """Verify that the K1's thermal specifications are met.""" mock_board.GetTracks.return_value = [] mock_load.return_value = mock_board @@ -551,7 +551,7 @@ def test_k1_thermal_specifications(self, mock_load, mock_board_path, mock_board) @patch('design_validation.pcbnew.LoadBoard') def test_k1_layer_stack_4layer(self, mock_load, mock_board_path, mock_board): - """Test K1 4-layer stack configuration""" + """Verify that the K1 board has the correct 4-layer stackup.""" mock_board.GetCopperLayerCount.return_value = 4 mock_load.return_value = mock_board @@ -564,7 +564,7 @@ def test_k1_layer_stack_4layer(self, mock_load, mock_board_path, mock_board): @patch('design_validation.pcbnew.LoadBoard') def test_k1_expected_results(self, mock_load, mock_board_path, mock_board): - """Test K1 expected validation results""" + """Verify that the K1 board passes all expected validation checks.""" mock_board.GetCopperLayerCount.return_value = 4 mock_board.GetTracks.return_value = [] mock_board.GetFootprints.return_value = [] diff --git a/test_elite_pcb_designer.py b/test_elite_pcb_designer.py index 77452c2..575108a 100644 --- a/test_elite_pcb_designer.py +++ b/test_elite_pcb_designer.py @@ -33,10 +33,10 @@ class TestElitePCBDesignerInitialization(unittest.TestCase): - """Test initialization and validation""" + """Tests for the initialization and input validation of the ElitePCBDesigner class.""" def setUp(self): - """Create temporary test files""" + """Set up a temporary directory and dummy files for testing.""" self.temp_dir = Path(tempfile.mkdtemp()) self.netlist_path = self.temp_dir / "test.net" self.board_path = self.temp_dir / "test.kicad_pcb" @@ -47,11 +47,11 @@ def setUp(self): self.board_path.write_text("(kicad_pcb (version 20221018)") def tearDown(self): - """Clean up temporary files""" + """Remove the temporary directory and its contents after testing.""" shutil.rmtree(self.temp_dir) def test_initialization_valid_inputs(self): - """Test successful initialization with valid inputs""" + """Verify that the designer initializes successfully with valid inputs.""" designer = ElitePCBDesigner( netlist_path=str(self.netlist_path), board_path=str(self.board_path), @@ -65,7 +65,7 @@ def test_initialization_valid_inputs(self): self.assertEqual(len(designer.skip_phases), 0) def test_initialization_missing_netlist(self): - """Test initialization fails with missing netlist""" + """Verify that initialization fails if the netlist file is missing.""" with self.assertRaises(FileNotFoundError): ElitePCBDesigner( netlist_path="nonexistent.net", @@ -73,7 +73,7 @@ def test_initialization_missing_netlist(self): ) def test_initialization_missing_board(self): - """Test initialization fails with missing board""" + """Verify that initialization fails if the board file is missing.""" with self.assertRaises(FileNotFoundError): ElitePCBDesigner( netlist_path=str(self.netlist_path), @@ -81,7 +81,7 @@ def test_initialization_missing_board(self): ) def test_initialization_with_skip_phases(self): - """Test initialization with skip phases""" + """Verify that phases can be skipped during initialization.""" designer = ElitePCBDesigner( netlist_path=str(self.netlist_path), board_path=str(self.board_path), @@ -91,7 +91,7 @@ def test_initialization_with_skip_phases(self): self.assertEqual(designer.skip_phases, {2, 3}) def test_initialization_verbose_mode(self): - """Test initialization with verbose mode""" + """Verify that verbose mode is correctly enabled during initialization.""" designer = ElitePCBDesigner( netlist_path=str(self.netlist_path), board_path=str(self.board_path), @@ -102,10 +102,10 @@ def test_initialization_verbose_mode(self): class TestPhaseExecution(unittest.TestCase): - """Test individual phase execution""" + """Tests for the execution of individual design phases.""" def setUp(self): - """Create temporary test environment""" + """Set up a temporary test environment for phase execution tests.""" self.temp_dir = Path(tempfile.mkdtemp()) self.netlist_path = self.temp_dir / "test.net" self.board_path = self.temp_dir / "test.kicad_pcb" @@ -120,12 +120,12 @@ def setUp(self): ) def tearDown(self): - """Clean up""" + """Remove the temporary directory and its contents after testing.""" shutil.rmtree(self.temp_dir) @patch('elite_pcb_designer.DesignPreparation') def test_phase_1_success(self, mock_phase1): - """Test Phase 1 successful execution""" + """Verify that a successful execution of Phase 1 is handled correctly.""" # Mock Phase 1 to return success mock_instance = MagicMock() mock_instance.execute.return_value = True @@ -143,7 +143,7 @@ def test_phase_1_success(self, mock_phase1): @patch('elite_pcb_designer.DesignPreparation') def test_phase_1_failure(self, mock_phase1): - """Test Phase 1 failure handling""" + """Verify that a failed execution of Phase 1 is handled correctly.""" mock_instance = MagicMock() mock_instance.execute.return_value = False mock_phase1.return_value = mock_instance @@ -157,7 +157,7 @@ def test_phase_1_failure(self, mock_phase1): @patch('elite_pcb_designer.DesignPreparation') def test_phase_1_exception(self, mock_phase1): - """Test Phase 1 exception handling""" + """Verify that exceptions during Phase 1 execution are handled correctly.""" mock_instance = MagicMock() mock_instance.execute.side_effect = Exception("Test error") mock_phase1.return_value = mock_instance @@ -170,7 +170,7 @@ def test_phase_1_exception(self, mock_phase1): self.assertIsNotNone(result.error_message) def test_phase_skip(self): - """Test phase skipping""" + """Verify that a phase can be successfully skipped.""" self.designer.skip_phases = {1} self.designer._skip_phase(1, "Design Preparation") @@ -180,10 +180,10 @@ def test_phase_skip(self): class TestReportGeneration(unittest.TestCase): - """Test report generation""" + """Tests for the generation of design reports.""" def setUp(self): - """Create test environment""" + """Set up a test environment with mock results for report generation.""" self.temp_dir = Path(tempfile.mkdtemp()) self.netlist_path = self.temp_dir / "test.net" self.board_path = self.temp_dir / "test.kicad_pcb" @@ -225,11 +225,11 @@ def setUp(self): self.designer.end_time = start_time + timedelta(seconds=180) def tearDown(self): - """Clean up""" + """Remove the temporary directory and its contents after testing.""" shutil.rmtree(self.temp_dir) def test_text_report_generation(self): - """Test text report generation""" + """Verify that the text report is generated with the correct content.""" report = self.designer._generate_text_report() self.assertIn("ELITE PCB DESIGNER AGENT", report) @@ -239,7 +239,7 @@ def test_text_report_generation(self): self.assertIn("COMPLETED", report) def test_json_report_generation(self): - """Test JSON report generation""" + """Verify that the JSON report is generated with the correct structure.""" report = self.designer._generate_json_report() self.assertIn('project', report) @@ -250,7 +250,7 @@ def test_json_report_generation(self): self.assertIn('2', report['phases']) def test_combined_report_saving(self): - """Test combined report saving""" + """Verify that both text and JSON reports are saved correctly.""" report_path = self.designer.generate_combined_report() # Check text report @@ -268,10 +268,10 @@ def test_combined_report_saving(self): class TestOutputOrganization(unittest.TestCase): - """Test output directory organization""" + """Tests for the organization of output files and directories.""" def setUp(self): - """Create test environment""" + """Set up a test environment for output organization tests.""" self.temp_dir = Path(tempfile.mkdtemp()) self.netlist_path = self.temp_dir / "test.net" self.board_path = self.temp_dir / "test.kicad_pcb" @@ -287,11 +287,11 @@ def setUp(self): ) def tearDown(self): - """Clean up""" + """Remove the temporary directory and its contents after testing.""" shutil.rmtree(self.temp_dir) def test_output_directory_creation(self): - """Test output directory structure creation""" + """Verify that the correct output directory structure is created.""" outputs = self.designer.save_all_outputs() # Check main output directory @@ -312,10 +312,10 @@ def test_output_directory_creation(self): class TestPhaseResult(unittest.TestCase): - """Test PhaseResult dataclass""" + """Tests for the PhaseResult dataclass.""" def test_phase_result_creation(self): - """Test PhaseResult creation""" + """Verify that a PhaseResult object is created with the correct attributes.""" from datetime import datetime start = datetime.now() @@ -334,7 +334,7 @@ def test_phase_result_creation(self): self.assertEqual(result.duration_seconds, 120.5) def test_duration_string_formatting(self): - """Test duration string formatting""" + """Verify that the duration is correctly formatted as a string.""" result = PhaseResult( phase_num=1, phase_name="Test", @@ -345,7 +345,7 @@ def test_duration_string_formatting(self): self.assertEqual(result.duration_str, "2:05") def test_phase_result_to_dict(self): - """Test PhaseResult to dict conversion""" + """Verify that the PhaseResult can be correctly converted to a dictionary.""" from datetime import datetime start = datetime.now() @@ -370,10 +370,10 @@ def test_phase_result_to_dict(self): class TestErrorRecovery(unittest.TestCase): - """Test error handling and recovery""" + """Tests for error handling and recovery mechanisms.""" def setUp(self): - """Create test environment""" + """Set up a test environment for error recovery tests.""" self.temp_dir = Path(tempfile.mkdtemp()) self.netlist_path = self.temp_dir / "test.net" self.board_path = self.temp_dir / "test.kicad_pcb" @@ -387,12 +387,12 @@ def setUp(self): ) def tearDown(self): - """Clean up""" + """Remove the temporary directory and its contents after testing.""" shutil.rmtree(self.temp_dir) @patch('elite_pcb_designer.DesignPreparation') def test_phase_failure_stops_pipeline(self, mock_phase1): - """Test that phase failure stops pipeline""" + """Verify that the pipeline stops execution after a phase failure.""" mock_instance = MagicMock() mock_instance.execute.return_value = False mock_phase1.return_value = mock_instance @@ -406,7 +406,7 @@ def test_phase_failure_stops_pipeline(self, mock_phase1): @patch('elite_pcb_designer.DesignPreparation') def test_keyboard_interrupt_handling(self, mock_phase1): - """Test keyboard interrupt handling""" + """Verify that a KeyboardInterrupt is handled gracefully.""" mock_instance = MagicMock() mock_instance.execute.side_effect = KeyboardInterrupt() mock_phase1.return_value = mock_instance @@ -418,10 +418,10 @@ def test_keyboard_interrupt_handling(self, mock_phase1): class TestUtilityMethods(unittest.TestCase): - """Test utility and helper methods""" + """Tests for utility and helper methods.""" def test_format_duration(self): - """Test duration formatting""" + """Verify that the duration is formatted correctly.""" from elite_pcb_designer import ElitePCBDesigner # 0 seconds diff --git a/test_ipc_standards.py b/test_ipc_standards.py index 56ee53a..23e3acf 100644 --- a/test_ipc_standards.py +++ b/test_ipc_standards.py @@ -30,10 +30,10 @@ # ============================================================================= class TestIPC2221ATraceWidth(unittest.TestCase): - """Test IPC-2221A trace width calculations.""" + """Tests for IPC-2221A trace width calculations.""" def test_trace_width_calculation_external_500ma(self): - """Test external trace width for 500mA at 20°C rise.""" + """Verify the trace width calculation for an external layer with 500mA current.""" trace_width, area = IPC2221A.calculate_trace_width( current_ma=500, temp_rise=TemperatureRise.DIGITAL_EXTERNAL, @@ -46,7 +46,7 @@ def test_trace_width_calculation_external_500ma(self): self.assertGreater(area, 0) def test_trace_width_calculation_internal_500ma(self): - """Test internal trace width for 500mA at 15°C rise.""" + """Verify the trace width calculation for an internal layer with 500mA current.""" trace_width, area = IPC2221A.calculate_trace_width( current_ma=500, temp_rise=TemperatureRise.DIGITAL_INTERNAL, @@ -58,7 +58,7 @@ def test_trace_width_calculation_internal_500ma(self): self.assertLess(trace_width, 20.0) def test_high_current_power_trace(self): - """Test high-current power distribution trace (8A LED 5V domain).""" + """Verify the trace width calculation for a high-current power trace (8A).""" trace_width, area = IPC2221A.calculate_trace_width( current_ma=8000, # 8 amperes temp_rise=TemperatureRise.POWER_EXTERNAL, @@ -70,7 +70,7 @@ def test_high_current_power_trace(self): print(f"8A @ 30°C rise: {trace_width:.2f} mils (area: {area:.2f} mils²)") def test_copper_thickness_effect(self): - """Test effect of copper thickness on trace width.""" + """Verify that copper thickness correctly affects the calculated trace width.""" # 2oz copper should allow narrower traces width_1oz, _ = IPC2221A.calculate_trace_width( current_ma=1000, @@ -89,7 +89,7 @@ def test_copper_thickness_effect(self): print(f"1oz: {width_1oz:.2f} mils, 2oz: {width_2oz:.2f} mils") def test_trace_width_rounding(self): - """Test trace width rounding to standard values.""" + """Verify that trace widths are correctly rounded to standard values.""" # 2.7 mils should round to 3 mils rounded = IPC2221A.round_trace_width(2.7) self.assertEqual(rounded, 3.0) @@ -103,7 +103,7 @@ def test_trace_width_rounding(self): self.assertEqual(rounded, 1.0) def test_k1_usb_domain_trace_width(self): - """Test K1 VBUS_USB_5V domain trace width calculation.""" + """Verify the trace width calculation for the K1's VBUS_USB_5V power domain.""" domain = K1Configuration.POWER_DOMAINS['VBUS_USB_5V'] trace_width, _ = IPC2221A.calculate_trace_width( current_ma=domain.current_peak_ma, @@ -115,7 +115,7 @@ def test_k1_usb_domain_trace_width(self): print(f"K1 VBUS 5V domain peak current trace: {trace_width_rounded} mils") def test_k1_led_domain_trace_width(self): - """Test K1 LED_5V high-current domain trace width.""" + """Verify the trace width calculation for the K1's high-current LED_5V power domain.""" domain = K1Configuration.POWER_DOMAINS['LED_5V'] trace_width, _ = IPC2221A.calculate_trace_width( current_ma=domain.current_peak_ma, @@ -133,10 +133,10 @@ def test_k1_led_domain_trace_width(self): # ============================================================================= class TestIPC2221AClearance(unittest.TestCase): - """Test IPC-2221A clearance calculations.""" + """Tests for IPC-2221A clearance calculations.""" def test_clearance_trace_to_trace_low_voltage_class1(self): - """Test trace-to-trace clearance for low voltage, Class 1 environment.""" + """Verify trace-to-trace clearance for low voltage in a Class 1 environment.""" clearance = IPC2221A.get_clearance( 'trace_to_trace', VoltageClass.LOW, @@ -145,7 +145,7 @@ def test_clearance_trace_to_trace_low_voltage_class1(self): self.assertEqual(clearance, 3) def test_clearance_increases_with_voltage(self): - """Test that clearance increases with voltage class.""" + """Verify that clearance requirements increase with higher voltage classes.""" clearance_low = IPC2221A.get_clearance( 'trace_to_trace', VoltageClass.ULTRA_LOW, @@ -159,7 +159,7 @@ def test_clearance_increases_with_voltage(self): self.assertLess(clearance_low, clearance_high) def test_clearance_increases_with_environment_severity(self): - """Test that clearance increases with environmental severity.""" + """Verify that clearance requirements increase in more severe environments.""" clearance_dry = IPC2221A.get_clearance( 'trace_to_trace', VoltageClass.MEDIUM, @@ -173,7 +173,7 @@ def test_clearance_increases_with_environment_severity(self): self.assertLess(clearance_dry, clearance_humid) def test_clearance_trace_to_edge_high_voltage(self): - """Test trace-to-board-edge clearance for high voltage.""" + """Verify trace-to-board-edge clearance for high voltage applications.""" clearance = IPC2221A.get_clearance( 'trace_to_edge', VoltageClass.HIGH, @@ -183,7 +183,7 @@ def test_clearance_trace_to_edge_high_voltage(self): self.assertEqual(clearance, 125) def test_clearance_trace_to_leads(self): - """Test trace-to-component-leads clearance.""" + """Verify trace-to-component-lead clearance.""" clearance = IPC2221A.get_clearance( 'trace_to_leads', VoltageClass.MEDIUM_LOW, @@ -192,7 +192,7 @@ def test_clearance_trace_to_leads(self): self.assertEqual(clearance, 15) def test_k1_clearance_requirements_usb_domain(self): - """Test K1 clearance requirements for VBUS_USB_5V domain.""" + """Verify the clearance requirements for the K1's VBUS_USB_5V power domain.""" clearances = K1Configuration.get_clearance_for_domain('VBUS_USB_5V') self.assertIn('trace_to_trace', clearances) self.assertIn('trace_to_edge', clearances) @@ -201,7 +201,7 @@ def test_k1_clearance_requirements_usb_domain(self): self.assertGreaterEqual(clearances['trace_to_trace'], 3) def test_k1_clearance_led_domain(self): - """Test K1 clearance requirements for LED_5V domain.""" + """Verify the clearance requirements for the K1's LED_5V power domain.""" clearances = K1Configuration.get_clearance_for_domain('LED_5V') # Same voltage class as VBUS but isolated trace_clearance = clearances['trace_to_trace'] @@ -213,25 +213,25 @@ def test_k1_clearance_led_domain(self): # ============================================================================= class TestIPC6012PCBClass(unittest.TestCase): - """Test IPC-6012 PCB classification requirements.""" + """Tests for IPC-6012 PCB classification and requirements.""" def test_class_1_characteristics(self): - """Test Class 1 (general electronics) characteristics.""" + """Verify the characteristics of a Class 1 (General Electronic Products) PCB.""" class_1_def = IPC6012.CLASS_DEFINITIONS[PCBClass.CLASS_1] self.assertIn('Consumer', class_1_def['typical_applications'][0]) def test_class_2_characteristics(self): - """Test Class 2 (dedicated service) characteristics.""" + """Verify the characteristics of a Class 2 (Dedicated Service Electronic Products) PCB.""" class_2_def = IPC6012.CLASS_DEFINITIONS[PCBClass.CLASS_2] self.assertIn('Automotive', class_2_def['typical_applications'][0]) def test_class_3_characteristics(self): - """Test Class 3 (high-reliability) characteristics.""" + """Verify the characteristics of a Class 3 (High Reliability Electronic Products) PCB.""" class_3_def = IPC6012.CLASS_DEFINITIONS[PCBClass.CLASS_3] self.assertIn('Military', class_3_def['typical_applications'][0]) def test_minimum_trace_width_progression(self): - """Test that minimum trace width becomes stricter for higher classes.""" + """Verify that trace width requirements become stricter for higher PCB classes.""" class_1_width = IPC6012.get_requirement('Trace Width/Spacing', PCBClass.CLASS_1) class_2_width = IPC6012.get_requirement('Trace Width/Spacing', PCBClass.CLASS_2) class_3_width = IPC6012.get_requirement('Trace Width/Spacing', PCBClass.CLASS_3) @@ -242,7 +242,7 @@ def test_minimum_trace_width_progression(self): self.assertEqual(class_3_width, '3 mil / 3 mil') def test_via_plating_thickness(self): - """Test via plating thickness requirements.""" + """Verify the via plating thickness requirements for different PCB classes.""" class_1_plating = IPC6012.get_requirement('Plating Thickness (Copper)', PCBClass.CLASS_1) class_3_plating = IPC6012.get_requirement('Plating Thickness (Copper)', PCBClass.CLASS_3) # Class 3 requires thicker plating than Class 1 @@ -250,7 +250,7 @@ def test_via_plating_thickness(self): self.assertIn('0.0015', class_3_plating) def test_electrical_test_voltage_progression(self): - """Test that hi-pot voltage increases for higher classes.""" + """Verify that hi-pot test voltage increases for higher PCB classes.""" class_1_voltage = IPC6012.ELECTRICAL_TEST_REQUIREMENTS[PCBClass.CLASS_1]['test_voltage'] class_2_voltage = IPC6012.ELECTRICAL_TEST_REQUIREMENTS[PCBClass.CLASS_2]['test_voltage'] class_3_voltage = IPC6012.ELECTRICAL_TEST_REQUIREMENTS[PCBClass.CLASS_3]['test_voltage'] @@ -260,22 +260,22 @@ def test_electrical_test_voltage_progression(self): self.assertEqual(class_3_voltage, '500V min') def test_select_class_automotive(self): - """Test selecting PCB class for automotive application.""" + """Verify that an automotive application correctly maps to PCB Class 2.""" selected_class = IPC6012.select_class_for_application("automotive audio processor") self.assertEqual(selected_class, PCBClass.CLASS_2) def test_select_class_consumer(self): - """Test selecting PCB class for consumer application.""" + """Verify that a consumer application correctly maps to PCB Class 1.""" selected_class = IPC6012.select_class_for_application("consumer gadget") self.assertEqual(selected_class, PCBClass.CLASS_1) def test_select_class_medical(self): - """Test selecting PCB class for medical application.""" + """Verify that a medical application correctly maps to PCB Class 3.""" selected_class = IPC6012.select_class_for_application("medical device control board") self.assertEqual(selected_class, PCBClass.CLASS_3) def test_k1_selected_class(self): - """Test K1 PCB class selection.""" + """Verify that the K1 project is correctly designated as PCB Class 2.""" selected_class = K1Configuration.DESIGN_RULES['pcb_class'] self.assertEqual(selected_class, PCBClass.CLASS_2) @@ -285,10 +285,10 @@ def test_k1_selected_class(self): # ============================================================================= class TestIPCA610Assembly(unittest.TestCase): - """Test IPC-A-610 assembly standards.""" + """Tests for IPC-A-610 assembly standards.""" def test_solder_joint_criteria_defined(self): - """Test that all solder joint criteria are defined.""" + """Verify that all required solder joint criteria are defined.""" required_criteria = [ 'fillet_shape', 'wetting', @@ -303,37 +303,37 @@ def test_solder_joint_criteria_defined(self): self.assertIn(criterion, IPCA610.SOLDER_JOINT_CRITERIA) def test_placement_tolerance_class_1(self): - """Test component placement tolerances for Class 1.""" + """Verify the component placement tolerances for Class 1 assembly.""" tolerance = IPCA610.PLACEMENT_TOLERANCES['chip_component_x_y']['class_1'] self.assertEqual(tolerance, 100) # ±0.100 inch def test_placement_tolerance_class_3(self): - """Test component placement tolerances for Class 3 (strictest).""" + """Verify the stricter component placement tolerances for Class 3 assembly.""" tolerance = IPCA610.PLACEMENT_TOLERANCES['chip_component_x_y']['class_3'] self.assertEqual(tolerance, 50) # ±0.050 inch def test_pad_size_0402(self): - """Test minimum pad size for 0402 components.""" + """Verify the minimum pad size requirements for 0402 components.""" pad_req = IPCA610.PAD_SIZE_REQUIREMENTS['chip_0402'] self.assertEqual(pad_req['min_pad_length'], 30) self.assertEqual(pad_req['min_pad_width'], 30) self.assertEqual(pad_req['min_paste_coverage'], 50) def test_pad_size_bga(self): - """Test pad size requirements for BGA packages.""" + """Verify the pad size requirements for BGA packages.""" pad_req = IPCA610.PAD_SIZE_REQUIREMENTS['bga'] self.assertEqual(pad_req['min_pad_diameter'], 8) self.assertEqual(pad_req['min_paste_coverage'], 75) # Higher for BGA def test_test_point_requirements(self): - """Test point spacing and clearance requirements.""" + """Verify the spacing and clearance requirements for test points.""" test_point_req = IPCA610.TEST_POINT_REQUIREMENTS self.assertEqual(test_point_req['diameter_min'], 25) self.assertEqual(test_point_req['diameter_max'], 50) self.assertEqual(test_point_req['spacing'], 100) def test_solder_joint_evaluation_perfect(self): - """Test solder joint evaluation with perfect criteria.""" + """Verify that a perfect solder joint is evaluated as acceptable.""" measurements = { 'fillet_shape': True, 'wetting': True, @@ -348,7 +348,7 @@ def test_solder_joint_evaluation_perfect(self): self.assertEqual(result, SolderJointQuality.ACCEPTABLE) def test_solder_joint_evaluation_rework(self): - """Test solder joint evaluation requiring rework.""" + """Verify that a solder joint with minor defects is evaluated as requiring rework.""" measurements = { 'fillet_shape': True, 'wetting': True, @@ -363,7 +363,7 @@ def test_solder_joint_evaluation_rework(self): self.assertEqual(result, SolderJointQuality.ACCEPTABLE) # Just above 85% for Class 2 def test_solder_joint_evaluation_reject(self): - """Test solder joint evaluation requiring rejection.""" + """Verify that a solder joint with major defects is evaluated as a reject.""" measurements = { 'fillet_shape': False, 'wetting': False, @@ -383,64 +383,64 @@ def test_solder_joint_evaluation_reject(self): # ============================================================================= class TestK1Configuration(unittest.TestCase): - """Test K1 Lightwave specific configuration.""" + """Tests for the K1 Lightwave project-specific configuration.""" def test_k1_power_domains_defined(self): - """Test that all K1 power domains are defined.""" + """Verify that all required power domains for the K1 project are defined.""" required_domains = ['VBUS_USB_5V', 'LED_5V', '3V3_LOGIC'] for domain in required_domains: self.assertIn(domain, K1Configuration.POWER_DOMAINS) def test_k1_usb_domain_properties(self): - """Test VBUS_USB_5V domain properties.""" + """Verify the properties of the VBUS_USB_5V power domain.""" domain = K1Configuration.POWER_DOMAINS['VBUS_USB_5V'] self.assertEqual(domain.voltage, 5.0) self.assertEqual(domain.current_typical_ma, 500) self.assertFalse(domain.isolation_required) def test_k1_led_domain_properties(self): - """Test LED_5V domain properties (high-current).""" + """Verify the properties of the high-current LED_5V power domain.""" domain = K1Configuration.POWER_DOMAINS['LED_5V'] self.assertEqual(domain.voltage, 5.0) self.assertEqual(domain.current_peak_ma, 8000) self.assertTrue(domain.isolation_required) def test_k1_logic_domain_properties(self): - """Test 3V3_LOGIC domain properties.""" + """Verify the properties of the 3V3_LOGIC power domain.""" domain = K1Configuration.POWER_DOMAINS['3V3_LOGIC'] self.assertEqual(domain.voltage, 3.3) self.assertLess(domain.current_peak_ma, 1000) self.assertFalse(domain.isolation_required) def test_k1_design_rules(self): - """Test K1 design rules are reasonable.""" + """Verify that the K1 project's design rules are reasonable.""" rules = K1Configuration.DESIGN_RULES self.assertGreaterEqual(rules['min_trace_width'], 3) self.assertLessEqual(rules['min_trace_width'], 10) self.assertEqual(rules['pcb_class'], PCBClass.CLASS_2) def test_k1_trace_width_recommendation_usb(self): - """Test trace width recommendation for USB domain.""" + """Verify the trace width recommendation for the USB power domain.""" width = K1Configuration.get_recommended_trace_width('VBUS_USB_5V') self.assertGreater(width, 0) self.assertLess(width, 50) def test_k1_trace_width_recommendation_led(self): - """Test trace width recommendation for LED domain (high current).""" + """Verify the trace width recommendation for the high-current LED power domain.""" width = K1Configuration.get_recommended_trace_width('LED_5V') # Should be much wider due to high peak current (8A) self.assertGreater(width, 20) print(f"K1 LED_5V recommended trace width: {width} mils") def test_k1_operating_conditions(self): - """Test K1 operating conditions are reasonable.""" + """Verify that the K1's operating conditions are within reasonable limits.""" conditions = K1Configuration.OPERATING_CONDITIONS self.assertEqual(conditions['temp_ambient_min'], 0) self.assertEqual(conditions['temp_ambient_max'], 50) self.assertGreater(conditions['temp_max_component'], conditions['temp_ambient_max']) def test_k1_signal_integrity_parameters(self): - """Test K1 signal integrity parameters.""" + """Verify the signal integrity parameters for the K1 project.""" sig_int = K1Configuration.SIGNAL_INTEGRITY # SPI clock should be high (inter-MCU communication) self.assertEqual(sig_int['spi_clock_max'], 40e6) @@ -449,7 +449,7 @@ def test_k1_signal_integrity_parameters(self): self.assertLess(sig_int['i2s_clock'], 10e6) def test_k1_test_requirements(self): - """Test K1 Class 2 test requirements.""" + """Verify the electrical test requirements for the K1 project (Class 2).""" test_reqs = K1Configuration.TEST_REQUIREMENTS self.assertEqual(test_reqs['hi_pot_voltage'], 250) self.assertEqual(test_reqs['insulation_resistance'], 100e6) @@ -461,10 +461,10 @@ def test_k1_test_requirements(self): # ============================================================================= class TestValidationAndUtilities(unittest.TestCase): - """Test validation and utility functions.""" + """Tests for validation and utility functions.""" def test_validate_design_good(self): - """Test validation of good design.""" + """Verify that a good design passes validation.""" valid, message = validate_design( trace_width_mils=8.0, clearance_mils=5.0, @@ -473,7 +473,7 @@ def test_validate_design_good(self): self.assertTrue(valid) def test_validate_design_trace_too_narrow(self): - """Test validation fails for trace width too narrow.""" + """Verify that a design with too narrow traces fails validation.""" valid, message = validate_design( trace_width_mils=2.0, clearance_mils=5.0, @@ -483,7 +483,7 @@ def test_validate_design_trace_too_narrow(self): self.assertIn('Trace width', message) def test_validate_design_clearance_too_small(self): - """Test validation fails for clearance too small.""" + """Verify that a design with insufficient clearance fails validation.""" valid, message = validate_design( trace_width_mils=8.0, clearance_mils=2.0, @@ -493,7 +493,7 @@ def test_validate_design_clearance_too_small(self): self.assertIn('Clearance', message) def test_validate_design_voltage_too_high(self): - """Test validation fails for voltage beyond K1 specification.""" + """Verify that a design with voltage exceeding specifications fails validation.""" valid, message = validate_design( trace_width_mils=8.0, clearance_mils=5.0, @@ -503,7 +503,7 @@ def test_validate_design_voltage_too_high(self): self.assertIn('Voltage', message) def test_ipc_reporter_generates_report(self): - """Test IPCReporter generates complete report.""" + """Verify that the IPCReporter generates a complete report.""" report = IPCReporter.generate_pcb_design_report() self.assertIn('K1 Lightwave PCB Design Report', report) self.assertIn('PCB CLASS SELECTION', report) @@ -516,10 +516,10 @@ def test_ipc_reporter_generates_report(self): # ============================================================================= class TestIntegration(unittest.TestCase): - """Integration tests combining multiple standards.""" + """Integration tests that combine multiple IPC standards.""" def test_k1_complete_design_workflow(self): - """Test complete K1 PCB design workflow.""" + """Verify the complete design workflow for the K1 project.""" # 1. Select PCB class pcb_class = K1Configuration.DESIGN_RULES['pcb_class'] self.assertEqual(pcb_class, PCBClass.CLASS_2) @@ -546,7 +546,7 @@ def test_k1_complete_design_workflow(self): print(f" {domain_name}: {width} mils trace width") def test_trace_width_vs_current_scaling(self): - """Test that trace width scales appropriately with current.""" + """Verify that the calculated trace width scales correctly with current.""" currents = [100, 500, 1000, 2000, 5000] widths = [] @@ -567,7 +567,7 @@ def test_trace_width_vs_current_scaling(self): print(f" {current}mA: {width:.2f} mils") def test_copper_thickness_impact(self): - """Test overall impact of copper thickness selection.""" + """Verify the impact of copper thickness on trace width calculations.""" current_ma = 2000 results = {} diff --git a/tools/elite-pcb-designer/analysis/__pycache__/thermal_power_analyzer.cpython-312.pyc b/tools/elite-pcb-designer/analysis/__pycache__/thermal_power_analyzer.cpython-312.pyc deleted file mode 100644 index 4b9a0cb..0000000 Binary files a/tools/elite-pcb-designer/analysis/__pycache__/thermal_power_analyzer.cpython-312.pyc and /dev/null differ diff --git a/tools/elite-pcb-designer/optimizers/__pycache__/placement_optimizer.cpython-312.pyc b/tools/elite-pcb-designer/optimizers/__pycache__/placement_optimizer.cpython-312.pyc deleted file mode 100644 index 96afca0..0000000 Binary files a/tools/elite-pcb-designer/optimizers/__pycache__/placement_optimizer.cpython-312.pyc and /dev/null differ diff --git a/tools/elite-pcb-designer/validators/__pycache__/dfm_validator.cpython-312.pyc b/tools/elite-pcb-designer/validators/__pycache__/dfm_validator.cpython-312.pyc deleted file mode 100644 index 1682e74..0000000 Binary files a/tools/elite-pcb-designer/validators/__pycache__/dfm_validator.cpython-312.pyc and /dev/null differ diff --git a/tools/kicad-footprint-hunter/core/__pycache__/__init__.cpython-312.pyc b/tools/kicad-footprint-hunter/core/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index 37ddf0b..0000000 Binary files a/tools/kicad-footprint-hunter/core/__pycache__/__init__.cpython-312.pyc and /dev/null differ diff --git a/tools/kicad-footprint-hunter/core/__pycache__/footprint_hunter.cpython-312.pyc b/tools/kicad-footprint-hunter/core/__pycache__/footprint_hunter.cpython-312.pyc deleted file mode 100644 index b16a3bc..0000000 Binary files a/tools/kicad-footprint-hunter/core/__pycache__/footprint_hunter.cpython-312.pyc and /dev/null differ diff --git a/tools/kicad-footprint-hunter/core/__pycache__/footprint_scraper.cpython-312.pyc b/tools/kicad-footprint-hunter/core/__pycache__/footprint_scraper.cpython-312.pyc deleted file mode 100644 index b2334c7..0000000 Binary files a/tools/kicad-footprint-hunter/core/__pycache__/footprint_scraper.cpython-312.pyc and /dev/null differ diff --git a/tools/kicad-footprint-hunter/core/__pycache__/netlist_parser.cpython-312.pyc b/tools/kicad-footprint-hunter/core/__pycache__/netlist_parser.cpython-312.pyc deleted file mode 100644 index d5caded..0000000 Binary files a/tools/kicad-footprint-hunter/core/__pycache__/netlist_parser.cpython-312.pyc and /dev/null differ diff --git a/tools/kicad-footprint-hunter/core/__pycache__/pcb_updater.cpython-312.pyc b/tools/kicad-footprint-hunter/core/__pycache__/pcb_updater.cpython-312.pyc deleted file mode 100644 index 71d2e71..0000000 Binary files a/tools/kicad-footprint-hunter/core/__pycache__/pcb_updater.cpython-312.pyc and /dev/null differ