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.
- harnice/__init__.py +0 -0
- harnice/__main__.py +4 -0
- harnice/cli.py +234 -0
- harnice/fileio.py +295 -0
- harnice/gui/launcher.py +426 -0
- harnice/lists/channel_map.py +182 -0
- harnice/lists/circuits_list.py +302 -0
- harnice/lists/disconnect_map.py +237 -0
- harnice/lists/formboard_graph.py +63 -0
- harnice/lists/instances_list.py +280 -0
- harnice/lists/library_history.py +40 -0
- harnice/lists/manifest.py +93 -0
- harnice/lists/post_harness_instances_list.py +66 -0
- harnice/lists/rev_history.py +325 -0
- harnice/lists/signals_list.py +135 -0
- harnice/products/__init__.py +1 -0
- harnice/products/cable.py +152 -0
- harnice/products/chtype.py +80 -0
- harnice/products/device.py +844 -0
- harnice/products/disconnect.py +225 -0
- harnice/products/flagnote.py +139 -0
- harnice/products/harness.py +522 -0
- harnice/products/macro.py +10 -0
- harnice/products/part.py +640 -0
- harnice/products/system.py +125 -0
- harnice/products/tblock.py +270 -0
- harnice/state.py +57 -0
- harnice/utils/appearance.py +51 -0
- harnice/utils/circuit_utils.py +326 -0
- harnice/utils/feature_tree_utils.py +183 -0
- harnice/utils/formboard_utils.py +973 -0
- harnice/utils/library_utils.py +333 -0
- harnice/utils/note_utils.py +417 -0
- harnice/utils/svg_utils.py +819 -0
- harnice/utils/system_utils.py +563 -0
- harnice-0.3.0.dist-info/METADATA +32 -0
- harnice-0.3.0.dist-info/RECORD +41 -0
- harnice-0.3.0.dist-info/WHEEL +5 -0
- harnice-0.3.0.dist-info/entry_points.txt +3 -0
- harnice-0.3.0.dist-info/licenses/LICENSE +19 -0
- 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,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
|