feat: Complete Phase 1 & 2 - Schematic workflow fix and JLCPCB integration

Phase 1: Schematic Workflow Fix (Issue #26)
- Fixed broken schematic workflow using template-based symbol cloning
- Updated create_project to generate both PCB and schematic files
- Rewrote add_schematic_component to use kicad-skip clone() API
- Added template schematics with cloneable R, C, LED symbols
- All schematic tests now passing

Phase 2: JLCPCB Integration Complete
- Integrated JLCSearch public API (no authentication required)
- Access to ~100k JLCPCB parts with real-time stock and pricing
- Implemented parametric search for resistors, capacitors, components
- Added package-to-footprint mapping for KiCad integration
- Cost optimization with Basic vs Extended library classification
- Alternative part suggestions with price comparison

New Components:
- python/commands/jlcsearch.py - JLCSearch API client
- python/templates/ - Template schematics for symbol cloning
- docs/JLCPCB_INTEGRATION.md - Comprehensive API documentation
- docs/SCHEMATIC_WORKFLOW_FIX.md - Phase 1 technical details
- CHANGELOG.md - Consolidated unified changelog
- PHASE_2_COMPLETE.md - Phase 2 implementation summary

MCP Tools Available:
- download_jlcpcb_database - Download full parts catalog
- search_jlcpcb_parts - Parametric search with filters
- get_jlcpcb_part - Part details + footprint suggestions
- get_jlcpcb_database_stats - Database statistics
- suggest_jlcpcb_alternatives - Find similar/cheaper parts

Technical Improvements:
- SQLite database with FTS5 full-text search
- HMAC-SHA256 authentication support (official JLCPCB API)
- Improved .gitignore to exclude credentials and databases
- Template-based schematic creation workflow

Testing:
- All integration tests passing
- Database operations validated
- Live API connectivity confirmed
- Schematic workflow end-to-end verified

Credits:
- JLCSearch API: @tscircuit
- Local JLCPCB search: @l3wi

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
KiCAD MCP Bot
2026-01-10 09:04:46 -05:00
parent d5402e134a
commit 994965e041
14 changed files with 1848 additions and 118 deletions

344
docs/JLCPCB_INTEGRATION.md Normal file
View File

@@ -0,0 +1,344 @@
# JLCPCB Parts Integration - Complete Guide
## Overview
The KiCAD MCP Server integrates with JLCPCB's parts library to provide intelligent component selection, cost optimization, and automated part sourcing for PCB assembly.
**Current Implementation**: Uses the **JLCSearch public API** (by tscircuit) for free, unauthenticated access to JLCPCB's ~100k parts catalog.
## Features
**Parametric Search** - Find components by specifications (resistance, capacitance, package, etc.)
**Price Comparison** - Compare Basic vs Extended library pricing
**Alternative Suggestions** - Find cheaper or higher-stock alternatives
**Footprint Mapping** - Automatic JLCPCB package to KiCad footprint mapping
**Stock Availability** - Real-time stock levels from JLCPCB
**No Authentication Required** - Public API, no API keys needed
## Quick Start
### 1. Search for Components
```python
from commands.jlcsearch import JLCSearchClient
client = JLCSearchClient()
# Search for resistors
resistors = client.search_resistors(
resistance=10000, # 10kΩ
package="0603",
limit=20
)
# Search for capacitors
capacitors = client.search_capacitors(
capacitance=1e-7, # 100nF
package="0603",
limit=20
)
# General component search
components = client.search_components(
"components",
package="0603",
limit=100
)
```
### 2. Get Part Details
```python
# Get specific part by LCSC number
part = client.get_part_by_lcsc(25804) # C25804
print(f"Part: {part['mfr']}")
print(f"Stock: {part['stock']}")
print(f"Price: ${part['price1']}")
print(f"Basic Library: {part['is_basic']}")
```
### 3. Database Integration
```python
from commands.jlcpcb_parts import JLCPCBPartsManager
# Initialize database
db = JLCPCBPartsManager() # Uses data/jlcpcb_parts.db
# Download and import parts (one-time setup)
client = JLCSearchClient()
parts = client.download_all_components()
db.import_jlcsearch_parts(parts)
# Search imported database
results = db.search_parts(
query="resistor",
package="0603",
library_type="Basic",
in_stock=True,
limit=20
)
```
### 4. Footprint Mapping
```python
# Map JLCPCB package to KiCad footprints
footprints = db.map_package_to_footprint("0603")
# Returns:
# [
# "Resistor_SMD:R_0603_1608Metric",
# "Capacitor_SMD:C_0603_1608Metric",
# "LED_SMD:LED_0603_1608Metric"
# ]
```
## API Reference
### JLCSearchClient
#### `search_resistors(resistance, package, limit)`
Search for resistors by value and package.
**Parameters:**
- `resistance` (int, optional): Resistance in ohms
- `package` (str, optional): Package size ("0402", "0603", "0805", etc.)
- `limit` (int): Maximum results (default: 100)
**Returns:** List of resistor dicts with fields:
- `lcsc`: LCSC number (integer)
- `mfr`: Manufacturer part number
- `package`: Package size
- `is_basic`: True if Basic library part (no assembly fee)
- `resistance`: Resistance in ohms
- `tolerance_fraction`: Tolerance (0.01 = 1%)
- `power_watts`: Power rating in mW
- `stock`: Available stock
- `price1`: Unit price in USD
#### `search_capacitors(capacitance, package, limit)`
Search for capacitors by value and package.
**Parameters:**
- `capacitance` (float, optional): Capacitance in farads (e.g., 1e-7 for 100nF)
- `package` (str, optional): Package size
- `limit` (int): Maximum results
**Returns:** List of capacitor dicts
#### `search_components(category, limit, offset, **filters)`
General component search.
**Parameters:**
- `category` (str): "resistors", "capacitors", "components", etc.
- `limit` (int): Maximum results
- `offset` (int): Pagination offset
- `**filters`: Additional filters (package="0603", lcsc=25804, etc.)
**Returns:** List of component dicts
#### `download_all_components(callback, batch_size)`
Download entire JLCPCB parts catalog.
**Parameters:**
- `callback` (callable, optional): Progress callback(parts_count, status_msg)
- `batch_size` (int): Parts per batch (default: 1000)
**Returns:** List of all parts (~100k components)
**Note:** This may take 5-10 minutes to complete.
### JLCPCBPartsManager
#### `import_jlcsearch_parts(parts, progress_callback)`
Import parts from JLCSearch into local SQLite database.
**Parameters:**
- `parts` (list): List of part dicts from JLCSearchClient
- `progress_callback` (callable, optional): Progress updates
#### `search_parts(query, category, package, library_type, manufacturer, in_stock, limit)`
Search local database with filters.
**Parameters:**
- `query` (str, optional): Free-text search
- `category` (str, optional): Category filter
- `package` (str, optional): Package filter
- `library_type` (str, optional): "Basic", "Extended", or "Preferred"
- `manufacturer` (str, optional): Manufacturer filter
- `in_stock` (bool): Only in-stock parts (default: True)
- `limit` (int): Maximum results
**Returns:** List of matching parts
#### `get_part_info(lcsc_number)`
Get detailed part information.
**Parameters:**
- `lcsc_number` (str): LCSC part number (e.g., "C25804")
**Returns:** Part dict or None
#### `get_database_stats()`
Get database statistics.
**Returns:** Dict with:
- `total_parts`: Total parts count
- `basic_parts`: Basic library count
- `extended_parts`: Extended library count
- `in_stock`: Parts with stock > 0
- `db_path`: Database file path
#### `map_package_to_footprint(package)`
Map JLCPCB package to KiCad footprints.
**Parameters:**
- `package` (str): JLCPCB package name
**Returns:** List of KiCad footprint library references
## Data Format
### JLCSearch Part Object
```json
{
"lcsc": 25804,
"mfr": "0603WAF1002T5E",
"package": "0603",
"is_basic": true,
"is_preferred": false,
"resistance": 10000,
"tolerance_fraction": 0.01,
"power_watts": 100,
"stock": 37165617,
"price1": 0.000842857
}
```
### Database Schema
```sql
CREATE TABLE components (
lcsc TEXT PRIMARY KEY, -- "C25804"
category TEXT, -- "Resistors"
subcategory TEXT, -- "Chip Resistor"
mfr_part TEXT, -- "0603WAF1002T5E"
package TEXT, -- "0603"
solder_joints INTEGER,
manufacturer TEXT,
library_type TEXT, -- "Basic" or "Extended"
description TEXT, -- "10kΩ ±1% 100mW"
datasheet TEXT,
stock INTEGER,
price_json TEXT, -- JSON array of price breaks
last_updated INTEGER -- Unix timestamp
);
```
## Package to Footprint Mappings
| JLCPCB Package | KiCad Footprints |
|----------------|------------------|
| 0402 | Resistor_SMD:R_0402_1005Metric<br>Capacitor_SMD:C_0402_1005Metric<br>LED_SMD:LED_0402_1005Metric |
| 0603 | Resistor_SMD:R_0603_1608Metric<br>Capacitor_SMD:C_0603_1608Metric<br>LED_SMD:LED_0603_1608Metric |
| 0805 | Resistor_SMD:R_0805_2012Metric<br>Capacitor_SMD:C_0805_2012Metric |
| 1206 | Resistor_SMD:R_1206_3216Metric<br>Capacitor_SMD:C_1206_3216Metric |
| SOT-23 | Package_TO_SOT_SMD:SOT-23<br>Package_TO_SOT_SMD:SOT-23-3 |
| SOT-23-5 | Package_TO_SOT_SMD:SOT-23-5 |
| SOT-23-6 | Package_TO_SOT_SMD:SOT-23-6 |
| SOT-223 | Package_TO_SOT_SMD:SOT-223 |
| SOIC-8 | Package_SO:SOIC-8_3.9x4.9mm_P1.27mm |
| QFN-20 | Package_DFN_QFN:QFN-20-1EP_4x4mm_P0.5mm_EP2.5x2.5mm |
## Best Practices
### 1. Always Use Basic Library Parts First
Basic library parts have **no assembly fee** ($0/part), while Extended parts cost **$3/part**.
```python
# Filter for Basic parts only
basic_parts = [p for p in results if p['is_basic']]
```
### 2. Check Stock Availability
Ensure sufficient stock before committing to a design.
```python
# Only use parts with >1000 stock
high_stock = [p for p in results if p['stock'] > 1000]
```
### 3. Compare Prices
Even within Basic library, prices vary significantly.
```python
# Find cheapest option
cheapest = min(results, key=lambda x: x.get('price1', 999))
```
### 4. Use Standardized Packages
Stick to common packages (0402, 0603, 0805) for better availability and pricing.
### 5. Cache Database Locally
Download the full parts database once and search locally for faster results.
```python
# Initial download (one-time, ~5-10 minutes)
if not os.path.exists("data/jlcpcb_parts.db"):
parts = client.download_all_components()
db.import_jlcsearch_parts(parts)
# Subsequent searches use local database (instant)
results = db.search_parts(...)
```
## Troubleshooting
### API Rate Limiting
JLCSearch is a community service. If you hit rate limits:
- Add delays between requests (`time.sleep(0.1)`)
- Use the local database instead of repeated API calls
- Download the full database once and work offline
### Missing Data
JLCSearch may not have all fields that official JLCPCB API provides:
- No datasheets (use manufacturer website)
- Limited category information
- No solder joint count
### Stock Discrepancies
Stock levels are updated periodically but may lag real-time JLCPCB data by a few hours.
## Official JLCPCB API (Alternative)
The project also includes an implementation of the official JLCPCB API with HMAC-SHA256 authentication. However, this requires:
1. API approval from JLCPCB (not all applications are approved)
2. APP_ID, ACCESS_KEY, and SECRET_KEY credentials
3. Previous order history with JLCPCB
To use the official API instead of JLCSearch:
```python
from commands.jlcpcb import JLCPCBClient
# Set credentials in .env file:
# JLCPCB_APP_ID=<your_app_id>
# JLCPCB_API_KEY=<your_access_key>
# JLCPCB_API_SECRET=<your_secret_key>
client = JLCPCBClient(app_id, access_key, secret_key)
data = client.fetch_parts_page()
```
**Note:** Most users should use JLCSearch public API instead, as it's freely available and requires no authentication.
## Credits
- **JLCSearch API**: https://jlcsearch.tscircuit.com/ (by [@tscircuit](https://github.com/tscircuit/jlcsearch))
- **JLCParts Database**: https://github.com/yaqwsx/jlcparts (by [@yaqwsx](https://github.com/yaqwsx))
- **JLCPCB**: https://jlcpcb.com/ (official parts library provider)
## License
This integration uses publicly available JLCPCB parts data via the JLCSearch community service. Users must comply with JLCPCB's terms of service when using this data for production PCB orders.

View File

@@ -0,0 +1,125 @@
# Schematic Workflow Fix - Issue #26
## Problem Summary
The schematic workflow was completely broken due to incorrect usage of the kicad-skip library:
1. **`create_project`** only created PCB files, no schematic
2. **`create_schematic`** created orphaned schematic files not linked to projects
3. **`add_schematic_component`** called non-existent `schematic.add_symbol()` method
4. Project files didn't reference schematics in their structure
## Root Cause
The kicad-skip library **does not support creating symbols from scratch**. The only way to add symbols is by **cloning existing symbol instances**.
From kicad-skip documentation:
> "symbols: these don't have a new()" because they require complex mappings to library elements, pins, and properties.
## Solution
### 1. Template-Based Approach
Created a template schematic (`python/templates/template_with_symbols.kicad_sch`) with:
- Complete `lib_symbols` section defining R, C, LED symbols
- Three template symbol instances placed off-screen at (-100, -110, -120)
- Template symbols marked as `dnp yes`, `in_bom no`, `on_board no` so they don't interfere
### 2. Updated Files
**python/commands/project.py:**
- Now creates both `.kicad_pcb` AND `.kicad_sch` files
- Project file includes schematic reference in `sheets` array
- Copies template schematic with cloneable symbols
**python/commands/schematic.py:**
- Uses template file instead of creating from scratch
- Proper minimal schematic structure when template unavailable
**python/commands/component_schematic.py:**
- Completely rewritten to use `clone()` API
- Maps component types to template symbols
- Proper UUID generation for each cloned symbol
- Correct position setting: `symbol.at.value = [x, y, rotation]`
### 3. Correct Workflow
```python
from commands.project import ProjectCommands
from commands.schematic import SchematicManager
from commands.component_schematic import ComponentManager
# Step 1: Create project (creates both PCB and schematic)
project_cmd = ProjectCommands()
result = project_cmd.create_project({
"name": "MyProject",
"path": "/path/to/project"
})
# Step 2: Load the schematic
sch = SchematicManager.load_schematic(result['project']['schematicPath'])
# Step 3: Add components by cloning templates
component_def = {
"type": "R", # Maps to _TEMPLATE_R
"reference": "R1", # Component reference
"value": "10k", # Component value
"footprint": "Resistor_SMD:R_0603_1608Metric",
"x": 50.8, # Position in mm
"y": 50.8, # Position in mm
"rotation": 0 # Rotation in degrees
}
symbol = ComponentManager.add_component(sch, component_def)
# Step 4: Save the schematic
SchematicManager.save_schematic(sch, result['project']['schematicPath'])
```
## Supported Component Types
Currently supported template symbols:
- `R` - Resistor (maps to `_TEMPLATE_R`)
- `C` - Capacitor (maps to `_TEMPLATE_C`)
- `D` or `LED` - LED (maps to `_TEMPLATE_D`)
To add more component types, update:
1. `python/templates/template_with_symbols.kicad_sch` - Add lib_symbol definition and template instance
2. `python/commands/component_schematic.py` - Add mapping in `TEMPLATE_MAP`
## Testing
Comprehensive test created at `/tmp/test_schematic_workflow.py`:
- Creates project with schematic
- Loads schematic
- Adds R, C, LED components
- Saves schematic
- Validates with `kicad-cli sch export pdf`
All tests passing ✓
## Files Modified
- `python/commands/project.py` - Added schematic creation
- `python/commands/schematic.py` - Fixed template usage
- `python/commands/component_schematic.py` - Rewritten to use clone() API
- `python/templates/empty.kicad_sch` - Minimal template (created)
- `python/templates/template_with_symbols.kicad_sch` - Template with cloneable symbols (created)
## Limitations
1. Can only add components that have templates defined
2. Template symbols remain in schematic (but marked as DNP/not in BOM)
3. Complex symbols (multi-unit, hierarchical) may need custom templates
## Future Improvements
1. Add more component templates (transistors, connectors, ICs)
2. Dynamic template generation from KiCad symbol libraries
3. Auto-hide template symbols in schematic
4. Support for custom user templates
## References
- GitHub Issue: #26
- kicad-skip documentation: https://github.com/psychogenic/kicad-skip
- Test results: `/tmp/test_schematic_workflow/`