harnice 0.3.0__tar.gz → 0.3.1__tar.gz

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 (47) hide show
  1. {harnice-0.3.0 → harnice-0.3.1}/PKG-INFO +2 -1
  2. {harnice-0.3.0 → harnice-0.3.1}/pyproject.toml +3 -2
  3. {harnice-0.3.0 → harnice-0.3.1}/src/harnice/fileio.py +3 -2
  4. {harnice-0.3.0 → harnice-0.3.1}/src/harnice/lists/instances_list.py +41 -39
  5. {harnice-0.3.0 → harnice-0.3.1}/src/harnice/lists/rev_history.py +248 -38
  6. {harnice-0.3.0 → harnice-0.3.1}/src/harnice/products/harness.py +8 -8
  7. {harnice-0.3.0 → harnice-0.3.1}/src/harnice/products/part.py +1 -3
  8. harnice-0.3.1/src/harnice/products/system.py +297 -0
  9. {harnice-0.3.0 → harnice-0.3.1}/src/harnice/state.py +4 -0
  10. {harnice-0.3.0 → harnice-0.3.1}/src/harnice/utils/feature_tree_utils.py +1 -1
  11. {harnice-0.3.0 → harnice-0.3.1}/src/harnice/utils/formboard_utils.py +5 -11
  12. {harnice-0.3.0 → harnice-0.3.1}/src/harnice/utils/system_utils.py +40 -18
  13. {harnice-0.3.0 → harnice-0.3.1}/src/harnice.egg-info/PKG-INFO +2 -1
  14. {harnice-0.3.0 → harnice-0.3.1}/src/harnice.egg-info/requires.txt +1 -0
  15. harnice-0.3.0/src/harnice/products/system.py +0 -125
  16. {harnice-0.3.0 → harnice-0.3.1}/LICENSE +0 -0
  17. {harnice-0.3.0 → harnice-0.3.1}/README.md +0 -0
  18. {harnice-0.3.0 → harnice-0.3.1}/setup.cfg +0 -0
  19. {harnice-0.3.0 → harnice-0.3.1}/src/harnice/__init__.py +0 -0
  20. {harnice-0.3.0 → harnice-0.3.1}/src/harnice/__main__.py +0 -0
  21. {harnice-0.3.0 → harnice-0.3.1}/src/harnice/cli.py +0 -0
  22. {harnice-0.3.0 → harnice-0.3.1}/src/harnice/gui/launcher.py +0 -0
  23. {harnice-0.3.0 → harnice-0.3.1}/src/harnice/lists/channel_map.py +0 -0
  24. {harnice-0.3.0 → harnice-0.3.1}/src/harnice/lists/circuits_list.py +0 -0
  25. {harnice-0.3.0 → harnice-0.3.1}/src/harnice/lists/disconnect_map.py +0 -0
  26. {harnice-0.3.0 → harnice-0.3.1}/src/harnice/lists/formboard_graph.py +0 -0
  27. {harnice-0.3.0 → harnice-0.3.1}/src/harnice/lists/library_history.py +0 -0
  28. {harnice-0.3.0 → harnice-0.3.1}/src/harnice/lists/manifest.py +0 -0
  29. {harnice-0.3.0 → harnice-0.3.1}/src/harnice/lists/post_harness_instances_list.py +0 -0
  30. {harnice-0.3.0 → harnice-0.3.1}/src/harnice/lists/signals_list.py +0 -0
  31. {harnice-0.3.0 → harnice-0.3.1}/src/harnice/products/__init__.py +0 -0
  32. {harnice-0.3.0 → harnice-0.3.1}/src/harnice/products/cable.py +0 -0
  33. {harnice-0.3.0 → harnice-0.3.1}/src/harnice/products/chtype.py +0 -0
  34. {harnice-0.3.0 → harnice-0.3.1}/src/harnice/products/device.py +0 -0
  35. {harnice-0.3.0 → harnice-0.3.1}/src/harnice/products/disconnect.py +0 -0
  36. {harnice-0.3.0 → harnice-0.3.1}/src/harnice/products/flagnote.py +0 -0
  37. {harnice-0.3.0 → harnice-0.3.1}/src/harnice/products/macro.py +0 -0
  38. {harnice-0.3.0 → harnice-0.3.1}/src/harnice/products/tblock.py +0 -0
  39. {harnice-0.3.0 → harnice-0.3.1}/src/harnice/utils/appearance.py +0 -0
  40. {harnice-0.3.0 → harnice-0.3.1}/src/harnice/utils/circuit_utils.py +0 -0
  41. {harnice-0.3.0 → harnice-0.3.1}/src/harnice/utils/library_utils.py +0 -0
  42. {harnice-0.3.0 → harnice-0.3.1}/src/harnice/utils/note_utils.py +0 -0
  43. {harnice-0.3.0 → harnice-0.3.1}/src/harnice/utils/svg_utils.py +0 -0
  44. {harnice-0.3.0 → harnice-0.3.1}/src/harnice.egg-info/SOURCES.txt +0 -0
  45. {harnice-0.3.0 → harnice-0.3.1}/src/harnice.egg-info/dependency_links.txt +0 -0
  46. {harnice-0.3.0 → harnice-0.3.1}/src/harnice.egg-info/entry_points.txt +0 -0
  47. {harnice-0.3.0 → harnice-0.3.1}/src/harnice.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: harnice
