def is_new_part(category_id: int, part_info: dict) -> int: ''' Check if part exists based on parameters (or description) ''' global inventree_api # Get category object part_category = PartCategory(inventree_api, category_id) # Fetch all parts from category and subcategories part_list = [] part_list.extend(part_category.getParts()) for subcategory in part_category.getChildCategories(): part_list.extend(subcategory.getParts()) # Extract parameter from part info new_part_parameters = part_info['parameters'] template_list = ParameterTemplate.list(inventree_api) def fetch_template_name(template_id): for item in template_list: if item.pk == template_id: return item.name # Retrieve parent category name for parameters compare try: category_name = part_category.getParentCategory().name except AttributeError: category_name = part_category.name filters = config_interface.load_category_parameters_filters( category=category_name, supplier_config_path=settings.CONFIG_PARAMETERS_FILTERS) # cprint(filters) for part in part_list: # Get part parameters db_part_parameters = part.getParameters() part_parameters = {} for parameter in db_part_parameters: parameter_name = fetch_template_name(parameter.template) parameter_value = parameter.data part_parameters[parameter_name] = parameter_value if new_part_parameters: # Compare database part with new part compare = part_tools.compare( new_part_parameters=new_part_parameters, db_part_parameters=part_parameters, include_filters=filters) else: # Compare with name and description compare = part_info['name'] == part.name or part_info[ 'description'] == part.description if compare: cprint(f'\n[TREE]\tFound part match in database (pk = {part.pk})', silent=settings.HIDE_DEBUG) return part.pk cprint(f'\n[TREE]\tNo match found in database', silent=settings.HIDE_DEBUG) return 0
def exec(c): cprint(f'[MAIN]\tBuilding Ki-nTree into "dist" directory') c.run( 'pyinstaller --clean --onefile ' '-p search/digikey_api/ -p kicad/ -p database/inventree-python/ ' 'kintree_gui.py', hide=True)
def package(c): import os import shutil cdir = os.getcwd() dist = os.path.join(cdir, 'dist') cprint('[MAIN]\tPackaging Ki-nTree') # Delete previous files try: c.run('rm dist/kintree.tgz', hide='err') except UnexpectedExit: pass try: c.run('rm dist/kintree.zip', hide='err') except UnexpectedExit: pass # Create ZIP shutil.make_archive(os.path.join(cdir, 'kintree'), 'zip', dist) # Create TGZ c.run(f'cd {dist} && tar -czvf kintree.tgz * && cd {cdir}', hide=True) # Move ZIP file into dist folder c.run(f'mv kintree.zip {dist}', hide=False)
def delete_component_from_lib(self, part_number, library_path): ''' Remove component (symbol) from KiCad library ''' # Check that Library file exists if not os.path.isfile(library_path): cprint(f'[KCAD]\tError loading library file ({library_path})', silent=settings.SILENT) cprint('[KCAD]\tCheck library file path and name', silent=settings.SILENT) return False schlib = SchLib(library_path) library_name = library_path.split(os.sep)[-1] cprint('[KCAD]\tNumber of parts in library ' + library_name + ': ' + str(schlib.getComponentCount()), silent=settings.SILENT) try: schlib.removeComponent(part_number) schlib.save() cprint( f'[KCAD]\tSuccess: Component {part_number} was removed from library', silent=settings.SILENT) return True except: cprint( f'[KCAD]\tError: Component {part_number} was not found in library {library_name} (no delete)', silent=settings.SILENT) return False
def find_supplier_category_match(supplier_category: str): # Check for match with Inventree categories category_match = None subcategory_match = None for inventree_category in category_map.keys(): fuzzy_match = fuzz.partial_ratio(supplier_category, inventree_category) display_result = f'"{supplier_category}" ?= "{inventree_category}"'.ljust( 50) cprint(f'{display_result} => {fuzzy_match}', silent=settings.HIDE_DEBUG) if fuzzy_match < settings.CATEGORY_MATCH_RATIO_LIMIT and \ category_map[inventree_category]: # Compare to subcategories for inventree_subcategory in category_map[ inventree_category]: fuzzy_match = fuzz.partial_ratio( supplier_category, inventree_subcategory) display_result = f'"{supplier_category}" ?= "{inventree_subcategory}"'.ljust( 50) cprint(f'{display_result} => {fuzzy_match}', silent=settings.HIDE_DEBUG) if fuzzy_match >= settings.CATEGORY_MATCH_RATIO_LIMIT: subcategory_match = inventree_subcategory break if fuzzy_match >= settings.CATEGORY_MATCH_RATIO_LIMIT: category_match = inventree_category break return category_match, subcategory_match
def package(c): cprint('[MAIN]\tPackaging Ki-nTree') try: c.run('rm dist/kintree.tgz', hide='err') except UnexpectedExit: pass c.run('cd dist/ && tar -czvf kintree.tgz * && cd -', hide=True)
def copy_configuration(c): cprint(f'[MAIN]\tCopying configuration files') c.run('mkdir dist/config && mkdir dist/kicad', hide=False) c.run('cp -r config/kicad/ dist/config/', hide=True) c.run('cp -r config/digikey/ dist/config/', hide=True) c.run('cp -r config/inventree/ dist/config/', hide=True) c.run('cp config/version.yaml dist/config/', hide=True) c.run('cp -r kicad/templates/ dist/kicad/', hide=True)
def kicad_settings_window(): ''' KiCad settings window ''' kicad_user_settings = config_interface.load_file(settings.CONFIG_KICAD) KICAD_SYMBOLS_PATH = kicad_user_settings['KICAD_SYMBOLS_PATH'] KICAD_TEMPLATES_PATH = kicad_user_settings['KICAD_TEMPLATES_PATH'] KICAD_FOOTPRINTS_PATH = kicad_user_settings['KICAD_FOOTPRINTS_PATH'] kicad_layout = [ [ sg.Text('Select Symbol Libraries Folder:'), sg.InputText(KICAD_SYMBOLS_PATH, key='library'), sg.FolderBrowse(target='library', initial_folder=KICAD_SYMBOLS_PATH), ], [ sg.Text('Select Symbol Templates Folder:'), sg.InputText(KICAD_TEMPLATES_PATH, key='template'), sg.FolderBrowse(target='template', initial_folder=KICAD_TEMPLATES_PATH), ], [ sg.Text('Select Footprint Libraries Folder:'), sg.InputText(KICAD_FOOTPRINTS_PATH, key='footprint'), sg.FolderBrowse(target='footprint', initial_folder=KICAD_FOOTPRINTS_PATH), ], [ sg.Button('Save', size=(10, 1)), ], ] kicad_window = sg.Window('KiCad Settings', kicad_layout, location=(500, 500)) kcd_event, kcd_values = kicad_window.read() if kcd_event == sg.WIN_CLOSED: # if user closes window pass else: new_settings = { 'KICAD_SYMBOLS_PATH': kcd_values['library'], 'KICAD_TEMPLATES_PATH': kcd_values['template'], 'KICAD_FOOTPRINTS_PATH': kcd_values['footprint'], } for name, path in new_settings.items(): if path == '': cprint(f'[INFO]\tWarning: KiCad {name} path is empty', silent=settings.SILENT) # Check if path has trailing slash elif path[-1] != os.sep: new_settings[name] = path + os.sep # Read user settings file kicad_user_settings = {**kicad_user_settings, **new_settings} # Write to user settings file config_interface.dump_file(kicad_user_settings, settings.CONFIG_KICAD) kicad_window.close() return
def is_component_in_library(self, library_instance, component_part_number): ''' Check if component already exists in library ''' for component in library_instance.components: cprint( f'[DBUG]\t{component.definition["name"]} ?= {component_part_number}', silent=settings.HIDE_DEBUG) if component.definition['name'] == component_part_number: cprint( f'[KCAD]\tWarning: Component {component_part_number} already in library', silent=settings.SILENT) return True return False
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 create_supplier_part(part_id: int, manufacturer_name: str, manufacturer_mpn: str, supplier_name: str, supplier_sku: str, description: str, link: str) -> bool: ''' Create InvenTree supplier part part_id: Part the supplier data is linked to manufacturer_name: Manufacturer the supplier data is linked to manufacturer_mpn: MPN the supplier data is linked to supplier: Company that supplies this SupplierPart object SKU: Stock keeping unit (supplier part number) manufacturer: Company that manufactures the SupplierPart (leave blank if it is the sample as the Supplier!) MPN: Manufacture part number link: Link to part detail page on supplier's website description: Descriptive notes field ''' global inventree_api # Get Supplier ID supplier_id = get_company_id(supplier_name) # Get Manufacturer ID manufacturer_id = get_company_id(manufacturer_name) if not manufacturer_id: # Unset MPN manufacturer_mpn = None if supplier_id: # Validate supplier link if not validators.url(link): link = '' supplier_part = SupplierPart.create( inventree_api, { 'part': part_id, 'manufacturer': manufacturer_id, 'MPN': manufacturer_mpn, 'supplier': supplier_id, 'SKU': supplier_sku, 'link': link, 'description': description, }) if supplier_part: return True else: cprint( f'[TREE]\tError: Supplier "{supplier_name}" not found (failed to create supplier part)', silent=settings.SILENT) return False
def digikey_search(part_number: str, test_mode=False) -> dict: ''' Wrapper for Digi-Key search, allow use of cached data (limited daily API calls) ''' part_info = {} # Check part number exist if not part_number: cprint(f'\n[MAIN]\tError: Missing Part Number', silent=settings.SILENT) return part_info # Load from file if cache is enabled search_filename = settings.search_results[ 'directory'] + part_number + settings.search_results['extension'] # Get cached data part_info = digikey_api.load_from_file(search_filename, test_mode) if part_info: cprint(f'\n[MAIN]\tUsing Digi-Key cached data for {part_number}', silent=settings.SILENT) else: cprint(f'\n[MAIN]\tDigi-Key search for {part_number}', silent=settings.SILENT) part_info = digikey_api.fetch_digikey_part_info(part_number) # Check Digi-Key data exist if not part_info: cprint(f'[INFO]\tError: Failed to fetch data for "{part_number}"', silent=settings.SILENT) # Save search results if part_info: digikey_api.save_to_file(part_info, search_filename) return part_info
def load_file(file_path: str, silent=True) -> dict: ''' Safe load YAML file ''' try: with open(file_path, 'r') as file: try: data = yaml.safe_load(file) except yaml.YAMLError as exc: print(exc) return None except FileNotFoundError: if not silent: cprint(f'[ERROR] File {file_path} does not exists!') return None return data
def clean(c): cprint('[MAIN]\tCleaning project directory') c.run('find . -name __pycache__ | xargs rm -r') try: c.run('rm .coverage', hide='err') except UnexpectedExit: pass try: c.run('rm .coverage.*', hide='err') except UnexpectedExit: pass try: c.run('rm -r dist build htmlcov', hide='err') except UnexpectedExit: pass
def copy_configuration(c): import os cdir = os.getcwd() dist = os.path.join(cdir, 'dist') config = os.path.join(dist, 'config') kicad = os.path.join(dist, 'kicad') cprint(f'[MAIN]\tCopying configuration files') c.run(f'mkdir {config} && mkdir {kicad}', hide=False) c.run('cp -r config/kicad/ dist/config/', hide=False) c.run('cp -r config/digikey/ dist/config/', hide=False) c.run('cp -r config/inventree/ dist/config/', hide=False) c.run('cp config/version.yaml dist/config/', hide=False) c.run('cp -r kicad/templates/ dist/kicad/', hide=False)
def is_new_manufacturer_part(manufacturer_name: str, manufacturer_mpn: str) -> bool: ''' Check if InvenTree manufacturer part exists to avoid duplicates ''' global inventree_api # Fetch all companies cprint(f'[TREE]\tFetching manufacturers', silent=settings.HIDE_DEBUG) company_list = Company.list(inventree_api, is_manufacturer=True, is_customer=False) companies = {} for company in company_list: companies[company.name] = company try: # Get all parts part_list = companies[manufacturer_name].getManufacturedParts() except: part_list = None if part_list == None: # Create cprint(f'[TREE]\tCreating new manufacturer "{manufacturer_name}"', silent=settings.SILENT) create_company( company_name=manufacturer_name, manufacturer=True, ) # Get all parts part_list = [] for item in part_list: try: if manufacturer_mpn in item.MPN: cprint(f'[TREE]\t{item.MPN} ?= {manufacturer_mpn} => True', silent=settings.HIDE_DEBUG) return False else: cprint(f'[TREE]\t{item.MPN} ?= {manufacturer_mpn} => False', silent=settings.HIDE_DEBUG) except TypeError: cprint( f'[TREE]\t{item.MPN} ?= {manufacturer_mpn} => *** SKIPPED ***', silent=settings.HIDE_DEBUG) return True
def test(c): try: c.run('pip show coverage', hide=True) except UnexpectedExit: c.run('pip install -U coverage', hide=True) cprint('[MAIN]\tRunning tests using coverage\n-----') c.run('cd InvenTree/InvenTree/ && python manage.py runserver', asynchronous=True) c.run('cd ../.. && sleep 5') setup_inventree = c.run('coverage run --parallel-mode setup_inventree.py') cprint('\n-----') if setup_inventree.exited == 0: run_tests = c.run('coverage run --parallel-mode run_tests.py') if run_tests.exited == 0: c.run('coverage combine') coverage_report(c, open_browser=False)
def create_category(parent: str, name: str): ''' Create InvenTree category, use parent for subcategories ''' global inventree_api parent_id = 0 is_new_category = False # Check if category already exists category_list = PartCategory.list(inventree_api) for category in category_list: if name == category.name: try: # Check if parents are the same if category.getParentCategory().name == parent: # Return category ID return category.pk, is_new_category except: return category.pk, is_new_category elif parent == category.name: # Get Parent ID parent_id = category.pk else: pass if parent: if parent_id > 0: category = PartCategory.create(inventree_api, { 'name': name, 'parent': parent_id, }) is_new_category = True else: cprint(f'[TREE]\tError: Check parent category name ({parent})', silent=settings.SILENT) return -1, is_new_category else: # No parent category = PartCategory.create(inventree_api, { 'name': name, 'parent': None, }) is_new_category = True return category.pk, is_new_category
def create_parameter(part_id: int, template_name: int, value: str): ''' Create InvenTree part parameter based on template ''' global inventree_api parameter_template_list = ParameterTemplate.list(inventree_api) template_id = 0 for item in parameter_template_list: if template_name == item.name: template_id = item.pk break # Check if template_id already exists for this part part = Part(inventree_api, part_id) part_parameters = part.getParameters() is_new_part_parameters_template_id = True for item in part_parameters: # cprint(f'[TREE]\t{parameter.template} ?= {template_id}', silent=SILENT) if item.template == template_id: is_new_part_parameters_template_id = False break # cprint(part_parameters, silent=SILENT) ''' Create parameter only if: - template exists - parameter does not exist for this part ''' parameter = None if template_id > 0 and is_new_part_parameters_template_id: parameter = Parameter.create(inventree_api, { 'part': part_id, 'template': template_id, 'data': value, }) if parameter: return parameter.pk, is_new_part_parameters_template_id else: if template_id == 0: cprint( f'[TREE]\tError: Parameter template "{template_name}" does not exist', silent=settings.SILENT) return 0, False
def connect_to_server(timeout=5) -> bool: ''' Connect to InvenTree server using user settings ''' connect = False settings.load_inventree_settings() try: connect = inventree_api.connect(server=settings.SERVER_ADDRESS, username=settings.USERNAME, password=settings.PASSWORD, connect_timeout=timeout) except TimeoutError: pass if not connect: if not settings.SERVER_ADDRESS: cprint( f'[TREE]\tError connecting to InvenTree server: missing server address' ) return connect if not settings.USERNAME: cprint( f'[TREE]\tError connecting to InvenTree server: missing username' ) return connect if not settings.PASSWORD: cprint( f'[TREE]\tError connecting to InvenTree server: missing password' ) return connect cprint( f'[TREE]\tError connecting to InvenTree server: invalid address, username or password' ) else: env = [ env_type.name for env_type in settings.Environment if env_type.value == settings.environment ][0] cprint( f'[TREE]\tSuccessfully connected to InvenTree server (ENV={env})', silent=settings.SILENT) return connect
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
def get_inventree_category_id(category_name: str, parent_category_id=None) -> int: ''' Get InvenTree category ID from name, specificy parent if subcategory ''' global inventree_api # Fetch all categories part_categories = PartCategory.list(inventree_api) for item in part_categories: if category_name == item.name: # Check parent id match (if passed as argument) match = True if parent_category_id: cprint( f'[TREE]\t{item.getParentCategory().pk} ?= {parent_category_id}', silent=settings.HIDE_DEBUG) if item.getParentCategory().pk != parent_category_id: match = False if match: cprint(f'[TREE]\t{item.name} ?= {category_name} => True', silent=settings.HIDE_DEBUG) return item.pk else: cprint(f'[TREE]\t{item.name} ?= {category_name} => False', silent=settings.HIDE_DEBUG) return -1
def install(c, is_install=True): if is_install: cprint('[MAIN]\tInstalling required dependencies') c.run('pip install -U wheel', hide='out') else: cprint('[MAIN]\tUpdating required dependencies') c.run('pip install -U -r requirements.txt', hide='out') if is_install: cprint('[MAIN]\tInstalling optional dependencies') try: c.run('pip install -U python-Levenshtein', hide=True) except UnexpectedExit: cprint('\n[INFO]\tFailed to install python-Levenshtein...\t' 'You may be missing python3.x-dev')
def create_manufacturer_part(part_id: int, manufacturer_name: str, manufacturer_mpn: str, description: str, datasheet: str) -> bool: ''' Create InvenTree manufacturer part part_id: Part the manufacturer data is linked to manufacturer: Company that manufactures the SupplierPart (leave blank if it is the sample as the Supplier!) MPN: Manufacture part number datasheet: Datasheet link description: Descriptive notes field ''' global inventree_api # Get Manufacturer ID manufacturer_id = get_company_id(manufacturer_name) if manufacturer_id: # Validate datasheet link if not validators.url(datasheet): datasheet = '' manufacturer_part = ManufacturerPart.create( inventree_api, { 'part': part_id, 'manufacturer': manufacturer_id, 'MPN': manufacturer_mpn, 'link': datasheet, 'description': description, }) if manufacturer_part: return True else: cprint( f'[TREE]\tError: Manufacturer "{manufacturer_name}" not found (failed to create manufacturer part)', silent=settings.SILENT) return False
def load_user_config_files(path_to_root: str, path_to_user_files: str) -> bool: ''' Load InvenTree user configuration ''' result = True def load_config(path): for template_file in os.listdir(path): filename = os.path.basename(template_file) template_data = load_file(path + template_file) try: user_data = load_file(path_to_user_files + filename) # Join user data to template data user_settings = {**template_data, **user_data} except: user_settings = template_data dump_file(user_settings, path_to_user_files + filename) # Load User settings try: config_files = os.path.join(path_to_root, 'settings', '') load_config(config_files) except: cprint(f'[INFO]\tWarning: Failed to load User settings') result = False # Load Digi-Key configuration files try: config_files = os.path.join(path_to_root, 'digikey', '') load_config(config_files) except: cprint(f'[INFO]\tWarning: Failed to load Digi-Key configuration') result = False # Load InvenTree configuration files try: config_files = os.path.join(path_to_root, 'inventree', '') load_config(config_files) except: cprint(f'[INFO]\tWarning: Failed to load InvenTree configuration') result = False # Load KiCad configuration files try: config_files = os.path.join(path_to_root, 'kicad', '') load_config(config_files) except: cprint(f'[INFO]\tWarning: Failed to load KiCad configuration') result = False return result
def make_python_badge(c): cprint('[MAIN]\tInstall pybadges') c.run('pip install pybadges pip-autoremove', hide=True) cprint('[MAIN]\tCreate badge') c.run('python -m pybadges --left-text="Python" --right-text="3.6 | 3.7 | 3.8" ' '--whole-link="https://www.python.org/" --browser --embed-logo ' '--logo="https://dev.w3.org/SVG/tools/svgweb/samples/svg-files/python.svg"') cprint('[MAIN]\tUninstall pybadges') c.run('pip-autoremove pybadges -y', hide=True) c.run('pip uninstall pip-autoremove -y', hide=True)
def install(c, is_install=True): if is_install: cprint('[MAIN]\tInstalling required dependencies') c.run('pip install -U wheel', hide='out') else: cprint('[MAIN]\tUpdating required dependencies') c.run('pip install -U -r requirements.txt', hide='out') # Install wrapt_timeout_decorator library from GitHub c.run( 'pip install git+https://github.com/bitranox/wrapt_timeout_decorator.git', hide='out') if is_install: cprint('[MAIN]\tInstalling optional dependencies') try: c.run('pip install -U python-Levenshtein', hide=True) except UnexpectedExit: cprint('\n[INFO]\tFailed to install python-Levenshtein...\t' 'You may be missing python3.x-dev')
def is_new_supplier_part(supplier_name: str, supplier_sku: str) -> bool: ''' Check if InvenTree supplier part exists to avoid duplicates ''' global inventree_api # Fetch all companies cprint(f'[TREE]\tFetching suppliers', silent=settings.HIDE_DEBUG) company_list = Company.list(inventree_api, is_supplier=True, is_customer=False) companies = {} for company in company_list: companies[company.name] = company try: # Get all parts part_list = companies[supplier_name].getSuppliedParts() except: part_list = None if part_list == None: # Create cprint(f'[TREE]\tCreating new supplier "{supplier_name}"', silent=settings.SILENT) create_company( company_name=supplier_name, supplier=True, ) # Get all parts part_list = [] for item in part_list: if supplier_sku in item.SKU: cprint(f'[TREE]\t{item.SKU} ?= {supplier_sku} => True', silent=settings.HIDE_DEBUG) return False else: cprint(f'[TREE]\t{item.SKU} ?= {supplier_sku} => False', silent=settings.HIDE_DEBUG) return True
def pretty_test_print(message: str): message = message.ljust(65) cprint(message, end='')
# Enable InvenTree settings.set_inventree_enable_flag(True, save=True) # Enable KiCad settings.set_kicad_enable_flag(True, save=True) # Create user configuration files settings.create_user_config_files() # Set path to test libraries test_library_path = os.path.join(settings.PROJECT_DIR, 'tests', 'TEST.lib') symbol_libraries_test_path = os.path.join(settings.PROJECT_DIR, 'tests', 'files', 'SYMBOLS') footprint_libraries_test_path = os.path.join(settings.PROJECT_DIR, 'tests', 'files', 'FOOTPRINTS', '') # Disable API logging disable_digikey_api_logger() if not test_digikey_api_connect(): cprint('[INFO]\tFailed to get Digi-Key API token, aborting.') sys.exit(-1) # Pretty test printing def pretty_test_print(message: str): message = message.ljust(65) cprint(message, end='') # Check result def check_result(status: str, new_part: bool) -> bool: # Build result success = False if (status == 'original') or (status == 'fake_alternate'): if new_part: