Skip to content

Systems

A collection of devices and harnesses that satisfies a set of functionality requirements for some external purpose.


How system data is stored

System data is stored in the following file formats.

Instances List

Interacting with Instances Lists

An instances list is a list of every physical or notional item, idea, note, part, instruction, circuit, drawing element, thing, concept literally anything that describes how to build that harness or system.

Instances lists are the single comprehensive source of truth for the project you are working on. Other inputs—especially build instructions (your Python script)—build this list, and all output documentation are derived from it.


Columns

Columns are automatically generated when instances_list.new() is called. Additional columns for this kind of list may be added by the user.

Column Description
harness the physical harness (harness refdes) that this instance is part of
instance_name the unique name of this instance
print_name the non-unique, human-readable name of this instance, used for printing on output documents
bom_line_number if this instance represents a physical procurable good, it gets assigned a line number on a bill of materials
mfg manufacturer of this instance
mpn manufacturer part number
item_type what this instance is: harness_connector, backshell, cable, conductor, node, segment, note, flagnote, macro, and so on
location_type node or segment; a node is a point, a segment is a length between nodes
segment_group the group of segments that this instance is part of
segment_order the sequential id of this item in its segment group
connector_group a group of co-located parts (connectors, backshells, nodes)
channel_group other instances associated with this one because they are part of the same channel will share this value
circuit_id which signal this component is electrically connected to
circuit_port_number the sequential id of this item in its signal chain
node_at_end_a end A: a node id on a segment, or a cavity instance name on a conductor
node_at_end_b end B: a node id on a segment, or a cavity instance name on a conductor
print_name_at_end_a human-readable name of this instance if needed, associated with 'node_at_end_a'
print_name_at_end_b human-readable name of this instance if needed, associated with 'node_at_end_b'
parent_csys_instance_name the other instance upon which this instance's location is based
parent_csys_outputcsys_name the specific output coordinate system of the parent that this instance's location is based
2d_translate dict {x, y, rotate} in the parent CSYS; used by flattened drawings
3d_translate 6DOF dict {x, y, z, alpha, beta, gamma}; α about X, γ about Z, used by STEP
absolute_rotation drawing heading in degrees; derive writes it on nodes and segments, flagnotes use 0
csys_children imported csys children from library attributes file
cable_group other instances associated with this one because they are part of the same cable will share this value
cable_identifier which conductor in that cable this instance is assigned to (unique within the cable)
length segment arc length from derive, or the routed length of a conductor or cable
length_tolerance tolerance kept from the flattened CSV when derive rewrites a segment
diameter apparent diameter of a segment, used when the segment is drawn
appearance see harnice.utils.appearance for details
device_configuration compact JSON of filled configuration-setup answers (from BOM)
note_type build_note, rev_change_callout, bom_item, part_name, and other note kinds
note_number if there is a counter involved (rev, bom, build_note, etc)
note_parent the instance the note applies to. typically don't use this in the instances list, just note_utils
note_text the content of the note
note_affected_instances list of instances that are affected by the note
lib_repo publicly traceable URL of the library this instance is from
lib_subpath path to the instance within the library (directories between the project type and the part number)
lib_desc description of the instance per the library's revision history
lib_latest_rev the latest revision of the instance that exists in the remote library
lib_rev_used_here the revision of the instance that is currently used in this project
lib_status the status of the instance per the library's revision history
lib_releaseticket release ticket on the library revision, when that library records one
lib_datestarted the date this instance was first added to the library
lib_datemodified the date this instance was last modified in the library
lib_datereleased the date this instance was released in the library, if applicable, per the library's revision history
lib_drawnby the name of the person who drew the instance, per the library's revision history
lib_checkedby the name of the person who checked the instance, per the library's revision history
project_editable_lib_modified a flag to indicate if the imported contents do not match the library's version (it's been locally modified)
lib_build_notes recommended build notes that come with the instance from the library
lib_tools recommended tools that come with the instance from the library
attributes_json if an instance is imported with an attributes json attached, it's added here
device_side_refdes if device_connector, refdes of the device it plugs into
device_side_connector_id if device_connector, name of the connector it plugs into
device_side_connector_mpn if device_connector, mpn of the connector it plugs into
device_side_from_cavity if circuit, cavity on the from side (from circuits list harness_from_cavity)
device_side_to_cavity if circuit, cavity on the to side (from circuits list harness_to_cavity)
harness_side_connector_id asserted name of the mating harness connector (from system connector list)
this_harness_from_device_refdes if this instance is a channel, circuit, conductor, etc, the refdes of the device it interfaces with, just within this harness
this_harness_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 harness
this_harness_from_device_connector_name if this instance is a channel, circuit, conductor, etc, the name of the connector it interfaces with, just within this harness
this_harness_to_device_refdes if this instance is a channel, circuit, conductor, etc, the refdes of the device it plugs into just within this harness
this_harness_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 harness
this_harness_to_device_connector_name if this instance is a channel, circuit, conductor, etc, the name of the connector it plugs into, just within this harness
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
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
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
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
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
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
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

Commands

These functions are already available in build instructions as instances_list. Use them as written.

instances_list.read()

Use this when a harness or system build needs every row of the instances list in the revision that is open.

Return every row of the instances list.

