Add 8HP layout with fully automated routing pipeline

- Update PCB to 8HP format (40x100mm) with v2 component placement
- Add automated routing scripts (autoroute.py runs full pipeline headlessly)
- Update panel spec and SVG for 8HP dimensions
- Board routes in <1 second with 0 unconnected pads

Scripts:
- autoroute.py: Full CLI pipeline (place → export → route → import → DRC)
- autoroute_full.py: Same pipeline for KiCad scripting console
- place_8hp.py: Component placement only
- route.sh/freeroute.sh: Routing helpers
This commit is contained in:
2026-01-23 07:59:50 +01:00
parent cd337b8718
commit 1ae49dc1bb
13 changed files with 15205 additions and 1905 deletions
File diff suppressed because it is too large Load Diff
+541 -17
View File
@@ -3,83 +3,585 @@
"3dviewports": [],
"design_settings": {
"defaults": {
"apply_defaults_to_fp_fields": false,
"apply_defaults_to_fp_shapes": false,
"apply_defaults_to_fp_text": false,
"board_outline_line_width": 0.1,
"copper_line_width": 0.2,
"copper_text_italic": false,
"copper_text_size_h": 1.5,
"copper_text_size_v": 1.5,
"copper_text_thickness": 0.3,
"copper_text_upright": false,
"courtyard_line_width": 0.05,
"dimension_precision": 4,
"dimension_units": 3,
"dimensions": {
"arrow_length": 1270000,
"extension_offset": 500000,
"keep_text_aligned": true,
"suppress_zeroes": true,
"text_position": 0,
"units_format": 0
},
"fab_line_width": 0.1,
"fab_text_italic": false,
"fab_text_size_h": 1.0,
"fab_text_size_v": 1.0,
"fab_text_thickness": 0.15,
"fab_text_upright": false,
"other_line_width": 0.15,
"other_text_italic": false,
"other_text_size_h": 1.0,
"other_text_size_v": 1.0,
"other_text_thickness": 0.15,
"other_text_upright": false,
"pads": {
"drill": 0.8,
"height": 1.27,
"width": 2.54
},
"silk_line_width": 0.15,
"silk_text_italic": false,
"silk_text_size_h": 1.0,
"silk_text_size_v": 1.0,
"silk_text_thickness": 0.15
"silk_text_thickness": 0.15,
"silk_text_upright": false,
"zones": {
"min_clearance": 0.5
}
},
"diff_pair_dimensions": [],
"drc_exclusions": [],
"meta": {
"version": 2
},
"rule_severities": {
"annular_width": "error",
"clearance": "error",
"connection_width": "warning",
"copper_edge_clearance": "error",
"copper_sliver": "warning",
"courtyards_overlap": "error",
"creepage": "error",
"diff_pair_gap_out_of_range": "error",
"diff_pair_uncoupled_length_too_long": "error",
"drill_out_of_range": "error",
"duplicate_footprints": "warning",
"extra_footprint": "warning",
"footprint": "error",
"footprint_filters_mismatch": "ignore",
"footprint_symbol_mismatch": "warning",
"footprint_type_mismatch": "ignore",
"hole_clearance": "error",
"hole_to_hole": "warning",
"holes_co_located": "warning",
"invalid_outline": "error",
"isolated_copper": "warning",
"item_on_disabled_layer": "error",
"items_not_allowed": "error",
"length_out_of_range": "error",
"lib_footprint_issues": "warning",
"lib_footprint_mismatch": "warning",
"malformed_courtyard": "error",
"microvia_drill_out_of_range": "error",
"mirrored_text_on_front_layer": "warning",
"missing_courtyard": "ignore",
"missing_footprint": "warning",
"net_conflict": "warning",
"nonmirrored_text_on_back_layer": "warning",
"npth_inside_courtyard": "ignore",
"padstack": "warning",
"pth_inside_courtyard": "ignore",
"shorting_items": "error",
"silk_edge_clearance": "warning",
"silk_over_copper": "warning",
"silk_overlap": "warning",
"skew_out_of_range": "error",
"solder_mask_bridge": "error",
"starved_thermal": "error",
"text_height": "warning",
"text_on_edge_cuts": "error",
"text_thickness": "warning",
"through_hole_pad_without_hole": "error",
"too_many_vias": "error",
"track_angle": "error",
"track_dangling": "warning",
"track_segment_length": "error",
"track_width": "error",
"tracks_crossing": "error",
"unconnected_items": "error",
"unresolved_variable": "error",
"via_dangling": "warning",
"zones_intersect": "error"
},
"rules": {
"max_error": 0.005,
"min_clearance": 0.2,
"min_connection": 0.0,
"min_copper_edge_clearance": 0.5,
"min_groove_width": 0.0,
"min_hole_clearance": 0.25,
"min_hole_to_hole": 0.25,
"min_microvia_diameter": 0.2,
"min_microvia_drill": 0.1,
"min_resolved_spokes": 2,
"min_silk_clearance": 0.0,
"min_text_height": 0.8,
"min_text_thickness": 0.08,
"min_through_hole_diameter": 0.3,
"min_track_width": 0.2,
"min_via_annular_width": 0.15,
"min_via_diameter": 0.5
"min_via_diameter": 0.5,
"solder_mask_to_copper_clearance": 0.0,
"use_height_for_length_calcs": true
},
"teardrop_options": [
{
"td_onpthpad": true,
"td_onroundshapesonly": false,
"td_onsmdpad": true,
"td_ontrackend": false,
"td_onvia": true
}
],
"teardrop_parameters": [
{
"td_allow_use_two_tracks": true,
"td_curve_segcount": 0,
"td_height_ratio": 1.0,
"td_length_ratio": 0.5,
"td_maxheight": 2.0,
"td_maxlen": 1.0,
"td_on_pad_in_zone": false,
"td_target_name": "td_round_shape",
"td_width_to_size_filter_ratio": 0.9
},
{
"td_allow_use_two_tracks": true,
"td_curve_segcount": 0,
"td_height_ratio": 1.0,
"td_length_ratio": 0.5,
"td_maxheight": 2.0,
"td_maxlen": 1.0,
"td_on_pad_in_zone": false,
"td_target_name": "td_rect_shape",
"td_width_to_size_filter_ratio": 0.9
},
{
"td_allow_use_two_tracks": true,
"td_curve_segcount": 0,
"td_height_ratio": 1.0,
"td_length_ratio": 0.5,
"td_maxheight": 2.0,
"td_maxlen": 1.0,
"td_on_pad_in_zone": false,
"td_target_name": "td_track_end",
"td_width_to_size_filter_ratio": 0.9
}
],
"track_widths": [
0.2,
0.3,
0.5,
0.8
],
"tuning_pattern_settings": {
"diff_pair_defaults": {
"corner_radius_percentage": 80,
"corner_style": 1,
"max_amplitude": 1.0,
"min_amplitude": 0.2,
"single_sided": false,
"spacing": 1.0
},
"diff_pair_skew_defaults": {
"corner_radius_percentage": 80,
"corner_style": 1,
"max_amplitude": 1.0,
"min_amplitude": 0.2,
"single_sided": false,
"spacing": 0.6
},
"single_track_defaults": {
"corner_radius_percentage": 80,
"corner_style": 1,
"max_amplitude": 1.0,
"min_amplitude": 0.2,
"single_sided": false,
"spacing": 0.6
}
},
"via_dimensions": [
{
"diameter": 0.6,
"drill": 0.3
}
]
],
"zones_allow_external_fillets": false
},
"ipc2581": {
"dist": "",
"distpn": "",
"internal_id": "",
"mfg": "",
"mpn": ""
},
"layer_pairs": [],
"layer_presets": [],
"viewports": []
},
"boards": [],
"cvpcb": {
"equivalence_files": []
},
"erc": {
"erc_exclusions": [],
"meta": {
"version": 0
},
"pin_map": [
[
0,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
2
],
[
0,
2,
0,
1,
0,
0,
1,
0,
2,
2,
2,
2
],
[
0,
0,
0,
0,
0,
0,
1,
0,
1,
0,
1,
2
],
[
0,
1,
0,
0,
0,
0,
1,
1,
2,
1,
1,
2
],
[
0,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
2
],
[
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
2
],
[
1,
1,
1,
1,
1,
0,
1,
1,
1,
1,
1,
2
],
[
0,
0,
0,
1,
0,
0,
1,
0,
0,
0,
0,
2
],
[
0,
2,
1,
2,
0,
0,
1,
0,
2,
2,
2,
2
],
[
0,
2,
0,
1,
0,
0,
1,
0,
2,
0,
0,
2
],
[
0,
2,
1,
1,
0,
0,
1,
0,
2,
0,
0,
2
],
[
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2
]
],
"rule_severities": {
"bus_definition_conflict": "error",
"bus_entry_needed": "error",
"bus_to_bus_conflict": "error",
"bus_to_net_conflict": "error",
"different_unit_footprint": "error",
"different_unit_net": "error",
"duplicate_reference": "error",
"duplicate_sheet_names": "error",
"endpoint_off_grid": "warning",
"extra_units": "error",
"footprint_filter": "ignore",
"footprint_link_issues": "warning",
"four_way_junction": "ignore",
"global_label_dangling": "warning",
"hier_label_mismatch": "error",
"label_dangling": "error",
"label_multiple_wires": "warning",
"lib_symbol_issues": "warning",
"lib_symbol_mismatch": "warning",
"missing_bidi_pin": "warning",
"missing_input_pin": "warning",
"missing_power_pin": "error",
"missing_unit": "warning",
"multiple_net_names": "warning",
"net_not_bus_member": "warning",
"no_connect_connected": "warning",
"no_connect_dangling": "warning",
"pin_not_connected": "error",
"pin_not_driven": "error",
"pin_to_pin": "warning",
"power_pin_not_driven": "error",
"same_local_global_label": "warning",
"similar_label_and_power": "warning",
"similar_labels": "warning",
"similar_power": "warning",
"simulation_model_issue": "ignore",
"single_global_label": "ignore",
"unannotated": "error",
"unconnected_wire_endpoint": "warning",
"unit_value_mismatch": "error",
"unresolved_variable": "error",
"wire_dangling": "error"
}
},
"libraries": {
"pinned_footprint_libs": [],
"pinned_symbol_libs": []
},
"meta": {
"filename": "SN-L00.kicad_pro",
"version": 1
"version": 3
},
"net_settings": {
"classes": [
{
"bus_width": 12,
"clearance": 0.2,
"diff_pair_gap": 0.25,
"diff_pair_via_gap": 0.25,
"diff_pair_width": 0.2,
"line_style": 0,
"microvia_diameter": 0.3,
"microvia_drill": 0.1,
"name": "Default",
"pcb_color": "rgba(0, 0, 0, 0.000)",
"priority": 2147483647,
"schematic_color": "rgba(0, 0, 0, 0.000)",
"track_width": 0.2,
"via_diameter": 0.6,
"via_drill": 0.3
},
{
"clearance": 0.3,
"name": "Power",
"track_width": 0.5,
"via_diameter": 0.8,
"via_drill": 0.4
"via_drill": 0.3,
"wire_width": 6
}
]
],
"meta": {
"version": 4
},
"net_colors": null,
"netclass_assignments": null,
"netclass_patterns": []
},
"pcbnew": {
"last_paths": {
"gencad": "",
"idf": "",
"netlist": "",
"specctra_dsn": "",
"step": ""
"netlist": "./",
"plot": "",
"pos_files": "",
"specctra_dsn": "SN-L00.dsn",
"step": "",
"svg": "",
"vrml": ""
},
"page_layout_descr_file": ""
},
"schematic": {
"annotate_start_num": 1,
"bom_export_filename": "${PROJECTNAME}.csv",
"bom_fmt_presets": [],
"bom_fmt_settings": {
"field_delimiter": ",",
"keep_line_breaks": false,
"keep_tabs": false,
"name": "CSV",
"ref_delimiter": ",",
"ref_range_delimiter": "",
"string_delimiter": "\""
},
"bom_presets": [],
"bom_settings": {
"exclude_dnp": false,
"fields_ordered": [
{
"group_by": false,
"label": "Reference",
"name": "Reference",
"show": true
},
{
"group_by": false,
"label": "Qty",
"name": "${QUANTITY}",
"show": true
},
{
"group_by": true,
"label": "Value",
"name": "Value",
"show": true
},
{
"group_by": true,
"label": "DNP",
"name": "${DNP}",
"show": true
},
{
"group_by": true,
"label": "Exclude from BOM",
"name": "${EXCLUDE_FROM_BOM}",
"show": true
},
{
"group_by": true,
"label": "Exclude from Board",
"name": "${EXCLUDE_FROM_BOARD}",
"show": true
},
{
"group_by": true,
"label": "Footprint",
"name": "Footprint",
"show": true
},
{
"group_by": false,
"label": "Datasheet",
"name": "Datasheet",
"show": true
}
],
"filter_string": "",
"group_symbols": true,
"include_excluded_from_bom": true,
"name": "Default Editing",
"sort_asc": true,
"sort_field": "Reference"
},
"connection_grid_size": 50.0,
"drawing": {
"dashed_lines_dash_length_ratio": 12.0,
"dashed_lines_gap_length_ratio": 3.0,
"default_line_thickness": 6.0,
"default_text_size": 50.0,
"field_names": [],
@@ -90,15 +592,37 @@
"intersheets_ref_suffix": "",
"junction_size_choice": 3,
"label_size_ratio": 0.25,
"operating_point_overlay_i_precision": 3,
"operating_point_overlay_i_range": "~A",
"operating_point_overlay_v_precision": 3,
"operating_point_overlay_v_range": "~V",
"overbar_offset_ratio": 1.23,
"pin_symbol_size": 0.0,
"text_offset_ratio": 0.08
},
"legacy_lib_dir": "",
"legacy_lib_list": [],
"meta": {
"version": 1
},
"net_format_name": "",
"page_layout_descr_file": "",
"plot_directory": "",
"space_save_all_events": true,
"spice_current_sheet_as_root": false,
"spice_external_command": "spice \"%I\"",
"spice_model_current_sheet_as_root": true,
"spice_save_all_currents": false,
"spice_save_all_dissipations": false,
"spice_save_all_voltages": false,
"subpart_first_id": 65,
"subpart_id_separator": 0
},
"sheets": [],
"sheets": [
[
"a0e1b2c3-d4e5-f6a7-b8c9-d0e1f2a3b4c5",
"Root"
]
],
"text_variables": {}
}
File diff suppressed because it is too large Load Diff
@@ -1,157 +1,211 @@
(footprint "OLED_128x32_I2C"
(version 20240108)
(generator "pcbnew")
(generator_version "8.0")
(layer "F.Cu")
(descr "0.91 inch 128x32 OLED display module, I2C interface, 4-pin header")
(tags "OLED SSD1306 display I2C")
(attr through_hole)
;; Typical 0.91" OLED module dimensions:
;; PCB: ~38mm x 12mm (varies by manufacturer)
;; Active display: ~22mm x 6mm
;; Pin header: 4 pins at 2.54mm pitch on one edge
;; Reference and value
(property "Reference" "MOD2"
(at 0 -9 0)
(layer "F.SilkS")
(uuid "ref-text")
(effects
(font (size 1 1) (thickness 0.15))
)
)
(property "Value" "OLED_128x32"
(at 0 9 0)
(layer "F.Fab")
(uuid "val-text")
(effects
(font (size 1 1) (thickness 0.15))
)
)
;; Module PCB outline on Fab layer
(fp_rect
(start -19 -6)
(end 19 6)
(stroke (width 0.1) (type solid))
(fill none)
(layer "F.Fab")
(uuid "module-outline")
)
;; Silkscreen outline
(fp_rect
(start -19.5 -6.5)
(end 19.5 6.5)
(stroke (width 0.12) (type solid))
(fill none)
(layer "F.SilkS")
(uuid "silk-outline")
)
;; Display active area indication
(fp_rect
(start -11 -3)
(end 11 3)
(stroke (width 0.12) (type solid))
(fill none)
(layer "F.SilkS")
(uuid "display-area")
)
(fp_text user "DISPLAY"
(at 0 0 0)
(layer "F.SilkS")
(uuid "display-text")
(effects
(font (size 1 1) (thickness 0.15))
)
)
;; Pin 1 indicator
(fp_circle
(center -18 4.5)
(end -17.5 4.5)
(stroke (width 0.12) (type solid))
(fill solid)
(layer "F.SilkS")
(uuid "pin1-dot")
)
;; Courtyard
(fp_rect
(start -20.5 -7.5)
(end 20.5 7.5)
(stroke (width 0.05) (type solid))
(fill none)
(layer "F.CrtYd")
(uuid "courtyard")
)
;; Pin header at left edge
;; Pins centered vertically, left edge at X=-17
;; 4 pins at 2.54mm pitch
;; Pin 1: GND
(pad "1" thru_hole rect
(at -17 3.81)
(size 1.7 1.7)
(drill 1.0)
(layers "*.Cu" "*.Mask")
(uuid "pad-1")
)
;; Pin 2: VCC
(pad "2" thru_hole oval
(at -17 1.27)
(size 1.7 1.7)
(drill 1.0)
(layers "*.Cu" "*.Mask")
(uuid "pad-2")
)
;; Pin 3: SCL
(pad "3" thru_hole oval
(at -17 -1.27)
(size 1.7 1.7)
(drill 1.0)
(layers "*.Cu" "*.Mask")
(uuid "pad-3")
)
;; Pin 4: SDA
(pad "4" thru_hole oval
(at -17 -3.81)
(size 1.7 1.7)
(drill 1.0)
(layers "*.Cu" "*.Mask")
(uuid "pad-4")
)
;; Pin labels on Fab layer
(fp_text user "GND"
(at -14 3.81 0)
(layer "F.Fab")
(uuid "lbl-1")
(effects (font (size 0.6 0.6) (thickness 0.1)))
)
(fp_text user "VCC"
(at -14 1.27 0)
(layer "F.Fab")
(uuid "lbl-2")
(effects (font (size 0.6 0.6) (thickness 0.1)))
)
(fp_text user "SCL"
(at -14 -1.27 0)
(layer "F.Fab")
(uuid "lbl-3")
(effects (font (size 0.6 0.6) (thickness 0.1)))
)
(fp_text user "SDA"
(at -14 -3.81 0)
(layer "F.Fab")
(uuid "lbl-4")
(effects (font (size 0.6 0.6) (thickness 0.1)))
)
;; Alternative: Some modules have pins on bottom edge
;; Uncomment and adjust if your module is different
;; (pad "1" thru_hole rect (at -3.81 5) ...)
(version 20241229)
(generator "pcbnew")
(layer "F.Cu")
(descr "0.91 inch 128x32 OLED display module, I2C interface, 4-pin header")
(tags "OLED SSD1306 display I2C")
(property "Reference" "MOD"
(at 0 -9 0)
(layer "F.SilkS")
(effects
(font
(size 1 1)
(thickness 0.15)
)
)
)
(property "Value" "OLED_128x32"
(at 0 9 0)
(layer "F.Fab")
(effects
(font
(size 1 1)
(thickness 0.15)
)
)
)
(attr through_hole)
(fp_line
(start -19.5 -6.5)
(end 19.5 -6.5)
(stroke
(width 0.12)
(type solid)
)
(layer "F.SilkS")
)
(fp_line
(start 19.5 -6.5)
(end 19.5 6.5)
(stroke
(width 0.12)
(type solid)
)
(layer "F.SilkS")
)
(fp_line
(start 19.5 6.5)
(end -19.5 6.5)
(stroke
(width 0.12)
(type solid)
)
(layer "F.SilkS")
)
(fp_line
(start -19.5 6.5)
(end -19.5 -6.5)
(stroke
(width 0.12)
(type solid)
)
(layer "F.SilkS")
)
(fp_line
(start -20.5 -7.5)
(end 20.5 -7.5)
(stroke
(width 0.05)
(type solid)
)
(layer "F.CrtYd")
)
(fp_line
(start 20.5 -7.5)
(end 20.5 7.5)
(stroke
(width 0.05)
(type solid)
)
(layer "F.CrtYd")
)
(fp_line
(start 20.5 7.5)
(end -20.5 7.5)
(stroke
(width 0.05)
(type solid)
)
(layer "F.CrtYd")
)
(fp_line
(start -20.5 7.5)
(end -20.5 -7.5)
(stroke
(width 0.05)
(type solid)
)
(layer "F.CrtYd")
)
(fp_line
(start -19 -6)
(end 19 -6)
(stroke
(width 0.1)
(type solid)
)
(layer "F.Fab")
)
(fp_line
(start 19 -6)
(end 19 6)
(stroke
(width 0.1)
(type solid)
)
(layer "F.Fab")
)
(fp_line
(start 19 6)
(end -19 6)
(stroke
(width 0.1)
(type solid)
)
(layer "F.Fab")
)
(fp_line
(start -19 6)
(end -19 -6)
(stroke
(width 0.1)
(type solid)
)
(layer "F.Fab")
)
(fp_line
(start -11 -3)
(end 11 -3)
(stroke
(width 0.12)
(type solid)
)
(layer "F.SilkS")
)
(fp_line
(start 11 -3)
(end 11 3)
(stroke
(width 0.12)
(type solid)
)
(layer "F.SilkS")
)
(fp_line
(start 11 3)
(end -11 3)
(stroke
(width 0.12)
(type solid)
)
(layer "F.SilkS")
)
(fp_line
(start -11 3)
(end -11 -3)
(stroke
(width 0.12)
(type solid)
)
(layer "F.SilkS")
)
(fp_text user "${REFERENCE}"
(at 0 0 0)
(layer "F.Fab")
(effects
(font
(size 1 1)
(thickness 0.15)
)
)
)
(pad "1" thru_hole rect
(at -17 3.81)
(size 1.7 1.7)
(drill 1)
(layers "*.Cu" "*.Mask")
(remove_unused_layers no)
)
(pad "2" thru_hole circle
(at -17 1.27)
(size 1.7 1.7)
(drill 1)
(layers "*.Cu" "*.Mask")
(remove_unused_layers no)
)
(pad "3" thru_hole circle
(at -17 -1.27)
(size 1.7 1.7)
(drill 1)
(layers "*.Cu" "*.Mask")
(remove_unused_layers no)
)
(pad "4" thru_hole circle
(at -17 -3.81)
(size 1.7 1.7)
(drill 1)
(layers "*.Cu" "*.Mask")
(remove_unused_layers no)
)
(embedded_fonts no)
)
+271 -358
View File
@@ -1,360 +1,273 @@
(footprint "RP2040-Zero"
(version 20240108)
(generator "pcbnew")
(generator_version "8.0")
(layer "F.Cu")
(descr "Waveshare RP2040-Zero module, 2x9 pin headers, 2.54mm pitch")
(tags "RP2040 Pico Zero Waveshare")
(attr through_hole)
;; RP2040-Zero dimensions:
;; Board: 23.5mm x 18mm
;; Pin rows: 2.54mm pitch, 9 pins each side
;; Row spacing: 15.24mm (600mil) center to center
;; USB-C on top edge
;; Reference and value
(property "Reference" "MOD1"
(at 0 -13 0)
(layer "F.SilkS")
(uuid "ref-text")
(effects
(font (size 1 1) (thickness 0.15))
)
)
(property "Value" "RP2040-Zero"
(at 0 13 0)
(layer "F.Fab")
(uuid "val-text")
(effects
(font (size 1 1) (thickness 0.15))
)
)
;; Board outline on Fab layer
(fp_rect
(start -9 -11.75)
(end 9 11.75)
(stroke (width 0.1) (type solid))
(fill none)
(layer "F.Fab")
(uuid "board-outline")
)
;; Silkscreen outline (slightly larger)
(fp_rect
(start -9.5 -12.25)
(end 9.5 12.25)
(stroke (width 0.12) (type solid))
(fill none)
(layer "F.SilkS")
(uuid "silk-outline")
)
;; USB-C indicator on top
(fp_rect
(start -4.5 -11.75)
(end 4.5 -9.5)
(stroke (width 0.12) (type solid))
(fill none)
(layer "F.SilkS")
(uuid "usb-indicator")
)
(fp_text user "USB-C"
(at 0 -10.5 0)
(layer "F.SilkS")
(uuid "usb-text")
(effects
(font (size 0.8 0.8) (thickness 0.12))
)
)
;; Pin 1 indicator
(fp_circle
(center -8.5 -8.5)
(end -8 -8.5)
(stroke (width 0.12) (type solid))
(fill solid)
(layer "F.SilkS")
(uuid "pin1-dot")
)
;; Courtyard
(fp_rect
(start -10.5 -13)
(end 10.5 13)
(stroke (width 0.05) (type solid))
(fill none)
(layer "F.CrtYd")
(uuid "courtyard")
)
;; Left column pins (active low Y, starting from top)
;; Pin spacing: 2.54mm, 9 pins = 8 gaps = 20.32mm total
;; Center at Y=0, so pins go from Y=-10.16 to Y=+10.16
;; Left column (X = -7.62mm = -300mil from center)
;; Pin 1: 5V
(pad "1" thru_hole rect
(at -7.62 -10.16)
(size 1.7 1.7)
(drill 1.0)
(layers "*.Cu" "*.Mask")
(uuid "pad-1")
)
;; Pin 2: GND
(pad "2" thru_hole oval
(at -7.62 -7.62)
(size 1.7 1.7)
(drill 1.0)
(layers "*.Cu" "*.Mask")
(uuid "pad-2")
)
;; Pin 3: 3V3
(pad "3" thru_hole oval
(at -7.62 -5.08)
(size 1.7 1.7)
(drill 1.0)
(layers "*.Cu" "*.Mask")
(uuid "pad-3")
)
;; Pin 4: GP29
(pad "4" thru_hole oval
(at -7.62 -2.54)
(size 1.7 1.7)
(drill 1.0)
(layers "*.Cu" "*.Mask")
(uuid "pad-4")
)
;; Pin 5: GP28
(pad "5" thru_hole oval
(at -7.62 0)
(size 1.7 1.7)
(drill 1.0)
(layers "*.Cu" "*.Mask")
(uuid "pad-5")
)
;; Pin 6: GP27
(pad "6" thru_hole oval
(at -7.62 2.54)
(size 1.7 1.7)
(drill 1.0)
(layers "*.Cu" "*.Mask")
(uuid "pad-6")
)
;; Pin 7: GP26
(pad "7" thru_hole oval
(at -7.62 5.08)
(size 1.7 1.7)
(drill 1.0)
(layers "*.Cu" "*.Mask")
(uuid "pad-7")
)
;; Pin 8: GP15
(pad "8" thru_hole oval
(at -7.62 7.62)
(size 1.7 1.7)
(drill 1.0)
(layers "*.Cu" "*.Mask")
(uuid "pad-8")
)
;; Pin 9: GP14
(pad "9" thru_hole oval
(at -7.62 10.16)
(size 1.7 1.7)
(drill 1.0)
(layers "*.Cu" "*.Mask")
(uuid "pad-9")
)
;; Right column (X = +7.62mm = +300mil from center)
;; Pin 10: 3V3
(pad "10" thru_hole oval
(at 7.62 -10.16)
(size 1.7 1.7)
(drill 1.0)
(layers "*.Cu" "*.Mask")
(uuid "pad-10")
)
;; Pin 11: GND
(pad "11" thru_hole oval
(at 7.62 -7.62)
(size 1.7 1.7)
(drill 1.0)
(layers "*.Cu" "*.Mask")
(uuid "pad-11")
)
;; Pin 12: GP0 (I2C SDA)
(pad "12" thru_hole oval
(at 7.62 -5.08)
(size 1.7 1.7)
(drill 1.0)
(layers "*.Cu" "*.Mask")
(uuid "pad-12")
)
;; Pin 13: GP1 (I2C SCL)
(pad "13" thru_hole oval
(at 7.62 -2.54)
(size 1.7 1.7)
(drill 1.0)
(layers "*.Cu" "*.Mask")
(uuid "pad-13")
)
;; Pin 14: GP2 (TRIG OUT)
(pad "14" thru_hole oval
(at 7.62 0)
(size 1.7 1.7)
(drill 1.0)
(layers "*.Cu" "*.Mask")
(uuid "pad-14")
)
;; Pin 15: GP3 (RETURN IN)
(pad "15" thru_hole oval
(at 7.62 2.54)
(size 1.7 1.7)
(drill 1.0)
(layers "*.Cu" "*.Mask")
(uuid "pad-15")
)
;; Pin 16: GP4 (BTN)
(pad "16" thru_hole oval
(at 7.62 5.08)
(size 1.7 1.7)
(drill 1.0)
(layers "*.Cu" "*.Mask")
(uuid "pad-16")
)
;; Pin 17: GP5
(pad "17" thru_hole oval
(at 7.62 7.62)
(size 1.7 1.7)
(drill 1.0)
(layers "*.Cu" "*.Mask")
(uuid "pad-17")
)
;; Pin 18: GND
(pad "18" thru_hole oval
(at 7.62 10.16)
(size 1.7 1.7)
(drill 1.0)
(layers "*.Cu" "*.Mask")
(uuid "pad-18")
)
;; Pin labels on Fab layer
(fp_text user "5V"
(at -5 -10.16 0)
(layer "F.Fab")
(uuid "lbl-1")
(effects (font (size 0.6 0.6) (thickness 0.1)))
)
(fp_text user "GND"
(at -5 -7.62 0)
(layer "F.Fab")
(uuid "lbl-2")
(effects (font (size 0.6 0.6) (thickness 0.1)))
)
(fp_text user "3V3"
(at -5 -5.08 0)
(layer "F.Fab")
(uuid "lbl-3")
(effects (font (size 0.6 0.6) (thickness 0.1)))
)
(fp_text user "GP29"
(at -4.5 -2.54 0)
(layer "F.Fab")
(uuid "lbl-4")
(effects (font (size 0.6 0.6) (thickness 0.1)))
)
(fp_text user "GP28"
(at -4.5 0 0)
(layer "F.Fab")
(uuid "lbl-5")
(effects (font (size 0.6 0.6) (thickness 0.1)))
)
(fp_text user "GP27"
(at -4.5 2.54 0)
(layer "F.Fab")
(uuid "lbl-6")
(effects (font (size 0.6 0.6) (thickness 0.1)))
)
(fp_text user "GP26"
(at -4.5 5.08 0)
(layer "F.Fab")
(uuid "lbl-7")
(effects (font (size 0.6 0.6) (thickness 0.1)))
)
(fp_text user "GP15"
(at -4.5 7.62 0)
(layer "F.Fab")
(uuid "lbl-8")
(effects (font (size 0.6 0.6) (thickness 0.1)))
)
(fp_text user "GP14"
(at -4.5 10.16 0)
(layer "F.Fab")
(uuid "lbl-9")
(effects (font (size 0.6 0.6) (thickness 0.1)))
)
(fp_text user "3V3"
(at 5 -10.16 0)
(layer "F.Fab")
(uuid "lbl-10")
(effects (font (size 0.6 0.6) (thickness 0.1)))
)
(fp_text user "GND"
(at 5 -7.62 0)
(layer "F.Fab")
(uuid "lbl-11")
(effects (font (size 0.6 0.6) (thickness 0.1)))
)
(fp_text user "GP0"
(at 4.5 -5.08 0)
(layer "F.Fab")
(uuid "lbl-12")
(effects (font (size 0.6 0.6) (thickness 0.1)))
)
(fp_text user "GP1"
(at 4.5 -2.54 0)
(layer "F.Fab")
(uuid "lbl-13")
(effects (font (size 0.6 0.6) (thickness 0.1)))
)
(fp_text user "GP2"
(at 4.5 0 0)
(layer "F.Fab")
(uuid "lbl-14")
(effects (font (size 0.6 0.6) (thickness 0.1)))
)
(fp_text user "GP3"
(at 4.5 2.54 0)
(layer "F.Fab")
(uuid "lbl-15")
(effects (font (size 0.6 0.6) (thickness 0.1)))
)
(fp_text user "GP4"
(at 4.5 5.08 0)
(layer "F.Fab")
(uuid "lbl-16")
(effects (font (size 0.6 0.6) (thickness 0.1)))
)
(fp_text user "GP5"
(at 4.5 7.62 0)
(layer "F.Fab")
(uuid "lbl-17")
(effects (font (size 0.6 0.6) (thickness 0.1)))
)
(fp_text user "GND"
(at 5 10.16 0)
(layer "F.Fab")
(uuid "lbl-18")
(effects (font (size 0.6 0.6) (thickness 0.1)))
)
;; 3D model reference (optional)
(model "${KIPRJMOD}/3dmodels/RP2040-Zero.step"
(offset (xyz 0 0 0))
(scale (xyz 1 1 1))
(rotate (xyz 0 0 0))
)
(version 20241229)
(generator "pcbnew")
(layer "F.Cu")
(descr "Waveshare RP2040-Zero module, 2x9 pin headers, 2.54mm pitch")
(tags "RP2040 Pico Zero Waveshare")
(property "Reference" "MOD"
(at 0 -13 0)
(layer "F.SilkS")
(effects
(font
(size 1 1)
(thickness 0.15)
)
)
)
(property "Value" "RP2040-Zero"
(at 0 13 0)
(layer "F.Fab")
(effects
(font
(size 1 1)
(thickness 0.15)
)
)
)
(attr through_hole)
(fp_line
(start -9.5 -12.25)
(end 9.5 -12.25)
(stroke
(width 0.12)
(type solid)
)
(layer "F.SilkS")
)
(fp_line
(start 9.5 -12.25)
(end 9.5 12.25)
(stroke
(width 0.12)
(type solid)
)
(layer "F.SilkS")
)
(fp_line
(start 9.5 12.25)
(end -9.5 12.25)
(stroke
(width 0.12)
(type solid)
)
(layer "F.SilkS")
)
(fp_line
(start -9.5 12.25)
(end -9.5 -12.25)
(stroke
(width 0.12)
(type solid)
)
(layer "F.SilkS")
)
(fp_line
(start -10.5 -13)
(end 10.5 -13)
(stroke
(width 0.05)
(type solid)
)
(layer "F.CrtYd")
)
(fp_line
(start 10.5 -13)
(end 10.5 13)
(stroke
(width 0.05)
(type solid)
)
(layer "F.CrtYd")
)
(fp_line
(start 10.5 13)
(end -10.5 13)
(stroke
(width 0.05)
(type solid)
)
(layer "F.CrtYd")
)
(fp_line
(start -10.5 13)
(end -10.5 -13)
(stroke
(width 0.05)
(type solid)
)
(layer "F.CrtYd")
)
(fp_line
(start -9 -11.75)
(end 9 -11.75)
(stroke
(width 0.1)
(type solid)
)
(layer "F.Fab")
)
(fp_line
(start 9 -11.75)
(end 9 11.75)
(stroke
(width 0.1)
(type solid)
)
(layer "F.Fab")
)
(fp_line
(start 9 11.75)
(end -9 11.75)
(stroke
(width 0.1)
(type solid)
)
(layer "F.Fab")
)
(fp_line
(start -9 11.75)
(end -9 -11.75)
(stroke
(width 0.1)
(type solid)
)
(layer "F.Fab")
)
(fp_text user "${REFERENCE}"
(at 0 0 0)
(layer "F.Fab")
(effects
(font
(size 1 1)
(thickness 0.15)
)
)
)
(pad "1" thru_hole rect
(at -7.62 -10.16)
(size 1.7 1.7)
(drill 1)
(layers "*.Cu" "*.Mask")
(remove_unused_layers no)
)
(pad "2" thru_hole circle
(at -7.62 -7.62)
(size 1.7 1.7)
(drill 1)
(layers "*.Cu" "*.Mask")
(remove_unused_layers no)
)
(pad "3" thru_hole circle
(at -7.62 -5.08)
(size 1.7 1.7)
(drill 1)
(layers "*.Cu" "*.Mask")
(remove_unused_layers no)
)
(pad "4" thru_hole circle
(at -7.62 -2.54)
(size 1.7 1.7)
(drill 1)
(layers "*.Cu" "*.Mask")
(remove_unused_layers no)
)
(pad "5" thru_hole circle
(at -7.62 0)
(size 1.7 1.7)
(drill 1)
(layers "*.Cu" "*.Mask")
(remove_unused_layers no)
)
(pad "6" thru_hole circle
(at -7.62 2.54)
(size 1.7 1.7)
(drill 1)
(layers "*.Cu" "*.Mask")
(remove_unused_layers no)
)
(pad "7" thru_hole circle
(at -7.62 5.08)
(size 1.7 1.7)
(drill 1)
(layers "*.Cu" "*.Mask")
(remove_unused_layers no)
)
(pad "8" thru_hole circle
(at -7.62 7.62)
(size 1.7 1.7)
(drill 1)
(layers "*.Cu" "*.Mask")
(remove_unused_layers no)
)
(pad "9" thru_hole circle
(at -7.62 10.16)
(size 1.7 1.7)
(drill 1)
(layers "*.Cu" "*.Mask")
(remove_unused_layers no)
)
(pad "10" thru_hole circle
(at 7.62 -10.16)
(size 1.7 1.7)
(drill 1)
(layers "*.Cu" "*.Mask")
(remove_unused_layers no)
)
(pad "11" thru_hole circle
(at 7.62 -7.62)
(size 1.7 1.7)
(drill 1)
(layers "*.Cu" "*.Mask")
(remove_unused_layers no)
)
(pad "12" thru_hole circle
(at 7.62 -5.08)
(size 1.7 1.7)
(drill 1)
(layers "*.Cu" "*.Mask")
(remove_unused_layers no)
)
(pad "13" thru_hole circle
(at 7.62 -2.54)
(size 1.7 1.7)
(drill 1)
(layers "*.Cu" "*.Mask")
(remove_unused_layers no)
)
(pad "14" thru_hole circle
(at 7.62 0)
(size 1.7 1.7)
(drill 1)
(layers "*.Cu" "*.Mask")
(remove_unused_layers no)
)
(pad "15" thru_hole circle
(at 7.62 2.54)
(size 1.7 1.7)
(drill 1)
(layers "*.Cu" "*.Mask")
(remove_unused_layers no)
)
(pad "16" thru_hole circle
(at 7.62 5.08)
(size 1.7 1.7)
(drill 1)
(layers "*.Cu" "*.Mask")
(remove_unused_layers no)
)
(pad "17" thru_hole circle
(at 7.62 7.62)
(size 1.7 1.7)
(drill 1)
(layers "*.Cu" "*.Mask")
(remove_unused_layers no)
)
(pad "18" thru_hole circle
(at 7.62 10.16)
(size 1.7 1.7)
(drill 1)
(layers "*.Cu" "*.Mask")
(remove_unused_layers no)
)
(embedded_fonts no)
)
+214
View File
@@ -0,0 +1,214 @@
#!/usr/bin/env python3
"""
SN-L00 Fully Automated Routing Pipeline
Works headlessly on KiCad 9+
Usage: python3 scripts/autoroute.py
Pipeline:
1. Load board
2. Update outline to 8HP (40x100mm)
3. Delete existing tracks
4. Place components (v2 layout)
5. Export Specctra DSN
6. Run Freerouting autorouter
7. Import Specctra SES
8. Save board
9. Run DRC
"""
import os
import sys
import subprocess
# Paths
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
KICAD_DIR = os.path.dirname(SCRIPT_DIR)
os.chdir(KICAD_DIR)
PCB_FILE = "SN-L00.kicad_pcb"
DSN_FILE = "SN-L00.dsn"
SES_FILE = "SN-L00.ses"
DRC_FILE = "DRC.rpt"
FREEROUTING_JAR = "/tmp/freerouting.jar"
FREEROUTING_URL = "https://github.com/freerouting/freerouting/releases/download/v2.0.1/freerouting-2.0.1.jar"
# Component positions for 8HP (40mm x 100mm) layout v2
PLACEMENTS = {
# Top - OLED display
"MOD3": (20, 15, 90),
# Audio jacks
"J3": (10, 35, 0), # RETURN_IN
"J4": (30, 35, 0), # TRIG_OUT
# Button + LED
"SW1": (20, 48, 0),
"D5": (32, 48, 0),
# RP2040-Zero
"MOD2": (20, 62, 0),
# Signal conditioning
"U2": (8, 78, 0), # 74LVC1G17
"U4": (32, 78, 0), # MCP6001
# Decoupling caps
"C4": (4, 78, 90),
"C5": (14, 78, 90),
"C6": (28, 82, 90),
# Protection diodes
"D3": (4, 82, 0),
"D4": (36, 78, 0),
# Resistors
"R2": (4, 86, 90),
"R3": (10, 86, 90),
"R4": (16, 86, 90),
"R5": (24, 86, 90),
"R6": (30, 86, 90),
"R7": (36, 86, 90),
# Power section
"J2": (20, 88, 0), # Eurorack power
"D2": (4, 92, 0),
"U3": (32, 94, 180), # LDO
"C2": (10, 94, 90),
"C3": (26, 94, 90),
}
def main():
print("=" * 50)
print("SN-L00 Automated Routing Pipeline")
print("=" * 50)
# Import pcbnew
try:
import pcbnew
print(f"KiCad version: {pcbnew.Version()}")
except ImportError:
print("ERROR: pcbnew not available. Install KiCad or set PYTHONPATH.")
sys.exit(1)
# Helper functions
def mm(val):
return pcbnew.FromMM(val)
def place(x, y):
return pcbnew.VECTOR2I(mm(x), mm(y))
# Step 1: Load board
print("\n[1/9] Loading board...")
board = pcbnew.LoadBoard(PCB_FILE)
print(f" Loaded {len(list(board.GetFootprints()))} footprints")
# Step 2: Update outline
print("\n[2/9] Updating board outline to 40x100mm...")
for drawing in board.GetDrawings():
if drawing.GetClass() == "PCB_SHAPE":
if drawing.GetShape() == pcbnew.SHAPE_T_RECT:
drawing.SetStart(pcbnew.VECTOR2I(mm(0), mm(0)))
drawing.SetEnd(pcbnew.VECTOR2I(mm(40), mm(100)))
print(" Done")
break
# Step 3: Delete tracks
print("\n[3/9] Deleting existing tracks...")
tracks = list(board.GetTracks())
for track in tracks:
board.Delete(track)
print(f" Deleted {len(tracks)} tracks")
# Step 4: Place components
print("\n[4/9] Placing components...")
placed = 0
for ref, (x, y, rot) in PLACEMENTS.items():
fp = board.FindFootprintByReference(ref)
if fp:
fp.SetPosition(place(x, y))
fp.SetOrientationDegrees(rot)
placed += 1
print(f" Placed {placed} components")
# Step 5: Export DSN
print("\n[5/9] Exporting Specctra DSN...")
board.Save(PCB_FILE) # Save first
result = pcbnew.ExportSpecctraDSN(board, DSN_FILE)
if result and os.path.exists(DSN_FILE):
print(f" Created {DSN_FILE} ({os.path.getsize(DSN_FILE)} bytes)")
else:
print(" ERROR: DSN export failed")
sys.exit(1)
# Step 6: Download and run Freerouting
print("\n[6/9] Running Freerouting...")
if not os.path.exists(FREEROUTING_JAR):
print(" Downloading Freerouting...")
subprocess.run(["curl", "-sL", "-o", FREEROUTING_JAR, FREEROUTING_URL], check=True)
result = subprocess.run(
["java", "-jar", FREEROUTING_JAR,
"-de", DSN_FILE,
"-do", SES_FILE,
"-mp", "200",
"-mt", "1",
"-oit"],
capture_output=True,
text=True
)
# Parse output
for line in (result.stdout + result.stderr).split('\n'):
if 'auto-rout' in line.lower() or 'completed' in line.lower():
print(f" {line.split(']')[-1].strip()}")
if not os.path.exists(SES_FILE):
print(" ERROR: Freerouting failed")
sys.exit(1)
# Step 7: Import SES
print("\n[7/9] Importing routed session...")
# Reload board to get fresh state
board = pcbnew.LoadBoard(PCB_FILE)
result = pcbnew.ImportSpecctraSES(board, SES_FILE)
if result:
print(f" Imported {len(list(board.GetTracks()))} tracks")
else:
print(" WARNING: SES import returned False")
# Step 8: Save
print("\n[8/9] Saving board...")
board.Save(PCB_FILE)
print(f" Saved {PCB_FILE}")
# Step 9: Run DRC
print("\n[9/9] Running DRC...")
result = subprocess.run(
["kicad-cli", "pcb", "drc",
"--severity-all",
"--units", "mm",
"-o", DRC_FILE,
PCB_FILE],
capture_output=True,
text=True
)
# Parse DRC output
errors = 0
warnings = 0
if os.path.exists(DRC_FILE):
with open(DRC_FILE) as f:
content = f.read()
errors = content.count('; error')
warnings = content.count('; warning')
print(f" {errors} errors, {warnings} warnings")
# Summary
print("\n" + "=" * 50)
if errors == 0:
print("SUCCESS! Board routed with no DRC errors.")
else:
print(f"Done. {errors} DRC errors remain - see {DRC_FILE}")
print("=" * 50)
return errors == 0
if __name__ == "__main__":
success = main()
sys.exit(0 if success else 1)
+161
View File
@@ -0,0 +1,161 @@
#!/usr/bin/env python3
"""
SN-L00 Full Autoroute Pipeline for 8HP Eurorack
Run in KiCad PCB Editor: Tools → Scripting Console
Then: exec(open('scripts/autoroute_full.py').read())
This script:
1. Updates board outline to 8HP (40mm x 100mm)
2. Deletes existing tracks
3. Places all components
4. Exports DSN file
5. Runs Freerouting
6. Imports routed SES file
"""
import pcbnew
import subprocess
import os
import time
board = pcbnew.GetBoard()
board_path = board.GetFileName()
board_dir = os.path.dirname(board_path)
def mm(val):
return pcbnew.FromMM(val)
def place(x, y):
return pcbnew.VECTOR2I(mm(x), mm(y))
print("=" * 50)
print("SN-L00 Full Autoroute Pipeline")
print("=" * 50)
# Step 1: Update board outline to 8HP (40mm x 100mm)
print("\n[1/6] Updating board outline to 40mm x 100mm...")
for drawing in board.GetDrawings():
if drawing.GetClass() == "PCB_SHAPE":
if drawing.GetShape() == pcbnew.SHAPE_T_RECT:
drawing.SetStart(pcbnew.VECTOR2I(mm(0), mm(0)))
drawing.SetEnd(pcbnew.VECTOR2I(mm(40), mm(100)))
print(" Board outline updated")
break
# Step 2: Delete all tracks
print("\n[2/6] Deleting existing tracks...")
tracks = list(board.GetTracks())
for track in tracks:
board.Delete(track)
print(f" Deleted {len(tracks)} tracks")
# Step 3: Place components (v2 - fixed spacing)
print("\n[3/6] Placing components...")
placements = {
# Top - OLED display (rotated 90°)
"MOD3": (20, 15, 90),
# Audio jacks - centered with good spacing
"J3": (10, 35, 0), # RETURN_IN
"J4": (30, 35, 0), # TRIG_OUT
# Button + LED
"SW1": (20, 48, 0),
"D5": (32, 48, 0), # LED near button
# RP2040-Zero module - centered
"MOD2": (20, 62, 0),
# Signal conditioning ICs - well spaced
"U2": (8, 78, 0), # 74LVC1G17
"U4": (32, 78, 0), # MCP6001
# Decoupling caps near ICs
"C4": (4, 78, 90),
"C5": (14, 78, 90),
"C6": (28, 82, 90),
# Protection diodes near jacks circuits
"D3": (4, 82, 0),
"D4": (36, 78, 0),
# Resistors - spread across middle
"R2": (4, 86, 90),
"R3": (10, 86, 90),
"R4": (16, 86, 90),
"R5": (24, 86, 90),
"R6": (30, 86, 90),
"R7": (36, 86, 90),
# Power section - J2 moved up, components spread out
"J2": (20, 88, 0), # Eurorack power header (extends ~6mm down)
"D2": (4, 92, 0), # Protection diode
"U3": (32, 94, 180), # LDO - moved left from edge
"C2": (10, 94, 90), # Input cap
"C3": (26, 94, 90), # Output cap
}
placed = 0
for ref, (x, y, rot) in placements.items():
fp = board.FindFootprintByReference(ref)
if fp:
fp.SetPosition(place(x, y))
fp.SetOrientationDegrees(rot)
placed += 1
print(f" Placed {placed} components")
# Step 4: Save and export DSN
print("\n[4/6] Saving board and exporting DSN...")
pcbnew.Refresh()
board.Save(board_path)
dsn_path = os.path.join(board_dir, "SN-L00.dsn")
ses_path = os.path.join(board_dir, "SN-L00.ses")
# Export DSN
if hasattr(pcbnew, 'ExportSpecctraDSN'):
pcbnew.ExportSpecctraDSN(dsn_path)
print(f" Exported: {dsn_path}")
else:
# Fallback for older KiCad versions
exporter = pcbnew.SPECCTRA_DB()
exporter.ExportPCB(dsn_path, False)
print(f" Exported: {dsn_path}")
# Step 5: Run Freerouting
print("\n[5/6] Running Freerouting...")
freerouting_jar = "/tmp/freerouting.jar"
if not os.path.exists(freerouting_jar):
print(" Downloading Freerouting...")
url = "https://github.com/freerouting/freerouting/releases/download/v2.0.1/freerouting-2.0.1.jar"
subprocess.run(["curl", "-L", "-o", freerouting_jar, url], check=True)
result = subprocess.run(
["java", "-jar", freerouting_jar,
"-de", dsn_path,
"-do", ses_path,
"-mp", "200",
"-mt", "1",
"-oit"],
capture_output=True,
text=True,
cwd=board_dir
)
if result.returncode == 0:
print(" Freerouting completed successfully")
else:
print(f" Freerouting error: {result.stderr}")
# Step 6: Import SES
print("\n[6/6] Importing routed session...")
if os.path.exists(ses_path):
if hasattr(pcbnew, 'ImportSpecctraSES'):
pcbnew.ImportSpecctraSES(ses_path)
print(f" Imported: {ses_path}")
else:
# Fallback
importer = pcbnew.SPECCTRA_DB()
importer.ImportSES(ses_path)
print(f" Imported: {ses_path}")
pcbnew.Refresh()
print("\n" + "=" * 50)
print("DONE! Save (Ctrl+S) and run DRC to verify.")
print("=" * 50)
else:
print(f" ERROR: SES file not found: {ses_path}")
+46
View File
@@ -0,0 +1,46 @@
#!/bin/bash
# Freerouting automation script
# Usage: ./scripts/freeroute.sh
#
# Prerequisites: Export DSN from KiCad first (File → Export → Specctra DSN)
set -e
cd "$(dirname "$0")/.."
DSN_FILE="SN-L00.dsn"
SES_FILE="SN-L00.ses"
FREEROUTING_JAR="/tmp/freerouting.jar"
FREEROUTING_URL="https://github.com/freerouting/freerouting/releases/download/v2.0.1/freerouting-2.0.1.jar"
echo "=================================="
echo "SN-L00 Freerouting Automation"
echo "=================================="
# Check DSN exists
if [ ! -f "$DSN_FILE" ]; then
echo "ERROR: $DSN_FILE not found"
echo "Export from KiCad: File → Export → Specctra DSN"
exit 1
fi
# Download Freerouting if needed
if [ ! -f "$FREEROUTING_JAR" ]; then
echo "Downloading Freerouting..."
curl -L -o "$FREEROUTING_JAR" "$FREEROUTING_URL"
fi
# Run Freerouting
echo "Running Freerouting..."
java -jar "$FREEROUTING_JAR" \
-de "$DSN_FILE" \
-do "$SES_FILE" \
-mp 200 \
-mt 1 \
-oit
echo ""
echo "=================================="
echo "DONE! Import in KiCad:"
echo " File → Import → Specctra Session"
echo " Select: $SES_FILE"
echo "=================================="
+45
View File
@@ -0,0 +1,45 @@
#!/usr/bin/env python3
"""
Move board and all components to origin (0,0)
Run in KiCad PCB Editor: Tools → Scripting Console
Then: exec(open('scripts/move_to_origin.py').read())
"""
import pcbnew
board = pcbnew.GetBoard()
# Get current board origin from edge cuts
bbox = board.GetBoardEdgesBoundingBox()
offset_x = bbox.GetX()
offset_y = bbox.GetY()
print(f"Current board origin: ({pcbnew.ToMM(offset_x):.2f}, {pcbnew.ToMM(offset_y):.2f}) mm")
if offset_x == 0 and offset_y == 0:
print("Board is already at origin!")
else:
# Move all footprints
for fp in board.GetFootprints():
pos = fp.GetPosition()
new_pos = pcbnew.VECTOR2I(pos.x - offset_x, pos.y - offset_y)
fp.SetPosition(new_pos)
# Move all drawings (including board outline)
for drawing in board.GetDrawings():
if hasattr(drawing, 'Move'):
drawing.Move(pcbnew.VECTOR2I(-offset_x, -offset_y))
# Move all tracks
for track in board.GetTracks():
track.Move(pcbnew.VECTOR2I(-offset_x, -offset_y))
# Move all zones
for zone in board.Zones():
zone.Move(pcbnew.VECTOR2I(-offset_x, -offset_y))
print(f"Moved everything by ({-pcbnew.ToMM(offset_x):.2f}, {-pcbnew.ToMM(offset_y):.2f}) mm")
print("Board is now at origin (0, 0)")
pcbnew.Refresh()
print("\nDone! Save (Ctrl+S), delete all tracks, then run place_6hp.py")
+79
View File
@@ -0,0 +1,79 @@
#!/usr/bin/env python3
"""
SN-L00 Component Placement Script for 8HP Eurorack (40mm x 100mm)
Run in KiCad PCB Editor: Tools → Scripting Console
Then: exec(open('scripts/place_8hp.py').read())
"""
import pcbnew
board = pcbnew.GetBoard()
def mm(val):
return pcbnew.FromMM(val)
def place(x, y):
return pcbnew.VECTOR2I(mm(x), mm(y))
# First, update the board outline to 8HP (40mm x 100mm)
for drawing in board.GetDrawings():
if drawing.GetClass() == "PCB_SHAPE":
if drawing.GetShape() == pcbnew.SHAPE_T_RECT:
drawing.SetStart(pcbnew.VECTOR2I(mm(0), mm(0)))
drawing.SetEnd(pcbnew.VECTOR2I(mm(40), mm(100)))
print("Updated board outline to 40mm x 100mm (8HP)")
# Delete all tracks for fresh routing
tracks = list(board.GetTracks())
for track in tracks:
board.Delete(track)
print(f"Deleted {len(tracks)} tracks")
# Component positions for 8HP (40mm x 100mm) layout v2
placements = {
"MOD3": (20, 15, 90), # OLED display
"J3": (10, 35, 0), # RETURN_IN jack
"J4": (30, 35, 0), # TRIG_OUT jack
"SW1": (20, 48, 0), # Button
"D5": (32, 48, 0), # LED
"MOD2": (20, 62, 0), # RP2040-Zero
"U2": (8, 78, 0), # 74LVC1G17
"U4": (32, 78, 0), # MCP6001
"C4": (4, 78, 90), # Decoupling
"C5": (14, 78, 90),
"C6": (28, 82, 90),
"D3": (4, 82, 0), # Protection diodes
"D4": (36, 78, 0),
"R2": (4, 86, 90), # Resistors
"R3": (10, 86, 90),
"R4": (16, 86, 90),
"R5": (24, 86, 90),
"R6": (30, 86, 90),
"R7": (36, 86, 90),
"J2": (20, 88, 0), # Eurorack power
"D2": (4, 92, 0),
"U3": (32, 94, 180), # LDO
"C2": (10, 94, 90),
"C3": (26, 94, 90),
}
print("\nPlacing components for 8HP Eurorack layout (v2)...")
placed = 0
not_found = []
for ref, (x, y, rot) in placements.items():
fp = board.FindFootprintByReference(ref)
if fp:
fp.SetPosition(place(x, y))
fp.SetOrientationDegrees(rot)
placed += 1
print(f" {ref} -> ({x}, {y}) rot={rot}°")
else:
not_found.append(ref)
print(f"\nPlaced {placed} components")
if not_found:
print(f"Not found: {', '.join(not_found)}")
pcbnew.Refresh()
print("\nDone! Save (Ctrl+S), then run: ./scripts/route.sh")
+97
View File
@@ -0,0 +1,97 @@
#!/bin/bash
# Full routing and DRC workflow
# Usage: ./scripts/route.sh
#
# Prerequisites:
# 1. Run place_8hp.py in KiCad scripting console
# 2. Export DSN: File → Export → Specctra DSN
# 3. Save PCB
set -e
cd "$(dirname "$0")/.."
PCB_FILE="SN-L00.kicad_pcb"
DSN_FILE="SN-L00.dsn"
SES_FILE="SN-L00.ses"
DRC_FILE="DRC.rpt"
FREEROUTING_JAR="/tmp/freerouting.jar"
FREEROUTING_URL="https://github.com/freerouting/freerouting/releases/download/v2.0.1/freerouting-2.0.1.jar"
echo "============================================"
echo "SN-L00 Routing Pipeline"
echo "============================================"
# Check DSN exists
if [ ! -f "$DSN_FILE" ]; then
echo ""
echo "ERROR: $DSN_FILE not found!"
echo ""
echo "In KiCad:"
echo " 1. Run: exec(open('scripts/place_8hp.py').read())"
echo " 2. Save: Ctrl+S"
echo " 3. Export: File → Export → Specctra DSN"
echo ""
exit 1
fi
# Download Freerouting if needed
if [ ! -f "$FREEROUTING_JAR" ]; then
echo "[1/3] Downloading Freerouting..."
curl -L -o "$FREEROUTING_JAR" "$FREEROUTING_URL"
else
echo "[1/3] Freerouting ready"
fi
# Run Freerouting
echo "[2/3] Running Freerouting..."
java -jar "$FREEROUTING_JAR" \
-de "$DSN_FILE" \
-do "$SES_FILE" \
-mp 200 \
-mt 1 \
-oit 2>&1 | grep -E "(completed|INFO.*Auto|ERROR)" || true
if [ ! -f "$SES_FILE" ]; then
echo "ERROR: Routing failed - no SES file created"
exit 1
fi
echo "[3/3] Running DRC..."
echo ""
echo "============================================"
echo "In KiCad: File → Import → Specctra Session"
echo "Select: $SES_FILE"
echo "Then save (Ctrl+S)"
echo "============================================"
echo ""
echo "Waiting for you to import SES and save..."
read -p "Press Enter when done: "
# Run DRC from CLI
kicad-cli pcb drc \
--severity-all \
--units mm \
--schematic-parity \
-o "$DRC_FILE" \
"$PCB_FILE" 2>&1
echo ""
echo "============================================"
echo "DRC Results:"
echo "============================================"
grep -E "^(Found|\*\*)" "$DRC_FILE" || cat "$DRC_FILE"
echo ""
# Count errors vs warnings
ERRORS=$(grep -c "error$" "$DRC_FILE" 2>/dev/null || echo "0")
WARNINGS=$(grep -c "warning$" "$DRC_FILE" 2>/dev/null || echo "0")
echo "Summary: $ERRORS errors, $WARNINGS warnings"
if [ "$ERRORS" -eq 0 ]; then
echo ""
echo "✓ No DRC errors - ready for manufacturing!"
else
echo ""
echo "Fix errors and re-run: ./scripts/route.sh"
fi
+48 -57
View File
@@ -1,10 +1,10 @@
# SN-L00 Panel Specification
## Eurorack 6HP Panel Dimensions
## Eurorack 8HP Panel Dimensions
| Parameter | Value | Notes |
|-----------|-------|-------|
| Width | 30.00mm | 6 × 5.08mm (6HP) |
| Width | 40.30mm | 8 × 5.08mm (8HP) |
| Height | 128.50mm | 3U standard |
| Thickness | 1.5-2.0mm | Aluminum or PCB |
| Corner radius | 0mm | Square corners |
@@ -16,9 +16,9 @@ Eurorack standard mounting rail positions:
| Hole | X | Y | Diameter | Notes |
|------|---|---|----------|-------|
| Top Left | 7.5mm | 3.0mm | 3.2mm | Horizontal slot optional |
| Top Right | 22.5mm | 3.0mm | 3.2mm | Horizontal slot optional |
| Top Right | 32.8mm | 3.0mm | 3.2mm | Horizontal slot optional |
| Bottom Left | 7.5mm | 125.5mm | 3.2mm | Horizontal slot optional |
| Bottom Right | 22.5mm | 125.5mm | 3.2mm | Horizontal slot optional |
| Bottom Right | 32.8mm | 125.5mm | 3.2mm | Horizontal slot optional |
**Note**: Slots (elongated holes) of 3.2mm × 4mm allow for rail tolerance.
@@ -26,47 +26,47 @@ Eurorack standard mounting rail positions:
| Component | X | Y | Diameter | Notes |
|-----------|---|---|----------|-------|
| OLED cutout | 15.0mm | 25.0mm | 28×12mm rect | Rectangular window |
| Button | 15.0mm | 45.0mm | 7.0mm | For 6mm tactile + cap |
| LED (power) | 24.0mm | 45.0mm | 3.0mm | For 3mm LED or light pipe |
| Jack TRIG | 7.5mm | 95.0mm | 6.0mm | Thonkiconn PJ398SM |
| Jack RETURN | 22.5mm | 95.0mm | 6.0mm | Thonkiconn PJ398SM |
| OLED cutout | 20.15mm | 25.0mm | 28×12mm rect | Rectangular window |
| Button | 20.15mm | 50.0mm | 7.0mm | For 6mm tactile + cap |
| LED (status) | 32.0mm | 50.0mm | 3.0mm | For 3mm LED or light pipe |
| Jack RETURN | 10.0mm | 95.0mm | 6.0mm | Thonkiconn PJ398SM |
| Jack TRIG | 30.0mm | 95.0mm | 6.0mm | Thonkiconn PJ398SM |
## Panel Layout Drawing
```
←───── 30.00mm ─────→
←─────── 40.30mm ───────→
┌─────────────────────┐ ─┬─ 0.00mm
│ │ 3.00mm (mounting holes)
│ │ │
│ SubModular │ │ 12.00mm
│ │ │
───────────────┐ │ │
│ │ │
OLED │ │ 19-31mm (display window)
DISPLAY │ │
│ │ │
───────────────┘ │ │
│ │ │
│ SN-L00 │ │ 38.00mm
│ │ │
• │ │ 45.00mm (button + LED)
│ BTN PWR │ │
│ │ │
│ │ │
│ │ │
│ ┌───┐ ┌───┐ │ │
│ │ │ │ │ │ │
│ │ ○ │ │ ○ │ │ │ 95.00mm (jacks)
│ │ │ │ │ │ │
│ └───┘ └───┘ │ │
TRIG RETURN │ │ 105.00mm (labels)
│ │ │
│ LATENCY TESTER │ │ 115.00mm
│ │ │
│ │ 125.50mm (mounting holes)
└─────────────────────┘ ─┴─ 128.50mm
┌───────────────────────────┐ ─┬─ 0.00mm
○ │ │ 3.00mm (mounting holes)
│ │
SubModular │ │ 12.00mm
│ │
┌─────────────────┐ │ │
│ │
OLED │ │ 19-31mm (display window)
DISPLAY │ │
│ │
└─────────────────┘ │ │
│ │
SN-L00 │ │ 42.00mm
│ │
• │ │ 50.00mm (button + LED)
BTN PWR │ │
│ │
│ │
│ │
┌───┐ ┌───┐ │ │
│ │ │ │ │ │
│ ○ │ │ ○ │ │ │ 95.00mm (jacks)
│ │ │ │ │ │
└───┘ └───┘ │ │
RETURN TRIG │ │ 105.00mm (labels)
│ │
LATENCY TESTER │ │ 115.00mm
│ │
○ │ │ 125.50mm (mounting holes)
└───────────────────────────┘ ─┴─ 128.50mm
```
## OLED Display Window
@@ -79,7 +79,7 @@ The 0.91" 128×32 OLED module dimensions:
| Active area | ~22 × 6mm |
| Mounting | Pin header on edge |
**Panel cutout**: 28mm × 10mm rectangle, centered at (15.0, 25.0)
**Panel cutout**: 28mm × 10mm rectangle, centered at (20.15, 25.0)
Alternatively, a slightly larger window (30 × 12mm) gives tolerance for module alignment.
@@ -124,17 +124,11 @@ Soldermask: Black (or custom color)
| Element | Position | Size | Font |
|---------|----------|------|------|
| "SubModular" | Top center, Y=12mm | 2.5mm | Bold sans-serif |
| "SN-L00" | Above button, Y=38mm | 3.0mm | Bold |
| "TRIG" | Below left jack, Y=105mm | 2.0mm | Regular |
| "RETURN" | Below right jack, Y=105mm | 2.0mm | Regular |
| "SN-L00" | Above button, Y=42mm | 3.0mm | Bold |
| "RETURN" | Below left jack, Y=105mm | 2.0mm | Regular |
| "TRIG" | Below right jack, Y=105mm | 2.0mm | Regular |
| "LATENCY TESTER" | Bottom, Y=115mm | 1.5mm | Light |
### Icon Ideas
- Small waveform graphic near jacks
- Clock/timer icon
- Sub-Net logo (if available)
## Hole Tolerances
| Hole Type | Nominal | Tolerance |
@@ -144,14 +138,11 @@ Soldermask: Black (or custom color)
| Button | 7.0mm | +0.2/-0.0 |
| LED | 3.0mm | +0.1/-0.0 |
## Files to Create
## Files
For manufacturing:
1. **DXF/DWG** - For laser cutting (aluminum/acrylic)
2. **KiCad PCB** - For PCB panel
3. **SVG** - For graphics reference
4. **PDF** - Dimensional drawing
- **PANEL_SPEC.md** - This file
- **SN-L00_panel.svg** - Vector graphic for manufacturing
- **SN-L00_panel.kicad_pcb** - PCB panel (if using FR4)
## Assembly
+24 -24
View File
@@ -1,10 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg"
width="30mm" height="128.5mm"
viewBox="0 0 30 128.5">
width="40.30mm" height="128.5mm"
viewBox="0 0 40.30 128.5">
<title>SN-L00 Eurorack Panel</title>
<desc>6HP Eurorack panel for SN-L00 Latency Tester</desc>
<desc>8HP Eurorack panel for SN-L00 Latency Tester</desc>
<defs>
<style>
@@ -20,53 +20,53 @@
</defs>
<!-- Panel background -->
<rect class="panel" x="0" y="0" width="30" height="128.5"/>
<rect class="panel" x="0" y="0" width="40.30" height="128.5"/>
<!-- Mounting holes (3.2mm) - RED for drill -->
<!-- Top mounting holes -->
<circle class="hole" cx="7.5" cy="3" r="1.6"/>
<circle class="hole" cx="22.5" cy="3" r="1.6"/>
<circle class="hole" cx="32.8" cy="3" r="1.6"/>
<!-- Bottom mounting holes -->
<circle class="hole" cx="7.5" cy="125.5" r="1.6"/>
<circle class="hole" cx="22.5" cy="125.5" r="1.6"/>
<circle class="hole" cx="32.8" cy="125.5" r="1.6"/>
<!-- OLED display window (28x10mm rectangle) - GREEN for cutout -->
<rect class="cutout" x="1" y="20" width="28" height="10" rx="0.5"/>
<rect class="cutout" x="6.15" y="20" width="28" height="10" rx="0.5"/>
<!-- Button hole (7mm) -->
<circle class="hole" cx="15" cy="45" r="3.5"/>
<circle class="hole" cx="20.15" cy="50" r="3.5"/>
<!-- LED hole (3mm) -->
<circle class="hole" cx="24" cy="45" r="1.5"/>
<circle class="hole" cx="32" cy="50" r="1.5"/>
<!-- Jack holes (6mm) -->
<circle class="hole" cx="7.5" cy="95" r="3"/>
<circle class="hole" cx="22.5" cy="95" r="3"/>
<circle class="hole" cx="10" cy="95" r="3"/>
<circle class="hole" cx="30" cy="95" r="3"/>
<!-- Text labels - WHITE silkscreen -->
<text class="text text-medium" x="15" y="12" text-anchor="middle">SubModular</text>
<text class="text text-medium" x="20.15" y="12" text-anchor="middle">SubModular</text>
<text class="text text-large" x="15" y="38" text-anchor="middle" font-weight="bold">SN-L00</text>
<text class="text text-large" x="20.15" y="42" text-anchor="middle" font-weight="bold">SN-L00</text>
<text class="text text-small" x="15" y="52" text-anchor="middle">BTN</text>
<text class="text text-small" x="24" y="52" text-anchor="middle">PWR</text>
<text class="text text-small" x="20.15" y="57" text-anchor="middle">BTN</text>
<text class="text text-small" x="32" y="57" text-anchor="middle">PWR</text>
<text class="text text-small" x="7.5" y="105" text-anchor="middle">TRIG</text>
<text class="text text-small" x="22.5" y="105" text-anchor="middle">RETURN</text>
<text class="text text-small" x="10" y="105" text-anchor="middle">RETURN</text>
<text class="text text-small" x="30" y="105" text-anchor="middle">TRIG</text>
<text class="text text-small" x="15" y="115" text-anchor="middle">LATENCY TESTER</text>
<text class="text text-small" x="20.15" y="115" text-anchor="middle">LATENCY TESTER</text>
<!-- Decorative elements -->
<!-- Jack nut indicators (circles around jacks) -->
<circle class="silkscreen" cx="7.5" cy="95" r="5"/>
<circle class="silkscreen" cx="22.5" cy="95" r="5"/>
<circle class="silkscreen" cx="10" cy="95" r="5"/>
<circle class="silkscreen" cx="30" cy="95" r="5"/>
<!-- Display frame -->
<rect class="silkscreen" x="0.5" y="19.5" width="29" height="11" rx="0.5"/>
<rect class="silkscreen" x="5.65" y="19.5" width="29" height="11" rx="0.5"/>
<!-- Horizontal lines for visual separation -->
<line class="silkscreen" x1="2" y1="55" x2="28" y2="55"/>
<line class="silkscreen" x1="2" y1="85" x2="28" y2="85"/>
<line class="silkscreen" x1="2" y1="110" x2="28" y2="110"/>
<line class="silkscreen" x1="4" y1="60" x2="36.30" y2="60"/>
<line class="silkscreen" x1="4" y1="85" x2="36.30" y2="85"/>
<line class="silkscreen" x1="4" y1="110" x2="36.30" y2="110"/>
</svg>

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB