harnice 0.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. harnice/__init__.py +0 -0
  2. harnice/__main__.py +4 -0
  3. harnice/cli.py +234 -0
  4. harnice/fileio.py +295 -0
  5. harnice/gui/launcher.py +426 -0
  6. harnice/lists/channel_map.py +182 -0
  7. harnice/lists/circuits_list.py +302 -0
  8. harnice/lists/disconnect_map.py +237 -0
  9. harnice/lists/formboard_graph.py +63 -0
  10. harnice/lists/instances_list.py +280 -0
  11. harnice/lists/library_history.py +40 -0
  12. harnice/lists/manifest.py +93 -0
  13. harnice/lists/post_harness_instances_list.py +66 -0
  14. harnice/lists/rev_history.py +325 -0
  15. harnice/lists/signals_list.py +135 -0
  16. harnice/products/__init__.py +1 -0
  17. harnice/products/cable.py +152 -0
  18. harnice/products/chtype.py +80 -0
  19. harnice/products/device.py +844 -0
  20. harnice/products/disconnect.py +225 -0
  21. harnice/products/flagnote.py +139 -0
  22. harnice/products/harness.py +522 -0
  23. harnice/products/macro.py +10 -0
  24. harnice/products/part.py +640 -0
  25. harnice/products/system.py +125 -0
  26. harnice/products/tblock.py +270 -0
  27. harnice/state.py +57 -0
  28. harnice/utils/appearance.py +51 -0
  29. harnice/utils/circuit_utils.py +326 -0
  30. harnice/utils/feature_tree_utils.py +183 -0
  31. harnice/utils/formboard_utils.py +973 -0
  32. harnice/utils/library_utils.py +333 -0
  33. harnice/utils/note_utils.py +417 -0
  34. harnice/utils/svg_utils.py +819 -0
  35. harnice/utils/system_utils.py +563 -0
  36. harnice-0.3.0.dist-info/METADATA +32 -0
  37. harnice-0.3.0.dist-info/RECORD +41 -0
  38. harnice-0.3.0.dist-info/WHEEL +5 -0
  39. harnice-0.3.0.dist-info/entry_points.txt +3 -0
  40. harnice-0.3.0.dist-info/licenses/LICENSE +19 -0
  41. harnice-0.3.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,563 @@
