diff --git a/python/InvenTreeLink/InvenTreeLink.py b/python/InvenTreeLink/InvenTreeLink.py index 7a1ce0d..5f4c5a7 100644 --- a/python/InvenTreeLink/InvenTreeLink.py +++ b/python/InvenTreeLink/InvenTreeLink.py @@ -3,6 +3,8 @@ import os +import threading + try: from .apper import apper from . import config @@ -12,41 +14,13 @@ my_addin = apper.FusionApp(config.app_name, config.company_name, False) my_addin.root_path = config.app_path - from .commands.ShowPartCommand import ShowPartCommand + from .commands.EditPartCommand import EditPartCommand from .commands.BOMOverviewCommand import BomOverviewPaletteShowCommand - from .commands.SendBomCommand import SendBomCommand - from .commands.SendBomOnlineCommand import SendBomOnlineCommand - from .commands.SendStepCommand import SendStepCommand - + from .commands.GenerateBomCommand import GenerateBomCommand + from .commands.ImportStlCommand import ImportStlCommand + from .commands.ImportPartCommand import ImportPartCommand + # Commands - my_addin.add_command( - 'Show part details', - ShowPartCommand, - { - 'cmd_description': 'Show the InvenTree part-details for the selected part', - 'cmd_id': config.DEF_SEND_PART, - 'workspace': 'FusionSolidEnvironment', - 'toolbar_panel_id': 'Commands', - 'cmd_resources': config.DEF_SEND_PART, - 'command_visible': True, - 'command_promoted': False, - } - ) - - my_addin.add_command( - 'Upload STEP to attachments', - SendStepCommand, - { - 'cmd_description': 'Generates a STEP file and attaches it to a part', - 'cmd_id': config.DEF_SEND_STEP, - 'workspace': 'FusionSolidEnvironment', - 'toolbar_panel_id': 'Commands', - 'cmd_resources': config.DEF_SEND_BOM, - 'command_visible': True, - 'command_promoted': False, - 'palette_id': config.ITEM_PALETTE, - } - ) # Palette my_addin.add_command( @@ -56,7 +30,7 @@ 'cmd_description': 'Show the BOM overview palette', 'cmd_id': config.DEF_SHOW_PALETTE, 'workspace': 'FusionSolidEnvironment', - 'toolbar_panel_id': config.APP_PANEL, + 'toolbar_panel_id': config.ToolbarPanelID.INVENTREE_LINK, 'cmd_resources': 'ShowPalette', 'command_visible': True, 'command_promoted': True, @@ -75,12 +49,12 @@ # Commands that need the palette my_addin.add_command( 'Load BOM for assembly', - SendBomCommand, + GenerateBomCommand, { 'cmd_description': 'Load the BOM for the assembly in the current file', 'cmd_id': config.DEF_SEND_BOM, 'workspace': 'FusionSolidEnvironment', - 'toolbar_panel_id': config.APP_PANEL, + 'toolbar_panel_id': config.ToolbarPanelID.INVENTREE_LINK, 'cmd_resources': config.DEF_SEND_BOM, 'command_visible': True, 'command_promoted': False, @@ -89,14 +63,43 @@ ) my_addin.add_command( - 'Get InvenTree Information', - SendBomOnlineCommand, + 'Edit Part', + EditPartCommand, { - 'cmd_description': 'Fetch the InvenTree information for all BOM-parts', - 'cmd_id': config.DEF_SEND_ONLINE_STATE, + 'cmd_description': 'Show the InvenTree part-details for the selected part', + 'cmd_id': config.DEF_SEND_PART, 'workspace': 'FusionSolidEnvironment', - 'toolbar_panel_id': config.APP_PANEL, - 'cmd_resources': config.DEF_SEND_ONLINE_STATE, + 'toolbar_panel_id': config.ToolbarPanelID.PART, + 'cmd_resources': config.DEF_SEND_PART, + 'command_visible': True, + 'command_promoted': False, + } + ) + + my_addin.add_command( + 'Import Part', + ImportPartCommand, + { + 'cmd_description': 'Import a Part as STL', + 'cmd_id': config.DEF_IMPORT_PART, + 'workspace': 'FusionSolidEnvironment', + 'toolbar_panel_id': config.ToolbarPanelID.PART, + 'cmd_resources': config.DEF_SEND_BOM, + 'command_visible': True, + 'command_promoted': False, + 'palette_id': config.ITEM_PALETTE, + } + ) + + my_addin.add_command( + 'Export STEP', + ImportStlCommand, + { + 'cmd_description': 'Generates a STEP file and attaches it to a part', + 'cmd_id': config.DEF_SEND_STEP, + 'workspace': 'FusionSolidEnvironment', + 'toolbar_panel_id': config.ToolbarPanelID.PART, + 'cmd_resources': 'ShowPalette', 'command_visible': True, 'command_promoted': False, 'palette_id': config.ITEM_PALETTE, @@ -113,6 +116,61 @@ functions.init_Fusion360() + # class MyDocumentActivatedHandler(adsk.core.DocumentEventHandler): + # def __init__(self): + # super().__init__() + # def notify(self, args): + # eventArgs = adsk.core.DocumentEventArgs.cast(args) + + # # Code to react to the event. + # ui.messageBox('In MyDocumentActivatedHandler event handler.\ndocument: {}'.format(eventArgs.document.name)) + + # class MyDocumentSavedHandler(adsk.core.DocumentEventHandler): + # def __init__(self): + # super().__init__() + + # def notify(self, args): + # eventArgs = adsk.core.DocumentEventArgs.cast(args) + + # # Code to react to the event. + # ui.messageBox('In MyDocumentSavedHandler event handler.') + + # onDocumentSaved = MyDocumentSavedHandler() + # app.documentSaved.add(onDocumentSaved) + + # onDocumentActivated = MyDocumentActivatedHandler() + # app.documentActivated.add(onDocumentActivated) + + # def correct_inventree_names_thread(): + # # Recursively correct the names to inventree names + # def correct_inventree_names(component): + # part = functions.inventree_get_part(component.id) + + # if part: + # if component.name.lower() != part.name.lower(): + # print(f"Correcting name of '{component.name}' to '{part.name}'") + # component.name = part.name + + # if component.partNumber.lower() != part.IPN.lower(): + # print(f"Correcting IPN of '{component.partNumber}' to '{part.IPN}'") + # component.partNumber = part.IPN + + # for occurrence in component.occurrences: + # if occurrence.component: + # correct_inventree_names(occurrence.component) + + # ao = apper.AppObjects() + # root = ao.product.rootComponent + + # for occurrence in root.occurrences: + # if occurrence.component: + # correct_inventree_names(occurrence.component) + + # t = threading.Thread(target=correct_inventree_names_thread) + # t.start() + + print("InvenTreeLink started.") + except: # noqa: E722 app = adsk.core.Application.get() ui = app.userInterface diff --git a/python/InvenTreeLink/commands/BOMOverviewCommand.py b/python/InvenTreeLink/commands/BOMOverviewCommand.py index e7b3947..54bb57e 100644 --- a/python/InvenTreeLink/commands/BOMOverviewCommand.py +++ b/python/InvenTreeLink/commands/BOMOverviewCommand.py @@ -7,7 +7,13 @@ from ..apper import apper from .. import config from .. import helpers +from .. import functions + +from ..functions import inv_api, inventree_get_part +import threading +import typing +import sys # Class for a Fusion 360 Palette Command class BomOverviewPaletteShowCommand(apper.PaletteCommandBase): @@ -22,6 +28,9 @@ def on_palette_execute(self, palette: adsk.core.Palette): # Run when ever a fusion event is fired from the corresponding web page def on_html_event(self, html_args: adsk.core.HTMLEventArgs): data = json.loads(html_args.data) + + from inventree.part import Part + from inventree.part import BomItem try: ao = apper.AppObjects() @@ -46,6 +55,14 @@ def on_html_event(self, html_args: adsk.core.HTMLEventArgs): selections.add(entitiesByToken) # TODO selection not working helpers.get_cmd(ao, config.DEF_SEND_PART).execute() + elif html_args.action == 'SyncAll': + root = ao.product.rootComponent + + # sync_all_thread(ao, root) + + t = threading.Thread(target=sync_all_thread, args=(ao, root)) + t.start() + # TODO investigate ghost answers # else: # raise NotImplementedError('unknown message received from HTML') @@ -56,3 +73,228 @@ def on_html_event(self, html_args: adsk.core.HTMLEventArgs): # Handle any extra cleanup when user closes palette here def on_palette_close(self): pass + +def propose_create_f360_part(ao, component: adsk.fusion.Component, buttons): + physicalProperties = component.physicalProperties + + fusion_360_data = ( + f"Part Number: {component.partNumber}
" + f"Name: {component.name}
" + f"Description: {component.description}

