Files
CreoMigrate/lib/manage_configs.py
T

539 lines
21 KiB
Python

import os
import json
from configparser import ConfigParser, RawConfigParser
import pandas as pd
import update_database
"""
Diese Lib verwaltet alle Config dateien (als .cfg oder als .json) von CreoMigrate
"""
class JsonBasedConfig:
"""Klasse mit generellen Methoden für alle Config Objekte mit json files"""
def __init__(self, config_dir, config_file):
"""merke dir den Pfad zu den Configdateien"""
self._config_path = os.path.join(config_dir, config_file)
def load_json_from_file(self):
"""lade das json file vom gegeben Pfad in ein json Objekt"""
with open(self._config_path, 'r', encoding='utf-8') as fh:
content = fh.read()
return json.loads(content)
def from_json(self, json_str):
"""nimm den Json String zur Initialisierung des Objektes"""
return json.loads(json_str)
class CSVBasedConfig:
def __init__(self, config_dir, config_file):
"""merke dir den Pfad zu den Configdateien"""
self._config_path = os.path.join(config_dir, config_file)
def load_csv_from_file(self):
"""lade die csv aus dem angegebenen Dateipfad in ein Dataframe"""
return pd.read_csv(self._config_path, sep=";", dtype="object")
class SivasAssemblyData(JsonBasedConfig):
"""Auslesen der Assembly-Informationen zu einer angegebenen Baugruppennummer"""
def __init__(self, config_dir, config_file):
super().__init__(config_dir, config_file)
self.assembly_data = None
def _lazy_init_assembly_data(func):
"""Sorgt dafür, dass json-Datei der Baugruppe nur 1x geladen wird"""
def wrapper(self, *args, **kwargs):
if self.assembly_data is None:
self.assembly_data = self.load_json_from_file()
return func(self, *args, **kwargs)
return wrapper
@_lazy_init_assembly_data
def get_assembly_ids(self):
"""Gibt alle Teilenummern der Stückliste zurück (auch Teilenummern aus tieferen Baugruppenebenen)"""
assembly_ids = set()
for child_id, parent_properties in self.assembly_data.items():
assembly_ids.add(child_id)
for parent_property in parent_properties:
parent_id = parent_property["Parent"]
assembly_ids.add(parent_id)
return assembly_ids
@_lazy_init_assembly_data
def get_parent_children(self) -> dict:
"""Ordnet Stücklistenpositionen (Kinder) den Baugruppennummern (Eltern) zu. Die Kinder sind entsprechend ihrer Positionsnummern in den Stücklisten sortiert."""
unordered_parent_children = {}
for child_id, parent_properties in self.assembly_data.items():
for parent_property in parent_properties:
parent_id = parent_property["Parent"]
child_posnr_in_parent_bom = int(parent_property["Position"].lstrip(".").split("/")[0])
if parent_id not in unordered_parent_children:
unordered_parent_children[parent_id] = {}
unordered_parent_children[parent_id][child_posnr_in_parent_bom] = child_id
ordered_parent_children = {}
for parent, children in unordered_parent_children.items():
for pos_nr, child in sorted(children.items()):
if parent not in ordered_parent_children:
ordered_parent_children[parent] = []
ordered_parent_children[parent].append(child)
return ordered_parent_children
def get_longest_assembly_id(self) -> str:
"""Gibt die längste Teilenummer von allen Stücklistenpositionen zurück"""
return max(self.get_assembly_ids())
@_lazy_init_assembly_data
def get_max_bom_depth(self) -> int:
"""Gibt die tiefste Stücklistenebene zurück (0 = höchste Ebene)"""
bom_levels = []
for parents in self.assembly_data.values():
for parent in parents:
current_bom_level = parent["Position"].count(".")
bom_levels.append(current_bom_level)
return max(bom_levels)
@_lazy_init_assembly_data
def get_property_names(self) -> list:
"""Gibt alle Eigenschaftsnamen zurück, die in der Assembly-json-Datei enthalten sind"""
for parent_data in self.assembly_data.values():
return list(parent_data[0])
@_lazy_init_assembly_data
def get_property(self, property) -> dict:
"""Gibt eine Eigenschaft zu den Teilenummern der Stückliste zurück (z.B. Bezeichnung)"""
if property not in self.get_property_names():
raise ValueError("Angegebener Eigenschaftsname exisitert nicht!")
id_property_mapping = {}
for child, parent_data in self.assembly_data.items():
for properties in parent_data:
id_property_mapping[child] = properties[property]
return id_property_mapping
class RuleDesignerDatabaseConfig(CSVBasedConfig):
"""Auslesen der Angaben aus der RuleDesigner-Datenbank-CSV"""
def __init__(self, config_dir=".", config_file="RD_Teilenummern.csv"):
super().__init__(config_dir, config_file)
def get_ruledesigner_ids(self):
return tuple(self.load_csv_from_file()['Teilenummern'])
class MigrationStatus:
def __init__(self, file_dir, assembly_json):
self._file_dir = file_dir
self._assembly_json = assembly_json
self._uselocal = False
def use_local(self):
self._uselocal = True
def get_migration_status(self) -> dict:
"""Gibt zurück, ob die Teilenummern der Baugruppe bereits migriert wurden, oder nicht"""
out_dir = self._file_dir
if self._uselocal == False:
ok = update_database.get_sivas_dbase(self._assembly_json, out_dir)
if not ok:
raise Exception("no json file could be generated")
update_database.get_rd_dbase()
else:
if not os.path.exists(os.path.join(out_dir, self._assembly_json)):
raise Exception("no local file exists "+ self._assembly_json)
self.sivas_assembly_data = SivasAssemblyData(out_dir, self._assembly_json)
sivas_assembly_ids = self.sivas_assembly_data.get_assembly_ids()
self.ruledesigner_data = RuleDesignerDatabaseConfig(out_dir)
ruledesigner_ids = self.ruledesigner_data.get_ruledesigner_ids()
migration_status = {}
for sivas_assembly_id in sivas_assembly_ids:
migration_status[sivas_assembly_id] = True if sivas_assembly_id in ruledesigner_ids else False
return migration_status
def get_migrated_ids(self) -> list:
"""Gibt alle Teilenummern aus der Baugruppenstückliste zurück, die bereits in RuleDesigner migriert wurden"""
migrated_ids = [assembly_id for assembly_id, migration_status in self.get_migration_status().items() if migration_status is True]
return migrated_ids
def get_unmigrated_ids(self) -> list:
"""Gibt alle Teilenummern aus der Baugruppenstückliste zurück, deren Migration in RuleDesigner noch aussteht"""
unmigrated_ids = [assembly_id for assembly_id, migration_status in self.get_migration_status().items() if migration_status is False]
return unmigrated_ids
class TOSGroupsConfig(JsonBasedConfig):
"""Auslesen der Config Angaben zu den TOS Gruppen"""
def __init__(self, config_dir='.', config_file='TOS-Gruppen.json'):
self.tos_hierarchy = None
super().__init__(config_dir, config_file)
def get_children_keys(self, tos_hierarchy, currentId, result):
# wenn eine Referenz nicht weiter in der TOS Hierarchy vorkommt, dann nicht mit einsammeln
if currentId not in tos_hierarchy:
return None
childIds = tos_hierarchy[currentId]["reference-ids"].values()
level = tos_hierarchy[currentId]["TOS-level"]
if len(childIds) > 0:
for cId in childIds:
# prüfen, ob die Referenz sonst noch verwendet wird
if cId in tos_hierarchy:
result[cId] = level
for cId in childIds:
self.get_children_keys(tos_hierarchy, cId, result)
def get_Id2TosGroup(self):
"hole alle ids zur jeweiligen Gruppe"
# lazy. nur einmal laden, wenn noch nicht geschehen
if not self.tos_hierarchy:
self.tos_hierarchy = super().load_json_from_file()
tos_hierarchy = self.tos_hierarchy
num2refId = dict()
num2child = dict()
allkwds = self.get_all_keywords()
for kw in allkwds.keys():
refids = tos_hierarchy[kw]["reference-ids"]
for cId in refids.keys():
num2refId[cId] = refids[cId]
children = tos_hierarchy[kw]["children"]
for cId in children.keys():
num2child[cId] = children[cId]
return (num2refId, num2child)
def get_all_keywords(self):
"""hole alle TOS Keywords, welche in der config Datei definiert sind"""
# lazy. nur einmal laden, wenn noch nicht geschehen
if not self.tos_hierarchy:
self.tos_hierarchy = super().load_json_from_file()
# beginne mit root als ersten, vordefiniertem Keyword
rootid = self.tos_hierarchy["root"]
# und von dort aus folge dann allen Referenzen
result = dict()
# root als Nullte Ebene dazu
result[rootid] = 0
self.get_children_keys(self.tos_hierarchy, rootid, result)
return result
def get_keywords_of_level(self, allKeywords, levelnum):
"""hole alle TOS Keywords der gegebenen Hierarchiestufe "levelnum" """
ret = list()
for kw in allKeywords:
if allKeywords[kw] == levelnum:
ret.append(kw)
return ret
def from_json(self, json_str):
"""lade den Json String zur Initialisierung des Objektes"""
self.tos_hierarchy = super().from_json(json_str)
class MaterialTransConfig(JsonBasedConfig):
"""Auslesen der Config zu allen Materialangaben"""
def __init__(self, config_dir='.', config_file='Materialübersetzung.json'):
self._materials = None
super().__init__(config_dir, config_file)
def from_json(self, json_str):
"""lade den Json String zur Initialisierung des Objektes"""
self._materials = super().from_json(json_str)
def get_all_materials(self):
"""hole alle Materialien, welche in der config Datei definiert sind"""
if not self._materials:
self._materials = super().load_json_from_file()
ret = list()
for k in self._materials.keys():
ret.append(k)
return ret
def get_material_of_type(self, mat):
"""hole alle Materialien, welche zu diesem Werkstofftyp existieren"""
if not self._materials:
self.load_materials()
return self._materials[mat]
class IniBasedConfig:
"""Klasse mit generellen Methoden für alle Config Objekte mit .cfg/.ini files"""
def __init__(self, config_dir, config_file):
"""merke dir den Pfad zu den Configdateien"""
self.config_path = os.path.join(config_dir, config_file)
if not os.path.exists(self.config_path):
raise Exception("path does not exist")
self._isInitalized = False
self._config = ConfigParser(allow_no_value=True, delimiters=("="))
self._config.optionxform = lambda option: option # preserve case for letters
def from_cfg_str(self, cfg_str):
"""nimm den config String zur Initialisierung des Objektes"""
self._config.read_string(cfg_str)
self._isInitalized = True
def load_cfg_from_file(self):
"""lade das config file vom gegeben Pfad"""
self._config.read(self.config_path, encoding='utf-8')
self._isInitalized = True
def get_section_options(self, section_name):
if not self._isInitalized:
self.load_cfg_from_file()
return self._config.options(section_name)
def sections(self):
if not self._isInitalized:
self.load_cfg_from_file()
return self._config.sections()
def get(self, section_name, option):
if not self._isInitalized:
self.load_cfg_from_file()
return self._config.get(section_name, option)
def get_int(self, section_name, option):
if not self._isInitalized:
self.load_cfg_from_file()
return self._config.getint(section_name, option)
def get_dict(self, section_name):
if not self._isInitalized:
self.load_cfg_from_file()
return dict(self._config.items(section_name))
class AllgemeinConfig(IniBasedConfig):
"""Auslesen der Config für alle allgemeinen Angaben"""
def __init__(self, config_dir='.', config_file='Allgemein.cfg'):
super().__init__(config_dir, config_file)
def get_sections(self):
""" gibt alle Sektionen zurück wie
Kategorien, Dispogruppe, Dispolaufgruppe, EinheitAusw, ...
"""
return super().sections()
def get_options(self, section_name):
""" get all options below"""
return super().get_section_options(section_name)
class BezeichnerConfig(IniBasedConfig):
"""Auslesen der Config zu allen möglichen Bezeichner"""
def __init__(self, config_dir='.', config_file='Bezeichner.cfg'):
super().__init__(config_dir, config_file)
def get_all(self):
"""hole alle möglichen Bezeichner """
return super().get_section_options("Bezeichner")
class FarbenConfig(IniBasedConfig):
"""Auslesen der Config zu allen Farbensarten"""
def __init__(self, config_dir='.', config_file='Farben.cfg'):
super().__init__(config_dir, config_file)
def get_farbauswahl(self):
"""hole alle Farben die möglich sind
-> [RAL 1004 Goldgelb, RAL 2000 Gelborange, RAL 3002 Kaminrot, RAL 5010 Enzianblau]
"""
return super().get_section_options("Farbauswahl")
def get_farben_eloxieren(self):
"""hole alle Farben fürs eloxieren
-> [Rot, Grün, Blau, Gelb, Schwarz, Violett, Natur]
"""
return super().get_section_options("FarbeEloxieren")
class FertigungsartConfig(IniBasedConfig):
"""Auslesen der Config zu allen Fertigungsarten"""
def __init__(self, config_dir='.', config_file='Fertigungsarten.cfg'):
super().__init__(config_dir, config_file)
def get_fertigungsarten(self):
"""hole alle Fertigungsarten, welche als Sektionen in der config gegeben sind, also
Fertigungsart, Allgemeintoleranz3DDruck, AllgemeintoleranzGiessen,
AllgemeintoleranzExtrudieren, ...
"""
return super().sections()
def get_list_in_section(self, section):
"""hole alle möglichen Oberflächen Behandlungsarten zum gegebenen Material
Allgemeintoleranz3DDruck ftype
-> [ISO 2768:1991-fH, ISO 2768:1991-fK, ISO 2768:1991-fL, ISO 2768:1991-mH, , ...]
"""
return super().get_section_options(section)
class HerstellerConfig(IniBasedConfig):
"""Auslesen der Config zu allen möglichen Hersteller"""
def __init__(self, config_dir='.', config_file='Hersteller.cfg'):
super().__init__(config_dir, config_file)
def get_all(self):
"""hole alle möglichen Bezeichner """
return super().get_section_options("Hersteller")
class NormenConfig(IniBasedConfig):
"""Auslesen der Config zu allen Normen"""
def __init__(self, config_dir='.', config_file='Normen.cfg'):
super().__init__(config_dir, config_file)
def get_normen(self):
"""hole alle Normen, welche als Sektionen in der config gegeben sind, also
Oberflaechennorm, Kantennorm, Massnorm, Tolerierungsnorm,..
"""
return super().sections()
def get_normen_of_type(self, otype):
"""hole alle möglichen Oberflächen Behandlungsarten zum gegebenen Material
Oberfläche otype
Oberflaechennorm -> [ISO 1302:2002, ISO 21920]
Tolerierungsnorm, ...
"""
return super().get_section_options(otype)
class OberflächeConfig(IniBasedConfig):
"""Auslesen der Config zu allen Oberflächen"""
def __init__(self, config_dir='.', config_file='Oberflächenbehandlung.cfg'):
super().__init__(config_dir, config_file)
def get_oberflächen(self):
"""hole alle Oberflächen, welche als Sektionen in der config gegeben
sind, also OberflächenbehandlungStahl, OberflächenbehandlungAluminium, ... """
return super().sections()
def get_oberflächen_behandlungen(self):
"""hole alle möglichen Arten von Oberflächenbehandlungen"""
return super().get_section_options('Oberflaechenbehandlungen')
def get_oberfläche_of_type(self, otype):
"""hole alle möglichen Oberflächen Behandlungsarten zum gegebenen Material
Oberfläche otype
OberflächenbehandlungStahl --> [lackieren, pulverbeschichten, verzinken, vernickeln, brünieren, härten, bürsten, sandstrahlen]
OberflächenbehandlungAluminium
...
"""
return super().get_section_options(otype)
class WerkstoffeConfig(IniBasedConfig):
"""Auslesen der Config zu allen Werkstoffen"""
def __init__(self, config_dir='.', config_file='Werkstoff.cfg'):
super().__init__(config_dir, config_file)
def get_werkstoff_namen(self):
"""hole alle Werkstoffe, welche als Sektionen in der config gegeben sind, also z.B.
Stahl, Aluminium, Kunst-/ Verbundstoff, ...
"""
return super().get_section_options('Werkstofftyp')
def get_all(self):
ret = list()
l = self.get_werkstoff_namen()
for item in l:
ltemp = self.get_werkstoffe_of_name(l)
ret.append(ltemp)
return ret
def get_werkstoffe_of_name(self, wName):
"""hole alle Werkstoffe unter der gegebenen Sektion
Werkstofftyp wName
Stahl --> [ 1.0035 (S185), 1.0038 (S235JR), 1.0122 (S235JRC), 1.0122+C (S235JRC+C), 1.0330 (DC01), ... ]
Aluminium, ...
"""
return super().get_section_options(wName)
class PfadConfig(IniBasedConfig):
"""Auslesen der Config zu allen Pfaden"""
def __init__(self, config_dir='.', config_file='Zielpfade.cfg'):
super().__init__(config_dir, config_file)
class ZusatzConfig(IniBasedConfig):
"""Auslesen der Zusatzinformationen die aus dem Änderungsindex abgeleitet werden"""
def __init__(self, config_dir='.', config_file='Zusatzinformationen.cfg'):
super().__init__(config_dir, config_file)
class InvaldCharsConfig():
"""Auslesen der Zusatzinformationen die aus dem Änderungsindex abgeleitet werden"""
def __init__(self, config_dir='.', config_file='InvalidCharacters.txt'):
self.config_path = os.path.join(config_dir, config_file)
if not os.path.exists(self.config_path):
raise Exception("path does not exist")
self._isInitalized = False
file = open(self.config_path,'r')
line = file.read()
line = line.rstrip()
self._characters = line.split(',')
def get(self):
return self._characters
class GuiConfig(IniBasedConfig):
"""Auslesen der Zusatzinformationen die aus dem Änderungsindex abgeleitet werden"""
def __init__(self, config_dir='.', config_file='gui.cfg'):
super().__init__(config_dir, config_file)
class Configs:
"""Sammlung aller Configdateien """
def __init__(self, cfg_dir):
self._cfg_dir = cfg_dir
self.Allgemein = AllgemeinConfig(cfg_dir)
self.Bezeichner = BezeichnerConfig(cfg_dir)
self.Farben = FarbenConfig(cfg_dir)
self.Fertigung = FertigungsartConfig(cfg_dir)
self.Hersteller = HerstellerConfig(cfg_dir)
self.Normen = NormenConfig(cfg_dir)
self.Oberfläche = OberflächeConfig(cfg_dir)
self.Pfade = PfadConfig(cfg_dir)
self.Werkstoffe = WerkstoffeConfig(cfg_dir)
self.Zusatz = ZusatzConfig(cfg_dir)
self.InvalidChars = InvaldCharsConfig(cfg_dir)
self.Gui = GuiConfig(cfg_dir)
self.TOSGruppen = TOSGroupsConfig(cfg_dir)
self.Materialien = MaterialTransConfig(cfg_dir)
self._TeileNummern = None
self._blacklist = None
def get_cfg_path(self):
return self._cfg_dir
def set_data_dir(self, data_dir):
self._data_dir = data_dir
def get_data_dir(self):
return self._data_dir
def get_ruledesigner_ids(self):
if not self._data_dir:
raise Exception("set_data_dir not called. path not set")
if not self._TeileNummern:
self._TeileNummern = RuleDesignerDatabaseConfig(self._data_dir).get_ruledesigner_ids()
return self._TeileNummern
def get_blacklist(self):
if not self._data_dir:
raise Exception("set_data_dir not called. path not set")
if not self._blacklist:
with open(os.path.join(self._data_dir, 'blacklist.txt')) as f:
lines = f.read().splitlines()
self._blacklist = lines
return self._blacklist