Files
kabellaengen/lib/getpositions.py
T

361 lines
14 KiB
Python

import argparse
import configparser
import ezdxf.document
from ezdxf import readfile
import os
import sys
import json
import re
from shapely import Point
from itertools import combinations
"""
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
- erzeugt daraus eine .json Datei im Work Ordner
"""
def write_results(jsnResults, outdir, filename):
""" write results to a json file
"""
print("writing results to a json file ...")
outfile = os.path.join(outdir, filename)
with open(outfile, 'w', encoding='utf-8') as fh:
fh.write(jsnResults)
print("done")
def merge_two_dicts(x, y):
z = x.copy()
z.update(y)
return z
def get_input_positions(msp: ezdxf.document.Drawing.modelspace):
"""hole alle Positionen der Eingänge !!Sensor Positionen erst nach Offsett-Addition "Mitte-Mitte"!!
"""
allIds = dict()
SpecialKeys = ["MB", # Separator
"MA", # Motor
"BG", # Stausensor
"FC" # Motorschutzschalter
]
# Über alle Blockreferenzen (INSERT) im Modelspace laufen
for insert in msp.query('INSERT'):
if len(insert.attribs) == 0:
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")
pos_midx = pos.x + breite_marker*0.5
pos_midy = pos.y + hoehe_marker*0.5
ld["pos"] = (round(pos_midx, 1), round(pos_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 create_mappings(positions:dict) -> dict:
unterverteiler_pfad = ""
dnamen = dict()
# sammle die Sensoren mit ihren zugehörigen Unterverteilern
sensor2unterverteiler = dict()
warnings = list()
for sensorname,v in positions.items():
unterverteiler_pfad = v["KENNZEICHNUNG"]
#print(unterverteiler_pfad)
# PFad zur Karte splitten. Dieser hat z.B. den Inhalt "=AH01+UH02-KF1FDI7"
pattern = r"^=([A-Z]+\d+)([+\-])([A-Z]+\d+)([+\-])([A-Z0-9]+)$"
match = re.match(pattern, unterverteiler_pfad)
if match:
anlage = match.group(1)
verteiler = match.group(3)
karte = match.group(5)
# match.group(1) # AH01
# match.group(2) # +
# match.group(3) # UH02
# match.group(4) # -
# match.group(5) # KF1FDI7
else:
warnings.append(unterverteiler_pfad)
continue
if not verteiler in dnamen:
dnamen[verteiler] = True
sensor2unterverteiler[sensorname] = verteiler
# jetzt zu jedem Unterverteiler die zugehörigen Sensoren merken
uv2sensor = dict()
for sensorname,verteiler in sensor2unterverteiler.items():
if verteiler not in uv2sensor:
uv2sensor[verteiler] = list()
uv2sensor[verteiler].append(sensorname)
return (uv2sensor, warnings)
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()
all_layers = config.items('GetPos-Layer_Distributors')
for (layer,v) in all_layers:
for distname in all_distributors:
selectstr = f'MTEXT[layer=="{layer}"]'
for text in msp.query(selectstr):
#print(f"Text auf Layer 'Busverteiler-Kennzeichnung': {text.dxf.text}")
match = re.search("-"+distname, text.dxf.text)
if match:
ret[distname] = (round(text.dxf.insert[0],1), round(text.dxf.insert[1],1)) #nur x und y Koordinate in Json schreiben
# for mtext in msp.query("MTEXT"):
# print(f"Inhalt: {mtext.text}")
# print(f"Layer: {mtext.dxf.layer}")
# print(f"Einfügepunkt: {mtext.dxf.insert}")
# print("---")
return ret
def get_tunnel_positions(msp):
"""hole alle Positionen aller Tunnel Ein und Ausgänge
"""
allTunnels = dict()
tunnel_length = dict()
# Alle Text mit "Tunnel" als Inhalt auf Layer "xy"
all_layers = config.items('GetPos-Layer_Tunnel')
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)
if match:
pos = (round(text.dxf.insert[0],1), round(text.dxf.insert[1],1)) #nur x und y Koordinate in Json schreiben
tunnelname = match.group(1)
laenge = match.group(2)
tunnel_length[tunnelname] = laenge
if not tunnelname in allTunnels:
allTunnels[tunnelname] = list()
allTunnels[tunnelname].append(pos)
else:
allTunnels[tunnelname].append(pos)
allTunnels['length'] = tunnel_length
return allTunnels
# helper function
def print_line(e):
print("LINE on layer: %s\n" % e.dxf.layer)
print("points: %s\n" % repr(e.dxf))
def print_polyline(e):
print("POLYLINE on layer: %s\n" % e.dxf.layer)
#print("points: %s\n" % repr(e.dxf))
#print("y point: %s\n" % e.dxf.y)
for x, y, start_width, end_width, bulge in e.get_points(): # Gibt Tuple (x, y, start_width, end_width, bulge)
print(f" Punkt: ({x}, {y}), Startbreite: ({start_width}, Endbreite: {end_width})")
if e.is_closed:
print("Diese Polyline ist geschlossen.")
def get_rack_positions(msp):
"""hole alle Positionen aller Kabelpritschen und nummeriere Racks"""
ret = dict()
rack_counter = 1 #Zaehler für Rack Nummerierung
all_layers = list(config.items('GetPos-Layer_Racks'))
for (layer,v) in all_layers:
selectstr = f'LWPOLYLINE[layer=="{layer}"]'
for e in msp.query(selectstr):
#print_polyline(e)
rack_key = f"Rack_{rack_counter}"
ret[rack_key] = list()
for x, y, start_width, end_width, bulge in e.get_points(): # Gibt Tuple (x, y, start_width, end_width, bulge)
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 scan(dxf_source:ezdxf.document.Drawing):
layer_names_inside = dxf_source.layers.entries.keys()
alle_block_defs = set(dxf_source.blocks.block_names())
used_block_names = set(insert.dxf.name for insert in dxf_source.modelspace().query("INSERT"))
ret = dict()
ret['all_layers'] = layer_names_inside
ret['used_blocks'] = used_block_names
ret['all_blocks'] = alle_block_defs
return ret
def to_json(d, pretty: bool = True) -> str:
return json.dumps(d, indent=2 if pretty else None, ensure_ascii=False, default=str) #ensure_ascii false für darstellung von "ue"
def get_dxf_file(filepath):
"""hole das dxf file
"""
try:
print("reading file ..", end='')
doc = ezdxf.readfile(filepath)
print("done")
except IOError:
print(f"Not a DXF file or a generic I/O error.")
sys.exit(1)
except ezdxf.DXFStructureError:
print(f"Invalid or corrupted DXF file.")
sys.exit(2)
return doc
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 check_existance(res_mappings, res_dist, res_pos):
ret = dict()
ret["missing_distributors"] = list()
ret["missing_sensors"] = list()
for dname in res_mappings.keys():
if dname not in res_dist:
ret["missing_distributors"].append(dname)
for sname,lofsensors in res_mappings.items():
for s in lofsensors:
if s not in res_pos:
ret['missing_sensors'].append(s)
return ret
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='fetches the x/y positions from a dxf file', prog='getpositions')
parser.add_argument('-f', '--filename', action='store', required=True, default="ST_6300_Steuerungstestlayout1_neueBloecke.dwg", help='which file should be fetched', metavar='myfile.dxf')
parser.add_argument('-s', '--sensors', action='store_true', help='fetch all position of sensors, motors, actors and subdistributors')
parser.add_argument('-r', '--rack', action='store_true', help='fetch all positions of all cable racks')
parser.add_argument('-w', '--write', action='store', help='write results into a json file')
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')
work_dir = os.environ.get('PROJECT_WORK')
config_dir = os.environ.get("PROJECT_CFG")
filename = args.filename
(dxf_path, dexists) = check_file_in_work(work_dir, filename)
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=("="))
config.optionxform = lambda option: option # preserve case for letters
config.read(os.path.join(config_dir, "allgemein.cfg"))
output_results = dict()
if args.sensors:
res_pos = get_input_positions(msp)
output_results['sensors'] = res_pos
if args.console:
print(to_json(res_pos))
(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)
output_results['distributors'] = res_dist
if args.console:
print(to_json(res_dist))
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)
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")
else:
parser.print_help()