Skip to content

Latest commit

 

History

History
271 lines (213 loc) · 9.75 KB

File metadata and controls

271 lines (213 loc) · 9.75 KB

OpenFixture - PCB Test Fixture Generator

Automated laser-cuttable PCB test fixture generation integrated with KiCAD 8.0 and 9.0+.

Project Structure

Modern src-layout (Python packaging best practice):

openfixture/
├── src/
│   ├── __init__.py                    # Plugin registration
│   ├── openfixture.py                 # KiCAD ActionPlugin
│   └── openfixture_support/           # Core package
│       ├── __init__.py
│       ├── GenFixture.py              # Main processing engine
│       ├── openfixture.scad           # OpenSCAD generator
│       └── fixture_config.toml        # Configuration template
├── build.py                           # KiCAD plugin build system
├── setup.py                           # Package installation
├── pyproject.toml                     # Modern Python packaging
└── .github/
    └── copilot-instructions.md        # This file

Benefits of src-layout:

  • Prevents accidental imports from development directory
  • Clear separation between source and built artifacts
  • Standard Python packaging structure
  • Matches OrthoRoute project pattern

Architecture

Dual-interface system:

  • openfixture.py: KiCAD plugin (wxPython UI) → calls GenFixture.py
  • GenFixture.py: Core processing engine (CLI + plugin backend)
  • openfixture.scad: OpenSCAD parametric fixture generator

Processing flow:

KiCAD PCB → GenFixture.py (extract test points + export DXF) 
           → OpenSCAD (generate 3D model + laser-cut DXF)

See README.md for features and installation.

Build & Deployment

Development Workflow (RECOMMENDED)

Fast sync for development and code verification:

# 1. Configure paths (one-time setup):
cp sync_to_kicad_config.ps1.template sync_to_kicad_config.ps1
# Edit sync_to_kicad_config.ps1 with your KiCAD plugins path

# 2. Fast sync to KiCAD (copies from src/ to plugins directory):
.\sync_to_kicad.ps1

# 3. Restart KiCAD to load changes

Why use sync_to_kicad.ps1?

  • ✅ Fastest way to test code changes (no rebuild needed)
  • ✅ Automatically clears Python cache to force reload
  • ✅ Copies directly from src/ to KiCAD plugins directory
  • ✅ Allows immediate verification of changes in KiCAD
  • ✅ Auto-detects KiCAD path or uses custom config

Production Build (for distribution)

Build KiCAD plugin package:

# Build package + ZIP for distribution
python build.py

# Build and deploy to local KiCAD installation
python build.py --deploy

# Build package directory only (no ZIP)
python build.py --no-zip

# Clean build artifacts
python build.py --clean

When to use build.py:

  • Creating release packages for distribution
  • Generating the official plugin ZIP file
  • Deploying to KiCAD Plugin Manager (PCM)

Script Inventory

Active Scripts:

  • sync_to_kicad.ps1 - Primary development workflow (fast sync + cache clear)
  • build.py - Production build system (creates distribution packages)
  • deploy_to_repository.ps1 - External deployment to KiCAD-Plugin distribution repo

Deprecated Scripts (outdated for src-layout):

  • clean_and_deploy.ps1 - References old flat structure
  • force_update.ps1 - References old flat structure
  • test_functionality.ps1 - References non-existent v2 files

Output directory structure (after build):

build/
└── com_github_RolandWa_openfixture/  # Plugin package
    ├── __init__.py                   # Plugin registration
    ├── openfixture.py                # Main plugin file
    ├── OpenFixture.png               # Icon
    ├── plugin.json                   # KiCAD plugin descriptor
    ├── metadata.json                 # KiCAD PCM metadata
    ├── openfixture_support/          # Core package
    │   ├── GenFixture.py
    │   ├── openfixture.scad
    │   └── fixture_config.toml
    └── README.md                     # Documentation

KiCAD installation structure (after deploy):

KiCAD\9.0\3rdparty\plugins\
└── com_github_RolandWa_openfixture/
    ├── __init__.py
    ├── openfixture.py
    ├── OpenFixture.png
    └── openfixture_support/
        ├── GenFixture.py
        ├── openfixture.scad
        └── fixture_config.toml

Code Conventions

KiCAD API Compatibility

Always wrap KiCAD 9.0 breaking changes with try-except:

# KiCAD 9 removed GetAuxOrigin/SetAuxOrigin
try:
    aux_origin_save = self.brd.GetAuxOrigin()
except AttributeError:
    logger.warning("GetAuxOrigin not available (KiCAD 9+), continuing...")

Use modern API methods:

  • brd.GetFootprints() (not GetModules())
  • VECTOR2I coordinates with direct division (not wxPoint, ToMM())
  • DXF_PLOTTER.DXF_PLOTTER_UNITS_MILLIMETERS constant (KiCAD 9+)

