Sanjay1905 commited on
Commit
5ecbb59
Β·
verified Β·
1 Parent(s): fe805bf

Upload kicad_writer.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. kicad_writer.py +315 -0
kicad_writer.py ADDED
@@ -0,0 +1,315 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import uuid
3
+ import sys
4
+ import os
5
+ from datetime import datetime
6
+
7
+ # ─────────────────────────────────────────────
8
+ # CONFIGURATION
9
+ # ─────────────────────────────────────────────
10
+ KICAD_VERSION = 20231120
11
+ GRID_SIZE = 100 # mils β€” schematic grid spacing
12
+ START_X = 50 # starting X position for first component
13
+ START_Y = 50 # starting Y position for first component
14
+ COLS = 8 # components per row before wrapping
15
+
16
+ # KiCAD symbol mapping per component type
17
+ # Format: (library, symbol_name, pin_count)
18
+ SYMBOL_MAP = {
19
+ "resistor": ("Device", "R", 2),
20
+ "resistor jumper": ("Device", "R", 2),
21
+ "capacitor": ("Device", "C", 2),
22
+ "capacitor jumper": ("Device", "C", 2),
23
+ "electrolytic capacitor": ("Device", "CP", 2),
24
+ "inductor": ("Device", "L", 2),
25
+ "diode": ("Device", "D", 2),
26
+ "led": ("Device", "LED", 2),
27
+ "transistor": ("Device", "Q_NPN_BCE", 3),
28
+ "ic": ("Device", "IC", 4),
29
+ "connector": ("Connector", "Conn_01x04", 4),
30
+ "jumper": ("Device", "Jumper", 2),
31
+ "button": ("Device", "SW_Push", 2),
32
+ "clock": ("Device", "Crystal", 2),
33
+ "transformer": ("Device", "Transformer_1P_1S", 4),
34
+ "potentiometer": ("Device", "R_POT", 3),
35
+ "fuse": ("Device", "Fuse", 2),
36
+ "ferrite_bead": ("Device", "Ferrite_Bead", 2),
37
+ "buzzer": ("Device", "Buzzer", 2),
38
+ "display": ("Device", "LED_7Seg", 4),
39
+ "battery": ("Device", "Battery", 2),
40
+ "emi_filter": ("Device", "L", 2),
41
+ "resistor network": ("Device", "R_Pack04", 8),
42
+ "pins": ("Connector", "TestPoint", 1),
43
+ }
44
+
45
+ DEFAULT_SYMBOL = ("Device", "R", 2)
46
+
47
+
48
+ # ─────────────────────────────────────────────
49
+ # UUID GENERATOR
50
+ # ─────────────────────────────────────────────
51
+ def new_uuid() -> str:
52
+ return str(uuid.uuid4())
53
+
54
+
55
+ # ─────────────────────────────────────────────
56
+ # POSITION CALCULATOR
57
+ # Arranges components in a grid layout
58
+ # ─────────────────────────────────────────────
59
+ def calculate_positions(components: list) -> list:
60
+ positioned = []
61
+ for i, comp in enumerate(components):
62
+ col = i % COLS
63
+ row = i // COLS
64
+ x = START_X + col * GRID_SIZE * 2
65
+ y = START_Y + row * GRID_SIZE * 3
66
+ positioned.append({**comp, "sch_x": x, "sch_y": y})
67
+ return positioned
68
+
69
+
70
+ # ─────────────────────────────────────────────
71
+ # BUILD lib_symbols SECTION
72
+ # Defines all unique symbols used in schematic
73
+ # ─────────────────────────────────────────────
74
+ def build_lib_symbols(components: list) -> list:
75
+ lines = []
76
+ lines.append(" (lib_symbols")
77
+
78
+ seen = set()
79
+ for comp in components:
80
+ label = comp['label'].lower()
81
+ lib, sym, pins = SYMBOL_MAP.get(label, DEFAULT_SYMBOL)
82
+ key = f"{lib}:{sym}"
83
+ if key in seen:
84
+ continue
85
+ seen.add(key)
86
+
87
+ lines.append(f" (symbol \"{lib}:{sym}\"")
88
+ lines.append(f" (pin_numbers (hide yes))")
89
+ lines.append(f" (pin_names (offset 1.016) (hide yes))")
90
+ lines.append(f" (in_bom yes) (on_board yes)")
91
+
92
+ # Simple box symbol body
93
+ lines.append(f" (symbol \"{lib}:{sym}_0_1\"")
94
+ lines.append(f" (rectangle (start -1.016 -2.032) (end 1.016 2.032)")
95
+ lines.append(f" (stroke (width 0) (type default))")
96
+ lines.append(f" (fill (type none))")
97
+ lines.append(f" )")
98
+ lines.append(f" )")
99
+
100
+ # Pins
101
+ lines.append(f" (symbol \"{lib}:{sym}_1_1\"")
102
+ for p in range(1, pins + 1):
103
+ py = 2.032 - (p - 1) * (4.064 / max(pins - 1, 1)) if pins > 1 else 0
104
+ lines.append(f" (pin unspecified line")
105
+ lines.append(f" (at -3.81 {py:.3f} 0)")
106
+ lines.append(f" (length 2.794)")
107
+ lines.append(f" (name \"{p}\" (effects (font (size 1.27 1.27))))")
108
+ lines.append(f" (number \"{p}\" (effects (font (size 1.27 1.27))))")
109
+ lines.append(f" )")
110
+ lines.append(f" )")
111
+ lines.append(f" )")
112
+
113
+ lines.append(" )")
114
+ return lines
115
+
116
+
117
+ # ─────────────────────────────────────────────
118
+ # BUILD SYMBOL INSTANCES
119
+ # Places each component on the schematic
120
+ # ─────────────────────────────────────────────
121
+ def build_symbol_instances(components: list) -> list:
122
+ lines = []
123
+
124
+ for comp in components:
125
+ label = comp['label'].lower()
126
+ refdes = comp['refdes']
127
+ part = comp.get('part_number', label)
128
+ x = comp['sch_x']
129
+ y = comp['sch_y']
130
+ comp_uuid = new_uuid()
131
+ lib, sym, _ = SYMBOL_MAP.get(label, DEFAULT_SYMBOL)
132
+
133
+ lines.append(f" (symbol")
134
+ lines.append(f" (lib_id \"{lib}:{sym}\")")
135
+ lines.append(f" (at {x} {y} 0)")
136
+ lines.append(f" (unit 1)")
137
+ lines.append(f" (in_bom yes) (on_board yes)")
138
+ lines.append(f" (uuid \"{comp_uuid}\")")
139
+
140
+ # Reference field
141
+ lines.append(f" (property \"Reference\" \"{refdes}\"")
142
+ lines.append(f" (at {x} {y - 3.81} 0)")
143
+ lines.append(f" (effects (font (size 1.27 1.27)))")
144
+ lines.append(f" )")
145
+
146
+ # Value field
147
+ lines.append(f" (property \"Value\" \"{part}\"")
148
+ lines.append(f" (at {x} {y + 3.81} 0)")
149
+ lines.append(f" (effects (font (size 1.27 1.27)))")
150
+ lines.append(f" )")
151
+
152
+ # Footprint field (empty for now)
153
+ lines.append(f" (property \"Footprint\" \"\"")
154
+ lines.append(f" (at {x} {y} 0)")
155
+ lines.append(f" (effects (font (size 1.27 1.27)) (hide yes))")
156
+ lines.append(f" )")
157
+
158
+ lines.append(f" )")
159
+
160
+ return lines
161
+
162
+
163
+ # ─────────────────────────────────────────────
164
+ # BUILD NET LABELS
165
+ # Places net labels connecting components
166
+ # ─────────────────────────────────────────────
167
+ def build_net_labels(components: list, nets: dict) -> list:
168
+ lines = []
169
+ refdes_map = {c['refdes']: c for c in components}
170
+
171
+ for net_name, members in nets.items():
172
+ for refdes in members:
173
+ comp = refdes_map.get(refdes)
174
+ if not comp:
175
+ continue
176
+
177
+ x = comp['sch_x'] - 3.81
178
+ y = comp['sch_y']
179
+
180
+ lines.append(f" (label \"{net_name}\"")
181
+ lines.append(f" (at {x} {y} 0)")
182
+ lines.append(f" (effects (font (size 1.27 1.27)) (justify left))")
183
+ lines.append(f" (uuid \"{new_uuid()}\")")
184
+ lines.append(f" )")
185
+
186
+ return lines
187
+
188
+
189
+ # ─────────────────────────────────────────────
190
+ # BUILD WIRES
191
+ # Draws wires between components in same net
192
+ # ─────────────────────────────────────────────
193
+ def build_wires(components: list, nets: dict) -> list:
194
+ lines = []
195
+ refdes_map = {c['refdes']: c for c in components}
196
+
197
+ for net_name, members in nets.items():
198
+ if len(members) < 2:
199
+ continue
200
+
201
+ # Connect each member to the next one with a wire
202
+ for i in range(len(members) - 1):
203
+ a = refdes_map.get(members[i])
204
+ b = refdes_map.get(members[i + 1])
205
+ if not a or not b:
206
+ continue
207
+
208
+ x1, y1 = a['sch_x'], a['sch_y']
209
+ x2, y2 = b['sch_x'], b['sch_y']
210
+
211
+ lines.append(f" (wire")
212
+ lines.append(f" (pts (xy {x1} {y1}) (xy {x2} {y2}))")
213
+ lines.append(f" (stroke (width 0) (type default))")
214
+ lines.append(f" (uuid \"{new_uuid()}\")")
215
+ lines.append(f" )")
216
+
217
+ return lines
218
+
219
+
220
+ # ─────────────────────────────────────────────
221
+ # MAIN β€” GENERATE .kicad_sch FILE
222
+ # ─────────────────────────────────────────────
223
+ def generate_kicad_schematic(netlist_json_path: str,
224
+ output_path: str = None) -> str:
225
+ print(f"\n{'='*50}")
226
+ print(f" KiCAD Schematic Writer")
227
+ print(f" Input: {netlist_json_path}")
228
+ print(f"{'='*50}\n")
229
+
230
+ # Load netlist JSON
231
+ with open(netlist_json_path) as f:
232
+ data = json.load(f)
233
+
234
+ components = data.get("components", [])
235
+ nets = data.get("nets", {})
236
+
237
+ for c in components:
238
+ c['bbox'] = tuple(c['bbox'])
239
+
240
+ print(f"[OK] Loaded {len(components)} components, {len(nets)} nets")
241
+
242
+ # Calculate grid positions
243
+ components = calculate_positions(components)
244
+
245
+ # Build schematic sections
246
+ lib_symbols = build_lib_symbols(components)
247
+ instances = build_symbol_instances(components)
248
+ net_labels = build_net_labels(components, nets)
249
+ wires = build_wires(components, nets)
250
+
251
+ # Assemble full .kicad_sch file
252
+ timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
253
+ sheet_uuid = new_uuid()
254
+
255
+ lines = []
256
+ lines.append(f"(kicad_sch (version {KICAD_VERSION}) (generator \"PCB_Image2Schematic\")")
257
+ lines.append(f"")
258
+ lines.append(f" (uuid \"{sheet_uuid}\")")
259
+ lines.append(f"")
260
+ lines.append(f" (paper \"A3\")")
261
+ lines.append(f"")
262
+ lines.append(f" (title_block")
263
+ lines.append(f" (title \"PCB Image to Schematic\")")
264
+ lines.append(f" (date \"{timestamp}\")")
265
+ lines.append(f" (rev \"1.0\")")
266
+ lines.append(f" (comment 1 \"Auto-generated by PCB Image2Schematic\")")
267
+ lines.append(f" (comment 2 \"Source: {netlist_json_path}\")")
268
+ lines.append(f" )")
269
+ lines.append(f"")
270
+
271
+ # lib_symbols
272
+ lines.extend(lib_symbols)
273
+ lines.append("")
274
+
275
+ # component instances
276
+ lines.extend(instances)
277
+ lines.append("")
278
+
279
+ # net labels
280
+ lines.extend(net_labels)
281
+ lines.append("")
282
+
283
+ # wires
284
+ lines.extend(wires)
285
+ lines.append("")
286
+
287
+ lines.append(")")
288
+
289
+ # Write file
290
+ if output_path is None:
291
+ base = os.path.splitext(netlist_json_path)[0]
292
+ output_path = base.replace("_netlist", "") + ".kicad_sch"
293
+
294
+ with open(output_path, "w") as f:
295
+ f.write("\n".join(lines))
296
+
297
+ print(f"[OK] KiCAD schematic saved: {output_path}")
298
+ print(f" Components placed : {len(components)}")
299
+ print(f" Nets wired : {len(nets)}")
300
+ print(f" Net labels added : {len(net_labels) // 5}")
301
+ print(f"\n Open in KiCAD: File > Open Schematic > {output_path}")
302
+
303
+ return output_path
304
+
305
+
306
+ # ─────────────────────────────────────────────
307
+ # ENTRY POINT
308
+ # ─────────────────────────────────────────────
309
+ if __name__ == "__main__":
310
+ if len(sys.argv) < 2:
311
+ print("Usage: python kicad_writer.py <netlist_json>")
312
+ print("Example: python kicad_writer.py 'sample 5_netlist.json'")
313
+ sys.exit(1)
314
+
315
+ generate_kicad_schematic(sys.argv[1])