sqlite3 availability in Python add-ins

sqlite3 availability in Python add-ins

jesse
Enthusiast Enthusiast
1,283 Views
9 Replies
Message 1 of 10

sqlite3 availability in Python add-ins

jesse
Enthusiast
Enthusiast

I am finding that I need a local database to store data for my add-in.  I see that Python's standard library has the sqlite3 package which provides an interface into sqlite.  It looks like on my copy of Fusion (macOS located in the United States), I can import sqlite3, read/write databases, etc.

 

Can I rely on this being available long term, for all Fusion users who might use my Python add-in?

 

Thanks!

 

Jesse

Accepted solutions (1)
1,284 Views
9 Replies
Replies (9)
Message 2 of 10

goyals
Autodesk
Autodesk
Accepted solution

Hi @jesse, sqlite3 is part of python standard package so it should be OK to use until for some reason Python.org decided to take it out from standard package. 



Shyam Goyal
Sr. Software Dev. Manager
Message 3 of 10

joergDVRXK
Contributor
Contributor

Hi, did you get sqlite3 to run?

 

I'am getting errors no matter where I place the database when trying to connect with the same parameters that work in a pycharm environment. I'd be glad for some advice:

 

... Library/Application Support/Autodesk/Autodesk Fusion 360/API/Scripts/0sqltest/0sqltest.py", line 63, in show_airfoils

conn = sqlite3.connect('airfoil_data.db')

^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

sqlite3.OperationalError: unable to open database file

 

 

Best Regards Jörg

0 Likes
Message 4 of 10

Jorge_Jaramillo
Collaborator
Collaborator

Hi,

 

I made a test and it worked in my environment with the next script:

import adsk, adsk.core, adsk.fusion, adsk.cam, traceback
import sqlite3

app = adsk.core.Application.get()
TEST_DATABASE_FILE = "/99tmp/test_db.sqlite"

def test_connect() -> None:
    try:
        conn = sqlite3.connect(TEST_DATABASE_FILE)
        app.log(f'{conn=}')
        conn.close()
    except:
        app.log(f'Failed: {traceback.format_exc()}')

def test_create_table_letters() -> None:
    try:
        CREATE_TABLE_LETTERS =  """create table if not exists letters (
                                    idx integer primary key,
                                    letter text not null
                                );"""
        with sqlite3.connect(TEST_DATABASE_FILE) as con:
            cursor = con.cursor()
            cursor.execute(CREATE_TABLE_LETTERS)
            app.log(f'{cursor=}')
    except:
        app.log(f'Failed: {traceback.format_exc()}')

def run(context) -> None:
    test_connect()
    test_create_table_letters()

 

I believe your problem is in the filename. You should provide a full path filename, and I'd suggest to located it outside the Fusion's installation directory.

 

This test was made with:

Fusion:app.version='2.0.20256'

Python:sys.version='3.12.4 (tags/v3.12.4:8e8a4ba, Jun 6 2024, 19:30:16) [MSC v.1940 64 bit (AMD64)]'

 

Regards,

Jorge Jaramillo

 

Message 5 of 10

joergDVRXK
Contributor
Contributor
Dear Jorge,

thanks a lot! Now it works on my windows machine, there seems to be something configured wrong with permissions on my mac.
I now created Dropdown Lists from the database. Adding and deleting Items by ui works so far but gets visuable only after restarting the skript. Is there a possibility to reststart handlers from within the skript or restart the skript?

Best regards Jörg
0 Likes
Message 6 of 10

Jorge_Jaramillo
Collaborator
Collaborator

Hi,

I don't get the point where you said you need to re-start the script.

Can you share your code where you add the items to the dropdown list?

 

0 Likes
Message 7 of 10

joergDVRXK
Contributor
Contributor

Hi Jorge,

 

I experimented on basis of the ui sampleskript. The dropdown Items (list of airfoilcoordinates) are addet from sql database (path in line 8) in lines 134-135. Adding and deleting are managed by clickbuttons. The commands for input change are in lines 47ff. I didn't find out, how to update the dropdownItem list in CommandCreatedHandler from the CommandInputChangeHandler.

 

