Skip to content

Commit

Permalink
Functional to print out PropSim data, and validate units. Doesn't ch…
Browse files Browse the repository at this point in the history
…ange EntryVAr color when unit is bad, and only has SimulateLiquid atm.
  • Loading branch information
newpomax committed Feb 27, 2021
1 parent d82aa3c commit b72530b
Show file tree
Hide file tree
Showing 24 changed files with 1,249 additions and 34 deletions.
12 changes: 12 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Compiled source #
###################
*.com
*.class
*.dll
*.exe
*.o
*.so

# Pycaches #
############
__pycache__/
62 changes: 31 additions & 31 deletions Outputs/F_thrust_RASAERO.txt
Original file line number Diff line number Diff line change
@@ -1,32 +1,32 @@
; Name diameter(mm) Length(mm) delay propellant_weight(kg) mass(kg)
O31100-I 143 2438 P 17.09 29.03 SSI
0.010 16.636
0.560 3829.721
1.110 3760.421
1.670 3696.314
2.220 3629.055
2.770 3565.833
3.320 3499.594
3.870 3431.874
4.420 3364.197
4.980 3298.928
5.530 3232.819
6.080 3160.431
6.630 3093.359
7.180 3024.405
7.730 2953.992
8.290 2878.338
8.840 2450.623
9.390 866.595
9.940 593.645
10.490 377.097
11.040 206.774
11.600 90.791
12.150 54.471
12.700 111.568
13.250 65.152
13.800 23.593
14.350 8.281
14.910 11.829
15.460 10.580
16.010 0.000
O6900-I 143 2438 P 3.85 15.79 SSI
0.010 1320.418
0.200 1561.631
0.400 1546.229
0.590 1531.306
0.790 1516.129
0.980 1500.406
1.180 1482.341
1.370 1466.933
1.570 1451.104
1.760 1434.491
1.960 1417.684
2.150 1401.527
2.350 1384.385
2.540 1368.230
2.740 1352.983
2.930 1337.996
3.130 1322.032
3.320 1307.148
3.520 1290.261
3.710 1275.134
3.910 1259.703
4.100 1245.385
4.300 1230.102
4.490 1215.732
4.690 1200.258
4.880 490.540
5.080 428.407
5.270 376.835
5.470 329.905
5.655 0.000
5 changes: 5 additions & 0 deletions PropSim.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from PythonLib.MainWindow import MainWindow

if __name__ == '__main__':
mainwindow = MainWindow() # construct the MainWindow
mainwindow.run() # run the MainWindow
80 changes: 80 additions & 0 deletions PythonLib/EntryVar.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
'''
EntryVar:
An InputVar class for a string-input value. Implements conversions to baseunit from the string-parsed unit before sending to MATLAB
(assumes that the input unit has already been validated).
The ToggleVar has two additional optional argument: linkedvars and disableon. If linkedvars is a list of InputVar items,
those items will be disabled whenever the ToggleVar is in the disableon state.
'''

import tkinter as tk
import tkinter.ttk as ttk
from .InputVar import InputVar
from .units import units

class EntryVar(InputVar):
def __init__(self, name, defaultval, baseunit, structname = None, description = ''):
super().__init__(name, defaultval, baseunit, structname, description) # use parent constructor

self.numeric_val = None # the numeric value parsed from the input string, set in self.parse_value
self.parsed_unit = None # the unit parsed from the input string, set in self.parse_value

def get(self):
''' Accesses the current value of self.var and returns it. '''
return self.var.get()

def put(self, val):
''' Set current value of self.var. '''
self.var.set(val)

def get_type(self):
''' Return the base type of this input var. '''
return self.baseunit

def build(self, matlabeng):
''' Build this variable in the MATLAB engine's workspace. '''
if self.numeric_val is None:
myval = 0 # if entry is disabled, just plug in a dummy value (won't matter)
else:
myval = units.convert(self.numeric_val, self.parsed_unit, self.baseunit) # convert to baseunit
matlabeng.eval(self.structname + '.' + self.name + '=' + str(myval) + ' ;', nargout = 0) # use eval to make struct variable in MATLAB workspace

def makewidget(self, parent):
''' Create the tk widget for this input, using parent as the parent widget. '''
self.var = tk.StringVar(parent, self.defaultval) # a Tk variable storing current status of this variable
self.widget = ttk.Entry(parent, style = 'EntryVar.TEntry', textvariable=self.var, validate='focus', validatecommand=lambda *a: 'invalid' not in self.widget.state())
self.widget.bind("<FocusOut>", lambda e: self.validate())
self.widget.bind("<FocusIn>", lambda e: self.on_entry())

