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,417 @@
|
|
|
1
|
+
import ast
|
|
2
|
+
from harnice import fileio
|
|
3
|
+
from harnice.lists import instances_list, rev_history
|
|
4
|
+
from harnice import state
|
|
5
|
+
|
|
6
|
+
note_counter = 1
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def new_note(
|
|
10
|
+
note_type,
|
|
11
|
+
note_text,
|
|
12
|
+
note_number=None,
|
|
13
|
+
bubble_text=None,
|
|
14
|
+
shape_mpn=None,
|
|
15
|
+
shape_lib_subpath=None,
|
|
16
|
+
shape_lib_repo="https://github.com/harnice/harnice",
|
|
17
|
+
affectedinstances=None,
|
|
18
|
+
):
|
|
19
|
+
"""
|
|
20
|
+
Creates or updates a note instance.
|
|
21
|
+
|
|
22
|
+
Behavior:
|
|
23
|
+
- If a note with identical (note_type, note_text) already exists:
|
|
24
|
+
* If either existing or new affectedinstances is empty → ERROR.
|
|
25
|
+
* Else → merge affectedinstances into existing note, DO NOT create a new one.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
if not shape_mpn:
|
|
29
|
+
shape_mpn = note_type
|
|
30
|
+
|
|
31
|
+
global note_counter
|
|
32
|
+
|
|
33
|
+
# ------------------------------------------------------------
|
|
34
|
+
# 1. Search for an existing identical note
|
|
35
|
+
# ------------------------------------------------------------
|
|
36
|
+
existing = None
|
|
37
|
+
for instance in fileio.read_tsv("instances list"):
|
|
38
|
+
if (
|
|
39
|
+
instance.get("item_type") == "note"
|
|
40
|
+
and instance.get("note_type") == note_type
|
|
41
|
+
and instance.get("note_text") == note_text
|
|
42
|
+
):
|
|
43
|
+
existing = instance
|
|
44
|
+
break
|
|
45
|
+
|
|
46
|
+
# Normalize new affectedinstances
|
|
47
|
+
new_affected_instances = affectedinstances or []
|
|
48
|
+
if isinstance(new_affected_instances, str):
|
|
49
|
+
import ast
|
|
50
|
+
|
|
51
|
+
try:
|
|
52
|
+
new_affected_instances = ast.literal_eval(new_affected_instances)
|
|
53
|
+
except Exception:
|
|
54
|
+
raise ValueError(f"Malformed affectedinstances value: {affectedinstances}")
|
|
55
|
+
|
|
56
|
+
# ------------------------------------------------------------
|
|
57
|
+
# 2. CASE A — Existing note found
|
|
58
|
+
# ------------------------------------------------------------
|
|
59
|
+
if existing:
|
|
60
|
+
# Parse existing list safely
|
|
61
|
+
old_affected_instances_raw = existing.get("note_affected_instances")
|
|
62
|
+
if isinstance(old_affected_instances_raw, str):
|
|
63
|
+
import ast
|
|
64
|
+
|
|
65
|
+
try:
|
|
66
|
+
old_affected_instances = ast.literal_eval(old_affected_instances_raw)
|
|
67
|
+
except Exception:
|
|
68
|
+
old_affected_instances = []
|
|
69
|
+
else:
|
|
70
|
+
old_affected_instances = old_affected_instances_raw or []
|
|
71
|
+
|
|
72
|
+
# A.1 — If either side lacks affected instances → ERROR
|
|
73
|
+
if not old_affected_instances or not new_affected_instances:
|
|
74
|
+
raise ValueError(
|
|
75
|
+
f"Note '{note_type}:{note_text}' already exists but "
|
|
76
|
+
"one of the notes has no affected_instances. "
|
|
77
|
+
"This note has already been assigned."
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
# A.2 — Merge and deduplicate into existing instance
|
|
81
|
+
merged = list(
|
|
82
|
+
dict.fromkeys(old_affected_instances + new_affected_instances)
|
|
83
|
+
) # keep order, remove dupes
|
|
84
|
+
existing["note_affected_instances"] = merged
|
|
85
|
+
|
|
86
|
+
# Write modification back to instances_list
|
|
87
|
+
instances_list.modify(
|
|
88
|
+
existing.get("instance_name"), {"note_affected_instances": merged}
|
|
89
|
+
)
|
|
90
|
+
return existing
|
|
91
|
+
|
|
92
|
+
# ------------------------------------------------------------
|
|
93
|
+
# 3. CASE B — No existing note, create a new one
|
|
94
|
+
# ------------------------------------------------------------
|
|
95
|
+
instances_list.new_instance(
|
|
96
|
+
f"note-{note_counter}",
|
|
97
|
+
{
|
|
98
|
+
"item_type": "note",
|
|
99
|
+
"note_type": note_type,
|
|
100
|
+
"print_name": bubble_text,
|
|
101
|
+
"note_text": note_text,
|
|
102
|
+
"mpn": shape_mpn,
|
|
103
|
+
"lib_repo": shape_lib_repo,
|
|
104
|
+
"lib_subpath": shape_lib_subpath,
|
|
105
|
+
"note_number": note_number,
|
|
106
|
+
"note_affected_instances": new_affected_instances,
|
|
107
|
+
},
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
note_counter += 1
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def assign_buildnote_numbers():
|
|
114
|
+
"""
|
|
115
|
+
Assigns sequential numbers to build note instances.
|
|
116
|
+
|
|
117
|
+
Iterates through all note instances in the instances list and assigns sequential
|
|
118
|
+
numbers to build notes. Each build note gets a unique number and that number is
|
|
119
|
+
set as both the `note_number` and `print_name` fields.
|
|
120
|
+
"""
|
|
121
|
+
build_note_counter = 0
|
|
122
|
+
for instance in fileio.read_tsv("instances list"):
|
|
123
|
+
if instance.get("note_type") == "build_note":
|
|
124
|
+
if instance.get("item_type") == "note":
|
|
125
|
+
build_note_counter += 1
|
|
126
|
+
instances_list.modify(
|
|
127
|
+
instance.get("instance_name"),
|
|
128
|
+
{"note_number": build_note_counter, "print_name": build_note_counter},
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def make_rev_history_notes(rev):
|
|
133
|
+
"""
|
|
134
|
+
Creates revision change callout notes based on revision history.
|
|
135
|
+
|
|
136
|
+
Creates a revision change callout note for a given revision, linking it to
|
|
137
|
+
the instances affected by that revision. The note text comes from the
|
|
138
|
+
revision's update description.
|
|
139
|
+
|
|
140
|
+
**Args:**
|
|
141
|
+
- `rev` (dict): Revision dictionary from revision history containing at least
|
|
142
|
+
`'rev'` and `'revisionupdates'` fields.
|
|
143
|
+
"""
|
|
144
|
+
affected_instances = rev_history.info(rev=rev.get("rev"), field="affectedinstances")
|
|
145
|
+
|
|
146
|
+
if affected_instances: # safer + more pythonic
|
|
147
|
+
new_note(
|
|
148
|
+
note_type="rev_change_callout",
|
|
149
|
+
note_text=rev.get("revisionupdates"),
|
|
150
|
+
note_number=rev.get("rev"),
|
|
151
|
+
bubble_text=rev.get("rev"),
|
|
152
|
+
shape_mpn="rev_change_callout",
|
|
153
|
+
shape_lib_repo="https://github.com/harnice/harnice",
|
|
154
|
+
affectedinstances=affected_instances,
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def make_bom_flagnote(affected_instance, output_csys_name):
|
|
159
|
+
"""
|
|
160
|
+
Creates a BOM item flagnote dictionary for an instance.
|
|
161
|
+
|
|
162
|
+
Creates a flagnote configuration dictionary that displays the BOM line number
|
|
163
|
+
for a given instance. The flagnote is positioned at the specified output
|
|
164
|
+
coordinate system of the instance.
|
|
165
|
+
|
|
166
|
+
**Args:**
|
|
167
|
+
- `affected_instance` (dict): Instance dictionary to create a flagnote for.
|
|
168
|
+
- `output_csys_name` (str): Name of the output coordinate system where the
|
|
169
|
+
flagnote should be positioned.
|
|
170
|
+
|
|
171
|
+
**Returns:**
|
|
172
|
+
- `dict`: A flagnote instance dictionary ready to be added to the instances list.
|
|
173
|
+
"""
|
|
174
|
+
return {
|
|
175
|
+
"net": state.net,
|
|
176
|
+
"instance_name": f"note-bom_item-{affected_instance.get('instance_name')}",
|
|
177
|
+
"print_name": affected_instance.get("bom_line_number"),
|
|
178
|
+
"mpn": "bom_item",
|
|
179
|
+
"item_type": "flagnote",
|
|
180
|
+
"parent_instance": affected_instance.get("instance_name"),
|
|
181
|
+
"segment_group": affected_instance.get("segment_group"),
|
|
182
|
+
"connector_group": affected_instance.get("connector_group"),
|
|
183
|
+
"parent_csys_instance_name": affected_instance.get("instance_name"),
|
|
184
|
+
"parent_csys_outputcsys_name": output_csys_name,
|
|
185
|
+
"absolute_rotation": 0,
|
|
186
|
+
"note_type": "bom_item",
|
|
187
|
+
"note_affected_instances": [affected_instance.get("instance_name")],
|
|
188
|
+
"lib_repo": "https://github.com/harnice/harnice",
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def make_part_name_flagnote(affected_instance, output_csys_name):
|
|
193
|
+
"""
|
|
194
|
+
Creates a part name flagnote dictionary for an instance.
|
|
195
|
+
|
|
196
|
+
Creates a flagnote configuration dictionary that displays the print name
|
|
197
|
+
of a given instance. The flagnote is positioned at the specified output
|
|
198
|
+
coordinate system of the instance.
|
|
199
|
+
|
|
200
|
+
**Args:**
|
|
201
|
+
- `affected_instance` (dict): Instance dictionary to create a flagnote for.
|
|
202
|
+
- `output_csys_name` (str): Name of the output coordinate system where the
|
|
203
|
+
flagnote should be positioned.
|
|
204
|
+
|
|
205
|
+
**Returns:**
|
|
206
|
+
- `dict`: A flagnote instance dictionary ready to be added to the instances list.
|
|
207
|
+
"""
|
|
208
|
+
return {
|
|
209
|
+
"net": state.net,
|
|
210
|
+
"instance_name": f"note-part_name-{affected_instance.get('instance_name')}",
|
|
211
|
+
"print_name": affected_instance.get("print_name"),
|
|
212
|
+
"mpn": "part_name",
|
|
213
|
+
"item_type": "flagnote",
|
|
214
|
+
"parent_instance": affected_instance.get("instance_name"),
|
|
215
|
+
"segment_group": affected_instance.get("segment_group"),
|
|
216
|
+
"connector_group": affected_instance.get("connector_group"),
|
|
217
|
+
"parent_csys_instance_name": affected_instance.get("instance_name"),
|
|
218
|
+
"parent_csys_outputcsys_name": output_csys_name,
|
|
219
|
+
"absolute_rotation": 0,
|
|
220
|
+
"note_type": "part_name",
|
|
221
|
+
"note_affected_instances": [affected_instance.get("instance_name")],
|
|
222
|
+
"lib_repo": "https://github.com/harnice/harnice",
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def make_buildnote_flagnote(note_instance, affected_instance, output_csys_name):
|
|
227
|
+
"""
|
|
228
|
+
Creates a build note flagnote dictionary linking a note to an instance.
|
|
229
|
+
|
|
230
|
+
Creates a flagnote configuration dictionary that displays a build note on
|
|
231
|
+
a specific instance. The flagnote shows the build note's print name and is
|
|
232
|
+
positioned at the specified output coordinate system of the affected instance.
|
|
233
|
+
|
|
234
|
+
**Args:**
|
|
235
|
+
- `note_instance` (dict): Build note instance dictionary.
|
|
236
|
+
- `affected_instance` (dict): Instance dictionary to attach the flagnote to.
|
|
237
|
+
- `output_csys_name` (str): Name of the output coordinate system where the
|
|
238
|
+
flagnote should be positioned.
|
|
239
|
+
|
|
240
|
+
**Returns:**
|
|
241
|
+
- `dict`: A flagnote instance dictionary ready to be added to the instances list.
|
|
242
|
+
"""
|
|
243
|
+
return {
|
|
244
|
+
"net": state.net,
|
|
245
|
+
"instance_name": f"note-build_note-{note_instance.get('instance_name')}-{affected_instance.get('instance_name')}",
|
|
246
|
+
"print_name": note_instance.get("print_name"),
|
|
247
|
+
"mpn": "build_note",
|
|
248
|
+
"item_type": "flagnote",
|
|
249
|
+
"parent_instance": affected_instance.get("instance_name"),
|
|
250
|
+
"segment_group": affected_instance.get("segment_group"),
|
|
251
|
+
"connector_group": affected_instance.get("connector_group"),
|
|
252
|
+
"parent_csys_instance_name": affected_instance.get("instance_name"),
|
|
253
|
+
"parent_csys_outputcsys_name": output_csys_name,
|
|
254
|
+
"absolute_rotation": 0,
|
|
255
|
+
"note_type": "build_note",
|
|
256
|
+
"note_affected_instances": [affected_instance.get("instance_name")],
|
|
257
|
+
"lib_repo": "https://github.com/harnice/harnice",
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def make_rev_change_flagnote(note_instance, affected_instance, output_csys_name):
|
|
262
|
+
"""
|
|
263
|
+
Creates a revision change callout flagnote dictionary linking a note to an instance.
|
|
264
|
+
|
|
265
|
+
Creates a flagnote configuration dictionary that displays a revision change
|
|
266
|
+
callout note on a specific instance. The flagnote shows the revision number
|
|
267
|
+
and is positioned at the specified output coordinate system of the affected instance.
|
|
268
|
+
|
|
269
|
+
**Args:**
|
|
270
|
+
- `note_instance` (dict): Revision change callout note instance dictionary.
|
|
271
|
+
- `affected_instance` (dict): Instance dictionary to attach the flagnote to.
|
|
272
|
+
- `output_csys_name` (str): Name of the output coordinate system where the
|
|
273
|
+
flagnote should be positioned.
|
|
274
|
+
|
|
275
|
+
**Returns:**
|
|
276
|
+
- `dict`: A flagnote instance dictionary ready to be added to the instances list.
|
|
277
|
+
"""
|
|
278
|
+
return {
|
|
279
|
+
"net": state.net,
|
|
280
|
+
"instance_name": f"note-rev_change_callout-{note_instance.get('instance_name')}-{affected_instance.get('instance_name')}",
|
|
281
|
+
"print_name": note_instance.get("print_name"),
|
|
282
|
+
"mpn": "rev_change_callout",
|
|
283
|
+
"item_type": "flagnote",
|
|
284
|
+
"parent_instance": affected_instance.get("instance_name"),
|
|
285
|
+
"segment_group": affected_instance.get("segment_group"),
|
|
286
|
+
"connector_group": affected_instance.get("connector_group"),
|
|
287
|
+
"parent_csys_instance_name": affected_instance.get("instance_name"),
|
|
288
|
+
"parent_csys_outputcsys_name": output_csys_name,
|
|
289
|
+
"absolute_rotation": 0,
|
|
290
|
+
"note_type": "rev_change_callout",
|
|
291
|
+
"note_affected_instances": [affected_instance.get("instance_name")],
|
|
292
|
+
"lib_repo": "https://github.com/harnice/harnice",
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def parse_note_instance(instance):
|
|
297
|
+
"""
|
|
298
|
+
Return a full copy of `instance`, but with note_affected_instances
|
|
299
|
+
parsed into a real Python list (or left alone if blank).
|
|
300
|
+
"""
|
|
301
|
+
parsed = {}
|
|
302
|
+
|
|
303
|
+
for key, value in instance.items():
|
|
304
|
+
if key == "note_affected_instances":
|
|
305
|
+
if isinstance(value, str) and value.strip():
|
|
306
|
+
try:
|
|
307
|
+
parsed[key] = ast.literal_eval(value)
|
|
308
|
+
except Exception:
|
|
309
|
+
parsed[key] = [] # fallback if malformed
|
|
310
|
+
else:
|
|
311
|
+
parsed[key] = []
|
|
312
|
+
else:
|
|
313
|
+
parsed[key] = value
|
|
314
|
+
|
|
315
|
+
return parsed
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def get_lib_build_notes(instance):
|
|
319
|
+
"""
|
|
320
|
+
Returns list of build_notes for this instance from the TSV row.
|
|
321
|
+
Safely parses with ast.literal_eval.
|
|
322
|
+
Always returns a Python list.
|
|
323
|
+
"""
|
|
324
|
+
|
|
325
|
+
raw = instance.get("lib_build_notes")
|
|
326
|
+
|
|
327
|
+
if not raw or raw in ["", None]:
|
|
328
|
+
return []
|
|
329
|
+
|
|
330
|
+
try:
|
|
331
|
+
# Expecting a string representation of a list
|
|
332
|
+
parsed = ast.literal_eval(raw)
|
|
333
|
+
|
|
334
|
+
# Ensure it's actually a list
|
|
335
|
+
return parsed if isinstance(parsed, list) else []
|
|
336
|
+
|
|
337
|
+
except Exception:
|
|
338
|
+
# Malformed literal → fail safe
|
|
339
|
+
return []
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
def get_lib_tools(instance):
|
|
343
|
+
"""
|
|
344
|
+
Returns list of tools for this instance from the TSV row.
|
|
345
|
+
Safely parses with ast.literal_eval.
|
|
346
|
+
Always returns a Python list.
|
|
347
|
+
"""
|
|
348
|
+
|
|
349
|
+
raw = instance.get("lib_tools")
|
|
350
|
+
|
|
351
|
+
if not raw or raw in ["", None]:
|
|
352
|
+
return []
|
|
353
|
+
|
|
354
|
+
try:
|
|
355
|
+
# Expecting a string representation of a list
|
|
356
|
+
parsed = ast.literal_eval(raw)
|
|
357
|
+
|
|
358
|
+
# Ensure it's actually a list
|
|
359
|
+
return parsed if isinstance(parsed, list) else []
|
|
360
|
+
|
|
361
|
+
except Exception:
|
|
362
|
+
# Malformed literal → fail safe
|
|
363
|
+
return []
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
def combine_notes(keep_note_text, merge_note_texts, note_type=None):
|
|
367
|
+
"""
|
|
368
|
+
Combines multiple notes by merging their affected instances into one note.
|
|
369
|
+
|
|
370
|
+
Merges one or more notes into a single note by combining their affected instances
|
|
371
|
+
lists. The note to keep is identified by `keep_note_text`, and all notes matching
|
|
372
|
+
`merge_note_texts` are merged into it and then removed.
|
|
373
|
+
|
|
374
|
+
**Args:**
|
|
375
|
+
- `keep_note_text` (str): The `note_text` value of the note to keep and merge others into.
|
|
376
|
+
- `merge_note_texts` (list): List of `note_text` values to find and merge into the kept note.
|
|
377
|
+
- `note_type` (list, optional): If provided, only notes with `note_type` in this list
|
|
378
|
+
will be considered for merging. If `None`, all notes are considered.
|
|
379
|
+
"""
|
|
380
|
+
keep_note_instance = None
|
|
381
|
+
merge_note_instances_raw = []
|
|
382
|
+
for instance in fileio.read_tsv("instances list"):
|
|
383
|
+
if instance.get("note_text") in [None, ""]:
|
|
384
|
+
continue
|
|
385
|
+
if instance.get("note_text") == keep_note_text:
|
|
386
|
+
if note_type:
|
|
387
|
+
if instance.get("note_type") in note_type:
|
|
388
|
+
keep_note_instance = instance
|
|
389
|
+
else:
|
|
390
|
+
keep_note_instance = instance
|
|
391
|
+
if instance.get("note_text") in merge_note_texts:
|
|
392
|
+
if note_type:
|
|
393
|
+
if instance.get("note_type") in note_type:
|
|
394
|
+
merge_note_instances_raw.append(instance)
|
|
395
|
+
else:
|
|
396
|
+
merge_note_instances_raw.append(instance)
|
|
397
|
+
|
|
398
|
+
merge_note_instances_parsed = []
|
|
399
|
+
for instance in merge_note_instances_raw:
|
|
400
|
+
merge_note_instances_parsed.append(parse_note_instance(instance))
|
|
401
|
+
|
|
402
|
+
new_affected_instances = set()
|
|
403
|
+
|
|
404
|
+
for merged_note_instance in merge_note_instances_parsed:
|
|
405
|
+
for affected_instance in merged_note_instance.get("note_affected_instances"):
|
|
406
|
+
new_affected_instances.add(affected_instance)
|
|
407
|
+
instances_list.remove_instance(merged_note_instance)
|
|
408
|
+
|
|
409
|
+
for affected_instance in parse_note_instance(keep_note_instance).get(
|
|
410
|
+
"note_affected_instances"
|
|
411
|
+
):
|
|
412
|
+
new_affected_instances.add(affected_instance)
|
|
413
|
+
|
|
414
|
+
instances_list.modify(
|
|
415
|
+
keep_note_instance.get("instance_name"),
|
|
416
|
+
{"note_affected_instances": list(new_affected_instances)},
|
|
417
|
+
)
|