" + + f"ID: {component.id}
" + f"Area: {physicalProperties.area}cm2
" + f"Volume: {physicalProperties.volume}cm3
" + f"Mass: {physicalProperties.mass}kg
" + f"Density: {physicalProperties.density}g/cm3
" + ) + + if component.material and component.material.name: + fusion_360_data += f"Material: {component.material.name}
" + + axis = ['x', 'y', 'z'] + bb_min = {a: getattr(component.boundingBox.minPoint, a) for a in axis} + bb_max = {a: getattr(component.boundingBox.maxPoint, a) for a in axis} + bb = {a: bb_max[a] - bb_min[a] for a in axis} + + fusion_360_data += ( + f"Dimensions: {bb['x']}cm x {bb['y']}cm x {bb['z']}cm
" + ) + + text = ( + f"Part {component.partNumber} | {component.name} is not recognized by Inventree.
" + "Do you want to create a Part with the values provided by Fusion360?

" + f"{fusion_360_data}" + ) + + return ao.ui.messageBox(text, "Sync All", buttons, 1) + + +@apper.lib_import(config.lib_path) +def sync_all_thread(ao, root: adsk.fusion.Component): + print("Starting sync_all thread.") + + palette = ao.ui.palettes.itemById(config.ITEM_PALETTE) + + log_html = [] + def log(message: str, color: str = "black"): + log_html.append(f'{message}') + + palette.sendInfoToHTML( + config.DEF_SYNC_LOG, + '
'.join(log_html) + ) + + palette.sendInfoToHTML( + config.DEF_SEND_BOM, + ( + '
' + '


