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,125 @@
1
+ import runpy
2
+ import os
3
+ from harnice import fileio, state, cli
4
+ from harnice.lists import post_harness_instances_list, instances_list, library_history
5
+
6
+ default_desc = "SYSTEM, SCOPE, etc."
7
+
8
+ system_feature_tree_utils_default = """from harnice import fileio
9
+ from harnice.utils import system_utils, feature_tree_utils
10
+ from harnice.lists import instances_list, manifest, channel_map, circuits_list, disconnect_map
11
+
12
+ #===========================================================================
13
+ # KICAD PROCESSING
14
+ #===========================================================================
15
+ feature_tree_utils.run_macro("kicad_sch_to_pdf", "system_artifacts", "https://github.com/harnice/harnice", artifact_id="blockdiagram-1")
16
+ feature_tree_utils.run_macro("kicad_pro_to_bom", "system_builder", "https://github.com/harnice/harnice", artifact_id="bom-1")
17
+
18
+ #===========================================================================
19
+ # COLLECT AND PULL DEVICES FROM LIBRARY
20
+ #===========================================================================
21
+ system_utils.make_instances_from_bom()
22
+
23
+ #===========================================================================
24
+ # CHANNEL MAPPING
25
+ #===========================================================================
26
+ feature_tree_utils.run_macro("kicad_pro_to_system_connector_list", "system_builder", "https://github.com/harnice/harnice", artifact_id="system-connector-list-1")
27
+ manifest.new()
28
+ channel_map.new()
29
+
30
+ #add manual channel map commands here. key=(from_device_refdes, from_device_channel_id)
31
+ #channel_map.map(("MIC3", "out1"), ("PREAMP1", "in2"))
32
+
33
+ #map channels to other compatible channels by sorting alphabetically then mapping compatibles
34
+ feature_tree_utils.run_macro("basic_channel_mapper", "system_builder", "https://github.com/harnice/harnice", artifact_id="channel-mapper-1")
35
+
36
+ #if mapped channels must connect via disconnects, add the list of disconnects to the channel map
37
+ system_utils.add_chains_to_channel_map()
38
+
39
+ #map channels that must pass through disconnects to available channels inside disconnects
40
+ disconnect_map.new()
41
+
42
+ #add manual disconnect map commands here
43
+ #disconnect_map.already_assigned_disconnects_set_append(('X1', 'ch0'))
44
+
45
+ #map channels passing through disconnects to available channels inside disconnects
46
+ feature_tree_utils.run_macro("disconnect_mapper", "system_builder", "https://github.com/harnice/harnice", artifact_id="disconnect-mapper-1")
47
+ feature_tree_utils.ensure_requirements_met()
48
+
49
+ #process channel and disconnect maps to make a list of every circuit in your system
50
+ circuits_list.new()
51
+
52
+ #===========================================================================
53
+ # INSTANCES LIST
54
+ #===========================================================================
55
+ system_utils.make_instances_for_connectors_cavities_nodes_channels_circuits()
56
+
57
+ #assign mating connectors
58
+ #for instance in fileio.read_tsv("instances list"):
59
+ #if instance.get("item_type") == "connector":
60
+ #if instance.get("this_instance_mating_device_connector_mpn") == "XLR3M":
61
+ #instances_list.modify(instance.get("instance_name"),{
62
+ #"mpn":"D38999_26ZA98PN",
63
+ #"lib_repo":"https://github.com/harnice/harnice"
64
+ #})
65
+
66
+ #===========================================================================
67
+ # SYSTEM DESIGN CHECKS
68
+ #===========================================================================
69
+ connector_list = fileio.read_tsv("system connector list")
70
+ circuits_list = fileio.read_tsv("circuits list")
71
+
72
+ #check for circuits with no connectors
73
+ system_utils.find_connector_with_no_circuit(connector_list, circuits_list)
74
+ """
75
+
76
+
77
+ def file_structure():
78
+ return {
79
+ f"{state.partnumber('pn-rev')}-feature_tree.py": "feature tree",
80
+ f"{state.partnumber('pn-rev')}-instances_list.tsv": "instances list",
81
+ f"{state.partnumber('pn-rev')}-library_import_history.tsv": "library history",
82
+ "instance_data": {},
83
+ "features_for_relatives": {},
84
+ "harnesses": {},
85
+ "lists": {
86
+ f"{state.partnumber('pn-rev')}-bom.tsv": "bom",
87
+ f"{state.partnumber('pn-rev')}-circuits_list.tsv": "circuits list",
88
+ f"{state.partnumber('pn-rev')}-post_harness_instances_list.tsv": "post harness instances list",
89
+ f"{state.partnumber('pn-rev')}-harness_manifest.tsv": "harness manifest",
90
+ f"{state.partnumber('pn-rev')}-system_connector_list.tsv": "system connector list",
91
+ f"{state.partnumber('pn-rev')}-mapped_channels_set.tsv": "mapped channels set",
92
+ f"{state.partnumber('pn-rev')}-mapped_disconnect_channels_set.tsv": "mapped disconnects set",
93
+ f"{state.partnumber('pn-rev')}-mapped_a_channels_through_disconnects_set.tsv": "mapped A-side channels through disconnects set",
94
+ },
95
+ "maps": {
96
+ f"{state.partnumber('pn-rev')}-channel_map.tsv": "channel map",
97
+ f"{state.partnumber('pn-rev')}-disconnect_map.tsv": "disconnect map",
98
+ },
99
+ }
100
+
101
+
102
+ def generate_structure():
103
+ os.makedirs(fileio.dirpath("instance_data"), exist_ok=True)
104
+ os.makedirs(fileio.dirpath("features_for_relatives"), exist_ok=True)
105
+ os.makedirs(fileio.dirpath("harnesses"), exist_ok=True)
106
+ os.makedirs(fileio.dirpath("lists"), exist_ok=True)
107
+ os.makedirs(fileio.dirpath("maps"), exist_ok=True)
108
+ pass
109
+
110
+
111
+ def render():
112
+ state.set_net(None)
113
+
114
+ if not os.path.exists(fileio.path("feature tree")):
115
+ with open(fileio.path("feature tree"), "w", encoding="utf-8") as f:
116
+ f.write(system_feature_tree_utils_default)
117
+
118
+ library_history.new()
119
+ instances_list.new()
120
+ cli.print_import_status_headers()
121
+ runpy.run_path(fileio.path("feature tree"))
122
+
123
+ post_harness_instances_list.rebuild()
124
+
125
+ print("\nSystem rendered successfully!\n")
@@ -0,0 +1,270 @@
1
+ import os
2
+ import json
3
+ import xml.etree.ElementTree as ET
4
+ from xml.dom import minidom
5
+ from harnice import fileio, cli, state
6
+
7
+
8
+ default_desc = "TITLEBLOCK, PAPER SIZE, DESIGN"
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
+ f"{state.partnumber('pn-rev')}-attributes.json": "attributes",
16
+ }
17
+
18
+
19
+ def generate_structure():
20
+ pass
21
+
22
+
23
+ def render():
24
+ if (
25
+ cli.prompt(
26
+ "Warning: rendering a titleblock may clear user edits to its svg. Proceed?",
27
+ default="yes",
28
+ )
29
+ != "yes"
30
+ ):
31
+ exit()
32
+
33
+ # === Default Parameters ===
34
+ params = {
35
+ "page_size": [11 * 96, 8.5 * 96],
36
+ "outer_margin": 20,
37
+ "inner_margin": 40,
38
+ "tick_spacing": 96,
39
+ "tb_origin_offset": [398, 48],
40
+ "row_heights": [24, 24],
41
+ "column_widths": [[264, 50, 84], [73, 126, 139, 60]],
42
+ "label_offset": [2, 7],
43
+ "key_offset_y": 16,
44
+ "cell_texts": [
45
+ [
46
+ ("DESCRIPTION", "tblock-key-desc"),
47
+ ("REV", "tblock-key-rev"),
48
+ ("PAGE DESC", "tblock-key-pagedesc"),
49
+ ],
50
+ [
51
+ ("SCALE", "tblock-key-scale"),
52
+ ("PART NUMBER", "tblock-key-pn"),
53
+ ("DRAWN BY", "tblock-key-drawnby"),
54
+ ("SHEET", "tblock-key-sheet"),
55
+ ],
56
+ ],
57
+ }
58
+
59
+ # === If param file doesn't exist, create it ===
60
+ if not os.path.exists(fileio.path("params")):
61
+ with open(fileio.path("params"), "w", encoding="utf-8") as f:
62
+ json.dump(params, f, indent=2)
63
+ # if it does exist, ignore it
64
+
65
+ # === Load parameters from JSON ===
66
+ with open(fileio.path("params"), "r", encoding="utf-8") as f:
67
+ p = json.load(f)
68
+
69
+ width, height = p["page_size"]
70
+ svg = ET.Element(
71
+ "svg",
72
+ {
73
+ "xmlns": "http://www.w3.org/2000/svg",
74
+ "version": "1.1",
75
+ "width": str(width),
76
+ "height": str(height),
77
+ },
78
+ )
79
+
80
+ contents_group = ET.SubElement(svg, "g", {"id": "tblock-contents-start"})
81
+
82
+ def add_rect(parent, x, y, w, h, stroke="black", fill="none", stroke_width=1):
83
+ ET.SubElement(
84
+ parent,
85
+ "rect",
86
+ {
87
+ "x": str(x),
88
+ "y": str(y),
89
+ "width": str(w),
90
+ "height": str(h),
91
+ "fill": fill,
92
+ "stroke": stroke,
93
+ "stroke-width": str(stroke_width),
94
+ },
95
+ )
96
+
97
+ def add_text(parent, x, y, text, size=8, anchor="start", bold=False, id=None):
98
+ style = f"font-size:{size}px;font-family:Arial"
99
+ if bold:
100
+ style += ";font-weight:bold"
101
+ attrs = {
102
+ "x": str(x),
103
+ "y": str(y),
104
+ "style": style,
105
+ "text-anchor": anchor,
106
+ }
107
+ if id:
108
+ attrs["id"] = id
109
+ ET.SubElement(parent, "text", attrs).text = text
110
+
111
+ # === Border Group ===
112
+ border_group = ET.SubElement(contents_group, "g", {"id": "border"})
113
+
114
+ x_ticks = int((width - 2 * p["inner_margin"]) // p["tick_spacing"])
115
+ for i in range(x_ticks):
116
+ x0 = p["inner_margin"] + i * p["tick_spacing"]
117
+ x_center = x0 + p["tick_spacing"] / 2
118
+ ET.SubElement(
119
+ border_group,
120
+ "line",
121
+ {
122
+ "x1": str(x0),
123
+ "y1": str(p["outer_margin"]),
124
+ "x2": str(x0),
125
+ "y2": str(height - p["outer_margin"]),
126
+ "stroke": "black",
127
+ "stroke-width": "0.5",
128
+ },
129
+ )
130
+ label_y_top = (p["outer_margin"] + p["inner_margin"]) / 2
131
+ label_y_bot = height - label_y_top
132
+ add_text(border_group, x_center, label_y_top, str(i + 1), anchor="middle")
133
+ add_text(border_group, x_center, label_y_bot, str(i + 1), anchor="middle")
134
+
135
+ x_end = p["inner_margin"] + x_ticks * p["tick_spacing"]
136
+ ET.SubElement(
137
+ border_group,
138
+ "line",
139
+ {
140
+ "x1": str(x_end),
141
+ "y1": str(p["outer_margin"]),
142
+ "x2": str(x_end),
143
+ "y2": str(height - p["outer_margin"]),
144
+ "stroke": "black",
145
+ "stroke-width": "0.5",
146
+ },
147
+ )
148
+
149
+ y_ticks = int((height - 2 * p["inner_margin"]) // p["tick_spacing"])
150
+ for j in range(y_ticks):
151
+ y0 = p["inner_margin"] + j * p["tick_spacing"]
152
+ y_center = y0 + p["tick_spacing"] / 2
153
+ ET.SubElement(
154
+ border_group,
155
+ "line",
156
+ {
157
+ "x1": str(p["outer_margin"]),
158
+ "y1": str(y0),
159
+ "x2": str(width - p["outer_margin"]),
160
+ "y2": str(y0),
161
+ "stroke": "black",
162
+ "stroke-width": "0.5",
163
+ },
164
+ )
165
+ label = chr(ord("A") + j)
166
+ label_x_left = (p["outer_margin"] + p["inner_margin"]) / 2
167
+ label_x_right = width - label_x_left
168
+ add_text(border_group, label_x_left, y_center + 4, label, anchor="middle")
169
+ add_text(border_group, label_x_right, y_center + 4, label, anchor="middle")
170
+
171
+ y_end = p["inner_margin"] + y_ticks * p["tick_spacing"]
172
+ ET.SubElement(
173
+ border_group,
174
+ "line",
175
+ {
176
+ "x1": str(p["outer_margin"]),
177
+ "y1": str(y_end),
178
+ "x2": str(width - p["outer_margin"]),
179
+ "y2": str(y_end),
180
+ "stroke": "black",
181
+ "stroke-width": "0.5",
182
+ },
183
+ )
184
+
185
+ add_rect(
186
+ border_group,
187
+ p["outer_margin"],
188
+ p["outer_margin"],
189
+ width - 2 * p["outer_margin"],
190
+ height - 2 * p["outer_margin"],
191
+ )
192
+ add_rect(
193
+ border_group,
194
+ p["inner_margin"],
195
+ p["inner_margin"],
196
+ width - 2 * p["inner_margin"],
197
+ height - 2 * p["inner_margin"],
198
+ stroke="black",
199
+ fill="white",
200
+ stroke_width=1,
201
+ )
202
+
203
+ # === Logo Group ===
204
+ tb_origin_x = width - p["inner_margin"] - p["tb_origin_offset"][0]
205
+ tb_origin_y = height - p["inner_margin"] - p["tb_origin_offset"][1]
206
+ logo_width = 1.25 * 96
207
+ logo_height = sum(p["row_heights"])
208
+ logo_group = ET.SubElement(contents_group, "g", {"id": "logo"})
209
+ add_rect(logo_group, tb_origin_x - logo_width, tb_origin_y, logo_width, logo_height)
210
+
211
+ # === Titleblock Cell Groups ===
212
+ y_cursor = tb_origin_y
213
+ for row_idx, row_height in enumerate(p["row_heights"]):
214
+ row_cols = p["column_widths"][row_idx]
215
+ row_cells = p["cell_texts"][row_idx]
216
+ x_cursor = tb_origin_x
217
+ for col_idx, col_width in enumerate(row_cols):
218
+ label, key_id = row_cells[col_idx]
219
+ group_id = (
220
+ label.lower().replace(" ", "-")
221
+ if label
222
+ else f"cell-r{row_idx}-c{col_idx}"
223
+ )
224
+ cell_group = ET.SubElement(contents_group, "g", {"id": group_id})
225
+ add_rect(cell_group, x_cursor, y_cursor, col_width, row_height)
226
+
227
+ if label:
228
+ add_text(
229
+ cell_group,
230
+ x_cursor + p["label_offset"][0],
231
+ y_cursor + p["label_offset"][1],
232
+ label,
233
+ size=7,
234
+ bold=True,
235
+ )
236
+ if key_id:
237
+ center_x = x_cursor + col_width / 2
238
+ add_text(
239
+ cell_group,
240
+ center_x,
241
+ y_cursor + p["key_offset_y"],
242
+ key_id,
243
+ size=7,
244
+ anchor="middle",
245
+ id=key_id,
246
+ )
247
+
248
+ x_cursor += col_width
249
+ y_cursor += row_height
250
+
251
+ ET.SubElement(svg, "g", {"id": "tblock-contents-end"})
252
+ rough_string = ET.tostring(svg, encoding="utf-8")
253
+ pretty = minidom.parseString(rough_string).toprettyxml(indent=" ")
254
+ with open(fileio.path("drawing"), "w", encoding="utf-8") as f:
255
+ f.write(pretty)
256
+
257
+ # === Write attributes file ===
258
+ periphery_json = {
259
+ "page_size_in": [
260
+ round(p["page_size"][0] / 96, 3),
261
+ round(p["page_size"][1] / 96, 3),
262
+ ],
263
+ }
264
+
265
+ with open(fileio.path("attributes"), "w", encoding="utf-8") as f:
266
+ json.dump(periphery_json, f, indent=2)
267
+
268
+ print()
269
+ print(f"Titleblock '{state.partnumber('pn')}' updated")
270
+ print()
harnice/state.py ADDED
@@ -0,0 +1,57 @@
1
+ import re
2
+
3
+
4
+ # not initializing these variables so that a NameError is raised if they are not set
5
+ def set_pn(x):
6
+ global pn
7
+ pn = x
8
+
9
+
10
+ def set_rev(x):
11
+ global rev
12
+ rev = x
13
+
14
+
15
+ def set_net(x):
16
+ global net
17
+ net = x
18
+
19
+
20
+ def set_file_structure(x):
21
+ global file_structure
22
+ file_structure = x
23
+
24
+
25
+ def partnumber(format):
26
+ # Returns part numbers in various formats based on the current working directory
27
+
28
+ # given a part number "pppppp-revR"
29
+
30
+ # format options:
31
+ # "pn-rev" returns "pppppp-revR"
32
+ # "pn" returns "pppppp"
33
+ # "rev" returns "revR"
34
+ # "R" returns "R"
35
+
36
+ pn_rev = f"{pn}-rev{rev}"
37
+
38
+ if format == "pn-rev":
39
+ return pn_rev
40
+
41
+ elif format == "pn":
42
+ match = re.search(r"-rev", pn_rev)
43
+ if match:
44
+ return pn_rev[: match.start()]
45
+
46
+ elif format == "rev":
47
+ match = re.search(r"-rev", pn_rev)
48
+ if match:
49
+ return pn_rev[match.start() + 1 :]
50
+
51
+ elif format == "R":
52
+ match = re.search(r"-rev", pn_rev)
53
+ if match:
54
+ return pn_rev[match.start() + 4 :]
55
+
56
+ else:
57
+ raise ValueError("Function 'partnumber' not presented with a valid format")
@@ -0,0 +1,51 @@
1
+ import ast
2
+ import webcolors
3
+
4
+ """
5
+ Appearance guide:
6
+ color lookup: https://www.w3.org/TR/SVG11/types.html#ColorKeywords
7
+ add the following dictionary-formatted string to a field in the instances list
8
+
9
+
10
+ format: design your macros to be able to parse this, define your cables to conform to this
11
+ {"base_color":"","parallelstripe":["",""],"perpstripe":[], "twisted":None}
12
+
13
+
14
+ """
15
+
16
+
17
+ def parse(val):
18
+ """
19
+ Parse appearance dictionary, converting color names and shorthand hex to full hex.
20
+ No validation or safeguards.
21
+ """
22
+ if not val:
23
+ return None
24
+
25
+ data = val if isinstance(val, dict) else ast.literal_eval(str(val))
26
+ result = {}
27
+
28
+ for key, value in data.items():
29
+ # lists → normalize each element
30
+ if isinstance(value, list):
31
+ parsed = []
32
+ for c in value:
33
+ c = c.strip().lower()
34
+ if c.startswith("#") and len(c) == 4:
35
+ c = "#" + "".join(ch * 2 for ch in c[1:])
36
+ if not c.startswith("#"):
37
+ c = webcolors.name_to_hex(c)
38
+ parsed.append(c)
39
+ result[key] = parsed
40
+ # single string → normalize directly
41
+ elif isinstance(value, str):
42
+ c = value.strip().lower()
43
+ if c.startswith("#") and len(c) == 4:
44
+ c = "#" + "".join(ch * 2 for ch in c[1:])
45
+ if not c.startswith("#"):
46
+ c = webcolors.name_to_hex(c)
47
+ result[key] = c
48
+ else:
49
+ result[key] = value
50
+
51
+ return result