1
+ import os
2
+ import csv
3
+ from collections import deque
4
+ from harnice import fileio
5
+ from harnice.lists import instances_list
6
+ from harnice.utils import library_utils
7
+
8
+
9
+ def mpn_of_device_refdes(refdes):
10
+ """
11
+ Looks up manufacturer part number information for a device reference designator.
12
+
13
+ Searches the BOM (Bill of Materials) for a device with the given reference designator
14
+ and returns its manufacturer, part number, and revision.
15
+
16
+ **Args:**
17
+ - `refdes` (str): Device reference designator to look up (e.g., `"J1"`, `"X1"`).
18
+
19
+ **Returns:**
20
+ - `tuple`: A tuple of `(MFG, MPN, rev)` if found, or `(None, None, None)` if not found.
21
+ """
22
+ for row in fileio.read_tsv("bom"):
23
+ if row.get("device_refdes") == refdes:
24
+ return row.get("MFG"), row.get("MPN"), row.get("rev")
25
+ return None, None, None
26
+
27
+
28
+ def connector_of_channel(key):
29
+ """
30
+ Finds the connector name associated with a device channel.
31
+
32
+ Given a device reference designator and channel ID tuple, looks up the corresponding
33
+ connector name from the device's signals list.
34
+
35
+ **Args:**
36
+ - `key` (tuple): A tuple of `(device_refdes, channel_id)` identifying the channel.
37
+
38
+ **Returns:**
39
+ - `str`: The connector name associated with the channel.
40
+
41
+ **Raises:**
42
+ - `ValueError`: If the connector is not found for the given channel.
43
+ """
44
+ refdes, channel_id = key
45
+
46
+ device_signals_list_path = os.path.join(
47
+ fileio.dirpath("instance_data"),
48
+ "device",
49
+ refdes,
50
+ f"{refdes}-signals_list.tsv",
51
+ )
52
+ for row in fileio.read_tsv(device_signals_list_path):
53
+ if row.get("channel_id", "").strip() == channel_id.strip():
54
+ return row.get("connector_name", "").strip()
55
+
56
+ raise ValueError(f"Connector not found for channel {key}")
57
+
58
+
59
+ def find_connector_with_no_circuit(connector_list, circuits_list):
60
+ """
61
+ Validates that all connectors have associated circuits.
62
+
63
+ Checks each connector in the connector list to ensure it has at least one
64
+ corresponding circuit in the circuits list. Skips connectors with `"unconnected"`
65
+ in their net name. Raises an error if any connector lacks a circuit.
66
+
67
+ **Args:**
68
+ - `connector_list` (list): List of connector dictionaries from the system connector list.
69
+ - `circuits_list` (list): List of circuit dictionaries from the circuits list.
70
+
71
+ **Raises:**
72
+ - `ValueError`: If a connector is found that has no associated circuits. The error
73
+ message suggests checking the channel map and channel compatibility.
74
+ """
75
+ for connector in connector_list:
76
+ device_refdes = connector.get("device_refdes", "").strip()
77
+ connector_name = connector.get("connector", "").strip()
78
+
79
+ # skip if either key is missing
80
+ if not device_refdes or not connector_name:
81
+ continue
82
+
83
+ # skip device if net name contains "unconnected"
84
+ if "unconnected" in connector.get("net", "").strip():
85
+ continue
86
+
87
+ found_match = False
88
+ for circuit in circuits_list:
89
+ from_device_refdes = circuit.get("net_from_refdes", "").strip()
90
+ from_connector_name = circuit.get("net_from_connector_name", "").strip()
91
+ to_device_refdes = circuit.get("net_to_refdes", "").strip()
92
+ to_connector_name = circuit.get("net_to_connector_name", "").strip()
93
+
94
+ if (
95
+ from_device_refdes == device_refdes
96
+ and from_connector_name == connector_name
97
+ ) or (
98
+ to_device_refdes == device_refdes
99
+ and to_connector_name == connector_name
100
+ ):
101
+ found_match = True
102
+ break
103
+
104
+ if not found_match:
105
+ raise ValueError(
106
+ f"Connector '{connector_name}' of device '{device_refdes}' does not contain any circuits. Check if it was mapped in the channel map. If n, check channel compatibility."
107
+ )
108
+
109
+
110
+ def make_instances_for_connectors_cavities_nodes_channels_circuits():
111
+ """
112
+ Creates instances for all system components based on circuits.
113
+
114
+ This function processes the circuits list and creates instances in the instances list
115
+ for all connectors, connector cavities, nodes, channels, and circuits in the system.
116
+ For each circuit, it creates:
117
+
118
+ - Connector nodes (at both ends)
119
+ - Connector instances (at both ends, with MPN lookup from system connector list)
120
+ - Connector cavity instances (at both ends)
121
+ - Circuit instance
122
+ - Channel instance
123
+ - Net-channel instances for nets in the channel chain (only for nets matching the circuit's net)
124
+
125
+ After processing all circuits, the function updates connector instances with mating device
126
+ information from the system connector list.
127
+
128
+ The function reads from the circuits list, system connector list, and channel map
129
+ to build the complete instance hierarchy.
130
+ """
131
+ connectors_list = fileio.read_tsv("system connector list")
132
+ channel_map = fileio.read_tsv("channel map")
133
+
134
+ for circuit in fileio.read_tsv("circuits list"):
135
+ from_connector_key = (
136
+ f"{circuit.get('net_from_refdes')}.{circuit.get('net_from_connector_name')}"
137
+ )
138
+ from_cavity = f"{from_connector_key}.{circuit.get('net_from_cavity')}"
139
+
140
+ # Look up connector MPN from system connector list for the from connector
141
+ from_connector_mpn = ""
142
+ for connector in connectors_list:
143
+ if connector.get("device_refdes") == circuit.get(
144
+ "net_from_refdes"
145
+ ) and connector.get("connector") == circuit.get("net_from_connector_name"):
146
+ from_connector_mpn = connector.get("connector_mpn")
147
+ break
148
+
149
+ # from connector node
150
+ instances_list.new_instance(
151
+ f"{from_connector_key}.node",
152
+ {
153
+ "net": circuit.get("net"),
154
+ "item_type": "node",
155
+ "location_type": "node",
156
+ "connector_group": from_connector_key,
157
+ },
158
+ ignore_duplicates=True,
159
+ )
160
+
161
+ # from connector
162
+ instances_list.new_instance(
163
+ f"{from_connector_key}.conn",
164
+ {
165
+ "net": circuit.get("net"),
166
+ "item_type": "connector",
167
+ "location_type": "node",
168
+ "connector_group": from_connector_key,
169
+ "parent_csys_instance_name": f"{from_connector_key}.node",
170
+ "parent_csys_outputcsys_name": "origin",
171
+ "this_instance_mating_device_refdes": circuit.get("net_from_refdes"),
172
+ "this_instance_mating_device_connector": circuit.get(
173
+ "net_from_connector_name"
174
+ ),
175
+ "this_instance_mating_device_connector_mpn": from_connector_mpn,
176
+ },
177
+ ignore_duplicates=True,
178
+ )
179
+
180
+ # from connector cavity
181
+ instances_list.new_instance(
182
+ from_cavity,
183
+ {
184
+ "net": circuit.get("net"),
185
+ "item_type": "connector_cavity",
186
+ "parent_instance": f"{from_connector_key}.conn", # from connector instance
187
+ "location_type": "node",
188
+ "connector_group": from_connector_key,
189
+ "circuit_id": circuit.get("circuit_id"),
190
+ "circuit_port_number": 0,
191
+ },
192
+ ignore_duplicates=True,
193
+ )
194
+
195
+ to_connector_key = (
196
+ f"{circuit.get('net_to_refdes')}.{circuit.get('net_to_connector_name')}"
197
+ )
198
+ to_cavity = f"{to_connector_key}.{circuit.get('net_to_cavity')}"
199
+
200
+ # Look up connector MPN from system connector list for the to connector
201
+ to_connector_mpn = ""
202
+ for connector in connectors_list:
203
+ if connector.get("device_refdes") == circuit.get(
204
+ "net_to_refdes"
205
+ ) and connector.get("connector") == circuit.get("net_to_connector_name"):
206
+ to_connector_mpn = connector.get("connector_mpn")
207
+ break
208
+
209
+ # to connector node
210
+ instances_list.new_instance(
211
+ f"{to_connector_key}.node",
212
+ {
213
+ "net": circuit.get("net"),
214
+ "item_type": "node",
215
+ "location_type": "node",
216
+ "connector_group": to_connector_key,
217
+ },
218
+ ignore_duplicates=True,
219
+ )
220
+
221
+ # to connector
222
+ instances_list.new_instance(
223
+ f"{to_connector_key}.conn",
224
+ {
225
+ "net": circuit.get("net"),
226
+ "item_type": "connector",
227
+ "location_type": "node",
228
+ "connector_group": to_connector_key,
229
+ "parent_csys_instance_name": f"{to_connector_key}.node",
230
+ "parent_csys_outputcsys_name": "origin",
231
+ "this_instance_mating_device_refdes": circuit.get("net_to_refdes"),
232
+ "this_instance_mating_device_connector": circuit.get(
233
+ "net_to_connector_name"
234
+ ),
235
+ "this_instance_mating_device_connector_mpn": to_connector_mpn,
236
+ },
237
+ ignore_duplicates=True,
238
+ )
239
+
240
+ # to connector cavity
241
+ instances_list.new_instance(
242
+ to_cavity,
243
+ {
244
+ "net": circuit.get("net"),
245
+ "item_type": "connector_cavity",
246
+ "parent_instance": f"{to_connector_key}.conn", # to connector instance
247
+ "location_type": "node",
248
+ "connector_group": to_connector_key,
249
+ "circuit_id": circuit.get("circuit_id"),
250
+ "circuit_port_number": 1,
251
+ },
252
+ ignore_duplicates=True,
253
+ )
254
+
255
+ # add circuit
256
+ instances_list.new_instance(
257
+ f"circuit-{circuit.get('circuit_id')}",
258
+ {
259
+ "net": circuit.get("net"),
260
+ "item_type": "circuit",
261
+ "print_name": f"{circuit.get("signal")} of {circuit.get('from_side_device_refdes')}.{circuit.get('from_side_device_chname')} <-> {circuit.get('to_side_device_refdes')}.{circuit.get('to_side_device_chname')}",
262
+ "channel_group": f"channel-{circuit.get('from_side_device_refdes')}.{circuit.get('from_side_device_chname')}-{circuit.get('to_side_device_refdes')}.{circuit.get('to_side_device_chname')}",
263
+ "circuit_id": circuit.get("circuit_id"),
264
+ "node_at_end_a": from_cavity,
265
+ "node_at_end_b": to_cavity,
266
+ "this_net_from_device_refdes": circuit.get("net_from_refdes"),
267
+ "this_net_from_device_channel_id": circuit.get("net_from_channel_id"),
268
+ "this_net_from_device_connector_name": circuit.get(
269
+ "net_from_connector_name"
270
+ ),
271
+ "this_net_to_device_refdes": circuit.get("net_to_refdes"),
272
+ "this_net_to_device_channel_id": circuit.get("net_to_channel_id"),
273
+ "this_net_to_device_connector_name": circuit.get(
274
+ "net_to_connector_name"
275
+ ),
276
+ "this_channel_from_device_refdes": circuit.get(
277
+ "from_side_device_refdes"
278
+ ),
279
+ "this_channel_from_device_channel_id": circuit.get(
280
+ "from_side_device_chname"
281
+ ),
282
+ "this_channel_to_device_refdes": circuit.get("to_side_device_refdes"),
283
+ "this_channel_to_device_channel_id": circuit.get(
284
+ "to_side_device_chname"
285
+ ),
286
+ "this_channel_from_channel_type": circuit.get("from_channel_type"),
287
+ "this_channel_to_channel_type": circuit.get("to_channel_type"),
288
+ "signal_of_channel_type": circuit.get("signal"),
289
+ },
290
+ ignore_duplicates=True,
291
+ )
292
+
293
+ # --- add channel
294
+ instances_list.new_instance(
295
+ f"channel-{circuit.get('from_side_device_refdes')}.{circuit.get('from_side_device_chname')}-"
296
+ f"{circuit.get('to_side_device_refdes')}.{circuit.get('to_side_device_chname')}",
297
+ {
298
+ "item_type": "channel",
299
+ "print_name": f"{circuit.get('from_side_device_refdes')}.{circuit.get('from_side_device_chname')} <-> {circuit.get('to_side_device_refdes')}.{circuit.get('to_side_device_chname')}",
300
+ "channel_group": (
301
+ f"channel-{circuit.get('from_side_device_refdes')}.{circuit.get('from_side_device_chname')}-"
302
+ f"{circuit.get('to_side_device_refdes')}.{circuit.get('to_side_device_chname')}"
303
+ ),
304
+ "location_type": "segment",
305
+ "this_channel_from_device_refdes": circuit.get(
306
+ "from_side_device_refdes"
307
+ ),
308
+ "this_channel_from_device_channel_id": circuit.get(
309
+ "from_side_device_chname"
310
+ ),
311
+ "this_channel_to_device_refdes": circuit.get("to_side_device_refdes"),
312
+ "this_channel_to_device_channel_id": circuit.get(
313
+ "to_side_device_chname"
314
+ ),
315
+ "this_channel_from_channel_type": circuit.get("from_channel_type"),
316
+ "this_channel_to_channel_type": circuit.get("to_channel_type"),
317
+ },
318
+ ignore_duplicates=True,
319
+ )
320
+
321
+ # Find the chain of nets for this channel from the channel map
322
+ # The chain_of_nets field contains semicolon-separated net names
323
+ chain_of_nets = []
324
+ for channel in channel_map:
325
+ if (
326
+ channel.get("from_device_refdes")
327
+ == circuit.get("from_side_device_refdes")
328
+ and channel.get("from_device_channel_id")
329
+ == circuit.get("from_side_device_chname")
330
+ and channel.get("to_device_refdes")
331
+ == circuit.get("to_side_device_refdes")
332
+ and channel.get("to_device_channel_id")
333
+ == circuit.get("to_side_device_chname")
334
+ ):
335
+ chain_of_nets = (channel.get("chain_of_nets") or "").split(";")
336
+ break
337
+
338
+ # Create net-channel instances for each net in the chain
339
+ for net in chain_of_nets:
340
+ if circuit.get('net') == net:
341
+ instances_list.new_instance(
342
+ f"{net}:channel-{circuit.get('from_side_device_refdes')}.{circuit.get('from_side_device_chname')}-"
343
+ f"{circuit.get('to_side_device_refdes')}.{circuit.get('to_side_device_chname')}",
344
+ {
345
+ "net": net,
346
+ "item_type": "net-channel",
347
+ "print_name": (f"{circuit.get('from_side_device_refdes')}.{circuit.get('from_side_device_chname')} <-> {circuit.get('to_side_device_refdes')}.{circuit.get('to_side_device_chname')}"),
348
+ "channel_group": (f"channel-{circuit.get('from_side_device_refdes')}.{circuit.get('from_side_device_chname')}-{circuit.get('to_side_device_refdes')}.{circuit.get('to_side_device_chname')}"),
349
+ "location_type": "segment",
350
+ "parent_instance": f"channel-{circuit.get('from_side_device_refdes')}.{circuit.get('from_side_device_chname')}-{circuit.get('to_side_device_refdes')}.{circuit.get('to_side_device_chname')}",
351
+ "node_at_end_a": from_cavity,
352
+ "node_at_end_b": to_cavity,
353
+ "this_net_from_device_refdes": circuit.get('net_from_refdes'),
354
+ "this_net_from_device_channel_id": circuit.get('net_from_channel_id'),
355
+ "this_net_from_device_connector_name": circuit.get('net_from_connector_name'),
356
+ "this_net_to_device_refdes": circuit.get('net_to_refdes'),
357
+ "this_net_to_device_channel_id": circuit.get('net_to_channel_id'),
358
+ "this_net_to_device_connector_name": circuit.get('net_to_connector_name'),
359
+ "this_channel_from_device_refdes": circuit.get("from_side_device_refdes"),
360
+ "this_channel_from_device_channel_id": circuit.get("from_side_device_chname"),
361
+ "this_channel_to_device_refdes": circuit.get("to_side_device_refdes"),
362
+ "this_channel_to_device_channel_id": circuit.get("to_side_device_chname"),
363
+ "this_channel_from_channel_type": circuit.get("from_channel_type"),
364
+ "this_channel_to_channel_type": circuit.get("to_channel_type"),
365
+ },
366
+ ignore_duplicates=True,
367
+ )
368
+
369
+ # Post-process: Update all connector instances with mating device information
370
+ # This ensures connector instances have the correct mating device refdes, connector name, and MPN
371
+ for connector in fileio.read_tsv("system connector list"):
372
+ try:
373
+ instances_list.modify(
374
+ f"{connector.get('device_refdes')}.{connector.get('connector')}.conn",
375
+ {
376
+ "this_instance_mating_device_refdes": connector.get(
377
+ "device_refdes"
378
+ ),
379
+ "this_instance_mating_device_connector": connector.get("connector"),
380
+ "this_instance_mating_device_connector_mpn": connector.get(
381
+ "connector_mpn"
382
+ ),
383
+ },
384
+ )
385
+ except ValueError:
386
+ # Skip if connector instance doesn't exist (may not have been created if no circuits)
387
+ pass
388
+
389
+
390
+ def add_chains_to_channel_map():
391
+ """
392
+ For each (from_device/channel) -> (to_device/channel) in the channel map,
393
+ find:
394
+ - 'disconnect_refdes_requirement' (like "X1(A,B);X2(B,A)")
395
+ - 'chain_of_nets' (like "WH-1;WH-2;WH-3" or a single net if no disconnects)
396
+ - 'chain_of_connectors' (like "WH-1.net.connector_name;WH-1.net.connector_name;WH-2.net.connector_name;WH-2.net.connector_name")
397
+ - Format: net_name.connector_name where connector_name is net.connector_name (e.g., "/MIC_CABLE_2.MIC3out1" where "/MIC_CABLE_2" is the net)
398
+ - Dots separate net_name from connector_name, semicolons separate connectors
399
+ """
400
+
401
+ channel_map = fileio.read_tsv("channel map")
402
+ by_device, by_net, net_of, is_disconnect = {}, {}, {}, set()
403
+
404
+ for row in fileio.read_tsv("system connector list"):
405
+ dev = (row.get("device_refdes") or "").strip()
406
+ con = (row.get("connector") or "").strip()
407
+ net = (row.get("net") or "").strip()
408
+ if not dev or not con:
409
+ continue
410
+
411
+ by_device.setdefault(dev, []).append(con)
412
+ if net:
413
+ by_net.setdefault(net, []).append((dev, con))
414
+ net_of[(dev, con)] = net
415
+ if (row.get("disconnect") or "").strip().upper() == "TRUE":
416
+ is_disconnect.add(dev)
417
+
418
+ def _shortest_disconnect_chain(from_cn_key, to_cn_key):
419
+ start, goal = from_cn_key, to_cn_key
420
+ q, seen, prev = deque([start]), {start}, {}
421
+
422
+ while q:
423
+ cur = q.popleft()
424
+ if cur == goal:
425
+ break
426
+ net = net_of.get(cur)
427
+ if net:
428
+ for nxt in by_net.get(net, []):
429
+ if nxt not in seen:
430
+ seen.add(nxt)
431
+ prev[nxt] = cur
432
+ q.append(nxt)
433
+ dev, _ = cur
434
+ for other_con in by_device.get(dev, []):
435
+ nxt = (dev, other_con)
436
+ if nxt not in seen:
437
+ seen.add(nxt)
438
+ prev[nxt] = cur
439
+ q.append(nxt)
440
+
441
+ if start != goal and goal not in prev:
442
+ return [], [], []
443
+
444
+ # reconstruct path
445
+ path = [goal]
446
+ while path[-1] != start:
447
+ path.append(prev[path[-1]])
448
+ path.reverse()
449
+
450
+ # -------------------------------------------------------------
451
+ # build disconnect chain, net chain, and connector chain
452
+ chain, net_chain, connector_chain = [], [], []
453
+
454
+ for a, b in zip(path, path[1:]):
455
+ net_a = net_of.get(a)
456
+
457
+ # collect unique net chain
458
+ if net_a and (not net_chain or net_chain[-1] != net_a):
459
+ net_chain.append(net_a)
460
+
461
+ # detect disconnect traversal
462
+ if a[0] == b[0] and a[0] in is_disconnect:
463
+ dev = a[0]
464
+ port_a, port_b = a[1], b[1]
465
+ chain.append(f"{dev}({port_a},{port_b})")
466
+
467
+ # Build connector chain in format: net.connector_name;net.connector_name;...
468
+ # Format: {net}.{connector_name} where net comes from net_of lookup
469
+ # For each net in the chain, find its first and last connector in the path
470
+ net_to_connectors = {}
471
+ for i, (dev, con) in enumerate(path):
472
+ net = net_of.get((dev, con))
473
+ if net:
474
+ if net not in net_to_connectors:
475
+ net_to_connectors[net] = []
476
+ net_to_connectors[net].append((i, dev, con))
477
+
478
+ # Build connector chain: for each net in net_chain, add net.connector_name pairs
479
+ # Format: {net}.{connector_name} (e.g., "WH-1.MIC3out1" where "WH-1" is the net and "MIC3out1" is the connector)
480
+ for net in net_chain:
481
+ if net in net_to_connectors:
482
+ connectors = net_to_connectors[net]
483
+ if len(connectors) >= 1:
484
+ # First connector is the from connector
485
+ _, from_dev, from_con = connectors[0]
486
+ # Last connector is the to connector
487
+ _, to_dev, to_con = connectors[-1]
488
+ # Look up the net for each connector (net_of maps (device_refdes, connector) -> net)
489
+ from_net = net_of.get((from_dev, from_con), "")
490
+ to_net = net_of.get((to_dev, to_con), "")
491
+ # Add both connectors: from connector and to connector for this net
492
+ connector_chain.append(f"{from_net}.{from_con}")
493
+ connector_chain.append(f"{to_net}.{to_con}")
494
+
495
+ return chain, net_chain, connector_chain
496
+
497
+ # -------------------------------------------------------------
498
+ # apply to each channel pair
499
+ for row in channel_map:
500
+ from_key = (row.get("from_device_refdes"), row.get("from_device_channel_id"))
501
+ to_key = (row.get("to_device_refdes"), row.get("to_device_channel_id"))
502
+ if not all(from_key) or not all(to_key):
503
+ continue
504
+
505
+ from_cn = (from_key[0], connector_of_channel(from_key))
506
+ to_cn = (to_key[0], connector_of_channel(to_key))
507
+
508
+ n_from = net_of.get(from_cn)
509
+ n_to = net_of.get(to_cn)
510
+
511
+ # Special case: from and to connectors are on the same net (direct connection, no disconnects)
512
+ if n_from and n_to and n_from == n_to:
513
+ row["disconnect_refdes_requirement"] = ""
514
+ row["chain_of_nets"] = n_from
515
+ # Format: net.net.connector_name;net.net.connector_name
516
+ # Look up the net for each connector (should match n_from/n_to)
517
+ from_net = net_of.get(from_cn, "")
518
+ to_net = net_of.get(to_cn, "")
519
+ row["chain_of_connectors"] = (
520
+ f"{n_from}.{from_net}.{from_cn[1]};{n_to}.{to_net}.{to_cn[1]}"
521
+ )
522
+ continue
523
+
524
+ chain, net_chain, connector_chain = _shortest_disconnect_chain(from_cn, to_cn)
525
+ if chain or net_chain or connector_chain:
526
+ row["disconnect_refdes_requirement"] = ";".join(chain)
527
+ row["chain_of_nets"] = ";".join(net_chain)
528
+ row["chain_of_connectors"] = ";".join(connector_chain)
529
+
530
+ with open(fileio.path("channel map"), "w", newline="", encoding="utf-8") as f:
531
+ writer = csv.DictWriter(f, fieldnames=channel_map[0].keys(), delimiter="\t")
532
+ writer.writeheader()
533
+ writer.writerows(channel_map)
534
+
535
+
536
+ def make_instances_from_bom():
537
+ """
538
+ Creates instances for all devices and disconnects from the BOM.
539
+
540
+ Reads the Bill of Materials (BOM) and imports each device or disconnect into the
541
+ instances list using `library_utils.pull()`. Each item is imported with its manufacturer,
542
+ part number, revision, and library information.
543
+
544
+ Items with the `"disconnect"` field set are imported as type `"disconnect"`,
545
+ all others are imported as type `"device"`.
546
+ """
547
+ for device in fileio.read_tsv("bom"):
548
+ if device.get("disconnect"):
549
+ item_type = "disconnect"
550
+ else:
551
+ item_type = "device"
552
+
553
+ library_utils.pull(
554
+ {
555
+ "instance_name": device.get("device_refdes"),
556
+ "mfg": device.get("MFG"),
557
+ "mpn": device.get("MPN"),
558
+ "item_type": item_type,
559
+ "lib_repo": device.get("lib_repo"),
560
+ "lib_subpath": device.get("lib_subpath"),
561
+ "lib_rev_used_here": device.get("rev"),
562
+ }
563
+ )
@@ -0,0 +1,32 @@
1
+ Metadata-Version: 2.4
2
+ Name: harnice
3
+ Version: 0.3.0
4
+ Summary: Electrical System CAD
5
+ Author-email: Kenyon Shutt <harnice.io@gmail.com>
6
+ Project-URL: Documentation, https://harnice.io/
7
+ Project-URL: Homepage, https://github.com/harnice/Harnice
8
+ Project-URL: Issues, https://github.com/harnice/Harnice/issues
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Operating System :: OS Independent
12
+ Requires-Python: >=3.8
13
+ Description-Content-Type: text/markdown
14
+ License-File: LICENSE
15
+ Requires-Dist: PySide6>=6.6
16
+ Requires-Dist: sexpdata
17
+ Requires-Dist: Pillow
18
+ Requires-Dist: PyYAML
19
+ Requires-Dist: xlwt
20
+ Requires-Dist: webcolors
21
+ Dynamic: license-file
22
+
23
+ Harnice is a free, open source electrical system CAD tool.
24
+
25
+ It designs harnesses for you... and does so much more... *nicely!*
26
+
27
+ Please visit...
28
+ - [harnice.io](https://harnice.io/) for documentation
29
+ - https://github.com/harnice/harnice/issues to report a bug or feature request
30
+ - harnice.io@gmail.com to send me an email
31
+
32
+ Made by Kenyon Shutt
@@ -0,0 +1,41 @@
1
+ harnice/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ harnice/__main__.py,sha256=6Ni-cHmuKYqm9WKNd5bgzHyMkpyJ80IzoUmBqldUanw,68
3
+ harnice/cli.py,sha256=npYeqdywC8C4dStWzGnwy25hTapUEkCTFeNwo9aDmi8,6571
4
+ harnice/fileio.py,sha256=HRPKH1V08vbhB0uihxZ_pehtngy354biklYWU109dW0,9491
5
+ harnice/state.py,sha256=QyGseSu1wSpAn1K3tljA7g_SH4BPUmC9aAKuJ2E9pxw,1192
6
+ harnice/gui/launcher.py,sha256=R0T48RJkgFOJqFhgO7CvZa05zzHOu_q5tgV9U1iwreY,12993
7
+ harnice/lists/channel_map.py,sha256=Q3cuBcYTQg7H25SFx8wb2Q0o4zNNCCS8WRBMPrwyGxg,6112
8
+ harnice/lists/circuits_list.py,sha256=kqbnZk4rCLTFipX6PVirYPCpaGkTkM7SOnG0OdCEys4,11897
9
+ harnice/lists/disconnect_map.py,sha256=43qY0osDzh6XtL8GuSWOUBr86R7YUNPOOqGjsP8kSfk,9332
10
+ harnice/lists/formboard_graph.py,sha256=M0KYuQobI7vyNl6X6mHopmP_zEfFrO4YHAgR09ldiMA,1727
11
+ harnice/lists/instances_list.py,sha256=Cz9RdnkFOisguOTR8p8lcI_periH0SaafULmIA2VfuU,11386
12
+ harnice/lists/library_history.py,sha256=DhuRJWs1ziyfnhzEsmn3kVD5iaRELQ8OQ9UmDvmFjPU,1576
13
+ harnice/lists/manifest.py,sha256=nCwibNehQ1VYDN9_Q7vqM8DfssQXO4IQ1n39AW9Bp8Y,3039
14
+ harnice/lists/post_harness_instances_list.py,sha256=dxTwCLSRHX0J4YdEUx8GNMy9-UL8DK__4llGKTRrWSY,2490
15
+ harnice/lists/rev_history.py,sha256=cwJ8wNPdnSFaCVSyTijlcFig_pP0wkzOVxw4bbv2W7c,10641
16
+ harnice/lists/signals_list.py,sha256=qqRAtxYsJeSVL0XDESFbBDrdcxhWKAq6oZAqPVVrSOE,6972
17
+ harnice/products/__init__.py,sha256=LlsPf7uk1kyBdmaBuLhlGwcQau3uzEUdCW_eqtTs7TQ,29
18
+ harnice/products/cable.py,sha256=I05__-WxJUR-teV-oYB1Qzi5ufWbbYCOfM8oqCSXWPs,5107
19
+ harnice/products/chtype.py,sha256=jGmjRAb4LuPfH6IV84avpfJMI-IUfPEqJ5lhpJ1Wg2o,2156
20
+ harnice/products/device.py,sha256=MUZgbBKzkBh1kKIQlAVXr6ywIaiXn-Sg-Ib5czjIf3I,28096
21
+ harnice/products/disconnect.py,sha256=vahbAWB_hM5k5BGWORbNGygw35R8RNyfZGpep3n_ks0,7042
22
+ harnice/products/flagnote.py,sha256=yUMLFPIs9eLVWllpVl1ZHNh8qL5pk2P_Fj-IDgD4jHM,4434
23
+ harnice/products/harness.py,sha256=Q40t4xOGhp4AQVrJtvLdTEIvdhQggIhcra6uIYFQgvY,21155
24
+ harnice/products/macro.py,sha256=c8HpcCcH1qMQkc1-H0QT560ghvvDN16y0_3_Bl6ByRU,135
25
+ harnice/products/part.py,sha256=DU1FmpGMW_2INSb0suQxKZ0DhT69mCRtzqqNmPBKWCg,22523
26
+ harnice/products/system.py,sha256=mdbWdxg50-ODUbMTGLbvSh9Z_J2UoG5btyyawGNqQNA,5872
27
+ harnice/products/tblock.py,sha256=y4Won5hXoHxHFuSA6mMltpiyX46kpjTmbdJIH5J_UIw,8435
28
+ harnice/utils/appearance.py,sha256=ps_tMLdbURL-FCIv0Z3X19bTRFVRIuP0J5N9zvugUbs,1551
29
+ harnice/utils/circuit_utils.py,sha256=k5CfwYaL2flvilE5r_pZPy_puY9nyC093DO-H3s0VYU,13085
30
+ harnice/utils/feature_tree_utils.py,sha256=-2fwliPQmuxlSkb_ppZH-f2IXtA89uaiZf63KcusOGs,6973
31
+ harnice/utils/formboard_utils.py,sha256=OALL9fHdG_C78j423HtWhjcw24G9MaAVLHthqrqDWKY,36921
32
+ harnice/utils/library_utils.py,sha256=KgBHisCuB3IOuhCSwXUoLNalI8Mz_8vFS_jI4j-1d0M,12564
33
+ harnice/utils/note_utils.py,sha256=flcWmaRNz03t-Xo6bbp8erF9FuuUuAG5hcW7ephx02M,15676
34
+ harnice/utils/svg_utils.py,sha256=HZrEdTS1dHyrHxaYM35Va644bdrkwao5OkJPG_RPX2Y,33490
35
+ harnice/utils/system_utils.py,sha256=SeQrCi1cfIgAeXdKrrIBcNvHG0G6MIj4kka1VBp7M8o,25081
36
+ harnice-0.3.0.dist-info/licenses/LICENSE,sha256=2bm9uFabQZ3Ykb_SaSU_uUbAj2-htc6WJQmS_65qD00,1073
37
+ harnice-0.3.0.dist-info/METADATA,sha256=3N8VOIvkxuUUClhjKxGv62Sr-AnTPFdrodGSGrCdGqQ,1042
38
+ harnice-0.3.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
39
+ harnice-0.3.0.dist-info/entry_points.txt,sha256=AsINav6n93Jl0X-cF2nEQT2Mqj0FI-BJu2ieyUqddnE,85
40
+ harnice-0.3.0.dist-info/top_level.txt,sha256=HF3Q2YiwIW9HIDhXjucyrtGy7dubDEcJUMpb5Ajb5pM,8
41
+ harnice-0.3.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.10.2)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ harnice = harnice.cli:main
3
+ harnice-gui = harnice.gui.launcher:main
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2018 The Python Packaging Authority
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ harnice