1"""Which part of a drawing is the vehicle.
3A stand drawing can carry its ground support on the same sheet: pid-designer draws the rocket on one
4page and the GSE on another, joined only by quick-disconnects paired across the pages
5(``options.pairedWith``). The feed twin mates those pairs into one network, because filling the
6vehicle is what the GSE is for. Layer X burns the vehicle: its propellant tanks are the tanks that
7feed the engine and its pressurant bottle is the bottle that presses them, not a GSE fuel-transfer
8tank or a 49 L K-bottle on the cart that happen to hold the same fluids.
10The vehicle is everything joined to the engine by a drawn line, walked through any symbol. A paired
11disconnect is not a drawn line, so the walk stops at the coupling, which is where the vehicle ends.
12A drawing whose GSE is drawn with lines into the vehicle, or that has no GSE, is one piece and the
13whole drawing is the vehicle: every drawing that ran before reads exactly as it did.
16from __future__
import annotations
19from typing
import Any, Dict, FrozenSet, Iterable, List, Optional, Tuple
23 """The ids of every symbol joined to an ENGINE by drawn lines, or None without an engine.
25 The physics library's rule (:func:`feedtwin.pid.roles.vehicle_ids`), so the cockpit and Layer X
26 never disagree about which tank is the vehicle's.
30 return library_vehicle_ids(diagram)
33def on_vehicle(nodes: Iterable[Any], vehicle: Optional[FrozenSet[str]]) -> List[Any]:
34 """``nodes`` that are on the vehicle; all of them when the drawing has no engine to scope by."""
36 return nodes
if vehicle
is None else [n
for n
in nodes
if n.id
in vehicle]
39def off_vehicle(nodes: Iterable[Any], vehicle: Optional[FrozenSet[str]]) -> List[Any]:
40 """``nodes`` that are ground support (not joined to the engine by a line)."""
41 return []
if vehicle
is None else [n
for n
in nodes
if n.id
not in vehicle]
45 """``payload`` cut to the vehicle, and the labels of the ground-support vessels left out.
47 Layer X primes the vehicle itself -- the loads, the bottle's fill, the regulator's dome -- which
48 is what the GSE is for, and then fires it. Left in, a GSE page mated through its paired
49 disconnects keeps working the vehicle during the burn: on LE4 the GSE dome regulator, mated to
50 the vehicle's dome line, loaded the press regulator past Layer X's lockup and tripped the LOX
51 tank at 802 psig before T-0. Cut, each vehicle-side disconnect half is capped, as a stand with
52 its GSE unplugged has it. A drawing that is one piece comes back unchanged (the same object).
56 diagram = read_diagram(payload, name=
"vehicle")
58 if vehicle
is None or len(vehicle) == len(diagram.nodes):
60 ground = [n.label
or n.id
for n
in diagram.nodes
61 if n.id
not in vehicle
and n.type
in (
"TANK",
"KBOTTLE",
"DEWAR")]
62 out = copy.deepcopy(payload)
63 out[
"nodes"] = [n
for n
in out.get(
"nodes")
or []
if isinstance(n, dict)
and str(n.get(
"id"))
in vehicle]
64 out[
"edges"] = [e
for e
in out.get(
"edges")
or []
if isinstance(e, dict)
65 and str(e.get(
"source"))
in vehicle
and str(e.get(
"target"))
in vehicle]
66 for node
in out[
"nodes"]:
67 options = (node.get(
"data")
or {}).get(
"options")
68 if isinstance(options, dict)
and str(options.get(
"pairedWith")
or "")
not in (
"",
"none") \
69 and str(options[
"pairedWith"])
not in vehicle:
70 options[
"pairedWith"] =
""
Optional[FrozenSet[str]] vehicle_ids(Any diagram)
List[Any] off_vehicle(Iterable[Any] nodes, Optional[FrozenSet[str]] vehicle)
List[Any] on_vehicle(Iterable[Any] nodes, Optional[FrozenSet[str]] vehicle)
Tuple[Dict[str, Any], List[str]] vehicle_payload(Dict[str, Any] payload)