fix: Address critical Windows compatibility and JLCPCB issues (#36, #37, #35)

Phase 1 critical fixes addressing three high-priority issues:

Issue #36 - Windows IPC backend crash (os.getuid not available):
- Add platform detection to skip Unix-specific socket paths on Windows
- Use hasattr check before calling os.getuid()
- Windows now uses auto-detect fallback (named pipes)
- Maintains full Unix socket support on Linux/macOS

Issue #37 - Windows schematic file creation broken:
- Generate unique UUIDs instead of invalid all-zeros UUID
- Add explicit UTF-8 encoding for cross-platform compatibility
- Force Unix line endings (LF) to prevent Windows CRLF issues
- Update template file with valid placeholder UUID
- Fix hardcoded /tmp/ paths in wire_manager.py and pin_locator.py

Issue #35 - JLCPCB download limited to 100 parts:
- Change batch_size from 1000 to 100 to match tscircuit API limit
- Remove premature loop termination when batch < batch_size
- Add documentation explaining API limitation
- Expected result: Full ~2.5M part catalog download (40-60 minutes)

All changes maintain backward compatibility and include detailed comments.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
KiCAD MCP Bot
2026-02-26 10:03:20 -05:00
parent b9bd401b11
commit b4d114ac74
6 changed files with 33 additions and 19 deletions

View File

@@ -143,14 +143,17 @@ class JLCSearchClient:
def download_all_components( def download_all_components(
self, self,
callback: Optional[Callable[[int, str], None]] = None, callback: Optional[Callable[[int, str], None]] = None,
batch_size: int = 1000 batch_size: int = 100
) -> List[Dict]: ) -> List[Dict]:
""" """
Download all components from jlcsearch database Download all components from jlcsearch database
Note: tscircuit API has a hard-coded 100 result limit per request.
Full catalog download requires ~25,000 paginated requests (~40-60 minutes).
Args: Args:
callback: Optional progress callback function(parts_count, status_msg) callback: Optional progress callback function(parts_count, status_msg)
batch_size: Number of parts per batch batch_size: Number of parts per batch (max 100 due to API limit)
Returns: Returns:
List of all parts List of all parts
@@ -168,7 +171,8 @@ class JLCSearchClient:
offset=offset offset=offset
) )
if not batch: # Stop if no results returned (end of catalog)
if not batch or len(batch) == 0:
break break
all_parts.extend(batch) all_parts.extend(batch)
@@ -179,9 +183,8 @@ class JLCSearchClient:
else: else:
logger.info(f"Downloaded {len(all_parts)} parts so far...") logger.info(f"Downloaded {len(all_parts)} parts so far...")
# If we got fewer results than requested, we've reached the end # Continue pagination - API returns exactly 100 results per page until exhausted
if len(batch) < batch_size: # Only stop when we get 0 results (handled above)
break
# Rate limiting - be nice to the API # Rate limiting - be nice to the API
time.sleep(0.1) time.sleep(0.1)

View File

@@ -7,6 +7,7 @@ Uses S-expression parsing to extract pin data from symbol definitions.
import logging import logging
import math import math
import tempfile
from pathlib import Path from pathlib import Path
from typing import List, Tuple, Optional, Dict from typing import List, Tuple, Optional, Dict
import sexpdata import sexpdata
@@ -314,8 +315,8 @@ if __name__ == '__main__':
print("PIN LOCATOR TEST") print("PIN LOCATOR TEST")
print("=" * 80) print("=" * 80)
# Create test schematic with components # Create test schematic with components (cross-platform temp directory)
test_path = Path('/tmp/test_pin_locator.kicad_sch') test_path = Path(tempfile.gettempdir()) / 'test_pin_locator.kicad_sch'
template_path = Path('/home/chris/MCP/KiCAD-MCP-Server/python/templates/template_with_symbols_expanded.kicad_sch') template_path = Path('/home/chris/MCP/KiCAD-MCP-Server/python/templates/template_with_symbols_expanded.kicad_sch')
shutil.copy(template_path, test_path) shutil.copy(template_path, test_path)

View File

