Skip to content

Commit

Permalink
Open FMEA Beta release v1.0.0
Browse files Browse the repository at this point in the history
**FMEAApp v1.0.0**

We are excited to announce the first release of FMEAApp, a comprehensive Failure Modes and Effects Analysis (FMEA) tool designed to streamline your risk management processes. This application offers a user-friendly interface with dynamic screens and an intuitive top app bar, ensuring ease of use and productivity.

**Features**

- Dynamic Screen Management: Effortlessly switch between different analysis tables with our screen manager.
- Top App Bar: Access commonly used functions with ease using icon-based buttons for home, open file, save as, undo, redo, next screen, and previous screen.
- Editable Tables: Interactive FMEA tables with editable cells for Severity, Occurrence, Detection, and action plans.
- Automated RPN Calculation: Automatic calculation and color-coded visualization of Risk Priority Numbers (RPN).
- File Operations: Seamlessly open, save, and manage FMEA tables in various formats including text, JSON, XML, and SQL.
- Database Connectivity: Ready for integration with databases to store and retrieve FMEA data.


**Installation**

Clone the Repository:
```

git clone https://github.com/yourusername/FMEAApp.git
cd FMEAApp
pip install -r requirements.txt
python fmea_app.py

```

Optional: To build the executable using PyInstaller, just build it:

python build.py

**Structure:**
FMEAApp/
├── build.py
├── fmea_app.py
├── requirements.txt
├── screens/
│   ├── home_screen.py
│   ├── table_screen1.py
│   ├── table_screen2.py
├── icons/
│   ├── home.png
│   ├── open_file.png
│   ├── save_as.png
│   ├── undo.png
│   ├── redo.png
│   ├── next.png
│   ├── previous.png
└── path/to/icon.ico

**Requirements**
Python 3.6 or higher
PIL (Pillow)
Tkinter
PyInstaller

**Known Issues**
Platform Specific: Some features may not work as expected on non-Windows platforms.
Icon Paths: Ensure that all icon paths are correctly set relative to the project directory.

**Contributing**
We welcome contributions! Please fork this repository and submit pull requests with your improvements and bug fixes.
  • Loading branch information
dromation authored Jun 5, 2024
1 parent 70094c5 commit 808e458
Show file tree
Hide file tree
Showing 38 changed files with 1,401 additions and 0 deletions.
Binary file added Ofmea_logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added __pycache__/data_handler.cpython-310.pyc
Binary file not shown.
Binary file added __pycache__/logger.cpython-310.pyc
Binary file not shown.
Binary file added __pycache__/main_app.cpython-310.pyc
Binary file not shown.
Binary file added __pycache__/menu_contents.cpython-310.pyc
Binary file not shown.
Binary file added __pycache__/table_contents.cpython-310.pyc
Binary file not shown.
11 changes: 11 additions & 0 deletions build.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# build.py
import sys
import PyInstaller.__main__

# Increase recursion limit
sys.setrecursionlimit(5000)

# Call PyInstaller with the .spec file
PyInstaller.__main__.run([
'fmea_app.spec', # if you want a windowed app without the console
])
61 changes: 61 additions & 0 deletions data_handler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import json
import xml.etree.ElementTree as ET
import sqlite3

def load_txt(filename):
with open(filename, 'r') as file:
data = [line.strip().split('\t') for line in file.readlines()]
return data

def save_txt(filename, data):
with open(filename, 'w') as file:
for row in data:
file.write('\t'.join(row) + '\n')

def load_json(filename):
with open(filename, 'r') as file:
data = json.load(file)
return data

def save_json(filename, data):
with open(filename, 'w') as file:
json.dump(data, file, indent=4)

def load_xml(filename):
tree = ET.parse(filename)
root = tree.getroot()
data = [[cell.text for cell in row] for row in root.findall('row')]
return data

def save_xml(filename, data):
root = ET.Element('data')
for row in data:
row_elem = ET.SubElement(root, 'row')
for cell in row:
cell_elem = ET.SubElement(row_elem, 'cell')
cell_elem.text = cell
tree = ET.ElementTree(root)
tree.write(filename)

def load_sql(filename):
conn = sqlite3.connect(filename)
cursor = conn.cursor()
cursor.execute("SELECT * FROM fmea")
data = cursor.fetchall()
conn.close()
return data

def save_sql(filename, data):
conn = sqlite3.connect(filename)
cursor = conn.cursor()
cursor.execute('''CREATE TABLE IF NOT EXISTS fmea
(operation_sequence TEXT, part_or_product TEXT, characteristics TEXT, failure_mode TEXT,
effect_of_failure TEXT, severity INTEGER, classification TEXT, cause_of_failure TEXT,
controls_prevention TEXT, occurrence INTEGER, controls_detection TEXT, detection INTEGER,
rpn INTEGER, recommended_action TEXT, responsibility TEXT, completion_date TEXT,
actions_taken TEXT, severity_a INTEGER, occurrence_a INTEGER, detection_a INTEGER, rpn_a INTEGER)''')
cursor.executemany('INSERT INTO fmea VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)', data)
conn.commit()
conn.close()


