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'); } }