diff --git a/.vscode/launch.json b/.vscode/launch.json index 830b142..be17882 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -55,6 +55,22 @@ "ST-Fortna_positions.json" ] }, + { + "name": "getpositions with ST-Fortna_ASCII.dxf", + "type": "debugpy", + "request": "launch", + "program": "${file}", + "console": "integratedTerminal", + "args": [ + "--filename", + "ST-Fortna_ASCII.dxf", + "--sensors", + "--rack", + "--console", + "--write", + "ST-Fortna_ASCII_positions.json" + ] + }, { "name": "getpositions with HundM.dxf", "type": "debugpy", @@ -116,17 +132,17 @@ "easy.dxf" ] }, - { - "name": "draw cable dxf from easy.json to new layer", + { + "name": "draw cables from ST-Fortna_todraw", "type": "debugpy", "request": "launch", "program": "${file}", "console": "integratedTerminal", "args": [ - "--json", - "easy.json", - "--dxf", - "easy.dxf" + "--filename", + "ST-Fortna_ASCII_todraw.json", + "-n", + "ST-Fortna_ASCII_cables.dxf" ] }, { diff --git a/doc/img/Icons/Icon_GPT.ico b/doc/img/Icons/Icon_GPT.ico index 84219dc..b8d750b 100644 Binary files a/doc/img/Icons/Icon_GPT.ico and b/doc/img/Icons/Icon_GPT.ico differ diff --git a/doc/img/Icons/Icon_GPT.png b/doc/img/Icons/Icon_GPT.png index 7fb15ee..5d4a3b5 100644 Binary files a/doc/img/Icons/Icon_GPT.png and b/doc/img/Icons/Icon_GPT.png differ diff --git a/lib/drawdxf.py b/lib/drawdxf.py index 41839c0..7d11b9f 100644 --- a/lib/drawdxf.py +++ b/lib/drawdxf.py @@ -204,7 +204,7 @@ def draw_subdists(plines, doc): continue subdist_positions.add(pos) - subdist_name = pl.id.split('_')[0] + subdist_name = pl.id.split('-')[0] pt2 = pl.coords[1] dx = pt2.x - pt1.x @@ -216,7 +216,7 @@ def draw_subdists(plines, doc): if abs(dx) > abs(dy): # Horizontal offsety = -80 # Wert -80 über Try-and-Error zur Mitte der Beschriftung angepasst - if dx > 0: + if dx < 0: halign = 0 # LEFT offsetx = 50 # Wert 50 durch Try-and-Error sodass etwas Abstand zu Kabelpritsche else: diff --git a/lib/getpositions.py b/lib/getpositions.py index e3450e4..8c0fac6 100644 --- a/lib/getpositions.py +++ b/lib/getpositions.py @@ -8,14 +8,17 @@ import json import re from shapely import Point from itertools import combinations +from ezdxf.addons import iterdxf +import re +import time """ Dieses Programm: - liest die dxf Datei und holt sich von den Layern der dxf Datei die Positionen -+ der Motoren, Sensoren und Aktoren -+ der Unterverteiler -+ der Polylinien der Kabelpritschen + + der Motoren, Sensoren und Aktoren + + der Unterverteiler + + der Polylinien der Kabelpritschen - erzeugt daraus eine .json Datei im Work Ordner """ @@ -49,6 +52,61 @@ def get_input_positions(msp: ezdxf.document.Drawing.modelspace): continue # Überspringe Blöcke ohne Attribute id = "" ld = dict() + for attrib in insert.attribs: + attr_tag = attrib.dxf.tag + attr_text = attrib.dxf.text + if len(insert.attribs) == 0: + continue # Überspringe Blöcke ohne Attribute + #print(f"Attribut Name: {attrib.dxf.tag}, Wert: {attrib.dxf.text}") + ld[attr_tag] = attr_text + if attr_tag == "IO": + id = attr_text + #print(f"-- coord io {id}--: {attrib.dxf.insert}") # position des Blocks + pos = attrib.dxf.insert #Position aufzeichnen und bei Bedarf später mit REAL_POS überschreiben + ld["pos"] = (round(pos.x, 1), round(pos.y, 1)) + + if attr_tag == "B": + for spec in SpecialKeys: + if spec in attr_text: + id = attr_text + #print(f"-- coord {attrib.dxf.text} --: {attrib.dxf.insert}") + if attr_tag == "REALE_POSITION" and attr_text == "x": + #print(f"-- coord real --: {attrib.dxf.insert}") + pos = attrib.dxf.insert #Position Ecke unten links von "x"-Marker auslesen + + # Hoehe und Breite von "x" addieren, um Mittelpunkt zu finden + breite_marker = config.getfloat("GetPos-Geom-Sensor", "Breite") + hoehe_marker = config.getfloat("GetPos-Geom-Sensor", "Hoehe") + midx = pos[0] + breite_marker * 0.5 + midy = pos[1] + hoehe_marker * 0.5 + ld["pos"] = (round(midx, 1), round(midy, 1)) + + + # Nur wenn eine ID vorhanden ist, und eine gültige Position existiert + if id and "pos" in ld and isinstance(ld["pos"], tuple) and len(ld["pos"]) == 2: + if id in allIds: + allIds[id] = merge_two_dicts(allIds[id], ld) #Kombiniert alle infos aus dxf und "pos" + else: + allIds[id] = ld + return allIds + +def get_input_positions_iter(dxf_path) -> dict: + """ + Iterative Version für große DXF-Dateien mit iterdxf (kein Context Manager!). + """ + + SpecialKeys = ["MB", "MA", "BG", "FC"] + allIds = dict() + + + for insert in iterdxf.modelspace(dxf_path): + if insert.dxftype() != 'INSERT': + continue + + attribs = {att.dxf.tag: att.dxf.text for att in insert.attribs or []} + id = "" + ld = dict() + for attrib in insert.attribs: attr_tag = attrib.dxf.tag attr_text = attrib.dxf.text @@ -85,6 +143,7 @@ def get_input_positions(msp: ezdxf.document.Drawing.modelspace): allIds[id] = merge_two_dicts(allIds[id], ld) #Kombiniert alle infos aus dxf und "pos" else: allIds[id] = ld + return allIds def create_mappings(positions:dict) -> dict: @@ -128,20 +187,6 @@ def create_mappings(positions:dict) -> dict: def get_subdistributor_positions(msp, dist2sensors): """hole alle Positionen der Unterverteiler !!UV-Positionen bereits "Mitte-Mitte"!! """ - # # Über alle Texte laufen - # for text in msp.query('MTEXT'): - # print(f"Inhalt: {text.dxf.text}") - # print(f"Layer: {text.dxf.layer}") - # print(f"Einfügepunkt: {text.dxf.insert}") - # print(f"Breite: {text.dxf.width}") - # # print("Farbe:", text.dxf.color) # Achtung: 256 = "ByLayer" - # # print("Höhe (height):", text.dxf.height) - # # print("Rotation (degrees):", text.dxf.rotation) - # # print("Breitenfaktor (width factor):", text.dxf.width) - # # print("Style (Schriftart):", text.dxf.style) - # # print("Handle:", text.dxf.handle) - # print("---") - ret = dict() # Alle Texte auf Layer "xy" all_distributors = dist2sensors.keys() @@ -161,6 +206,28 @@ def get_subdistributor_positions(msp, dist2sensors): # print("---") return ret +def get_subdistributor_positions_iter(dxf_path, dist2sensors): + """Hole alle Positionen der Unterverteiler aus MTEXT-Objekten mithilfe von iterdxf.""" + ret = {} + all_distributors = dist2sensors.keys() + all_layers = config.items('GetPos-Layer_Distributors') + + for entity in iterdxf.modelspace(dxf_path): + if entity.dxftype() != "MTEXT": + continue + entity_text = entity.dxf.text + entity_layer = entity.dxf.layer + insert_point = entity.dxf.insert + + for (layer_name, _) in all_layers: + if entity_layer != layer_name: + continue + for distname in all_distributors: + if f"-{distname}" in entity_text: + ret[distname] = (round(insert_point[0], 1), round(insert_point[1], 1)) + + return ret + def get_tunnel_positions(msp): """hole alle Positionen aller Tunnel Ein und Ausgänge """ @@ -171,11 +238,6 @@ def get_tunnel_positions(msp): for (layer,v) in all_layers: selectstr = f'MTEXT[layer=="{layer}"]' for text in msp.query(selectstr): - #print(f"Text auf Layer 'Busverteiler-Kennzeichnung': {text.dxf.text}") - # match = re.search("Tunnel-", text.dxf.text) - # if match: - # if tunnelname in allTunnels: - # pos = (round(text.dxf.insert[0],1), round(text.dxf.insert[1],1)) #nur x und y Koordinate in Json schreiben txt = text.dxf.text pattern = r"(TUNNEL\d+)-(\d+)" match = re.search(pattern, txt) @@ -192,6 +254,40 @@ def get_tunnel_positions(msp): allTunnels['length'] = tunnel_length return allTunnels +def get_tunnel_positions_iter(dxf_path): + """Hole alle Positionen aller Tunnel Ein- und Ausgänge mithilfe von iterdxf.""" + allTunnels = dict() + tunnel_length = dict() + + all_layers = config.items('GetPos-Layer_Tunnel') + + for entity in iterdxf.modelspace(dxf_path): + if entity.dxftype() != "MTEXT": + continue + + txt = entity.dxf.text + layer = entity.dxf.layer + insert = entity.dxf.insert + + for (layer_name, _) in all_layers: + if layer != layer_name: + continue + + pattern = r"(TUNNEL\d+)-(\d+)" + match = re.search(pattern, txt) + if match: + tunnelname = match.group(1) + laenge = match.group(2) + pos = (round(insert[0], 1), round(insert[1], 1)) + + if tunnelname not in allTunnels: + allTunnels[tunnelname] = [] + allTunnels[tunnelname].append(pos) + tunnel_length[tunnelname] = laenge + + allTunnels['length'] = tunnel_length + return allTunnels + # helper function def print_line(e): print("LINE on layer: %s\n" % e.dxf.layer) @@ -222,12 +318,34 @@ def get_rack_positions(msp): p = [round(x,1), round(y,1)] ret[rack_key].append(p) rack_counter +=1 - # iterate over all entities in modelspace - # for e in msp: - # if e.dxftype() == "LINE": - # print_line(e) - # if e.dxftype() == "LWPOLYLINE": - # print_polyline(e) + return ret + +def get_rack_positions_iter(dxf_path): + """Hole alle Positionen aller Kabelpritschen (Racks) mithilfe von iterdxf.""" + ret = dict() + rack_counter = 1 # Zähler für Rack-Nummerierung + + all_layers = config.items('GetPos-Layer_Racks') + + for entity in iterdxf.modelspace(dxf_path): + if entity.dxftype() != "LWPOLYLINE": + continue + + layer = entity.dxf.layer + + if not any(layer == cfg_layer for cfg_layer, _ in all_layers): + continue + + rack_key = f"Rack_{rack_counter}" + ret[rack_key] = [] + + # Verwende entity.vertices() statt get_points() + for point in entity.vertices(): # (x, y, start_width, end_width, bulge) + x, y, *_ = point # wir interessieren uns nur für x und y + ret[rack_key].append([round(x, 1), round(y, 1)]) + + rack_counter += 1 + return ret def scan(dxf_source:ezdxf.document.Drawing): @@ -282,6 +400,10 @@ def check_existance(res_mappings, res_dist, res_pos): return ret +def dxf_is_binary(dxf_path): + with open(dxf_path, 'rb') as f: + header = f.read(22) + return b'AutoCAD Binary DXF' in header if __name__ == '__main__': @@ -293,7 +415,6 @@ if __name__ == '__main__': parser.add_argument('-c', '--console', action='store_true', help='print results to output') parser.add_argument('-n', '--scan', action='store_true', help='print all layer of racs, distributes and equiment not empty') - args = parser.parse_args() out_dir = os.environ.get('PROJECT_DATA') @@ -303,19 +424,22 @@ if __name__ == '__main__': filename = args.filename (dxf_path, dexists) = check_file_in_work(work_dir, filename) + zeitanfang = time.time() + + if dxf_is_binary(dxf_path): # Wenn dxf eine binary ist, dann komplett parsen und modelspace anlegen + doc = get_dxf_file(dxf_path) + msp = doc.modelspace() + use_iter = False + else: + use_iter = True - doc = get_dxf_file(dxf_path) # type: ignore - msp = doc.modelspace() - - if args.scan: - res = scan(doc) - print(to_json(res)) - sys.exit() + res_pos = dict() res_dist = dict() res_rac = dict() res_mappings = dict() + if args.sensors or args.dists or args.rack: config = configparser.ConfigParser(allow_no_value=True, delimiters=("=")) @@ -324,37 +448,66 @@ if __name__ == '__main__': config.read(os.path.join(config_dir, "allgemein.cfg")) output_results = dict() + if args.sensors: - res_pos = get_input_positions(msp) + # Sensoren auslesen + if use_iter: + res_pos = get_input_positions_iter(dxf_path) + else: + res_pos = get_input_positions(msp) + output_results['sensors'] = res_pos + if args.console: print(to_json(res_pos)) + # Mapping zu Sensoren auslesen (res_mappings, warnings) = create_mappings(res_pos) output_results['mappings'] = res_mappings if args.console: print(to_json(res_mappings)) - res_dist = get_subdistributor_positions(msp, res_mappings) + # Distributoren auslesen + if use_iter: + res_dist = get_subdistributor_positions_iter(dxf_path, res_mappings) + else: + res_dist = get_subdistributor_positions(msp, res_mappings) + output_results['distributors'] = res_dist + if args.console: print(to_json(res_dist)) - res_tunnel = get_tunnel_positions(msp) + # Tunnel auslesen + if use_iter: + res_tunnel = get_tunnel_positions_iter(dxf_path) + else: + res_tunnel = get_tunnel_positions(msp) + output_results['tunnels'] = res_tunnel + if args.console: print(to_json(res_tunnel)) if args.rack: - res_rac = get_rack_positions(msp) + if use_iter: + res_rac = get_rack_positions_iter(dxf_path) + else: + res_rac = get_rack_positions(msp) + output_results['racks'] = res_rac + if args.console: print(to_json(res_rac)) + + if args.write: basename = os.path.splitext(args.write)[0] res_not_found = check_existance(res_mappings, res_dist, res_pos) output_results["not_found"] = res_not_found write_results(to_json(output_results), work_dir, f"{basename}.json") + zeitende = time.time() + print(zeitende-zeitanfang) else: parser.print_help()