# Fuzzy category matching part_info = {'category': 'Capacitors', 'subcategory': 'Super'} categories = tuple(inventree_interface.get_categories(part_info)) if not (categories[0] and categories[1]): method_results = False # Digi-Key search with missing part number search = inventree_interface.digikey_search('') if search: method_results = False # Load KiCad library paths config_interface.load_library_path(settings.CONFIG_KICAD, silent=True) symbol_libraries_paths = config_interface.load_libraries_paths( settings.CONFIG_KICAD_CATEGORY_MAP, symbol_libraries_test_path) footprint_libraries_paths = config_interface.load_footprint_paths( settings.CONFIG_KICAD_CATEGORY_MAP, footprint_libraries_test_path) if not (symbol_libraries_paths and footprint_libraries_paths): method_results = False # Add symbol library to user file add_symbol_lib = config_interface.add_library_path( user_config_path=settings.CONFIG_KICAD_CATEGORY_MAP, category='category_test', symbol_library='symbol_library_test') if not add_symbol_lib: method_results = False # Add footprint library to user file
def user_defined_symbol_template_footprint(categories: list, part_number: str, symbol_lib=None, template=None, footprint_lib=None, symbol_confirm=False, footprint_confirm=False): ''' Symbol and Footprint user defined window ''' symbol = None footprint = None if symbol_confirm and '---' not in symbol_lib: if not config_interface.add_library_path(user_config_path=settings.CONFIG_KICAD_CATEGORY_MAP, category=categories[0], symbol_library=symbol_lib): cprint(f'[INFO]\tWarning: Failed to add symbol library to {categories[0]} category', silent=settings.SILENT) if footprint_confirm and '---' not in footprint_lib: if not config_interface.add_footprint_library(user_config_path=settings.CONFIG_KICAD_CATEGORY_MAP, category=categories[0], library_folder=footprint_lib): cprint(f'[INFO]\tWarning: Failed to add footprint library to {categories[0]} category', silent=settings.SILENT) # Load user settings settings.load_kicad_settings() def fuzzy_default(value:str, choices:list) -> str: match = None LIMIT = 85 for item in choices: fuzzy_match = fuzz.partial_ratio(value, item) display_result = f'"{value}" ?= "{item}"'.ljust(50) cprint(f'{display_result} => {fuzzy_match}', silent=settings.HIDE_DEBUG) if fuzzy_match >= LIMIT: match = item break return match def build_choices(items: dict, category: str, subcategory=None) -> list: choices = [] try: for key, value in items[category].items(): if value: choices.append(key) except: if subcategory: error_message = f'Warning: No templates defined for "{category}"' cprint(f'[INFO]\t{error_message}', silent=settings.SILENT) # sg.popup_ok(error_message, title='No Templates', location=(500, 500)) if subcategory: # Load templates only for given category/subcategory pair return sorted(choices) # Separate libraries not officially assigned to category if choices: choices.append('-' * 10) more_choices = [] for cat in items.keys(): if cat != category and cat != 'uncategorized': for key in items[cat].keys(): more_choices.append(key) # Process uncategorized entries try: for item in items['uncategorized']: more_choices.append(item) except: # error_message = f'Warning: No libraries defined for "{category}"' # cprint(f'[INFO]\t{error_message}', silent=settings.SILENT) # sg.popup_ok(error_message, title='No Libraries', location=(500, 500)) pass try: choices.extend(sorted(more_choices)) except: pass return choices # Load symbol libraries if not settings.KICAD_SYMBOLS_PATH: sg.popup_ok(f'Error: KiCad symbol library folder path is not defined ("Settings > KiCad")', title='KiCad Symbol Library Folder', location=(500, 500)) return symbol, template, footprint symbol_library = config_interface.load_libraries_paths(user_config_path=settings.CONFIG_KICAD_CATEGORY_MAP, library_path=settings.KICAD_SYMBOLS_PATH) # cprint(symbol_library) if not symbol_library: sg.popup_ok(f'Error: Symbol library files were not found in {settings.KICAD_SYMBOLS_PATH}', title='KiCad Symbol Library Folder', location=(500, 500)) return symbol, template, footprint # Build symbol choices symbol_lib_choices = build_choices(symbol_library, categories[0]) if symbol_lib: symbol_lib_default = symbol_lib else: # Try fuzzy matching symbol_lib_default = fuzzy_default(categories[0], symbol_lib_choices) if not symbol_lib_default: symbol_lib_default = symbol_lib_choices[0] # Load templates if not settings.KICAD_TEMPLATES_PATH: sg.popup_ok(f'Error: KiCad template folder path is not defined ("Settings > KiCad")', title='KiCad Template Folder', location=(500, 500)) return symbol, template, footprint templates = config_interface.load_templates_paths(user_config_path=settings.CONFIG_KICAD_CATEGORY_MAP, template_path=settings.KICAD_TEMPLATES_PATH) # cprint(templates) if not templates: sg.popup_ok(f'Error: Template files were not found in {settings.KICAD_TEMPLATES_PATH}', title='KiCad Template Folder', location=(500, 500)) return symbol, template, footprint # Build template choices if not categories[0]: category = symbol_lib_choices[0] else: category = categories[0] if not categories[1]: subcategory = 'None' else: subcategory = categories[1] try: template_choices = build_choices(templates, category, subcategory) if template: template_default = template else: template_default = template_choices[0] except: pass if not template_choices: template_choices = ['None'] template_default = template_choices[0] # Load footprint libraries if not settings.KICAD_FOOTPRINTS_PATH: sg.popup_ok(f'Error: KiCad footprint library folder path is not defined ("Settings > KiCad")', title='KiCad Footprint Library Folder', location=(500, 500)) return symbol, template, footprint footprint_library = config_interface.load_footprint_paths(user_config_path=settings.CONFIG_KICAD_CATEGORY_MAP, footprint_path=settings.KICAD_FOOTPRINTS_PATH) # cprint(f'{footprint_library=}') if not footprint_library: sg.popup_ok(f'Error: Footprint library files were not found in {settings.KICAD_FOOTPRINTS_PATH}', title='KiCad Footprint Library Folder', location=(500, 500)) return symbol, template, footprint # Build symbol choices footprint_lib_choices = build_choices(footprint_library, categories[0]) # Footprint mod list footprint_mod_choices = [] if footprint_lib: footprint_lib_default = footprint_lib try: footprint_lib_path = footprint_library[categories[0]][footprint_lib] except: pass try: footprint_mod_choices = [ item.replace('.kicad_mod','') \ for item in sorted(os.listdir(footprint_lib_path)) \ if os.path.isfile(os.path.join(footprint_lib_path, item)) ] except: cprint(f'[INFO]\tWarning: Failed fetching footprint mod files for {footprint_lib}', silent=settings.SILENT) # cprint(f'{footprint_lib=}\t{categories[0]}', silent=settings.HIDE_DEBUG) cprint(footprint_library, silent=settings.HIDE_DEBUG) else: # Try fuzzy matching footprint_lib_default = fuzzy_default(categories[0], footprint_lib_choices) if not footprint_lib_default: footprint_lib_default = footprint_lib_choices[0] try: footprint_lib_path = footprint_library[categories[0]][footprint_lib_default] footprint_mod_choices = [ item.replace('.kicad_mod','') \ for item in sorted(os.listdir(footprint_lib_path)) \ if os.path.isfile(os.path.join(footprint_lib_path, item)) ] except: cprint(f'[INFO]\tWarning: Failed fetching footprint mod files for {footprint_lib_default}', silent=settings.SILENT) # cprint(f'{footprint_lib_default=}\t{categories[0]}', silent=settings.HIDE_DEBUG) cprint(footprint_library, silent=settings.HIDE_DEBUG) if not footprint_mod_choices: footprint_mod_choices = ['None'] footprint_mod_default = footprint_mod_choices[0] else: footprint_mod_default = None library_layout = [ [ sg.Text('Select Symbol Library:'), sg.Combo(symbol_lib_choices, default_value=symbol_lib_default, key='symbol_lib'), sg.Button('Confirm'), ], [ sg.Text(f'Select Symbol Template ({categories[0]}):'), sg.Combo(template_choices, default_value=template_default, key='template'), ], [ sg.Text('Select Footprint Library:'), sg.Combo(footprint_lib_choices, default_value=footprint_lib_default, key='footprint_lib'), sg.Button('Confirm'), ], [ sg.Text('Select Footprint:'), sg.Combo(footprint_mod_choices, default_value=footprint_mod_default, key='footprint_mod_sel'), sg.Text('Or Enter Name:'), sg.In(size=(20,1),key='footprint_mod_man'), ], [ sg.Text('') ], ] if part_number: library_layout.append([ sg.Button('Check SnapEDA'), sg.Button('Submit') ]) else: library_layout.append([ sg.Button('Submit') ]) library_window = sg.Window('KiCad Libraries', library_layout, location=(500, 500)) lib_event, lib_values = library_window.read() library_window.close() if lib_event == sg.WIN_CLOSED: return symbol, template, footprint elif lib_event == 'Check SnapEDA': # SnapEDA window snapeda_window(part_number) return user_defined_symbol_template_footprint(categories=categories, part_number=part_number, symbol_lib=lib_values['symbol_lib'], template=lib_values['template'], footprint_lib=lib_values['footprint_lib']) elif lib_event == 'Confirm': return user_defined_symbol_template_footprint(categories=categories, part_number=part_number, symbol_lib=lib_values['symbol_lib'], template=lib_values['template'], footprint_lib=lib_values['footprint_lib'], symbol_confirm=True) elif lib_event == 'Confirm0': return user_defined_symbol_template_footprint(categories=categories, part_number=part_number, symbol_lib=lib_values['symbol_lib'], template=lib_values['template'], footprint_lib=lib_values['footprint_lib'], footprint_confirm=True) else: symbol = lib_values['symbol_lib'] template = lib_values['template'] if lib_values['footprint_mod_man']: footprint = lib_values['footprint_lib'] + ':' + lib_values['footprint_mod_man'] elif lib_values['footprint_mod_sel'] and lib_values['footprint_mod_sel'] != 'None': footprint = lib_values['footprint_lib'] + ':' + lib_values['footprint_mod_sel'] if not footprint: footprint = lib_values['footprint_lib'] + ':' + settings.footprint_name_default # Save paths if not config_interface.add_library_path(user_config_path=settings.CONFIG_KICAD_CATEGORY_MAP, category=categories[0], symbol_library=lib_values['symbol_lib']): cprint(f'[INFO]\tWarning: Failed to add symbol library to {categories[0]} category', silent=settings.SILENT) if not config_interface.add_footprint_library(user_config_path=settings.CONFIG_KICAD_CATEGORY_MAP, category=categories[0], library_folder=lib_values['footprint_lib']): cprint(f'[INFO]\tWarning: Failed to add footprint library to {categories[0]} category', silent=settings.SILENT) return symbol, template, footprint
def main(): ''' Main GUI window ''' # Create user configuration files if not settings.create_user_config_files(): cprint('\n[ERROR]\tSome Ki-nTree configuration files seem to be missing') return CREATE_CUSTOM = False # Select PySimpleGUI theme # sg.theme_previewer() # Show all sg.theme('DarkTeal10') # Main Menu menu_def = [ ['Settings', [ 'Digi-Key', 'KiCad', 'InvenTree', ], ], [ 'More', [ # 'Synchronize', 'Custom Part', ], ], ] # Main Window layout = [ [sg.Menu(menu_def,)], [ sg.Text('Enter Part Number:'), sg.InputText(key='part_number'), ], [ sg.Checkbox('Add to KiCad', enable_events=True, default=settings.ENABLE_KICAD, key='enable_kicad'), sg.Checkbox('Add to InvenTree', enable_events=True, default=settings.ENABLE_INVENTREE, key='enable_inventree'), ], [ sg.Button('CREATE', size=(59,1)), ], ] # Create the Window window = sg.Window(f'Ki-nTree [{settings.version}]', layout, location=(500, 500) ) # Event Loop to process 'events' and get the 'values' of the inputs while True: if CREATE_CUSTOM: event = 'CREATE_CUSTOM' else: event, values = window.read() if event == sg.WIN_CLOSED: # if user closes window or clicks cancel break if event == 'Digi-Key': search_api_settings_window() elif event == 'InvenTree': inventree_settings_window() elif event == 'KiCad': kicad_settings_window() elif 'enable' in event: settings.set_inventree_enable_flag(values['enable_inventree'], save=True) settings.set_kicad_enable_flag(values['enable_kicad'], save=True) elif event == 'Custom Part': custom_part_info = add_custom_part(part_data={}) if custom_part_info: CREATE_CUSTOM = True else: # Adding part information to InvenTree categories = [None, None] symbol = None template = None footprint = None new_part = False part_pk = 0 part_info = {} part_data = {} progressbar = False if CREATE_CUSTOM: if custom_part_info['name'] and custom_part_info['description']: part_info = custom_part_info cprint('\n[MAIN]\tCustom Part', silent=settings.SILENT) else: if values['part_number']: # New part separation new_search = '-' * 20 cprint(f'\n{new_search}', silent=settings.SILENT) # Load KiCad settings settings.load_kicad_settings() # Load InvenTree settings settings.load_inventree_settings() # SnapEDA test # snapeda_window(values['part_number']) # Digi-Key Search part_info = inventree_interface.digikey_search(values['part_number']) if not part_info: # Missing Part Information if CREATE_CUSTOM: sg.popup_ok(f'Missing "Name" and "Description"', title='Incomplete Custom Part Data', location=(500, 500)) else: sg.popup_ok(f'Failed to fetch part information\n' 'Make sure:\n- Digi-Key API settings are correct ("Settings > Digi-Key")' '\n- Part number is valid', title='Digi-Key API Search', location=(500, 500)) else: if settings.ENABLE_INVENTREE: cprint('\n[MAIN]\tConnecting to Inventree server', silent=settings.SILENT) inventree_connect = inventree_interface.connect_to_server() if part_info and not inventree_connect: sg.popup_ok(f'Failed to access InvenTree server\nMake sure your username and password are correct', title='InvenTree Server Error', location=(500, 500)) # Reset part info part_info = {} # User Categories if part_info and (settings.ENABLE_INVENTREE or settings.ENABLE_KICAD): if settings.ENABLE_INVENTREE: cprint('\n[MAIN]\tCreating part in Inventree', silent=settings.SILENT) categories = inventree_interface.get_categories(part_info=part_info, supplier_only=False) # If categories do not exist: request user to fill in categories if not categories[0]: categories = user_defined_categories(extend=settings.ENABLE_INVENTREE) if categories[0]: cprint(f'[INFO]\tCategory: "{categories[0]}"', silent=settings.SILENT) if categories[1]: cprint(f'[INFO]\tSubcategory: "{categories[1]}"', silent=settings.SILENT) elif categories[0] and not categories[1]: categories = user_defined_categories(category=categories[0], extend=settings.ENABLE_INVENTREE) if categories[1]: cprint(f'[INFO]\tUpdated Category: "{categories[0]}"', silent=settings.SILENT) cprint(f'[INFO]\tSubcategory: "{categories[1]}"', silent=settings.SILENT) else: # Ask user to re-confirm categories (pre-filled) categories = user_defined_categories(category=categories[0], subcategory=categories[1], extend=settings.ENABLE_INVENTREE) cprint(f'[INFO]\tUser Category: "{categories[0]}"', silent=settings.SILENT) cprint(f'[INFO]\tUser Subcategory: "{categories[1]}"', silent=settings.SILENT) # User Part Info if not (categories[0] and categories[1]): part_info = {} else: if CREATE_CUSTOM: # Translate custom part data part_info = inventree_interface.translate_form_to_digikey(part_info=part_info, categories=categories, custom=True) else: # Add to supplier categories configuration file category_dict = { categories[0]: { categories[1]: part_info['subcategory'] } } if not config_interface.add_supplier_category(category_dict, settings.CONFIG_DIGIKEY_CATEGORIES): config_file = settings.CONFIG_DIGIKEY_CATEGORIES.split(os.sep)[-1] cprint(f'[INFO]\tWarning: Failed to add new supplier category to {config_file} file', silent=settings.SILENT) cprint(f'[DBUG]\tcategory_dict = {category_dict}', silent=settings.SILENT) # Confirm part data with user form_data = add_custom_part(inventree_interface.translate_digikey_to_inventree(part_info=part_info, categories=categories, skip_params=True)) if form_data: # Translate to part info format user_part_info = inventree_interface.translate_form_to_digikey(part_info=form_data, categories=categories, custom=False) # Merge original part_info with user_part_info part_info = {**part_info, **user_part_info} else: # User did not proceed part_info = {} # Set KiCad user libraries and symbol/footprint if part_info and settings.ENABLE_KICAD: # Request user to select symbol and footprint libraries symbol, template, footprint = user_defined_symbol_template_footprint(categories, values['part_number']) # cprint(f'{symbol=}\t{template=}\t{footprint=}', silent=settings.HIDE_DEBUG) if not symbol and not footprint: part_info = {} if part_info: # Create progress bar window progressbar = progress.create_progress_bar_window() # InvenTree if (symbol and footprint) or settings.ENABLE_INVENTREE: # Create part in InvenTree if settings.ENABLE_INVENTREE: new_part, part_pk, part_data = inventree_interface.inventree_create(part_info=part_info, categories=categories, kicad=settings.ENABLE_KICAD, symbol=symbol, footprint=footprint, show_progress=progressbar) if not part_data: cprint(f'[INFO]\tError: Could not add part to InvenTree', silent=settings.SILENT) else: if not categories[0]: pseudo_categories = [symbol, None] part_data = inventree_interface.translate_digikey_to_inventree(part_info=part_info, categories=pseudo_categories) else: part_data = inventree_interface.translate_digikey_to_inventree(part_info=part_info, categories=categories) part_data['parameters']['Symbol'] = symbol part_data['parameters']['Footprint'] = footprint if not part_data: cprint(f'[INFO]\tError: Could not format part data', silent=settings.SILENT) if part_data: if not settings.ENABLE_INVENTREE: # Replace IPN with part name if InvenTree is not used (no part number) if CREATE_CUSTOM: try: manufacturer = part_data['manufacturer'].keys()[0] part_data['IPN'] = part_data['manufacturer'][manufacturer][0] except: part_data['IPN'] = part_data['name'] else: part_data['IPN'] = values['part_number'] if part_data['datasheet']: part_data['inventree_url'] = part_data['datasheet'] kicad_success = False # KiCad if settings.ENABLE_KICAD: # Reload paths settings.load_kicad_settings() symbol_libraries_paths = config_interface.load_libraries_paths(settings.CONFIG_KICAD_CATEGORY_MAP, settings.KICAD_SYMBOLS_PATH) symbol_templates_paths = config_interface.load_templates_paths(settings.CONFIG_KICAD_CATEGORY_MAP, settings.KICAD_TEMPLATES_PATH) # Adding part symbol to KiCAD cprint(f'\n[MAIN]\tAdding part to KiCad', silent=settings.SILENT) if not symbol: kicad_error = 'Incorrect symbol choice' cprint(f'[INFO]\tError: {kicad_error}', silent=settings.SILENT) elif not template: kicad_error = 'Incorrect template choice' cprint(f'[INFO]\tError: {kicad_error}', silent=settings.SILENT) elif not footprint: kicad_error = 'Incorrect footprint choice' cprint(f'[INFO]\tError: {kicad_error}', silent=settings.SILENT) else: try: library_path = symbol_libraries_paths[categories[0]][symbol] except: library_path = symbol_libraries_paths[symbol][symbol] if template == 'None': cprint(f'[INFO]\tWarning: Missing template, using default', silent=settings.SILENT) template_path = settings.KICAD_TEMPLATES_PATH + 'default.lib' else: try: template_path = symbol_templates_paths[categories[0]][template] except: template_path = symbol_templates_paths[symbol][template] try: library_directory = os.path.dirname(library_path) except: library_directory = None cprint(f'[INFO]\tError: Failed to map library file', silent=settings.SILENT) if library_directory: if settings.AUTO_GENERATE_LIB: create_library(library_directory, symbol, settings.symbol_template_lib) try: kicad_success, kicad_new_part = kicad_interface.inventree_to_kicad(part_data=part_data, library_path=library_path, template_path=template_path, show_progress=progressbar) except: cprint(f'[INFO]\tError: Failed to add part to KiCad (incomplete InvenTree data)', silent=settings.SILENT) # Final result message result_message = '' # Result pop-up window if settings.ENABLE_INVENTREE: if not new_part: if part_pk: result_message = 'Part already in InvenTree database' else: result_message = 'Error while adding part to InvenTree (check output)' else: result_message = 'Part added to InvenTree database' if settings.ENABLE_KICAD and settings.ENABLE_INVENTREE: result_message += '\n' if settings.ENABLE_KICAD: if not kicad_success: result_message += 'Error while adding part in KiCad (check output)' try: result_message += f'\nINFO: {kicad_error}' except: pass else: if kicad_new_part: result_message += 'Part added to KiCad library' else: result_message += 'Part already in KiCad library' else: if settings.ENABLE_INVENTREE: if not categories[0] or categories[1]: result_message = 'Part categories were not set properly' if settings.ENABLE_INVENTREE or settings.ENABLE_KICAD: if not part_data: result_message = 'Part data not found - Check part number' if not part_pk: result_message = 'Unexpected error - Contact developper' # Update progress bar to complete and close window if progressbar: progress.update_progress_bar_window(progress.MAX_PROGRESS) progress.close_progress_bar_window() if symbol and result_message: sg.popup_ok(result_message, title='Results', location=(500, 500)) if part_data.get('inventree_url', None): # Auto-Open Browser Window cprint(f'\n[MAIN]\tOpening URL {part_data["inventree_url"]} in browser', silent=settings.SILENT) try: webbrowser.open(part_data['inventree_url'], new=2) except TypeError: cprint(f'[INFO]\tError: Failed to open URL', silent=settings.SILENT) # Reset create custom flag CREATE_CUSTOM = False window.close()
# Enable flag def set_kicad_enable_flag(value: bool, save=False): global ENABLE_KICAD ENABLE_KICAD = value if save: global CONFIG_KICAD kicad_user_settings = config_interface.load_inventree_user_settings( CONFIG_KICAD) kicad_user_settings['KICAD_ENABLE'] = value config_interface.dump_file(kicad_user_settings, CONFIG_KICAD) return # Library Paths if not ENABLE_TEST: symbol_libraries_paths = config_interface.load_libraries_paths( CONFIG_KICAD_CATEGORY_MAP, KICAD_SYMBOLS_PATH) # cprint(symbol_libraries_paths) # Template Paths symbol_templates_paths = config_interface.load_templates_paths( CONFIG_KICAD_CATEGORY_MAP, KICAD_TEMPLATES_PATH) # cprint(symbol_templates_paths) # Footprint Libraries footprint_libraries_paths = config_interface.load_footprint_paths( CONFIG_KICAD_CATEGORY_MAP, KICAD_FOOTPRINTS_PATH) # cprint(footprint_libraries_paths) footprint_name_default = 'TBD' AUTO_GENERATE_LIB = True symbol_template_lib = os.path.join(