def validate(self):
''' Parse the user input, separating it into a value and a unit. Set the self.numeric_val and self.parsed_unit vars based on findings. '''
# basic string format is <value> [<unit>] or just <value>. Handle both cases:
if 'disabled' in self.widget.state():
return True # if entry is disabled, don't bother validating
splitstr = self.get()
splitstr = splitstr.strip('] ') # remove closing brace and leading/trailing whitespace
splitstr = splitstr.split('[') # split into a list
try:
self.numeric_val = float(splitstr[0].strip())
except:
self.set_err()
return "Error: Variable " + self.name + " has a non-float value: " + self.splitstr[0]
if len(splitstr) > 1: # if a unit was included
if units.validate_units(splitstr[1].strip(), self.baseunit):
self.parsed_unit = splitstr[1]
return True
else:
self.set_err()
return "Error: Variable " + self.name + " has a unit incompatible with the base unit. \n" + "\t Base unit is [" + self.baseunit + "] and compatible units are: " + ', '.join(units.get_compatible_units(self.baseunit))
else:
self.parsed_unit = self.baseunit # if no unit was provided, assume the base unit
return True # if you got here, you're good

def set_err(self):
''' Change the style to reflect that an error was found during parsing. '''
self.widget.state(['invalid'])

def on_entry(self):
''' Change the style back to the default when the user clicks in this entry. This stops error color-changes from being permanent. '''
self.widget.state(['!invalid'])
54 changes: 54 additions & 0 deletions PythonLib/GasVar.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
'''
GasVar:
An InputVar class for a Gas object (see the Matlab code for Gas.m in Supporting Files). Users select from a dropdown of defined
gases. To add gases, they must be manually included in the gas_library dict below.
TODO: Add more common gases!
'''

import tkinter as tk
import tkinter.ttk as ttk
from .InputVar import InputVar

# gases available in GasVar dropdown - cv is in J/kg/K, molecular mass in kg/mol
gas_library = {'nitrogen' :{'c_v': 743.0, 'molecular_mass':0.0280134},
'helium' :{'c_v':3.12, 'molecular_mass': 0.0040026}
}

class GasVar(InputVar):
def __init__(self, name, defaultval, baseunit='gas', structname = None, description = ''):
super().__init__(name, defaultval, baseunit, structname, description) # use parent constructor

def get(self):
''' Accesses the current value of self.var and returns it. '''
return self.var.get()

def put(self, val):
''' Set current value of self.var. '''
self.var.set(val)

def get_type(self):
''' Return the base type of this input var. '''
return self.baseunit

def build(self, matlabeng):
''' Build this variable in the MATLAB engine's workspace. '''
''' NOTE: This assumes that a Pressurant object has already been created and assigned to the struct name. '''
mygas = self.get() # get the name of the gas from the gas library to be used
matlabeng.eval(self.name+' = Gas();' ,nargout=0) # create gas object
matlabeng.eval(self.name+'.c_v = ' + str(gas_library[mygas]['c_v']) + ' ;', nargout=0)
matlabeng.eval(self.name+'.molecular_mass = ' + str(gas_library[mygas]['molecular_mass']) + ' ;', nargout=0)
matlabeng.eval(self.structname+'.gas_properties = '+self.name+' ;', nargout=0)

def makewidget(self, parent):
''' Create the tk widget for this input, using parent as the parent widget. '''
self.var = tk.StringVar(parent, self.defaultval) # a Tk variable storing current status of this variable (0/False is off, 1/True is on)
self.widget = ttk.OptionMenu(parent, self.var, self.defaultval, *(gas_library.keys())) # create dropdown of gas names

def validate(self):
''' Determine if the selection is valid. '''
if self.get() in gas_library.keys(): # should always be true, as the user has selected from a dropdown
return True
else:
return "You have selected an invalid gas for " + self.name
43 changes: 43 additions & 0 deletions PythonLib/InputPane.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
'''
InputPane:
A ttk.Notebook of SimPages, allowing the user to switch between which MATLAB function they want to use.
If a page has a solution, users will be asked if they wish to save before switching tabs using SimPage.promptforsave() .
This class also owns the Save and Save As methods, and directs them to the appropriate SimPage based on which is selected.
'''

import tkinter as tk
import tkinter.ttk as ttk