The file is fileio.path("instances list"). Cells come back as strings. attribute_of parses a cell that starts with [ or {.

instances_list.new()

Use this when the project build is starting the instances list. Harness and system builds call this before the script and replace the file. A build script does not call it again.

Create a new empty instances list file with only the standard header (COLUMNS). Overwrites existing file if present.

instances_list.new_instance(instance_name, instance_data, ignore_duplicates=False)

Use this when a harness or system build needs one new item added to the instances list.

Add a new instance to the instances list.

Usage new_instance(instance_name, instance_data, ignore_duplicates=False)

Args - instance_name: String; must be unique within the list. - instance_data: Dict of column names to values. May include instance_name; if present it must match the instance_name argument or the code will fail. - ignore_duplicates: If True, does nothing when an instance with the same instance_name already exists. If False (default), raises an error on duplicate.

Returns -1 on success. Raises on invalid input or duplicate (when ignore_duplicates is False).

instances_list.remove_instance(instance_to_delete)

Use this when a harness or system build needs one item removed from the instances list.

Remove one instance from the instances list.

Args - instance_to_delete: Instance row dict (or any dict) whose instance_name key identifies the instance to remove. Matching is done by instance_name only.

instances_list.modify(instance_name, instance_data)

Use this when a harness or system build needs to change columns on an item already in the instances list.

Update columns for an existing instance by name.

Args - instance_name: The unique name of the instance to modify. - instance_data: Dict of column names to new values. Only provided keys are updated; others are unchanged.

Raises ValueError if no instance with instance_name exists.

instances_list.attribute_of(target_instance, attribute)

Use this when a harness or system build needs one column of one item in the instances list.

Return the value of one column for a single instance.

String values that look like Python literals (list or dict, e.g. starting with `[` or `{`) are parsed with `ast.literal_eval` and the parsed value is returned; otherwise the raw string is returned.

Args - target_instance: The instance_name of the instance to look up. - attribute: The column name to read (e.g. "mpn", "harness").

Returns The value of that column for the matching instance, or None if not found or attribute missing. List/dict-like strings are returned as list/dict.

instances_list.list_of_uniques(attribute)

Use this when a harness or system build needs the distinct non-empty values in one column of the instances list.

Return a list of unique non-empty values for one column across all instances.

Args - attribute: The column name to collect (e.g. "harness", "item_type").

Returns List of unique values; blanks and None are omitted. Order follows first occurrence in the instances list.

instances_list.instance_in_connector_group_with_item_type(connector_group, item_type)

Use this when a harness build needs the single connector, backshell, or other item type in one connector group.

Return the single instance in a connector group with the given item type.

Args - connector_group: The connector_group value to match. - item_type: The item_type value to match (e.g. connector, backshell).

Returns The matching instance row dict, or 0 if no match.

Raises ValueError if connector_group or item_type is blank, or if more than one instance matches.

instances_list.seed_connector_nodes()

Use this when a harness build needs a node instance for each connector group, with that group's connectors parented to the node.

Creates a node instance for each connector group and parents each connector to it.

Intended for harness build instructions after connectors exist (for example after importing a harness from a system). For each harness_connector or device_connector with a non-blank connector_group:

  • Ensures {connector_group}.node exists (item_type / location_type "node")
  • If the connector has no parent_csys_instance_name, sets it to that node with parent_csys_outputcsys_name "origin"

Connectors that already have a parent coordinate system are left unchanged.

instances_list.raise_error_on_unassigned_connectors(how_to_fix=None)

Use this when a harness build should stop because a harness connector still has no manufacturer part number.

Raise if any harness_connector instance has no mpn assigned.

Call this after connector assignment (parent connector_chooser and any local rules) and before library_utils.pull.

Args: - how_to_fix (str, optional): Extra sentence appended to the error. Defaults to guidance to edit the parent system's connector_chooser or assign mpn, lib_repo, and lib_subpath from the connector-choosing examples ribbon before pull.

Raises: - ValueError: If any harness connector has a blank mpn. The message lists each instance name and its device_side_connector_mpn.

instances_list.assign_bom_line_numbers()

Use this when a harness or system build needs sequential BOM line numbers on instances whose bom_line_number cell is "True".

Assign sequential BOM line numbers to instances that have bom_line_number set to "True".

Groups by MPN and assigns the same line number to all instances sharing an MPN. Requires every such instance to have a non-empty mpn. Line numbers are assigned in order of first occurrence of each MPN.

Raises ValueError if any instance marked for BOM has an empty mpn.

Library Import History

Interacting with Library History

A report of what was imported during the most recent build of the current project


Columns

Columns are automatically generated when library_history.new() is called. Additional columns are ok but discouraged due to consistency. If you need more columns, use data structures in build instructions.

Column Description
instance_name instance this import was recorded under; a macro uses its artifact id
mpn manufacturer part number that was pulled
item_type kind of part that was pulled, such as harness_connector, cable, backshell, or macro
lib_repo library URL, or "local"
lib_subpath path under that library to the part folder
lib_desc description from the library revision history
lib_latest_rev latest revision of this part in the library
lib_rev_used_here revision copied into this project
lib_status status of that library revision
lib_releaseticket release ticket on that library revision, when the library records one
lib_datestarted date this part was first added to the library
lib_datemodified date this part was last modified in the library
lib_datereleased date this library revision was released, when that field is set
lib_drawnby author recorded on the library revision
lib_checkedby checker recorded on the library revision
project_editable_lib_modified the editable copy in this project differs from the library snapshot

Commands

These functions are already available in build instructions as library_history. Use them as written.

library_history.read()

Use this when a harness or system build needs every row of the library-import history.

Return every row of the library-import history.

The file is fileio.path("library history"). One row per pull. run_macro refuses an artifact id that is already here unless rerun is true.

library_history.new()

Use this when the project build is starting the library-import history. Harness and system builds call this before the script and replace the file. A build script does not call it again.

Create an empty library-history file, replacing one that already exists.

library_history.append(instance_name, instance_data)

Use this when a harness or system build needs one library import recorded in the library history.

Record one library import in the library history.

Args: - instance_name: Name for this import. Raises if that name is already in the history. - instance_data: Dict of library-history columns (mpn, item_type, lib_repo, lib_subpath, and the lib_* metadata). instance_name in the dict is replaced by the instance_name argument. Missing columns are written blank.

Channel Map

Interacting with Channel Maps

A list of channels on devices within groups of connected harnesses. Each completed pairing is a mapped channel; remaining rows are unmapped.


Columns

Columns are automatically generated when channel_map.new() is called. Additional columns are ok but discouraged due to consistency. If you need more columns, use data structures in build instructions.

Column Description
group_of_connected_harnesses harnesses linked through passthroughs (or a single standalone harness)
from_device_refdes device instance refdes of this channel endpoint (seeded by new())
from_device_channel_id channel id on that device (from its signals list)
from_repeat_channel_id required when that channel has two endpoints; blank otherwise
from_channel_type channel type of the from endpoint (from the signals list)
to_device_refdes device instance refdes of the mapped partner; blank until mapped
to_device_channel_id channel id of the mapped partner; blank until mapped
to_repeat_channel_id required when that channel has two endpoints; blank otherwise
to_channel_type channel type of the mapped partner; blank until mapped
multi_ch_junction_id set by map(..., multi_ch_junction_key=...); shared id for a multi-channel junction net
chain_of_connectors filled by passthrough_map.resolve(); semicolon-separated refdes.connector path (e.g. MIC3.J1;X1.A;X1.B;PREAMP1.J1)
chain_of_harnesses filled by passthrough_map.resolve(); semicolon-separated harness refdes hops (e.g. H1;H2)
manual_map_channel_python_equiv channel_map.map(...) call that produced this pairing; copy back into build instructions

Commands

These functions are already available in build instructions as channel_map. Use them as written.

channel_map.read()

Use this when a system build needs every row of the channel map in the revision that is open.

Return the channel map (cached).

channel_map.new()

Use this when a system build is starting a blank channel map. system.build() calls this before the script and replaces the file. A system script does not call it again.

Makes a new blank channel map. Overwrites existing channel map.

Seeds one row per channel endpoint, including both faces of a passthrough. Auto-map skips those multi-endpoint rows; map() uses a 3-tuple to name a face.

Args: none

Returns: none

channel_map.map(from_key, to_key=None, multi_ch_junction_key='')

Use this when a system build needs one channel endpoint patched to another, or landed on a multi-channel junction, and the channel types are compatible.

Map one channel endpoint to another (a patch), or consume it onto a multi-channel junction.

Each key is (device_refdes, channel_id) when that channel has one endpoint, or (device_refdes, channel_id, repeat_channel_id) when it has two. A 3-tuple on a single-instance channel is an error; omitting the repeat on a two-instance channel is an error.

Dual-map is two calls to the two repeats of the same passthrough. That consumes both faces as patch ends and does not write a passthrough-map row.

Both endpoints must belong to the same group_of_connected_harnesses. Unmated terminating connectors are in no group. An unmated passthrough face still belongs to the group of its other, mated face.

A patch may end by crossing a device only if the destination face has no harness. If that face has a harness, the last hop must be that harness.

Pass multi_ch_junction_key="jcn1" instead of to_key to consume from_key and write jcn1 as multi_ch_junction_id. Several rows can share that id (connecting those channels to each other) without pairing to a single partner. A channel may join only if its type is, or is compatible with, every other member already on that junction.

Args: - from_key: Endpoint to map. (device_refdes, channel_id) when the channel has one endpoint, or (device_refdes, channel_id, repeat_channel_id) when it has two. A 3-tuple on a single-endpoint channel is an error, and omitting the repeat on a two-endpoint channel is an error. - to_key: Other endpoint of the patch, in the same tuple shape as from_key. Both ends must belong to the same group of connected harnesses. Optional. Omit it, or pass None, when you pass multi_ch_junction_key instead. - multi_ch_junction_key: Junction id, such as "jcn1", that consumes from_key. Every channel that shares that id is connected, without pairing to one partner. The channel type must be compatible with members already on the junction. Do not pass this together with to_key. Optional.

channel_map.force(from_key, to_key)

Use this when a system build needs one channel endpoint patched to another even though the channel types do not match. Pair the signals with circuits_list.force before circuits_list.new.

Map one channel endpoint to another regardless of channel-type compatibility.

Same key, group, and face rules as map(). A 1:1 patch only: there is no multi_ch_junction_key. map() is unchanged and does not record a pending patch.

Signal names still pair by name when both channel types list the same set, and circuits_list.new() compiles those circuits as usual. When the sets differ, the patch is recorded as pending. Call circuits_list.force() with a from-signal → to-signal dict, and drop every to-side signal you are leaving unwired, before circuits_list.new(). A pending patch still waiting at circuit compile raises ValueError.

The circuit's from_signal is the from-side name. A from-side signal cannot be left unwired. If the from channel has a name the to channel does not, swap the endpoints so that extra name is on the to side, then drop it.

channel_map.flatten()

Use this when a system build needs the flattened channel map of this system and the subsystems it contains.

Write maps/channel_map_flattened.tsv for this system and every child subsystem.

Reads this system's channel map, then the channel map imported with each child subsystem, recursively. An imported subsystem channel map is the source of truth for which endpoints were mapped. This does not re-run channel mapping inside the subsystem.

A passthrough channel whose two faces are both mapped patch ends is folded into one row between the outer endpoints before subsystem prefixes are applied. The lexicographically smaller outer endpoint (refdes, channel id, repeat) is the from side. chain_of_connectors records the disconnect between them (SNK-0.J1;X-6.B;X-6.A;SNK-3.J1). chain_of_harnesses on that row is left blank. A chain of dual-mapped disconnects folds to the two outer devices, with every disconnect in the connector chain. A channel with only one face mapped stays as that one patch. A row that already joins the two faces is left unchanged. Rows with no partner and no multi-channel junction are omitted. A junction row is kept. The flat channel map is not rewritten.

Rows from a subsystem prefix that subsystem's instance name onto group_of_connected_harnesses, from_device_refdes, to_device_refdes, multi_ch_junction_id, chain_of_connectors, and chain_of_harnesses. A list is prefixed per entry (AMP.H1+AMP.H2, AMP.H1;AMP.H2, AMP.MIC3.J1;AMP.X1.A). Nested subsystems accumulate prefixes (AMP.PRE.MIC1). from_repeat_channel_id, to_repeat_channel_id, and manual_map_channel_python_equiv are omitted. from_channel_type_description and to_channel_type_description sit beside the channel type columns and are the description from that type's channel_types.tsv. Channel ids and channel types are copied unchanged. This system's own rows are not prefixed.

Call after channel mapping, including map_unmapped_compatibles_alphabetically(). The sheet snapshots the channel map as it stands at the call.

Args: none

Returns: the written rows

channel_map.already_mapped(key)

Use this when a system build needs to know whether one channel endpoint is already consumed by a map or a route.

Return whether an endpoint is already in the mapped-channels set.

Args: - key: Endpoint key in the same shape map uses: (device_refdes, channel_id) or (device_refdes, channel_id, repeat_channel_id).

channel_map.already_mapped_set()

Use this when a system build needs every channel endpoint already consumed by map or route.

Return the set of endpoint keys already consumed by map or route.

channel_map.already_mapped_set_append(key)

Use this when a system build needs to mark one channel endpoint as already consumed.

Mark one endpoint as already mapped.

Args: - key: Endpoint key in the same shape map uses: (device_refdes, channel_id) or (device_refdes, channel_id, repeat_channel_id). Raises if that key is already in the mapped set.

Circuits List

Interacting with Circuits Lists

A list of every individual electrical connection that must be present in your system or harness to satisfy your channel and passthrough maps.


Columns

Columns are automatically generated when circuits_list.new() is called. Additional columns are ok but discouraged due to consistency. If you need more columns, use data structures in build instructions.

Column Description
harness harness refdes that carries this hop, taken from chain_of_harnesses
harness_chain_index 0-based index into channel map chain_of_harnesses for this segment
circuit_id sequential id of this row, one per signal per harness hop, starting at 0
from_signal from-side signal name; also the name used on passthrough hops
to_signal to-side signal name when circuits_list.force() paired different names; blank when both ends use from_signal
harness_from_refdes refdes at the from end of this harness hop
harness_from_channel_id channel id on that from-end refdes
harness_from_connector_name connector on that refdes for this signal
harness_from_cavity cavity of this signal on that connector
harness_to_refdes refdes at the to end of this hop; a junction id when the row ends on a multi-channel junction
harness_to_channel_id channel id on that to-end refdes; the signal name on a junction row
harness_to_connector_name connector on the to-end refdes; blank on a junction row
harness_to_cavity cavity of this signal on that connector; blank on a junction row
from_side_device_refdes channel-map from device; the same on every hop of the mapped channel
from_side_device_chname channel id on that from device
to_side_device_refdes channel-map to device; a junction id when the row ends on a multi-channel junction
to_side_device_chname channel id on that to device; the signal name on a junction row
from_channel_type channel type of the channel-map from endpoint
to_channel_type channel type of the channel-map to endpoint, or the junction's channel type
multi_ch_junction_id set when the circuit is from_channel.signal → mcj.signal

Commands

These functions are already available in build instructions as circuits_list. Use them as written.

circuits_list.read()

Use this when a system build needs every row of the circuits list in the revision that is open.

Return the circuits list (cached).

circuits_list.force(from_key, signal_pairs, drop=())

Use this when a system build has a pending channel_map.force patch and needs its from-signals paired to to-signals before circuits_list.new.

Pair the signals of one pending channel_map.force() patch.

from_key is the same endpoint key passed as the first argument to channel_map.force(). signal_pairs maps every from-side signal name to a to-side signal name ({"pos": "live", "neg": "neutral"}). drop lists every to-side signal left unwired (drop=["earth"]).

The circuit keeps the from-side name. The to-side cavity is looked up under the paired name. A from-side signal cannot be dropped: if the from channel has a signal the to channel does not, swap which endpoint is from_key so the extra name is on the to side, then drop it.

Raises ValueError if from_key is not pending (the signal names already matched, or channel_map.force() was not called), or if any from-side name is unpaired or any to-side name is neither paired nor dropped.

circuits_list.new()

Use this when a system build needs the circuits list compiled from the channel map and the passthrough map.

Build the circuits list from the channel map and passthrough map.

Uses chain_of_connectors / chain_of_harnesses (from passthrough_map.resolve()) and ordered satisfaction rows on the passthrough map for passthrough channel IDs.

A channel-map row with multi_ch_junction_id and no to_* partner becomes one circuit per signal: from is from_channel.signal, to is mcj.signal. Signal names come from the first channel type seen on that junction id.

A patch from channel_map.force() whose signal names differ must already have been burned down by circuits_list.force(). Those circuits keep the from-side name in from_signal and look up the to-side cavity under to_signal. Passthrough hops are still looked up under from_signal. Any pending forced patch raises ValueError.

Clears any existing circuits list.

Passthrough Map

Interacting with Passthrough Maps

Every passthrough channel in the system. Call new() to build the map and routing context, optionally route() required hops, then resolve() to fill channel-map chains.


Columns

Columns are automatically generated when passthrough_map.new() is called. Additional columns are ok but discouraged due to consistency. If you need more columns, use data structures in build instructions.

Column Description
from_channel_type channel-map from type
from-passthrough_channel_type passthrough face on the same side as from
to-passthrough_channel_type passthrough face on the same side as to
to_channel_type channel-map to type
requirement user route() step number for this passthrough-channel mapping
satisfaction resolve() route() step number (always >= requirement when both set)
is_imaginary TRUE if this mapping was minted because no real passthrough channel was available
has_imaginary TRUE if this passthrough device has any imaginary channel mappings
manual_map_passthrough_python_equiv passthrough_map.route() that would form this requirement

Commands

These functions are already available in build instructions as passthrough_map. Use them as written.

passthrough_map.read()

Use this when a system build needs every row of the passthrough map in the revision that is open.

Return the passthrough map (cached).

passthrough_map.new()

Use this when a system build needs passthrough rows created from the signals lists of the devices on the block diagram.

Create passthrough channel rows from placed-device signals lists.

Rebuilds system.passthrough_routing_context as one row per unique (from_channel_type, to_channel_type) pair within each group_of_connected_harnesses, per passthrough device, per directionality (1 / -1, or 0 if reversible), with an availables count of passthrough channels that obey that directionality. Does not route channels or fill channel-map chains (see resolve()).

passthrough_map.route(to_from_channel_device_refdes, to_from_channel_id, intended_device_refdes, intended_channel_id=None, repeat_channel_id=None, satisfaction=False)

Use this when a system build needs one mapped channel sent through one passthrough device.

Route a mapped channel through a passthrough device.

Looks up the mapped channel's from→to types and group_of_connected_harnesses, finds the matching routing-context row(s), picks a free passthrough channel that obeys directionality, records the mapping on the passthrough map, and decrements availables on the matching context row (never below 0).

repeat_channel_id is the face the from-side arrives on. Omit it to infer.

The passthrough device must belong to the same group_of_connected_harnesses as the mapped channel.

By default writes requirement (user route). With satisfaction=True (used by resolve), writes satisfaction instead.

Returns 0 on a real assignment, -1 if an imaginary passthrough-map row was appended because availables was already 0.

Args: - to_from_channel_device_refdes: Device refdes of the mapped channel you are routing. - to_from_channel_id: Channel id on that device. - intended_device_refdes: Passthrough device to route through. It must belong to the same group of connected harnesses as the mapped channel. - intended_channel_id: Specific passthrough channel to use. Optional. Omit it to let route pick a free channel that matches directionality. - repeat_channel_id: Face the from-side arrives on. Optional. Omit it to infer the face. - satisfaction: When True, write a satisfaction row (what resolve does). When False, write a requirement row. Optional.

passthrough_map.resolve()

Use this when a system build needs every mapped channel routed through its passthroughs, in channel-map order.

Complete passthrough routing for each mapped channel (channel-map order).

Uses the routing context (filtered to group_of_connected_harnesses + from→to pair) to find the fewest passthrough channels that connect from_harness → optional ordered requirement waypoints → to_harness. Prefers paths with remaining availables; if none exist, allows availables == 0 (imaginaries).

Gap fills call route(..., satisfaction=True). Existing requirement rows get a satisfaction stamp. Writes chain_of_connectors and chain_of_harnesses on each mapped channel-map row from the resulting path.

Stamps is_imaginary / has_imaginary on the passthrough map. Prints one warning per passthrough that needed imaginary channels to satisfy the map.

No-op when passthrough_map.new() found no harness topology (empty parent diagram / no harness-mated connectors): there is nothing to resolve.

Netlist

Interacting with Netlists

Post-Harness Instances List

Interacting with Post Harness Instances Lists

A list of every physical or notional thing, drawing element, or concept that includes instances added at the harness level, that represents a system


Commands

These functions are already available in build instructions as post_harness_instances_list. Use them as written.

post_harness_instances_list.rebuild()

Use this when a system build needs the post-harness instances list rebuilt from the harnesses that were pushed and from system instances those harnesses do not cover.

Build the 'post harness instances list' by merging instance data from: - Every instances.tsv under the system harnesses/ directory - The system-level instances list for harnesses not covered by those files, and for instances with no harness

Writes a clean TSV with INSTANCES_LIST_COLUMNS (plus any extra columns found).

post_harness_instances_list.push(path_to_system_rev, system_pn_rev)

Use this when a harness build needs its instances list copied into a system revision's harnesses folder.

Copy this harness instances list into a system revision's harnesses folder.

Args: - path_to_system_rev: Directory of the system revision. The instances list is copied to {path_to_system_rev}/harnesses/instances.tsv. - system_pn_rev: System part-and-revision label, such as "mypart-rev1". The file is copied using path_to_system_rev; pass the label of the system revision that directory belongs to.

Signals List

Interacting with Signals Lists

Signals Lists define every signal going into or out of a device or disconnect. They are the primary way Harnice stores information about devices.

The signals list those instructions produce

Signals lists are usually repetative, long, and deterministic. Harnice helps you build signals lists efficiently using build instructuons, which are python rules that build the lists for you.

Build instructions that write a device signals list


Columns

Columns are automatically generated when signals_list.new() is called. Additional columns are ok but discouraged due to consistency. If you need more columns, use data structures in build instructions.

Column Description
channel_id the channel this row belongs to; the same id on every signal of that channel
repeat_channel_id which endpoint, when the channel is a passthrough with two ends; the two repeats must differ; blank when the channel has one endpoint
channel_type channel type string; every signal name it declares must be appended for this endpoint, and the string must parse as a channel type
signal signal name; one of the names declared by channel_type
cavity pin, socket, or stud this signal uses on its connector; required, and unique on that connector
connector_id the connector that holds this signal; every signal of one endpoint uses the same connector
device_connector_mpn manufacturer part number of the device-side connector; set this or harness_connector_mpn, not both, and use the same choice for every row of that connector
harness_connector_mpn manufacturer part number of the harness-side connector; set this or device_connector_mpn, not both

Signals List Validation Checks:

(These are automatically validated when you build the device that owns the list.)

General Signals List Rules

  • Every signal in the Signals List must be contained by a pre-defined channel type

    Channel Types

    Channel Types


    How are channels mapped?


    How to define a new channel type

    1. In a repository of your choice (or start with harnice-general-library on your own branch), navigate to channel_types.tsv at the library root
    2. If you want channel definitions to be private and are therefore working in a private repository, ensure the repo's path is listed in file repository_locations.csv (located at root of your harnice source code repo). The first column is the URL or traceable path, and the second column is your local path.
    3. If you find the channel_type you're looking for, temporarily note it as a touple in a notepad somewhere with format (ch_type_id, universal_library_repository).
    4. If you don't find it, make a new one. It's important to try and reduce the number of channel_types in here to reduce complexity, but it's also important that you adhere to strict and true rules about what is allowed to be mapped to what. Modifications and additions to this document should be taken and reviewed very seriously.

    Two optional columns set the default appearance. Both cells are Python dict literals, parsed with ast.literal_eval. Leave a cell blank to leave that default unset. Harnice reads the channel_types.tsv at the root of the library named by the channel type's lib_repo.

    • channel_default_appearance — one engineering appearance dict. A channel trace (channel or harness-channel) and the channel card in the connectors view use it. Example: {'base_color': '#D59A10'}.
    • signal_default_appearances — signal name to appearance dict. A circuit trace uses the entry for its signal_of_channel_type, and the connectors view uses that entry for the signal bar. Names match the signals column. Example: {'pos': {'base_color': '#E24B4A'}, 'neg': {'base_color': '#2E6B9F'}}.

    An instance appearance that already includes base_color is the appearance drawn for that trace. Traces are drawn when the project is built, so a change to these columns shows up on overlays after the next build. The connectors view reads the columns when the page opens. A trace whose resolved appearance has no base_color is the rainbow placeholder. A card or bar with no base_color keeps the console palette.

    The dict is the engineering appearance from the Appearance guide: base_color, and optionally outline_color, parallelstripe, perpstripe, and twisted. Single quotes match the rows already in the file. The separator is a tab, so a comma inside the literal stays in the cell.

    chtype.parse(val)

    Use this when a build has a channel type as stored text and needs the (channel_type_id, lib_repo) pair.

    Convert stored string into a tuple (chid:int, lib_repo:str). Handles both single tuples and extracts first tuple from lists.

    chtype.signals(channel_type)

    Use this when a system or device build needs the signal names declared for one channel type.

    Return the list of signal names associated with a specific channel type.

    Args

    • channel_type: Channel type identifier in standard tuple format (channel_type_id, lib_repo) or any string representation that parse can understand.

    Returns

    • list[str]: List of signal names from the signals column of channel_types.tsv for the matching channel_type_id. If the column is blank or the channel type cannot be found, returns an empty list.

    Data format

    • The signals column is expected to be a comma-separated string, for example: "CAN_H, CAN_L, SHIELD".
    chtype.channel_default_appearance(channel_type)

    Use this when a drawing needs the default appearance of one channel type's trace and card.

    Optional default appearance for this channel type's channel trace and card.

    Reads channel_default_appearance from the channel_types.tsv at the root of the library named by this channel type's lib_repo. The cell is a Python dict literal, parsed with ast.literal_eval (see appearance.parse). Example: {'base_color': '#D59A10'}. A blank cell returns None.

    A channel trace (item_type channel or harness-channel) and the channel card in the connectors view use this appearance. A circuit trace uses signal_default_appearances for its signal_of_channel_type instead.

    An instance appearance that already includes base_color is the appearance drawn for that trace. Traces are drawn when the project is built. The connectors view reads this column when the page opens. A trace whose resolved appearance has no base_color is the rainbow placeholder. A channel card with no base_color keeps the console palette.

    The dict is an engineering appearance: base_color plus any of outline_color, parallelstripe, perpstripe, and twisted.

    Args

    • channel_type: Channel type identifier in standard tuple format (channel_type_id, lib_repo) or any string representation that parse can understand.

    Returns

    • dict or None: Parsed appearance, or None when the cell is empty or the channel type cannot be found.
    chtype.signal_default_appearances(channel_type)

    Use this when a drawing needs the default appearance of each signal on one channel type.

    Optional default appearance for each signal of this channel type.

    Reads signal_default_appearances from the channel_types.tsv at the root of the library named by this channel type's lib_repo. The cell is a Python dict literal, parsed with ast.literal_eval: signal name to appearance dict. Example: {'pos': {'base_color': '#E24B4A'}, 'neg': {'base_color': '#2E6B9F'}}. A blank cell returns {}. Names that are not themselves dicts are dropped. Signal names match the signals column.

    A circuit trace uses the entry for its signal_of_channel_type. The connectors view uses the same entry for that signal's bar. A channel trace uses channel_default_appearance.

    An instance appearance that already includes base_color is the appearance drawn for that trace. Traces are drawn when the project is built. The connectors view reads this column when the page opens. A trace whose resolved appearance has no base_color is the rainbow placeholder. A signal bar with no base_color keeps the console palette.

    Each value is an engineering appearance: base_color plus any of outline_color, parallelstripe, perpstripe, and twisted.

    Args

    • channel_type: Channel type identifier in standard tuple format (channel_type_id, lib_repo) or any string representation that parse can understand.

    Returns

    • dict: Signal name to appearance dict. Empty when unset or when the channel type cannot be found.
    chtype.compatibles(channel_type)

    Use this when a system build needs the channel types declared compatible with one channel type, not including that type itself.

    Look up other channel types that are declared as compatible with the given channel type.

    Args

    • channel_type: Channel type identifier in standard tuple format (channel_type_id, lib_repo) or any string representation that parse can understand.

    Returns

    • list[tuple[int, str]]: List of (channel_type_id, lib_repo) tuples taken directly from the compatible_channel_types column of channel_types.tsv. Returns an empty list if no compatibles are defined or if the channel type cannot be found.

    Data format

    • The compatible_channel_types column must be an AST-parseable Python value:
      • Single tuple: (1, "library_repo")
      • List of tuples: [(1, "library_repo"), (2, "library_repo")]
    chtype.is_or_is_compatible_with(channel_type)

    Use this when a system build is validating or mapping channels and this channel type should count if it is the requested type or listed as compatible with it.

    Return the given channel type plus all channel types declared as compatible with it.

    Args

    • channel_type: Channel type identifier in standard tuple format (channel_type_id, lib_repo) or any string representation that parse can understand.

    Returns

    • list[tuple[int, str]]: List of (channel_type_id, lib_repo) tuples where the first entry is the parsed channel_type itself and the remaining entries are the compatibles returned by compatibles(channel_type).

    Typical use

    • Use this when validating or mapping channels and you want to treat a channel type as valid if it is either exactly the requested type or explicitly listed as compatible with it.
    chtype.attribute(channel_type, attribute)

    Use this when a system or device build needs an extra column from channel_types.tsv for one channel type.

    Read any additional column from channel_types.tsv for a given channel type.

    Args

    • channel_type: Channel type identifier in standard tuple format (channel_type_id, lib_repo) or any string representation that parse can understand.
    • attribute: Column header name in channel_types.tsv for the value you want to read (for example "description", "notes", "voltage_rating").

    Returns

    • Any: Value stored in the requested attribute column for the matching channel_type_id. Returns an empty list [] if the channel type cannot be found.

    Notes

    • Reads <library_root>/channel_types.tsv (same file as path()).
    • Use this for any per-channel-type metadata you've added as extra columns beyond the core ones like channel_type_id, signals, and compatible_channel_types.
    chtype.path(channel_type)

    Use this when a build needs the path of channel_types.tsv for one channel type's library checkout.

    Resolve the on-disk path to the channel_types.tsv file for a given channel type.

    Args

    • channel_type: Channel type identifier in standard tuple format (channel_type_id, lib_repo) or any string representation that parse can understand (for example "(5, 'https://github.com/harnice/harnice-general-library')").

    Returns

    • str: Absolute path to channel_types.tsv at the root of the library repository that owns the given channel type.

    Notes

    • This does not filter rows; it only locates the TSV file that defines all channel types for the given lib_repo.
  • Each signal in the signals list must have every other signal defined by its channel type also present in the list.

    • you can't just define 'positive' if the channel type requires 'positive' and 'negative'
  • Each signal defined in the list is contained by one or more cavities of connectors.

    • you can't "cap off" or not populate one of the signals within a channel because that changes the channel type.
  • Every combination of (channel_id, signal, repeat_channel_id) must be unique within the signals list

    • you can’t have two i.e. “ch1, pos” signals on the same endpoint
    • if you need to break one signal out onto multiple conductors, you'll need to change the channel type to one that defines multiple conductors (i.e. named "ch1, pos-1")
  • Each channel endpoint lives on exactly one connector_id

    • a passthrough is the same channel_id on two connectors, each with its own repeat_channel_id
    • signals that belong to one endpoint cannot be split across connectors
    • more than two endpoints on one channel_id is an error

Configurable Device Signals List Rules

A configuration is how a device is used, not what it is. The same part number can have different electrical behavior (balanced vs unbalanced, voltage setting, phantom on/off) without a new part number, as long as form, fit, and function of the hardware stay the same.

The signals list is not a pile of alternate rows that get filtered. Build instructions write one signals list for the answers currently in configuration (a dict). Harnice fills that dict from the defaults in fileio.path("configuration setup"), or—when the device is placed in a system—from that instance's answers on the block diagram / BOM.

  • Declare fields in fileio.path("configuration setup"), then read them in build instructions

    • Each field has an id. In the script, use configuration["that_id"] (channel type, select, boolean, number, and so on).
    • You can have as many fields as you need. One field is enough for an SM58 that is either balanced or unbalanced. A mixing console might have a field per input (mic vs line, balanced vs unbalanced) so automatic channel mapping sees the type you actually set, and the instance records how to set the hardware up.
    • Field types, validation, show_if, and a full YAML example are in the device configurations documentation.
  • The list you emit still has to obey the general signals-list rules

    • Complete endpoints, unique (channel_id, signal, repeat_channel_id), one connector per endpoint. Validation only sees the list for this configuration.
  • Do not use configuration to change the physical part

    • If the connector or the hardware build changes, that is a new part number, not a configuration.
  • Unused pins stay off the list
    • A pin is on the signals list only when a channel in this configuration uses it. Omitting a pin this configuration does not use is correct. A channel that is present still includes every signal its channel type defines.

Passthrough Signals List Rules

  • A passthrough is exactly two endpoints of the same channel_id, each with a distinct repeat_channel_id
  • The two channel types must be compatible with each other
    • this is so a patch that crosses the device still mates compatible types on both faces
  • A single-endpoint channel must leave repeat_channel_id blank; a 3-tuple in map() on that channel is an error


Commands

These functions are already available in build instructions as signals_list. Use them as written.

signals_list.read(path_to_signals_list=None)

Use this when a device or system build needs the rows of a signals list: a path, a file key, or the signals list of the revision that is open.

Return signals-list rows from a path, file key, or the current project list.

signals_list.new()

Use this when a device build is starting the signals list and should replace the file that is already there.

Create a signals TSV at fileio.path("signals list") with only the header row.

signals_list.append(**kwargs)

Use this when a device build needs one signal added to the signals list.

Append one signal row. Missing optional fields are written as empty strings.

Required: channel_id, signal, cavity, connector_id, channel_type, and exactly one of device_connector_mpn or harness_connector_mpn.

Args: - channel_id: Channel this row belongs to. Use the same id on every signal of that channel. - channel_type: Channel type string. Every signal name it declares must be appended for this endpoint, and the string must parse as a channel type. - signal: Signal name. It must be one of the names declared by channel_type. - cavity: Pin, socket, or stud this signal uses on its connector. Required, and unique on that connector. - connector_id: Connector that holds this signal. Every signal of one endpoint must use the same connector. - repeat_channel_id: Which endpoint, when the channel is a passthrough with two ends. The two repeats must differ. Leave blank when the channel has one endpoint. Optional. - device_connector_mpn: Manufacturer part number of the device-side connector. Set this or harness_connector_mpn, not both, and use the same choice for every row of that connector. - harness_connector_mpn: Manufacturer part number of the harness-side connector. Set this or device_connector_mpn, not both.

signals_list.cavity_of_signal(channel_id, signal, path_to_signals_list, repeat_channel_id='')

Use this when a build needs the cavity one signal uses on a channel endpoint.

Return the cavity a signal uses on one channel endpoint.

Args: - channel_id: Channel id of the signal whose cavity you want. - signal: Signal name on that channel, such as pos or neg. - path_to_signals_list: Path, or file key, of the signals list to read. - repeat_channel_id: Which endpoint when the channel has two. Leave blank to match a single-endpoint channel. Optional.

signals_list.connector_id_of_channel(channel_id, path_to_signals_list, repeat_channel_id='')

Use this when a build needs the connector id of one channel in a signals list.

Return connector_id for a channel endpoint.

Args: - channel_id: Channel id whose connector you want. - path_to_signals_list: Path, or file key, of the signals list to read. Raises if that file does not exist. - repeat_channel_id: Which endpoint when the channel has two. Leave blank to match a single-endpoint channel. Optional.



Building a system

  1. Harnice runs the build instructions if it's found in the system directory.

  2. The system is validated and verified.

  3. The external ICD is compiled from external-interface flags (after build instructions).

  4. The system block diagram SVG is updated from placed-instance symbol files under instance_data/ (so device/subsystem symbol edits are picked up), including derived channel/circuit trace overlays based on the built system.

  5. PDF drawings: call run_macro("pdf_generator", …, page_setup=…) in build instructions after other macros that write masters.

The subsystem_symbol macro runs in build instructions, so it reads the ICD from the previous build and runs device signals-list / symbol-pin verification against it. After ICD changes, build twice. That lag is expected.

The first build writes these instructions when build.py does not exist yet.

Default system build instructions
# ===========================================================================
#                CHANNEL AND PASSTHROUGH MAPPING
# ===========================================================================
# system.build() already called channel_map.new() - one row per channel endpoint.
#
# A patch is two endpoints. Each key is:
#   (device_refdes, channel_id)                        # one endpoint
#   (device_refdes, channel_id, repeat_channel_id)     # two endpoints (a face)
# 3-tuple on a single-instance channel is an error; omitting the repeat on a
# two-instance channel is an error.
# Both endpoints (and any route() hop) must be in the same group of connected
# harnesses. Unmated terminating connectors are in no group.
#
# channel_map.map(from_key, to_key=None, multi_ch_junction_key="")
#
# endpoint -> endpoint:
# channel_map.map(("MIC1", "out1"), ("PRE1", "in1"))
#
# map onto one passthrough face. That face must have no harness, or the
# last hop must be that harness that your channel is routed through
# channel_map.map(("MIC1", "out1"), ("X1", "ch0", "A"))
#
# dual-map: two map() calls to the two faces of the same passthrough.
# consumes both as patch ends; does not write a passthrough-map row.
# channel_map.map(("MIC1", "out1"), ("X1", "ch0", "A"))
# channel_map.map(("SNK1", "in1"), ("X1", "ch0", "B"))
#
# map a channel endpoint to a shared multi_ch_junction_key
# channel_map.map(("MIC1", "out1"), multi_ch_junction_key="jcn1")

# force a 1:1 patch regardless of channel-type compatibility.
# Same signal names compile by name (pos still meets pos).
# Different signal names stay pending until circuits_list.force()
# pairs every from-side signal and drops every unused to-side signal.
# The circuit keeps the from-side name. A from-side signal cannot be dropped;
# swap the endpoints so the extra name is on the to side, then drop it.
# channel_map.force(("MIC1", "out1"), ("PWR1", "in1"))
# circuits_list.force(
#     ("MIC1", "out1"),
#     {"pos": "live", "neg": "neutral"},
#     drop=["earth"],
# )

# remaining single-endpoint channels in the same group of connected harnesses,
# compatible types, alphabetical. Skips every channel with more than one endpoint.
channel_map.map_unmapped_compatibles_alphabetically()

# this system's channel map, plus the channel map imported with every child
# subsystem (recursive). Unmapped rows are omitted. A passthrough with both
# faces mapped as patch ends becomes one row between the outer devices.
# Inside a subsystem, refdes,
# harness groups, junction ids, and connector/harness chains are prefixed:
# subsystem.name, or subsystem.subsystem.name. Channel ids are not.
channel_map.flatten()

# one row per passthrough channel, plus routing context (availables per
# group / from->to type / directionality). Dual-mapped passthroughs are omitted.
passthrough_map.new()

# add manual hops here (after new, before resolve).
# passthrough_map.route(
#     to_from_channel_device_refdes,  # either end of an already-mapped patch
#     to_from_channel_id,
#     intended_device_refdes,         # the passthrough device
#     intended_channel_id=None,       # omit to pick any available channel
#     repeat_channel_id=None,         # face the from-side arrives on; omit to infer
# )
# passthrough_map.route("MIC1", "out1", "X1")
# passthrough_map.route(
#     "MIC1", "out1", "X1", intended_channel_id="ch0", repeat_channel_id="A"
# )

# shortest remaining path; writes chain_of_connectors / chain_of_harnesses
passthrough_map.resolve()

# expand every mapped patch (and its passthrough path) into conductor circuits.
# multi-ch junction rows become from_channel.signal -> mcj.signal (first type's signals)
circuits_list.new()

# ===========================================================================
#        SEED THE INSTANCES LIST FROM CIRCUITS THAT EXIST SO FAR
# ===========================================================================
system_utils.create_instances_from_circuits_list()


# ===========================================================================
#                 RUN SYSTEM DESIGN CHECKS
# ===========================================================================
connector_list = fileio.read_tsv("system connector list")
circuits = circuits_list.read()

# check for circuits with no connectors
system_utils.find_connector_with_no_circuit(connector_list, circuits)

# TODO: HARNESS VALIDATION (imported harnesses contain conductors that match the required circuits)

# ===========================================================================
#              ADD APPEARANCES TO CHANNEL TYPES AND CIRCUITS
# ===========================================================================
# Channel traces use channel_default_appearance from the channel type's
# channel_types.tsv. Circuit traces use signal_default_appearances for
# signal_of_channel_type. The maps below write an instance appearance, and
# an instance appearance that includes base_color replaces that default.
# color -> channel types that should use that color
channel_type_colors = {
    "#D59A10": [
        (1, "https://github.com/harnice/harnice-general-library"),
        (2, "https://github.com/harnice/harnice-general-library"),
    ],
    "#4039A1": [
        (5, "https://github.com/harnice/harnice-general-library"),
    ],
}

# channel type -> signal -> color
circuit_signal_colors = {
    (1, "https://github.com/harnice/harnice-general-library"): {
        "pos": "#E24B4A",
        "neg": "#2E6B9F",
    },
    (2, "https://github.com/harnice/harnice-general-library"): {
        "pos": "#E24B4A",
        "neg": "#2E6B9F",
    },
    (5, "https://github.com/harnice/harnice-general-library"): {
        "chassis": "#4039A1",
    },
}

# apply the above appearances in the instances list as a simple base-color.
for instance in instances_list.read():
    color = None
    channel_type = instance.get("this_channel_from_channel_type")
    if instance.get("item_type") == "circuit":
        for mapped_channel_type, signals in circuit_signal_colors.items():
            if channel_type == str(mapped_channel_type):
                color = signals.get(instance.get("signal_of_channel_type"))
                break
    if not color:
        for mapped_color, channel_types in channel_type_colors.items():
            if channel_type in [str(t) for t in channel_types]:
                color = mapped_color
                break
    if color:
        instances_list.modify(
            instance.get("instance_name"),
            {"appearance": {"base_color": color}},
        )

# If you need more specific appearances, you can assign a full appearance dict to an instance like this:
# {
#     "base_color": "#E24B4A",          # required fill
#     "outline_color": "black",         # optional path outline
#     "parallelstripe": ["white"],      # optional stripes along the path
#     "perpstripe": ["red", "white"],   # optional stripes across the path
#     "twisted": "RH",                  # optional "RH", "LH", or None
# }
# example: twisted red/white pos conductor for channel type 1
# circuit_signal_colors[(1, "https://github.com/harnice/harnice-general-library")]["pos"] = {
#     "base_color": "#E24B4A",
#     "outline_color": "black",
#     "parallelstripe": ["white"],
#     "twisted": "RH",
# }
# instances_list.modify(
#     instance.get("instance_name"),
#     {"appearance": circuit_signal_colors[(1, "https://github.com/harnice/harnice-general-library")]["pos"]},
# )

# ===========================================================================
#              RECOMMEND CABLES ON CIRCUITS
# ===========================================================================
# Write rules at the system level based on system metrics to suggest information that is later consumed by
# downstream harness projects about what cables to apply onto what conductors.

# this_channel_from_channel_type -> library cable fields for instances_list.modify
circuit_cable_mpns = {
    (1, "https://github.com/harnice/harnice-general-library"): {
        "mpn": "M27500-22SB2T23",
        "lib_repo": "https://github.com/harnice/harnice-aerospace-library",
        "lib_subpath": "M27500",
    },
    (2, "https://github.com/harnice/harnice-general-library"): {
        "mpn": "M27500-22SB2T23",
        "lib_repo": "https://github.com/harnice/harnice-aerospace-library",
        "lib_subpath": "M27500",
    },
}

# signal_of_channel_type -> cable identifier (must be unique in the cable JSON)
circuit_cable_identifiers = {
    "pos": "black",
    "neg": "white",
    "chassis": "drain_wire",
}

# modify the instances list per the above
cable_counter = 1
channel_to_cable = {}  # keep every channel inside its own sequentially-counted cable
for instance in instances_list.read():
    if instance.get("item_type") != "circuit":
        continue

    channel_type = chtype.parse(instance.get("this_channel_from_channel_type"))
    if channel_type in circuit_cable_mpns:
        channel_group = instance.get("channel_group")
        if channel_group not in channel_to_cable:
            channel_to_cable[channel_group] = f"cable-{cable_counter}"
            cable_counter += 1
        instances_list.modify(
            instance.get("instance_name"),
            {
                **circuit_cable_mpns[channel_type],
                "cable_group": channel_to_cable[channel_group],
                "cable_identifier": circuit_cable_identifiers.get(
                    instance.get("signal_of_channel_type")
                ),
            },
        )


# ===========================================================================
#              SUBSYSTEM SYMBOLS
# ===========================================================================
# Each run_macro call is one symbol (switch instances in the console to edit it).
# scale is applied to the system block diagram primitive in the symbol.
# Connector pins come from unique ICD connectors. The ICD is written after
# build instructions, so a second build is required after ICD changes.
# This macro also runs device signals-list / symbol-pin verification on the ICD.
scale = 0.1

build_utils.run_macro(
    "subsystem_symbol",
    "macros/system_artifacts",
    "https://github.com/harnice/harnice-general-library",
    artifact_id="subsystem-symbol-1",
    scale=scale,
)

# Orthographic drawing of the physical assembly. One view per call.
# scale is the drawing scale. lineweight is the sheet stroke in px.
build_utils.run_macro(
    "physical_model_drawing",
    "macros/system_artifacts",
    "https://github.com/harnice/harnice-general-library",
    artifact_id="physical-1",
    scale=1,
    lineweight=1,
    hidden_instances=[],
    orientation="front",
)

# Store the default trace layout styles into a variable to be used later when
# generating the GUI block diagram layouts if desired.
default_trace_overlay_style = dict(svg_utils.trace_overlay_style)

# Example of how to edit the trace layouts for various outputs. Copy and paste
# svg_utils.regenerate_trace_overlays and modify the style dict before a macro
# to modify that macro's trace layout appearance.
svg_utils.regenerate_trace_overlays(
    style={
        "label_font_size": 0.5,
        "segment_spacing": 1.5,
        "stroke_width": 0.5 / 96,
        "min_label_segment_length": None,
        "network_segment_stroke_width": 1.8,
        "network_segment_font_size": 11,
        "network_node_radius": 2.2,
        "network_node_font_size": 10,
    },
)

# Block diagram sheet. Writes {artifact_id}-master.svg for the PDF generator.
# scale is the drawing scale. Each show_* flag is one of the old sheet
# checkboxes: True draws that layer, False hides it.
build_utils.run_macro(
    "block-diagram-svg-exporter",
    "macros/system_artifacts",
    "https://github.com/harnice/harnice-general-library",
    artifact_id="block-diagram-1",
    scale=1,
    show_boundary=True,
    show_devices=True,
    show_subsystems=True,
    show_harnesses=True,
    show_mating_connectors=True,
    show_external_interface_flags=True,
    show_harness_labels=True,
    show_channel_traces=True,
    show_circuit_traces=True,
)

# Each run_macro call is one PDF file.
build_utils.run_macro(
    "pdf_generator",
    "macros/harness_artifacts",
    "https://github.com/harnice/harnice-general-library",
    artifact_id="pdf_drawing-1",
    page_setup={
        "pages": [
            {
                "tblock_mpn": "harnice_tblock",
                "tblock_lib_repo": "https://github.com/harnice/harnice-general-library",
                "tblock_lib_subpath": "titleblocks/",
                "page_size_in": [11.0, 8.5],
                "tblock_anchor": "bottom-right",
                "tblock_offset_px": [40.0, 40.0],
                "text_replacements": {
                    "tblock-key-pagedesc": "block_diagram",
                    "tblock-key-desc": rev_history.info(field="desc"),
                    "tblock-key-pn": state.partnumber("pn"),
                    "tblock-key-drawnby": rev_history.info(field="drawnby"),
                    "tblock-key-rev": state.partnumber("R"),
                    "tblock-key-scale": scale,
                    "tblock-key-sheet": "autosheet",
                },
            },
        ],
    },
)


# ===========================================================================
#              FEATURES FOR RELATIVES
# ===========================================================================
# Any other harnice project can import and these functions using build_utils.run_instruction_from_relative().
# The following functions are defaults. Adjust, remove, duplicate them as needed. Features for relatives are
# intended to be used as a cohesive set of rules by any child part such that anything derived from this sytem
# follows the same set of rules.


def connector_chooser():
    # if the device-side connector MPN is x, assign mating connector y
    mating_connector_lookup_table = {
        #       x  :   y
        "XLR3M": {
            "mpn": "NC3FXX",  # Neutrik 3-pin female cable
            "lib_repo": "https://github.com/harnice/harnice-av-library",
            "lib_subpath": "neutrik",
            "lib_rev_used_here": None,
        },
        "XLR3F": {
            "mpn": "NC3MXX",  # Neutrik 3-pin male cable
            "lib_repo": "https://github.com/harnice/harnice-av-library",
            "lib_subpath": "neutrik",
            "lib_rev_used_here": None,
        },
        "DB25F": {
            "mpn": "M24308_4-3T",  # MIL-DTL-24308 25-pin crimp plug, nickel fluorocarbon
            "lib_repo": "https://github.com/harnice/harnice-aerospace-library",
            "lib_subpath": "dsub",
            "lib_rev_used_here": None,
        },
        "DB25M": {
            "mpn": "M24308_2-3T",  # MIL-DTL-24308 25-pin crimp receptacle, nickel fluorocarbon
            "lib_repo": "https://github.com/harnice/harnice-aerospace-library",
            "lib_subpath": "dsub",
            "lib_rev_used_here": None,
        },
    }

    # if the harness connector instance is named x, assign mating connector y
    # regardless of the device-side part number. leave empty unless you mean it.
    connector_by_instance_name = {
        #       x  :   y
        # "J1": {
        #     "mpn": "NC3FXX",
        #     "lib_repo": "https://github.com/harnice/harnice-av-library",
        #     "lib_subpath": "",
        #     "lib_rev_used_here": None,
        # },
    }

    for instance in instances_list.read():
        if instance.get("item_type") != "harness_connector":
            continue

        instance_name = instance.get("instance_name")

        mate = mating_connector_lookup_table.get(
            instance.get("device_side_connector_mpn")
        )
        if mate:
            instances_list.modify(
                instance_name=instance_name,
                instance_data=mate,
            )
            continue

        mate = connector_by_instance_name.get(instance_name)
        if mate:
            instances_list.modify(
                instance_name=instance_name,
                instance_data=mate,
            )
            continue

        # per-position rules (state.harness is the child's system position name):
        # if state.harness == "H01" and instance_name == "P1":
        #     instances_list.modify(
        #         instance_name,
        #         {
        #             "mpn": "NC3FXX",
        #             "lib_repo": "https://github.com/harnice/harnice-av-library",
        #             "lib_subpath": "neutrik",
        #             "lib_rev_used_here": None,
        #         },
        #     )
        #     continue

        # family choosers (D38999, Mighty Mouse, ...) are in the connector-choosing
        # examples ribbon - insert them here, then continue if they assign an mpn.

        # No rule matched. Keep the harness building with a placeholder.
        # Artwork goes in the general library at
        # connectors/example/EXAMPLE_CONNECTOR/rev1/drawing.svg.
        cli.warn(
            f"connector_chooser could not find a solution for {instance_name}; "
            "using EXAMPLE_CONNECTOR"
        )
        instances_list.modify(
            instance_name=instance_name,
            instance_data={
                "mpn": "EXAMPLE_CONNECTOR",
                "lib_repo": "https://github.com/harnice/harnice-general-library",
                "lib_subpath": "connectors/example",
                "lib_rev_used_here": None,
            },
        )

# Reset the trace overlay style to default saved earlier to set the gui trace
# overlay style.
svg_utils.trace_overlay_style.update(default_trace_overlay_style)

Channel and passthrough mapping

After Harnice exports a BOM and system connector list from your block diagram SVG, it seeds a channel map—one row per channel endpoint, including both faces of a passthrough. Your build instructions then declare which endpoints carry the same logical signal (map()), and (when a patch must cross a two-face device) which passthrough routes each cross-harness link uses. That process is what turns diagram connectivity into a circuits list and downstream harness instances.

Channel map file format

Interacting with Channel Maps

A list of channels on devices within groups of connected harnesses. Each completed pairing is a mapped channel; remaining rows are unmapped.


Columns

Columns are automatically generated when channel_map.new() is called. Additional columns are ok but discouraged due to consistency. If you need more columns, use data structures in build instructions.

Column Description
group_of_connected_harnesses harnesses linked through passthroughs (or a single standalone harness)
from_device_refdes device instance refdes of this channel endpoint (seeded by new())
from_device_channel_id channel id on that device (from its signals list)
from_repeat_channel_id required when that channel has two endpoints; blank otherwise
from_channel_type channel type of the from endpoint (from the signals list)
to_device_refdes device instance refdes of the mapped partner; blank until mapped
to_device_channel_id channel id of the mapped partner; blank until mapped
to_repeat_channel_id required when that channel has two endpoints; blank otherwise
to_channel_type channel type of the mapped partner; blank until mapped
multi_ch_junction_id set by map(..., multi_ch_junction_key=...); shared id for a multi-channel junction net
chain_of_connectors filled by passthrough_map.resolve(); semicolon-separated refdes.connector path (e.g. MIC3.J1;X1.A;X1.B;PREAMP1.J1)
chain_of_harnesses filled by passthrough_map.resolve(); semicolon-separated harness refdes hops (e.g. H1;H2)
manual_map_channel_python_equiv channel_map.map(...) call that produced this pairing; copy back into build instructions

Commands

These functions are already available in build instructions as channel_map. Use them as written.

channel_map.read()

Use this when a system build needs every row of the channel map in the revision that is open.

Return the channel map (cached).

channel_map.new()

Use this when a system build is starting a blank channel map. system.build() calls this before the script and replaces the file. A system script does not call it again.

Makes a new blank channel map. Overwrites existing channel map.

Seeds one row per channel endpoint, including both faces of a passthrough. Auto-map skips those multi-endpoint rows; map() uses a 3-tuple to name a face.

Args: none

Returns: none

channel_map.map(from_key, to_key=None, multi_ch_junction_key='')

Use this when a system build needs one channel endpoint patched to another, or landed on a multi-channel junction, and the channel types are compatible.

Map one channel endpoint to another (a patch), or consume it onto a multi-channel junction.

Each key is (device_refdes, channel_id) when that channel has one endpoint, or (device_refdes, channel_id, repeat_channel_id) when it has two. A 3-tuple on a single-instance channel is an error; omitting the repeat on a two-instance channel is an error.

Dual-map is two calls to the two repeats of the same passthrough. That consumes both faces as patch ends and does not write a passthrough-map row.

Both endpoints must belong to the same group_of_connected_harnesses. Unmated terminating connectors are in no group. An unmated passthrough face still belongs to the group of its other, mated face.

A patch may end by crossing a device only if the destination face has no harness. If that face has a harness, the last hop must be that harness.

Pass multi_ch_junction_key="jcn1" instead of to_key to consume from_key and write jcn1 as multi_ch_junction_id. Several rows can share that id (connecting those channels to each other) without pairing to a single partner. A channel may join only if its type is, or is compatible with, every other member already on that junction.

Args: - from_key: Endpoint to map. (device_refdes, channel_id) when the channel has one endpoint, or (device_refdes, channel_id, repeat_channel_id) when it has two. A 3-tuple on a single-endpoint channel is an error, and omitting the repeat on a two-endpoint channel is an error. - to_key: Other endpoint of the patch, in the same tuple shape as from_key. Both ends must belong to the same group of connected harnesses. Optional. Omit it, or pass None, when you pass multi_ch_junction_key instead. - multi_ch_junction_key: Junction id, such as "jcn1", that consumes from_key. Every channel that shares that id is connected, without pairing to one partner. The channel type must be compatible with members already on the junction. Do not pass this together with to_key. Optional.

channel_map.force(from_key, to_key)

Use this when a system build needs one channel endpoint patched to another even though the channel types do not match. Pair the signals with circuits_list.force before circuits_list.new.

Map one channel endpoint to another regardless of channel-type compatibility.

Same key, group, and face rules as map(). A 1:1 patch only: there is no multi_ch_junction_key. map() is unchanged and does not record a pending patch.

Signal names still pair by name when both channel types list the same set, and circuits_list.new() compiles those circuits as usual. When the sets differ, the patch is recorded as pending. Call circuits_list.force() with a from-signal → to-signal dict, and drop every to-side signal you are leaving unwired, before circuits_list.new(). A pending patch still waiting at circuit compile raises ValueError.

The circuit's from_signal is the from-side name. A from-side signal cannot be left unwired. If the from channel has a name the to channel does not, swap the endpoints so that extra name is on the to side, then drop it.

channel_map.flatten()

Use this when a system build needs the flattened channel map of this system and the subsystems it contains.

Write maps/channel_map_flattened.tsv for this system and every child subsystem.

Reads this system's channel map, then the channel map imported with each child subsystem, recursively. An imported subsystem channel map is the source of truth for which endpoints were mapped. This does not re-run channel mapping inside the subsystem.

A passthrough channel whose two faces are both mapped patch ends is folded into one row between the outer endpoints before subsystem prefixes are applied. The lexicographically smaller outer endpoint (refdes, channel id, repeat) is the from side. chain_of_connectors records the disconnect between them (SNK-0.J1;X-6.B;X-6.A;SNK-3.J1). chain_of_harnesses on that row is left blank. A chain of dual-mapped disconnects folds to the two outer devices, with every disconnect in the connector chain. A channel with only one face mapped stays as that one patch. A row that already joins the two faces is left unchanged. Rows with no partner and no multi-channel junction are omitted. A junction row is kept. The flat channel map is not rewritten.

Rows from a subsystem prefix that subsystem's instance name onto group_of_connected_harnesses, from_device_refdes, to_device_refdes, multi_ch_junction_id, chain_of_connectors, and chain_of_harnesses. A list is prefixed per entry (AMP.H1+AMP.H2, AMP.H1;AMP.H2, AMP.MIC3.J1;AMP.X1.A). Nested subsystems accumulate prefixes (AMP.PRE.MIC1). from_repeat_channel_id, to_repeat_channel_id, and manual_map_channel_python_equiv are omitted. from_channel_type_description and to_channel_type_description sit beside the channel type columns and are the description from that type's channel_types.tsv. Channel ids and channel types are copied unchanged. This system's own rows are not prefixed.

Call after channel mapping, including map_unmapped_compatibles_alphabetically(). The sheet snapshots the channel map as it stands at the call.

Args: none

Returns: the written rows

channel_map.already_mapped(key)

Use this when a system build needs to know whether one channel endpoint is already consumed by a map or a route.

Return whether an endpoint is already in the mapped-channels set.

Args: - key: Endpoint key in the same shape map uses: (device_refdes, channel_id) or (device_refdes, channel_id, repeat_channel_id).

channel_map.already_mapped_set()

Use this when a system build needs every channel endpoint already consumed by map or route.

Return the set of endpoint keys already consumed by map or route.

channel_map.already_mapped_set_append(key)

Use this when a system build needs to mark one channel endpoint as already consumed.

Mark one endpoint as already mapped.

Args: - key: Endpoint key in the same shape map uses: (device_refdes, channel_id) or (device_refdes, channel_id, repeat_channel_id). Raises if that key is already in the mapped set.

Passthrough map file format

Interacting with Passthrough Maps

Every passthrough channel in the system. Call new() to build the map and routing context, optionally route() required hops, then resolve() to fill channel-map chains.


Columns

Columns are automatically generated when passthrough_map.new() is called. Additional columns are ok but discouraged due to consistency. If you need more columns, use data structures in build instructions.

Column Description
from_channel_type channel-map from type
from-passthrough_channel_type passthrough face on the same side as from
to-passthrough_channel_type passthrough face on the same side as to
to_channel_type channel-map to type
requirement user route() step number for this passthrough-channel mapping
satisfaction resolve() route() step number (always >= requirement when both set)
is_imaginary TRUE if this mapping was minted because no real passthrough channel was available
has_imaginary TRUE if this passthrough device has any imaginary channel mappings
manual_map_passthrough_python_equiv passthrough_map.route() that would form this requirement

Commands

These functions are already available in build instructions as passthrough_map. Use them as written.

passthrough_map.read()

Use this when a system build needs every row of the passthrough map in the revision that is open.

Return the passthrough map (cached).

passthrough_map.new()

Use this when a system build needs passthrough rows created from the signals lists of the devices on the block diagram.

Create passthrough channel rows from placed-device signals lists.

Rebuilds system.passthrough_routing_context as one row per unique (from_channel_type, to_channel_type) pair within each group_of_connected_harnesses, per passthrough device, per directionality (1 / -1, or 0 if reversible), with an availables count of passthrough channels that obey that directionality. Does not route channels or fill channel-map chains (see resolve()).

passthrough_map.route(to_from_channel_device_refdes, to_from_channel_id, intended_device_refdes, intended_channel_id=None, repeat_channel_id=None, satisfaction=False)

Use this when a system build needs one mapped channel sent through one passthrough device.

Route a mapped channel through a passthrough device.

Looks up the mapped channel's from→to types and group_of_connected_harnesses, finds the matching routing-context row(s), picks a free passthrough channel that obeys directionality, records the mapping on the passthrough map, and decrements availables on the matching context row (never below 0).

repeat_channel_id is the face the from-side arrives on. Omit it to infer.

The passthrough device must belong to the same group_of_connected_harnesses as the mapped channel.

By default writes requirement (user route). With satisfaction=True (used by resolve), writes satisfaction instead.

Returns 0 on a real assignment, -1 if an imaginary passthrough-map row was appended because availables was already 0.

Args: - to_from_channel_device_refdes: Device refdes of the mapped channel you are routing. - to_from_channel_id: Channel id on that device. - intended_device_refdes: Passthrough device to route through. It must belong to the same group of connected harnesses as the mapped channel. - intended_channel_id: Specific passthrough channel to use. Optional. Omit it to let route pick a free channel that matches directionality. - repeat_channel_id: Face the from-side arrives on. Optional. Omit it to infer the face. - satisfaction: When True, write a satisfaction row (what resolve does). When False, write a requirement row. Optional.

passthrough_map.resolve()

Use this when a system build needs every mapped channel routed through its passthroughs, in channel-map order.

Complete passthrough routing for each mapped channel (channel-map order).

Uses the routing context (filtered to group_of_connected_harnesses + from→to pair) to find the fewest passthrough channels that connect from_harness → optional ordered requirement waypoints → to_harness. Prefers paths with remaining availables; if none exist, allows availables == 0 (imaginaries).

Gap fills call route(..., satisfaction=True). Existing requirement rows get a satisfaction stamp. Writes chain_of_connectors and chain_of_harnesses on each mapped channel-map row from the resulting path.

Stamps is_imaginary / has_imaginary on the passthrough map. Prints one warning per passthrough that needed imaginary channels to satisfy the map.

No-op when passthrough_map.new() found no harness topology (empty parent diagram / no harness-mated connectors): there is nothing to resolve.

Where mapping runs in a system build

  1. Before build instructions: channel_map.new() runs automatically during system.build(). It reads the system connector list and each device's signals list, and writes one channel-map row per channel endpoint (device_refdes, channel_id[, repeat_channel_id]).

  2. In build instructions (typical order): map channels → channel_map.flatten() → passthrough_map.new() → optional route() hops → passthrough_map.resolve() → build the circuits list.

  3. After mapping: circuits_list.new() expands every mapped channel pair (and its passthrough path) into individual conductor circuits. A multi-channel junction row (no to_* partner) becomes one circuit per signal: from is from_channel.signal, to is mcj.signal. Signal names come from the first channel type seen on that junction id.

The default system build instructions follow this pattern:

# 2-tuple when the channel has one endpoint; 3-tuple when it has two
# channel_map.map(("MIC1", "out1"), ("PRE1", "in1"))
# channel_map.map(("MIC1", "out1"), ("X1", "ch0", "A"))

channel_map.map_unmapped_compatibles_alphabetically()
channel_map.flatten()

passthrough_map.new()

# optional: passthrough_map.route("MIC1", "out1", "X1", intended_channel_id="ch0", repeat_channel_id="A")
passthrough_map.resolve()

circuits_list.new()

Concepts

Channel. A channel is one logical link on a device—audio in, power, differential pair, etc. Its channel type (from the channel-types library) defines required signals and which other types it may mate with. See channel types documentation for compatibility rules.

Channel endpoint. One complete instance of a channel on one connector (channel_id + channel_type + connector_id, plus repeat_channel_id when that channel has two faces).

Patch. A pair of channel endpoints created by channel_map.map(). Stored in the channel map.

Passthrough. A channel with exactly two channel endpoints on the same device. Identified by (device_refdes, channel_id) plus a repeat_channel_id for each face.

Mapped channel. A mapped channel is a connection from one channel on a device to another. They define the functional requirement of any harness, and form the basis of which circuits are generated and where they go. The channel map is the list of mapped channels (and any still-unmapped channels).

Group of connected harnesses (group_of_connected_harnesses). Harness wires in your block diagram define equivalence classes of harnesses: any harnesses linked through a passthrough share the same group_of_connected_harnesses label on the system connector list, copied onto each channel-map row. Unmated terminating connectors are in no group. An unmated passthrough face still belongs to the group of its other, mated face. Automatic channel mapping only considers pairs within the same group of connected harnesses—channels in different groups are never auto-mapped together. Manual map() and route() also require the same group.

Channel mapping in detail

Seeding: channel_map.new()

For every row in the system connector list where device_refdes is set, Harnice loads that device's signals list. For each channel endpoint on that connector, it appends a row with:

  • group_of_connected_harnesses — copied from the matching system connector list row

  • from_device_refdes, from_device_channel_id, from_repeat_channel_id, from_channel_type — the channel endpoint (from_repeat_channel_id is set when that channel has two faces)

  • to_* columns empty until mapped

It also creates an empty mapped channels set TSV used to prevent double-mapping.

Manual mapping: channel_map.map(from_key, to_key=None, multi_ch_junction_key="")

from_key and to_key are (device_refdes, channel_id) when that channel has one endpoint, or (device_refdes, channel_id, repeat_channel_id) when it has two. A 3-tuple on a single-instance channel is an error. Dual-map is two map() calls to the two repeats of the same passthrough (no passthrough-map row). A patch may end by crossing a device only if that destination face has no harness. Calling map(from_key, to_key):

  1. Finds the row whose from_* columns match from_key and copies to_device_refdes, to_device_channel_id, to_repeat_channel_id, and to_channel_type from the row that matches to_key.

  2. Removes the separate "to-only" row for to_key (each physical endpoint appears once as a from_* row; mapping is directed).

  3. Records both keys in the mapped channels set and writes a manual_map_channel_python_equiv cell you can copy back into build instructions.

To connect several channels to each other on one multi-channel junction, pass multi_ch_junction_key="jcn1" instead of to_key. That consumes from_key and writes jcn1 as multi_ch_junction_id on the same row. Several rows can share that id without pairing to a single partner. A channel may join only if its type is, or is compatible with, every other member already on that junction (chtype.is_or_is_compatible_with, either direction). Automatic mapping never attaches leftover channels to a multi-channel junction. On the block-diagram channel overlay, traces of the same id follow the harness to the T (one arm per member). The circuit overlay does the same per signal (from_channel.signal → mcj.signal), using the first channel type seen on that id for signal names. A junction dot is placed where that net arrives on three or more arms.

Raises ValueError if either key was already mapped, if the endpoints are not in the same group, if a required to_key row is missing, if both to_key and multi_ch_junction_key are passed, or if the joining channel is not compatible with every existing junction member.

Force mapping: channel_map.force(from_key, to_key)

Maps one endpoint to another with the same group and face rules as map(), and does not check channel-type compatibility. map() itself is unchanged. When both channel types list the same signal names, circuits compile by those names. When the names differ, the patch stays pending until circuits_list.force(from_key, signal_pairs, drop=()) pairs every from-side name and explicitly drops every unused to-side name. The circuit keeps the from-side name; the to-side cavity is looked up under the paired name. A from-side signal cannot be dropped. circuits_list.new() raises if any pending patch is still waiting.

Automatic mapping: channel_map.map_unmapped_compatibles_alphabetically()

For each group_of_connected_harnesses, rows are sorted alphabetically by device refdes and channel id. Channels with more than one endpoint (passthroughs) are skipped, even if nothing else is left. For each remaining row whose from_key is not yet mapped, Harnice scans later rows in the same group of connected harnesses and maps to the first candidate whose channel types are mutually compatible: either to_type is in chtype.compatibles(from_type) or from_type is in chtype.compatibles(to_type). This is deterministic but not unique—use manual channel_map.map() first for anything that must not follow alphabetical pairing.

Flattened channel map: channel_map.flatten()

Writes maps/channel_map_flattened.tsv with the same columns as the channel map. The sheet starts with this system's channel map, then appends the channel map imported with each child subsystem, and repeats that walk through every nested subsystem. Those imported channel maps are the source of truth for which endpoints were mapped. This does not re-run channel mapping inside the subsystem.

Before those prefixes are applied, a passthrough channel whose two faces are both mapped patch ends is folded into one row between the outer endpoints. The lexicographically smaller outer endpoint is the from side. chain_of_connectors records the disconnect between them. chain_of_harnesses on that row is left blank. A chain of dual-mapped disconnects folds to the two outer devices. A channel with only one face mapped stays as that one patch. A row that already joins the two faces is left unchanged. Rows with no partner and no multi-channel junction are omitted. A junction row is kept. The flat channel map is not rewritten.

Rows from a subsystem prefix that subsystem's instance name onto group_of_connected_harnesses, from_device_refdes, to_device_refdes, multi_ch_junction_id, chain_of_connectors, and chain_of_harnesses. Each entry in a list is prefixed (AMP.H1+AMP.H2, AMP.MIC3.J1;AMP.X1.A). Nested subsystems accumulate prefixes (AMP.PRE.MIC1). from_repeat_channel_id, to_repeat_channel_id, and manual_map_channel_python_equiv are omitted. from_channel_type_description and to_channel_type_description sit beside the channel type columns and are the description from that type's channel_types.tsv. Channel ids and channel types are copied unchanged. This system's own rows are not prefixed. Call it after map_unmapped_compatibles_alphabetically(). It snapshots the channel map as it stands at the call.

Channel-map columns filled later

| Column | When it is filled |

|--------|-------------------|

| chain_of_connectors, chain_of_harnesses | Via passthrough_map.resolve() from the passthrough path |

| multi_ch_junction_id | When you pass multi_ch_junction_key= to channel_map.map(). Copied onto circuits (from_channel.signal → mcj.signal) |

A row is fully mapped when to_device_refdes and to_device_channel_id are set. Passthrough routing requires fully mapped rows whose endpoints sit on different harnesses.

Passthrough routing in detail

Channel mapping answers which device channels are the same signal. The passthrough map lists every unused passthrough channel; passthrough_map.resolve() chooses which passthroughs carry each mapped signal and writes chain_of_connectors / chain_of_harnesses on the channel map. Dual-map consumes both faces as patch ends and does not write a passthrough-map row.

passthrough_map.new()

Reads the system BOM, loads each device's signals list, and writes one row per unused passthrough channel. Builds the routing context used by route() / resolve(). Passthroughs already consumed as dual-map ends are omitted.

passthrough_map.route()

Pins a mapped channel through a chosen passthrough (optional channel id and repeat_channel_id for the face the patch arrives on). Call after new() and before resolve() for required hops. The passthrough must be in the same group as the mapped channel. Routing a channel whose faces are already map-consumed is an error.

passthrough_map.resolve()

Completes passthrough routing for each mapped channel (optional user route() waypoints first). Writes satisfaction on the passthrough map and fills chain_of_connectors / chain_of_harnesses on the channel map. Connector tokens are refdes.connector (e.g. MIC3.J1;X1.A;X1.B;PREAMP1.J1); harness names are refdes per hop (H1;H2).

Practical tips

  • Map critical channel pairs manually before map_unmapped_compatibles_alphabetically().

  • Use route() for required passthrough hops; let resolve() fill the rest.

  • Both endpoints of a map(), and any route() hop, must be in the same group of connected harnesses.


Physical model

Open physical model on a system to pose every placed device that has a body.step. Poses and fasten mates are physical.json (fileio.path("physical assembly")). The page rewrites assembly.step from that file. How the mates solve, and how a device gets a body, is CAD.

File Structure

Reference the files in your project by calling fileio.path("file key") from your script. They'll automatically use this structure:

fileio.dirpath("part_directory")                 |-- yourpn/
                                                     |-- earlier revs/
fileio.path("revision history")                      |-- revhistory.csv
fileio.dirpath("rev_directory")                      L-- your rev/
fileio.path("build instructions")                        |-- build.py
fileio.path("instances list")                            |-- instances.tsv
fileio.path("block diagram")                             |-- blockdiagram.svg
fileio.path("icd")                                       |-- signals.tsv
fileio.path("physical assembly")                         |-- physical.json
fileio.path("assembly")                                  |-- assembly.step
fileio.path("available network")                         |-- available_network.json
fileio.dirpath("contexts")                               |-- contexts/
fileio.dirpath("instance_data")                          |-- instance_data/
fileio.dirpath("harnesses")                              |-- harnesses/
fileio.dirpath("lists")                                  |-- lists/
fileio.path("bom")                                       |   |-- bom.tsv
fileio.path("circuits list")                             |   |-- circuits.tsv
fileio.path("post harness instances list")               |   |-- instances_w_hrns.tsv
fileio.path("system connector list")                     |   |-- connectors.tsv
fileio.path("mapped channels set")                       |   |-- mapped.tsv
fileio.path("library history")                           |   L-- libhist.tsv
fileio.dirpath("maps")                                   L-- maps/
fileio.path("channel map")                                   |-- channels.tsv
fileio.path("flattened channel map")                         |-- channel_map_flattened.tsv
fileio.path("passthrough map")                               |-- passthrough.tsv
fileio.path("passthrough routing context")                   |-- context.tsv
fileio.path("pending forced channels")                       |-- forced_channels_pending.tsv
fileio.path("forced signal map")                             L-- forced_signals.tsv

Instructions for child harnesses

A system can define functions such as connector_chooser and m85049_chooser in its build instructions. Child harnesses call them after they import from this revision:

build_utils.run_instruction_from_relative(
    "connector_chooser",
    system_pn,
    system_lib_repo,
    system_lib_subpath,
    system_rev,
)
build_utils.run_instruction_from_relative(
    "m85049_chooser",
    system_pn,
    system_lib_repo,
    system_lib_subpath,
    system_rev,
)

That is how system-level behavior (mating-connector policy, per-position assignments) stays on the parent instead of being copy-pasted into every harness. A missing file or function raises. It runs in the harness working directory. It is not a macro: no instance folder, no library-history row. Only the function (plus any remaining imports and literal data) is loaded from the system file — Harnice list, utility, and channel-type modules are already in the namespace. The system's own channel-mapping and macros are not re-run.

Use state.harness inside the function for one harness position. When the system revision changes, the next harness rebuild picks up the new rules.


How to define a new system

  1. Make a folder for the part number of your system somewhere on your computer. Click Build in Harnice, which will generate an example system that you can then edit.

    Building a project
    1. In Harnice, click New project…. You do not need a rev folder yet.

    2. Fill in part number, revision (1 is fine), project type, and a description, then click OK. Harnice creates the folders and an example you can edit.

    3. Click Build to generate the rest of the project files.

  2. Open that system in Harnice and edit the block diagram. Place devices from your library, draw harness wires between connectors, then save.

  3. Click Build often as you refine the diagram and build instructions.


Designing your block diagram

Device symbols are placed in the system block diagram SVG from the Harnice console (Place device). A two-face passthrough is a device.

Harness wires represent entire harnesses between connectors. The diagram is agnostic to the individual conductors, channels, or signals of a harness—only that certain connectors are connected via a harness.

To join two harnesses through a passthrough, place a two-face device and route harness wires to its faces. Set library pull fields on the placed symbol (same attributes used when placing from the library):

  1. MPN — part number of the device

  2. lib_repo — traceable path to the repo that contains the device

  3. lib_subpath — path between the library root and the part number, if any (for example, if the part lives at {library_root}/devices/audio/tascam-db25/rev1/, use devices/audio/)

  4. rev — revision to use in this system (optional)

An external interface flag (red triangle) marks a connector that appears on the system's external ICD (fileio.path("icd")). Place it on a harness mating connector (yellow dot) or on a device connector that has no harness mated to it. It cannot be placed on a device connector that already has a harness. On build, flags on harness-mated passthrough connectors fill the ICD from circuits; flags on unmated device connectors copy that connector's rows from the device's signals list.

Block diagram symbols

Default system build instructions run the subsystem_symbol macro once (artifact_id="subsystem-symbol-1", scale=0.1). Each additional run_macro call of that MPN is another imported instance; open it from instance data. The editor is the same drawing tools as a device symbol, plus System diagram, which drops the current system block diagram contents (not trace overlays) as a primitive. scale sets that primitive's scale; a later build updates the scale of a diagram already on the symbol and leaves its position. The macro seeds a blank SVG if none exists and does not otherwise redraw a symbol you have already drawn.

On first seed (and on later builds when pins are still missing), the macro places one connector pin per unique connector_name in the system ICD (fileio.path("icd")). It also runs device signals-list checks against that ICD (the system's signals list). Pin mismatches vs the ICD print a warning and do not fail the build; missing pins are still appended. The ICD is compiled after build instructions, so this macro always reads the ICD from the previous build. After you add, remove, or rename external-interface flags, build twice: the first build writes the ICD, and the second build updates symbol pins and re-checks from it. That one-build lag is expected. If no previous ICD exists yet, verification is skipped.