Harnesses
A physical assembly that contains a set of electrical circuits that satisfies a channel map. Can also contain other parts and instructions about how to be built.
How to define a new harness
-
Make a folder for the part number of your harness somewhere on your computer. Click Build in Harnice, which will generate an example harness that you can then edit.
Building a project
-
In Harnice, click New project…. You do not need a rev folder yet.
-
Fill in part number, revision (
1is fine), project type, and a description, then click OK. Harnice creates the folders and an example you can edit. -
Click Build to generate the rest of the project files.
-
-
Edit the attributes of your new harness.
Editing the Attributes of a Project
-
Navigate to the device folder, find the new rev folder you just made, and open
fileio.path("attributes"). -
Change the default reference designator here, as well as any other attributes you may want to record.
- Click Build again.
-
If the harness was created from a system position, build instructions already import that harness. Connector and M85049 backshell policy belong on the system as connector_chooser and m85049_chooser; the harness default build rules call them after the import.
The first build writes these instructions when build.py does not exist yet.
Default harness build instructions
import math
import random
# ===========================================================================
# pull harness definition from system
# ===========================================================================
system_utils.import_harness_from_harnice_system(
system_pn=rev_history.info().get("source_part_number"),
system_rev=rev_history.info().get("source_revision"),
system_lib_repo=rev_history.info().get("source_lib_repo"),
system_lib_subpath=rev_history.info().get("source_subpath"),
target_harness=rev_history.info().get("source_refdes"),
)
rev_history.overwrite(
{
"desc": f"HARNESS '{rev_history.info().get('source_refdes')}' FROM SYSTEM '{rev_history.info().get('source_part_number')}-{rev_history.info().get('source_revision')}'",
}
)
# ===========================================================================
# HARNESS BUILD RULES
# ===========================================================================
# Connector policy lives on the parent system.
build_utils.run_instruction_from_relative(
"connector_chooser",
rev_history.info().get("source_part_number"),
rev_history.info().get("source_lib_repo"),
rev_history.info().get("source_subpath"),
rev_history.info().get("source_revision"),
)
# If you want to define build rules local to this harness, write them here.
# Don't forget to rely on pre-defined functions and examples in the ribbon toolbar above.
# After assigning parts to instances, import them into this harness's file tree.
instances_list.raise_error_on_unassigned_connectors()
for instance in instances_list.read():
if instance.get("item_type") in ["harness_connector", "backshell"]:
library_utils.pull(instance)
# ===========================================================================
# PROCESS AVAILABLE NETWORK DOWN INTO A CHOSEN NETWORK
# ===========================================================================
# available network (cad or manual) -> chosen network (modified subset) -> flattened network (visually arranged in 2D)
available_network = available_network.load()
# Assign nodes to any segments that may not have nodes attached
available_network = chosen_network.end_all_segments_in_nodes(available_network)
# Gather the collection of all instances with item_type == node
needed_nodes = []
for instance in instances_list.read():
if instance.get("item_type") == "node":
name = instance.get("instance_name")
if name not in needed_nodes:
needed_nodes.append(name)
# Ensures that every node in the list is present in the network.
missing_nodes = chosen_network.ensure_nodes_exist(
input_network=available_network,
set_of_required_nodes=needed_nodes,
)
# Generate geometry for required nodes that the available network did not provide.
# Set to False to raise an error instead.
if True:
for missing_node in missing_nodes:
existing_nodes = available_network.get("nodes") or []
# Empty network: park at the origin. Otherwise sprout a short random
# leg off the first existing node.
new_node_location = [0, 0, 0]
if existing_nodes:
hub = existing_nodes[0]
new_node_distance_from_hub = random.randint(6, 18)
new_node_angle_from_x_axis = math.radians(random.randint(0, 359))
new_node_location = [
hub.get("location")[0] + new_node_distance_from_hub * math.cos(new_node_angle_from_x_axis),
hub.get("location")[1] + new_node_distance_from_hub * math.sin(new_node_angle_from_x_axis),
hub.get("location")[2],
]
available_network = bundle_network_utils.new_node(
available_network,
new_id=missing_node,
new_location=new_node_location,
)
# Connect back to the hub so the new node is not floating.
if existing_nodes:
available_network.setdefault("segments", []).append({
"segment_id": f"{missing_node}_leg",
"location_at_end_a": list(hub.get("location") or [0, 0, 0]),
"location_at_end_b": new_node_location,
"spline_control_points": [],
})
else:
if len(missing_nodes) > 0:
raise ValueError(f"Missing node(s) {missing_nodes}")
# Find any possible solution within the network
found_solutions = chosen_network.calculate_possible_sub_networks(
input_network=available_network,
set_of_required_nodes=needed_nodes,
)
# Optionally prefer a solution containing particular imported segments. Leave
# these empty unless the parent system provides stable IDs worth selecting on.
preferred_segment_ids = [] # example: ["segment-1", "segment-2"]
preferred_node_ids = [] # example: ["branch-node"]
if len(found_solutions) > 1 and (preferred_segment_ids or preferred_node_ids):
sorted_solutions = chosen_network.find_network_with(
input_networks=found_solutions,
segment_ids=preferred_segment_ids,
node_ids=preferred_node_ids,
)
found_solutions = sorted_solutions
# Ensure only one solution is found
number_of_found_solutions = len(found_solutions)
if number_of_found_solutions != 1:
raise ValueError(f"Available network contains {number_of_found_solutions} solution(s)")
bundle_network = found_solutions[0]
# Clock a backshell towards the next nearest node in the chosen network
# bundle_network = bundle_network_utils.clock_backshell(
# network=bundle_network,
# connector_group="P1",
# towards_node="incident",
# round_to_nearest_angle=15,
# )
# Move chosen nodes around to keep the mating faces of the connectors at the same position as available based on the size of the assigned parts
for instance in instances_list.read():
group = instance.get("connector_group")
node_id = f"{group}.node"
if instance.get("item_type") == "backshell":
bundle_network = bundle_network_utils.clock_backshell(bundle_network, group)
elif instance.get("item_type") == "harness_connector": # if it's a connector that has no backshell, move chosen node to the connector
if instances_list.instance_in_connector_group_with_item_type( # backshell-doesn't-exist check
instance.get("connector_group"), "backshell"
):
continue
bundle_network = bundle_network_utils.move_node(
bundle_network,
node_id,
bundle_network_utils.output_offset(instance, "3d-mate"),
)
# ===========================================================================
# FLATTEN THE CHOSEN NETWORK
# ===========================================================================
# Save the chosen network back to disk
chosen_network.save(bundle_network)
# Build the flattened network
flattened_network.derive(bundle_network)
# ===========================================================================
# ASSIGN CABLES AND CONDUCTORS
# ===========================================================================
# Process usable cable recommendations from the system. Recommendations are
# optional, so incomplete or stale values should not make a new harness fail.
cable_recommendations = []
for instance in instances_list.read():
if instance.get("item_type") != "conductor":
continue
cable_group = instance.get("cable_group")
if cable_group not in ["", None]:
recommendation = {
"instance_name": instance.get("instance_name"),
"cable_group": cable_group,
"mpn": instance.get("mpn"),
"lib_repo": instance.get("lib_repo"),
"lib_subpath": instance.get("lib_subpath"),
"cable_identifier": instance.get("cable_identifier"),
}
cable_recommendations.append(recommendation)
cable_utils.prepare_for_conductor_assign(recommendation["instance_name"])
cables_created = [] # fill up with cable refdes's as they get processed
for recommendation in cable_recommendations:
required_fields = ["mpn", "lib_repo", "cable_identifier"]
missing_fields = [
field
for field in required_fields
if recommendation.get(field) in ["", None]
]
if missing_fields:
cli.warn(
f"Skipping incomplete cable recommendation for "
f"'{recommendation['instance_name']}': missing "
f"{', '.join(missing_fields)}"
)
continue
cable_group = recommendation["cable_group"]
if cable_group not in cables_created:
cable_utils.new_cable(
cable_group,
recommendation["mpn"],
recommendation["lib_repo"],
recommendation["lib_subpath"],
)
cables_created.append(cable_group)
try:
cable_utils.assign_conductor_to_cable(
recommendation["instance_name"],
cable_group, # which cable instance name gets assigned
recommendation["cable_identifier"], # which unique conductor within the cable goes here
)
except ValueError as error:
cli.warn(
f"Skipping unusable cable recommendation for "
f"'{recommendation['instance_name']}': {error}"
)
# assign a cable not recommended by the upstream system
# cable_utils.new_cable(
# "W1",
# "M27500-22SB2T23",
# "https://github.com/harnice/harnice-aerospace-library",
# "M27500",
# )
# cable_utils.assign_conductor_to_cable("conductor-1", "W1", "black")
# cable_utils.assign_conductor_to_cable("conductor-2", "W1", "white")
# route conductors between their end nodes, then map each cable onto the
# segments shared by all of its conductors
cable_utils.map_cables_and_conductors_to_segments()
# ===========================================================================
# CALCULATE SEGMENT BUNDLE DIAMETERS
# ===========================================================================
for instance in instances_list.read():
if instance.get("item_type") == "segment":
cable_utils.calculate_bundle_diameter(instance.get("instance_name"))
# ===========================================================================
# ASSIGN BOM LINE NUMBERS
# ===========================================================================
for instance in instances_list.read():
if instance.get("item_type") in ["harness_connector", "cable", "backshell"]:
instances_list.modify(instance.get("instance_name"), {"bom_line_number": True})
instances_list.assign_bom_line_numbers()
# ===========================================================================
# ADD BUILD NOTES
# ===========================================================================
for rev_row in rev_history.read():
if rev_history._revs_equivalent(rev_row.get("rev"), state.rev):
note_utils.make_rev_history_notes(rev_row)
for instance in instances_list.read():
for note in note_utils.get_lib_build_notes(instance):
note_utils.new_note(
"build_note", note, affectedinstances=[instance.get("instance_name")]
)
note_utils.assign_buildnote_numbers()
# example: add notes to describe actions
# note_utils.new_note(
# "build_note",
# "do this",
# affectedinstances=["X1.B.conn"]
# )
# note_utils.new_note(
# "build_note",
# "do that"
# )
# example: combine buildnotes if their texts are similar
# note_utils.combine_notes("Torque backshell to connector at 40 in-lbs","Torque backshell to connector at about 40 in-lbs")
# ===========================================================================
# PUT TOGETHER FORMBOARD SVG INSTANCE CONTENT
# ===========================================================================
instances = instances_list.read()
note_instances = []
for instance in instances:
if instance.get("item_type") == "note":
note_instances.append(note_utils.parse_note_instance(instance))
formboard_overview_instances = []
formboard_detail_instances = []
for instance in instances:
if instance.get("item_type") not in [
"harness_connector",
"backshell",
"segment",
"node",
"origin",
]:
continue
formboard_overview_instances.append(instance)
formboard_detail_instances.append(instance)
detail_flag_note_counter = 1
overview_flag_note_counter = 1
if instance.get("item_type") in ["harness_connector", "backshell"]:
formboard_detail_instances.append(
note_utils.make_bom_flagnote(
instance, f"flagnote-{detail_flag_note_counter}"
)
)
detail_flag_note_counter += 1
formboard_detail_instances.append(
note_utils.make_part_name_flagnote(
instance, f"flagnote-{detail_flag_note_counter}"
)
)
detail_flag_note_counter += 1
if instance.get("item_type") == "harness_connector":
formboard_overview_instances.append(
note_utils.make_part_name_flagnote(
instance, f"flagnote-{overview_flag_note_counter}"
)
)
overview_flag_note_counter += 1
for note_instance in note_instances:
if note_instance.get("note_type") == "build_note":
if instance.get("instance_name") in note_instance.get(
"note_affected_instances"
):
formboard_detail_instances.append(
note_utils.make_buildnote_flagnote(
note_instance, instance, f"flagnote-{detail_flag_note_counter}"
)
)
detail_flag_note_counter += 1
if note_instance.get("note_type") == "rev_change_callout":
if instance.get("instance_name") in note_instance.get(
"note_affected_instances"
):
formboard_detail_instances.append(
note_utils.make_rev_change_flagnote(
note_instance, instance, f"flagnote-{detail_flag_note_counter}"
)
)
detail_flag_note_counter += 1
# ===========================================================================
# BUILD HARNESS OUTPUTS
# ===========================================================================
instances = instances_list.read()
scales = {"A": 0.25, "B": 0.3, "C": 1}
build_utils.run_macro(
"bom_exporter_bottom_up",
"macros/harness_artifacts",
"https://github.com/harnice/harnice-general-library",
artifact_id="bom-1",
)
build_utils.run_macro(
"tooling_list_exporter",
"macros/harness_artifacts",
"https://github.com/harnice/harnice-general-library",
artifact_id="tool_list-1",
)
build_utils.run_macro(
"standard_harnice_formboard",
"macros/harness_artifacts",
"https://github.com/harnice/harnice-general-library",
artifact_id="formboard-overview",
dimension=0,
scale=scales.get("A"),
input_instances=formboard_overview_instances,
)
build_utils.run_macro(
"standard_harnice_formboard",
"macros/harness_artifacts",
"https://github.com/harnice/harnice-general-library",
artifact_id="formboard-detail",
scale=scales.get("C"),
input_instances=formboard_detail_instances,
)
# 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,
},
)
build_utils.run_macro(
"trace_layout_visualizer",
"macros/harness_artifacts",
"https://github.com/harnice/harnice-general-library",
artifact_id="conductor_layout-1",
trace_item_types=["conductor"],
scale=scales.get("A"),
)
build_utils.run_macro(
"trace_layout_visualizer",
"macros/harness_artifacts",
"https://github.com/harnice/harnice-general-library",
artifact_id="cable_layout-1",
trace_item_types=["cable"],
scale=scales.get("A"),
)
build_utils.run_macro(
"circuit_visualizer",
"macros/harness_artifacts",
"https://github.com/harnice/harnice-general-library",
artifact_id="circuitviz-1",
input_instances=instances_list.read(),
)
build_utils.run_macro(
"revision_history_table",
"macros/harness_artifacts",
"https://github.com/harnice/harnice-general-library",
artifact_id="revhistory-1",
)
build_notes_list_instances = []
for instance in instances_list.read():
if (
instance.get("item_type") == "note"
and instance.get("note_type") == "build_note"
):
build_notes_list_instances.append(instance)
build_utils.run_macro(
"build_notes_table",
"macros/harness_artifacts",
"https://github.com/harnice/harnice-general-library",
artifact_id="build_notes_table-1",
input_instances=build_notes_list_instances,
)
build_utils.run_macro(
"wirelist_exporter",
"macros/harness_artifacts",
"https://github.com/harnice/harnice-general-library",
artifact_id="wirelist-1",
input_instances=instances_list.read(),
)
build_utils.run_macro(
"harness_step_builder",
"macros/harness_artifacts",
"https://github.com/harnice/harnice-general-library",
artifact_id="harness-step-1",
chosen_network=bundle_network,
input_instances=instances_list.read(),
)
# Each run_macro call is one PDF drawing (switch instances in the console to edit layout).
# page_setup is the list of pages; edit it here, not in the PDF editor.
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": "overview",
"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": scales.get("A"),
"tblock-key-sheet": "autosheet",
},
},
{
"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": "to-from table",
"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": scales.get("A"),
"tblock-key-sheet": "autosheet",
},
},
{
"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": "channel map",
"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": scales.get("A"),
"tblock-key-sheet": "autosheet",
},
},
],
},
)
# 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)
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.dirpath("networks") |-- networks/
fileio.path("flattened network") | |-- flat.csv
fileio.path("chosen network") | |-- chosen.json
fileio.path("available network") | |-- available.json
fileio.path("chosen entity list") | |-- entities.json
fileio.dirpath("contexts") | L-- contexts/
fileio.dirpath("generated_svgs") |-- generated_svgs/
fileio.path("flattened network svg") | |-- flat.svg
fileio.path("circuits overlay") | |-- circuits.svg
fileio.path("cable overlay") | L-- cable.svg
fileio.path("build instructions") |-- build.py
fileio.path("instances list") |-- instances.tsv
fileio.path("library history") |-- libhist.tsv
fileio.dirpath("instance_data") L-- instance_data/