@@ -2,6 +2,7 @@ from skip import Schematic
import os import os
import shutil import shutil
import logging import logging
import uuid
logger = logging.getLogger('kicad_interface') logger = logging.getLogger('kicad_interface')
@@ -28,9 +29,12 @@ class SchematicManager:
else: else:
# Fallback: create minimal schematic # Fallback: create minimal schematic
logger.warning(f"Template not found at {template_path}, creating minimal schematic") logger.warning(f"Template not found at {template_path}, creating minimal schematic")
with open(output_path, 'w') as f: # Generate unique UUID for this schematic
schematic_uuid = str(uuid.uuid4())
# Write with explicit UTF-8 encoding and Unix line endings for cross-platform compatibility
with open(output_path, 'w', encoding='utf-8', newline='\n') as f:
f.write('(kicad_sch (version 20230121) (generator "KiCAD-MCP-Server")\n\n') f.write('(kicad_sch (version 20230121) (generator "KiCAD-MCP-Server")\n\n')
f.write(' (uuid 00000000-0000-0000-0000-000000000000)\n\n') f.write(f' (uuid {schematic_uuid})\n\n')
f.write(' (paper "A4")\n\n') f.write(' (paper "A4")\n\n')
f.write(' (lib_symbols\n )\n\n') f.write(' (lib_symbols\n )\n\n')
f.write(' (sheet_instances\n (path "/" (page "1"))\n )\n') f.write(' (sheet_instances\n (path "/" (page "1"))\n )\n')

View File

@@ -9,6 +9,7 @@ manipulate the .kicad_sch file directly.
import uuid import uuid
import logging import logging
import math import math
import tempfile
from pathlib import Path from pathlib import Path
from typing import List, Tuple, Optional, Dict from typing import List, Tuple, Optional, Dict
import sexpdata import sexpdata
@@ -382,8 +383,8 @@ if __name__ == '__main__':
print("WIRE MANAGER TEST") print("WIRE MANAGER TEST")
print("=" * 80) print("=" * 80)
# Create test schematic # Create test schematic (cross-platform temp directory)
test_path = Path('/tmp/test_wire_manager.kicad_sch') test_path = Path(tempfile.gettempdir()) / 'test_wire_manager.kicad_sch'
template_path = Path('/home/chris/MCP/KiCAD-MCP-Server/python/templates/empty.kicad_sch') template_path = Path('/home/chris/MCP/KiCAD-MCP-Server/python/templates/empty.kicad_sch')
shutil.copy(template_path, test_path) shutil.copy(template_path, test_path)

View File

@@ -15,6 +15,7 @@ Key Benefits over SWIG:
""" """
import logging import logging
import os import os
import platform
from pathlib import Path from pathlib import Path
from typing import Optional, Dict, Any, List, Callable from typing import Optional, Dict, Any, List, Callable
@@ -74,12 +75,16 @@ class IPCBackend(KiCADBackend):
if socket_path: if socket_path:
socket_paths_to_try.append(socket_path) socket_paths_to_try.append(socket_path)
else: else:
# Common socket locations # Common socket locations (Unix-like systems only)
socket_paths_to_try = [ # Windows uses named pipes, handled by auto-detect
'ipc:///tmp/kicad/api.sock', # Linux default if platform.system() != "Windows":
f'ipc:///run/user/{os.getuid()}/kicad/api.sock', # XDG runtime socket_paths_to_try.append('ipc:///tmp/kicad/api.sock') # Linux default
None # Let kipy auto-detect # XDG runtime directory (requires getuid, Unix only)
] if hasattr(os, 'getuid'):
socket_paths_to_try.append(f'ipc:///run/user/{os.getuid()}/kicad/api.sock')
# Auto-detect for all platforms (Windows uses named pipes, Unix uses sockets)
socket_paths_to_try.append(None)
last_error = None last_error = None
for path in socket_paths_to_try: for path in socket_paths_to_try:

View File

@@ -1,6 +1,6 @@
(kicad_sch (version 20230121) (generator "KiCAD-MCP-Server") (kicad_sch (version 20230121) (generator "KiCAD-MCP-Server")
(uuid 00000000-0000-0000-0000-000000000000) (uuid a1b2c3d4-e5f6-4a5b-8c9d-0e1f2a3b4c5d)
(paper "A4") (paper "A4")