3
- Version: 0.3.0
3
+ Version: 0.3.1
4
4
  Summary: Electrical System CAD
5
5
  Author-email: Kenyon Shutt <harnice.io@gmail.com>
6
6
  Project-URL: Documentation, https://harnice.io/
@@ -18,6 +18,7 @@ Requires-Dist: Pillow
18
18
  Requires-Dist: PyYAML
19
19
  Requires-Dist: xlwt
20
20
  Requires-Dist: webcolors
21
+ Requires-Dist: prompt_toolkit
21
22
  Dynamic: license-file
22
23
 
23
24
  Harnice is a free, open source electrical system CAD tool.
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "harnice"
3
- version = "0.3.0"
3
+ version = "0.3.1"
4
4
  authors = [
5
5
  { name="Kenyon Shutt", email="harnice.io@gmail.com" },
6
6
  ]
@@ -19,7 +19,8 @@ dependencies = [
19
19
  "Pillow",
20
20
  "PyYAML",
21
21
  "xlwt",
22
- "webcolors"
22
+ "webcolors",
23
+ "prompt_toolkit"
23
24
  ]
24
25
 
25
26
  [project.urls]
@@ -209,12 +209,13 @@ def verify_revision_structure():
209
209
  f"Harnice only renders revisions with a blank status."
210
210
  )
211
211
 
212
- print(f"Working on PN: {state.pn}, Rev: {state.rev}")
212
+ print(f"Rendering PN: {state.pn}, Rev: {state.rev}")
213
213
  rev_history.update_datemodified()
214
214
 
215
215
 
216
216
  def today():
217
- return datetime.date.today().strftime("%-m/%-d/%y")
217
+ d = datetime.date.today()
218
+ return f"{d.month}/{d.day}/{d.year % 100}"
218
219
 
219
220
 
220
221
  def get_git_hash_of_harnice_src():
@@ -5,23 +5,25 @@ from threading import Lock
5
5
  from harnice import fileio, state
6
6
 
