Call a Python function with a variable name from a QMainWindow - python

I am trying to find an efficient and secure way to call different functions based on the transaction name the user enters. There are a 100+ different transactions. A 100 "IF" would do the job, however, I want to find a more efficent way to call the transaction. The "eval" would do it, but I read that this should not be used, as the user can enter any transaction name.
from operator import methodcaller
import sys
from PyQt5.QtWidgets import (QMainWindow,QToolBar,QLineEdit,
QLabel, QApplication)
def one():
print ("1")
def two():
print ("2")
def three():
print("3")
class main_menu(QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
menuBar = self.menuBar()
self.ToolBar = QToolBar()
self.ToolBar.setMovable(False)
self.addToolBar(self.ToolBar)
self.tcode = QLineEdit(maxLength=5)
self.tcode.returnPressed.connect(self.tcode_action)
self.ToolBar.addWidget(QLabel(" Transaction : "))
self.ToolBar.addWidget(self.tcode)
def tcode_action(self):
## if self.tcode.text() == "one":
## one()
## if self.tcode.text() == "two":
## two()
## if self.tcode.text() == "three":
## three()
## eval(self.tcode.text()+"()")
def main(args):
app = QApplication(sys.argv)
mm = main_menu()
mm.show()
sys.exit(app.exec_())
if __name__=="__main__":
main(sys.argv)

Global variables can be accessed via globals() in python.
You can use:
def tcode_action(self):
fn = globals().get(self.tcode.text())
if fn:
fn()
else:
print("invalid input")

One option could be to use a QComboBox to restrict the function set. You can also use an Enum to enumerate valid functions.
from enum import Enum
from functools import partial
# function definitions
def fcn_1( x ):
print( 'F1' )
def fcn_2( x ):
print( 'F2' )
# valid functions Enum
# Must wrap functions in partial so they are not defined as methods.
# See below post for more details.
class ValidFunctions( Enum ):
Echo = partial( fcn_1 )
Increment = partial( fcn_2 )
# function selection ComboBox
cb = QComboBox()
cb.addItem( 'Echo' )
cb.AddItem( 'Increment' )
# connecting the signal
def call_function():
fcn = ValidFunctions[ cb.currentText() ]
fcn()
cb.currentIndexChanged.connect( call_function )
Note: I haven't debugged this code.
How to define enum values that are functions

I will do this with this code now:
def tcode_action(self):
try:
func = getattr(self,self.tcode.text())
func()
except:
pass
Any comments to this?

Related

Custom Search menu to add my own operators?

Hi I'd like to create a custom search menu to add my own operators. Much like the blender F3 command search:
If possible I'd just like to add any type of operators I want to it. So for example I would have an Animation search menu, or a modelling search menu. Or even just a menu with my own custom scripts in it.
Is this possible in blender?
I found the answer with some help. Pretty much you need to use:
invoke_search_popup
You'll need to create an enum on an operator then run the invoke_search_popup on the operator.
Here is the example code from Gorgious, and Yilmazz:
https://blender.stackexchange.com/questions/247695/invoke-search-popup-for-a-simple-panel
import bpy
import re
import json
items = (("Metal", "Metal", ""),
("Plastic", "Plastic", ""),
("Glass", "Glass", ""),
("Shadow", "Shadow", ""),
)
PROPS = [
('material', bpy.props.PointerProperty(type=bpy.types.Material, name='Material')),
]
# == OPERATORS
class MYCAT_OT_search_popup(bpy.types.Operator):
bl_idname = "object.search_popup"
bl_label = "Material Renamer"
bl_property = "my_enum"
my_enum: bpy.props.EnumProperty(items = items, name='New Name', default=None)
#classmethod
def poll(cls, context):
return context.scene.material # This prevents executing the operator if we didn't select a material
def execute(self, context):
material = context.scene.material
material.name = self.my_enum
return {'FINISHED'}
def invoke(self, context, event):
wm = context.window_manager
wm.invoke_search_popup(self)
return {'FINISHED'}
# == PANELS
class ObjectRenamerPanel(bpy.types.Panel):
bl_idname = 'VIEW3D_PT_object_renamer'
bl_label = 'Material Renamer'
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
def draw(self, context):
col = self.layout.column()
row = col.row()
row.prop(context.scene, "material")
col.operator('object.search_popup', text='Rename') #Display the search popup operator
# == MAIN ROUTINE
CLASSES = [
MYCAT_OT_search_popup,
ObjectRenamerPanel,
]
def register():
for (prop_name, prop_value) in PROPS:
setattr(bpy.types.Scene, prop_name, prop_value)
for klass in CLASSES:
bpy.utils.register_class(klass)
def unregister():
for (prop_name, _) in PROPS:
delattr(bpy.types.Scene, prop_name)
for klass in CLASSES:
bpy.utils.unregister_class(klass)
if __name__ == '__main__':
register()

python ipywidges: how to pass an extra variable to a handler

I would like to observe an ipywidget text input
out4 = wd.Output()
wd_input_text4 = wd.Text(value="",
placeholder='placeholder',
description='Number:',
)
def method4(sender):
with out4:
out4.clear_output()
print(wd_input_text4.value)
wd_input_text4.observe(method4, names=['value'], type='change')
display(wd.VBox([wd_input_text4,out4]))
What I would like is to pass to the handler an extra variable, just call it A.
pseudocode would be (it does not work):
def method4(sender, A):
with out4:
out4.clear_output()
print(wd_input_text4.value, A)
wd_input_text4.observe(method4, names=['value'], type='change', A)
Is that even possible?
How can I do it?
class Method4:
def __init__(self,ctx):
self.ctx = ctx
def method4(self, sender):
with out4:
out4.clear_output()
print(wd_input_text4.value, self.ctx)
wd_input_text4.observe(Method4(A).method4, names=['value'], type='change')
Using functools.partial would avoid a class to hold the extra state.
import ipywidgets as widgets
import functools
a = widgets.IntText()
def observe_val(to_add, widg):
val = widg['new']
print(val + to_add)
a.observe(functools.partial(observe_val, 4), names=['value'])
display(a)

blender python increment an integer

I'm pretty sure this has been answered, but I can't seem to locate it.
What I want is a python script for Blender that creates a custom tab that contains a button. When that button is pressed, it prints the value of an integer and increments it, so that when you press the button again, it shows an incremented value. Everything seems to work, except for the incremental part.
Here is the code I am using at the moment:
===
import bpy
from bpy.props import (IntProperty,)
from bpy.types import (Panel, Operator, AddonPreferences, PropertyGroup,)
def main(context):
my_number += 1
print(str(my_number))
class MySettings(PropertyGroup):
my_number = IntProperty(
name="Int property",
description="This is an integer.",
default = 1
)
class AddOne(bpy.types.Operator):
"""This is an operator"""
bl_idname = "op.add_one"
bl_label = "Increment by 1"
def execute(self, context):
main(context)
return {'FINISHED'}
class CreatePanel(bpy.types.Panel):
bl_label = "Render Setup Panel"
bl_idname = "OBJECT_PT_hello"
bl_space_type = 'NODE_EDITOR'
bl_region_type = 'TOOLS'
bl_category = "Increment by 1 Tab"
def draw(self, context):
layout = self.layout
obj = context.object
row = layout.row()
row.operator("op.add_one")
def register():
bpy.utils.register_class(AddOne)
bpy.utils.register_class(MySettings)
bpy.utils.register_class(CreatePanel)
def unregister():
bpy.utils.unregister_class(AddOne)
bpy.utils.unregister_class(MySettings)
bpy.utils.unregister_class(CreatePanel)
if __name__ == "__main__":
register()
===
However, when I press the button 'Increment by 1', I get the following error:
"local variable 'my_number' referenced before assignment"
The point of this exercise is just to create an integer variable, store it, then increment it's value and print it out.
EDIT: I added the actual code, rather than an image of it.
The variable my_number is defined in the class MySettings - it can only be accessed through that class, whether that is inside a method that is also part of the class (self.my_number) or directly as a property that is part of an instance of the class (settings_instance.my_number).
You need to find a place outside of the operator and panel to store persistent variables. Adding a custom property to the object or scene types are common options. As you are showing your panel in the node editor, maybe you will want to add it to the material to keep it specific to a material, instead of global to the scene. You define these properties in the addons register() and remove them in unregister().
def register():
bpy.types.Scene.my_settings = bpy.props.PointerProperty(type=MySettings)
def unregister():
del bpy.types.Scene.my_settings
Then in your operator (or main() function) and your panel you can access the variable through the context paramater.
context.scene.my_settings.my_number += 1
Putting that together into your example, with a label to show the value -
import bpy
from bpy.props import (IntProperty,)
from bpy.types import (Panel, Operator, AddonPreferences, PropertyGroup,)
def main(context):
context.scene.my_settings.my_number += 1
print(str(context.scene.my_settings.my_number))
class MySettings(PropertyGroup):
my_number: IntProperty(
name="Int property",
description="This is an integer.",
default = 1
)
class AddOne(Operator):
"""This is an operator"""
bl_idname = "op.add_one"
bl_label = "Increment by 1"
def execute(self, context):
main(context)
return {'FINISHED'}
class CreatePanel(Panel):
bl_label = "Render Setup Panel"
bl_idname = "OBJECT_PT_hello"
bl_space_type = 'NODE_EDITOR'
bl_region_type = 'UI'
bl_category = "Increment by 1 Tab"
def draw(self, context):
layout = self.layout
obj = context.object
row = layout.row()
row.operator("op.add_one")
row = layout.row()
row.label(text='Value is: '+str(context.scene.my_settings.my_number))
def register():
bpy.utils.register_class(AddOne)
bpy.utils.register_class(MySettings)
bpy.utils.register_class(CreatePanel)
bpy.types.Scene.my_settings = bpy.props.PointerProperty(type=MySettings)
def unregister():
bpy.utils.unregister_class(AddOne)
bpy.utils.unregister_class(MySettings)
bpy.utils.unregister_class(CreatePanel)
del bpy.types.Scene.my_settings
if __name__ == "__main__":
register()
You will find blender.stackexchange a better place to ask for blender specific python help.
Generally this problem "local variable 'my_number' referenced before assignment" comes when you have 'my_number' variable in code and you had not initialized that variable at top of your code or before using that variable do one thing .
Declare my_number=0 and then do your calculation on my_number variable .

QCheckBox state change PyQt4

I'm trying to implement a system in PyQt4 where unchecking a checkbox would call function disable_mod and checking it would call enable_mod. But even though state is changing the checkboxes call the initial function they started with. For this case if an already checked box was clicked it'd always keep calling the disable_mod function! I don't understand why is this happening? Can you guys help me out here a little bit? Here's my code:
from PyQt4 import QtCore, QtGui
from os import walk
from os.path import join
import sys
def list_files_regex(dir):
l = []
for (root, dirnames, filenames) in walk(dir):
for d in dirnames:
list_files_regex(join(root, d))
l.extend(filenames)
return l
directory = "J:/test_pack"
directory = join(directory, "/mods")
count = 0
for y in list_files_regex(directory):
print y
count += 1
print count
class ModEdit(QtGui.QMainWindow):
def __init__(self, title, icon, x, y, w, h):
super(ModEdit, self).__init__()
self.setWindowTitle(title)
self.setWindowIcon(QtGui.QIcon(icon))
self.setGeometry(x, y, w, h)
self.choices = []
self.init()
def init(self):
scroll_widget = QtGui.QScrollArea()
sub_widget = QtGui.QWidget()
v_layout = QtGui.QVBoxLayout()
for y in list_files_regex(directory):
tmp = QtGui.QCheckBox(y, self)
tmp.resize(tmp.sizeHint())
if tmp.text()[len(tmp.text()) - 8: len(tmp.text())] != 'disabled':
tmp.setChecked(True)
# if tmp.isChecked() == 0:
# tmp.stateChanged.connect(self.enable_mod)
# if tmp.isChecked():
# tmp.stateChanged.connect(self.disable_mod)
# v_layout.addWidget(tmp)
self.choices.append(tmp)
print self.choices
for choice in self.choices:
v_layout.addWidget(choice)
if choice.isChecked():
choice.stateChanged.connect(self.disable_mod)
else:
choice.stateChanged.connect(self.enable_mod)
sub_widget.setLayout(v_layout)
scroll_widget.setWidget(sub_widget)
self.setCentralWidget(scroll_widget)
self.show()
def enable_mod(self):
print "ENABLE_TEST"
print self.choices[1].isChecked()
def disable_mod(self):
print "DISABLE_TEST"
print self.choices[1].isChecked()
def test(self):
print 'test'
for ch in self.choices:
if ch.isChecked():
ch.stateChanged.connect(self.disable_mod)
else:
ch.stateChanged.connect(self.enable_mod)
class Rename(QtCore.QObject):
enable = QtCore.pyqtSignal
disable = QtCore.pyqtSignal
app = QtGui.QApplication(sys.argv)
ex = ModEdit("Minecraft ModEdit", "ModEdit.png", 64, 64, 640, 480)
sys.exit(app.exec_())
The problem is that you're only connecting the checkbox stateChanged signal once during initialization. After the state of the checkbox changes, you're not disconnecting the signal and reconnecting it to the correct slot.
You'll need to connect the stateChanged signal to an intermediary slot that will decide which function to call based on the checkbox state. Since you're using the same slot for multiple checkboxes, it's probably best to also pass the checkbox to the slot as well.
from functools import partial
def init(self):
...
for tmp in list_of_checkboxes:
enable_slot = partial(self.enable_mod, tmp)
disable_slot = partial(self.disable_mod, tmp)
tmp.stateChanged.connect(lambda x: enable_slot() if x else disable_slot())
def enable_mod(self, checkbox):
print "ENABLE_TEST"
print checkbox.isChecked()
def disable_mod(self, checkbox):
print "DISABLE_TEST"
print checkbox.isChecked()
Alternatively, since we are now passing the checkbox to the slots, you could just use a single slot and check the checkbox state inside the slot
def init(self):
...
for tmp in list_of_checkboxes:
slot = partial(self.enable_disable_mod, tmp)
tmp.stateChanged.connect(lambda x: slot())
def enable_disable_mod(self, checkbox):
if checkbox.isChecked():
...
else:
...

Differentiating between signal sources in PySide

Is there trivial or elegant way to differentiate between many same-type signal sources in PySide/PyQt?
I am learning PySide. I have written simple application, which multiplies two numbers from two different QLineEdit() objects. Result is displayed in third QLineEdit.
Multiplier and multiplicand QLineEdit.textChanged() signals are connected to one method (TxtChanged). In this method i have to differentiate between signal sources. After some trials I figured out some workaround based upon placeholder text (4 lines below "is there another way?" comment in my code)
code:
import sys
from PySide import QtGui, QtCore
class myGUI(QtGui.QWidget):
def __init__(self, *args, **kwargs):
QtGui.QWidget.__init__(self, *args, **kwargs)
self.multiplier = 0
self.multiplicand = 0
self.myGUIInit()
def myGUIInit(self):
# input forms
a1_label = QtGui.QLabel("a1")
a1_edit = QtGui.QLineEdit()
a1_edit.setPlaceholderText("a1")
a2_label = QtGui.QLabel("a2")
a2_edit = QtGui.QLineEdit()
a2_edit.setPlaceholderText("a2")
# output form
a1a2_label = QtGui.QLabel("a1*a2")
self.a1a2_edit = QtGui.QLineEdit()
self.a1a2_edit.setReadOnly(True)
# forms events
a1_edit.textChanged.connect(self.TxtChanged)
a2_edit.textChanged.connect(self.TxtChanged)
# grid
grid = QtGui.QGridLayout()
grid.setSpacing(10)
grid.addWidget(a1_label,1,0)
grid.addWidget(a1_edit,1,1)
grid.addWidget(a2_label,2,0)
grid.addWidget(a2_edit,2,1)
grid.addWidget(a1a2_label,3,0)
grid.addWidget(self.a1a2_edit,3,1)
self.setLayout(grid)
self.setGeometry(100,100,200,200)
self.setWindowTitle("a*b")
self.show()
def TxtChanged(self,text):
sender = self.sender()
sender_text = sender.text()
if sender_text == '': sender_text = '0'
# is there another way?
if sender.placeholderText() == 'a1':
self.multiplicand = sender_text
else:
self.multiplier = sender_text
product = int(self.multiplier) * int(self.multiplicand)
print(self.multiplier,self.multiplicand,product)
self.a1a2_edit.setText(str(product))
def main():
app = QtGui.QApplication(sys.argv)
mainWindow = myGUI()
sys.exit(app.exec_())
main()
best regards,
ostrzysz
You can use the functools.partial function - and therefore connect your signals to straight to your method/function but rather to a python object which will automatically call your function with some extra data you pass it:
from functools import partial
...
....
a1_edit.textChanged.connect(partial(self.TxtChanged, a1_edit))
a2_edit.textChanged.connect(partial(self.TxtChanged, a2_edit))
...
def TxtChanged(self,sender, text):
# and here you have the "sender" parameter as it was filled in the call to "partial"
...
partials is part of the stdlib, and is very readable, but one can always use lambda instead of partial for the same effect -
a1_edit.textChanged.connect(lambda text: self.TxtChanged(a1_edit, text))
In this way the object yielded by the lambda expression will be a temporary function that will use the values for "self" and "a1_edit" from the current local variables (at the time the button is clicked), and the variable named "text" will be supplied by Pyside's callback.
One thing that bugs me most in your code is that you are using placeholderText to differentiate. QObjects has another property called objectName that is more suitable for your task. And, you don't need to use sender.text() to get the text of QLineEdit. textChanged already sends it, so you will have it in your text parameter.
Also, using a dictionary instead of two separate variables (multiplier and multiplicand) will simplify your code further.
Here is the changed code:
class myGUI(QtGui.QWidget):
def __init__(self, *args, **kwargs):
QtGui.QWidget.__init__(self, *args, **kwargs)
self.data = {"multiplier": 0,
"multiplicand": 0}
self.myGUIInit()
def myGUIInit(self):
a1_label = QtGui.QLabel("a1")
a1_edit = QtGui.QLineEdit()
a1_edit.setObjectName("multiplicand")
a2_label = QtGui.QLabel("a2")
a2_edit = QtGui.QLineEdit()
a2_edit.setObjectName("multiplier")
# skipped the rest because same
def TxtChanged(self, text):
sender = self.sender()
# casting to int while assigning seems logical.
self.data[sender.objectName()] = int(text)
product = self.data["multiplier"] * self.data["multiplicand"]
print(self.data["multiplier"], self.data["multiplicand"], product)
self.a1a2_edit.setText(str(product))
Although #jsbueno and #Avaris answered your direct question about signal sources, I wouldn't relay on this sources in your concrete case. You can make instance members a1_edit and a2_edit:
...
self.a1_edit = QtGui.QLineEdit()
...
self.a2_edit = QtGui.QLineEdit()
...
It will simplify your TxtChanged function:
def TxtChanged(self,text):
try:
multiplier = int(self.a1_edit.text())
multiplicand = int(self.a2_edit.text())
except ValueError:
self.a1a2_edit.setText('Enter two numbers')
return
product = multiplier * multiplicand
print(multiplier, multiplicand, product)
self.a1a2_edit.setText(str(product))
Also, instead of handling ValueError exception, you can use QIntValidator for input controls:
self.int_validator = QtGui.QIntValidator()
self.a1_edit.setValidator(self.int_validator)
self.a2_edit.setValidator(self.int_validator)

Categories