class InputPane(ttk.Notebook):
def __init__(self, mainframe, mainwindow, matlabeng, simpages):
super().__init__(mainframe)
self.mainframe = mainframe
self.mainwindow = mainwindow
self.matlabeng = matlabeng
self.simpages = simpages

self.curr_tab = 0 # index of current tab

for page in simpages:
page.makewidget(self, self.matlabeng) # create each SimPage with self as the parent
self.add(page, text = page.name) # create a tab for this frame, with name as the tab name

#self.bind("<<NotebookTabChanged>>", lambda event: self.ontabchange() )

def promptsave_sim(self):
self.simpages[self.curr_tab].promptforsave(self.matlabeng)

def save_sim(self):
self.simpages[self.curr_tab].saveworkspace(self.matlabeng)

def saveas_sim(self):
self.simpages[self.curr_tab].saveworkspaceas(self.matlabeng)

def ontabchange(self):
if self.simpages[self.curr_tab].promptforsave(self.matlabeng): # prompt for SimPage save on the tab you're switching from
self.curr_tab = self.index(self.select()) # if saved or rejected option, set new tab
else:
self.select(self.curr_tab) # if canceled or closed window, go back to previous tab
78 changes: 78 additions & 0 deletions PythonLib/InputVar.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
'''
InputVar:
An abstract base class containing information about an input variable. Input variables have the following properties:
- a name, corresponding to the name of the matlab variable they modify
- a widget, corresponding to a tK widget object created on init
- a value, which can be retrieved using the get() function or set using put() (initially set to a user-defined default)
- a baseunit, which is either a unit string (e.g. 'kg', 'ft', or 'psi') indicating the unit of the MATLAB var, 'boolean', 'file', or 'gas', accessed with get_type()
- a struct name, corresponding to which struct in MATLAB this variable belongs (None or '' means the variable isn't part of a struct)
In addition to the methods described above, InputVars should have a build(matlabeng) function that constructions that variable in the workspace
of the matlab engine passed as a function argument. They should also have a makewidget(parent) function that constructs a tkinter widget, using the
passed argument as the parent widget. Finally, inheritors should have a validate() function that confirms if the user input for that value is consistent
(return a string describing the err if not).
All children inherit the function make_tooltip(widget), which creates a tooltip linked to the widget passed as an argument. When users hover their mouse over
that widget, a pop-up window appears providing a description of that InputVar.
'''
from .ToolTip import ToolTip

MAX_TOOLTIP_LINELEN = 40

class InputVar():
def __init__(self, name, defaultval, baseunit, structname = None, description = ''):
self.name = name
self.defaultval = defaultval
self.baseunit = baseunit
self.structname = structname
self.description = description
self.var = None # a Tk variable storing current status of this variable
self.widget = None # a Tk widget
self.tooltip = None # a Tooltip object, initialized with maketooltip()

def get(self):
''' Accesses the current value of self.var and returns it. '''
raise NotImplementedError()

def put(self, val):
''' Set current value of self.var. '''
raise NotImplementedError()

def get_type(self):
''' Return the base type of this input var. '''
return self.baseunit

def build(self, matlabeng):
''' Build this variable in the MATLAB engine's workspace. '''
raise NotImplementedError()

def makewidget(self, parent):
''' Create the tk widget for this input, using parent as the parent widget. '''
raise NotImplementedError()

def validate(self):
''' Confirm whether the user input is valid or not. '''
raise NotImplementedError()

def maketooltip(self, tip_widget):
''' Create a tooltip that is linked to a tip_widget. '''
if self.get_type().lower() not in ('gas','boolean','file'):
descrip_str = self.name + ' ( unit: [' + self.get_type() + '] )\n'
else:
descrip_str = self.name + ' ( type: ' + self.get_type() + ' )\n'
descrip_str += '-'*len(descrip_str)+ '\n'
split_desc = self.description.split(' ') # split description by spaces
curr_line_len = 0
while split_desc: # while words remain in list
nextword = split_desc.pop(0)
if curr_line_len + len(nextword) < MAX_TOOLTIP_LINELEN:
descrip_str += ' ' + nextword # if room on line, add next word (with space before it)
curr_line_len += len(nextword)
else:
descrip_str += '\n' # if no room on line, end line
descrip_str += nextword # add word
curr_line_len = len(nextword) # line is now this long

self.tooltip = ToolTip(tip_widget, descrip_str) # use ToolTip constructor
Loading

0 comments on commit b72530b

Please sign in to comment.