222 changes: 222 additions & 0 deletions fmea_app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
import tkinter as tk
from tkinter import ttk, filedialog, messagebox
import sqlite3
import os
import importlib
from PIL import Image, ImageTk
import menu_contents
import table_contents
import data_handler
import logger

class FMEAApp(tk.Tk):
def __init__(self):
super().__init__()
self.base_width = 600
self.base_height = int(self.base_width * 0.75) # Example ratio (4:3)
self.geometry(f"{self.base_width}x{self.base_height}")
self.minsize(self.base_width, self.base_height)

self.current_file = None
self.undo_stack = []
self.redo_stack = []

# Create a container for the screen manager
container = tk.Frame(self)
container.pack(side="top", fill="both", expand=True)

self.frames = {}
self.create_top_app_bar()

self.load_screens(container)
self.show_frame("HomeScreen")
menu_contents.create_menu(self)
self.init_db()

logger.app_logger.info("FMEA Application initialized")

def create_top_app_bar(self):
top_app_bar = tk.Frame(self, bg="lightgrey", height=50)
top_app_bar.pack(side="top", fill="x")

# Create buttons with icons
buttons_info = [
("media/topbar/back_arrow.png", self.show_home),
("media/topbar/new_file.png", self.open_file),
("media/topbar/save.png", self.save_file_as),
("media/topbar/undo.png", self.undo),
("media/topbar/next.png", self.next_screen),
("media/topbar/previous.png", self.previous_screen)]

for icon_path, command in buttons_info:
image = Image.open(icon_path)
image = image.resize((30, 30), Image.ANTIALIAS)
icon = ImageTk.PhotoImage(image)
button = tk.Button(top_app_bar, image=icon, command=command)
button.image = icon # Keep a reference to avoid garbage collection
button.pack(side="left", padx=2, pady=2)

def load_screens(self, container):
screens_dir = 'screens'
for filename in os.listdir(screens_dir):
if filename.endswith(".py") and filename != "__init__.py":
module_name = filename[:-3]
module = importlib.import_module(f"{screens_dir}.{module_name}")
class_name = ''.join([part.capitalize() for part in module_name.split('_')])
screen_class = getattr(module, class_name)
frame = screen_class(parent=container, controller=self)
self.frames[class_name] = frame
frame.grid(row=0, column=0, sticky="nsew")

def show_frame(self, page_name):
frame = self.frames[page_name]
frame.tkraise()

def show_home(self):
self.show_frame("HomeScreen")

def next_screen(self):
current_frame = self._current_frame()
next_frame = (current_frame + 1) % len(self.frames)
frame_name = list(self.frames.keys())[next_frame]
self.show_frame(frame_name)

def previous_screen(self):
current_frame = self._current_frame()
previous_frame = (current_frame - 1) % len(self.frames)
frame_name = list(self.frames.keys())[previous_frame]
self.show_frame(frame_name)

def _current_frame(self):
for idx, frame in enumerate(self.frames.values()):
if frame.winfo_ismapped():
return idx
return 0

def open_file(self):
filetypes = (("All files", "*.*"), ("Text files", "*.txt"), ("JSON files", "*.json"), ("XML files", "*.xml"), ("SQLite files", "*.sql"))
filename = filedialog.askopenfilename(title="Open file", filetypes=filetypes)
if filename:
try:
if filename.endswith('.txt'):
data = data_handler.load_txt(filename)
elif filename.endswith('.json'):
data = data_handler.load_json(filename)
elif filename.endswith('.xml'):
data = data_handler.load_xml(filename)
elif filename.endswith('.sql'):
data = data_handler.load_sql(filename)
table_contents.load_data_to_tree(self, data)
self.current_file = filename # Set the current file
logger.info_logger.info(f"File opened: {filename}")
except Exception as e:
logger.error_logger.error(f"Failed to open file: {filename}, Error: {e}")

def save_file(self):
if self.current_file:
try:
data = table_contents.get_table_data(self)
if self.current_file.endswith('.txt'):
data_handler.save_txt(self.current_file, data)
elif self.current_file.endswith('.json'):
data_handler.save_json(self.current_file, data)
elif self.current_file.endswith('.xml'):
data_handler.save_xml(self.current_file, data)
elif self.current_file.endswith('.sql'):
data_handler.save_sql(self.current_file, data)
logger.info_logger.info(f"File saved: {self.current_file}")
except Exception as e:
logger.error_logger.error(f"Failed to save file: {self.current_file}, Error: {e}")
else:
self.save_file_as()

def save_file_as(self):
filetypes = (("All files", "*.*"), ("Text files", "*.txt"), ("JSON files", "*.json"), ("XML files", "*.xml"), ("SQLite files", "*.sql"))
filename = filedialog.asksaveasfilename(title="Save file", filetypes=filetypes, defaultextension=".txt")
if filename:
try:
data = table_contents.get_table_data(self)
if filename.endswith('.txt'):
data_handler.save_txt(filename, data)
elif filename.endswith('.json'):
data_handler.save_json(filename, data)
elif filename.endswith('.xml'):
data_handler.save_xml(filename, data)
elif filename.endswith('.sql'):
data_handler.save_sql(filename, data)
self.current_file = filename # Set the current file
logger.info_logger.info(f"File saved: {filename}")
except Exception as e:
logger.error_logger.error(f"Failed to save file: {filename}, Error: {e}")