Best regards Jörg

 

 

import adsk.core, adsk.fusion, traceback
import sqlite3

_app = None
_ui  = None
_rowNumber = 0

DATABASE = 'C:/Users/<username>/airfoil_data.db'

# Global set of event handlers to keep them referenced for the duration of the command
_handlers = []

# Adds a new row to the table.
def addRowToTable(tableInput):
    global _rowNumber
    # Get the CommandInputs object associated with the parent command.
    cmdInputs = adsk.core.CommandInputs.cast(tableInput.commandInputs)
    
    # Create three new command inputs.
    valueInput = cmdInputs.addValueInput('TableInput_value{}'.format(_rowNumber), 'Value', 'cm', adsk.core.ValueInput.createByReal(_rowNumber))
    stringInput =  cmdInputs.addStringValueInput('TableInput_string{}'.format(_rowNumber), 'String', str(_rowNumber))
    spinnerInput = cmdInputs.addIntegerSpinnerCommandInput('spinnerInt{}'.format(_rowNumber), 'Integer Spinner', 0 , 100 , 2, int(_rowNumber))
    
    # Add the inputs to the table.
    row = tableInput.rowCount
    tableInput.addCommandInput(valueInput, row, 0)
    tableInput.addCommandInput(stringInput, row, 1)
    tableInput.addCommandInput(spinnerInput, row, 2)
    
    # Increment a counter used to make each row unique.
    _rowNumber = _rowNumber + 1

# Event handler that reacts to any changes the user makes to any of the command inputs.
class MyCommandInputChangedHandler(adsk.core.InputChangedEventHandler):
    def __init__(self):
        super().__init__()
    def notify(self, args):
        
        global foil_id
        
        try:
            eventArgs = adsk.core.InputChangedEventArgs.cast(args)
            inputs = eventArgs.inputs
            cmdInput = eventArgs.input

            # onInputChange for click Button
            if cmdInput.id == 'import':
                
                db = databaseF(DATABASE)
                db.create_database()

                dlg = _ui.createFileDialog()
                dlg.title = 'Open bez.dat File'
                dlg.filter = 'Airfoil bez.dat files (*.dat);;All Files (*.*)'
                if dlg.showOpen() != adsk.core.DialogResults.DialogOK:
                    return

                filename = dlg.filename
                
                test2 = db.import_airfoil_data(filename)
                db.add_airfoil(str(test2[0]), str(test2[1]), str(test2[2]))
                temp = cmdInput.id == 'Airfoils'
                airfoil_list = db.show_airfoils()
            
            if cmdInput.id == 'Airfoils':
                
                objectItems = cmdInput.selectedItem
                selection_name = objectItems.name
                split = selection_name.split(".")
                foil_id = str(split[0])


            if cmdInput.id == 'delete':
                
                db = databaseF(DATABASE)
                db.create_database()
                db.delete_airfoil(foil_id)
          
        except:
            _ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))


# Event handler that reacts to when the command is destroyed. This terminates the script.            
class MyCommandDestroyHandler(adsk.core.CommandEventHandler):
    def __init__(self):
        super().__init__()
    def notify(self, args):
        try:
            # When the command is done, terminate the script
            # This will release all globals which will remove all event handlers
            adsk.terminate()
        except:
            _ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))


