Merge branch 'main' of http://gitea.schoenenberger.de/mistangl/kabellaengen
This commit is contained in:
+261
-286
@@ -145,47 +145,18 @@ def draw_sensors(plines, doc):
|
|||||||
pos2sensors[(pt2.x, pt2.y)].append(pl)
|
pos2sensors[(pt2.x, pt2.y)].append(pl)
|
||||||
|
|
||||||
# Sensor Blöcke zeichnen
|
# Sensor Blöcke zeichnen
|
||||||
for (x,y), pls in pos2sensors.items():
|
for (x,y), pls in pos2sensors.items():
|
||||||
for i, pl in enumerate(pls):
|
for i, pl in enumerate(pls):
|
||||||
sensor_name = pl.id.split('-')[-1]
|
sensor_name = pl.id.split('-')[-1]
|
||||||
pt1 = pl.coords[-2]
|
pt1, pt2 = pl.coords[-2], pl.coords[-1]
|
||||||
pt2 = pl.coords[-1]
|
|
||||||
|
# Platzierungsinfo über Hilfsfunktion
|
||||||
|
placement_info = _calculate_text_placement(pt1, pt2, 'sensor', i, len(pls))
|
||||||
|
|
||||||
dx = pt2.x - pt1.x
|
|
||||||
dy = pt2.y - pt1.y
|
|
||||||
text = msp.add_text(sensor_name, dxfattribs=dxfattribs_sensors)
|
text = msp.add_text(sensor_name, dxfattribs=dxfattribs_sensors)
|
||||||
|
text.dxf.halign = placement_info["halign"]
|
||||||
# Offsets für Beschriftungen
|
text.dxf.valign = placement_info["valign"]
|
||||||
offsetx = 0
|
text.set_placement(placement_info["placement"])
|
||||||
offsety = 0
|
|
||||||
|
|
||||||
# Kabel Horizontal
|
|
||||||
if abs(dx) > abs(dy):
|
|
||||||
n = len(pls)
|
|
||||||
center_offset = i - (n - 1) / 2 # Falls mehrere Sensoren -> Verschiebung so, dass Kabel immer in Mitte ankommt
|
|
||||||
offsety = -80 + center_offset * 110 # Wert -80 über Try-and-Error zur Mitte der Beschriftung angepasst
|
|
||||||
if dx > 0:
|
|
||||||
halign = 0 # LEFT
|
|
||||||
offsetx = 50 # Wert 50 durch Try-and-Error sodass etwas Abstand zu Kabelpritsche
|
|
||||||
else:
|
|
||||||
halign = 2 # RIGHT
|
|
||||||
offsetx = -50 # Wert 50 durch Try-and-Error sodass etwas Abstand zu Kabelpritsche
|
|
||||||
valign = 1 # BOTTOM
|
|
||||||
|
|
||||||
# Kabel vertikal
|
|
||||||
else:
|
|
||||||
if dy > 0:
|
|
||||||
valign = 0 # BASELINE
|
|
||||||
offsety = 50 + i * 110 # nach oben anfügen
|
|
||||||
else:
|
|
||||||
valign = 3 # TOP
|
|
||||||
offsety = -50 - i * 110 # nach unten anfügen
|
|
||||||
halign = 1 # CENTER
|
|
||||||
|
|
||||||
# Alignments setzen
|
|
||||||
text.dxf.halign = halign
|
|
||||||
text.dxf.valign = valign
|
|
||||||
text.set_placement((pt2.x + offsetx, pt2.y + offsety))
|
|
||||||
|
|
||||||
def draw_subdists(plines, doc):
|
def draw_subdists(plines, doc):
|
||||||
msp = doc.modelspace()
|
msp = doc.modelspace()
|
||||||
@@ -211,36 +182,58 @@ def draw_subdists(plines, doc):
|
|||||||
subdist_name = pl.id.split('-')[0]
|
subdist_name = pl.id.split('-')[0]
|
||||||
|
|
||||||
pt2 = pl.coords[1]
|
pt2 = pl.coords[1]
|
||||||
dx = pt2.x - pt1.x
|
placement_info = _calculate_text_placement(pt1, pt2, 'subdist')
|
||||||
dy = pt2.y - pt1.y
|
|
||||||
|
|
||||||
# Offsets für Beschriftungen
|
|
||||||
offsetx = 0
|
|
||||||
offsety = 0
|
|
||||||
|
|
||||||
if abs(dx) > abs(dy): # Horizontal
|
|
||||||
offsety = -80 # Wert -80 über Try-and-Error zur Mitte der Beschriftung angepasst
|
|
||||||
if dx < 0:
|
|
||||||
halign = 0 # LEFT
|
|
||||||
offsetx = 50 # Wert 50 durch Try-and-Error sodass etwas Abstand zu Kabelpritsche
|
|
||||||
else:
|
|
||||||
halign = 2 # RIGHT
|
|
||||||
offsetx = -50 # Wert 50 durch Try-and-Error sodass etwas Abstand zu Kabelpritsche
|
|
||||||
valign = 1 # BOTTOM
|
|
||||||
else: # Vertikal
|
|
||||||
if dy < 0:
|
|
||||||
valign = 0 # BASELINE
|
|
||||||
offsety = 50
|
|
||||||
else:
|
|
||||||
valign = 3 # TOP
|
|
||||||
offsety = -50
|
|
||||||
halign = 1 # CENTER
|
|
||||||
|
|
||||||
# Text platzieren
|
|
||||||
text = msp.add_text(subdist_name, dxfattribs=dxfattribs_subdists)
|
text = msp.add_text(subdist_name, dxfattribs=dxfattribs_subdists)
|
||||||
text.dxf.halign = halign
|
text.dxf.halign = placement_info["halign"]
|
||||||
text.dxf.valign = valign
|
text.dxf.valign = placement_info["valign"]
|
||||||
text.set_placement((pt1.x + offsetx, pt1.y + offsety))
|
text.set_placement(placement_info["placement"])
|
||||||
|
|
||||||
|
def _calculate_text_placement(pt1, pt2, text_type='sensor', item_index=0, total_items=1):
|
||||||
|
"""
|
||||||
|
Berechnet die optimale Position und Ausrichtung für Beschriftungen.
|
||||||
|
Gibt ein Dictionary mit Platzierungskoordinaten und Ausrichtungs-Flags zurück.
|
||||||
|
"""
|
||||||
|
dx = pt2.x - pt1.x
|
||||||
|
dy = pt2.y - pt1.y
|
||||||
|
|
||||||
|
# Lese Offsets aus der Konfiguration, mit Fallback-Werten
|
||||||
|
offset_h_dist = 50.0
|
||||||
|
offset_v_dist = 50.0
|
||||||
|
offset_y_center = -80.0
|
||||||
|
offset_y_stack = 110.0
|
||||||
|
|
||||||
|
offsetx, offsety = 0, 0
|
||||||
|
|
||||||
|
if abs(dx) > abs(dy): # Kabel verläuft hauptsächlich horizontal
|
||||||
|
valign = 1 # BOTTOM
|
||||||
|
if text_type == 'sensor':
|
||||||
|
center_offset = item_index - (total_items - 1) / 2
|
||||||
|
offsety = offset_y_center + center_offset * offset_y_stack
|
||||||
|
else: # subdist
|
||||||
|
offsety = offset_y_center
|
||||||
|
|
||||||
|
if dx > 0:
|
||||||
|
halign = 0 # LEFT
|
||||||
|
offsetx = offset_h_dist
|
||||||
|
else:
|
||||||
|
halign = 2 # RIGHT
|
||||||
|
offsetx = -offset_h_dist
|
||||||
|
|
||||||
|
else: # Kabel verläuft hauptsächlich vertikal
|
||||||
|
halign = 1 # CENTER
|
||||||
|
if dy > 0:
|
||||||
|
valign = 0 # BASELINE
|
||||||
|
offsety = offset_v_dist + item_index * offset_y_stack
|
||||||
|
else:
|
||||||
|
valign = 3 # TOP
|
||||||
|
offsety = -offset_v_dist - item_index * offset_y_stack
|
||||||
|
|
||||||
|
return {
|
||||||
|
"placement": (pt2.x + offsetx, pt2.y + offsety),
|
||||||
|
"halign": halign,
|
||||||
|
"valign": valign,
|
||||||
|
}
|
||||||
|
|
||||||
def model_from_json(json_file):
|
def model_from_json(json_file):
|
||||||
with open(json_file, encoding='utf-8') as fh:
|
with open(json_file, encoding='utf-8') as fh:
|
||||||
@@ -266,12 +259,6 @@ def parse_sensors_from_json(positions_json):
|
|||||||
sensors[name] = sensor
|
sensors[name] = sensor
|
||||||
return sensors
|
return sensors
|
||||||
|
|
||||||
def export_excel(plines, sens2cable, out_path):
|
|
||||||
# Hier für Excel Export
|
|
||||||
print("creating excel file with cable information ..")
|
|
||||||
write_excel_from_json(plines, sens2cable, out_path)
|
|
||||||
print("done")
|
|
||||||
|
|
||||||
def get_all_artnrs(plines: Polylines, sens2cable: dict) -> set:
|
def get_all_artnrs(plines: Polylines, sens2cable: dict) -> set:
|
||||||
all_artnrs = set()
|
all_artnrs = set()
|
||||||
for pl in plines.kabel:
|
for pl in plines.kabel:
|
||||||
@@ -294,249 +281,237 @@ def mark_missings(all_artnrs):
|
|||||||
if artnr not in bezeichner_cfg["Sivasnummern"]:
|
if artnr not in bezeichner_cfg["Sivasnummern"]:
|
||||||
bezeichner_cfg["Missing"][artnr] = ""
|
bezeichner_cfg["Missing"][artnr] = ""
|
||||||
|
|
||||||
def write_excel_from_json(plines:Polylines, sens2cable: dict, outpath:str, with_bom = True):
|
def write_excel_from_json(plines: Polylines, sens2cable: dict, outpath: str, with_bom=True):
|
||||||
wb1 = Workbook()
|
"""
|
||||||
# Dicts für Anzahl bzw kummulierte Länge
|
Orchestriert die Erstellung der Excel-Reports (Kabelübersicht und Stückliste).
|
||||||
count_summary = defaultdict(int)
|
"""
|
||||||
length_summary = defaultdict(float)
|
# 1. Daten aggregieren und für die Reports vorbereiten
|
||||||
sensor_summary = defaultdict(int)
|
processed_data = _process_cable_data(plines, sens2cable)
|
||||||
|
|
||||||
uv_sensor_count = defaultdict(lambda: defaultdict(int))
|
# 2. Haupt-Excel-Datei (Kabellängen und Fehler) erstellen
|
||||||
uv_artnr_count = defaultdict(lambda: defaultdict(int))
|
wb_main = Workbook()
|
||||||
uv_artnr_length = defaultdict(lambda: defaultdict(float))
|
|
||||||
|
_create_cable_list_sheet(wb_main.active, plines, sens2cable, bezeichner_cfg)
|
||||||
|
_create_cable_summary_sheet(wb_main.create_sheet(), processed_data)
|
||||||
|
_create_error_sheets(wb_main, plines)
|
||||||
|
|
||||||
|
wb_main.save(outpath)
|
||||||
|
print("Cable-Summary exported to Excel file")
|
||||||
|
|
||||||
|
# 3. Optionale Stücklisten-Datei (BOM) erstellen
|
||||||
|
if with_bom:
|
||||||
|
_create_bom_workbook(outpath, processed_data, bezeichner_cfg)
|
||||||
|
|
||||||
|
def _get_sivas_name(artnr, bezeichner_cfg):
|
||||||
|
"""Holt den SIVAS-Namen für eine gegebene Artikelnummer aus der Konfiguration."""
|
||||||
|
if not artnr:
|
||||||
|
return "Keine Artikelnummer vergeben. Layout prüfen."
|
||||||
|
|
||||||
|
name = bezeichner_cfg["Sivasnummern"].get(artnr, "")
|
||||||
|
if not name:
|
||||||
|
return f"Kein Eintrag zu Art.-Nr: {artnr} in bezeichner.cfg."
|
||||||
|
return name
|
||||||
|
|
||||||
|
def _process_cable_data(plines: Polylines, sens2cable: dict):
|
||||||
|
"""
|
||||||
|
Verarbeitet die Kabeldaten und aggregiert sie für die Excel-Ausgabe.
|
||||||
|
Gibt mehrere Dictionaries mit zusammengefassten Daten zurück.
|
||||||
|
"""
|
||||||
|
# Globale Zusammenfassungen
|
||||||
|
artnr_to_stückware_count = defaultdict(int)
|
||||||
|
artnr_to_meterware_length = defaultdict(float)
|
||||||
|
artnr_to_sensor_count = defaultdict(int)
|
||||||
|
|
||||||
|
# UV-spezifische (Unterverteiler) Zusammenfassungen
|
||||||
|
uv_to_artnr_to_stückware_count = defaultdict(lambda: defaultdict(int))
|
||||||
|
uv_to_artnr_to_meterware_length = defaultdict(lambda: defaultdict(float))
|
||||||
|
uv_to_artnr_to_sensor_count = defaultdict(lambda: defaultdict(int))
|
||||||
|
|
||||||
seen_sensors = set()
|
seen_sensors = set()
|
||||||
uv_sensor_mapping = defaultdict(dict)
|
uv_sensor_mapping = defaultdict(dict)
|
||||||
|
|
||||||
#Worksheet 1 - Kabellängen nach Kabel-ID
|
|
||||||
ws1 = wb1.active
|
|
||||||
ws1.title = "Length by ID"
|
|
||||||
ws1.append(["Cable-ID", "True Length (m)", "Cable-ArtNr", "Cable-Name (short)"])
|
|
||||||
|
|
||||||
ws1.column_dimensions['A'].width = 18
|
|
||||||
ws1.column_dimensions['B'].width = 15
|
|
||||||
ws1.column_dimensions['C'].width = 15
|
|
||||||
ws1.column_dimensions['D'].width = 25
|
|
||||||
|
|
||||||
|
|
||||||
for pl in plines.kabel:
|
for pl in plines.kabel:
|
||||||
length = round(pl.length /1000 , 1) # Umrechnung von mm in m
|
length_m = round(pl.length / 1000, 1)
|
||||||
uv_name = pl.id.split("-")[0]
|
uv_name, sensor_name = pl.id.split("-")[0], pl.id.split("-")[-1]
|
||||||
sensor_name = pl.id.split("-")[-1]
|
|
||||||
|
# Die Unterscheidung zwischen Stück- und Meterware wird hier explizit gemacht
|
||||||
|
is_meterware = "MA" in pl.id
|
||||||
|
|
||||||
# Globaler Sensor-Count (für erstes Sheet)
|
# Globaler Sensor-Count (einmal pro Sensor)
|
||||||
if sensor_name not in seen_sensors:
|
if sensor_name not in seen_sensors:
|
||||||
seen_sensors.add(sensor_name)
|
seen_sensors.add(sensor_name)
|
||||||
sensor_summary[pl.s_artinr] += 1
|
artnr_to_sensor_count[pl.s_artinr] += 1
|
||||||
|
|
||||||
# UV-spezifische Sensor-Mapping-Zwischenspeicherung
|
# UV-spezifisches Sensor-Mapping für präzise Zählung pro UV
|
||||||
if sensor_name not in uv_sensor_mapping[uv_name]:
|
if sensor_name not in uv_sensor_mapping[uv_name]:
|
||||||
uv_sensor_mapping[uv_name][sensor_name] = pl.s_artinr
|
uv_sensor_mapping[uv_name][sensor_name] = pl.s_artinr
|
||||||
|
|
||||||
for artnr in sens2cable[pl.id]:
|
for artnr in sens2cable[pl.id]:
|
||||||
cable_name = ""
|
if is_meterware:
|
||||||
if artnr.isdigit():
|
artnr_to_meterware_length[artnr] += math.ceil(length_m)
|
||||||
cable_name = bezeichner_cfg["Sivasnummern"][artnr]
|
uv_to_artnr_to_meterware_length[uv_name][artnr] += math.ceil(length_m)
|
||||||
ws1.append([pl.id, length, artnr, cable_name])
|
else: # Stückware
|
||||||
|
artnr_to_stückware_count[artnr] += 1
|
||||||
|
uv_to_artnr_to_stückware_count[uv_name][artnr] += 1
|
||||||
|
|
||||||
if "MA" in pl.id:
|
# UV-spezifische Sensorzählung basierend auf dem Mapping
|
||||||
length_summary[artnr] += math.ceil(length) # Aufrunden von z.b. 10,3 auf 11 m (volle Meter!)
|
|
||||||
uv_artnr_length[uv_name][artnr] += math.ceil(length)
|
|
||||||
|
|
||||||
else:
|
|
||||||
count_summary[artnr] += 1
|
|
||||||
uv_artnr_count[uv_name][artnr] += 1
|
|
||||||
|
|
||||||
# UV-spezifische Sensorzählung (robust, 1x je Sensor pro UV)
|
|
||||||
for uv_name, sensor_dict in uv_sensor_mapping.items():
|
for uv_name, sensor_dict in uv_sensor_mapping.items():
|
||||||
for sensor_name, artnr in sensor_dict.items():
|
for artnr in sensor_dict.values():
|
||||||
uv_sensor_count[uv_name][artnr] += 1
|
uv_to_artnr_to_sensor_count[uv_name][artnr] += 1
|
||||||
|
|
||||||
# Worksheet 2 - Kabelnummern und Stückzahlen
|
|
||||||
ws2 = wb1.create_sheet("Cables SIVAS")
|
|
||||||
ws2.append(["Cable-ArtNr", "Amount (pcs)", "Cumm. Length (m)"])
|
|
||||||
|
|
||||||
ws2.column_dimensions['A'].width = 20
|
|
||||||
ws2.column_dimensions['B'].width = 12
|
|
||||||
ws2.column_dimensions['C'].width = 15
|
|
||||||
|
|
||||||
all_artnrs = set(count_summary.keys()) | set(length_summary.keys())
|
|
||||||
|
|
||||||
for artnr in sorted(all_artnrs):
|
|
||||||
count = count_summary.get(artnr, "")
|
|
||||||
cumm_length = length_summary.get(artnr, "")
|
|
||||||
ws2.append([artnr, count, cumm_length])
|
|
||||||
|
|
||||||
|
|
||||||
# Abfage ob Fehler Worsheets ausgegeben werden
|
|
||||||
if len(plines.errors_sensors) > 0 or len(plines.errors_dists) > 0:
|
|
||||||
|
|
||||||
# Worksheet 3 - Nicht an Racks gekoppeltes Equipment
|
|
||||||
ws3 = wb1.create_sheet("ERR-Equipment-Connection")
|
|
||||||
ws3.append(["Type", "ID", "x", "y"])
|
|
||||||
|
|
||||||
ws3.column_dimensions['A'].width = 20
|
|
||||||
|
|
||||||
# nicht angebundene Sensoren
|
|
||||||
for error in plines.errors_sensors:
|
|
||||||
ws3.append(["Sensor / Actuator", error.name, error.coords.x, error.coords.y])
|
|
||||||
# nicht angebundene Distributoren
|
|
||||||
for error in plines.errors_dists:
|
|
||||||
ws3.append(["Subistributor", error.name, error.coords.x, error.coords.y])
|
|
||||||
|
|
||||||
if len(plines.errors_routing) > 0:
|
|
||||||
# Worksheet 4 - Fehlgeschlagenes Routing
|
|
||||||
ws4 = wb1.create_sheet("ERR-Routing")
|
|
||||||
ws4.append(["Subdistributor", "Sensor / Actuator", "Details"])
|
|
||||||
|
|
||||||
ws4.column_dimensions['C'].width = 35
|
|
||||||
|
|
||||||
nicht_angebunden = set(e.name for e in plines.errors_sensors + plines.errors_dists)
|
|
||||||
|
|
||||||
for routing_error in plines.errors_routing:
|
|
||||||
uv = routing_error.unterverteiler
|
|
||||||
uv_nicht_angebunden = uv in nicht_angebunden
|
|
||||||
|
|
||||||
# Wenn Distributor nicht in Layout gefunden, dann nur diesen Fehler ausgeben. Alle Verbindungen hier fehlgeschlagen
|
return {
|
||||||
|
"artnr_to_stückware_count": artnr_to_stückware_count,
|
||||||
|
"artnr_to_meterware_length": artnr_to_meterware_length,
|
||||||
|
"artnr_to_sensor_count": artnr_to_sensor_count,
|
||||||
|
"uv_to_artnr_to_stückware_count": uv_to_artnr_to_stückware_count,
|
||||||
|
"uv_to_artnr_to_meterware_length": uv_to_artnr_to_meterware_length,
|
||||||
|
"uv_to_artnr_to_sensor_count": uv_to_artnr_to_sensor_count,
|
||||||
|
}
|
||||||
|
|
||||||
|
def _create_cable_list_sheet(ws, plines, sens2cable, bezeichner_cfg):
|
||||||
|
"""Erstellt das Arbeitsblatt 'Length by ID'."""
|
||||||
|
ws.title = "Length by ID"
|
||||||
|
ws.append(["Cable-ID", "True Length (m)", "Cable-ArtNr", "Cable-Name (short)"])
|
||||||
|
ws.column_dimensions['A'].width = 18
|
||||||
|
ws.column_dimensions['B'].width = 15
|
||||||
|
ws.column_dimensions['C'].width = 15
|
||||||
|
ws.column_dimensions['D'].width = 25
|
||||||
|
|
||||||
|
for pl in plines.kabel:
|
||||||
|
length = round(pl.length / 1000, 1)
|
||||||
|
for artnr in sens2cable[pl.id]:
|
||||||
|
cable_name = bezeichner_cfg["Sivasnummern"].get(artnr, "") if artnr.isdigit() else ""
|
||||||
|
ws.append([pl.id, length, artnr, cable_name])
|
||||||
|
|
||||||
|
def _create_cable_summary_sheet(ws, processed_data):
|
||||||
|
"""Erstellt das Arbeitsblatt 'Cables SIVAS'."""
|
||||||
|
ws.title = "Cables SIVAS"
|
||||||
|
ws.append(["Cable-ArtNr", "Amount (pcs)", "Cumm. Length (m)"])
|
||||||
|
ws.column_dimensions['A'].width = 20
|
||||||
|
ws.column_dimensions['B'].width = 12
|
||||||
|
ws.column_dimensions['C'].width = 15
|
||||||
|
|
||||||
|
count_summary = processed_data["artnr_to_stückware_count"]
|
||||||
|
length_summary = processed_data["artnr_to_meterware_length"]
|
||||||
|
all_artnrs = sorted(set(count_summary.keys()) | set(length_summary.keys()))
|
||||||
|
|
||||||
|
for artnr in all_artnrs:
|
||||||
|
ws.append([
|
||||||
|
artnr,
|
||||||
|
count_summary.get(artnr, ""),
|
||||||
|
length_summary.get(artnr, "")
|
||||||
|
])
|
||||||
|
|
||||||
|
def _create_error_sheets(wb, plines):
|
||||||
|
"""Erstellt die Arbeitsblätter für alle aufgetretenen Fehler."""
|
||||||
|
# Sheet: ERR-Equipment-Connection
|
||||||
|
if plines.errors_sensors or plines.errors_dists:
|
||||||
|
ws = wb.create_sheet("ERR-Equipment-Connection")
|
||||||
|
ws.append(["Type", "ID", "x", "y"])
|
||||||
|
ws.column_dimensions['A'].width = 20
|
||||||
|
for error in plines.errors_sensors:
|
||||||
|
ws.append(["Sensor / Actuator", error.name, int(error.coords.x), int(error.coords.y)])
|
||||||
|
for error in plines.errors_dists:
|
||||||
|
ws.append(["Subistributor", error.name, int(error.coords.x), int(error.coords.y)])
|
||||||
|
|
||||||
|
# Sheet: ERR-Routing
|
||||||
|
if plines.errors_routing:
|
||||||
|
ws = wb.create_sheet("ERR-Routing")
|
||||||
|
ws.append(["Subdistributor", "Sensor / Actuator", "Details"])
|
||||||
|
ws.column_dimensions['A'].width = 20
|
||||||
|
ws.column_dimensions['B'].width = 20
|
||||||
|
ws.column_dimensions['C'].width = 50
|
||||||
|
nicht_angebunden = {e.name for e in plines.errors_sensors + plines.errors_dists}
|
||||||
|
for error in plines.errors_routing:
|
||||||
|
uv, uv_nicht_angebunden = error.unterverteiler, error.unterverteiler in nicht_angebunden
|
||||||
if uv in plines.errors_dists_not_in_layout:
|
if uv in plines.errors_dists_not_in_layout:
|
||||||
ws4.append([uv,"-", "Distributor not found in given layout."])
|
ws.append([uv, "-", "Distributor not found in given layout."])
|
||||||
continue
|
continue
|
||||||
|
for sensor in error.sensoren:
|
||||||
for sensor in routing_error.sensoren:
|
|
||||||
sensor_nicht_angebunden = sensor in nicht_angebunden
|
sensor_nicht_angebunden = sensor in nicht_angebunden
|
||||||
|
if sensor_nicht_angebunden and uv_nicht_angebunden: grund = "Subdistributor and sensor / actuator not connected to racks"
|
||||||
if sensor_nicht_angebunden and uv_nicht_angebunden:
|
elif sensor_nicht_angebunden: grund = "Sensor / actuator not connected to racks"
|
||||||
grund = "Subdistributor and sensor / actuator not connected to racks"
|
elif uv_nicht_angebunden: grund = "Subdistributor not connected to racks"
|
||||||
elif sensor_nicht_angebunden:
|
else: grund = "Failed routing (not caused by missing connection)"
|
||||||
grund = "Sensor / actuator not connected to racks"
|
ws.append([uv, sensor, grund])
|
||||||
elif uv_nicht_angebunden:
|
|
||||||
grund = "Subdistributor not connected to racks"
|
|
||||||
else:
|
|
||||||
grund = "Failed routing (not caused by missing connection)"
|
|
||||||
|
|
||||||
ws4.append([uv, sensor, grund])
|
|
||||||
|
|
||||||
if len(plines.errors_missing_attributes) > 0:
|
# Sheet: ERR-Attributes
|
||||||
# Worksheet 5 - Fehlende Attribute -> kein Routing
|
if plines.errors_missing_attributes:
|
||||||
ws5 = wb1.create_sheet("ERR-Attributes")
|
ws = wb.create_sheet("ERR-Attributes")
|
||||||
ws5.append(["ID", "Error Detail"])
|
ws.append(["ID", "Error Detail"])
|
||||||
ws5.column_dimensions['B'].width = 35
|
ws.column_dimensions['B'].width = 35
|
||||||
|
|
||||||
for sname, err_msg in plines.errors_missing_attributes.items():
|
for sname, err_msg in plines.errors_missing_attributes.items():
|
||||||
ws5.append([sname, err_msg])
|
ws.append([sname, err_msg])
|
||||||
|
|
||||||
wb1.save(outpath)
|
def _create_bom_workbook(outpath, processed_data, bezeichner_cfg):
|
||||||
print("Cable-Summary exported to Excel file")
|
"""Erstellt eine separate Excel-Arbeitsmappe für die Stückliste (BOM)."""
|
||||||
|
wb = Workbook()
|
||||||
|
|
||||||
|
# Daten aus dem verarbeiteten Dictionary extrahieren
|
||||||
|
sensor_counts = processed_data["artnr_to_sensor_count"]
|
||||||
|
stückware_counts = processed_data["artnr_to_stückware_count"]
|
||||||
|
meterware_lengths = processed_data["artnr_to_meterware_length"]
|
||||||
|
|
||||||
|
uv_sensor_counts = processed_data["uv_to_artnr_to_sensor_count"]
|
||||||
|
uv_stückware_counts = processed_data["uv_to_artnr_to_stückware_count"]
|
||||||
|
uv_meterware_lengths = processed_data["uv_to_artnr_to_meterware_length"]
|
||||||
|
|
||||||
if with_bom:
|
# Sheet 1: Globale BOM
|
||||||
wb2 = Workbook()
|
ws1 = wb.active
|
||||||
ws1 = wb2.active
|
ws1.title = "BOM"
|
||||||
ws1.title = "BOM"
|
ws1.append(["Art.-Number", "Amount (pcs)", "Length (m)", "Name (SIVAS)"])
|
||||||
ws1.append(["Art.-Number", "Amount (pcs)", "Length (m)", "Name (SIVAS)"])
|
ws1.column_dimensions['A'].width = 15
|
||||||
|
ws1.column_dimensions['B'].width = 12
|
||||||
|
ws1.column_dimensions['C'].width = 10
|
||||||
|
ws1.column_dimensions['D'].width = 50
|
||||||
|
|
||||||
ws1.column_dimensions['A'].width = 15
|
# Sensoren auflisten
|
||||||
ws1.column_dimensions['B'].width = 12
|
for artnr in sorted(sensor_counts.keys()):
|
||||||
ws1.column_dimensions['C'].width = 10
|
ws1.append([artnr, sensor_counts[artnr], "", _get_sivas_name(artnr, bezeichner_cfg)])
|
||||||
ws1.column_dimensions['D'].width = 50
|
|
||||||
|
|
||||||
# Sensoren separat auflisten
|
# Kabel auflisten
|
||||||
for artnr in sorted(sensor_summary.keys()):
|
all_cable_artnrs = sorted(set(stückware_counts.keys()) | set(meterware_lengths.keys()))
|
||||||
if artnr == "":
|
for artnr in all_cable_artnrs:
|
||||||
name = "Keine Artikelnummer vergeben. Layout prüfen."
|
ws1.append([
|
||||||
else:
|
artnr,
|
||||||
name = bezeichner_cfg["Sivasnummern"].get(artnr, "")
|
stückware_counts.get(artnr, ""),
|
||||||
if name == "":
|
meterware_lengths.get(artnr, ""),
|
||||||
name = f"Kein Eintrag zu Art.-Nr: {artnr} in bezeichner.cfg."
|
_get_sivas_name(artnr, bezeichner_cfg)
|
||||||
|
])
|
||||||
|
|
||||||
count = sensor_summary[artnr]
|
# Sheet 2: BOM nach UV
|
||||||
|
ws2 = wb.create_sheet("BOM by UV")
|
||||||
ws1.append([
|
ws2.append(["UV", "Art.-Number", "Amount (pcs)", "Length (m)", "Name (SIVAS)"])
|
||||||
artnr,
|
ws2.column_dimensions['A'].width = 15
|
||||||
count,
|
ws2.column_dimensions['B'].width = 15
|
||||||
"", # Sensoren haben keine Länge
|
ws2.column_dimensions['C'].width = 12
|
||||||
name
|
ws2.column_dimensions['D'].width = 10
|
||||||
])
|
ws2.column_dimensions['E'].width = 50
|
||||||
|
|
||||||
|
all_uvs = sorted(set(uv_sensor_counts.keys()) | set(uv_stückware_counts.keys()) | set(uv_meterware_lengths.keys()))
|
||||||
|
|
||||||
# Kabel separat auflisten
|
for uv in all_uvs:
|
||||||
for artnr in sorted(set(count_summary.keys()) | set(length_summary.keys())):
|
all_artnrs_in_uv = sorted(
|
||||||
if artnr == "":
|
set(uv_sensor_counts.get(uv, {}).keys()) |
|
||||||
name = "Keine Artikelnummer vergeben. Layout prüfen."
|
set(uv_stückware_counts.get(uv, {}).keys()) |
|
||||||
else:
|
set(uv_meterware_lengths.get(uv, {}).keys())
|
||||||
name = bezeichner_cfg["Sivasnummern"].get(artnr, "")
|
)
|
||||||
if name == "":
|
for artnr in all_artnrs_in_uv:
|
||||||
name = f"Kein Eintrag zu Art.-Nr: {artnr} in bezeichner.cfg."
|
name = _get_sivas_name(artnr, bezeichner_cfg)
|
||||||
|
sensor_amount = uv_sensor_counts.get(uv, {}).get(artnr, 0)
|
||||||
|
cable_count = uv_stückware_counts.get(uv, {}).get(artnr, 0)
|
||||||
|
cable_length = uv_meterware_lengths.get(uv, {}).get(artnr, 0)
|
||||||
|
|
||||||
count = count_summary.get(artnr, "")
|
if sensor_amount > 0:
|
||||||
length = length_summary.get(artnr, "")
|
ws2.append([uv, artnr, sensor_amount, "", name])
|
||||||
ws1.append([
|
if cable_count > 0:
|
||||||
artnr,
|
ws2.append([uv, artnr, cable_count, "", name])
|
||||||
count,
|
if cable_length > 0:
|
||||||
length,
|
ws2.append([uv, artnr, "", cable_length, name])
|
||||||
name
|
|
||||||
])
|
|
||||||
|
|
||||||
# Neues Sheet für UV-spezifische Übersicht
|
bom_path = outpath.replace("_cables.xlsx", "_BOM.xlsx")
|
||||||
ws2 = wb2.create_sheet("BOM by UV")
|
wb.save(bom_path)
|
||||||
ws2.append(["UV", "Art.-Number", "Amount (pcs)", "Length (m)", "Name (SIVAS)"])
|
print(f"BOM saved as an excel file")
|
||||||
|
|
||||||
ws2.column_dimensions['A'].width = 15
|
|
||||||
ws2.column_dimensions['B'].width = 15
|
|
||||||
ws2.column_dimensions['C'].width = 12
|
|
||||||
ws2.column_dimensions['D'].width = 10
|
|
||||||
ws2.column_dimensions['E'].width = 50
|
|
||||||
|
|
||||||
all_uvs = set(uv_sensor_count.keys()) | set(uv_artnr_count.keys()) | set(uv_artnr_length.keys())
|
|
||||||
|
|
||||||
for uv in sorted(all_uvs):
|
|
||||||
all_artnrs = set()
|
|
||||||
all_artnrs.update(uv_sensor_count.get(uv, {}).keys())
|
|
||||||
all_artnrs.update(uv_artnr_count.get(uv, {}).keys())
|
|
||||||
all_artnrs.update(uv_artnr_length.get(uv, {}).keys())
|
|
||||||
|
|
||||||
for artnr in sorted(all_artnrs):
|
|
||||||
if artnr == "":
|
|
||||||
name = "Keine Artikelnummer vergeben. Layout prüfen."
|
|
||||||
else:
|
|
||||||
name = bezeichner_cfg["Sivasnummern"].get(artnr, "")
|
|
||||||
if name == "":
|
|
||||||
name = f"Kein Eintrag zu Art.-Nr: {artnr} in bezeichner.cfg."
|
|
||||||
sensor_amount = uv_sensor_count.get(uv, {}).get(artnr, 0)
|
|
||||||
cable_count = uv_artnr_count.get(uv, {}).get(artnr, 0)
|
|
||||||
cable_length = uv_artnr_length.get(uv, {}).get(artnr, 0)
|
|
||||||
|
|
||||||
# Sensoren separat eintragen (Anzahl)
|
|
||||||
if sensor_amount > 0:
|
|
||||||
ws2.append([
|
|
||||||
uv,
|
|
||||||
artnr,
|
|
||||||
sensor_amount,
|
|
||||||
"",
|
|
||||||
name
|
|
||||||
])
|
|
||||||
|
|
||||||
# a) Nur Anzahl, wenn kein "MA" im Namen
|
|
||||||
if cable_count > 0 and cable_length == 0:
|
|
||||||
ws2.append([
|
|
||||||
uv,
|
|
||||||
artnr,
|
|
||||||
cable_count,
|
|
||||||
"",
|
|
||||||
name
|
|
||||||
])
|
|
||||||
|
|
||||||
# b) Nur Länge, wenn Kabel mit "MA" (Meterware)
|
|
||||||
if cable_length > 0:
|
|
||||||
ws2.append([
|
|
||||||
uv,
|
|
||||||
artnr,
|
|
||||||
"",
|
|
||||||
cable_length,
|
|
||||||
name
|
|
||||||
])
|
|
||||||
|
|
||||||
bom_path = outpath.replace("_cables.xlsx", "_BOM.xlsx")
|
|
||||||
wb2.save(bom_path)
|
|
||||||
print(f"BOM saved as an excel file")
|
|
||||||
|
|
||||||
def check_file_in_work(work_dir, filename):
|
def check_file_in_work(work_dir, filename):
|
||||||
fexists = True
|
fexists = True
|
||||||
|
|||||||
Reference in New Issue
Block a user