I have the following code to enable the file browser using blender:
import bpy
import os
from bpy.props import StringProperty
from bpy_extras.io_utils import ImportHelper
from bpy.types import Operator
sel = ''
class OpenBrowser(bpy.types.Operator):
bl_idname = "open.file"
bl_label = "Select Excel File"
bli_description = "Simulation output excel file"
filter_glob: StringProperty(default = '*.xls;*.xlsx',options = {'HIDDEN'})
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
#somewhere to remember the address of the file
def execute(self, context):
global sel
sel = self.filepath
#self.selected_file = self.filepath
#display = "filepath= "+self.filepath
#print(display) #Prints to console
#Window>>>Toggle systen console
return {'FINISHED'}
def invoke(self, context, event): # See comments at end [1]
context.window_manager.fileselect_add(self)
global sel
sel = self.filepath
#Open browser, take reference to 'self'
#read the path to selected file,
#put path in declared string type data structure self.filepath
return {'RUNNING_MODAL'}
# Tells Blender to hang on for the slow user input
bpy.utils.register_class(OpenBrowser)
#Tell Blender this exists and should be used
# [1] In this invoke(self, context, event) is being triggered by the below command
#but in your script you create a button or menu item. When it is clicked
# Blender runs invoke() automatically.
#execute(self,context) prints self.filepath as proof it works.. I hope.
bpy.ops.open.file('INVOKE_DEFAULT')
print(sel)
The issue I am facing is that I have declared a global variable sel to which I want to save the filepath selected from the user when running the code. However, when I run the script I see that sel has not changed and it is as it was initialized. Could someone please help me on how to access from the class the self.filepath variable? What am I doing wrong here?
If I understand correctly, you want to store that value for later.
I'm not sure why 'sel' doesn't even update in your case, but I think the more correct way would be to use a property like so:
import bpy
# Assign a custom property to an existing type.
bpy.types.Scene.my_sel_value = bpy.props.StringProperty(name="Sel")
# Set property value.
bpy.context.scene.my_sel_value = "Foo"
# Get property value.
print(bpy.context.scene.my_sel_value)
Properties can be added to all ID types, but for "global" values, bpy.types.scene us ussualy used. Though there can be multiple scenes in one project and they will have separate values. Property values are stored when Blender closes.
If you are making an addon, you can also store your value in Addon Preferences. This value will be the same for all blender projects.
Related
So this function is a cut-out, from a class that should create and then extract information from an ini file. The problem I have is that the ini file definitely shows that all the added sections exist, but whenever I want the program to extract information or read from the file, it returns [], an empty list. I tried this function in a more basic version using Anaconda Navigator, well it turned out there it worked.
The screenshot shows the ini file.
As seen in the screenshot, the file is indeed not empty and the function successfully wrote to it, problem is whenever the function should print from it or when I want another function to read it, it returns [].
Code is as following:
class cSetup():
import os
import time
import datetime
import configparser as configparser
"""Class creates an ini file, this ini file contains a number of settings which can be read by the get function.
the program will later access this file to check for certain settings.
If the file is not existing yet, a function within the class will create it
"""
config = configparser.ConfigParser()
def write_setup(self):
if os.path.exists("Setup.ini"):
setup_file = open("Setup.ini", "r")
print(self.config.read(setup_file))
setup_file.close()
else:
"""This function is the initial setup, it writes all the necessary information to a .ini file."""
global config
"""Asks user for a name"""
Name = input("Please enter your name, note this can be changed in the settings""\n"
"Please enter the name here: ")
self.time.sleep(1)
"""asks user for prefered input method"""
PreferredInput = input("Input method required: 0 Shutdown, 1 Voice Commands, 2 Text Commands, 3 Dev Mode: ")
setup_file = open("Setup.ini", "w")
setup_file
self.config.write(setup_file)
self.config.add_section("Setup state")
self.config.set("Setup state", "Activated?", "True") # Convert to Bool later
section_option = "Settings"
self.config.add_section(section_option)
self.config.set(section_option, "Name", Name)
self.config.set(section_option, "Preferred Input", PreferredInput) # Convert to int later
self.config.write(setup_file)
setup_file = open("Setup.ini", "r")
print(self.config.read(setup_file))
setup_file.close()
I'm working on a Python desktop app using wxPython and SQLite. The SQLite db is basically being used as a save file for my program so I can save and backup and reload the data being entered. I've created separate classes for parts of my UI so make it easier to manage from the "main" window. The problem I'm having is that each control needs to access the database, but the filename, and therefore the connection name, needs to be dynamic. I originally created a DBManager class that hardcoded a class variable with the connection string, which worked but didn't let me change the filename. For example
class DBManager:
conn = sqlite3.Connection('my_file.db')
#This could then be passed to other objects as needed
class Control1:
file = DBManager()
class Control2:
file = DBManager()
etc.
However, I'm running into a lot of problems trying to create this object with a dynamic filename while also using the same connection across all controls. Some examples of this I've tried...
class DBManager:
conn = None
def __init__(self):
pass
def __init__(self, filename):
self.conn = sqlite3.Connection(filename)
class Control1:
file = DBManager()
class Control2:
file = DBManager()
The above doesn't work because Python doesn't allow overloading constructors, so I always have to pass a filename. I tried adding some code to the constructor to act differently based upon whether the filename passed was blank or not.
class DBManager:
conn = None
def __init__(self, filename):
if filename != '':
self.conn = sqlite3.Connection(filename)
class Control1:
file = DBManager('')
class Control2:
file = DBManager('')
This let me compile, but the controls only had an empty connection. The conn object was None. It seems like I can't change a class variable after it's been created? Or am I just doing something wrong?
I've thought about creating one instance of DBManager that I then pass into each control, but that would be a huge mess if I need to load a new DB after starting the program. Also, it's just not as elegant.
So, I'm looking for ideas on achieving the one-connection path with a dynamic filename. For what it's worth, this is entirely for personal use, so it doesn't really have to follow "good" coding convention.
Explanation of your last example
You get None in the last example because you are instantiating DBManager in Control1 and Control2 with empty strings as input, and the DBManager constructor has an if-statement saying that a connection should not be created if filename is just an empty string. This leads to the self.conn instance variable never being set and any referal to conn would resolve to the conn class variable which is indeed set to None.
self.conn would create an instance variable only accessible by the specific object.
DBManager.conn would refer to the class variable and this is what you want to update.
Example solution
If you only want to keep one connection, you would need to do it with e.g. a. class variable, and update the class variable every time you interact with a new db.
import sqlite3
from sqlite3 import Connection
class DBManager:
conn = None
def __init__(self, filename):
if filename != '':
self.filename = filename
def load(self) -> Connection:
DBManager.conn = sqlite3.Connection(self.filename) # updating class variable with new connection
print(DBManager.conn, f" used for {self.filename}")
return DBManager.conn
class Control1:
db_manager = DBManager('control1.db')
conn = db_manager.load()
class Control2:
db_manager = DBManager('control2.db')
conn = db_manager.load()
if __name__ == "__main__":
control1 = Control1()
control2 = Control2()
would output the below. Note that the class variable conn refers to different memory addresses upon instantiating each control, showing that it's updated.
<sqlite3.Connection object at 0x10dc1e1f0> used for control1.db
<sqlite3.Connection object at 0x10dc1e2d0> used for control2.db
I have JSON data in my main file, lets call this main.py (this uses Selenium driver):
main.py
def get_user_data(browser):
browser.get(JSON_URL)
user_data = json.loads(browser.find_element_by_tag_name('body').text)
browser.back()
return user_data
To get a value from this, I use:
name = user_data['name']
This works just fine. This is a larger program and I would like to have these locators in another file called locators.py using a class so if a locator changes, I just change it in one place:
locators.py
class UserDataLocators:
NAME = user_data['name']
Now, the above fails because 'user_data' is not defined. I like this approach since I have other selenium locators in this file in a separate class. This may be a super simple fix, but how would I get the above to work so in my main.py file, I can import UserDataLocators and do the following:
from locators import UserDataLocators
name = UserDataLocators.NAME
Thanks!
Update 1:
#maxhaz So with what you wrote, it gave me an idea to maybe just use your UserData class to parse everything instead of just storing locators. Basically this is what I'm looking to accomplish. The user_data['name'] will get updated/changed as other modules interact with it. I'd like to store the user_data in locators.py and be able to access it and update it with the other modules, and after everytime it is update, each module can get the updated data.
For example, locators.py has the user_data as None to start since no browser is initiated. Main.py will be the first to interact with it and passes the browser to get the initial user_data['name'] value. Module2.py will also import from locators.py and get that same user_data that main.py just updated. Module2 will update the user_data['name']. Once this happens, is this new user_data['name'] now updated for main.py and locators.py?
#locators.py
user_data = None
class UserData:
def __init__(self, browser):
self.browser = browser
self.name = self.get_user_data()['name']
def get_user_data(self):
self.browser.get(JSON_URL)
user_data = json.loads(browser.find_element_by_tag_name('body').text)
browser.back()
return user_data
#main.py
import locators
locators.user_data = UserData(browser).name
#module2.py
import locators
## do something here that modifies the actual user data that gets pulled ##
locators.user_data = UserData(browser).name
You probably want to move the get_user_data function in the class:
#locators.py
class UserDataLocators:
def __init__(self, browser):
self.browser = browser
self.name = self.get_user_data()['name']
def get_user_data(self):
self.browser.get(JSON_URL)
user_data = json.loads(browser.find_element_by_tag_name('body').text)
browser.back()
return user_data
#main.py
from locators import UserDataLocators
name = UserDataLocators(browser).name
My app has a simple process, the user uploads a gif file and then the gif is conveted to frames that are saved as objects. For the upload_to part of my gif, I run a function content_file_name() that uses uuid to create a folder path. I want the image frames to be saved to the same folder as the gif. Problem is, I can't set the path as a variable as much as I try. The variable needs to be defined first, but if I define it, it doesn't change no matter what I do. Here's what I got now:
currentFilePath = '' # Defining the variable
def content_file_name(instance, filename):
ext = ''.join(filename.split())[:-4]
foldername = "%s/%s" % (uuid.uuid4(), ext)
store_random_path('/'.join(['documents', str(foldername)])) # Running a function to change the variable
return '/'.join(['documents', str(foldername), filename])
def store_random_path(path):
currentFilePath = str(path) # The variable should be changed here
class Document(models.Model):
docfile = models.ImageField(upload_to=content_file_name)
def create_documentfiles(self):
gif = Image.open(self.docfile.path)
frames = [frame.copy() for frame in ImageSequence.Iterator(gif)]
basename, _ext = os.path.splitext(self.docfile.name)
for index, frame in enumerate(frames):
buffer = BytesIO()
frame.convert('RGB').save(fp=buffer, format='JPEG')
destname = "{}{}.png".format(basename, index)
finalImage = InMemoryUploadedFile(buffer, None, destname, 'image/jpeg', frame.tell, None)
imageToSave = DocumentImage(imagefile=finalImage)
imageToSave.save()
class DocumentImage(models.Model):
imagefile = models.ImageField(upload_to=currentFilePath) # Trying to save using the variable as path
image = models.ForeignKey(Document, related_name='Image', null=True, on_delete=models.CASCADE)
So, when the DocumentImage gets saved, it should have seen the variable as path, but it doesn't. Instead it just saves to the initially declared '' which is the root of my media file. Not sure if what I'm trying is possible in Python/Django. I've just started learing Python a month ago. Thank you for your time.
You are assigning to a global variable inside a function. You need to use the global keyword for that or you could refactor your code to not use any global variable (see Why are global variables evil?). This is not specific to Django.
The wrong way
global_var = ''
def function():
global_var = 'hello' # Local variable created due to assignment
Here global_var is still ''
The Correct way
global_var = ''
def function():
global global_var
global_var = "hello" # Assignment to the global variable
Here global_var is "Hello"
But all you've done is to set a local variable inside store_random_path. You haven't even returned that variable, let alone changed the global variable with the same name.
But you must not use global variables like this anyway. That would cause serious bugs as any future request would get the same value of the global variable. Don't do this.
I'm not sure why you are trying to set this variable in the first place. You have access to the Document, which contains the docfile which has its path correctly set. You should use that to calculate the path of the frames.
I have a module that needs to update new variable values from the web, about once a week. I could place those variable values in a file & load those values on startup. Or, a simpler solution would be to simply auto-update the code.
Is this possible in Python?
Something like this...
def self_updating_module_template():
dynamic_var1 = {'dynamic var1'} # some kind of place holder tag
dynamic_var2 = {'dynamic var2'} # some kind of place holder tag
return
def self_updating_module():
dynamic_var1 = 'old data'
dynamic_var2 = 'old data'
return
def updater():
new_data_from_web = ''
new_dynamic_var1 = new_data_from_web # Makes API call. gets values.
new_dynamic_var2 = new_data_from_web
# loads self_updating_module_template
dynamic_var1 = new_dynamic_var1
dynamic_var2 = new_dynamic_var2
# replace module place holders with new values.
# overwrite self_updating_module.py.
return
I would recommend that you use configparser and a set of default values located in an ini-style file.
The ConfigParser class implements a basic configuration file parser
language which provides a structure similar to what you would find on
Microsoft Windows INI files. You can use this to write Python programs
which can be customized by end users easily.
Whenever the configuration values are updated from the web api endpoint, configparser also lets us write those back out to the configuration file. That said, be careful! The reason that most people recommend that configuration files be included at build/deploy time and not at run time is for security/stability. You have to lock down the endpoint that allows updates to your running configuration in production and have some way to verify any configuration value updates before they are retrieved by your application:
import configparser
filename = 'config.ini'
def load_config():
config = configparser.ConfigParser()
config.read(filename)
if 'WEB_DATA' not in config:
config['WEB_DATA'] = {'dynamic_var1': 'dynamic var1', # some kind of place holder tag
'dynamic_var2': 'dynamic var2'} # some kind of place holder tag
return config
def update_config(config):
new_data_from_web = ''
new_dynamic_var1 = new_data_from_web # Makes API call. gets values.
new_dynamic_var2 = new_data_from_web
config['WEB_DATA']['dynamic_var1'] = new_dynamic_var1
config['WEB_DATA']['dynamic_var2'] = new_dynamic_var2
def save_config(config):
with open(filename, 'w') as configfile:
config.write(configfile)
Example usage::
# Load the configuration
config = load_config()
# Get new data from the web
update_config(config)
# Save the newly updated configuration back to the file
save_config(config)