7
7
  COLUMNS = [
8
- "net", #documentation needed
9
- "instance_name", #documentation needed
10
- "print_name", #documentation needed
11
- "bom_line_number", #documentation needed
12
- "mfg", #documentation needed
13
- "mpn", # unique part identifier (manufacturer + part number concatenated)
8
+ "net", # the physical harness (represented by a net in Kicad) that this instance is part of
9
+ "instance_name", # the unique name of this instance
10
+ "print_name", # the non-unique, human-readable name of this instance, used for printing on output documents
11
+ "bom_line_number", # if this instance represents a physical procurable good, it gets assigned a line number on a bill of materials
12
+ "mfg", # manufacturer of this instance
13
+ "mpn", # manufacturer part number
14
14
  "item_type", # connector, backshell, whatever
15
15
  "parent_instance", # general purpose reference
16
16
  "location_type", # each instance is either better represented by one or ther other
17
17
  "segment_group", # the group of segments that this instance is part of
18
18
  "segment_order", # the sequential id of this item in its segment group
19
19
  "connector_group", # a group of co-located parts (connectors, backshells, nodes)
20
- "channel_group", #documentation needed
20
+ "channel_group", # other instances associated with this one because they are part of the same channel will share this value
21
21
  "circuit_id", # which signal this component is electrically connected to
22
22
  "circuit_port_number", # the sequential id of this item in its signal chain
23
23
  "node_at_end_a", # derived from formboard definition
24
24
  "node_at_end_b", # derived from formboard definition
25
+ "print_name_at_end_a", # human-readable name of this instance if needed, associated with 'node_at_end_a'
26
+ "print_name_at_end_b", # human-readable name of this instance if needed, associated with 'node_at_end_b'
25
27
  "parent_csys_instance_name", # the other instance upon which this instance's location is based
26
28
  "parent_csys_outputcsys_name", # the specific output coordinate system of the parent that this instance's location is based
27
29
  "translate_x", # derived from parent_csys and parent_csys_name
@@ -29,9 +31,9 @@ COLUMNS = [
29
31
  "rotate_csys", # derived from parent_csys and parent_csys_name
30
32
  "absolute_rotation", # manual add, not nominally used unless it's a flagnote, segment, or node
31
33
  "csys_children", # imported csys children from library attributes file
32
- "cable_group", #documentation needed
33
- "cable_container", #documentation needed
34
- "cable_identifier", #documentation needed
34
+ "cable_group", # other instances associated with this one because they are part of the same cable will share this value
35
+ "cable_container", # which cable is this instance physically bundled inside of
36
+ "cable_identifier", # cable unique identifier
35
37
  "length", # derived from formboard definition, the length of a segment
36
38
  "diameter", # apparent diameter of a segment <---------- change to print_diameter
37
39
  "appearance", # see harnice.utils.appearance for details
@@ -40,39 +42,39 @@ COLUMNS = [
40
42
  "note_parent", # the instance the note applies to. typically don't use this in the instances list, just note_utils
41
43
  "note_text", # the content of the note
42
44
  "note_affected_instances", # list of instances that are affected by the note
43
- "lib_repo", #documentation needed
44
- "lib_subpath", #documentation needed
45
- "lib_desc", #documentation needed
46
- "lib_latest_rev", #documentation needed
47
- "lib_rev_used_here", #documentation needed
48
- "lib_status", #documentation needed
45
+ "lib_repo", # publically-traceable URL of the library this instance is from
46
+ "lib_subpath", # path to the instance within the library (directories between the product type and the part number)
47
+ "lib_desc", # description of the instance per the library's revision history
48
+ "lib_latest_rev", # the latest revision of the instance that exists in the remote library
49
+ "lib_rev_used_here", # the revision of the instance that is currently used in this project
50
+ "lib_status", # the status of the instance per the library's revision history
49
51
  "lib_releaseticket", #documentation needed
50
- "lib_datestarted", #documentation needed
51
- "lib_datemodified", #documentation needed
52
- "lib_datereleased", #documentation needed
53
- "lib_drawnby", #documentation needed
54
- "lib_checkedby", #documentation needed
55
- "project_editable_lib_modified", #documentation needed
56
- "lib_build_notes", #documentation needed
57
- "lib_tools", #documentation needed
52
+ "lib_datestarted", # the date this instance was first added to the library
53
+ "lib_datemodified", # the date this instance was last modified in the library
54
+ "lib_datereleased", # the date this instance was released in the library, if applicable, per the library's revision history
55
+ "lib_drawnby", # the name of the person who drew the instance, per the library's revision history
56
+ "lib_checkedby", # the name of the person who checked the instance, per the library's revision history
57
+ "project_editable_lib_modified", # a flag to indicate if the imported contents do not match the library's version (it's been locally modified)
58
+ "lib_build_notes", # recommended build notes that come with the instance from the library
59
+ "lib_tools", # recommended tools that come with the instance from the library
58
60
  "this_instance_mating_device_refdes", # if connector, refdes of the device it plugs into
59
61
  "this_instance_mating_device_connector", # if connector, name of the connector it plugs into
60
62
  "this_instance_mating_device_connector_mpn", # if connector, mpn of the connector it plugs into
61
- "this_net_from_device_refdes", #documentation needed
62
- "this_net_from_device_channel_id", #documentation needed
63
- "this_net_from_device_connector_name", #documentation needed
64
- "this_net_to_device_refdes", #documentation needed
65
- "this_net_to_device_channel_id", #documentation needed
66
- "this_net_to_device_connector_name", #documentation needed
67
- "this_channel_from_device_refdes", # if channel, refdes of the device on one side of the channel
68
- "this_channel_from_device_channel_id", #documentation needed
69
- "this_channel_to_device_refdes", # if channel, refdes of the device on the other side of the channel
70
- "this_channel_to_device_channel_id", #documentation needed
71
- "this_channel_from_channel_type", #documentation needed
72
- "this_channel_to_channel_type", #documentation needed
73
- "signal_of_channel_type", #documentation needed
74
- "debug", #documentation needed
75
- "debug_cutoff", #documentation needed
63
+ "this_net_from_device_refdes", # if this instance is a channel, circuit, conductor, etc, the refdes of the device it interfaces with, just within this net
64
+ "this_net_from_device_channel_id", # if this instance is a channel, circuit, conductor, etc, the channel id in the device it interfaces with, just within this net
65
+ "this_net_from_device_connector_name", # if this instance is a channel, circuit, conductor, etc, the name of the connector it interfaces with, just within this net
66
+ "this_net_to_device_refdes", # if this instance is a channel, circuit, conductor, etc, the refdes of the device it plugs into just within this net
67
+ "this_net_to_device_channel_id", # if this instance is a channel, circuit, conductor, etc, the channel id in the device it plugs into, just within this net
68
+ "this_net_to_device_connector_name", # if this instance is a channel, circuit, conductor, etc, the name of the connector it plugs into, just within this net
69
+ "this_channel_from_device_refdes", # if this instance is a channel, circuit, conductor, etc, the refdes of the device it interfaces with, at the very end of the channel
70
+ "this_channel_from_device_channel_id", # if this instance is a channel, circuit, conductor, etc, the channel id in the device it interfaces with, at the very end of the channel
71
+ "this_channel_to_device_refdes", # if this instance is a channel, circuit, conductor, etc, the refdes of the device it plugs into, at the very end of the channel
72
+ "this_channel_to_device_channel_id", # if this instance is a channel, circuit, conductor, etc, the channel id in the device it plugs into, at the very end of the channel
73
+ "this_channel_from_channel_type", # if this instance is a channel, circuit, conductor, etc, the type of the channel it interfaces with, at the very end of the channel
74
+ "this_channel_to_channel_type", # if this instance is a channel, circuit, conductor, etc, the type of the channel it plugs into, at the very end of the channel
75
+ "signal_of_channel_type", # if this instance is a channel, circuit, conductor, etc, the signal of the channel it interfaces with, at the very end of the channel
76
+ "debug", # the call chain of the function that last modified this instance row
77
+ "debug_cutoff", # blank cell to visually cut off the previous column
76
78
  ]
77
79
 
78
80
 
@@ -6,27 +6,59 @@ from harnice import fileio, state
6
6
 
7
7
  # === Global Columns Definition ===
8
8
  COLUMNS = [
9
- "product", #documentation needed
10
- "mfg", #documentation needed
11
- "pn", #documentation needed
12
- "desc", #documentation needed
13
- "rev", #documentation needed
14
- "status", #documentation needed
15
- "releaseticket", #documentation needed
16
- "library_repo", #documentation needed
17
- "library_subpath", #documentation needed
18
- "datestarted", #documentation needed
19
- "datemodified", #documentation needed
20
- "datereleased", #documentation needed
21
- "git_hash_of_harnice_src", #documentation needed
22
- "drawnby", #documentation needed
23
- "checkedby", #documentation needed
24
- "revisionupdates", #documentation needed
25
- "affectedinstances", #documentation needed
9
+ "product", # the harnice product type (e.g. "harness", "connector", "device", "system", "macro", "flagnote", "tblock")
10
+ "mfg", # who manufactures this product (blank ok)
11
+ "pn", # name, part number, other identifier of this part. mfg+mpn combination must be unique within the library.
12
+ "desc", # a brief description of this product
13
+ "rev", # the revision of the part
14
+ "status", # "released", "obsolete", etc. Harnice will not render a revision if the status has text in this field as a form of protection.
15
+ "releaseticket", # many companies do this, but it's not required.
16
+ "library_repo", # auto-filled on render if the current working directory is discovered to be a library repository.
17
+ "library_subpath", # auto-filled on render if in a library repository, this is the chain of directories between the product type and the part number
18
+ "datestarted", # auto-filled to be the date when this part was first intialized
19
+ "datemodified", # updates to today's date upon rendering
20
+ "datereleased", # up to user to fill in as needed
21
+ "git_hash_of_harnice_src", # auto-filled, git hash of the harnice source code during the latest render
22
+ "drawnby", # auto-filled, the person who created the part
23
+ "checkedby", # the person who checked the part, blank ok
24
+ "revisionupdates", # a brief description of the changes made to this revision
25
+ "affectedinstances", # the instance names of the instances that were affected by this revision. can be referenced later by PDF builders and more.
26
26
  ]
27
27
 
28
28
 
29
29
  def overwrite(content_dict):
30
+ """
31
+ Overwrite a revision history entry.
32
+
33
+ **Arguments:**
34
+
35
+ - `content_dict` (dict): The content to overwrite the revision history entry with.
36
+ - This should be a dictionary with the keys and values to overwrite.
37
+ - The keys should be the column names, and the values should be the new values.
38
+ - Some keys are protected and cannot be overwritten:
39
+ - `"product"`
40
+ - `"mfg"`
41
+ - `"pn"`
42
+ - `"rev"`
43
+ - `"releaseticket"`
44
+ - `"library_repo"`
45
+ - `"library_subpath"`
46
+ - `"datestarted"`
47
+
48
+ The function will update the revision history file as referenced by the current product file structure.
49
+
50
+ **Returns:**
51
+
52
+ - `None`
53
+
54
+ **Raises:**
55
+
56
+ - `KeyError`: If a key is provided that is not in the COLUMNS list.
57
+ - `KeyError`: If a protected key is provided.
58
+ - `ValueError`: If the revision history file is not found.
59
+ - `ValueError`: If the revision is not found in the revision history file.
60
+ - `RuntimeError`: If `state.rev` is not set.
61
+ """
30
62
  PROTECTED_KEYS = [
31
63
  "product",
32
64
  "mfg",
@@ -84,6 +116,31 @@ def overwrite(content_dict):
84
116
 
85
117
 
86
118
  def info(rev=None, path=None, field=None, all=False):
119
+ """
120
+ Get information about a revision history entry.
121
+
122
+ **Arguments:**
123
+
124
+ - `rev` (str): The revision to get information about.
125
+ - `path` (str): The path to the revision history file.
126
+ - If not provided, the function will use the default path: `"revision history"`.
127
+ - `field` (str): The field to get information about.
128
+ - If not provided, the function will return the entire row.
129
+ - If provided, the function will return the value of the field.
130
+ - `all` (bool): If `True`, return all rows.
131
+ - If not provided, the function will return the first row.
132
+
133
+ **Returns:**
134
+
135
+ - `dict`: The row of the revision history entry (when `field` is not provided).
136
+ - `list`: A list of all rows in the revision history file (when `all=True`).
137
+ - `str`: The value of the field (when `field` is provided).
138
+
139
+ **Raises:**
140
+
141
+ - `FileNotFoundError`: If the revision history file is not found.
142
+ - `ValueError`: If the revision is not found in the revision history file.
143
+ """
87
144
  if path is None:
88
145
  path = fileio.path("revision history")
89
146
 
@@ -104,7 +161,6 @@ def info(rev=None, path=None, field=None, all=False):
104
161
 
105
162
  for row in rows:
106
163
  if row.get("rev") == rev:
107
-
108
164
  # ------------------------------------------------------
109
165
  # Field requested
110
166
  # ------------------------------------------------------
@@ -142,6 +198,17 @@ def info(rev=None, path=None, field=None, all=False):
142
198
 
143
199
 
144
200
  def initial_release_exists():
201
+ """
202
+ Check if an initial release exists.
203
+
204
+ **Arguments:**
205
+
206
+ None
207
+
208
+ **Returns:**
209
+
210
+ - `bool`: `True` if a revision with the text `"INITIAL RELEASE"` in the `"revisionupdates"` field exists, `False` otherwise.
211
+ """
145
212
  try:
146
213
  for row in fileio.read_tsv("revision history"):
147
214
  if str(row.get("revisionupdates", "")).strip() == "INITIAL RELEASE":
@@ -153,12 +220,39 @@ def initial_release_exists():
153
220
 
154
221
 
155
222
  def initial_release_desc():
223
+ """
224
+ Get the description of the initial release.
225
+
226
+ **Arguments:**
227
+
228
+ None
229
+
230
+ **Returns:**
231
+
232
+ - `str`: The description of the revision which has `revisionupdates == 'INITIAL RELEASE'`.
233
+ """
156
234
  for row in fileio.read_tsv("revision history"):
157
235
  if row.get("revisionupdates") == "INITIAL RELEASE":
158
236
  return row.get("desc")
159
237
 
160
238
 
161
239
  def update_datemodified():
240
+ """
241
+ Update the `datemodified` field of the current revision with today's date.
242
+
243
+ **Arguments:**
244
+
245
+ None
246
+
247
+ **Returns:**
248
+
249
+ - `None`
250
+
251
+ **Raises:**
252
+
253
+ - `ValueError`: If the revision history file is not found.
254
+ - `ValueError`: If the revision is not found in the revision history file.
255
+ """
162
256
  target_rev = state.partnumber("R")
163
257
 
164
258
  # Read all rows
@@ -182,42 +276,94 @@ def update_datemodified():
182
276
  writer.writerows(rows)
183
277
 
184
278
 
185
- def new():
279
+ def new(ignore_product=False, path=None):
280
+ """
281
+ Create a new revision history file.
282
+
283
+ **Arguments:**
284
+
285
+ - `ignore_product` (bool):
286
+ - If `True`, the function will raise an error if `state.product` is not set first.
287
+ - If `False`, the function will prompt the user to select a product type.
288
+
289
+ **Returns:**
290
+
291
+ - `None`
292
+
293
+ **Raises:**
294
+
295
+ - `ValueError`: If attempting to create a new revision history file without a product type when `ignore_product=True`.
296
+ - `ValueError`: If attempting to overwrite an existing revision history file.
297
+ """
186
298
  columns = COLUMNS
187
- from harnice.cli import select_product_type
188
299
 
189
- global product
190
- product = select_product_type()
191
- with open(fileio.path("revision history"), "w", newline="", encoding="utf-8") as f:
192
- writer = csv.DictWriter(f, fieldnames=columns, delimiter="\t")
193
- writer.writeheader()
300
+ if path is None:
301
+ path = fileio.path("revision history")
302
+
303
+ if not ignore_product:
304
+ from harnice.cli import select_product_type
305
+
306
+ state.set_product(select_product_type())
307
+
308
+ if ignore_product and not state.product:
309
+ raise ValueError(
310
+ "You tried to create a new revision history file without a product type. This is not allowed."
311
+ )
312
+
313
+ if not os.path.exists(path):
314
+ with open(path, "w", newline="", encoding="utf-8") as f:
315
+ writer = csv.DictWriter(f, fieldnames=columns, delimiter="\t")
316
+ writer.writeheader()
317
+
318
+ else:
319
+ raise ValueError(
320
+ "You tried to overwrite a revision history file- this is not allowed."
321
+ )
194
322
 
195
323
 
196
324
  def append(next_rev=None):
197
- from harnice import cli
325
+ """
326
+ Append a new revision history entry to the current revision history file.
327
+
328
+ If the revision history file does not exist, the function will create it.
329
+ If the revision history file exists, the function will append a new entry to the file.
330
+
331
+ It will prompt the user for the following fields:
332
+
333
+ - `product`: The product type of the part.
334
+ - `desc`: The description of the part.
335
+ - `revisionupdates`: What is the purpose of this revision?
336
+
337
+ If the previous revision has a blank status, the function will prompt the user to obsolete it with a message.
338
+
339
+ **Arguments:**
340
+
341
+ - `next_rev` The next revision number to append.
342
+
343
+ **Returns:**
198
344
 
199
- global product
345
+ - `None`
346
+ """
347
+ from harnice import cli
200
348
 
201
349
  if not os.path.exists(fileio.path("revision history")):
202
350
  new()
203
351
  rows = fileio.read_tsv("revision history")
204
- product_name = None
205
352
  if rows:
206
353
  for row in reversed(rows):
207
354
  candidate = (row.get("product") or "").strip()
208
355
  if candidate:
209
- product_name = candidate
356
+ state.set_product(candidate)
210
357
  break
211
- if not product_name:
212
- product_name = globals().get("product")
213
- if not product_name:
214
- product_name = cli.select_product_type()
215
- product = product_name
358
+ if not state.product:
359
+ state.set_product(cli.select_product_type())
216
360
 
217
361
  default_desc = ""
218
- if product_name:
362
+ if state.product:
219
363
  try:
220
- product_module = importlib.import_module(f"harnice.products.{product_name}")
364
+ product_module = importlib.import_module(
365
+ f"harnice.products.{state.product}"
366
+ )
221
367
  except ModuleNotFoundError:
222
368
  product_module = None
223
369
  else:
@@ -252,7 +398,7 @@ def append(next_rev=None):
252
398
 
253
399
  if desc in [None, ""]:
254
400
  desc = cli.prompt(
255
- f"Enter a description of this {product_name}",
401
+ f"Enter a description of this {state.product}",
256
402
  default=default_desc,
257
403
  )
258
404
 
@@ -305,7 +451,7 @@ def append(next_rev=None):
305
451
 
306
452
  rows.append(
307
453
  {
308
- "product": product_name,
454
+ "product": state.product,
309
455
  "pn": state.pn,
310
456
  "rev": next_rev,
311
457
  "desc": desc,
@@ -323,3 +469,67 @@ def append(next_rev=None):
323
469
  writer = csv.DictWriter(f, fieldnames=columns, delimiter="\t")
324
470
  writer.writeheader()
325
471
  writer.writerows(rows)
472
+
473
+
474
+ def part_family_append(content_dict, rev_history_path):
475
+ """
476
+ Append a new revision history entry to the part family revision history file.
477
+
478
+ Intended to be called by part family scripts only.
479
+
480
+ The function will automatically update the following fields in the content dictionary:
481
+
482
+ - `datemodified`: Set to today's date
483
+ - `drawnby`: Set to the current user's name
484
+ - `git_hash_of_harnice_src`: Set to the current git hash of the harnice source code
485
+
486
+ If the revision history file does not exist, the function will create it.
487
+ If an entry with the same revision number already exists, the function will update that entry.
488
+ Otherwise, the function will append a new entry to the file.
489
+
490
+ **Arguments:**
491
+
492
+ - `content_dict` (dict): The content to append to the part family revision history file.
493
+ - Should contain keys matching the `COLUMNS` list.
494
+ - The `rev` key is used to determine if an entry already exists.
495
+ - `rev_history_path` (str): The path to the part family revision history file.
496
+
497
+ **Returns:**
498
+
499
+ - `None`
500
+
501
+ **Raises:**
502
+
503
+ - `ValueError`: If the content dictionary contains invalid keys or missing required fields.
504
+ """
505
+ actual_content_dict = content_dict
506
+
507
+ actual_content_dict["datemodified"] = fileio.today()
508
+ actual_content_dict["drawnby"] = fileio.drawnby()["name"]
509
+ actual_content_dict["git_hash_of_harnice_src"] = (
510
+ fileio.get_git_hash_of_harnice_src()
511
+ )
512
+
513
+ rev = content_dict.get("rev")
514
+
515
+ if os.path.exists(rev_history_path):
516
+ rows = fileio.read_tsv(rev_history_path)
517
+ else:
518
+ rows = []
519
+
520
+ found = False
521
+ for i, row in enumerate(rows):
522
+ if row.get("rev") == actual_content_dict.get("rev"):
523
+ rows[i] = actual_content_dict
524
+ found = True
525
+ break
526
+
527
+ if not found:
528
+ rows.append(actual_content_dict)
529
+
530
+ if not os.path.exists(rev_history_path):
531
+ new(path=rev_history_path, ignore_product=True)
532
+ with open(rev_history_path, "w", newline="", encoding="utf-8") as f:
533
+ writer = csv.DictWriter(f, fieldnames=COLUMNS, delimiter="\t")
534
+ writer.writeheader()
535
+ writer.writerows(rows)
@@ -162,7 +162,7 @@ for instance in instances:
162
162
  if instance.get("item_type") == "circuit":
163
163
  circuit_instance = instance
164
164
  connector_at_end_a = instances_list.attribute_of(instance.get("node_at_end_a"), "connector_group")
165
- new_instance_name = f"{{circuit_instance.get("instance_name")}}-special_contact"
165
+ new_instance_name = f"{{circuit_instance.get('instance_name')}}-special_contact"
166
166
  circuit_id = int(circuit_instance.get("circuit_id"))
167
167
  instances_list.new_instance(
168
168
  new_instance_name, {{
@@ -181,7 +181,7 @@ circuit_utils.squeeze_instance_between_ports_in_circuit(
181
181
  # example: add a backshell
182
182
  for instance in instances:
183
183
  if instance.get("instance_name") in ["X1.B.conn", "PREAMP2.in2.conn"]:
184
- instances_list.new_instance(f"{{instance.get("connector_group")}}.bs", {{
184
+ instances_list.new_instance(f"{{instance.get('connector_group')}}.bs", {{
185
185
  "bom_line_number": True,
186
186
  "mpn": "M85049-90_9Z03",
187
187
  "item_type": "backshell",
@@ -193,7 +193,7 @@ for instance in instances:
193
193
  "lib_repo": "https://github.com/harnice/harnice"
194
194
  }})
195
195
  instances_list.modify(instance.get("instance_name"), {{
196
- "parent_csys_instance_name": f"{{instance.get("connector_group")}}.bs",
196
+ "parent_csys_instance_name": f"{{instance.get('connector_group')}}.bs",
197
197
  "parent_csys_outputcsys_name": "connector",
198
198
  }})
199
199
 
@@ -270,23 +270,23 @@ for x in range(2):
270
270
 
271
271
  elif instance.get("item_type") in ["conductor", "conductor-segment"]:
272
272
  instances_list.modify(instance.get("instance_name"), {{
273
- "print_name": f"'{{instance.get("cable_identifier")}}' of '{{instances_list.attribute_of(instance.get("cable_group"), "print_name")}}'"
273
+ "print_name": f"'{{instance.get('cable_identifier')}}' of '{{instances_list.attribute_of(instance.get('cable_group'), 'print_name')}}'"
274
274
  }})
275
275
 
276
276
  elif instance.get("item_type") == "net-channel":
277
- print_name = f"'{{instance.get("this_channel_from_device_channel_id")}}' of '{{instance.get("this_channel_from_device_refdes")}}' to '{{instance.get("this_channel_to_device_channel_id")}}' of '{{instance.get("this_channel_to_device_refdes")}}'"
277
+ print_name = f"'{{instance.get('this_channel_from_device_channel_id')}}' of '{{instance.get('this_channel_from_device_refdes')}}' to '{{instance.get('this_channel_to_device_channel_id')}}' of '{{instance.get('this_channel_to_device_refdes')}}'"
278
278
  instances_list.modify(instance.get("instance_name"), {{"print_name": print_name}})
279
279
 
280
280
  elif instance.get("item_type") == "net-channel-segment":
281
- print_name = f"'{{instances_list.attribute_of(instance.get("parent_instance"), "this_channel_from_device_channel_id")}}' of '{{instances_list.attribute_of(instance.get("parent_instance"), "this_channel_from_device_refdes")}}' to '{{instances_list.attribute_of(instance.get("parent_instance"), "this_channel_to_device_channel_id")}}' of '{{instances_list.attribute_of(instance.get("parent_instance"), "this_channel_to_device_refdes")}}'"
281
+ print_name = f"'{{instances_list.attribute_of(instance.get('parent_instance'), 'this_channel_from_device_channel_id')}}' of '{{instances_list.attribute_of(instance.get('parent_instance'), 'this_channel_from_device_refdes')}}' to '{{instances_list.attribute_of(instance.get('parent_instance'), 'this_channel_to_device_channel_id')}}' of '{{instances_list.attribute_of(instance.get('parent_instance'), 'this_channel_to_device_refdes')}}'"
282
282
  instances_list.modify(instance.get("instance_name"), {{"print_name": print_name}})
283
283
 
284
284
  elif instance.get("item_type") == "connector":
285
- print_name = f"{{instance.get("connector_group")}}"
285
+ print_name = f"{{instance.get('connector_group')}}"
286
286
  instances_list.modify(instance.get("instance_name"), {{"print_name": print_name}})
287
287
 
288
288
  elif instance.get("item_type") == "cable-segment":
289
- print_name = f"{{instance.get("cable_group")}}"
289
+ print_name = f"{{instance.get('cable_group')}}"
290
290
  instances_list.modify(instance.get("instance_name"), {{"print_name": print_name}})
291
291
 
292
292
  elif instance.get("item_type") == "contact":
@@ -635,6 +635,4 @@ def render():
635
635
  png_path = fileio.path("drawing png")
636
636
  img.save(png_path, dpi=(1000, 1000))
637
637
 
638
- print()
639
- print(f"Part file '{state.partnumber('pn')}' updated")
640
- print()
638
+ print(f"Part file '{state.partnumber('pn')}' updated\n")