Files
kabellaengen/lib/drawdxf.py
T

795 lines
29 KiB
Python

import argparse
import ezdxf
import json
import os.path
from dataclasses import dataclass, asdict, field
from dacite import from_dict
from typing import List, Dict
from datetime import datetime
from openpyxl import Workbook
import math
from collections import defaultdict
import configparser
import updateconfignames as uc
@dataclass
class Point:
x: float
y: float
@dataclass
class Polyline:
id: str
s_artinr: str
coords: List[Point]
length: float
def to_tuple(self):
ret = list()
for p in self.coords:
ret.append( (p.x, p.y) )
return ret
@dataclass
# Fehlgeschlagene Anbindung von einem Sensor / Dist zu einem Rack
class Error_Connection:
name: str
coords: Point
@dataclass
# Felgeschlagene Verbindung von einem Dist zu Sensor(en) aus beliebigem Grund
class Error_Routing:
unterverteiler: str
sensoren: List[str]
@dataclass
class Coordinate:
x: float
y: float
z: float
@dataclass
class RackGeometry:
length: float
coordinates: List[Coordinate]
@dataclass
class Polylines:
kabel: List[Polyline]
errors_routing: List[Error_Routing]
errors_sensors: List[Error_Connection]
errors_dists: List[Error_Connection]
errors_dists_not_in_layout: List[str]
errors_sensors_not_in_layout: List[str]
errors_missing_attributes: Dict[str, str]
rack_geometry: Dict[str, RackGeometry] = field(default_factory=dict)
def add_polyline(msp, points:Polyline, dxf_attribs):
pts = points.to_tuple()
pline = msp.add_lwpolyline(points=pts, dxfattribs=dxf_attribs)
pline.rgb = (255, 128, 0)
def new_dxf(plines, out_path):
""" creates a new dxf file with a polyline inside which is created by the given json file
"""
doc = ezdxf.new('R2018', setup=True)
draw_cables(plines, doc)
draw_sensors(plines, doc)
draw_subdists(plines, doc)
draw_racks(plines, doc)
doc.saveas(out_path)
print("Cable-Routes exported to new dxf-file")
def modify_original_dxf(plines, originaldxf):
""" adds new layer to original .dxf-file that contains cables
"""
print("adding cables into original .dxf ..")
doc = ezdxf.readfile(originaldxf)
draw_cables(plines, doc)
doc.saveas(out_path)
print("done")
def draw_cables(plines, doc):
msp = doc.modelspace()
timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M")
cable_layer = f"cables_{timestamp}"
# Kabel-Layer anlegen
if cable_layer not in doc.layers:
doc.layers.add(name=cable_layer, color=7)
dxfattribs_cable={"layer": cable_layer}
# Kabel zeichnen
for pl in plines.kabel:
# Polyline für Kabel zeichnen
add_polyline(msp, pl, dxfattribs_cable)
def draw_racks(plines, doc):
msp = doc.modelspace()
timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M")
rack_layer = f"racks_{timestamp}"
# Rack-Layer anlegen (Farbe 3 = grün z.B.)
if rack_layer not in doc.layers:
doc.layers.add(name=rack_layer, color=3, lineweight=200) # lineweight für Dicke
dxfattribs_rack = {
"layer": rack_layer,
"color": 3,
"lineweight": 200 # Lineweight (in 1/100 mm)
}
for rack_name, rack_geom in plines.rack_geometry.items():
classifier = rack_name[0]
if classifier in ("t", "v", "d"):
continue
coords = [(pt.x, pt.y, pt.z) for pt in rack_geom.coordinates]
if not coords:
continue
polyline = msp.add_polyline3d(coords, dxfattribs=dxfattribs_rack)
if classifier == "c":
continue
# Textplatzierung
x, y, z = coords[0] # Get the first coordinate for text placement
# Orientierung bestimmen (horizontal oder vertikal)
if len(coords) >= 2:
x2, y2, _ = coords[1]
dx = x2 - x
dy = y2 - y
is_vertical = abs(dy) > abs(dx)
else:
is_vertical = False # Standard: horizontal
# Text platzieren
text_entity = msp.add_text(
rack_name,
dxfattribs={
"layer": rack_layer,
"height": 75,
"color": 3,
"rotation": 90 if is_vertical else 0,
}
)
# Offset definieren
offset_x = -50 if is_vertical else 50
offset_y = 100 if is_vertical else 50
text_entity.set_placement((x + offset_x, y + offset_y))
def find_close_key(pos2sensors, x, y, tolerance=10): # !!! Toleranz nicht in Config !!!
''' Funktion überprüft ob Sensoren nahezu identisch an der gleichen Stelle liegen und legt sie in diesem fall aufeinander
Wird benötigt, um zusammengehörige Sensoren gestaffelt auf dxf zu zeichen
'''
for (px, py) in pos2sensors:
if abs(px - x) <= tolerance and abs(py - y) <= tolerance:
return (px, py)
return None
def draw_sensors(plines, doc):
msp = doc.modelspace()
timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M")
sensor_layer = f"sensors_{timestamp}"
# Sensor-Layer erzeugen
if sensor_layer not in doc.layers:
doc.layers.add(name=sensor_layer, color=5)
dxfattribs_sensors={"layer": sensor_layer, "height": 100}
# Sensoren nach Endpunkten gruppieren -> mehrfacheinträge gestaffelt zeichnen
pos2sensors = defaultdict(list)
for pl in plines.kabel:
pt2 = pl.coords[-1] #Endpunkt des Kabels = Sensor Position
pos_key = find_close_key(pos2sensors, pt2.x, pt2.y)
if pos_key:
pos2sensors[pos_key].append(pl)
else:
pos2sensors[(pt2.x, pt2.y)].append(pl)
# Sensor Blöcke zeichnen
for (x,y), pls in pos2sensors.items():
for i, pl in enumerate(pls):
sensor_name = pl.id.split('-')[-1]
pt1, pt2 = pl.coords[-2], pl.coords[-1]
# Platzierungsinfo über Hilfsfunktion
placement_info = _calculate_text_placement(pt1, pt2, 'sensor', i, len(pls))
text = msp.add_text(sensor_name, dxfattribs=dxfattribs_sensors)
text.dxf.halign = placement_info["halign"]
text.dxf.valign = placement_info["valign"]
text.set_placement(placement_info["placement"])
def draw_subdists(plines, doc):
msp = doc.modelspace()
timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M")
subdist_layer = f"subdists_{timestamp}"
# Sensor-Layer erzeugen
if subdist_layer not in doc.layers:
doc.layers.add(name=subdist_layer, color=3)
dxfattribs_subdists={"layer": subdist_layer, "height": 100}
subdist_positions = set()
for pl in plines.kabel:
pt1 = pl.coords[1] # Startposition = UV-Position
pos = (pt1.x, pt1.y)
if pos in subdist_positions:
continue
subdist_positions.add(pos)
subdist_name = pl.id.split('-')[0]
pt2 = pl.coords[0]
placement_info = _calculate_text_placement(pt1, pt2, 'subdist')
text = msp.add_text(subdist_name, dxfattribs=dxfattribs_subdists)
text.dxf.halign = placement_info["halign"]
text.dxf.valign = placement_info["valign"]
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):
with open(json_file, encoding='utf-8') as fh:
data = json.load(fh)
plines = from_dict(
data_class=Polylines,
data=data
)
return plines
def parse_sensors_from_json(positions_json):
with open(positions_json, encoding='utf-8') as fh:
data = json.load(fh)
sensors = {}
for name, data in data.get("sensors", {}).items():
sensor = Sensors(
name=name,
artinr=data.get("ARTINR", ""),
pos=data.get("pos", [0.0, 0.0]),
)
sensors[name] = sensor
return sensors
def get_all_artnrs(plines: Polylines, sens2cable: dict) -> set:
all_artnrs = set()
for pl in plines.kabel:
if pl.s_artinr:
all_artnrs.add(pl.s_artinr)
for cablelist in sens2cable.values():
for artnr in cablelist:
if isinstance(artnr, str) and artnr.isdigit():
all_artnrs.add(artnr)
return all_artnrs
def mark_missings(all_artnrs):
if "Sivasnummern" not in bezeichner_cfg:
bezeichner_cfg["Sivasnummern"] = {}
if "Missing" not in bezeichner_cfg:
bezeichner_cfg["Missing"] = {}
for artnr in all_artnrs:
if artnr not in bezeichner_cfg["Sivasnummern"]:
bezeichner_cfg["Missing"][artnr] = ""
def write_excel_from_json(plines: Polylines, sens2cable: dict, outpath: str, with_bom=True):
"""
Steuert die Erstellung der Excel-Reports (Kabelübersicht und Stückliste).
"""
# 1. Daten aggregieren und für die Reports vorbereiten
processed_data = _process_cable_data(plines, sens2cable)
# 2. Haupt-Excel-Datei (Kabellängen und Fehler) erstellen
wb_main = Workbook()
_create_cable_list_sheet(wb_main.active, plines, sens2cable, bezeichner_cfg)
_create_cable_summary_sheet(wb_main.create_sheet(), processed_data)
_create_rack_lengths_sheet(wb_main.create_sheet(), plines)
_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()
uv_sensor_mapping = defaultdict(dict)
for pl in plines.kabel:
length_m = round(pl.length / 1000, 1)
uv_name, sensor_name = pl.id.split("-")[0], pl.id.split("-")[-1]
# Die Unterscheidung zwischen Stück- und Meterware wird hier explizit gemacht
is_meterware = "MA" in pl.id
# Globaler Sensor-Count (einmal pro Sensor)
if sensor_name not in seen_sensors:
seen_sensors.add(sensor_name)
artnr_to_sensor_count[pl.s_artinr] += 1
# UV-spezifisches Sensor-Mapping für präzise Zählung pro UV
if sensor_name not in uv_sensor_mapping[uv_name]:
uv_sensor_mapping[uv_name][sensor_name] = pl.s_artinr
for artnr in sens2cable[pl.id]:
if is_meterware:
artnr_to_meterware_length[artnr] += math.ceil(length_m)
uv_to_artnr_to_meterware_length[uv_name][artnr] += math.ceil(length_m)
else: # Stückware
artnr_to_stückware_count[artnr] += 1
uv_to_artnr_to_stückware_count[uv_name][artnr] += 1
# UV-spezifische Sensorzählung basierend auf dem Mapping
for uv_name, sensor_dict in uv_sensor_mapping.items():
for artnr in sensor_dict.values():
uv_to_artnr_to_sensor_count[uv_name][artnr] += 1
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_rack_lengths_sheet(ws, plines):
"""Erstellt das Arbeitsblatt 'Rack-Lengths'."""
ws.title = "Rack-Lengths"
ws.append(["Rack-ID", "Length (m)"])
ws.column_dimensions['A'].width = 18
ws.column_dimensions['B'].width = 15
for rackname, rack_geom in plines.rack_geometry.items():
classifier = rackname[0]
if classifier in ("t", "v", "c", "d"):
continue
else:
ws.append([rackname, rack_geom.length])
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:
ws.append([uv, "-", "Distributor not found in given layout."])
continue
for sensor in error.sensoren:
sensor_nicht_angebunden = sensor in nicht_angebunden
if sensor_nicht_angebunden and uv_nicht_angebunden: grund = "Subdistributor and sensor / actuator not connected to racks"
elif sensor_nicht_angebunden: grund = "Sensor / actuator not connected to racks"
elif uv_nicht_angebunden: grund = "Subdistributor not connected to racks"
else: grund = "Failed routing (not caused by missing connection)"
ws.append([uv, sensor, grund])
# Sheet: ERR-Attributes
if plines.errors_missing_attributes:
ws = wb.create_sheet("ERR-Attributes")
ws.append(["ID", "Error Detail"])
ws.column_dimensions['B'].width = 35
for sname, err_msg in plines.errors_missing_attributes.items():
ws.append([sname, err_msg])
def _create_bom_workbook(outpath, processed_data, bezeichner_cfg):
"""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"]
# Sheet 1: Globale BOM
ws1 = wb.active
ws1.title = "BOM"
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
# Sensoren auflisten
for artnr in sorted(sensor_counts.keys()):
ws1.append([artnr, sensor_counts[artnr], "", _get_sivas_name(artnr, bezeichner_cfg)])
# Kabel auflisten
all_cable_artnrs = sorted(set(stückware_counts.keys()) | set(meterware_lengths.keys()))
for artnr in all_cable_artnrs:
ws1.append([
artnr,
stückware_counts.get(artnr, ""),
meterware_lengths.get(artnr, ""),
_get_sivas_name(artnr, bezeichner_cfg)
])
# Sheet 2: BOM nach UV
ws2 = wb.create_sheet("BOM by UV")
ws2.append(["UV", "Art.-Number", "Amount (pcs)", "Length (m)", "Name (SIVAS)"])
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 = sorted(set(uv_sensor_counts.keys()) | set(uv_stückware_counts.keys()) | set(uv_meterware_lengths.keys()))
for uv in all_uvs:
all_artnrs_in_uv = sorted(
set(uv_sensor_counts.get(uv, {}).keys()) |
set(uv_stückware_counts.get(uv, {}).keys()) |
set(uv_meterware_lengths.get(uv, {}).keys())
)
for artnr in all_artnrs_in_uv:
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)
if sensor_amount > 0:
ws2.append([uv, artnr, sensor_amount, "", name])
if cable_count > 0:
ws2.append([uv, artnr, cable_count, "", name])
if cable_length > 0:
ws2.append([uv, artnr, "", cable_length, name])
bom_path = outpath.replace("_cables.xlsx", "_BOM.xlsx")
wb.save(bom_path)
print(f"BOM exported to Excel-file")
def check_file_in_work(work_dir, filename):
fexists = True
if not os.path.exists(filename):
mypath = os.path.join(work_dir, filename)
if not os.path.exists(mypath):
fexists = False
else:
mypath = filename
return (mypath, fexists)
def copy_layers_into_dxf_by_filter(dxf_source: ezdxf.document.Drawing, dxf_target:ezdxf.document.Drawing):
msp_source = dxf_source.modelspace()
msp_target = dxf_target.modelspace()
subdist_layers = set(config.options('GetPos-Layer_Distributors'))
rack_layers = set(config.options('GetPos-Layer_Racks'))
equipment_layers = set(config.options('GetPos-Layer_Equipment'))
tunnel_layers = set(config.options('GetPos-Layer_Tunnel'))
layernames = set()
layernames.update(subdist_layers)
layernames.update(rack_layers)
layernames.update(equipment_layers)
layernames.update(tunnel_layers)
# # welche Texte existieren
# for layername in layernames:
# selectstr = f'MTEXT[layer=="{layername}"]'
# for text in msp_source.query(selectstr):
# inhalt = text.dxf.text
# position = text.dxf.insert
# print(f"Text: '{inhalt}' an Position: {position} auf Layer: {layername}")
# text_entity = text.copy()
# msp_target.add_entity(text_entity)
layer_names_inside = dxf_source.layers
alle_block_defs = set(dxf_source.blocks.block_names())
verwendete = {insert.dxf.name for insert in msp_source.query("INSERT")}
# 1. Textstyles kopieren
for style in dxf_source.styles:
if style.dxf.name not in dxf_target.styles:
dxf_target.styles.new(name=style.dxf.name)
# 4. Filter-Layernamen bestimmen
for layername in layernames:
if layername not in dxf_source.layers:
continue
# Falls der Layer noch nicht im Zieldokument existiert, neu anlegen
if layername not in dxf_target.layers:
quelle_layer = dxf_source.layers.get(layername)
dxf_target.layers.add(
name=layername,
color=quelle_layer.color,
linetype=quelle_layer.dxf.linetype,
lineweight=quelle_layer.dxf.lineweight
)
# Alle Entities auf diesem Layer kopieren
entities = msp_source.query(f"*[layer=='{layername}']")
for entity in entities:
msp_target.add_entity(entity.copy())
def get_cable_artnr(section, length):
"""
Sucht in der angegebenen Config-Section die passende Kabel-Artikelnr. für die gegebene Länge.
Übergibt bei Treffer True, artnr
bei Fehler: False, errmsg
"""
# Existiert über BMK vergebene Kabel-Kennzeichnung in kabel.cfg?
if section not in cable_cfg:
return False, f"Keine Kabelkennzeichnung '{section}' in kabel.cfg"
entries = cable_cfg[section]
length_keys = sorted([float(k) for k in entries.keys()])
for l in length_keys:
if length <= l:
return True, entries[str(l)]
return False, f"Kabel länger als max. Kabellänge in Sektion '{section}'"
def map_sensor_to_cable_cfg(plines):
sens2cable = defaultdict(list)
mapping = config_BMK["Cable-Mapping"]
for pl in plines.kabel:
sensor_name = '-'.join(pl.id.split('-')[1:])
cable_length = round(pl.length/1000, 1)
sensor_artinr = pl.s_artinr
# SPS Prefex
name_prefix = sensor_name[:2]
# Suche nach Key in der BMK-Config
key_with_artnr = f"{name_prefix}-{sensor_artinr}" # Spezialfälle über "Key mit Artikelnummer" abgleichen
if key_with_artnr in mapping:
section_list = mapping[key_with_artnr]
elif name_prefix in mapping: # Standardzuweisung
section_list = mapping[name_prefix]
else:
sens2cable[pl.id].append("Kein Kabeltyp zugewiesen (BMK.cfg)")
section_list = list()
# TODO was soll hier passieren wenn die Config falsch ist?
# Liste aus evtl. mehreren Sektionen erzeugen
sections = [s.strip() for s in section_list.split(",")]
# Evtl. Kabelkürzung durchführen, falls Kabelschwanz vorhanden
if config_BMK.has_section("Length-Adjustments") and config_BMK.has_option("Length-Adjustments", name_prefix):
length_reduction = float(config_BMK.get("Length-Adjustments", name_prefix))
cable_length = max(0.0, cable_length-length_reduction)
# Kabel-Atikelnummer innerhalb der Sektion der kabel.cfg bestimmen
for section in sections:
sucess, result = get_cable_artnr(section, cable_length)
sens2cable[pl.id].append(result)
return sens2cable
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='draws a dxf file with the given cable coordinates', prog='drawdxf')
parser.add_argument('-f', '--filename', action='store', required=True, help='this json file contains all cables and its coordinates which should be drawn. Saved with an unique timestamp', metavar='myfile.json')
parser.add_argument('-d', '--dxf', action='store', help='this dxf drawing will be copied and the new layer with the cables will be added. Original file must be added with --origin', metavar='myfile.dxf')
parser.add_argument('-n', '--new', action='store', help='create a new dxf file only with cables in it. Name is basename and a timestamp')
parser.add_argument('-x', '--excel', action='store', help='create a xlsx file with cables data', metavar='allCables.xls')
parser.add_argument('-o', '--origin', action='store', help='name of original .dxf file used by -d and -a', metavar='original.dxf')
parser.add_argument('-l', '--local', action='store_true', help='using only local data for naming of article numbers. If not set: fetching names from SIVAS.')
args = parser.parse_args()
config_dir = os.environ.get("PROJECT_CFG")
work_dir = os.fspath(os.environ.get('PROJECT_WORK'))
json_file = args.filename
(json_path, jexists) = check_file_in_work(work_dir, json_file)
if not jexists:
print(f"file {json_file} does not exist")
parser.print_help()
exit()
plines = model_from_json(json_path)
# Allgemeine Config laden
config = configparser.ConfigParser(allow_no_value=True, delimiters=("="))
config.optionxform = lambda option: option # preserve case for letters
config.read(os.path.join(config_dir, "allgemein.cfg"))
# Config für Kabel-Artikelnummern laden
cable_cfg = configparser.ConfigParser()
cable_cfg.optionxform = str #Keys case-sensitive
with open(os.path.join(config_dir, "kabel.cfg"), encoding="utf-8") as f:
cable_cfg.read_file(f)
# Betriebsmittelkennzeichnungs-Config laden
config_BMK = configparser.ConfigParser(allow_no_value=True, delimiters=("="))
config_BMK.optionxform = lambda option: option # preserve case for letters
config_BMK.read(os.path.join(config_dir, "BMK.cfg"))
bezeichner_cfg = configparser.ConfigParser()
bezeichner_cfg.optionxform = str #Keys case-sensitive
with open(os.path.join(config_dir, "bezeichner.cfg"), encoding="utf-8") as f:
bezeichner_cfg.read_file(f)
dxf_file = args.dxf
if args.dxf:
(dxf_path, dexists) = check_file_in_work(work_dir, dxf_file)
if not dexists:
print(f"file {dxf_file} does not exist")
parser.print_help()
exit()
out_path = dxf_path
res_pos = new_dxf(plines, dxf_path)
if args.new:
# erzeuge dxf Datei nur mit Kabeln
out_path = os.path.join(work_dir, args.new)
res_pos = new_dxf(plines, out_path)
if args.excel:
# 1. Kabelzuordnung
sens2cable = map_sensor_to_cable_cfg(plines)
# 2. verwendete Artikelnummer (Sensoren + Kabel)
all_artnrs = get_all_artnrs(plines, sens2cable)
# 3. Fehlende Bezeichner in bezeichner.cfg markieren
mark_missings(all_artnrs)
# 4. Bezeichner ggf. aus SIVAS abrufen
if args.local == False:
config_path=os.path.join(config_dir, "bezeichner.cfg")
uc.update_bezeichner(bezeichner_cfg, config_path)
# 5. Excel schreiben
excel_path = os.path.join(work_dir, args.excel)
write_excel_from_json(plines, sens2cable, excel_path)