# Event handler that reacts when the command definitio is executed which
# results in the command being created and this event being fired.
class MyCommandCreatedHandler(adsk.core.CommandCreatedEventHandler):
    def __init__(self):
        super().__init__()
    def notify(self, args):
        try:
            # Get the command that was created.
            cmd = adsk.core.Command.cast(args.command)

            # Connect to the command destroyed event.
            onDestroy = MyCommandDestroyHandler()
            cmd.destroy.add(onDestroy)
            _handlers.append(onDestroy)

            # Connect to the input changed event.           
            onInputChanged = MyCommandInputChangedHandler()
            cmd.inputChanged.add(onInputChanged)
            _handlers.append(onInputChanged)    

            # Get the CommandInputs collection associated with the command.
            inputs = cmd.commandInputs

            # Create a tab input.
            tabCmdInput1 = inputs.addTabCommandInput('tab_1', 'Tab 1')
            tab1ChildInputs = tabCmdInput1.children

            # Create bool value input with button style that can be clicked.
            tab1ChildInputs.addBoolValueInput('import', 'import', False, "", True)
            tab1ChildInputs.addBoolValueInput('delete', 'delete', False, "", True)

            # Create dropdown input with test list style.
            dropdownInput = tab1ChildInputs.addDropDownCommandInput('Airfoils', 'Airfoils', adsk.core.DropDownStyles.TextListDropDownStyle);
            DROPDOWN_ITEMS = dropdownInput.listItems
            
            db = databaseF(DATABASE)
            db.create_database()
            airfoil_list = db.show_airfoils()
            for i in range(len(airfoil_list)):
                DROPDOWN_ITEMS.add(str(airfoil_list[i][0]) + " ." + str(airfoil_list[i][1]), True, '')

        except:
            _ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))


def run(context):
    try:
        global _app, _ui
        _app = adsk.core.Application.get()
        _ui = _app.userInterface

        cmdDef = _ui.commandDefinitions.itemById('cmdInputsSample')
        if not cmdDef:
            cmdDef = _ui.commandDefinitions.addButtonDefinition('cmdInputsSample', 'Command Inputs Sample', 'Sample to demonstrate various command inputs.')

        onCommandCreated = MyCommandCreatedHandler()
        cmdDef.commandCreated.add(onCommandCreated)
        _handlers.append(onCommandCreated)

        db = databaseF(DATABASE)
        db.create_database()
        cmdDef.execute()
        adsk.autoTerminate(False)
    except:
        if _ui:
            _ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))




class databaseF():
    def __init__(self, db):
        self.db = db
     

    def create_database(self):
        conn = sqlite3.connect(self.db)
        c = conn.cursor()
        c.execute('''
            CREATE TABLE IF NOT EXISTS airfoils (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                name TEXT NOT NULL,
                coordinates_top TEXT NOT NULL,
                coordinates_bottom TEXT NOT NULL

            )
        ''')
        conn.commit()
        conn.close()

    # airfoil in die Datenbank hinzufügen (name -> str, coords -> array as a str "[(0,0),...]"
    def add_airfoil(self, name, coordinates_top, coordinates_bottom):
        conn = sqlite3.connect(self.db)
        c = conn.cursor()
        c.execute("INSERT INTO airfoils (name, coordinates_top, coordinates_bottom) VALUES (?, ?, ?)",
                (name, coordinates_top, coordinates_bottom))
        conn.commit()
        conn.close()

    # airfoil aus der Datenbank löschen
    def delete_airfoil(self, airfoil_id):
        conn = sqlite3.connect(self.db)
        c = conn.cursor()
        c.execute("DELETE FROM airfoils WHERE id = ?", (airfoil_id,))
        conn.commit()
        conn.close()

    # airfoil infos abrufen (index -> int, name -> str, top -> tupel, bottom -> tupel)
    def show_airfoils(self):
        conn = sqlite3.connect(self.db)
        c = conn.cursor()
        c.execute("SELECT * FROM airfoils")
        rows = c.fetchall()
        conn.close()
        return rows

    # Funktion, um Daten aus einer Datei im bez Format zu importieren
    def import_airfoil_data(self, filepath):
        
        with open(filepath, 'r') as file:
            lines = file.readlines()

        name = lines[0].strip()  # Der erste Eintrag ist der Name des airfoilen
        coordinates = []

        for line in lines[1:]:
            if line.strip():  # Ignoriere leere Zeilen
                try:
                    x, y = map(float, line.split())  # Zwei Werte pro Zeile erwarten
                    coordinates.append((x, y))  # (x, y) Tupel zu Liste hinzufügen

                except ValueError:
                    print("Fehler", "Ungültiges Koordinatenformat in der Datei.")
                    return None, None

        coordinates_top = [coordinates[i] for i in range(0, 10)]
        coordinates_bottom = list(reversed([coordinates[i] for i in range(9, 19)]))

        return name, coordinates_top, coordinates_bottom



 

 

 

The Importfiles look like this:

 

