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,225 @@
1
+ import runpy
2
+ import os
3
+ import csv
4
+ from harnice import fileio, state
5
+ from harnice.products import chtype
6
+ from harnice.lists import signals_list
7
+
8
+ default_desc = "DISCONNECT, FUNCTION, ATTRIBUTES, etc."
9
+
10
+ disconnect_feature_tree_utils_default = """
11
+ from harnice.lists import signals_list
12
+ from harnice.products import chtype
13
+
14
+ ch_type_ids = {
15
+ "A": {
16
+ "balanced audio mic level in": (1, "https://github.com/harnice/harnice"),
17
+ "chassis": (5, "https://github.com/harnice/harnice")
18
+ },
19
+ "B": {
20
+ "balanced audio mic level out": (2, "https://github.com/harnice/harnice"),
21
+ "chassis": (5, "https://github.com/harnice/harnice")
22
+ }
23
+ }
24
+
25
+ cn_mpns = {
26
+ "A": "DB25F",
27
+ "B": "DB25M"
28
+ }
29
+
30
+ cavity_number = {
31
+ "ch0": {
32
+ "pos": 24,
33
+ "neg": 12,
34
+ "chassis": 25
35
+ },
36
+ "ch1": {
37
+ "pos": 10,
38
+ "neg": 23,
39
+ "chassis": 11
40
+ },
41
+ "ch2": {
42
+ "pos": 21,
43
+ "neg": 9,
44
+ "chassis": 22
45
+ },
46
+ "ch3": {
47
+ "pos": 7,
48
+ "neg": 20,
49
+ "chassis": 8
50
+ },
51
+ "ch4": {
52
+ "pos": 18,
53
+ "neg": 6,
54
+ "chassis": 19
55
+ },
56
+ "ch5": {
57
+ "pos": 4,
58
+ "neg": 17,
59
+ "chassis": 5
60
+ },
61
+ "ch6": {
62
+ "pos": 15,
63
+ "neg": 3,
64
+ "chassis": 16
65
+ },
66
+ "ch7": {
67
+ "pos": 1,
68
+ "neg": 14,
69
+ "chassis": 2
70
+ },
71
+ }
72
+
73
+ signals_list.new()
74
+
75
+ for channel in range(8):
76
+ channel_name = f"ch{channel}"
77
+
78
+ for signal in chtype.signals(ch_type_ids["A"]["balanced audio mic level in"]):
79
+ signals_list.append(
80
+ channel_id=channel_name,
81
+ signal=signal,
82
+
83
+ A_cavity=cavity_number[channel_name][signal],
84
+ A_connector_mpn=cn_mpns["A"],
85
+ A_channel_type=ch_type_ids["A"]["balanced audio mic level in"],
86
+
87
+ B_cavity=cavity_number[channel_name][signal],
88
+ B_connector_mpn=cn_mpns["B"],
89
+ B_channel_type=ch_type_ids["B"]["balanced audio mic level out"],
90
+ )
91
+
92
+ for signal in chtype.signals(ch_type_ids["A"]["chassis"]):
93
+ signals_list.append(
94
+ channel_id=f"{channel_name}-shield",
95
+ signal=signal,
96
+
97
+ A_cavity=cavity_number[channel_name][signal],
98
+ A_connector_mpn=cn_mpns["A"],
99
+ A_channel_type=ch_type_ids["A"]["chassis"],
100
+
101
+ B_cavity=cavity_number[channel_name][signal],
102
+ B_connector_mpn=cn_mpns["B"],
103
+ B_channel_type=ch_type_ids["B"]["chassis"],
104
+ )
105
+
106
+ """
107
+
108
+
109
+ def file_structure():
110
+ return {
111
+ f"{state.partnumber('pn-rev')}-feature_tree.py": "feature tree",
112
+ f"{state.partnumber('pn-rev')}-signals_list.tsv": "signals list",
113
+ f"{state.partnumber('pn-rev')}-attributes.json": "attributes",
114
+ }
115
+
116
+
117
+ def generate_structure():
118
+ pass
119
+
120
+
121
+ def _validate_signals_list():
122
+ print("--------------------------------")
123
+ print("Validating signals list...")
124
+ if not os.path.exists(fileio.path("signals list")):
125
+ raise FileNotFoundError("Signals list was not generated.")
126
+
127
+ with open(fileio.path("signals list"), "r", encoding="utf-8") as f:
128
+ reader = csv.DictReader(f, delimiter="\t")
129
+ headers = reader.fieldnames
130
+ signals_list = list(reader)
131
+
132
+ if not headers:
133
+ raise ValueError("Signals list has no header row.")
134
+
135
+ counter = 2
136
+ for signal in signals_list:
137
+ print("Looking at csv row:", counter)
138
+ A_channel_type = chtype.parse(signal.get("A_channel_type"))
139
+ B_channel_type = chtype.parse(signal.get("B_channel_type"))
140
+
141
+ # make sure all the fields are there
142
+ if signal.get("channel_id") in ["", None]:
143
+ raise ValueError("A_channel_id is blank")
144
+ if signal.get("signal") in ["", None]:
145
+ raise ValueError("signal is blank")
146
+ if signal.get("A_cavity") in ["", None]:
147
+ raise ValueError("A_cavity is blank")
148
+ if signal.get("B_cavity") in ["", None]:
149
+ raise ValueError("B_cavity is blank")
150
+ if signal.get("A_connector_mpn") in ["", None]:
151
+ raise ValueError("A_connector_mpn is blank")
152
+ if signal.get("A_channel_type") in ["", None]:
153
+ raise ValueError("A_channel_type is blank")
154
+ if signal.get("B_connector_mpn") in ["", None]:
155
+ raise ValueError("B_connector_mpn is blank")
156
+ if signal.get("B_channel_type") in ["", None]:
157
+ raise ValueError("B_channel_type is blank")
158
+
159
+ # make sure signal is a valid signal of its channel type
160
+ if signal.get("signal") not in chtype.signals(A_channel_type):
161
+ raise ValueError(
162
+ f"Signal {signal.get('A_signal')} is not a valid signal of its channel type"
163
+ )
164
+
165
+ # make sure A and B sides are compatible
166
+ if B_channel_type not in chtype.compatibles(A_channel_type):
167
+ if A_channel_type not in chtype.compatibles(B_channel_type):
168
+ raise ValueError("A and B channel types are not compatible")
169
+
170
+ expected_signals = chtype.signals(A_channel_type)
171
+ found_signals = set()
172
+
173
+ # make sure all the signals of each channel type are present
174
+ for expected_signal in expected_signals:
175
+ for signal2 in signals_list:
176
+ if (
177
+ signal2.get("channel_id") == signal.get("channel_id")
178
+ and signal2.get("signal") == expected_signal
179
+ ):
180
+ found_signals.add(expected_signal)
181
+
182
+ missing_signals = set(expected_signals) - found_signals
183
+ if missing_signals:
184
+ raise ValueError(
185
+ f"Channel {signal.get('channel_id')} is missing signals: {', '.join(missing_signals)}"
186
+ )
187
+
188
+ counter += 1
189
+
190
+ # make sure no duplicate A-side cavities are present
191
+ seen_A = set()
192
+ for signal in signals_list:
193
+ A_cavity = signal.get("A_cavity")
194
+ if A_cavity in seen_A:
195
+ raise ValueError(f"Duplicate A_cavity found in disconnect: {A_cavity}")
196
+ seen_A.add(A_cavity)
197
+
198
+ # make sure no duplicate B-side cavities are present
199
+ seen_B = set()
200
+ for signal in signals_list:
201
+ B_cavity = signal.get("B_cavity")
202
+ if B_cavity in seen_B:
203
+ raise ValueError(f"Duplicate B_cavity found in disconnect: {B_cavity}")
204
+ seen_B.add(B_cavity)
205
+
206
+ if counter == 2:
207
+ raise ValueError(
208
+ "No signals have been specified. Check your feature tree or add rows manually."
209
+ )
210
+
211
+ print(f"Signals list of {state.partnumber('pn')} is valid.\n")
212
+
213
+
214
+ def render():
215
+ signals_list.set_list_type("disconnect")
216
+ if not os.path.exists(fileio.path("signals list")):
217
+ if not os.path.exists(fileio.path("feature tree")):
218
+ with open(fileio.path("feature tree"), "w", encoding="utf-8") as f:
219
+ f.write(disconnect_feature_tree_utils_default)
220
+
221
+ if os.path.exists(fileio.path("feature tree")):
222
+ runpy.run_path(fileio.path("feature tree"))
223
+ print("Successfully rebuilt signals list per feature tree.")
224
+
225
+ _validate_signals_list()
@@ -0,0 +1,139 @@
1
+ import os
2
+ import json
3
+ import random
4
+ import math
5
+ from harnice import fileio, cli, state
6
+
7
+
8
+ default_desc = "FLAGNOTE, PURPOSE"
9
+
10
+
11
+ def file_structure():
12
+ return {
13
+ f"{state.partnumber('pn-rev')}-params.json": "params",
14
+ f"{state.partnumber('pn-rev')}-drawing.svg": "drawing",
15
+ }
16
+
17
+
18
+ def generate_structure():
19
+ pass
20
+
21
+
22
+ def render():
23
+ if (
24
+ cli.prompt(
25
+ "Warning: rendering a titleblock may clear user edits to its svg. Proceed?",
26
+ default="yes",
27
+ )
28
+ != "yes"
29
+ ):
30
+ exit()
31
+
32
+ # Geometry generators
33
+ def regular_ngon(n, radius=19.2, rotation_deg=0):
34
+ angle_offset = math.radians(rotation_deg)
35
+ return [
36
+ [
37
+ round(radius * math.cos(2 * math.pi * i / n + angle_offset), 2),
38
+ round(radius * math.sin(2 * math.pi * i / n + angle_offset), 2),
39
+ ]
40
+ for i in range(n)
41
+ ]
42
+
43
+ def right_arrow():
44
+ return [[-24, -12], [0, -12], [0, -24], [24, 0], [0, 24], [0, 12], [-24, 12]]
45
+
46
+ def left_arrow():
47
+ return [[24, -12], [0, -12], [0, -24], [-24, 0], [0, 24], [0, 12], [24, 12]]
48
+
49
+ def flag_pennant():
50
+ return [[-24, -12], [24, 0], [-24, 12]]
51
+
52
+ # List of shape options with (label, generator)
53
+ shape_options = [
54
+ ("circle", None),
55
+ ("square", lambda: regular_ngon(4, rotation_deg=45)),
56
+ ("triangle", lambda: regular_ngon(3, rotation_deg=-90)),
57
+ ("upside down triangle", lambda: regular_ngon(3, rotation_deg=90)),
58
+ ("hexagon", lambda: regular_ngon(6)),
59
+ ("pentagon", lambda: regular_ngon(5)),
60
+ ("right arrow", right_arrow),
61
+ ("left arrow", left_arrow),
62
+ ("octagon", lambda: regular_ngon(8)),
63
+ ("diamond", lambda: regular_ngon(4, rotation_deg=0)),
64
+ ("flag / pennant", flag_pennant),
65
+ ]
66
+
67
+ # === Prompt shape if no params exist ===
68
+ if not os.path.exists(fileio.path("params")):
69
+ print("No flagnote params found.")
70
+ print("Choose a shape for your flagnote:")
71
+ for i, (label, _) in enumerate(shape_options, 1):
72
+ print(f" {i}) {label}")
73
+
74
+ while True:
75
+ response = cli.prompt("Enter the number of your choice").strip()
76
+ if response.isdigit():
77
+ index = int(response)
78
+ if 1 <= index <= len(shape_options):
79
+ shape_label, shape_func = shape_options[index - 1]
80
+ break
81
+ print("Invalid selection. Please enter a number from the list.")
82
+
83
+ params = {"fill": 0xFFFFFF, "border": 0x000000, "text inside": "flagnote-text"}
84
+
85
+ if shape_func:
86
+ params["vertices"] = shape_func()
87
+
88
+ with open(fileio.path("params"), "w", encoding="utf-8") as f:
89
+ json.dump(params, f, indent=2)
90
+
91
+ # === Load params ===
92
+ with open(fileio.path("params"), "r", encoding="utf-8") as f:
93
+ p = json.load(f)
94
+
95
+ svg_width = 6 * 96
96
+ svg_height = 6 * 96
97
+
98
+ fill = p.get("fill")
99
+ if not isinstance(fill, int):
100
+ fill = random.randint(0x000000, 0xFFFFFF)
101
+
102
+ border = p.get("border", 0x000000)
103
+ shape_svg = ""
104
+
105
+ # === Shape element ===
106
+ if "vertices" in p:
107
+ if p["vertices"]:
108
+ points_str = " ".join(f"{x},{y}" for x, y in p["vertices"])
109
+ shape_svg = f' <polygon points="{points_str}" fill="#{fill:06X}" stroke="#{border:06X}"/>\n'
110
+ else:
111
+ shape_svg = f' <circle cx="0" cy="0" r="10" fill="#{fill:06X}" stroke="#{border:06X}"/>\n'
112
+
113
+ # === Text element ===
114
+ text_content = p.get("text inside", "")
115
+ text_svg = (
116
+ f' <text x="0" y="0" '
117
+ f'style="font-size:8px;font-family:Arial" '
118
+ f'text-anchor="middle" dominant-baseline="middle" id="flagnote-text">{text_content}</text>\n'
119
+ )
120
+
121
+ contents = shape_svg + text_svg if shape_svg else ""
122
+
123
+ lines = [
124
+ '<?xml version="1.0" encoding="UTF-8"?>',
125
+ f'<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="{svg_width}" height="{svg_height}">',
126
+ f' <g id="{state.partnumber("pn")}-drawing-contents-start">',
127
+ contents.rstrip(),
128
+ " </g>",
129
+ f' <g id="{state.partnumber("pn")}-drawing-contents-end">',
130
+ " </g>",
131
+ "</svg>",
132
+ ]
133
+
134
+ with open(fileio.path("drawing"), "w", encoding="utf-8") as f:
135
+ f.write("\n".join(lines) + "\n")
136
+
137
+ print()
138
+ print(f"Flagnote '{state.partnumber('pn')}' updated")
139
+ print()