Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Template handling #172

Merged
merged 12 commits into from
Oct 14, 2023
2 changes: 1 addition & 1 deletion .github/workflows/test_deploy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ jobs:
git clone https://github.com/inventree/InvenTree/
mkdir InvenTree/static
cp tests/files/inventree_default_db.sqlite3 InvenTree/
cd InvenTree/ && git switch 0.11.x && invoke install && invoke migrate && cd -
cd InvenTree/ && git switch stable && invoke install && invoke migrate && cd -
- name: Ki-nTree setup
run: |
invoke install
Expand Down
11 changes: 10 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,15 @@ Ki-nTree currently supports APIs for the following electronics suppliers: Digi-K
5. It will take some time to complete the part creation in InvenTree and/or KiCad, once it finishes you'll be notified of the result
6. Finally, if the part was created or found in InvenTree, your browser will automatically open a new tab with the part information

#### Kicad Templates

The automatic part generation in KiCad is controlled via templates:

* Template examples are shipped together with Ki-nTree, these can be adjusted to your likings or you also can create completely new ones.
* Each template has its own library file where the file name defines the templates name.
* The templates can use the parameters and attributes of the InvenTree part on a wildcard base. So you can add for example `Resistance @ Tolerance` into a field and the resulting part will then have the resitance and the tolerance value inside this text field.


Enjoy!

