I am making a form dialog in PyQt5. The objective of the program is to generate numbers according to a particular format depending on the Part type and store these numbers in an Excel sheet. The excel sheet is named 'Master Part Sheet.xlsx' and stored in the same directory as the script. The name of the excel sheet can be changed in the workbookLayer Class. The number of generated numbers is determined by the entry in the number of part numbers required field
The customer selected in the drop down menu also plays a role in generating a part number(only if it is a Machine Part). A number from 0-99 is stored in a dictionary against each customers name which is accessed while generating corresponding part numbers.
The input fields have to be checked when a user enters a value in them. Every time the value entered is incorrect(not in a certain range/ not in the specified format), an exception is called. The values are checked when the generate Qpushbutton in the form dialog is clicked.
The NTID field must be of the form UUUNUUU where U stands for upper case string and N stands for a Number. It is checked for with regular expressions and must otherwise throw exceptions.
The machine number and line number fields must be in the range 0-99, and must otherwise throw an exception prompting the user to check his/her entries in the message label.
After the entry of data by the user and these checks, the part numbers must be generated and written onto the excel sheet.
If the part type is a standard part, the part numbers are written onto the Standard parts sheet of the workbook. The max number of parts that can exist in this sheet are 10000. The part numbers must be unique.
If the sheet is a machine part, new sheets are created in the workbook where the part numbers are written onto. The sheets are created based on the customer name the machine number and the line number. One sheet can have a maximum of 1000 parts and the numbers generated shouldn't be repeated in the sheet i.e they are unique.
Below is the code
'''import all necessary libraries required for the project'''
import os
import numpy as np
import openpyxl
from openpyxl.workbook import Workbook
from openpyxl import load_workbook
import random
import time
import pickle
import re
class workbookLayer:
'''The constructor for the workbook object'''
def __init__(self, source_file='Master Part Sheet.xlsx'): # Change source_file to get different sheet as source
self.source_file = source_file
self.part_type = None
self.line_number = None
self.machine_number = None
self.customer_name = None
self.ntid = None
self.get_sheet_name()
'''Get the path of the Excel workbook and load the workbook onto the object'''
def get_sheet_name(self):
self.file_path = os.path.realpath(self.source_file)
self.wb = load_workbook(self.file_path)
self.wb.get_sheet_names()
return self.file_path, self.wb
from PyQt5.QtWidgets import (QApplication, QComboBox, QDialog,
QDialogButtonBox, QFormLayout, QGridLayout, QGroupBox, QHBoxLayout,
QLabel, QLineEdit, QMenu, QMenuBar, QPushButton, QSpinBox, QTextEdit,
QVBoxLayout, QMessageBox)
import sys
'''The Pop up Box for addition of a new customer'''
class DialogCustomerAdd(QDialog):
NumGridRows = 3
NumButtons = 4
def __init__(self):
super(DialogCustomerAdd, self).__init__()
self.createFormGroupBox()
self.ok_button=QPushButton("OK")
mainLayout = QVBoxLayout()
mainLayout.addWidget(self.formGroupBox)
mainLayout.addWidget(self.ok_button)
self.ok_button.clicked.connect(self.ok_button_action)
self.setLayout(mainLayout)
self.setWindowTitle("Add New Customer")
def ok_button_action(self):
global CUSTOMER_NAME
global CUSTOMER_ID
CUSTOMER_NAME = self.customer_name_text.text()
CUSTOMER_ID = self.customer_id_text.text()
self.close()
def createFormGroupBox(self):
self.formGroupBox = QGroupBox("Customer Details")
layout = QFormLayout()
self.customer_name_label = QLabel("Name of the Customer")
self.customer_name_text = QLineEdit()
self.customer_id_label = QLabel("Enter Customer ID")
self.customer_id_text = QLineEdit()
layout.addRow(self.customer_name_label, self.customer_name_text)
layout.addRow(self.customer_id_label, self.customer_id_text)
self.formGroupBox.setLayout(layout)
'''The Pop up Box for deletion of an existing customer'''
class DialogCustomerDelete(QDialog):
NumGridRows = 3
NumButtons = 4
def __init__(self):
super(DialogCustomerDelete, self).__init__()
self.createFormGroupBox()
self.ok_button=QPushButton("OK")
mainLayout = QVBoxLayout()
mainLayout.addWidget(self.formGroupBox)
mainLayout.addWidget(self.ok_button)
self.ok_button.clicked.connect(self.ok_button_action)
self.setLayout(mainLayout)
self.setWindowTitle("Delete Existing Customer")
def ok_button_action(self):
global POP_CUSTOMER
POP_CUSTOMER = self.customer_delete_combobox.currentText()
self.customer_delete_combobox.clear()
self.close()
def createFormGroupBox(self):
self.formGroupBox = QGroupBox("Customer Details")
layout = QFormLayout()
self.customer_name_label = QLabel(" Select the Customer to be deleted")
self.customer_delete_combobox = QComboBox()
self.customer_delete_combobox.addItems(CUSTOMER_LIST)
layout.addRow(self.customer_name_label)
layout.addRow(self.customer_delete_combobox)
self.formGroupBox.setLayout(layout)
class DialogMain(QDialog):
NumGridRows = 3
NumButtons = 4
wbl = workbookLayer()
def __init__(self):
super(DialogMain, self).__init__()
self.createFormGroupBox()
generate_button=QPushButton("Generate")
generate_button.clicked.connect(self.generateNumbers)
self.user_message_label = QLabel(" ")
buttonBox = QDialogButtonBox(QDialogButtonBox.Cancel)
mainLayout = QVBoxLayout()
mainLayout.addWidget(self.formGroupBox)
mainLayout.addWidget(self.user_message_label)
mainLayout.addWidget(generate_button)
mainLayout.addWidget(self.add_new_customer_button)
mainLayout.addWidget(self.delete_customer_button)
buttonBox.clicked.connect(self.closeIt)
mainLayout.addWidget(buttonBox)
self.setLayout(mainLayout)
self.open_or_create_pickle_file()
customer_name = [customer for customer,customer_id in self.customer_dict.items()]
self.customer_name_combobox.addItems(customer_name)
self.add_new_customer_button.clicked.connect(self.add_customer_window)
self.delete_customer_button.clicked.connect(self.delete_customer_window)
self.setWindowTitle("Part Number Generator ")
global CUSTOMER_LIST
CUSTOMER_LIST = customer_name
def open_or_create_pickle_file(self):
self.customer_dict = dict()
try:
assert open("customer_diction.pickle","rb")
pickle_in = open("customer_diction.pickle","rb")
self.customer_dict = pickle.load(pickle_in)
pickle_in.close()
except FileNotFoundError:
pickle_out = open("customer_dict.pickle","wb")
def add_new_customer_to_pickle(self):
global CUSTOMER_NAME
global CUSTOMER_ID
global CUSTOMER_LIST
self.customer_name_combobox.addItem(CUSTOMER_NAME)
self.customer_dict[CUSTOMER_NAME] = CUSTOMER_ID
customer_name = [customer for customer,customer_id in self.customer_dict.items()]
CUSTOMER_LIST = customer_name
pickle_out = open("customer_diction.pickle","wb")
pickle.dump(self.customer_dict, pickle_out)
pickle_out.close()
def delete_customer_from_pickle(self):
global POP_CUSTOMER
del self.customer_dict[POP_CUSTOMER]
customer_name = [customer for customer,customer_id in self.customer_dict.items()]
global CUSTOMER_LIST
CUSTOMER_LIST = customer_name
self.customer_name_combobox.clear()
self.customer_name_combobox.addItems(customer_name)
pickle_out = open("customer_diction.pickle","wb")
pickle.dump(self.customer_dict, pickle_out)
pickle_out.close()
def add_customer_window(self):
widget = DialogCustomerAdd()
widget.exec_()
self.add_new_customer_to_pickle()
def delete_customer_window(self):
widget = DialogCustomerDelete()
widget.exec_()
self.delete_customer_from_pickle()
global POP_CUSTOMER
self.user_message_label.setText('The customer ' + POP_CUSTOMER + ' has been deleted' )
def closeIt(self):
self.close()
def generateNumbers(self):
self.wbl.line_number = self.line_number_text.text()
self.wbl.machine_number = self.machine_number_text.text()
self.wbl.customer_name =self.customer_name_combobox.currentText()
self.wbl.part_type = self.part_type_combobox.currentText()
self.wbl.ntid = self.ntid_text.text()
try:
self.check_ntid()
except AssertionError:
self.user_message_label.setText('Please Enter User Details Correctly')
try:
if int(self.wbl.machine_number) > 99:
raise ValueError
except ValueError:
self.user_message_label.setText('Please Enter machine number within 100')
try:
if int(self.wbl.line_number) > 99:
raise ValueError
except ValueError:
self.user_message_label.setText('Please Enter Line number within 100')
self.create_sheet_and_check_values()
try:
self.part_number_generator()
except PermissionError:
self.user_message_label.setText('Please Close the excel sheet before using this application')
def createFormGroupBox(self):
self.formGroupBox = QGroupBox("Part Details")
layout = QFormLayout()
self.part_type_label = QLabel("Part Type:")
self.part_type_combobox = QComboBox()
self.part_type_combobox.addItems(['Standard Part', 'Machine Part'])
self.customer_name_label = QLabel("Customer:")
self.customer_name_combobox = QComboBox()
self.add_new_customer_button = QPushButton("Add New Customer")
self.delete_customer_button = QPushButton("Delete Existing Customer")
self.ntid_label = QLabel("NTID of the User:")
self.ntid_text = QLineEdit()
self.machine_number_label = QLabel("Machine Number of the Part:")
self.machine_number_text = QLineEdit()
self.line_number_label = QLabel("Line Number of the Part:")
self.line_number_text = QLineEdit()
self.part_numbers_label = QLabel("Number of Part Numbers Required:")
self.part_numbers_spinbox = QSpinBox()
self.part_numbers_spinbox.setRange(1, 999)
layout.addRow(self.part_type_label, self.part_type_combobox)
layout.addRow(self.customer_name_label, self.customer_name_combobox)
layout.addRow(self.ntid_label, self.ntid_text)
layout.addRow(self.machine_number_label, self.machine_number_text)
layout.addRow(self.line_number_label, self.line_number_text)
layout.addRow(self.part_numbers_label, self.part_numbers_spinbox)
self.formGroupBox.setLayout(layout)
def check_ntid(self):
pattern = re.compile("^[A-Z][A-Z][A-Z][0-9][A-Z][A-Z][A-Z]")
assert pattern.match(self.wbl.ntid)
def create_sheet_and_check_values(self):
self.check_ntid()
if self.wbl.part_type == 'Machine Part':
self.prefix = int('3' + self.customer_dict[self.wbl.customer_name] + self.wbl.line_number + self.wbl.machine_number)
self.check_and_create_customer_sheet()
else:
self.prefix = 500000
self.add_standard_parts_sheet()
self.dest_filename = self.wbl.file_path
self.ws1 = self.wbl.wb[self.wbl.sheet_name]
self.random_numbers = self.part_numbers_spinbox.value()
if (self.wbl.part_type == 'Machine Part'):
try:
assert self.random_numbers < (1002 -(self.ws1.max_row))
except AssertionError:
self.user_message_label.setText('You have exceeded the range of allowable parts in the machine, you can get only {} more part numbers'.format(1001 -(self.ws1.max_row)))
else:
try:
assert self.random_numbers < (10002 -(self.ws1.max_row))
except AssertionError:
self.user_message_label.setText('You have exceeded the range of allowable parts in this list, you can get only {} more part numbers'.format(10001 -(self.ws1.max_row)))
'''This section of the method contains the logic for the generation of random number parts
according to the required format and also checks for clashes with already existing part numbers'''
def part_number_generator(self):
if (self.wbl.part_type == 'Machine Part'):
part_numbers_list = random.sample(range( self.prefix*1000, self.prefix*1000 + 1000), self.random_numbers)
else:
part_numbers_list = random.sample(range( self.prefix*10000, self.prefix*10000 + 10000), self.random_numbers)
first_column = set([(self.ws1[x][0].value) for x in range(2,self.ws1.max_row + 1 )])
while True:
additional_part_numbers_list = random.sample(range( self.prefix*1000, self.prefix*1000 + 1000), self.random_numbers - len (part_numbers_list))
part_numbers_list = part_numbers_list + additional_part_numbers_list
part_numbers_list = list(set(part_numbers_list) - (first_column))
if len(part_numbers_list) == self.random_numbers:
break
for row in range( self.ws1.max_row + 1, self.ws1.max_row + 1 + len(part_numbers_list)):
'''write the generated part numbers onto the excel sheet corresponding to the customer'''
self.ws1.cell(column = 1 , row = row, value = part_numbers_list.pop())
self.ws1.cell(column = 3 , row = row, value = self.wbl.ntid)
self.wbl.wb.save(self.wbl.file_path)
self.user_message_label.setText(str(self.part_numbers_spinbox.value()) + " Part Numbers have been successfully generated. Please check the sheet " + self.wbl.sheet_name)
def check_and_create_customer_sheet(self):
self.wbl.sheet_name = '3' + self.customer_dict[self.wbl.customer_name] + self.wbl.line_number + self.wbl.machine_number +'xxx'
if self.customer_not_exists():
self.wbl.wb.create_sheet(self.wbl.sheet_name)
self.wbl.wb.save(self.wbl.file_path)
'''Check if the customer sheet exists'''
def customer_not_exists(self):
sheet_name = '<Worksheet "'+ '3' + self.customer_dict[self.wbl.customer_name] + self.wbl.line_number + self.wbl.machine_number +'xxx' + '">'
return True if sheet_name not in str(list(self.wbl.wb)) else False
'''Check if standard parts sheet exists. If not make a new sheet'''
def add_standard_parts_sheet(self):
sheet_name = '<Worksheet "'+ 'Standard Parts'+ '">'
if sheet_name not in str(list(self.wbl.wb)):
self.wbl.wb.create_sheet('Standard Parts')
self.wbl.wb.save(self.wbl.file_path)
self.wbl.sheet_name = 'Standard Parts'
if __name__ == '__main__':
app = QApplication(sys.argv)
dialog = DialogMain()
sys.exit(dialog.exec_())
The generateNumbers() function in the DialogMain() Class is called every time a 'generate' QpushButton is clicked in the QDialog layout. The program checks for correctness of the inputs and generates random numbers according to the required format in an excel sheet through openpyxl. The exceptions for the self.wbl.ntid and PermissionError work as expected. However the exceptions for self.wbl.machine_number and self.wb1.line_number i.e for machine number inputs and line number inputs in the dialog seem to be ignored and the program executes even when the user entry is wrong. Only the exception for NTID behaves as expected and the other exceptions fail to be raised even on wrong entry.
The dialog Box
I have added the entire code used to make it reproducible. Thanks
Related
I'm trying to make a widget with multiple tabs that are occupied with different excel tables per tab and can't figure why I cant populate each tab separately.
About the tables the number of columns remain the same for all Excel tables only the number of rows change.
import sys
from PyQt5 import QtCore , QtWidgets ,QtGui
import pandas as pd
import os
class MyApp(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.sizeHint().width(), self.sizeHint().height()
self.resize(self.width(), self.height())
layout = QtWidgets.QVBoxLayout()
self.setLayout(layout)
self.table = QtWidgets.QTableWidget()
self.tabs = QtWidgets.QTabWidget()
self.table.setSortingEnabled(True)
self.table.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
self.table.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
self.table.selectionModel().selectionChanged.connect(self.selected)
# self.add_flight_tab()
layout.addWidget(self.tabs)
tab_list = self.add_flight_tab(path_xl)
for tables , names in zip(range(tab_list.__sizeof__()), tab_list):
self.tabs.setCurrentIndex(tables)
self.load_data(f"{path_xl}/{names}")
self.setLayout(layout)
def load_data(self, path_to_xl):
df = pd.read_excel(path_to_xl,sheet_name=0, usecols=["AWB", "Destn.", "SCC", "Agent Name",
"Stated Pcs/Wgt/Vol (kg/CBM)","Booked Flight"])
if df.size == 0:
return
df.fillna('', inplace=True)
self.table.setRowCount(df.shape[0])
self.table.setColumnCount(df.shape[1])
self.table.setHorizontalHeaderLabels(df.columns)
self.table.insertColumn(self.table.columnCount())
self.table.setHorizontalHeaderItem(6 , QtWidgets.QTableWidgetItem("Offload"))
self.table.horizontalHeader().setSectionResizeMode(QtWidgets.QHeaderView.ResizeToContents)
for row in df.iterrows():
values = row[1]
for i in range(df.shape[0]):
check_box = QtWidgets.QTableWidgetItem()
check_box.setFlags(QtCore.Qt.ItemIsUserCheckable | QtCore.Qt.ItemIsEnabled)
check_box.setCheckState(QtCore.Qt.CheckState())
self.table.setItem(i, 6, check_box)
for col_index, value in enumerate(values):
if isinstance(value, (float, int)):
value = '{0:0,.0f}'.format(value)
tableItem = QtWidgets.QTableWidgetItem(str(value))
self.table.setItem(row[0], col_index, tableItem)
def add_flight_tab(self, path_files):
files = os.listdir(path_files)
new_ls = []
for xl, tab_ind in zip(files, range(files.__sizeof__())):
if xl.endswith(".xlsx") or xl.endswith(".XLSX"):
new_ls.append(xl)
self.tabs.insertTab(tab_ind,QtWidgets.QTableWidget(), xl)
self.tabs.setCurrentIndex(tab_ind)
return new_ls
if __name__ == '__main__':
path_xl = "path to excel"
app = QtWidgets.QApplication(sys.argv)
myApp = MyApp()
myApp.show()
# print(myApp.add_flight_tab())
try:
sys.exit(app.exec_())
except SystemExit:
print("Closing window...")
Everything I have tried gives two results(there must be something I'm missing out in here) :
The Widget is populated but the are no other tabs only thing changed is the following lines:
layout.addWidget(self.tabs)
tab_list = self.add_flight_tab(path_xl)
for tables , names in zip(range(tab_list.__sizeof__()), tab_list):
self.tabs.setCurrentIndex(tables)
self.load_data(f"{path_xl}/{names}")
layout.addChildWidget(self.table)
For now the names of the tabs is irrelevant , I only need to have number of tabs according to number of Excel files and each tab is filled with table accordingly.
I need bit of assistance with my python code. I am a newbie to python so not very good at it.
I have an excel spreadsheet with a bunch of lecture times and I am using the code below;
df = pd.read_excel('/home/pi/timetable1.xlsx')
df['Date'] = pd.to_datetime(df['Date']).dt.strftime("%d-%m-%Y")
now = pd.to_datetime('today').strftime("%d-%m-%Y")
print(df[df['Date'] == now])
which displays a simple line of timetable text such as below [xx:xx is time in 24 hr format];
Lesson 1: Lesson 2: Lesson 3: Lession 4: Lesson 5:
xx:xx xx:xx xx:xx xx:xx xx:xx
I have configured it so that the above displayed times only shows the times for the "Current Date".
What I am trying to acheive is that I want to use PyQt4 to display this information on a graphical window.
So for example, the below displays "HELLO WORLD" text in the gui window;
def wxfinished():
attribution3.setText("HELLO WORLD")
attribution3 = QtGui.QLabel(foreGround)
attribution3.setObjectName("attribution3")
attribution3.setStyleSheet("#attribution3 { " +
"background-color: transparent; color: " +
Config.textcolor +
"; font-size: " +
str(int(50 * xscale)) + #50 is the size of the text
"px; " +
Config.fontattr +
"}")
attribution3.setAlignment(Qt.AlignTop)
attribution3.setGeometry(450 * xscale, 670 * yscale, 1000 * xscale, 1000)
w.show()
w.showFullScreen()
sys.exit(app.exec_())
How can I change it so that instead of "HELLO WORLD" I can print out the timetable output?
While I'm not that familiar with Pandas, here's an MRE to help you. In this, a table is made, the labels are set to the excel's labels (lesson x), and the singular row is filled out with the dates.
from PyQt4.QtGui import QApplication, QTableWidget, QLabel, QFont
from PyQt4.QtCore import Qt
import sys
import pandas
CELL_FONT = QFont()
CELL_FONT.setPointSize(8)
def getLessions(): # assuming the code you provided works
"""
Returns Pandas DataFrame object w/ today's date info
:return:
"""
data = pandas.read_excel("path")
date = pandas.to_datatime(data["Date"]).dtftime("%d-%m-%Y")
now = pandas.to_datetime('today').strftime("%d-%m-%Y")
return data[date == now]
def makeWidget():
datum = getLessions()
columns = len(datum.columns)
# Use table to display information
table = QTableWidget()
table.setRowCount(1)
table.setColumnCount(columns)
table.setHorizontalHeaderLabels(datum.columns) # I think datum.columns returns [str], I could be wrong
table.setVerticalHeaderLabels([""])
# Row will be filled with Labels w/ times
for i in range(columns):
lession_time = str(datum.iloc[0,i]) # edit: iloc returns a Timestamp object
label = QLabel()
label.setFont(CELL_FONT)
label.setText(lession_time)
label.setAlignment(Qt.AlignHCenter | Qt.AlignBottom)
table.setCellWidget(0, i, label)
return table
def makeWidgetDummy(): # Doesn't use Pandas
columns = 5
table = QTableWidget()
table.setRowCount(1)
table.setColumnCount(columns)
horizontal_lbls = ["Lesson {}".format(i + 1) for i in range(columns)]
table.setHorizontalHeaderLabels(horizontal_lbls)
table.setVerticalHeaderLabels([""])
for i in range(columns):
lession_time = "XX:XX"
label = QLabel()
label.setFont(CELL_FONT)
label.setText(lession_time)
label.setAlignment(Qt.AlignHCenter | Qt.AlignBottom)
table.setCellWidget(0, i, label)
def main():
app = QApplication(sys.argv)
widget = makeWidget()
# widget = makeWidgetDummy()
widget.show()
app.exec_()
if __name__ == "__main__":
main()
Basically I am displaying a QTreeView with 20 columns. I want the user to re-arrange the columns and push a save button to store the ordering as a list in an ini-file. On starting up the program, I want to re-order the columns based on the settings from the ini-file.
I am storing the original colum orders as list in "list_origin".
The desired order is in "list_custom".
E.g.
list_origin=['From', 'Subject', 'Date']
list_custom=['Date', 'Subject', 'From']
Now the problem is, when I move columns with the model headers moveSection() command, the original indexes are sometimes not correct anymore, because the columns might get inserted in between and thus lose their origin position index.
See example below: pushing the button "Rearrange cols to Date/Subject/From" will create an undesired order of the columns. How to arrange the colums in the desired order, based on the list_custom?
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# file: treeView_FindCol.py
import sys
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
def find_col_by_name(tree_obj, col_name: str) -> int:
""" Returns found column position as integer, else -1 """
pos_found: int = -1
model = tree_obj.model()
if model:
for col in range(model.columnCount()):
header = model.headerData(col, Qt.Horizontal, Qt.DisplayRole)
if str(header) == col_name:
pos_found = col
return pos_found
def find_col_by_index(tree_obj, col_index: int) -> int:
""" Returns found column position as integer, else -1 """
pos_found: int = -1
model = tree_obj.model()
header = tree_obj.header()
pos_found = header.visualIndex(col_index)
header_txt = model.headerData(pos_found, Qt.Horizontal, Qt.DisplayRole)
return pos_found
class App(QWidget):
FROM, SUBJECT, DATE = range(3)
def __init__(self):
super().__init__()
self.title = 'PyQt5 Treeview Example - pythonspot.com'
self.left = 800
self.top = 200
self.width = 640
self.height = 240
self.initUI()
def initUI(self):
self.setWindowTitle(self.title)
self.setGeometry(self.left, self.top, self.width, self.height)
pushButton = QPushButton("Rearrange cols to Date/Subject/From")
groupBox = QGroupBox("Inbox")
treeView = QTreeView()
treeView.setRootIsDecorated(False)
treeView.setAlternatingRowColors(True)
pushButton.clicked.connect(lambda: self.rearrange_column_layout(treeView))
dataLayout = QHBoxLayout()
dataLayout.addWidget(treeView)
dataLayout.addWidget(pushButton)
groupBox.setLayout(dataLayout)
model = self.createMailModel(self)
treeView.setModel(model)
self.addMail(model, 'service#github.com', 'Your Github Donation','03/25/2017 02:05 PM')
self.addMail(model, 'support#github.com', 'Github Projects','02/02/2017 03:05 PM')
self.addMail(model, 'service#phone.com', 'Your Phone Bill','01/01/2017 04:05 PM')
self.addMail(model, 'service#abc.com', 'aaaYour Github Donation','03/25/2017 02:05 PM')
self.addMail(model, 'support#def.com', 'bbbGithub Projects','02/02/2017 03:05 PM')
self.addMail(model, 'service#xyz.com', 'cccYour Phone Bill','01/01/2017 04:05 PM')
mainLayout = QVBoxLayout()
mainLayout.addWidget(groupBox)
self.setLayout(mainLayout)
self.show()
def createMailModel(self,parent):
model = QStandardItemModel(0, 3, parent)
model.setHeaderData(self.FROM, Qt.Horizontal, "From")
model.setHeaderData(self.SUBJECT, Qt.Horizontal, "Subject")
model.setHeaderData(self.DATE, Qt.Horizontal, "Date")
return model
def addMail(self,model, mailFrom, subject, date):
model.insertRow(0)
model.setData(model.index(0, self.FROM), mailFrom)
model.setData(model.index(0, self.SUBJECT), subject)
model.setData(model.index(0, self.DATE), date)
def rearrange_column_layout(self, treeView):
print("restore_column_layout() called.")
list_custom: list = ['Date', 'Subject', 'From']
list_origin: list = []
model = treeView.model()
header = treeView.header()
col_count = model.columnCount()
for col_search_index in range(col_count):
col_found = header.visualIndex(col_search_index)
header_txt = model.headerData(col_search_index, Qt.Horizontal, Qt.DisplayRole)
list_origin.append(header_txt)
print(f"{list_origin=}")
print(f"{list_custom=}")
pos_custom: int = 0
pos_origin_last: int = 0
for item_custom in list_custom:
pos_origin: int = 0
for item_origin in list_origin:
if item_custom == item_origin:
msg_txt = f"moving col '{item_origin}' from {pos_origin} to {pos_custom}."
print(msg_txt)
QMessageBox.information(self, f"{item_origin}", msg_txt)
header.moveSection(pos_origin, pos_custom)
pos_origin_last = pos_origin
pos_origin += 1
pos_custom += 1
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = App()
sys.exit(app.exec_())
I want to set the column ordering based on an input list
"list_origin" contains the header names in original order, e.g.
list_origin=['From', 'Subject', 'Date']
"list_custom" is the desired order of the columns in my QTreeView, e.g.
list_custom=['Date', 'Subject', 'From']
Now I am iterating over the custom list, and want to put the columns based on this positions with header moveSection().
So the algorithm basically is:
step1: start with index 0 of the custom list, it's "Date"
step2: get the position of "Date" from origin list, which is 2.
step3: call moveSection(2,0)
Trace output:
moving col 'Date' from 2 to 0.
moving col 'Subject' from 1 to 1.
moving col 'From' from 0 to 2.
But anyway the result is "From"/"Subject"/"Date" (!) and not as desired "Date"/"Subject"/"From".
The logic is to obtain the index of each element at the time of the iteration since the visual order is changing within the loop:
def rearrange_column_layout(self, treeView):
to_list = ["Date", "Subject", "From"]
header = treeView.header()
model = treeView.model()
for i, c in enumerate(to_list[:-1]):
from_list = [
model.headerData(header.logicalIndex(visual_index), Qt.Horizontal)
for visual_index in range(header.count())
]
j = from_list.index(c)
header.moveSection(j, i)
I have a question for some of you who are familiar with the Revit API and python:
I’ve been using the spring nodes package in dynamo to create a rather large series of freeform objects each in their own family. The way that the FamilyInstance.ByGeometry works, it takes a list of solids and creates a family instance for each using a template family file. The result is quite good. (spring nodes can be found here: https://github.com/dimven/SpringNodes)
However, the drawback is that that now I have roughly 200 separate instances, so to make changes to each is rather painful. I thought at first it would be possible to use dynamo to create a new subcategory and set the solid inside each family instance to this new subcategory. Unfortunately, I realized this is not possible since dynamo cannot be open in two different Revit environments simultaneously (the project I am working in and each instance of the family). This leads me to look to see if I can do this using python.
I have used python in rhino and can get along pretty well, I am still learning the Revit API however. But basically my idea would be to:
1. select a series of family instances in the Revit project environment
2. loop through each instance
3. save it to a specified location
4. create a new subcategory in each family instances (the subcategory would the same for all the selected family instances)
5. select the solid in each in instance
6. set the solid to this newly created subcategory
7. close the family instance and save
My question for you is does this sound like it is achievable based on your knowledge of the Revit API?
Many thanks for your time and advice.
UPDATE:
I've found a section in the revit api that describes what i'm looking to do: http://help.autodesk.com/view/RVT/2015/ENU/?guid=GUID-FBF9B994-ADCB-4679-B50B-2E9A1E09AA48
I've made a first pass at inserting this into the python code of the dynamo node. The rest of the code works fine except for the new section im adding (see below). Please excuse the variables, I am simply keeping with logic of the original author of the code i am hacking:
(Note: the variables come in are in arrays)
#set subcategory
try:
#create new sucategory
fam_subcat = famdoc.Settings.Categories.NewSubcategory(fam_cat, get_Item(subcat1.Name))
#assign the mataterial(fam_mat.Id) to the subcategory
fam_subcat.Material = famdoc.GetElement(fam_mat.Id)
#assign the subcategory to the element (s2)
s2.Subcategory = fam_subcat
except: pass
Any help or advice with this section of code would be much appreciated.
UPDATE:
See full code below for context of the section in question:
#Copyright(c) 2015, Dimitar Venkov
# #5devene, dimitar.ven#gmail.com
import clr
import System
from System.Collections.Generic import *
pf_path = System.Environment.GetFolderPath(System.Environment.SpecialFolder.ProgramFilesX86)
import sys
sys.path.append("%s\IronPython 2.7\Lib" %pf_path)
import traceback
clr.AddReference('ProtoGeometry')
from Autodesk.DesignScript.Geometry import *
clr.AddReference("RevitServices")
import RevitServices
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager
doc = DocumentManager.Instance.CurrentDBDocument
app = DocumentManager.Instance.CurrentUIApplication.Application
clr.AddReference("RevitAPI")
from Autodesk.Revit.DB import *
from Autodesk.Revit.DB.Structure import StructuralType
clr.AddReference("RevitNodes")
import Revit
clr.ImportExtensions(Revit.Elements)
clr.ImportExtensions(Revit.GeometryConversion)
def tolist(obj1):
if hasattr(obj1,"__iter__"): return obj1
else: return [obj1]
def output1(l1):
if len(l1) == 1: return l1[0]
else: return l1
def PadLists(lists):
len1 = max([len(l) for l in lists])
for i in xrange(len(lists)):
if len(lists[i]) == len1:
continue
else:
len2 = len1 - len(lists[i])
for j in xrange(len2):
lists[i].append(lists[i][-1])
return lists
class FamOpt1(IFamilyLoadOptions):
def __init__(self):
pass
def OnFamilyFound(self,familyInUse, overwriteParameterValues):
return True
def OnSharedFamilyFound(self,familyInUse, source, overwriteParameterValues):
return True
geom = tolist(IN[0])
fam_path = IN[1]
names = tolist(IN[2])
category = tolist(IN[3])
material = tolist(IN[4])
isVoid = tolist(IN[5])
subcategory = tolist(IN[6])
isRvt2014 = False
if app.VersionName == "Autodesk Revit 2014": isRvt2014 = True
units = doc.GetUnits().GetFormatOptions(UnitType.UT_Length).DisplayUnits
factor = UnitUtils.ConvertToInternalUnits(1,units)
acceptable_views = ["ThreeD", "FloorPlan", "EngineeringPlan", "CeilingPlan", "Elevation", "Section"]
origin = XYZ(0,0,0)
str_typ = StructuralType.NonStructural
def NewForm_background(s1, name1, cat1, isVoid1, mat1, subcat1):
t1 = TransactionManager.Instance
TransactionManager.ForceCloseTransaction(t1)
famdoc = doc.Application.NewFamilyDocument(fam_path)
message = None
temp_path = System.IO.Path.GetTempPath()
sat_path = "%s%s.sat" % (temp_path, name1)
try:
if factor != 1:
s1 = s1.Scale(factor)
sat1 = Geometry.ExportToSAT(s1, sat_path)
satOpt = SATImportOptions()
satOpt.Placement = ImportPlacement.Origin
satOpt.Unit = ImportUnit.Foot
view_fec = FilteredElementCollector(famdoc).OfClass(View)
view1 = None
for v in view_fec:
if str(v.ViewType) in acceptable_views:
view1 = v
break
t1.EnsureInTransaction(famdoc)
satId = famdoc.Import(sat1, satOpt, view1)
opt1 = Options()
opt1.ComputeReferences = True
el1 = famdoc.GetElement(satId)
geom1 = el1.get_Geometry(opt1)
enum = geom1.GetEnumerator()
enum.MoveNext()
geom2 = enum.Current.GetInstanceGeometry()
enum2 = geom2.GetEnumerator()
enum2.MoveNext()
s1 = enum2.Current
famdoc.Delete(satId)
TransactionManager.ForceCloseTransaction(t1)
System.IO.File.Delete(sat_path)
except:
message = traceback.format_exc()
pass
if message == None:
try:
save_path = "%s%s.rfa" % (temp_path, name1)
SaveAsOpt = SaveAsOptions()
SaveAsOpt.OverwriteExistingFile = True
t1.EnsureInTransaction(famdoc)
#set the category
try:
fam_cat = famdoc.Settings.Categories.get_Item(cat1.Name)
famdoc.OwnerFamily.FamilyCategory = fam_cat
except: pass
s2 = FreeFormElement.Create(famdoc,s1)
if isVoid1:
void_par = s2.get_Parameter("Solid/Void")
void_par.Set(1)
void_par2 = famdoc.OwnerFamily.get_Parameter("Cut with Voids When Loaded")
void_par2.Set(1)
else: #voids do not have a material value
try:
mat_fec = FilteredElementCollector(famdoc).OfClass(Material)
for m in mat_fec:
if m.Name == mat1:
fam_mat = m
break
mat_par = s2.get_Parameter("Material")
mat_par.Set(fam_mat.Id)
except: pass
#set subcategory
try:
#create new sucategory
fam_subcat = document.Settings.Categories.NewSubcategory(document.OwnerFamily.FamilyCategory, get_Item(subcat1.Name))
#assign the mataterial(fam_mat.Id) to the subcategory
fam_subcat.Material = famdoc.GetElement(fam_mat.Id)
#assign the subcategory to the element (s2)
s2.Subcategory = fam_subcat
except: pass
TransactionManager.ForceCloseTransaction(t1)
famdoc.SaveAs(save_path, SaveAsOpt)
family1 = famdoc.LoadFamily(doc, FamOpt1())
famdoc.Close(False)
System.IO.File.Delete(save_path)
symbols = family1.Symbols.GetEnumerator()
symbols.MoveNext()
symbol1 = symbols.Current
t1.EnsureInTransaction(doc)
if not symbol1.IsActive: symbol1.Activate()
inst1 = doc.Create.NewFamilyInstance(origin, symbol1, str_typ)
TransactionManager.ForceCloseTransaction(t1)
return inst1.ToDSType(False), family1.ToDSType(False)
except:
message = traceback.format_exc()
return message
else:
return message
def NewForm_background_R16(s1, name1, cat1, isVoid1, mat1, subcat1):
t1 = TransactionManager.Instance
TransactionManager.ForceCloseTransaction(t1)
famdoc = doc.Application.NewFamilyDocument(fam_path)
message = None
temp_path = System.IO.Path.GetTempPath()
sat_path = "%s%s.sat" % (temp_path, name1)
try:
if factor != 1:
s1 = s1.Scale(factor)
sat1 = Geometry.ExportToSAT(s1, sat_path)
satOpt = SATImportOptions()
satOpt.Placement = ImportPlacement.Origin
satOpt.Unit = ImportUnit.Foot
view_fec = FilteredElementCollector(famdoc).OfClass(View)
view1 = None
for v in view_fec:
if str(v.ViewType) in acceptable_views:
view1 = v
break
t1.EnsureInTransaction(famdoc)
satId = famdoc.Import(sat1, satOpt, view1)
opt1 = Options()
opt1.ComputeReferences = True
el1 = famdoc.GetElement(satId)
geom1 = el1.get_Geometry(opt1)
enum = geom1.GetEnumerator()
enum.MoveNext()
geom2 = enum.Current.GetInstanceGeometry()
enum2 = geom2.GetEnumerator()
enum2.MoveNext()
s1 = enum2.Current
famdoc.Delete(satId)
TransactionManager.ForceCloseTransaction(t1)
System.IO.File.Delete(sat_path)
except:
message = traceback.format_exc()
pass
if message == None:
try:
save_path = "%s%s.rfa" % (temp_path, name1)
SaveAsOpt = SaveAsOptions()
SaveAsOpt.OverwriteExistingFile = True
t1.EnsureInTransaction(famdoc)
#set the category
try:
fam_cat = famdoc.Settings.Categories.get_Item(cat1.Name)
famdoc.OwnerFamily.FamilyCategory = fam_cat
except: pass
s2 = FreeFormElement.Create(famdoc,s1)
if isVoid1:
void_par = s2.LookupParameter("Solid/Void")
void_par.Set(1)
void_par2 = famdoc.OwnerFamily.LookupParameter("Cut with Voids When Loaded")
void_par2.Set(1)
else: #voids do not have a material value
try:
mat_fec = FilteredElementCollector(famdoc).OfClass(Material)
for m in mat_fec:
if m.Name == mat1:
fam_mat = m
break
mat_par = s2.LookupParameter("Material")
mat_par.Set(fam_mat.Id)
except: pass
#apply same subcategory code as before
#set subcategory
try:
#create new sucategory
fam_subcat = famdoc.Settings.Categories.NewSubcategory(fam_cat, get_Item(subcat1.Name))
#assign the mataterial(fam_mat.Id) to the subcategory
fam_subcat.Material = famdoc.GetElement(fam_mat.Id)
#assign the subcategory to the element (s2)
s2.Subcategory = fam_subcat
except: pass
TransactionManager.ForceCloseTransaction(t1)
famdoc.SaveAs(save_path, SaveAsOpt)
family1 = famdoc.LoadFamily(doc, FamOpt1())
famdoc.Close(False)
System.IO.File.Delete(save_path)
symbols = family1.GetFamilySymbolIds().GetEnumerator()
symbols.MoveNext()
symbol1 = doc.GetElement(symbols.Current)
t1.EnsureInTransaction(doc)
if not symbol1.IsActive: symbol1.Activate()
inst1 = doc.Create.NewFamilyInstance(origin, symbol1, str_typ)
TransactionManager.ForceCloseTransaction(t1)
return inst1.ToDSType(False), family1.ToDSType(False)
except:
message = traceback.format_exc()
return message
else:
return message
if len(geom) == len(names) == len(category) == len(isVoid) == len(material) == len(subcategory):
if isRvt2014:
OUT = output1(map(NewForm_background, geom, names, category, isVoid, material, subcategory))
else:
OUT = output1(map(NewForm_background_R16, geom, names, category, isVoid, material, subcategory))
elif len(geom) == len(names):
padded = PadLists((geom, category, isVoid, material, subcategory))
p_category = padded[1]
p_isVoid = padded[2]
p_material = padded[3]
p_subcategory = padded [4]
if isRvt2014:
OUT = output1(map(NewForm_background, geom, names, p_category, p_isVoid, p_material, p_subcategory))
else:
OUT = output1(map(NewForm_background_R16, geom, names, p_category, p_isVoid, p_material, subcategory))
else: OUT = "Make sure that each geometry\nobject has a unique family name."
Update:
Was able to get it working:
try:
#create new sucategory
fam_subcat = famdoc.Settings.Categories.NewSubcategory(famdoc.OwnerFamily.FamilyCategory, subcat1)
#assign the mataterial(fam_mat.Id) to the subcategory
#fam_subcat.Material = famdoc.GetElement(fam_mat.Id)
#assign the subcategory to the element (s2)
s2.Subcategory = fam_subcat
except: pass
As I answered on your initial query per email, what you are aiming for sounds perfectly feasible to me in the Revit API. Congratulations on getting as far as you have. Looking at the link to the Revit API help file and developer guide that you cite above, it seems that the code has to be executed in the family document while defining the family. The context in which you are trying to execute it is not clear. Have you used EditFamily to open the family definition document? What context are you executing in?
I need someone's expertise on this exporting problem of mine.
How it works: Select a camera (animated or not is optional) >> File >> Export Selection >> File Type : .chan (need to load this script as a plugin)
Here's where the problem starts. It is able to create a .text file, however, it is not 'exporting' or writing out the contents into the text file and the file size is of zero bytes.
I am making use of the current API that it has been coded, modifying the code to add in some maya cmds
Can someone kindly help me out?
import math, sys, string, os
import maya.OpenMaya as OpenMaya
import maya.OpenMayaMPx as OpenMayaMPx
import maya.OpenMayaAnim as OpenMayaAnim
import maya.cmds as cmds
import maya.mel as mel
kPluginTranslatorTypeName = "chan Export/Import"
kVersionNumber = "0.5a"
camSel = []
win_name = "chan_window"
class CustomNodeTranslator(OpenMayaMPx.MPxFileTranslator):
def __init__(self):
OpenMayaMPx.MPxFileTranslator.__init__(self)
def haveWriteMethod(self):
return True
def haveReadMethod(self):
return True
def filter(self):
return " .chan"
def defaultExtension(self):
return "chan"
def writer( self, fileObject, optionString, accessMode ):
try:
fullName = fileObject.fullName()
fileHandle = open(fullName,"w")
selectList = OpenMaya.MSelectionList()
OpenMaya.MGlobal.getActiveSelectionList(selectList)
node = OpenMaya.MObject()
depFn = OpenMaya.MFnDependencyNode()
path = OpenMaya.MDagPath()
iterator = OpenMaya.MItSelectionList(selectList)
animationTime = OpenMayaAnim.MAnimControl()
maxTime = int(animationTime.maxTime().value())
minTime = int(animationTime.minTime().value())
while (iterator.isDone() == 0):
iterator.getDependNode(node)
depFn.setObject(node)
iterator.getDagPath(path, node)
cameraObject = OpenMaya.MFnCamera(path)
transform = OpenMaya.MFnTransform(path)
chanMe = fileExporter(transform, minTime, maxTime, cameraObject)
for all in chanMe():
fileHandle.write(all)
iterator.next()
fileHandle.close()
except:
sys.stderr.write( "Failed to write file information\n")
raise
def processLine( self, lineStr ):
self.importTheChan.writeFrameData(lineStr)
class fileExporter():
""" module for exporting chan files from application. arguments: object, startFrame, endFrame """
def __init__(self, transform, startAnimation, endAnimation, cameraObj):
self.fileExport = []
self.transform = transform
self.cameraObj = cameraObj
self.start = startAnimation
self.end = endAnimation
self.exportWin()
def exportWin(self):
self.expWindow = cmds.window(w=150, h=100, title = "Export Selection" )
cmds.columnLayout( adjustableColumn=True )
form = cmds.formLayout(numberOfDivisions=100)
cmds.radioCollection()
self.chk1 = cmds.radioButton( label='option1', onc = self.opt1On, ofc = self.opt1Off )
self.chk2 = cmds.radioButton( label='option2', onc = self.opt2On, ofc = self.opt2Off )
self.okayBtn = cmds.button(label='okay!', command=self.runSel, width=150, height=35)
cmds.formLayout(form, edit=True, attachForm=[\
(self.chk1, 'top', 15),\
(self.chk1, 'left', 15),\
(self.chk2, 'top', 30),\
(self.chk2, 'left', 15),\
(self.okayBtn, 'top', 50),\
(self.okayBtn, 'left', 15)])
cmds.showWindow( self.expWindow )
def opt1On(self, args):
print "User checked option1"
startAnimation = cmds.playbackOptions(query=True, minTime=True)
endAnimation = cmds.playbackOptions(query=True, maxTime=True)
self.start = startAnimation
self.end = endAnimation
def opt1Off(self, args):
print "User un-checked option1"
cmds.radioButton(self.chk2, edit = True, enable = True)
self.start = ""
self.end = ""
def opt2On(self, args):
print "User checked option2"
startAnimation = cmds.findKeyframe(which='first')
endAnimation = cmds.findKeyframe(which='last')
self.start = startAnimation
self.end = endAnimation
#self.start.append(int(startAnimation))
#self.end.append(int(endAnimation))
def opt2Off(self, args):
print "User un-checked option2"
self.start = ""
self.end = ""
def runSel(self, args):
chkVal1 = cmds.radioButton(self.chk1, query=True, sl=1)
chkVal2 = cmds.radioButton(self.chk2, query=True, sl=1)
if chkVal1 == 1:
print "opt1 Pressed!"
print self.start
print self.end
self.test()
self.closeWindow()
elif chkVal2 == 1:
print "opt2 Pressed!"
print self.start
print self.end
self.test()
self.closeWindow()
else:
cmds.warning("Check an option")
def closeWindow(self):
cmds.deleteUI(self.expWindow, window=True)
def test(self):
self.actualExp(self.transform, self.start, self.end, self.cameraObj)
def actualExp(self, transform, startAnimation, endAnimation, cameraObj):
mayaGlobal = OpenMaya.MGlobal()
mayaGlobal.viewFrame(OpenMaya.MTime(1))
# Converts the float arguement into integer
for i in range(int(startAnimation), int(endAnimation + 1)):
focalLength = cameraObj.focalLength()
vFilmApp = cameraObj.verticalFilmAperture()
focalOut = 2 math.degrees(math.atan(vFilmApp 25.4/ (2 focalLength)))
myEuler = OpenMaya.MEulerRotation()
spc = OpenMaya.MSpace.kWorld
trans = transform.getTranslation(spc)
rotation = transform.getRotation(myEuler)
rotVector = OpenMaya.MVector(myEuler.asVector())
self.fileExport.append((str(i) + '\t' + str(trans[0]) + "\t" + str(trans[1]) + "\t" + str(trans[2]) + "\t" + str(math.degrees(rotVector[0])) + "\t" + str(math.degrees(rotVector[1])) + "\t" + str(math.degrees(rotVector[2])) + "\t" + str(focalOut) + "\n"))
mayaGlobal.viewFrame(OpenMaya.MTime(i+1))
def __call__(self, args):
return self.fileExport
def radianToDegree(self, radians):
outDegrees = 0.0
outDegrees = (float(radians) / (math.pi)) 180
return outDegrees
# creator
def translatorCreator():
return OpenMayaMPx.asMPxPtr( CustomNodeTranslator() )
# initialize the script plug-in
def initializePlugin(mobject):
mplugin = OpenMayaMPx.MFnPlugin(mobject)
try:
mplugin.registerFileTranslator(kPluginTranslatorTypeName, None, translatorCreator)
except:
sys.stderr.write( "Failed to register translator: %s" % kPluginTranslatorTypeName )
raise
# uninitialize the script plug-in
def uninitializePlugin(mobject):
mplugin = OpenMayaMPx.MFnPlugin(mobject)
try:
mplugin.deregisterFileTranslator( kPluginTranslatorTypeName )
except:
sys.stderr.write( "Failed to deregister translator: %s" % kPluginTranslatorTypeName )
raise
the __call__ method is what's supposed to provide the contents of the file. It returns self.fileExport, which is an empty list that is not getting populated.
The problem here is the writer method of the plugin will not wait for your exportWin UI to return the user inputs when you call
chanMe = fileExporter(transform, minTime, maxTime, cameraObject)
By the time the user has entered the inputs, the statements that follow have already been executed:
for all in chanMe():
fileHandle.write(all)
iterator.next()
fileHandle.close()
That is why plugin-based file exporters like these have their options UI tucked away in the option box. These options will be passed prior to call to the plugin's writer().
You will need to export your options UI code (in a certain specific format) using MEL in another script file and specify the name of that file in the optionsScriptName param of the registerFileTranslator call. There is a communication protocol that needs to be followed for communication between this options UI and the writer plugin itself. RobTheBloke's awesome post illustrates this process.
Ideally, the writer() method should have all the details it needs for computing and exporting without having to wait for user input.
Alternatively, if you prefer to have your own UI window and more control over the flow of things, you could write the exporter not as a plugin, but as a simple MEL/Python module. You could still use the power of the API.
Hope this helped!