Python Subprocess Calls

Always use list-based commands (avoids shell escaping):

cmd = [openscad_exe, '-D', f'mode="lasercut"', '-o', output_file, scad_file]
result = subprocess.run(cmd, capture_output=True, text=True, timeout=120)

Windows path handling for OpenSCAD:

# Convert backslashes to forward slashes (OpenSCAD uses Unix paths)
outline_path.replace("\\", "/")

Test Point Extraction Logic

Inclusion criteria (implemented in get_test_points()):

  1. Force include: Pad on Eco2.User layer → always include
  2. Force exclude: Pad on Eco1.User layer → always exclude
  3. Paste mask check: Pad has paste → exclude (not a test point)
  4. SMD pads: Include if config.include_smd_pads = true
  5. PTH pads: Include ONLY when component is on opposite side of test layer
    • Rationale: Component body blocks access from same side
    • Example: Testing bottom layer → include PTH from top-side components only

Carrier Centering Algorithm for Scaled Cutouts

Critical geometry calculation (module carrier() in openfixture.scad):

Problem: When OpenSCAD scales a DXF with scale([scale_x, scale_y]), it scales all coordinates including the board origin position. For a centered carrier plate with inset border, simple offset formulas fail.

Example (real-world values causing the bug):

  • Board origin in DXF: (155, 113) mm (absolute KiCAD coordinates)
  • Board size: (72, 45) mm
  • Bottom carrier inset: 1 mm → scale_x = 0.972, scale_y = 0.978
  • Board center at: (155 + 72/2, 113 + 45/2) = (191, 135.5) mm
  • After scaling: (191 × 0.972, 135.5 × 0.978) = (185.65, 131.71) mm
  • Shift needed: 5.35 mm in X, 3.79 mm in Y

Correct formula:

sx_offset = (board_origin_x + pcb_x / 2) * (1 - scale_x);
sy_offset = (board_origin_y + pcb_y / 2) * (1 - scale_y);

Why it works:

  1. board_origin_x + pcb_x/2 = board center X in original coordinates
  2. When scaled by scale_x, center moves to center * scale_x
  3. Shift amount = center - center * scale_x = center * (1 - scale_x)
  4. Translation by this offset re-centers the scaled board

Why simple formulas fail:

  • (pcb_x - pcb_x * scale_x) / 2 - Only works when board origin is (0, 0)
  • border / 2 - Doesn't account for scaling affecting board_origin
  • ✅ Must account for both board size AND board origin being scaled

Coordinate system evolution:

  • Old versions (pre-KiCAD 9): Boards exported at (0, 0) → simple offset worked
  • KiCAD 9: Absolute coordinates (board_origin_x/y can be large) → needs full formula

Testing: Use mode = "check_aligned" in OpenSCAD to verify cyan (bottom carrier) and magenta (top carrier) cutouts are perfectly concentric.

Configuration Pattern

Priority: CLI args > TOML config > hardcoded defaults

# Try TOML libraries in order:
try:
    import tomllib  # Python 3.11+
except ImportError:
    try:
        import tomli as tomllib  # pip install tomli
    except ImportError:
        logger.warning("TOML support unavailable, using defaults")

Common Gotchas

Deployment Issues

Problem Cause Solution
Plugin doesn't load Wrong path in config Re-run sync after editing sync_to_kicad_config.ps1
Import errors after update Stale .pyc cache sync_to_kicad.ps1 auto-clears cache
Changes not visible KiCAD still running Restart KiCAD after running sync script
Code changes don't apply Wrong Python executing Check KiCAD uses bundled Python

Test Point Extraction

Problem Cause Solution
"No test points found" SMD pads have paste mask Remove paste mask in footprint editor
Missing obvious pads Wrong layer selected Check test_layer config (F.Cu, B.Cu, or both)
PTH pads not included Component on same side By design (component blocks access)

OpenSCAD Integration

Problem Cause Solution
OpenSCAD not found Not in PATH Plugin auto-searches common Windows paths
Timeout (>120s) Complex board geometry Simplify outline or use testcut mode

File Organization

Security-sensitive paths (excluded from git):

  • sync_to_kicad_config.ps1 - Personal KiCAD installation paths
  • __pycache__/ - Python bytecode cache

Output files generated by GenFixture:

  • {board}-outline.dxf - Board perimeter from Edge.Cuts
  • {board}-track.dxf - Copper tracks for alignment verification
  • {board}-fixture.dxf - Laser-cuttable fixture parts
  • {board}-fixture.png - 3D preview rendering
  • {board}-test.dxf - Test point validation piece

Development Resources