' + '
' + '
' + '
' + '
' + + '
' + '

Syncing ...

' + '
' + + '
' + '' + + '
' + '
' + '{root.name}{parent_info} does not have a IPN set" + ), "warn") + else: + # The part number is not empty, so search InvenTree for an existing part. + item_list = Part.list(inv_api(), IPN=root.partNumber, has_ipn=True) + + if len(item_list) == 0: + # If the partnumber is not recognized by InvenTree, remove it for Fusion360 + root.partNumber = "" + + print(f"Item {root.name}{parent_info} is not matched by part number, but will be created with an empty IPN.") + + # There are no matches with this IPN, create the part. + root_part = helpers.create_f360_part(root, functions.config_ref(config.CFG_PART_CATEGORY)) + elif len(item_list) == 1: + print(f"Item {root.name} is singly matched by part number.") + # There's a single match by IPN, it must be the part we want. + root_part = item_list[0] + elif len(item_list) > 1: + print(f"Item {root.name}{parent_info} is not matched by Fusion360 ID, but found {len(item_list)} items matching IPN {root.partNumber}") + + log(( + f"Skipping part {root.name}{parent_info} because it does not have a unique IPN. (IPN = '{root.partNumber}')" + ), "red") + + return + + # If there's an inventree name, it will be copied and set. + # If the part was created right before this, it was created with the + # part name of Fusion360, so this would have no side effect. + new_name = root_part.name + new_IPN = root_part.IPN + + # Exception is thrown when trying to edit root component name. + try: + root.name = new_name + except: + pass + + root.partNumber = new_IPN + + if not root_part.assembly: + # Delete previous bom + for item in root_part.getBomItems(): + item.delete() + + #print(f"Before save: {new_name} was an assembly: {root_part.assembly} with {len(root.occurrences)} occs") + + root_part.save(data={ + 'name': new_name, + 'IPN': new_IPN, + 'description': root.description if root.description else f'Fusion360 Name: {root.name}', + }) + helpers.write_f360_parameters(root_part, root) + + #print(f"After save: {new_name} is an assembly: {root_part.assembly} with {len(root.occurrences)} occs") + + if root_part.assembly: + instance_name = {} + instance_count = {} + for occurrence in root.occurrences: + if occurrence.component: + if str(occurrence.component.id) in instance_count: + instance_count[str(occurrence.component.id)] += 1 + else: + instance_count[str(occurrence.component.id)] = 1 + instance_name[str(occurrence.component.id)] = occurrence.component.name + + if str(occurrence.component.id) in visited: + #print(f"Skipping {occurrence.component.name} because it's already visited.") + noop = True + else: + sync_all(ao, occurrence.component, log, root, visited, warning_raised) + visited[str(occurrence.component.id)] = True + + + palette.sendInfoToHTML( + "exec", + f"document.getElementById('status').innerHTML = '{root.name}'s Bill of Materials';" + ) + + parts = inventree_get_part([id for id in instance_count]) + print(f"Creating Bill of Materials for {new_name}... ") + for id in instance_count: + part = parts[id] + + if part is not False: + BomItem.create(inv_api(), { + 'part': root_part.pk, + 'quantity': instance_count[id], + 'sub_part': part.pk + }) + else: + log(f"Unable to get part for {instance_name[id]}") + warning_raised = True + + print(f"Done creating Bill of Materials for {new_name}.") + + #print(f"Updated part {root.name} ({root.id})") + + return warning_raised diff --git a/python/InvenTreeLink/commands/ShowPartCommand.py b/python/InvenTreeLink/commands/EditPartCommand.py similarity index 72% rename from python/InvenTreeLink/commands/ShowPartCommand.py rename to python/InvenTreeLink/commands/EditPartCommand.py index 2a6ff93..5f8ec6d 100644 --- a/python/InvenTreeLink/commands/ShowPartCommand.py +++ b/python/InvenTreeLink/commands/EditPartCommand.py @@ -10,11 +10,13 @@ from ..functions import Fusion360Parameters -class ShowPartCommand(apper.Fusion360CommandBase): +class EditPartCommand(apper.Fusion360CommandBase): def on_input_changed(self, command, inputs, changed_input, input_values): try: ao = apper.AppObjects() - if ao.ui.activeSelections.count == 1: + if ao.ui.activeSelections.count == 1: + # TODO: Support Design's + occ = adsk.fusion.Occurrence.cast(ao.ui.activeSelections[0].entity) arg_id = changed_input.id inp = inputs.command.commandInputs @@ -49,6 +51,8 @@ def getText(text_name, obj, item, data): # compare if not getattr(obj, item) == value: data[item] = value + # Return the value so it can be set. + return value def getValue(text_name, obj, item, data): value = inp.itemById(text_name).value @@ -58,8 +62,14 @@ def getValue(text_name, obj, item, data): if part: _data = {} - getText('text_part_name', part, 'name', _data) - getText('text_part_ipn', part, 'IPN', _data) + + try: + occ.component.name = getText('text_part_name', part, 'name', _data) + except: + pass + + occ.component.partNumber = getText('text_part_ipn', part, 'IPN', _data) + getText('text_part_description', part, 'description', _data) getText('text_part_notes', part, 'notes', _data) getText('text_part_keywords', part, 'keywords', _data) @@ -84,21 +94,21 @@ def on_create(self, command, inputs): # Tabs tabCmdInput1 = inputs.addTabCommandInput('tab_1', 'Start') tab1ChildInputs = tabCmdInput1.children - tabCmdInput2 = inputs.addTabCommandInput('tab_2', 'Teil-Details') - tab2ChildInputs = tabCmdInput2.children + part_details_tab = inputs.addTabCommandInput('tab_2', 'Part Details') + part_details_children = part_details_tab.children # TextInputs for general information - tab2ChildInputs.addTextBoxCommandInput('text_id', 'id', 'id', 1, True) - tab2ChildInputs.addTextBoxCommandInput('text_name', 'name', 'name', 1, True) - tab2ChildInputs.addTextBoxCommandInput('text_description', 'description', 'description', 1, True) - tab2ChildInputs.addTextBoxCommandInput('text_opacity', 'opacity', 'opacity', 1, True) - tab2ChildInputs.addTextBoxCommandInput('text_partNumber', 'partNumber', 'partNumber', 1, True) - tab2ChildInputs.addTextBoxCommandInput('text_area', 'area', 'area', 1, True) - tab2ChildInputs.addTextBoxCommandInput('text_volume', 'volume', 'volume', 1, True) - tab2ChildInputs.addTextBoxCommandInput('text_mass', 'mass', 'mass', 1, True) - tab2ChildInputs.addTextBoxCommandInput('text_density', 'density', 'density', 1, True) - tab2ChildInputs.addTextBoxCommandInput('text_material', 'material', 'material', 1, True) - tableInput = tab2ChildInputs.addTableCommandInput('table', 'Table', 4, '1:1:1:1') + part_details_children.addTextBoxCommandInput('text_id', 'ID', '', 1, True) + part_details_children.addTextBoxCommandInput('text_name', 'Name', '', 1, True) + part_details_children.addTextBoxCommandInput('text_description', 'Description', '', 1, True) + part_details_children.addTextBoxCommandInput('text_opacity', 'Opacity', '', 1, True) + part_details_children.addTextBoxCommandInput('text_partNumber', 'Part Number', '', 1, True) + part_details_children.addTextBoxCommandInput('text_area', 'Area', '', 1, True) + part_details_children.addTextBoxCommandInput('text_volume', 'Volume', '', 1, True) + part_details_children.addTextBoxCommandInput('text_mass', 'Mass', '', 1, True) + part_details_children.addTextBoxCommandInput('text_density', 'Density', '', 1, True) + part_details_children.addTextBoxCommandInput('text_material', 'Material', '', 1, True) + tableInput = part_details_children.addTableCommandInput('table', 'Table', 4, '1:1:1:1') tableInput.isFullWidth = True tableInput.tablePresentationStyle = 2 @@ -107,31 +117,31 @@ def on_create(self, command, inputs): selectInput.addSelectionFilter('Occurrences') selectInput.setSelectionLimits(1, 1) # Buttons - tab1ChildInputs.addBoolValueInput('button_create', 'create part', False, 'commands/resources/ButtonCreate', True) - tab1ChildInputs.addBoolValueInput('button_refresh', 'refresh Information', False, 'commands/resources/SendOnlineState', True) + tab1ChildInputs.addBoolValueInput('button_create', 'Create part', False, 'commands/resources/ButtonCreate', True) + tab1ChildInputs.addBoolValueInput('button_refresh', 'Refresh', False, 'commands/resources/SendOnlineState', True) # TextInputs for InvenTree grpCmdInput1 = tab1ChildInputs.addGroupCommandInput('grp_1', 'General') grp1ChildInputs = grpCmdInput1.children # img = tab1ChildInputs.addImageCommandInput('text_part_image', 'image', 'blank_image.png') # img.isVisible = False # TODO implement - grp1ChildInputs.addTextBoxCommandInput('text_part_name', 'name', 'name', 1, False) - grp1ChildInputs.addTextBoxCommandInput('text_part_ipn', 'IPN', 'IPN', 1, False) - grp1ChildInputs.addTextBoxCommandInput('text_part_description', 'description', 'description', 1, False) - grp1ChildInputs.addTextBoxCommandInput('text_part_notes', 'note', 'note', 2, False) - grp1ChildInputs.addTextBoxCommandInput('text_part_keywords', 'keywords', 'keywords', 1, False) - grp1ChildInputs.addTextBoxCommandInput('text_part_category', 'category', 'category', 1, True) + grp1ChildInputs.addTextBoxCommandInput('text_part_name', 'Name', '', 1, False) + grp1ChildInputs.addTextBoxCommandInput('text_part_ipn', 'IPN', '', 1, False) + grp1ChildInputs.addTextBoxCommandInput('text_part_description', 'Description', '', 1, False) + grp1ChildInputs.addTextBoxCommandInput('text_part_notes', 'Note', '', 2, False) + grp1ChildInputs.addTextBoxCommandInput('text_part_keywords', 'Keywords', '', 1, False) + grp1ChildInputs.addTextBoxCommandInput('text_part_category', 'Category', 'category', 1, True) grp1ChildInputs.addTextBoxCommandInput('text_part_link', '', 'linktext', 1, True) grpCmdInput2 = tab1ChildInputs.addGroupCommandInput('grp_2', 'Settings') grp2ChildInputs = grpCmdInput2.children - grp2ChildInputs.addBoolValueInput('bool_part_virtual', 'virtual', True) - grp2ChildInputs.addBoolValueInput('bool_part_template', 'template', True) - grp2ChildInputs.addBoolValueInput('bool_part_assembly', 'assembly', True) - grp2ChildInputs.addBoolValueInput('bool_part_component', 'component', True) - grp2ChildInputs.addBoolValueInput('bool_part_trackable', 'trackable', True) - grp2ChildInputs.addBoolValueInput('bool_part_purchaseable', 'purchaseable', True) - grp2ChildInputs.addBoolValueInput('bool_part_salable', 'salable', True) + grp2ChildInputs.addBoolValueInput('bool_part_virtual', 'Virtual', True) + grp2ChildInputs.addBoolValueInput('bool_part_template', 'Template', True) + grp2ChildInputs.addBoolValueInput('bool_part_assembly', 'Assembly', True) + grp2ChildInputs.addBoolValueInput('bool_part_component', 'Component', True) + grp2ChildInputs.addBoolValueInput('bool_part_trackable', 'Trackable', True) + grp2ChildInputs.addBoolValueInput('bool_part_purchaseable', 'Purchasable', True) + grp2ChildInputs.addBoolValueInput('bool_part_salable', 'Salable', True) grpCmdInput3 = tab1ChildInputs.addGroupCommandInput('grp_3', 'Supply') grp3ChildInputs = grpCmdInput3.children @@ -149,54 +159,8 @@ def on_create(self, command, inputs): helpers.error() # cstm fnc - @apper.lib_import(config.lib_path) - def part_create(self, occ, cat): - """ create part based on occurence """ - from inventree.part import Part - - ao = apper.AppObjects() - - # build up args - part_kargs = { - 'name': occ.component.name, - 'description': occ.component.description if occ.component.description else 'None', - 'IPN': occ.component.partNumber, - 'active': True, - 'virtual': False, - } - # add category if set - if cat: - part_kargs.update({'category': cat.pk}) - # create part itself - part = Part.create(functions.inv_api(), part_kargs) - # check if part created - else raise error - if not part: - ao.ui.messageBox('Error occured during API-call') - return - elif not part.pk: - error_detail = [f'{a}\n{b[0]}' for a, b in part._data.items()] - ao.ui.messageBox(f'Error occured:

{"
".join(error_detail)}') - return - - Fusion360Parameters.ID.value.create_parameter(part, occ.component.id) - Fusion360Parameters.AREA.value.create_parameter(part, occ.physicalProperties.area) - Fusion360Parameters.VOLUME.value.create_parameter(part, occ.physicalProperties.volume) - Fusion360Parameters.MASS.value.create_parameter(part, occ.physicalProperties.mass) - Fusion360Parameters.DENSITY.value.create_parameter(part, occ.physicalProperties.density) - - if occ.component.material and occ.component.material.name: - Fusion360Parameters.MATERIAL.value.create_parameter(part, occ.component.material.name) - - axis = ['x', 'y', 'z'] - bb_min = {a: getattr(occ.boundingBox.minPoint, a) for a in axis} - bb_max = {a: getattr(occ.boundingBox.maxPoint, a) for a in axis} - bb = {a: bb_max[a] - bb_min[a] for a in axis} - - Fusion360Parameters.BOUNDING_BOX_WIDTH.value.create_parameter(part, bb["x"]) - Fusion360Parameters.BOUNDING_BOX_HEIGHT.value.create_parameter(part, bb["y"]) - Fusion360Parameters.BOUNDING_BOX_DEPTH.value.create_parameter(part, bb["z"]) - - return part + def part_create(self, occ: adsk.fusion.Occurrence, cat: str): + return helpers.create_f360_part(occ, cat) def part_refresh(self, occ, inp, part): """ updates PartInfo command-inputs with values for supplied parts """ diff --git a/python/InvenTreeLink/commands/GenerateBomCommand.py b/python/InvenTreeLink/commands/GenerateBomCommand.py new file mode 100644 index 0000000..488beb6 --- /dev/null +++ b/python/InvenTreeLink/commands/GenerateBomCommand.py @@ -0,0 +1,107 @@ +import adsk.core +import adsk.fusion +import adsk.cam + +import json +from datetime import datetime + +from ..apper import apper +from .. import config +from .. import functions + +import threading + +# Loads the Bill of Materials from the current Fusion360 design. +class GenerateBomCommand(apper.Fusion360CommandBase): + def on_execute(self, command: adsk.core.Command, command_inputs: adsk.core.CommandInputs, args, input_values): + try: + ao = apper.AppObjects() + design = ao.product + + if not design: + ao.ui.messageBox('No active design', 'Extract BOM') + return + + root = design.rootComponent + + # Get Reference to Palette + ao = apper.AppObjects() + palette = ao.ui.palettes.itemById(config.ITEM_PALETTE) + + # Send message to the HTML Page + def create_bom_thread(): + loading_html = ( + '


' + + '
' + '
' + 'Loading...' + '
' + '
' + + '
' + '

Retrieving BOM...

' + '
' + ) + + palette.sendInfoToHTML( + config.DEF_SEND_BOM, + loading_html + ) + + start = datetime.now() + bom = functions.extract_bom() + + element_synced = " Synced " + element_not_synced = " Not synced " + + header_html = ( + f"

{root.name}

" + ) + + body = ''.join([ + ( + "" + f"{item['IPN']} | {item['name']}" + f"{item['instances']}" + f"{element_synced if item['part'] else element_not_synced}" + "" + ) + for item in bom + ]) + + table_html = ( + "
" + "" + "" + "" + "" + "" + "" + "" + "" + "" + f"{body}" + "" + "
Name Count Synced
" + "
" + ) + + complete_html = ( + f"{header_html}" + f"

{len(bom)} parts found in {datetime.now() - start}

" + f"{table_html}" + "" + ) + + palette.sendInfoToHTML( + config.DEF_SEND_BOM, + json.dumps(complete_html) + ) + + #create_bom_thread() + t = threading.Thread(target=create_bom_thread, args=()) + t.start() + except Exception as _e: + config.app_tracking.capture_exception(_e) + raise _e diff --git a/python/InvenTreeLink/commands/ImportPartCommand.py b/python/InvenTreeLink/commands/ImportPartCommand.py new file mode 100644 index 0000000..da6bcca --- /dev/null +++ b/python/InvenTreeLink/commands/ImportPartCommand.py @@ -0,0 +1,23 @@ +import adsk.core +import adsk.fusion +import adsk.cam + +import os +from datetime import datetime + +from ..apper import apper +from .. import functions +from .. import helpers +from .. import config + + +class ImportPartCommand(apper.Fusion360CommandBase): + + @apper.lib_import(config.lib_path) + def on_execute(self, command: adsk.core.Command, command_inputs: adsk.core.CommandInputs, args, input_values): + try: + ao = apper.AppObjects() + + except Exception as _e: + config.app_tracking.capture_exception(_e) + raise _e diff --git a/python/InvenTreeLink/commands/SendStepCommand.py b/python/InvenTreeLink/commands/ImportStlCommand.py similarity index 92% rename from python/InvenTreeLink/commands/SendStepCommand.py rename to python/InvenTreeLink/commands/ImportStlCommand.py index ebe1067..58e6f28 100644 --- a/python/InvenTreeLink/commands/SendStepCommand.py +++ b/python/InvenTreeLink/commands/ImportStlCommand.py @@ -11,7 +11,7 @@ from .. import config -class SendStepCommand(apper.Fusion360CommandBase): +class ImportStlCommand(apper.Fusion360CommandBase): @apper.lib_import(config.lib_path) def on_execute(self, command: adsk.core.Command, command_inputs: adsk.core.CommandInputs, args, input_values): @@ -20,8 +20,6 @@ def on_execute(self, command: adsk.core.Command, command_inputs: adsk.core.Comma try: ao = apper.AppObjects() - ao.ui.messageBox("STEP FILE") - if ao.ui.activeSelections.count == 1: occ = adsk.fusion.Occurrence.cast(ao.ui.activeSelections[0].entity) @@ -35,8 +33,6 @@ def on_execute(self, command: adsk.core.Command, command_inputs: adsk.core.Comma self._write_step(temp_path, occ.component) - ao.ui.messageBox("File at {}, size: {}".format(temp_path, os.path.getsize(temp_path))) - part = functions.inventree_get_part(occ.component.id) if part is False: diff --git a/python/InvenTreeLink/commands/SendBomCommand.py b/python/InvenTreeLink/commands/SendBomCommand.py deleted file mode 100644 index 359d277..0000000 --- a/python/InvenTreeLink/commands/SendBomCommand.py +++ /dev/null @@ -1,47 +0,0 @@ -import adsk.core -import adsk.fusion -import adsk.cam - -import json -from datetime import datetime - -from ..apper import apper -from .. import config -from .. import functions - - -class SendBomCommand(apper.Fusion360CommandBase): - def on_execute(self, command: adsk.core.Command, command_inputs: adsk.core.CommandInputs, args, input_values): - try: - # Get Reference to Palette - ao = apper.AppObjects() - palette = ao.ui.palettes.itemById(config.ITEM_PALETTE) - - # Send message to the HTML Page - if palette: - palette.sendInfoToHTML( - config.DEF_SEND_BOM, - '


Loading...
' - ) - - start = datetime.now() - config.BOM = functions.extract_bom() - config.BOM_HIR = functions.make_component_tree() - body = ''.join(['%s%s' % (a['name'], a['instances']) for a in config.BOM]) - table_c = '
{body}
NameCount
'.format(body=body) - - palette.sendInfoToHTML( - config.DEF_SEND_BOM, - '

{nbr} parts found in {time}

{table}'.format( - nbr=len(config.BOM), - table=table_c, - time=datetime.now() - start - ) - ) - palette.sendInfoToHTML( - 'SendTree', - json.dumps(config.BOM_HIR) - ) - except Exception as _e: - config.app_tracking.capture_exception(_e) - raise _e diff --git a/python/InvenTreeLink/commands/SendBomOnlineCommand.py b/python/InvenTreeLink/commands/SendBomOnlineCommand.py deleted file mode 100644 index 45de1bb..0000000 --- a/python/InvenTreeLink/commands/SendBomOnlineCommand.py +++ /dev/null @@ -1,40 +0,0 @@ -import adsk.core -import adsk.fusion -import adsk.cam - -from ..apper import apper -from .. import config -from .. import functions - - -class SendBomOnlineCommand(apper.Fusion360CommandBase): - def on_execute(self, command: adsk.core.Command, command_inputs: adsk.core.CommandInputs, args, input_values): - try: - # Get Reference to Palette - ao = apper.AppObjects() - palette = ao.ui.palettes.itemById(config.ITEM_PALETTE) - - # Send message to the HTML Page - if palette: - palette.sendInfoToHTML( - config.DEF_SEND_BOM, - '


Loading...
' - ) - - # Work with it - inv_status = functions.inventree_get_part([a['id'] for a in config.BOM]) - for a in config.BOM: - a['status'] = inv_status[a['id']] - - body = ''.join( - ['%s%s%s' % (a['name'], a['instances'], a['status']) for a in config.BOM] - ) - table = '
{body}
NameCountIs InvenTree
'.format(body=body) - - palette.sendInfoToHTML( - config.DEF_SEND_BOM, - '{table}'.format(table=table) - ) - except Exception as _e: - config.app_tracking.capture_exception(_e) - raise _e diff --git a/python/InvenTreeLink/commands/palette_html/palette.html b/python/InvenTreeLink/commands/palette_html/palette.html index 54453a2..f97aaf1 100644 --- a/python/InvenTreeLink/commands/palette_html/palette.html +++ b/python/InvenTreeLink/commands/palette_html/palette.html @@ -1,83 +1,108 @@ - - - - - + + + - - - - - -
-
-
-
-

InvenTreeBom more Information -

-
-
-
-

Check the github for more information

-
+ + + + + + + + + +
+
+
+
+

InvenTreeBom more Information +

+
+
+
+

Check the github for more + information

+
-