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:
344
docs/JLCPCB_INTEGRATION.md
Normal file
344
docs/JLCPCB_INTEGRATION.md
Normal 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.
|
||||
125
docs/SCHEMATIC_WORKFLOW_FIX.md
Normal file
125
docs/SCHEMATIC_WORKFLOW_FIX.md
Normal 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/`
|
||||
Reference in New Issue
Block a user