Materialfluss: Ein-/Ausgabestationen im Chain-Graph zeichnen
BTMT Beladung/Entladung bzw. die generischen Eingang/Ausgang-Symbole waren bislang von --chain-graph ausgenommen und wurden als Kantenziel einfach uebersprungen. Die Anlage laeuft als Kreislauf, diese Stationen sind ihre einzigen Materialquellen/-senken und gehoeren als eigene Knoten in jeden Graphtyp (coarsebox/compound/subgraph). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
0477f05180
commit
df6d56090e
@@ -244,7 +244,7 @@ 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 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`. |
|
||||
| `--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`), every Strecke/Gefällestrecke ("VF"/"GF"), and every Ein-/Ausgabestation (BTMT `Beladung`/`Entladung`, or the generic `Eingang`/`Ausgang` symbol where no BTMT station absorbed it — see `_merge_station_symbols()`) — the plant runs as a closed loop, so these stations are its only material sources/sinks and are drawn like any other node, not skipped. Connected by the already-resolved top-level `graph.edges` (material flow, Kreisel-Umlauf, unresolved). 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`
|
||||
|
||||
+56
-10
@@ -2551,11 +2551,19 @@ def render_chain_svg(
|
||||
"""
|
||||
Massstabsgetreuer Chain-/Lageplan der groben Objekte (--chain-graph): jeder
|
||||
Kreisel als seine beiden Bahnen L/R (_kreisel_lane_rects(), Zuordnung aus
|
||||
allen Elementen fuer den Mehrheitsentscheid - wie --show-bbs) und jede
|
||||
allen Elementen fuer den Mehrheitsentscheid - wie --show-bbs), jede
|
||||
Strecke/Gefaellestrecke als ein Rechteck an ihrer echten Position/
|
||||
Boundingbox. Kanten kommen direkt aus dem schon aufgeloesten groben
|
||||
Boundingbox, und die BTMT-/generischen Ein-/Ausgabestationen (Beladung/
|
||||
Entladung/Eingang/Ausgang) ebenso - die Anlage laeuft als Kreislauf, die
|
||||
Stationen sind seine einzigen Materialquellen/-senken und gehoeren mit in
|
||||
jeden Chain-Graph. Gezeichnet werden nur Stationen, die auch echte Knoten
|
||||
im groben Materialfluss-Graphen sind (graph.nodes) - ein durch eine
|
||||
deckungsgleiche BTMT-Station verschlucktes generisches Eingang/Ausgang-
|
||||
Symbol (_merge_station_symbols(), siehe build_graph()) taucht folgerichtig
|
||||
nicht doppelt auf. 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.
|
||||
gezeichnet wird (z. B. eine Station ohne Position/Boundingbox), wird
|
||||
schlicht uebersprungen.
|
||||
|
||||
track nur fuer --graphtype compound (siehe render_chain_compound_svg): mit
|
||||
angegebenem TrackGraph werden zusaetzlich die Anbauteile (Separator,
|
||||
@@ -2570,6 +2578,7 @@ def render_chain_svg(
|
||||
rects: dict[str, tuple[float, float, float, float]] = {}
|
||||
lane_info: dict[str, tuple[Element, str]] = {}
|
||||
strecke_labels: dict[str, Element] = {}
|
||||
station_labels: dict[str, Element] = {}
|
||||
|
||||
for element in elements:
|
||||
if element.position is None or element.bbox is None:
|
||||
@@ -2586,6 +2595,16 @@ def render_chain_svg(
|
||||
)
|
||||
strecke_labels[element.teile_id] = element
|
||||
|
||||
for node in graph.nodes.values():
|
||||
element = node.element
|
||||
if not element.is_station or element.position is None or element.bbox is None:
|
||||
continue
|
||||
rects[node.node_id] = (
|
||||
element.x - element.bbox_x / 2, element.y - element.bbox_y / 2,
|
||||
element.x + element.bbox_x / 2, element.y + element.bbox_y / 2,
|
||||
)
|
||||
station_labels[node.node_id] = element
|
||||
|
||||
if not rects:
|
||||
raise ValueError("keine Kreisel/Strecken mit Position/Boundingbox gefunden")
|
||||
|
||||
@@ -2666,10 +2685,15 @@ def render_chain_svg(
|
||||
kreisel, lane = lane_info[node_id]
|
||||
style = NODE_STYLES.get(kreisel.kind, DEFAULT_NODE_STYLE)
|
||||
label = f"{kreisel.name or kreisel.teile_id} Bahn {lane}"
|
||||
else:
|
||||
elif node_id in strecke_labels:
|
||||
strecke = strecke_labels[node_id]
|
||||
style = NODE_STYLES.get(strecke.kind, DEFAULT_NODE_STYLE)
|
||||
label = f"{strecke.name or node_id} [{node_id}]"
|
||||
else:
|
||||
station = station_labels[node_id]
|
||||
style = NODE_STYLES.get(station.kind, DEFAULT_NODE_STYLE)
|
||||
role = "Materialeingang" if station.is_entry else "Materialausgang"
|
||||
label = f"{station.name or node_id} [{node_id}] - {role}"
|
||||
box_svg.append(
|
||||
f'<rect x="{sx(x0):.1f}" y="{sy(y1):.1f}" width="{(x1 - x0) * scale:.1f}" '
|
||||
f'height="{(y1 - y0) * scale:.1f}" fill="{style["fillcolor"]}" fill-opacity="0.3" '
|
||||
@@ -2700,12 +2724,14 @@ def render_chain_svg(
|
||||
el_lane = _lane_of_point((el.x, el.y), lane_rects)
|
||||
if el_lane == lane:
|
||||
children.append(el)
|
||||
else:
|
||||
elif node_id in strecke_labels:
|
||||
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
|
||||
]
|
||||
else:
|
||||
continue # Station - kein Container, keine Anbauteile zum Verschachteln
|
||||
if not children:
|
||||
continue
|
||||
|
||||
@@ -2804,8 +2830,11 @@ def render_chain_subgraph_dot(
|
||||
--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.
|
||||
massstabsgetreuer SVG, plus die Ein-/Ausgabestationen (Beladung/Entladung/
|
||||
Eingang/Ausgang) als eigene, nicht verschachtelte Knoten - die Anlage
|
||||
laeuft als Kreislauf, sie sind seine einzigen Materialquellen/-senken.
|
||||
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}
|
||||
|
||||
@@ -2884,6 +2913,21 @@ def render_chain_subgraph_dot(
|
||||
add(" }")
|
||||
add("")
|
||||
|
||||
# Ein-/Ausgabestationen (Beladung/Entladung/Eingang/Ausgang): die Anlage
|
||||
# laeuft als Kreislauf, sie sind seine einzigen Materialquellen/-senken -
|
||||
# eigene (nicht verschachtelte) Knoten, damit ihre schon aufgeloesten
|
||||
# Kanten unten nicht wie bisher uebersprungen werden.
|
||||
for node in sorted(graph.nodes.values(), key=lambda n: n.node_id):
|
||||
if not node.element.is_station:
|
||||
continue
|
||||
top_ids.add(node.node_id)
|
||||
style = NODE_STYLES.get(node.element.kind, DEFAULT_NODE_STYLE)
|
||||
attrs = ", ".join(f'{key}="{value}"' for key, value in style.items())
|
||||
role = "Materialeingang" if node.element.is_entry else "Materialausgang"
|
||||
label = _dot_label([node.element.name or node.node_id, f"{node.element.kind} - {role}"])
|
||||
add(f' "{node.node_id}" [label="{label}", {attrs}];')
|
||||
add("")
|
||||
|
||||
for edge in graph.edges:
|
||||
if edge.src not in top_ids or edge.dst not in top_ids:
|
||||
continue
|
||||
@@ -3336,9 +3380,11 @@ def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
|
||||
dest="chain_graph",
|
||||
action="store_true",
|
||||
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.",
|
||||
"Gefaellestrecke, plus Beladung/Entladung/Eingang/Ausgang - die "
|
||||
"Anlage laeuft als Kreislauf, sie sind seine einzigen Material-"
|
||||
"quellen/-senken) nach %%SKEL_RESULTS%% schreiben, verbunden durch "
|
||||
"die bereits aufgeloesten Materialfluss-Kanten. Methode/Ausgabedatei "
|
||||
"je --graphtype.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--graphtype",
|
||||
|
||||
Reference in New Issue
Block a user