kundenkarte/lib/wiring_diagram.lib.php
data 8826c286ef feat(schematic): Leitungslaufplan PDF-Export nach DIN EN 61082
Neues separates Feature: Stromlaufplan in aufgelöster Darstellung als PDF.

- WiringDiagramAnalyzer: PHP-Port der JS Phase-Map-Logik, Strompfad-Tracing
- WiringDiagramRenderer: TCPDF-Zeichnung mit VDE-Symbolen (LS, FI/RCD)
- PDF enthält: Schaltplan, Abgangsverzeichnis pro Hutschiene, Legende
- Abgangsnummern im Format R{Reihe}.{Position}
- Grüner Button in Schaltplan-Editor (Kunden + Kontakte)
- CLAUDE.md: Dateistruktur komplett überarbeitet, neue Features dokumentiert
- Version 9.7

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 17:06:26 +01:00

1239 lines
40 KiB
PHP

<?php
/* Copyright (C) 2026 Alles Watt lauft
*
* Leitungslaufplan (Stromlaufplan in aufgelöster Darstellung)
* PDF-Export nach DIN EN 61082
*
* Dieses Feature ist vollständig separiert und kann ohne Auswirkungen
* auf den restlichen Code entfernt werden.
*/
dol_include_once('/kundenkarte/class/anlage.class.php');
dol_include_once('/kundenkarte/class/equipment.class.php');
dol_include_once('/kundenkarte/class/equipmentpanel.class.php');
dol_include_once('/kundenkarte/class/equipmentcarrier.class.php');
dol_include_once('/kundenkarte/class/equipmentconnection.class.php');
/**
* Phasenfarben nach DIN VDE (RGB-Arrays für TCPDF)
*/
function getPhaseColorRGB($phase)
{
$colors = array(
'L1' => array(139, 69, 19), // Braun
'L2' => array(26, 26, 26), // Schwarz
'L3' => array(102, 102, 102), // Grau
'N' => array(0, 102, 204), // Blau
'PE' => array(39, 174, 96), // Grün
'LN' => array(139, 69, 19), // Braun
'3P' => array(155, 89, 182), // Lila
'3P+N' => array(52, 73, 94), // Dunkelblau
);
$p = strtoupper($phase);
return isset($colors[$p]) ? $colors[$p] : array(136, 136, 136);
}
/**
* Phase-Labels parsen (PHP-Port von JS parsePhaseLabels)
*/
function parsePhaseLabels($phases)
{
if (empty($phases)) return array();
$p = strtoupper(trim($phases));
$map = array(
'3P' => array('L1', 'L2', 'L3'),
'L1L2L3' => array('L1', 'L2', 'L3'),
'3P+N' => array('L1', 'L2', 'L3', 'N'),
'3PN' => array('L1', 'L2', 'L3', 'N'),
'3P+N+PE' => array('L1', 'L2', 'L3', 'N', 'PE'),
'3PNPE' => array('L1', 'L2', 'L3', 'N', 'PE'),
'L1N' => array('L1', 'N'),
'L1+N' => array('L1', 'N'),
'L1' => array('L1'),
'L2' => array('L2'),
'L3' => array('L3'),
'N' => array('N'),
'PE' => array('PE'),
);
if (isset($map[$p])) return $map[$p];
if (strpos($p, '+') !== false) return explode('+', $p);
if (strpos($p, ',') !== false) return array_map('trim', explode(',', $p));
return array($phases);
}
/**
* Terminals eines Equipment ermitteln (PHP-Port von JS getTerminals)
*/
function getEquipmentTerminals($eq)
{
// terminals_config aus dem Equipment-Typ
$terminalsConfig = isset($eq->terminals_config) ? $eq->terminals_config : '';
if (!empty($terminalsConfig)) {
// Literale \r\n bereinigen
$configStr = str_replace(array("\\r\\n", "\\r", "\\n", "\\t"), array(' ', ' ', ' ', ''), $terminalsConfig);
$config = @json_decode($configStr, true);
if (is_array($config) && isset($config['terminals'])) {
return $config['terminals'];
}
if (is_array($config)) {
$terminals = array();
if (isset($config['inputs'])) {
foreach ($config['inputs'] as $t) {
$terminals[] = array('id' => $t['id'], 'label' => $t['label'] ?? '●', 'pos' => 'top');
}
}
if (isset($config['outputs'])) {
foreach ($config['outputs'] as $t) {
$terminals[] = array('id' => $t['id'], 'label' => $t['label'] ?? '●', 'pos' => 'bottom');
}
}
if (!empty($terminals)) return $terminals;
}
}
// Default-Terminals nach type_ref
$typeRef = strtoupper(isset($eq->type_ref) ? $eq->type_ref : '');
$defaults = array(
'LS' => array(array('id'=>'t1','pos'=>'top'), array('id'=>'t2','pos'=>'bottom')),
'FI' => array(
array('id'=>'t1','pos'=>'top'), array('id'=>'t2','pos'=>'top'),
array('id'=>'t3','pos'=>'top'), array('id'=>'t4','pos'=>'top'),
array('id'=>'t5','pos'=>'bottom'), array('id'=>'t6','pos'=>'bottom'),
array('id'=>'t7','pos'=>'bottom'), array('id'=>'t8','pos'=>'bottom'),
),
'FI4P' => array(
array('id'=>'t1','pos'=>'top'), array('id'=>'t2','pos'=>'top'),
array('id'=>'t3','pos'=>'top'), array('id'=>'t4','pos'=>'top'),
array('id'=>'t5','pos'=>'bottom'), array('id'=>'t6','pos'=>'bottom'),
array('id'=>'t7','pos'=>'bottom'), array('id'=>'t8','pos'=>'bottom'),
),
'LS3P' => array(
array('id'=>'t1','pos'=>'top'), array('id'=>'t2','pos'=>'top'), array('id'=>'t3','pos'=>'top'),
array('id'=>'t4','pos'=>'bottom'), array('id'=>'t5','pos'=>'bottom'), array('id'=>'t6','pos'=>'bottom'),
),
'KLEMME' => array(array('id'=>'t1','pos'=>'top'), array('id'=>'t2','pos'=>'bottom')),
);
if (isset($defaults[$typeRef])) return $defaults[$typeRef];
if (strpos($typeRef, 'FI') !== false || strpos($typeRef, 'RCD') !== false) {
return (strpos($typeRef, '4P') !== false) ? $defaults['FI4P'] : $defaults['FI'];
}
return $defaults['LS'];
}
/**
* WiringDiagramAnalyzer - Analysiert die Schaltplan-Daten und baut Strompfade
*/
class WiringDiagramAnalyzer
{
private $db;
private $anlageId;
// Rohdaten
public $panels = array();
public $carriers = array();
public $allEquipment = array();
public $allConnections = array();
// Lookup-Maps
private $equipmentById = array();
private $carrierById = array();
private $carrierByEquipmentId = array();
// Phase-Propagierung
private $terminalPhaseMap = array();
private $terminalColorMap = array();
// Ergebnis
public $circuitPaths = array();
public function __construct($db, $anlageId)
{
$this->db = $db;
$this->anlageId = (int) $anlageId;
}
/**
* Alle Daten laden
*/
public function loadData()
{
// Panels
$panelObj = new EquipmentPanel($this->db);
$this->panels = $panelObj->fetchByAnlage($this->anlageId);
// Carriers
$carrierObj = new EquipmentCarrier($this->db);
$this->carriers = $carrierObj->fetchByAnlage($this->anlageId);
// Equipment + Connections pro Carrier
$eqObj = new Equipment($this->db);
$connObj = new EquipmentConnection($this->db);
foreach ($this->carriers as $c) {
$eqList = $eqObj->fetchByCarrier($c->id);
$connList = $connObj->fetchByCarrier($c->id);
$this->allEquipment = array_merge($this->allEquipment, $eqList);
$this->allConnections = array_merge($this->allConnections, $connList);
}
// Lookup-Maps bauen
foreach ($this->allEquipment as $eq) {
$this->equipmentById[$eq->id] = $eq;
}
foreach ($this->carriers as $c) {
$this->carrierById[$c->id] = $c;
}
foreach ($this->allEquipment as $eq) {
$this->carrierByEquipmentId[$eq->id] = isset($this->carrierById[$eq->fk_carrier])
? $this->carrierById[$eq->fk_carrier] : null;
}
}
/**
* Analyse durchführen: Phase-Map bauen + Strompfade finden
*/
public function analyze()
{
$this->buildPhaseMap();
$this->buildCircuitPaths();
}
/**
* PHP-Port von JS buildTerminalPhaseMap()
* Propagiert Phasen von Eingängen durch Blöcke, Leitungen und Busbars
*/
private function buildPhaseMap()
{
$this->terminalPhaseMap = array();
$this->terminalColorMap = array();
$validPhases = array('L1', 'L2', 'L3', 'N', 'PE');
// Hilfsfunktionen als Closures
$setPhase = function($eqId, $termId, $phase, $color = null) use ($validPhases) {
if (!isset($this->terminalPhaseMap[$eqId])) $this->terminalPhaseMap[$eqId] = array();
if (!isset($this->terminalColorMap[$eqId])) $this->terminalColorMap[$eqId] = array();
if (isset($this->terminalPhaseMap[$eqId][$termId])) return false;
$this->terminalPhaseMap[$eqId][$termId] = $phase;
$this->terminalColorMap[$eqId][$termId] = $color ?: '#888';
return true;
};
$forcePhase = function($eqId, $termId, $phase, $color = null) {
if (!isset($this->terminalPhaseMap[$eqId])) $this->terminalPhaseMap[$eqId] = array();
if (!isset($this->terminalColorMap[$eqId])) $this->terminalColorMap[$eqId] = array();
if (isset($this->terminalPhaseMap[$eqId][$termId]) && $this->terminalPhaseMap[$eqId][$termId] === $phase) return false;
$this->terminalPhaseMap[$eqId][$termId] = $phase;
$this->terminalColorMap[$eqId][$termId] = $color ?: '#888';
return true;
};
$getColor = function($eqId, $termId) {
return isset($this->terminalColorMap[$eqId][$termId]) ? $this->terminalColorMap[$eqId][$termId] : null;
};
// Schritt 1: Anschlusspunkte (Inputs) als Startpunkte
foreach ($this->allConnections as $conn) {
if ($conn->is_rail) continue;
if (!empty($conn->fk_source)) continue;
if (empty($conn->fk_target) || empty($conn->target_terminal_id)) continue;
$phase = strtoupper($conn->connection_type ?: '');
if (!in_array($phase, $validPhases)) continue;
$rgb = getPhaseColorRGB($phase);
$inputColor = !empty($conn->color) ? $conn->color : sprintf('#%02x%02x%02x', $rgb[0], $rgb[1], $rgb[2]);
$setPhase($conn->fk_target, $conn->target_terminal_id, $phase, $inputColor);
}
// Schritt 2: Iterativ propagieren
$changed = true;
$iterations = 0;
while ($changed && $iterations++ < 20) {
$changed = false;
// Block-Durchreichung (top ↔ bottom)
foreach ($this->allEquipment as $eq) {
$terminals = getEquipmentTerminals($eq);
$topTerminals = array_values(array_filter($terminals, function($t) { return ($t['pos'] ?? '') === 'top'; }));
$bottomTerminals = array_values(array_filter($terminals, function($t) { return ($t['pos'] ?? '') === 'bottom'; }));
$pairCount = min(count($topTerminals), count($bottomTerminals));
for ($i = 0; $i < $pairCount; $i++) {
$topId = $topTerminals[$i]['id'] ?? 't'.($i+1);
$botId = $bottomTerminals[$i]['id'] ?? 't'.($i + count($topTerminals) + 1);
$topPhase = $this->terminalPhaseMap[$eq->id][$topId] ?? null;
$botPhase = $this->terminalPhaseMap[$eq->id][$botId] ?? null;
if ($topPhase && !$botPhase) {
if ($setPhase($eq->id, $botId, $topPhase, $getColor($eq->id, $topId))) $changed = true;
} elseif ($botPhase && !$topPhase) {
if ($setPhase($eq->id, $topId, $botPhase, $getColor($eq->id, $botId))) $changed = true;
}
}
}
// Leitungen propagieren
foreach ($this->allConnections as $conn) {
if ($conn->is_rail) continue;
if (empty($conn->fk_source) || empty($conn->fk_target)) continue;
if (empty($conn->source_terminal_id) || empty($conn->target_terminal_id)) continue;
$srcPhase = $this->terminalPhaseMap[$conn->fk_source][$conn->source_terminal_id] ?? null;
$tgtPhase = $this->terminalPhaseMap[$conn->fk_target][$conn->target_terminal_id] ?? null;
if ($srcPhase && !$tgtPhase) {
if ($setPhase($conn->fk_target, $conn->target_terminal_id, $srcPhase, $getColor($conn->fk_source, $conn->source_terminal_id))) $changed = true;
} elseif ($tgtPhase && !$srcPhase) {
if ($setPhase($conn->fk_source, $conn->source_terminal_id, $tgtPhase, $getColor($conn->fk_target, $conn->target_terminal_id))) $changed = true;
}
}
// Busbar-Verteilung
foreach ($this->allConnections as $busbar) {
if (!$busbar->is_rail) continue;
$railStart = (int) ($busbar->rail_start_te ?: 1);
$railEnd = (int) ($busbar->rail_end_te ?: $railStart);
$posY = (int) ($busbar->position_y ?: 0);
$targetPos = ($posY === 0) ? 'top' : 'bottom';
// Phase-Labels
$phaseLabels = array();
if (!empty($busbar->phases_config)) {
$pc = @json_decode($busbar->phases_config, true);
if (is_array($pc) && !empty($pc)) $phaseLabels = $pc;
}
if (empty($phaseLabels)) {
$phaseLabels = parsePhaseLabels($busbar->rail_phases ?: $busbar->connection_type ?: '');
}
if (empty($phaseLabels)) continue;
// Eingespeiste Phasen sammeln
$fedPhases = array();
$fedColors = array();
foreach ($this->allEquipment as $eq) {
if ($eq->fk_carrier != $busbar->fk_carrier) continue;
$eqPosTE = floatval($eq->position_te ?: 1);
$eqWidthTE = floatval($eq->width_te ?: 1);
if (!($eqPosTE < $railEnd + 1 && $railStart < $eqPosTE + $eqWidthTE)) continue;
$terminals = getEquipmentTerminals($eq);
foreach ($terminals as $term) {
if (($term['pos'] ?? '') !== $targetPos) continue;
$termId = $term['id'] ?? '';
$phase = $this->terminalPhaseMap[$eq->id][$termId] ?? null;
if ($phase) {
$fedPhases[$phase] = true;
if (!isset($fedColors[$phase])) {
$fedColors[$phase] = $getColor($eq->id, $termId);
}
}
}
}
if (empty($fedPhases)) continue;
// Excluded TEs
$excludedTEs = array();
if (!empty($busbar->excluded_te)) {
$excludedTEs = array_map('intval', array_filter(array_map('trim', explode(',', $busbar->excluded_te))));
}
// Verteilen
foreach ($this->allEquipment as $eq) {
if ($eq->fk_carrier != $busbar->fk_carrier) continue;
$eqPosTE = floatval($eq->position_te ?: 1);
$eqWidthTE = floatval($eq->width_te ?: 1);
if (!($eqPosTE < $railEnd + 1 && $railStart < $eqPosTE + $eqWidthTE)) continue;
$terminals = getEquipmentTerminals($eq);
$posTerminals = array_values(array_filter($terminals, function($t) use ($targetPos) { return ($t['pos'] ?? '') === $targetPos; }));
foreach ($posTerminals as $idx => $term) {
$col = isset($term['col']) ? $term['col'] : ($idx % max(1, $eqWidthTE));
$absoluteTE = round($eqPosTE + $col);
if (in_array($absoluteTE, $excludedTEs)) continue;
if ($absoluteTE < $railStart || $absoluteTE > $railEnd) continue;
$teOffset = $absoluteTE - $railStart;
$phase = $phaseLabels[$teOffset % count($phaseLabels)];
if (!isset($fedPhases[$phase])) continue;
$phaseColor = $fedColors[$phase] ?? '#888';
$termId = $term['id'] ?? 't'.($idx+1);
if ($forcePhase($eq->id, $termId, $phase, $phaseColor)) $changed = true;
}
}
}
}
}
/**
* Strompfade bauen: Für jeden Abgang eine Spalte
*/
private function buildCircuitPaths()
{
$this->circuitPaths = array();
// Alle Abgänge (Outputs) finden
foreach ($this->allConnections as $conn) {
if ($conn->is_rail) continue;
// Abgang = hat Source, kein Target
if (empty($conn->fk_source) || !empty($conn->fk_target)) continue;
// path_data = Junction-Verbindung, kein echter Abgang
if (!empty($conn->path_data)) continue;
$sourceEq = $this->equipmentById[$conn->fk_source] ?? null;
if (!$sourceEq) continue;
$carrier = $this->carrierByEquipmentId[$conn->fk_source] ?? null;
if (!$carrier) continue;
// Panel ermitteln
$panel = null;
if (!empty($carrier->fk_panel)) {
foreach ($this->panels as $p) {
if ($p->id == $carrier->fk_panel) { $panel = $p; break; }
}
}
// Phase bestimmen
$phase = '';
if (!empty($conn->connection_type) && in_array(strtoupper($conn->connection_type), array('L1','L2','L3','N','PE','LN','3P','3P+N'))) {
$phase = strtoupper($conn->connection_type);
}
// Fallback: Aus Phase-Map
if (empty($phase) && !empty($conn->source_terminal_id)) {
$phase = $this->terminalPhaseMap[$conn->fk_source][$conn->source_terminal_id] ?? '';
}
// Fallback: Erstbeste Phase vom Equipment
if (empty($phase) && isset($this->terminalPhaseMap[$conn->fk_source])) {
$phases = array_values($this->terminalPhaseMap[$conn->fk_source]);
if (!empty($phases)) $phase = $phases[0];
}
// Schutzgerät (FI/RCD)
$protectionDevice = null;
if (!empty($sourceEq->fk_protection)) {
$protectionDevice = $this->equipmentById[$sourceEq->fk_protection] ?? null;
}
// Block-Label (z.B. "B16", "C32")
$blockLabel = $sourceEq->getBlockLabel();
// Abgangsnummer: R{Reihe}.{Position}
$reihe = ($carrier->position ?? 0) + 1;
$pos = round(floatval($sourceEq->position_te));
$abgangNr = 'R'.$reihe.'.'.$pos;
// Kette aufbauen (von oben nach unten)
$chain = array();
// Phase-Rail
$chain[] = array('type' => 'phase_rail', 'label' => $phase ?: '?');
// Schutzgerät
if ($protectionDevice) {
$protLabel = $protectionDevice->label ?: ($protectionDevice->type_label_short ?: 'FI');
$protBlock = $protectionDevice->getBlockLabel();
$chain[] = array(
'type' => 'protection',
'equipment' => $protectionDevice,
'label' => $protLabel,
'block_label' => $protBlock,
);
}
// LS-Schalter (Breaker)
$chain[] = array(
'type' => 'breaker',
'equipment' => $sourceEq,
'label' => $sourceEq->label ?: ($sourceEq->type_label_short ?: 'LS'),
'block_label' => $blockLabel,
);
// Verbraucher (Abgang)
$chain[] = array('type' => 'consumer', 'label' => $conn->output_label ?: '-');
$this->circuitPaths[] = array(
'abgang_nr' => $abgangNr,
'output_label' => $conn->output_label ?: '-',
'phase' => $phase ?: '?',
'phase_color_rgb' => getPhaseColorRGB($phase),
'medium_type' => $conn->medium_type ?: '',
'medium_spec' => $conn->medium_spec ?: '',
'medium_length' => $conn->medium_length ?: '',
'chain' => $chain,
'protection_device' => $protectionDevice,
'breaker' => $sourceEq,
'carrier' => $carrier,
'panel' => $panel,
'connection' => $conn,
);
}
// Sortierung: FI-Gruppe → Carrier → Position
usort($this->circuitPaths, function($a, $b) {
$protA = $a['protection_device'] ? $a['protection_device']->id : PHP_INT_MAX;
$protB = $b['protection_device'] ? $b['protection_device']->id : PHP_INT_MAX;
if ($protA !== $protB) return $protA - $protB;
$carrA = $a['carrier']->position ?? 0;
$carrB = $b['carrier']->position ?? 0;
if ($carrA !== $carrB) return $carrA - $carrB;
$posA = $a['breaker']->position_te ?? 0;
$posB = $b['breaker']->position_te ?? 0;
return $posA <=> $posB;
});
}
/**
* Ergebnis: Strompfade
*/
public function getCircuitPaths()
{
return $this->circuitPaths;
}
/**
* Abgangs-Tabelle pro Carrier/Panel
*/
public function getAbgangTabelle()
{
$tabellen = array();
foreach ($this->carriers as $carrier) {
$panel = null;
if (!empty($carrier->fk_panel)) {
foreach ($this->panels as $p) {
if ($p->id == $carrier->fk_panel) { $panel = $p; break; }
}
}
$header = '';
if ($panel) $header .= $panel->label . ', ';
$header .= $carrier->label ?: ('Reihe '.($carrier->position + 1));
$rows = array();
foreach ($this->circuitPaths as $path) {
if ($path['carrier']->id != $carrier->id) continue;
$protLabel = '';
if ($path['protection_device']) {
$pd = $path['protection_device'];
$protLabel = ($pd->label ?: $pd->type_label_short ?: 'FI');
$protBlock = $pd->getBlockLabel();
if ($protBlock) $protLabel .= ' '.$protBlock;
}
$kabel = $path['medium_type'];
if ($path['medium_spec']) $kabel .= ' '.$path['medium_spec'];
if ($path['medium_length']) $kabel .= ' ('.$path['medium_length'].')';
$rows[] = array(
'abgang_nr' => $path['abgang_nr'],
'bezeichnung' => $path['output_label'],
'phase' => $path['phase'],
'absicherung' => $path['chain'][count($path['chain'])-2]['block_label'] ?? '',
'kabel' => trim($kabel),
'schutzgeraet' => $protLabel,
'bemerkung' => '',
);
}
if (!empty($rows)) {
$tabellen[] = array(
'header' => $header,
'carrier' => $carrier,
'panel' => $panel,
'rows' => $rows,
);
}
}
return $tabellen;
}
}
/**
* WiringDiagramRenderer - Zeichnet den Leitungslaufplan als PDF
*/
class WiringDiagramRenderer
{
private $pdf;
private $circuitPaths;
private $analyzer;
private $anlage;
private $societe;
private $user;
// Seitengröße
private $pageWidth;
private $pageHeight;
private $orientation;
private $format;
// Layout-Konstanten
const MARGIN_LEFT = 15;
const MARGIN_RIGHT = 15;
const MARGIN_TOP = 12;
const COLUMN_WIDTH = 25;
const COLUMN_GAP = 3;
const PHASE_GAP = 5;
// Vertikale Positionen (werden in calculateLayout berechnet)
private $yPhaseL1;
private $yPhaseL2;
private $yPhaseL3;
private $yFiTop;
private $yFiBottom;
private $yLsTop;
private $yLsBottom;
private $yConsumer;
private $yCableLabel;
private $yAbgangLabel;
private $yAbgangNr;
private $yNRail;
private $yPeRail;
private $maxColumnsPerPage;
private $currentPage = 0;
private $totalPages = 1;
public function __construct($pdf, $analyzer, $anlage, $societe, $user, $format = 'A3', $orientation = 'L')
{
$this->pdf = $pdf;
$this->analyzer = $analyzer;
$this->circuitPaths = $analyzer->getCircuitPaths();
$this->anlage = $anlage;
$this->societe = $societe;
$this->user = $user;
$this->format = $format;
$this->orientation = $orientation;
// Seitengröße
if ($format == 'A3') {
$this->pageWidth = 420;
$this->pageHeight = 297;
} else {
$this->pageWidth = 297;
$this->pageHeight = 210;
}
if ($orientation == 'P') {
$tmp = $this->pageWidth;
$this->pageWidth = $this->pageHeight;
$this->pageHeight = $tmp;
}
$this->calculateLayout();
}
/**
* Layout berechnen
*/
private function calculateLayout()
{
$this->yPhaseL1 = self::MARGIN_TOP + 15;
$this->yPhaseL2 = $this->yPhaseL1 + self::PHASE_GAP;
$this->yPhaseL3 = $this->yPhaseL2 + self::PHASE_GAP;
$this->yFiTop = $this->yPhaseL3 + 18;
$this->yFiBottom = $this->yFiTop + 22;
$this->yLsTop = $this->yFiBottom + 12;
$this->yLsBottom = $this->yLsTop + 18;
$this->yConsumer = $this->yLsBottom + 12;
$this->yCableLabel = $this->yConsumer + 10;
$this->yAbgangLabel = $this->yCableLabel + 10;
$this->yAbgangNr = $this->yAbgangLabel + 8;
// N und PE unten (vor Titelfeld)
$this->yNRail = $this->pageHeight - 85;
$this->yPeRail = $this->yNRail + self::PHASE_GAP;
// Max Spalten pro Seite
$usableWidth = $this->pageWidth - self::MARGIN_LEFT - self::MARGIN_RIGHT - 30; // 30mm Phase-Labels links
$this->maxColumnsPerPage = max(1, floor($usableWidth / (self::COLUMN_WIDTH + self::COLUMN_GAP)));
// Gesamtseiten berechnen
$totalPaths = count($this->circuitPaths);
$this->totalPages = max(1, ceil($totalPaths / $this->maxColumnsPerPage));
}
/**
* Leitungslaufplan zeichnen (alle Seiten)
*/
public function render()
{
$totalPaths = count($this->circuitPaths);
if ($totalPaths === 0) {
$this->pdf->AddPage($this->orientation, array($this->pageWidth, $this->pageHeight));
$this->pdf->SetFont('dejavusans', 'B', 14);
$this->pdf->SetTextColor(100, 100, 100);
$this->pdf->Text(self::MARGIN_LEFT, $this->pageHeight / 2, 'Keine Abgänge konfiguriert');
$this->drawTitleBlock(1, 1);
return;
}
$pathIndex = 0;
$pageNum = 0;
while ($pathIndex < $totalPaths) {
$pageNum++;
$this->pdf->AddPage($this->orientation, array($this->pageWidth, $this->pageHeight));
// Spalten für diese Seite
$pagePaths = array_slice($this->circuitPaths, $pathIndex, $this->maxColumnsPerPage);
$numCols = count($pagePaths);
$startX = self::MARGIN_LEFT + 30; // 30mm für Phase-Labels links
$endX = $startX + ($numCols * (self::COLUMN_WIDTH + self::COLUMN_GAP));
// Phase-Labels links
$this->drawPhaseLabels();
// Phasenleiter oben (L1, L2, L3)
$this->drawPhaseRails($startX - 5, $endX + 5);
// N und PE unten
$this->drawNPeRails($startX - 5, $endX + 5);
// FI-Gruppen identifizieren für Trennlinien
$lastProtId = null;
// Strompfad-Spalten zeichnen
for ($col = 0; $col < $numCols; $col++) {
$path = $pagePaths[$col];
$x = $startX + $col * (self::COLUMN_WIDTH + self::COLUMN_GAP) + self::COLUMN_WIDTH / 2;
// FI-Gruppen-Trenner
$currentProtId = $path['protection_device'] ? $path['protection_device']->id : 0;
if ($lastProtId !== null && $lastProtId !== $currentProtId && $col > 0) {
$sepX = $x - (self::COLUMN_WIDTH + self::COLUMN_GAP) / 2;
$this->pdf->SetDrawColor(180, 180, 180);
$this->pdf->SetLineWidth(0.2);
$this->pdf->SetLineDashPattern(array(2, 2));
$this->pdf->Line($sepX, $this->yPhaseL1 - 5, $sepX, $this->yPeRail + 5);
$this->pdf->SetLineDashPattern(array());
}
$lastProtId = $currentProtId;
$this->drawCircuitColumn($path, $x);
}
// Titelfeld
$this->drawTitleBlock($pageNum, $this->totalPages + 2); // +2 für Tabelle + Legende
$pathIndex += $numCols;
}
}
/**
* Phase-Labels links zeichnen
*/
private function drawPhaseLabels()
{
$x = self::MARGIN_LEFT;
$this->pdf->SetFont('dejavusans', 'B', 9);
// L1
$rgb = getPhaseColorRGB('L1');
$this->pdf->SetTextColor($rgb[0], $rgb[1], $rgb[2]);
$this->pdf->Text($x, $this->yPhaseL1 - 1, 'L1');
// L2
$rgb = getPhaseColorRGB('L2');
$this->pdf->SetTextColor($rgb[0], $rgb[1], $rgb[2]);
$this->pdf->Text($x, $this->yPhaseL2 - 1, 'L2');
// L3
$rgb = getPhaseColorRGB('L3');
$this->pdf->SetTextColor($rgb[0], $rgb[1], $rgb[2]);
$this->pdf->Text($x, $this->yPhaseL3 - 1, 'L3');
// N
$rgb = getPhaseColorRGB('N');
$this->pdf->SetTextColor($rgb[0], $rgb[1], $rgb[2]);
$this->pdf->Text($x, $this->yNRail - 1, 'N');
// PE
$rgb = getPhaseColorRGB('PE');
$this->pdf->SetTextColor($rgb[0], $rgb[1], $rgb[2]);
$this->pdf->Text($x, $this->yPeRail - 1, 'PE');
$this->pdf->SetTextColor(0, 0, 0);
}
/**
* Horizontale Phasenleiter oben (L1, L2, L3)
*/
private function drawPhaseRails($startX, $endX)
{
$this->pdf->SetLineWidth(0.5);
$phases = array('L1' => $this->yPhaseL1, 'L2' => $this->yPhaseL2, 'L3' => $this->yPhaseL3);
foreach ($phases as $phase => $y) {
$rgb = getPhaseColorRGB($phase);
$this->pdf->SetDrawColor($rgb[0], $rgb[1], $rgb[2]);
$this->pdf->Line($startX, $y, $endX, $y);
}
}
/**
* N und PE Leiter unten
*/
private function drawNPeRails($startX, $endX)
{
$this->pdf->SetLineWidth(0.5);
$rgb = getPhaseColorRGB('N');
$this->pdf->SetDrawColor($rgb[0], $rgb[1], $rgb[2]);
$this->pdf->Line($startX, $this->yNRail, $endX, $this->yNRail);
$rgb = getPhaseColorRGB('PE');
$this->pdf->SetDrawColor($rgb[0], $rgb[1], $rgb[2]);
$this->pdf->Line($startX, $this->yPeRail, $endX, $this->yPeRail);
}
/**
* Eine Strompfad-Spalte zeichnen
*/
private function drawCircuitColumn($path, $x)
{
$phase = $path['phase'];
$phaseRGB = $path['phase_color_rgb'];
// Y-Position der Phase bestimmen (L1, L2 oder L3)
$phaseY = $this->yPhaseL1;
if ($phase === 'L2') $phaseY = $this->yPhaseL2;
elseif ($phase === 'L3') $phaseY = $this->yPhaseL3;
$this->pdf->SetDrawColor($phaseRGB[0], $phaseRGB[1], $phaseRGB[2]);
$this->pdf->SetLineWidth(0.4);
// Vertikale Linie von Phase runter zum FI oder LS
$hasFI = !empty($path['protection_device']);
$topTarget = $hasFI ? $this->yFiTop : $this->yLsTop;
// Anschluss an Phase-Rail (kleiner Punkt)
$this->pdf->Circle($x, $phaseY, 1, 0, 360, 'F', array(), array($phaseRGB[0], $phaseRGB[1], $phaseRGB[2]));
// Vertikale Linie Phase → FI/LS
$this->pdf->Line($x, $phaseY, $x, $topTarget);
// FI/RCD zeichnen
if ($hasFI) {
$this->drawRCDSymbol($x, $this->yFiTop, $path['protection_device']);
// Linie FI → LS
$this->pdf->SetDrawColor($phaseRGB[0], $phaseRGB[1], $phaseRGB[2]);
$this->pdf->SetLineWidth(0.4);
$this->pdf->Line($x, $this->yFiBottom, $x, $this->yLsTop);
}
// LS-Schalter zeichnen
$this->drawBreakerSymbol($x, $this->yLsTop, $path['breaker'], $path['chain']);
// Linie LS → Verbraucher
$this->pdf->SetDrawColor($phaseRGB[0], $phaseRGB[1], $phaseRGB[2]);
$this->pdf->SetLineWidth(0.4);
$this->pdf->Line($x, $this->yLsBottom, $x, $this->yConsumer);
// Abgang-Pfeil
$this->pdf->SetFillColor($phaseRGB[0], $phaseRGB[1], $phaseRGB[2]);
$arrowSize = 3;
$this->pdf->Polygon(array(
$x - $arrowSize, $this->yConsumer - $arrowSize,
$x + $arrowSize, $this->yConsumer - $arrowSize,
$x, $this->yConsumer + 1,
), 'F');
// Vertikale Linie zum N-Leiter
$this->pdf->SetDrawColor(180, 180, 180);
$this->pdf->SetLineWidth(0.15);
$this->pdf->SetLineDashPattern(array(1, 2));
$this->pdf->Line($x, $this->yConsumer + 2, $x, $this->yNRail);
$this->pdf->SetLineDashPattern(array());
// Anschluss an N-Rail
$nRGB = getPhaseColorRGB('N');
$this->pdf->Circle($x, $this->yNRail, 0.8, 0, 360, 'F', array(), array($nRGB[0], $nRGB[1], $nRGB[2]));
// Kabelbezeichnung
$this->pdf->SetFont('dejavusans', '', 6);
$this->pdf->SetTextColor(100, 100, 100);
$cableText = $path['medium_type'];
if ($path['medium_spec']) $cableText .= "\n".$path['medium_spec'];
if (!empty($cableText)) {
$this->pdf->SetXY($x - 12, $this->yCableLabel);
$this->pdf->MultiCell(24, 3, $cableText, 0, 'C');
}
// Abgang-Label (Verbraucher-Name)
$this->pdf->SetFont('dejavusans', 'B', 7);
$this->pdf->SetTextColor(0, 0, 0);
$this->pdf->SetXY($x - 13, $this->yAbgangLabel);
$this->pdf->MultiCell(26, 3, $path['output_label'], 0, 'C');
// Abgangsnummer
$this->pdf->SetFont('dejavusans', '', 6);
$this->pdf->SetTextColor(120, 120, 120);
$this->pdf->Text($x - 6, $this->yAbgangNr + 8, $path['abgang_nr']);
}
/**
* LS-Schalter Symbol (vereinfacht)
*/
private function drawBreakerSymbol($x, $y, $eq, $chain)
{
$this->pdf->SetDrawColor(0, 0, 0);
$this->pdf->SetLineWidth(0.3);
// Vertikale Linie oben
$this->pdf->Line($x, $y, $x, $y + 4);
// Schaltkontakt (schräge Linie)
$this->pdf->Line($x, $y + 4, $x + 4, $y + 8);
// Auslöser (kleines Rechteck)
$this->pdf->SetFillColor(255, 255, 255);
$this->pdf->Rect($x - 2, $y + 8, 4, 3, 'DF');
// Vertikale Linie unten
$this->pdf->Line($x, $y + 11, $x, $y + 18);
// Block-Label rechts (z.B. "B16")
$blockLabel = '';
foreach ($chain as $c) {
if ($c['type'] === 'breaker') {
$blockLabel = $c['block_label'] ?? '';
break;
}
}
$this->pdf->SetFont('dejavusans', 'B', 7);
$this->pdf->SetTextColor(0, 0, 0);
if ($blockLabel) {
$this->pdf->Text($x + 5, $y + 7, $blockLabel);
}
// Equipment-Label links (z.B. "F1")
$label = $eq->label ?: '';
if ($label) {
$this->pdf->SetFont('dejavusans', '', 6);
$this->pdf->SetTextColor(80, 80, 80);
$this->pdf->Text($x - 12, $y + 7, $label);
}
}
/**
* FI/RCD Symbol (vereinfacht)
*/
private function drawRCDSymbol($x, $y, $eq)
{
$this->pdf->SetDrawColor(0, 0, 0);
$this->pdf->SetLineWidth(0.3);
$w = 16;
$h = 20;
// Umrandung
$this->pdf->SetFillColor(255, 255, 255);
$this->pdf->Rect($x - $w/2, $y, $w, $h, 'DF');
// Differenzstrom-Symbol (Kreis)
$this->pdf->Circle($x, $y + $h/2, 4, 0, 360, 'D');
// Vertikale Linie durch Kreis (Auslöser)
$this->pdf->Line($x, $y + $h/2 - 4, $x, $y + $h/2 + 4);
// Block-Label (z.B. "40A 30mA")
$blockLabel = $eq->getBlockLabel();
$this->pdf->SetFont('dejavusans', '', 5);
$this->pdf->SetTextColor(0, 0, 0);
if ($blockLabel) {
$this->pdf->SetXY($x - $w/2, $y + $h - 5);
$this->pdf->Cell($w, 4, $blockLabel, 0, 0, 'C');
}
// Equipment-Label links (z.B. "Q1")
$label = $eq->label ?: '';
if ($label) {
$this->pdf->SetFont('dejavusans', '', 6);
$this->pdf->SetTextColor(80, 80, 80);
$this->pdf->Text($x - $w/2 - 10, $y + $h/2, $label);
}
// Ein/Ausgangs-Linien
$this->pdf->SetDrawColor(0, 0, 0);
$this->pdf->Line($x, $y - 2, $x, $y);
$this->pdf->Line($x, $y + $h, $x, $y + $h + 2);
}
/**
* Abgangs-Tabellen zeichnen
*/
public function renderAbgangTabelle()
{
$tabellen = $this->analyzer->getAbgangTabelle();
if (empty($tabellen)) return;
// Spaltenbreiten
$colWidths = array(18, 50, 15, 28, 45, 40, 30);
$colHeaders = array('Abg.Nr.', 'Bezeichnung', 'Phase', 'Absicherung', 'Kabel', 'Schutzgerät', 'Bemerkung');
$totalWidth = array_sum($colWidths);
foreach ($tabellen as $tabelle) {
$this->pdf->AddPage($this->orientation, array($this->pageWidth, $this->pageHeight));
$y = self::MARGIN_TOP;
// Tabellen-Header
$this->pdf->SetFont('dejavusans', 'B', 12);
$this->pdf->SetTextColor(0, 0, 0);
$this->pdf->Text(self::MARGIN_LEFT, $y + 5, 'Abgangsverzeichnis - '.$tabelle['header']);
$y += 15;
// Spalten-Header
$this->pdf->SetFont('dejavusans', 'B', 7);
$this->pdf->SetFillColor(230, 230, 230);
$x = self::MARGIN_LEFT;
for ($i = 0; $i < count($colHeaders); $i++) {
$this->pdf->SetXY($x, $y);
$this->pdf->Cell($colWidths[$i], 7, $colHeaders[$i], 1, 0, 'C', true);
$x += $colWidths[$i];
}
$y += 7;
// Zeilen
$this->pdf->SetFont('dejavusans', '', 7);
$this->pdf->SetFillColor(255, 255, 255);
foreach ($tabelle['rows'] as $rowIdx => $row) {
$bgFill = ($rowIdx % 2 === 0);
if ($bgFill) $this->pdf->SetFillColor(248, 248, 248);
else $this->pdf->SetFillColor(255, 255, 255);
$x = self::MARGIN_LEFT;
$cells = array(
$row['abgang_nr'],
$row['bezeichnung'],
$row['phase'],
$row['absicherung'],
$row['kabel'],
$row['schutzgeraet'],
$row['bemerkung'],
);
for ($i = 0; $i < count($cells); $i++) {
$this->pdf->SetXY($x, $y);
$align = ($i === 2) ? 'C' : 'L'; // Phase zentriert
$this->pdf->Cell($colWidths[$i], 6, $cells[$i], 1, 0, $align, true);
$x += $colWidths[$i];
}
$y += 6;
// Seitenumbruch
if ($y > $this->pageHeight - 30) {
$this->pdf->AddPage($this->orientation, array($this->pageWidth, $this->pageHeight));
$y = self::MARGIN_TOP + 10;
}
}
// Titelfeld
$this->drawTitleBlock(0, 0);
}
}
/**
* Legende zeichnen
*/
public function renderLegende()
{
$this->pdf->AddPage($this->orientation, array($this->pageWidth, $this->pageHeight));
$y = self::MARGIN_TOP;
$x = self::MARGIN_LEFT;
$this->pdf->SetFont('dejavusans', 'B', 14);
$this->pdf->SetTextColor(0, 0, 0);
$this->pdf->Text($x, $y + 5, 'Legende');
$y += 15;
// Phasenfarben
$this->pdf->SetFont('dejavusans', 'B', 10);
$this->pdf->Text($x, $y, 'Phasenfarben nach DIN VDE');
$y += 8;
$phases = array(
'L1' => 'Außenleiter 1 (Braun)',
'L2' => 'Außenleiter 2 (Schwarz)',
'L3' => 'Außenleiter 3 (Grau)',
'N' => 'Neutralleiter (Blau)',
'PE' => 'Schutzleiter (Grün-Gelb)',
);
$this->pdf->SetFont('dejavusans', '', 8);
foreach ($phases as $phase => $label) {
$rgb = getPhaseColorRGB($phase);
$this->pdf->SetFillColor($rgb[0], $rgb[1], $rgb[2]);
$this->pdf->Rect($x, $y, 20, 5, 'F');
$this->pdf->SetTextColor(0, 0, 0);
$this->pdf->Text($x + 25, $y + 3, $phase.' - '.$label);
$y += 8;
}
$y += 5;
// Symbole
$this->pdf->SetFont('dejavusans', 'B', 10);
$this->pdf->SetTextColor(0, 0, 0);
$this->pdf->Text($x, $y, 'Symbole');
$y += 10;
// LS-Symbol + Beschreibung
$this->pdf->SetFont('dejavusans', '', 8);
$this->drawBreakerSymbol($x + 8, $y, (object)array('label'=>'F1'), array(array('type'=>'breaker','block_label'=>'B16')));
$this->pdf->SetTextColor(0, 0, 0);
$this->pdf->Text($x + 30, $y + 8, 'Leitungsschutzschalter (LS-Schalter)');
$y += 25;
// FI-Symbol + Beschreibung
$protDummy = new stdClass();
$protDummy->label = 'Q1';
$protDummy->type_label_short = 'FI';
// getBlockLabel brauchen wir nicht, Text wird direkt gesetzt
$this->pdf->SetDrawColor(0, 0, 0);
$this->pdf->SetLineWidth(0.3);
$this->pdf->SetFillColor(255, 255, 255);
$this->pdf->Rect($x, $y, 16, 20, 'DF');
$this->pdf->Circle($x + 8, $y + 10, 4, 0, 360, 'D');
$this->pdf->Line($x + 8, $y + 6, $x + 8, $y + 14);
$this->pdf->SetFont('dejavusans', '', 5);
$this->pdf->Text($x + 2, $y + 17, 'FI/RCD');
$this->pdf->SetFont('dejavusans', '', 8);
$this->pdf->SetTextColor(0, 0, 0);
$this->pdf->Text($x + 30, $y + 10, 'Fehlerstrom-Schutzschalter (FI/RCD)');
$y += 28;
// Abgang-Pfeil
$this->pdf->SetFillColor(0, 0, 0);
$this->pdf->Polygon(array($x + 5, $y, $x + 11, $y, $x + 8, $y + 5), 'F');
$this->pdf->SetFont('dejavusans', '', 8);
$this->pdf->Text($x + 30, $y + 3, 'Abgang zum Verbraucher');
$y += 15;
// Norm-Hinweis
$y += 10;
$this->pdf->SetFont('dejavusans', 'I', 7);
$this->pdf->SetTextColor(120, 120, 120);
$this->pdf->Text($x, $y, 'Erstellt nach DIN EN 61082 / DIN EN 81346');
$this->pdf->Text($x, $y + 5, 'Bezugsbezeichnungen nach DIN EN 81346-2');
$this->pdf->Text($x, $y + 10, 'Schaltzeichen nach DIN EN 60617');
$this->drawTitleBlock(0, 0);
}
/**
* Titelfeld nach DIN EN 61082 / ISO 7200
*/
private function drawTitleBlock($pageNum = 0, $totalPages = 0)
{
$titleBlockWidth = 180;
$titleBlockHeight = 56;
$titleBlockX = $this->pageWidth - $titleBlockWidth - 10;
$titleBlockY = $this->pageHeight - $titleBlockHeight - 10;
// Rahmen
$this->pdf->SetDrawColor(0, 0, 0);
$this->pdf->SetLineWidth(0.5);
$this->pdf->Rect($titleBlockX, $titleBlockY, $titleBlockWidth, $titleBlockHeight);
$rowHeight = 8;
$col1 = 30; $col2 = 50; $col3 = 50; $col4 = 50;
// Horizontale Linien
for ($i = 1; $i < 7; $i++) {
$y = $titleBlockY + ($i * $rowHeight);
$this->pdf->Line($titleBlockX, $y, $titleBlockX + $titleBlockWidth, $y);
}
// Vertikale Linien
$this->pdf->Line($titleBlockX + $col1, $titleBlockY, $titleBlockX + $col1, $titleBlockY + $titleBlockHeight);
$this->pdf->Line($titleBlockX + $col1 + $col2, $titleBlockY, $titleBlockX + $col1 + $col2, $titleBlockY + $titleBlockHeight);
$this->pdf->Line($titleBlockX + $col1 + $col2 + $col3, $titleBlockY, $titleBlockX + $col1 + $col2 + $col3, $titleBlockY + $titleBlockHeight);
$this->pdf->SetTextColor(0, 0, 0);
// Zeile 1: Titel
$this->pdf->SetFont('dejavusans', 'B', 12);
$this->pdf->SetXY($titleBlockX + 2, $titleBlockY + 1);
$this->pdf->Cell($titleBlockWidth - 4, $rowHeight - 2, 'LEITUNGSLAUFPLAN', 0, 0, 'C');
// Zeile 2: Anlage
$this->pdf->SetFont('dejavusans', 'B', 10);
$this->pdf->SetXY($titleBlockX + 2, $titleBlockY + $rowHeight + 1);
$this->pdf->Cell($titleBlockWidth - 4, $rowHeight - 2, $this->anlage->label, 0, 0, 'C');
// Zeile 3: Erstellt | Kunde | Projekt | Blatt
$y = $titleBlockY + (2 * $rowHeight);
$this->pdf->SetFont('dejavusans', '', 6);
$this->pdf->SetXY($titleBlockX + 1, $y + 1); $this->pdf->Cell($col1 - 2, 3, 'Erstellt', 0, 0);
$this->pdf->SetXY($titleBlockX + $col1 + 1, $y + 1); $this->pdf->Cell($col2 - 2, 3, 'Kunde', 0, 0);
$this->pdf->SetXY($titleBlockX + $col1 + $col2 + 1, $y + 1); $this->pdf->Cell($col3 - 2, 3, 'Projekt-Nr.', 0, 0);
$this->pdf->SetXY($titleBlockX + $col1 + $col2 + $col3 + 1, $y + 1); $this->pdf->Cell($col4 - 2, 3, 'Blatt', 0, 0);
$this->pdf->SetFont('dejavusans', '', 8);
$this->pdf->SetXY($titleBlockX + 1, $y + 4); $this->pdf->Cell($col1 - 2, 4, dol_print_date(dol_now(), 'day'), 0, 0);
$this->pdf->SetXY($titleBlockX + $col1 + 1, $y + 4); $this->pdf->Cell($col2 - 2, 4, dol_trunc($this->societe->name, 25), 0, 0);
$this->pdf->SetXY($titleBlockX + $col1 + $col2 + 1, $y + 4); $this->pdf->Cell($col3 - 2, 4, $this->anlage->ref ?: '-', 0, 0);
$blatt = ($pageNum > 0 && $totalPages > 0) ? $pageNum.' / '.$totalPages : '';
$this->pdf->SetXY($titleBlockX + $col1 + $col2 + $col3 + 1, $y + 4); $this->pdf->Cell($col4 - 2, 4, $blatt, 0, 0);
// Zeile 4: Bearbeiter | Adresse | Anlage | Revision
$y = $titleBlockY + (3 * $rowHeight);
$this->pdf->SetFont('dejavusans', '', 6);
$this->pdf->SetXY($titleBlockX + 1, $y + 1); $this->pdf->Cell($col1 - 2, 3, 'Bearbeiter', 0, 0);
$this->pdf->SetXY($titleBlockX + $col1 + 1, $y + 1); $this->pdf->Cell($col2 - 2, 3, 'Adresse', 0, 0);
$this->pdf->SetXY($titleBlockX + $col1 + $col2 + 1, $y + 1); $this->pdf->Cell($col3 - 2, 3, 'Anlage', 0, 0);
$this->pdf->SetXY($titleBlockX + $col1 + $col2 + $col3 + 1, $y + 1); $this->pdf->Cell($col4 - 2, 3, 'Revision', 0, 0);
$this->pdf->SetFont('dejavusans', '', 8);
global $langs;
$this->pdf->SetXY($titleBlockX + 1, $y + 4); $this->pdf->Cell($col1 - 2, 4, dol_trunc($this->user->getFullName($langs), 15), 0, 0);
$address = trim(($this->societe->address ?? '').' '.($this->societe->zip ?? '').' '.($this->societe->town ?? ''));
$this->pdf->SetXY($titleBlockX + $col1 + 1, $y + 4); $this->pdf->Cell($col2 - 2, 4, dol_trunc($address, 25), 0, 0);
$this->pdf->SetXY($titleBlockX + $col1 + $col2 + 1, $y + 4); $this->pdf->Cell($col3 - 2, 4, $this->anlage->type_label ?? '-', 0, 0);
$this->pdf->SetXY($titleBlockX + $col1 + $col2 + $col3 + 1, $y + 4); $this->pdf->Cell($col4 - 2, 4, 'A', 0, 0);
// Zeile 5: Abgänge | Format | Norm
$y = $titleBlockY + (4 * $rowHeight);
$this->pdf->SetFont('dejavusans', '', 6);
$this->pdf->SetXY($titleBlockX + 1, $y + 1); $this->pdf->Cell($col1 - 2, 3, 'Abgänge', 0, 0);
$this->pdf->SetXY($titleBlockX + $col1 + 1, $y + 1); $this->pdf->Cell($col2 - 2, 3, 'Format', 0, 0);
$this->pdf->SetXY($titleBlockX + $col1 + $col2 + 1, $y + 1); $this->pdf->Cell($col3 + $col4 - 2, 3, 'Norm', 0, 0);
$this->pdf->SetFont('dejavusans', '', 8);
$this->pdf->SetXY($titleBlockX + 1, $y + 4); $this->pdf->Cell($col1 - 2, 4, count($this->circuitPaths), 0, 0);
$this->pdf->SetXY($titleBlockX + $col1 + 1, $y + 4); $this->pdf->Cell($col2 - 2, 4, $this->format.' '.$this->orientation, 0, 0);
$this->pdf->SetFont('dejavusans', '', 6);
$this->pdf->SetXY($titleBlockX + $col1 + $col2 + 1, $y + 4); $this->pdf->Cell($col3 + $col4 - 2, 4, 'DIN EN 61082 / DIN EN 81346', 0, 0);
// Zeile 6-7: Firmenname
$y = $titleBlockY + (5 * $rowHeight);
$this->pdf->SetFont('dejavusans', 'B', 9);
$this->pdf->SetXY($titleBlockX + 2, $y + 3);
$this->pdf->Cell($titleBlockWidth - 4, $rowHeight * 2 - 6, $GLOBALS['mysoc']->name ?? 'ALLES WATT LÄUFT', 0, 0, 'C');
}
}