def close_file(self):
self.tree.delete(*self.tree.get_children())
self.current_file = None # Clear the current file
messagebox.showinfo("Info", "File closed successfully.")
logger.info_logger.info("File closed")

def undo(self):
if self.undo_stack:
last_action = self.undo_stack.pop()
row_id, column, previous_value = last_action
self.redo_stack.append((row_id, column, self.tree.set(row_id, column)))
self.tree.set(row_id, column, previous_value)
table_contents.update_rpn(self, row_id)
logger.debug_logger.debug("Undo performed")

def redo(self):
if self.redo_stack:
last_undo = self.redo_stack.pop()
row_id, column, previous_value = last_undo
self.undo_stack.append((row_id, column, self.tree.set(row_id, column)))
self.tree.set(row_id, column, previous_value)
table_contents.update_rpn(self, row_id)
logger.debug_logger.debug("Redo performed")

def connect_db(self):
menu_contents.connect_db(self)

def init_db(self):
self.conn = sqlite3.connect(":memory:")
self.cursor = self.conn.cursor()
self.cursor.execute('''CREATE TABLE IF NOT EXISTS fmea
(operation_sequence TEXT, part_or_product TEXT, characteristics TEXT, failure_mode TEXT,
effect_of_failure TEXT, severity INTEGER, classification TEXT, cause_of_failure TEXT,
controls_prevention TEXT, occurrence INTEGER, controls_detection TEXT, detection INTEGER,
rpn INTEGER, recommended_action TEXT, responsibility TEXT, completion_date TEXT,
actions_taken TEXT, severity_a INTEGER, occurrence_a INTEGER, detection_a INTEGER, rpn_a INTEGER)''')
self.conn.commit()
logger.info_logger.info("Database initialized")

def get_data_from_tree(self):
return table_contents.get_table_data(self)

def add_sub_row(self, parent_id, values):
table_contents.insert_row(self, values, tags=self.tree.item(parent_id, "tags"))

def create_default_table(self):
default_data = [
["1", "Part A", "Characteristic 1", "Failure Mode 1", "Effect 1", "5", "", "Cause 1", "Control 1", "3", "Detection 1", "2", "30", "Action 1", "Person A", "2024-01-01", "Action Taken 1", "4", "2", "2", "16"],
["2", "Part B", "Characteristic 2", "Failure Mode 2", "Effect 2", "4", "", "Cause 2", "Control 2", "4", "Detection 2", "3", "48", "Action 2", "Person B", "2024-01-02", "Action Taken 2", "3", "3", "3", "27"]
]

self.tree.delete(*self.tree.get_children())
for i, row in enumerate(default_data):
tag = 'evenrow' if i % 2 == 0 else 'oddrow'
row_id = table_contents.insert_row(self, row, tags=(tag,))
table_contents.update_rpn(self, row_id)

messagebox.showinfo("Info", "Default table created successfully.")
logger.info_logger.info("Default table created")

def update_cell(self, row_id, col, value):
previous_value = self.tree.set(row_id, col)
self.undo_stack.append((row_id, col, previous_value))
self.tree.set(row_id, col, value)
table_contents.update_rpn(self, row_id)
logger.debug_logger.debug(f"Cell updated: row_id={row_id}, col={col}, value={value}")


if __name__ == "__main__":
app = FMEAApp()
app.mainloop()
58 changes: 58 additions & 0 deletions fmea_app.spec
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# fmea_app.spec
# -*- mode: python ; coding: utf-8 -*-

block_cipher = None

a = Analysis(
['main_app.py'],
pathex=['.'], # Add any paths where your modules are located
binaries=[],
datas=[
('media/topbar/back_arrow.png', '.'), # Add paths to your icon files
('media/topbar/new_file.png', '.'),
('media/topbar/save.png', '.'),
('media/topbar/load.png', '.'),
('media/topbar/undo.png', '.'),
('media/topbar/previous.png', '.'),
('media/topbar/next.png', '.'),
],
hiddenimports=['PIL', 'PIL._imagingtk'], # Add any hidden imports here
hookspath=[],
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False,
)

pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)

exe = EXE(
pyz,
a.scripts,
a.binaries,
a.zipfiles,
a.datas,
[],
name='FMEAApp',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
upx_exclude=[],
runtime_tmpdir=None,
console=False, # Change to True if you want to see console output
icon='Ofmea_logo.png' # Add path to your app icon if you have one
)

coll = COLLECT(
exe,
a.binaries,
a.zipfiles,
a.datas,
strip=False,
upx=True,
upx_exclude=[],
name='FMEAApp',
)
Loading

0 comments on commit 808e458

Please sign in to comment.