NACA 0008.bez
1.0000000000000000 0.0000000000000000
0.7777777777777778 0.0279374422757889
0.5833333333333334 0.0133249808131489
0.4166666666666666 0.0771256925102178
0.2777777777777778 0.0103172561882940
0.1666666666666667 0.0655471695666222
0.0833333333333333 0.0257378087502697
0.0277777777777778 0.0317317906938658
0.0000000000000000 0.0067688756070382
0.0000000000000000 0.0000000000000000
0.0000000000000000 -0.0067688756070382
0.0277777777777778 -0.0317317906938658
0.0833333333333333 -0.0257378087502697
0.1666666666666667 -0.0655471695666222
0.2777777777777778 -0.0103172561882940
0.4166666666666666 -0.0771256925102178
0.5833333333333334 -0.0103172561882940
0.7777777777777778 -0.0279374422757889
1.0000000000000000 0.0000000000000000

0 Likes
Message 8 of 10

Jorge_Jaramillo
Collaborator
Collaborator

Hi,

 

After you import or delete an item to the db, you need to update the dropdown with the items from the db.

This the new version of the Input Changed Handler function with the change:

class MyCommandInputChangedHandler(adsk.core.InputChangedEventHandler):
    def __init__(self):
        super().__init__()
    def notify(self, args):
        
        global foil_id
        
        try:
            eventArgs = adsk.core.InputChangedEventArgs.cast(args)
            inputs = eventArgs.inputs
            cmdInput = eventArgs.input

            # onInputChange for click Button
            if cmdInput.id == 'import':

                db = databaseF(DATABASE)
                db.create_database()

                dlg = _ui.createFileDialog()
                dlg.title = 'Open bez.dat File'
                dlg.filter = 'Airfoil bez.dat files (*.dat);;All Files (*.*)'
                if dlg.showOpen() != adsk.core.DialogResults.DialogOK:
                    return

                filename = dlg.filename
                
                test2 = db.import_airfoil_data(filename)
                db.add_airfoil(str(test2[0]), str(test2[1]), str(test2[2]))
                # temp = cmdInput.id == 'Airfoils'
                DROPDOWN_ITEMS: adsk.core.ListItems = eventArgs.inputs.itemById('Airfoils').listItems
                DROPDOWN_ITEMS.clear()
                airfoil_list = db.show_airfoils()
                for i in range(len(airfoil_list)):
                    DROPDOWN_ITEMS.add(str(airfoil_list[i][0]) + " ." + str(airfoil_list[i][1]), True, '')

            if cmdInput.id == 'Airfoils':
                
                objectItems = cmdInput.selectedItem
                selection_name = objectItems.name
                split = selection_name.split(".")
                foil_id = str(split[0])


            if cmdInput.id == 'delete':
                
                db = databaseF(DATABASE)
                db.create_database()
                db.delete_airfoil(foil_id)

                DROPDOWN_ITEMS: adsk.core.ListItems = eventArgs.inputs.itemById('Airfoils').listItems
                DROPDOWN_ITEMS.clear()
                airfoil_list = db.show_airfoils()
                for i in range(len(airfoil_list)):
                    DROPDOWN_ITEMS.add(str(airfoil_list[i][0]) + " ." + str(airfoil_list[i][1]), True, '')
          
        except:
            _ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))

 

It worked for me in my tests.

 

Regards,

Jorge Jaramillo

 

 

Message 9 of 10

joergDVRXK
Contributor
Contributor

Thanks a lot Jorge, works perfect!

Best regards Jörg

Message 10 of 10

joergDVRXK
Contributor
Contributor

Jorge,

the code of the entire skript can be found here if it is of interest for other users. Placing the database in user directory worked well.

 

The script has another aspect I, as a beginner, found challenging and migth be usefull to share here:

Since the api only lets you sketch degree 3 and 5 controlPoint splines as a workaround to get degree 9 controlPoint splines I sketched a degree 3 controlPoint spline with "random" points. In the next step it is set to degree nine and the ordered ControlPoints then get placed by offsetDimension to two construction lines to form the top and bottom sides of an airfoil fully controlled by parameters.

 

Best regards

Jörg