*For any problem/bug you find, please [report an issue](https://github.com/sparkmicro/Ki-nTree/issues).*
Expand Down Expand Up @@ -272,4 +281,4 @@ The Ki-nTree source code is licensed under the [GPL3.0 license](https://github.c
* https://github.com/mvnmgrx/kiutils
* https://github.com/peeter123/digikey-api

The [KiCad templates](https://github.com/sparkmicro/Ki-nTree/tree/main/kintree/kicad/templates) are licensed under the [Creative Commons CC0 1.0 license](https://github.com/sparkmicro/Ki-nTree/blob/main/kintree/kicad/templates/LICENSE) which means that "you can copy, modify, distribute and perform the work, even for commercial purposes, all without asking permission" ([reference](https://creativecommons.org/publicdomain/zero/1.0/)).
The [KiCad templates](https://github.com/sparkmicro/Ki-nTree/tree/main/kintree/kicad/templates) are licensed under the [Creative Commons CC0 1.0 license](https://github.com/sparkmicro/Ki-nTree/blob/main/kintree/kicad/templates/LICENSE) which means that "you can copy, modify, distribute and perform the work, even for commercial purposes, all without asking permission" ([reference](https://creativecommons.org/publicdomain/zero/1.0/)).
6 changes: 5 additions & 1 deletion kintree/config/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,12 +116,14 @@ def reload_enable_flags():
global ENABLE_INVENTREE
global ENABLE_ALTERNATE
global UPDATE_INVENTREE
global CHECK_EXISTING

try:
ENABLE_KICAD = CONFIG_GENERAL.get('ENABLE_KICAD', False)
ENABLE_INVENTREE = CONFIG_GENERAL.get('ENABLE_INVENTREE', False)
ENABLE_ALTERNATE = CONFIG_GENERAL.get('ENABLE_ALTERNATE', False)
UPDATE_INVENTREE = CONFIG_GENERAL.get('UPDATE_INVENTREE', False)
CHECK_EXISTING = CONFIG_GENERAL.get('CHECK_EXISTING', True)
return True
except TypeError:
pass
Expand Down Expand Up @@ -370,7 +372,7 @@ def set_enable_flag(key: str, value: bool):
global CONFIG_GENERAL

user_settings = CONFIG_GENERAL
if key in ['kicad', 'inventree', 'alternate', 'update']:
if key in ['kicad', 'inventree', 'alternate', 'update', 'check_existing']:
if key == 'kicad':
user_settings['ENABLE_KICAD'] = value
elif key == 'inventree':
Expand All @@ -379,6 +381,8 @@ def set_enable_flag(key: str, value: bool):
user_settings['ENABLE_ALTERNATE'] = value
elif key == 'update':
user_settings['UPDATE_INVENTREE'] = value
elif key == 'check_existing':
user_settings['CHECK_EXISTING'] = value

# Save
config_interface.dump_file(
Expand Down
12 changes: 8 additions & 4 deletions kintree/database/inventree_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,12 +155,16 @@ def get_category_parameters(category_id: int) -> list:
return parameter_templates


def get_part_number(part_id: int) -> str:
''' Get InvenTree part number from specified Part ID '''
def get_part_info(part_id: int) -> str:
''' Get InvenTree part info from specified Part ID '''
global inventree_api

part = Part(inventree_api, part_id)
return part.IPN
part_info = {'IPN': part.IPN}
attachment = part.getAttachments()
if attachment:
part_info['datasheet'] = f'{inventree_api.base_url.strip("/")}{attachment[0]["attachment"]}'
return part_info


def set_part_number(part_id: int, ipn: str) -> bool:
Expand Down Expand Up @@ -390,7 +394,7 @@ def upload_part_datasheet(datasheet_url: str, part_id: int) -> str:
try:
attachment = part.uploadAttachment(attachment=datasheet_location)
os.remove(datasheet_location)
return inventree_api.base_url.strip('/') + attachment['attachment']
return f'{inventree_api.base_url.strip("/")}{attachment["attachment"]}'
except Exception:
return ''
else:
Expand Down
42 changes: 24 additions & 18 deletions kintree/database/inventree_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ def build_tree(tree, left_to_go, level) -> list:
elif left_to_go is None:
pass
return

if reload:
categories = inventree_api.get_categories()
category_data.update({'CATEGORIES': categories})
Expand Down Expand Up @@ -515,21 +515,22 @@ def inventree_create(part_info: dict, kicad=False, symbol=None, footprint=None,
if category_pk <= 0:
cprint(f'[ERROR]\tCategory ({category_tree}) does not exist in InvenTree', silent=settings.SILENT)
else:
# Check if part already exists
part_pk = inventree_api.is_new_part(category_pk, inventree_part)
# Part exists
if part_pk > 0:
cprint('[INFO]\tPart already exists, skipping.', silent=settings.SILENT)
ipn = inventree_api.get_part_number(part_pk)
if ipn:
# Update InvenTree part number
inventree_part['IPN'] = ipn
# Update InvenTree URL
inventree_part['inventree_url'] = f'{settings.PART_URL_ROOT}{inventree_part["IPN"]}/'
else:
inventree_part['inventree_url'] = f'{settings.PART_URL_ROOT}{part_pk}/'
if settings.CHECK_EXISTING:
# Check if part already exists
part_pk = inventree_api.is_new_part(category_pk, inventree_part)
# Part exists
if part_pk > 0:
cprint('[INFO]\tPart already exists, skipping.', silent=settings.SILENT)
info = inventree_api.get_part_info(part_pk)
if info:
# Update InvenTree part number
inventree_part = {**inventree_part, **info}
# Update InvenTree URL
inventree_part['inventree_url'] = f'{settings.PART_URL_ROOT}{inventree_part["IPN"]}/'
else:
inventree_part['inventree_url'] = f'{settings.PART_URL_ROOT}{part_pk}/'
# Part is new
else:
if not part_pk:
new_part = True
# Create a new Part
# Use the pk (primary-key) of the category
Expand Down Expand Up @@ -757,11 +758,16 @@ def inventree_create_alternate(part_info: dict, part_id='', part_ipn='', show_pr
manufacturer_mpn = part_info.get('manufacturer_part_number', '')
datasheet = part_info.get('datasheet', '')

attachment = part.getAttachments()
# if datasheet upload is enabled and no attachment present yet then upload
if settings.DATASHEET_UPLOAD and not part.getAttachments():
if settings.DATASHEET_UPLOAD and not attachment:
if datasheet:
inventree_api.upload_part_datasheet(part_id=part_pk,
datasheet_url=datasheet)
part_info['datasheet'] = inventree_api.upload_part_datasheet(
part_id=part_pk,
datasheet_url=datasheet)
# if an attachment is present, set it as the datasheet field
if attachment:
part_info['datasheet'] = f'{inventree_api.inventree_api.base_url.strip("/")}{attachment[0]["attachment"]}'

# Create manufacturer part
if manufacturer_name and manufacturer_mpn:
Expand Down
2 changes: 1 addition & 1 deletion kintree/gui/views/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ def handle_transition(page: ft.Page, transition: bool, update_page=False, timeou
# Update
if update_page:
page.update()


def update_theme(page: ft.Page, mode='light', transition=False, compact=True):
# Color theme
Expand Down
102 changes: 90 additions & 12 deletions kintree/gui/views/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ class PartSearchView(MainView):
label="Part Number",
dense=True,
hint_text="Part Number",
width=300,
width=250,
expand=True,
),
'supplier': ft.Dropdown(
Expand All @@ -268,25 +268,33 @@ class PartSearchView(MainView):
width=48,
tooltip="Submit",
),
'parameter_view': ft.Switch(
label='View Parameters',
disabled=True
),
'search_form': {},
'parameter_form': {},
}

def reset_view(self, e, ignore=['enable']):
hidden_fields = {
'searched_part_number': '',
'custom_part': None,
}
self.fields['parameter_form'] = {}
return super().reset_view(e, ignore=ignore, hidden=hidden_fields)

def enable_search_fields(self):
for form_field in self.fields['search_form'].values():
form_field.disabled = False
self.fields['parameter_view'].disabled = False
self.page.update()
return

def run_search(self, e):
# Reset view
self.reset_view(e, ignore=['part_number', 'supplier'])
self.switch_view()
# Validate form
if bool(self.fields['part_number'].value) != bool(self.fields['supplier'].value):
if not self.fields['part_number'].value:
Expand Down Expand Up @@ -323,23 +331,30 @@ def run_search(self, e):
supplier=supplier,
part_info=part_supplier_info,
)
if part_supplier_form:
for field_idx, field_name in enumerate(self.fields['search_form'].keys()):
# print(field_idx, field_name, get_default_search_keys()[field_idx], search_form_field[field_name])
try:
self.fields['search_form'][field_name].value = part_supplier_form.get(field_name, '')
except IndexError:
pass
# Enable editing
self.enable_search_fields()
# Stitch parameters
if part_supplier_info.get('parameters', None):
self.data['parameters'] = part_supplier_info['parameters']
for parameter, value in self.data['parameters'].items():
text_field = ft.TextField(
label=parameter,
value=value,
expand=True,
on_change=self.push_data,
)
self.fields['parameter_form'][parameter] = text_field
# and pricing
if part_supplier_info.get('pricing', None):
self.data['pricing'] = part_supplier_info['pricing']

if part_supplier_form:
for field_idx, field_name in enumerate(self.fields['search_form'].keys()):
# print(field_idx, field_name, get_default_search_keys()[field_idx], search_form_field[field_name])
try:
self.fields['search_form'][field_name].value = part_supplier_form.get(field_name, '')
except IndexError:
pass
# Enable editing
self.enable_search_fields()

# Add to data buffer
self.push_data()
self.page.splash.visible = False
Expand All @@ -364,6 +379,8 @@ def push_data(self, e=None):
}
for key, field in self.fields['search_form'].items():
self.data[key] = field.value
for key, field in self.fields['parameter_form'].items():
self.data['parameters'][key] = field.value
return super().push_data(e, hidden=hidden_fields)

def partial_update(self):
Expand All @@ -385,10 +402,34 @@ def update_suppliers(self):
# Control not added to page yet
pass

def switch_view(self, e=None):
# show parameters instead of part information
parameters_view = self.fields['parameter_view'].value
self.column.controls[0].content.controls = [
ft.Row(),
ft.Row(
controls=[
self.fields['part_number'],
self.fields['supplier'],
self.fields['search_button'],
self.fields['parameter_view'],
],
),
ft.Divider(),
]
if not parameters_view:
for field, text_field in self.fields['search_form'].items():
self.column.controls[0].content.controls.append(ft.Row([text_field]))
else:
for field, text_field in self.fields['parameter_form'].items():
self.column.controls[0].content.controls.append(ft.Row([text_field]))
self.page.update()

def build_column(self):
self.update_suppliers()
# Enable search method
self.fields['search_button'].on_click = self.run_search
self.fields['parameter_view'].on_change = self.switch_view

self.column = ft.Column(
controls=[
Expand All @@ -401,6 +442,7 @@ def build_column(self):
self.fields['part_number'],
self.fields['supplier'],
self.fields['search_button'],
self.fields['parameter_view'],
],
),
ft.Divider(),
Expand Down Expand Up @@ -480,6 +522,11 @@ class InventreeView(MainView):
'Create New Code': SwitchWithRefs(
label='Create New Code',
),
'check_existing': ft.Switch(
label='Check for existing Parts',
value=settings.CHECK_EXISTING if settings.ENABLE_INVENTREE else False,
disabled=not settings.ENABLE_INVENTREE,
),
'New Category Code': ft.TextField(
label='New Category Code',
width=GUI_PARAMS['textfield_width'] / 2 - 5,
Expand Down Expand Up @@ -586,6 +633,20 @@ def process_update(self, e, value=None):
settings.set_enable_flag('update', update_enabled)
self.push_data(e)

def process_button(self, e, value=None):
if value is not None:
button_enabled = value
else:
# Get switch value
button_enabled = False
if e.data.lower() == 'true':
button_enabled = True
if e.control.label == 'Update existing':
settings.set_enable_flag('update', button_enabled)
elif e.control.label == 'Check for existing Parts':
settings.set_enable_flag('check_existing', button_enabled)
self.push_data(e)

def process_category(self, e=None, label=None, value=None):
parent_category = None
if isinstance(self.fields['Category'].value, str):
Expand Down Expand Up @@ -654,6 +715,8 @@ def build_column(self):
self.fields['IPN: Category Code'].on_change = self.push_data
self.fields['Create New Code'].on_change = self.create_ipn_code
self.fields['New Category Code'].on_change = self.push_data
# Other Settings
self.fields['check_existing'].on_change = self.process_button
# Alternate fields
self.fields['alternate'].on_change = self.process_alternate
self.fields['Existing Part ID'].on_change = self.push_data
Expand Down Expand Up @@ -694,6 +757,11 @@ def build_column(self):
),
],
),
ft.Row(
[
self.fields['check_existing'],
],
),
],
),
],
Expand Down Expand Up @@ -1289,12 +1357,22 @@ def create_part(self, e=None):
progress.CREATE_PART_PROGRESS = 0
# Add part symbol to KiCAD
cprint('\n[MAIN]\tAdding part to KiCad', silent=settings.SILENT)
kicad_success, kicad_new_part = kicad_interface.inventree_to_kicad(
kicad_success, kicad_new_part, kicad_part_name = kicad_interface.inventree_to_kicad(
part_data=part_info,
library_path=symbol_library_path,
show_progress=self.fields['kicad_progress'],
)
# print(kicad_success, kicad_new_part)
# Update symbol name in InvenTree
if settings.ENABLE_INVENTREE and part_pk:
old_state = settings.UPDATE_INVENTREE
settings.UPDATE_INVENTREE = True
inventree_interface.inventree_process_parameters(
part_pk,
{'Symbol': f"{symbol_lib}:{kicad_part_name}"},
show_progress=self.fields['inventree_progress'],
)
settings.UPDATE_INVENTREE = old_state

# Complete add operation
if kicad_success:
Expand Down
Loading
Loading