Materialfluss: --graphtype fuer --chain-graph (coarsebox/compound/subgraph)

--chain-graph zeichnete bisher immer nur die groben Objekte (Kreisel-Bahnen,
Strecke/Gefaellestrecke). Neuer Schalter --graphtype waehlt die Methode:
coarsebox (bisheriges Verhalten, unveraendert), compound (dieselbe
massstabsgetreue SVG, zusaetzlich die Anbauteile - Separator, Scanner,
Ausschleus-/Einschleuselement, Weiche - verschachtelt im Container an ihrer
echten Position, verbunden ueber die bereits aufgeloesten TrackGraph-Kanten)
und subgraph (dieselbe Verschachtelung als geschachtelte Graphviz-Cluster,
schematisch statt lagerichtig, <name>_chain.dot / mit --tosvg auch .svg).

_container_children() liest die Anbauteile direkt aus Element.track_ids -
derselben Quelle, die build_track_graph()/--track schon nutzt - statt sie wie
im Prototyp data/gen_chain_svg.py erneut aus der Nachbarn-Spalte herzuleiten.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Samer Ayadi
2026-09-22 16:26:02 +02:00
co-authored by Claude Sonnet 5
parent b197bf8150
commit 0477f05180
2 changed files with 300 additions and 19 deletions
+4 -2
View File
@@ -244,13 +244,15 @@ See `doc/track.md` for the fuller derivation notes.
| `--show-bbs` | — | off | Also write `<name>_bbs.svg`: a plain 2-D top-down view of every CSV row (not just flow nodes — Separator/Scanner/Ein-/Ausschleuselement too), one axis-aligned rectangle per row from the `Position` column (its center, X/Y) and the `Boundingbox` column (its width/depth, X/Y — the Z extent is ignored), with the row's `Bezeichnung` centered in it. A Kreisel is the one exception: instead of one rectangle for the whole ring, it's split into its two Bahn L/R halves (`_kreisel_lane_rects()` — short axis divided in half, `Zuordnung`-majority-vote decides which half is L vs R), each drawn and labelled separately; its K1–K4 points (with `--show-kx`) still belong to the Kreisel as a whole and are drawn once, not per half. No rotation, no Graphviz — hand-built SVG; the `viewBox` stays in plant millimetres (Y flipped so plant "up" renders as screen "up") but the printed page size is scaled to DIN A3 (landscape or portrait, whichever matches the layout's aspect ratio) instead of the plant's real-world extent. Larger objects are drawn first so small ones (Separator, Scanner, …) stay visible on top. |
| `--show-kx` | — | off | Only valid together with `--show-bbs` (else exit 1). Additionally draw each object's connection coordinate systems K1–K4 into the `_bbs.svg`: the K1–K4 columns are 12-char base64 strings (three 24-bit fixed-point values `x,y,z`, factor 10, `csv:trans-encode` in `dxfmakros/Lisp/export.lsp`) decoded to a plant-mm X/Y. Each present K-point is drawn as a small red cross (two lines) with a small `K1`…`K4` label, and consecutive points are joined `K1→K2→K3→K4` by a blue line. Empty K columns are skipped, so an object with only K1/K2 gets two crosses and one link. |
| `--track` | — | off | Additionally write the finer Separator/Ausschleus/Einschleus/Weiche graph described above (`<name>_track.dot`, and `<name>_track.svg` with `--tosvg`). Does not change the normal `_material_flow.*` output. See `doc/track.md`. |
| `--chain-graph` | — | off | Write **one** to-scale "chain-/Lageplan" SVG of the **grobe Objekte** (big items) as `<name>_chain.svg`: every Kreisel split into its Bahn L/R halves (`_kreisel_lane_rects()`, same to-scale split as `--show-bbs`) and every Strecke/Gefällestrecke ("VF"/"GF") drawn as rectangles at their real `Position`/`Boundingbox` coordinates, connected by the already-resolved top-level `graph.edges` (material flow, Kreisel-Umlauf, unresolved). Step 1 of the new chain-graph build-up — no Separator/Scanner/Weiche/Ausschleus-/Einschleuselement and no BTMT Ein-/Ausgang yet; an edge touching a node that isn't drawn (e.g. a station) is simply skipped. Hand-built SVG, no Graphviz. |
| `--chain-graph` | — | off | Write a "chain-/Lageplan" of the **grobe Objekte** (big items): every Kreisel split into its Bahn L/R halves (`_kreisel_lane_rects()`, same split as `--show-bbs`) and every Strecke/Gefällestrecke ("VF"/"GF"), connected by the already-resolved top-level `graph.edges` (material flow, Kreisel-Umlauf, unresolved). Still no BTMT Ein-/Ausgang — an edge touching a node that isn't drawn (e.g. a station) is simply skipped. Rendering method and output file are chosen by `--graphtype`. |
| `--graphtype` | `coarsebox`\|`compound`\|`subgraph` | `coarsebox` | Only valid together with `--chain-graph` (else exit 1). `coarsebox`: containers only (as above), to real scale, hand-built `<name>_chain.svg`, no Graphviz — the original behaviour. `compound`: the same to-scale picture, **plus** each container's Anbauteile (Separator, Scanner, Ausschleus-/Einschleuselement, Weiche) drawn nested inside it at their real position (`_container_children()` reads them off `Element.track_ids`, the same source `build_track_graph()`/`--track` uses — not a re-derivation from `Nachbarn` like the old `data/gen_chain_svg.py` prototype), linked by the already-resolved `TrackGraph.edges`; still `<name>_chain.svg`, no Graphviz. `subgraph`: the same containment expressed as **nested Graphviz clusters** (`subgraph cluster_*` — one per Kreisel, nested one per Bahn L/R, plus one per Strecke/Gefällestrecke, each containing its Anbauteile as child nodes) — schematic (`dot` auto-layout), **not** to scale; writes `<name>_chain.dot` always, and `<name>_chain.svg` additionally with `--tosvg` (needs Graphviz `dot`, unlike the other two graph types). |
**Output** (in `%SKEL_RESULTS%`): `<name>_material_flow.dot` (always, with `pos`
attributes when `--use-cords` was used), `<name>_material_flow.svg` (with `--tosvg`),
`<name>_material_flow.md` (with `--doc`), `<name>_connect.ini` (with `--connect`),
`<name>_bbs.svg` (with `--show-bbs`), `<name>_track.dot` / `<name>_track.svg` (with `--track`),
`<name>_chain.svg` (with `--chain-graph`).
`<name>_chain.svg` (with `--chain-graph --graphtype coarsebox|compound`, or `--graphtype subgraph --tosvg`),
`<name>_chain.dot` (with `--chain-graph --graphtype subgraph`).
**Exit codes:** `0` ok · `1` input/CLI error · `2` SVG rendering failed
(Graphviz).
+296 -17
View File
@@ -2479,6 +2479,45 @@ def _kreisel_lane_rects(
return {lane_a: half_a, lane_b: half_b}
def _lane_of_point(pt: tuple[float, float], lane_rects: dict[str, tuple[float, float, float, float]]) -> str:
"""Fallback-Bahnzuordnung ueber die Geometrie (naechstes Rechteck), falls ein
Feinelement keine auswertbare '<Kreisel>-L'/'-R'-Zuordnung traegt (siehe
_container_children)."""
px, py = pt
def dist(rect: tuple[float, float, float, float]) -> float:
x0, y0, x1, y1 = rect
cx, cy = min(max(px, x0), x1), min(max(py, y0), y1)
return (cx - px) ** 2 + (cy - py) ** 2
return min(lane_rects, key=lambda lane: dist(lane_rects[lane]))
def _container_children(element: Element, by_id: dict[str, Element]) -> list[Element]:
"""
Anbauteile (Separator, Scanner, Ausschleus-/Einschleuselement, Weiche) eines
Containers (Kreisel/Strecke/Gefaellestrecke) aus seiner TrackIds-Kette, in
Reihenfolge - fuer die verschachtelte Darstellung in --graphtype compound/
subgraph. Nutzt dieselbe Quelle (Element.track_ids) und dieselbe
Richtungs-Regel fuer Strecke/Gefaellestrecke wie build_track_graph() Schritt 1
(Kreisel-seitiges Element zuerst umdrehen), statt Nachbarn-Heuristiken wie im
Prototyp data/gen_chain_svg.py neu zu erfinden.
"""
chain = list(element.track_ids)
if element.is_transport and chain:
first_el, last_el = by_id.get(chain[0]), by_id.get(chain[-1])
first_kind = first_el.kind if first_el else ""
last_kind = last_el.kind if last_el else ""
if first_kind == "Einschleus" or last_kind == "Ausschleus":
chain = list(reversed(chain))
children: list[Element] = []
for tid in chain:
el = by_id.get(tid)
if el is not None and el.kind in TRACK_KINDS:
children.append(el)
return children
# ---------------------------------------------------------------------------
# Chain-/Lageplan-SVG der groben Objekte erzeugen (--chain-graph)
# ---------------------------------------------------------------------------
@@ -2506,7 +2545,9 @@ def _chain_marker_id(color: str) -> str:
return "chainArrow_" + color.lstrip("#")
def render_chain_svg(elements: list[Element], graph: Graph, source: str) -> str:
def render_chain_svg(
elements: list[Element], graph: Graph, source: str, track: TrackGraph | None = None
) -> str:
"""
Massstabsgetreuer Chain-/Lageplan der groben Objekte (--chain-graph): jeder
Kreisel als seine beiden Bahnen L/R (_kreisel_lane_rects(), Zuordnung aus
@@ -2515,6 +2556,16 @@ def render_chain_svg(elements: list[Element], graph: Graph, source: str) -> str:
Boundingbox. Kanten kommen direkt aus dem schon aufgeloesten groben
Materialfluss-Graphen (graph.edges) - eine Kante, deren Knoten hier nicht
gezeichnet wird (z. B. eine BTMT-Station), wird schlicht uebersprungen.
track nur fuer --graphtype compound (siehe render_chain_compound_svg): mit
angegebenem TrackGraph werden zusaetzlich die Anbauteile (Separator,
Scanner, Ausschleus-/Einschleuselement, Weiche) jedes Containers an
ihrer echten Position hinein verschachtelt gezeichnet, verbunden mit
den schon aufgeloesten Kanten aus track.edges (_container_children()
liefert die Reihenfolge aus derselben TrackIds-Quelle, die auch
build_track_graph() benutzt - keine eigene Nachbarn-Herleitung wie im
Prototyp data/gen_chain_svg.py). None (Default) = reines --graphtype
coarsebox ohne Anbauteile, wie bisher.
"""
rects: dict[str, tuple[float, float, float, float]] = {}
lane_info: dict[str, tuple[Element, str]] = {}
@@ -2629,6 +2680,58 @@ def render_chain_svg(elements: list[Element], graph: Graph, source: str) -> str:
f'fill="{style["color"]}">{_xml_escape(label)}</text>'
)
nested_svg: list[str] = []
if track is not None:
by_id = {e.teile_id: e for e in elements}
kreisel_lane_rects: dict[str, dict[str, tuple[float, float, float, float]]] = {}
for node_id, rect in rects.items():
if node_id in lane_info:
kreisel, lane = lane_info[node_id]
lane_rects = kreisel_lane_rects.setdefault(
kreisel.teile_id, _kreisel_lane_rects(kreisel, elements)
)
children = []
for el in _container_children(kreisel, by_id):
if el.x is None or el.y is None:
continue
base, _, el_lane = el.assignment.partition("-")
if base != kreisel.teile_id or el_lane not in (LANE_LEFT, LANE_RIGHT):
el_lane = _lane_of_point((el.x, el.y), lane_rects)
if el_lane == lane:
children.append(el)
else:
strecke = strecke_labels[node_id]
children = [
el for el in _container_children(strecke, by_id)
if el.x is not None and el.y is not None
]
if not children:
continue
rx0, ry0, rx1, ry1 = rect
child_ids = {el.teile_id for el in children}
edge_color = CHAIN_EDGE_COLOR[EDGE_CIRCLE if node_id in lane_info else EDGE_FLOW]
for te in track.edges:
if te.src not in child_ids or te.dst not in child_ids:
continue
src_el, dst_el = by_id[te.src], by_id[te.dst]
x1, y1 = sx(min(max(src_el.x, rx0), rx1)), sy(min(max(src_el.y, ry0), ry1))
x2, y2 = sx(min(max(dst_el.x, rx0), rx1)), sy(min(max(dst_el.y, ry0), ry1))
nested_svg.append(
f'<line x1="{x1:.1f}" y1="{y1:.1f}" x2="{x2:.1f}" y2="{y2:.1f}" '
f'stroke="{edge_color}" stroke-width="1"/>'
)
for el in children:
style = TRACK_NODE_STYLES.get(el.kind, DEFAULT_NODE_STYLE)
cx, cy = min(max(el.x, rx0), rx1), min(max(el.y, ry0), ry1)
px, py = sx(cx), sy(cy)
title = f"{el.kind} {el.name or el.teile_id} [{el.teile_id}]"
nested_svg.append(
f'<circle cx="{px:.1f}" cy="{py:.1f}" r="3.4" fill="{style["fillcolor"]}" '
f'stroke="{style["color"]}" stroke-width="1.2"><title>{_xml_escape(title)}</title></circle>'
)
heading = f"{Path(source).stem} - Materialfluss (grobe Objekte)"
out: list[str] = []
@@ -2655,10 +2758,142 @@ def render_chain_svg(elements: list[Element], graph: Graph, source: str) -> str:
f'Massstabsgetreu &#8226; Quelle: {_xml_escape(source)}</text>')
out.extend(box_svg)
out.extend(edge_svg)
out.extend(nested_svg)
add("</svg>")
return "\n".join(out) + "\n"
def render_chain_compound_svg(
elements: list[Element], graph: Graph, track: TrackGraph, source: str
) -> str:
"""--graphtype compound: wie render_chain_svg(), aber mit den Anbauteilen
(Separator, Scanner, Ausschleus-/Einschleuselement, Weiche) verschachtelt in
ihrem Container gezeichnet. Duenner Wrapper um render_chain_svg(track=...),
damit Rechteck-/Massstab-/Kanten-Aufbau nicht doppelt existiert."""
return render_chain_svg(elements, graph, source, track=track)
# ---------------------------------------------------------------------------
# Chain-/Lageplan als verschachtelte Graphviz-Cluster (--graphtype subgraph)
# ---------------------------------------------------------------------------
#
# Schematische (nicht massstabsgetreue) Alternative zu render_chain_svg(track=...):
# dieselbe Containment - Kreisel/Bahn/Strecke enthaelt seine Anbauteile - aber als
# verschachtelte Graphviz "subgraph cluster_*"-Bloecke statt handgebauter SVG-
# Rechtecke, gerendert von Graphviz' eigenem Layout (dot). Nuetzlich, wenn die
# Verschachtelung selbst interessiert und nicht die reale Geometrie.
CHAIN_SUBGRAPH_EDGE = {
EDGE_FLOW: '[color="#2f5597", penwidth=1.4]',
EDGE_CIRCLE: '[color="#8ea9db", style=dashed, penwidth=1.2, constraint=false]',
EDGE_UNRESOLVED: '[color="#bf8f00", style=dashed, dir=both]',
}
def _chain_child_dot(node_id: str, element: Element) -> str:
style = TRACK_NODE_STYLES.get(element.kind, DEFAULT_NODE_STYLE)
attrs = ", ".join(f'{key}="{value}"' for key, value in style.items())
label = _dot_label([f"{element.name or element.teile_id}", f"{element.kind} [{element.teile_id}]"])
return f' "{node_id}" [label="{label}", {attrs}];'
def render_chain_subgraph_dot(
elements: list[Element], graph: Graph, track: TrackGraph, source: str
) -> str:
"""
--graphtype subgraph: dieselbe Containment wie render_chain_compound_svg(),
aber als verschachtelte Graphviz-Cluster (Kreisel-Cluster mit einem
Bahn-L/R-Cluster je Bahn darin, Strecke/Gefaellestrecke-Cluster) statt
massstabsgetreuer SVG. Rendern mit dot -Tsvg (siehe dot_to_svg()) - NICHT
lagerichtig, das Layout kommt von Graphviz.
"""
by_id = {e.teile_id: e for e in elements}
out: list[str] = []
add = out.append
add("// Automatisch erzeugt von lib/material_flow.py - nicht manuell aendern.")
add(f"// Quelle: {source}")
add("// --chain-graph --graphtype subgraph: verschachtelte Cluster, nicht lagerichtig.")
add("digraph Materialfluss_Chain {")
add(' graph [rankdir=LR, splines=spline, nodesep=0.22, ranksep=1.2,')
add(' fontname="Segoe UI", fontsize=11, labelloc="t",')
add(f' label="Chain-Graph (verschachtelt) - {_dot_escape(source)}"];')
add(' node [shape=box, style="rounded,filled", fontname="Segoe UI", fontsize=8];')
add(' edge [fontname="Segoe UI", fontsize=8, arrowsize=0.8];')
add("")
top_ids: set[str] = set()
for index, element in enumerate(
sorted((e for e in elements if e.is_circle and e.position and e.bbox), key=lambda e: e.teile_id)
):
add(f" subgraph cluster_kreisel_{index} {{")
add(f' label="{_dot_escape(element.kind)} {_dot_escape(element.name or element.teile_id)} '
f'[{element.teile_id}]"; style="rounded,filled"; fillcolor="#eef2fa"; color="#2f5597";')
add(' fontname="Segoe UI"; fontsize=10; fontcolor="#1f3864"; margin=16;')
lane_rects = _kreisel_lane_rects(element, elements)
for lane in sorted(lane_rects):
lane_id = f"{element.teile_id}-{lane}"
top_ids.add(lane_id)
add(f' subgraph "cluster_{lane_id}" {{')
add(f' label="Bahn {lane}"; style="rounded,filled"; fillcolor="#ffffff"; '
f'color="#8ea9db"; fontname="Segoe UI"; fontsize=9; fontcolor="#375623"; margin=10;')
add(f' "{lane_id}" [label="Bahn {lane}", shape=box, style="rounded,filled", '
f'fillcolor="#2f5597", fontcolor="#ffffff", color="#1f3864"];')
children = []
for el in _container_children(element, by_id):
if el.x is None or el.y is None:
continue
base, _, el_lane = el.assignment.partition("-")
if base != element.teile_id or el_lane not in (LANE_LEFT, LANE_RIGHT):
el_lane = _lane_of_point((el.x, el.y), lane_rects)
if el_lane == lane:
children.append(el)
child_ids = {el.teile_id for el in children}
for el in children:
add(_chain_child_dot(el.teile_id, el))
for te in track.edges:
if te.src in child_ids and te.dst in child_ids:
add(f' "{te.src}" -> "{te.dst}" [color="#8ea9db"];')
add(" }")
add(" }")
add("")
for index, element in enumerate(
sorted((e for e in elements if e.is_transport and e.position and e.bbox), key=lambda e: e.teile_id)
):
node_id = element.teile_id
top_ids.add(node_id)
add(f" subgraph cluster_strecke_{index} {{")
add(f' label="{_dot_escape(element.name or node_id)} [{node_id}]"; '
f'style="rounded,filled"; fillcolor="#f5faf0"; color="#548235";')
add(' fontname="Segoe UI"; fontsize=10; fontcolor="#375623"; margin=14;')
style = NODE_STYLES.get(element.kind, DEFAULT_NODE_STYLE)
attrs = ", ".join(f'{key}="{value}"' for key, value in style.items())
add(f' "{node_id}" [label="{_dot_escape(element.name or node_id)}", {attrs}];')
children = [
el for el in _container_children(element, by_id)
if el.x is not None and el.y is not None
]
child_ids = {el.teile_id for el in children}
for el in children:
add(_chain_child_dot(el.teile_id, el))
for te in track.edges:
if te.src in child_ids and te.dst in child_ids:
add(f' "{te.src}" -> "{te.dst}" [color="#2f5597"];')
add(" }")
add("")
for edge in graph.edges:
if edge.src not in top_ids or edge.dst not in top_ids:
continue
attrs = CHAIN_SUBGRAPH_EDGE.get(edge.kind, CHAIN_SUBGRAPH_EDGE[EDGE_FLOW])
add(f' "{edge.src}" -> "{edge.dst}" {attrs};')
add("}")
return "\n".join(out) + "\n"
# ---------------------------------------------------------------------------
# SVG erzeugen
# ---------------------------------------------------------------------------
@@ -2935,6 +3170,7 @@ def report(
bbs_file: Path | None = None,
track_dot_file: Path | None = None,
track_svg_file: Path | None = None,
chain_dot_file: Path | None = None,
chain_file: Path | None = None,
) -> None:
kinds: dict[str, int] = {}
@@ -2968,7 +3204,11 @@ def report(
print(f"Bahnzusammenhang = {track_dot_file if track_dot_file else '- (ohne --track)'}")
if track_dot_file:
print(f" SVG = {track_svg_file if track_svg_file else '- (ohne --tosvg)'}")
print(f"Chain-Graph = {chain_file if chain_file else '- (ohne --chain-graph)'}")
if chain_dot_file:
print(f"Chain-Graph = {chain_dot_file}")
print(f" SVG = {chain_file if chain_file else '- (ohne --tosvg)'}")
else:
print(f"Chain-Graph = {chain_file if chain_file else '- (ohne --chain-graph)'}")
print("================================================================")
if stale_svg is not None:
@@ -3020,6 +3260,8 @@ def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
" material_flow.bat --file HundM05.csv --show-bbs\n"
" material_flow.bat --file mubea.csv --track --tosvg\n"
" material_flow.bat --file HundM05.csv --chain-graph\n"
" material_flow.bat --file HundM05.csv --chain-graph --graphtype compound\n"
" material_flow.bat --file HundM05.csv --chain-graph --graphtype subgraph --tosvg\n"
"\n"
"Exit-Codes:\n"
" 0 ok\n"
@@ -3093,13 +3335,24 @@ def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
"--chain-graph",
dest="chain_graph",
action="store_true",
help="Massstabsgetreuen Chain-/Lageplan der groben Objekte als "
"<name>_chain.svg nach %%SKEL_RESULTS%% schreiben: jeder Kreisel "
"(je Bahn L/R) und jede Strecke/Gefaellestrecke (VF/GF) als "
"Rechteck an ihrer echten Position/Boundingbox, verbunden durch "
"die bereits aufgeloesten Materialfluss-Kanten. Schritt 1 - noch "
"ohne Anbauteile (Separator, Scanner, Weiche, ...) oder Ein-/"
"Ausgabestationen. Braucht kein Graphviz.",
help="Chain-/Lageplan der groben Objekte (Kreisel je Bahn L/R, Strecke/"
"Gefaellestrecke) nach %%SKEL_RESULTS%% schreiben, verbunden durch "
"die bereits aufgeloesten Materialfluss-Kanten. Noch ohne Ein-/"
"Ausgabestationen. Methode/Ausgabedatei je --graphtype.",
)
parser.add_argument(
"--graphtype",
choices=("coarsebox", "compound", "subgraph"),
default="coarsebox",
help="Nur zusammen mit --chain-graph: Darstellungsmethode. 'coarsebox' "
"(Default) = nur die groben Objekte, massstabsgetreu, wie bisher "
"(<name>_chain.svg, kein Graphviz). 'compound' = zusaetzlich die "
"Anbauteile (Separator, Scanner, Ausschleus-/Einschleuselement, "
"Weiche) massstabsgetreu in ihrem Container verschachtelt "
"(<name>_chain.svg, kein Graphviz). 'subgraph' = dieselbe "
"Verschachtelung als geschachtelte Graphviz-Cluster, schematisch "
"statt lagerichtig (<name>_chain.dot, mit --tosvg zusaetzlich "
"<name>_chain.svg).",
)
return parser.parse_args(argv)
@@ -3139,6 +3392,11 @@ def main(argv: list[str] | None = None) -> int:
file=sys.stderr)
return 1
if args.graphtype != "coarsebox" and not args.chain_graph:
print("FEHLER: --graphtype ist nur zusammen mit --chain-graph moeglich.",
file=sys.stderr)
return 1
bbs_file = None
if args.show_bbs:
bbs_target = results / f"{stem}_bbs.svg"
@@ -3214,8 +3472,10 @@ def main(argv: list[str] | None = None) -> int:
track: TrackGraph | None = None
track_dot_file = track_svg_file = None
if args.track:
needs_track = args.track or (args.chain_graph and args.graphtype != "coarsebox")
if needs_track:
track = build_track_graph(elements, by_id, graph, warnings)
if args.track:
track_dot_target = results / f"{stem}_track.dot"
track_dot_target.write_text(render_track_dot(track), encoding="utf-8")
track_dot_file = track_dot_target
@@ -3228,15 +3488,33 @@ def main(argv: list[str] | None = None) -> int:
print(f"FEHLER: Bahnzusammenhang-SVG nicht erzeugt: {exc}", file=sys.stderr)
chain_file = None
chain_dot_file = None
if args.chain_graph:
try:
svg = render_chain_svg(elements, graph, csv_file.name)
except ValueError as exc:
print(f"WARNUNG: Chain-Graph: {exc} - nicht erzeugt.", file=sys.stderr)
if args.graphtype == "subgraph":
chain_dot_target = results / f"{stem}_chain.dot"
chain_dot_target.write_text(
render_chain_subgraph_dot(elements, graph, track, csv_file.name), encoding="utf-8"
)
chain_dot_file = chain_dot_target
if args.tosvg:
chain_svg_target = results / f"{stem}_chain.svg"
try:
dot_to_svg(chain_dot_file, chain_svg_target)
chain_file = chain_svg_target
except RuntimeError as exc:
print(f"FEHLER: Chain-Graph-SVG nicht erzeugt: {exc}", file=sys.stderr)
else:
chain_target = results / f"{stem}_chain.svg"
chain_target.write_text(svg, encoding="utf-8")
chain_file = chain_target
try:
if args.graphtype == "compound":
svg = render_chain_compound_svg(elements, graph, track, csv_file.name)
else:
svg = render_chain_svg(elements, graph, csv_file.name)
except ValueError as exc:
print(f"WARNUNG: Chain-Graph: {exc} - nicht erzeugt.", file=sys.stderr)
else:
chain_target = results / f"{stem}_chain.svg"
chain_target.write_text(svg, encoding="utf-8")
chain_file = chain_target
report(
graph,
@@ -3249,6 +3527,7 @@ def main(argv: list[str] | None = None) -> int:
bbs_file=bbs_file,
track_dot_file=track_dot_file,
track_svg_file=track_svg_file,
chain_dot_file=chain_dot_file,
chain_file=chain_file,
)