22
$\begingroup$

I am new to blender, but not python, and have been asked to create a user-input dependent, stylized animation of a protein network I am working with:

text document >> information processed by python-script >> blender animates

So, if certain parameters in the network are changed, the differences in behavior can be observed visually as well.

Instead of coding in Blender's built-in Text Editor, I have been working in "pycharm". However, when I attempt to run the program through blender, it will throw an error and inform me that all my custom modules cannot be found and imported, though they are all in the same directory. Same goes for all the text documents. The text files in particular are a problem as we are talking about thousands of data-points that can't be manually entered.

All modules and text files are saved in the same directory as the program's master runner.

I am working on a windows 7, 64 bit windows machine.

additional information

the errors:

 # import error: 
 no module named 'x'
 # filenotfounderror:
 [errno2] No such file or directory: 'y'

blender_file_directory: C:\Users\Henry\Documents\Blender\file.blend

python_files_directory: C:\Users\Henry\PycharmProjects\project_name\runner.py

# import of modules:
from module import *

# import of txt file:
numpy.loadtxt('gillespie.txt', unpack=False)

The script is called through blender's own SDK like so:

filename = "C:/Users/Henry/PycharmProjects/project_name/runner.py"
exec(compile(open(filename).read(), filename, 'exec'))

Text files are stored in the same directory as python scripts, so importing them when running the script through python is no problem. Only when it's run through blender does it throw an error.

$\endgroup$
2
  • $\begingroup$ What error is thrown? Could you add the imports and directory structure you use? $\endgroup$
    – stacker
    Commented Jul 8, 2015 at 13:04
  • $\begingroup$ @stacker I hope the information provided in the edit suffices? $\endgroup$ Commented Jul 8, 2015 at 13:18

5 Answers 5

12
$\begingroup$

When you execute a python script inside Blender, the current working directory is not the base directory of your .blend file. You can easily confirm this by importing os and printing os.getcwd(), and that will explain why blender isn't finding these modules you're trying to import.

you'll get the current .blend filepath using bpy.data.filepath, from which you should be able to construct relative paths, or switch pythons working directory. if that's more convenient (remember to switch it back )

$\endgroup$
2
  • $\begingroup$ my bpy.data.filepath is empty, as I haven't created any file. os.getcwd() returns the path of my modules. Yet, I get the error. $\endgroup$
    – McLawrence
    Commented Sep 4, 2017 at 13:06
  • $\begingroup$ feel free to ask a proper new question, that will give you room to describe your scenario and you might get a more pointed answer. $\endgroup$
    – zeffii
    Commented Sep 5, 2017 at 11:38
30
$\begingroup$

A more detailed example of the technique zeffi outlined is illustrated by the following example from http://web.purplefrog.com/~thoth/blender/python-cookbook/import-python.html

import bpy
import sys
import os

dir = os.path.dirname(bpy.data.filepath)
if not dir in sys.path:
    sys.path.append(dir )
    #print(sys.path)

import cityFunctions

# this next part forces a reload in case you edit the source after you first start the blender session
import imp
imp.reload(cityFunctions)

# this is optional and allows you to call the functions without specifying the package name
from cityFunctions import *
$\endgroup$
2
  • 2
    $\begingroup$ This solution works for me. However, it is not clear to me why: printing dir gives me an empty string (len(dir)=0) and dir is not where my modules are, which are at the current working directory. $\endgroup$
    – McLawrence
    Commented Sep 4, 2017 at 13:16
  • 2
    $\begingroup$ maybe having an empty string in your path is equivalent to having . in your path. $\endgroup$
    – Mutant Bob
    Commented Sep 5, 2017 at 14:25
4
$\begingroup$

I have been using this snippet at the beginning of main python script to be able to load other text blocks from the same blend file (without having to save them first as file) because other answers did not do it for me:

import sys
import bpy
for name, text in bpy.data.texts.items():
    if name.endswith('.py') and name[:-3] not in sys.modules:
        sys.modules[name[:-3]] = text.as_module()
$\endgroup$
1
  • $\begingroup$ I tried doing this and I get a runtime error maximum recursion depth exceeded while calling a Python object, just naming the files I want loaded here worked instead of doing the filter. I'm not sure how I'd filter the name of the calling file because __file__ is not defined in the text context. $\endgroup$
    – iluvcapra
    Commented Sep 11, 2020 at 18:37
3
$\begingroup$

Extending Mutant Bob's answer, and because I didn't see this anywhere else, I want to show a way to make relative imports possible as well.

We will assume this example folder structure:

myProjects/
    MainFolder/
        something.py
        SubFolder/
            printHello.py
            blendFileWithSomeScriptOpen.blend

If you try to run it like this:

import bpy
import sys
import os

dir = os.path.dirname(bpy.data.filepath)
if not dir in sys.path:
    sys.path.append(dir)
    #print(sys.path)

from . import printHello  # a dot is a relative path
# more code would follow

Blender will throw you an ImportError:

"ImportError: attempted relative import with no known parent package"

The solution (which seems kind of dumb, but somehow works) is to do two things:

  • Add the parent folder of the MainFolder directory to sys.path
  • give the __package__ variable the name of all its parent directories from the MainFolder in order, seperated by dots. (In this case: __package__ = "MainFolder.SubFolder"

Now the code looks like this:

import bpy
import sys
import os
import pathlib

# pathlib.Path allows you to easily deal with paths, such as getting parent paths
path = pathlib.Path(bpy.data.filepath)
myProjects_dir = path.parent.parent.parent.resolve() 
myProjects_dir = str(myProjects_dir) #very important, otherwise it will be a Path-object and unusable for sys.path

print(myProjects_dir) #will print something like "C:\ ... \myProjects", which is the parent folder of our Main Folder
if not myProjects_dir in sys.path:
    sys.path.append(myProjects_dir)

__package__ = "MainFolder.SubFolder"

from . import printHello  # works
from .. import something # also works, and is in a directory above

If we had written __package__ = "SubFolder" instead of "MainFolder.SubFolder" (and had used the path of MainFolder as sys.path), we would not be able to use from .. import something, because of another ImportError:

"ImportError: attempted relative import beyond top-level package"

Further improving with some other functionalities made me create this code snippet that you should be able to easily copy-paste into your own script without much thinking.

import bpy
import sys
import pathlib


#enable relative imports:
if __name__ == '__main__': #makes sure this only happens when you run the script from inside Blender
    
    # INCREASE THIS VALUE IF YOU WANT TO ACCESS MODULES IN PARENT FOLDERS (for using something like "from ... import someModule") 
    number_of_parents = 1 # default = 1
    
    original_path = pathlib.Path(bpy.data.filepath)
    parent_path = original_path.parent
    
    for i in range(number_of_parents):
        parent_path = parent_path.parent
    
    
    str_parent_path = str(parent_path.resolve()) # remember, paths only work if they're strings
    #print(str_parent_path)    
    if not str_parent_path in sys.path:
        sys.path.append(str_parent_path)

    # building the correct __package__ name
    relative_path = original_path.parent.relative_to(parent_path)
    with_dots = '.'.join(relative_path.parts)
    #print(with_dots)
    __package__ = with_dots


#the relative imports don't have to be in the if clause above, they should work for other scripts that import this file as well.
from . import printHello  # a dot is a relative path
from .. import something # is in a directory above, only works if number_of_parents is at least 2 

The only value you might have to change yourself (depending on how deep your file is located inside your projects file structure) is the number_of_parents variable.

$\endgroup$
3
  • 1
    $\begingroup$ works like a charm! $\endgroup$
    – user146132
    Commented Apr 11, 2022 at 8:14
  • $\begingroup$ it doesn't work. I'm using v2.93.1 and it throws an error ModuleNotFoundError: No module named 'MainFolder' $\endgroup$ Commented Apr 27, 2022 at 7:31
  • $\begingroup$ The MainFolder was part of example code, what you actually want to use for yourself is the code in the last box $\endgroup$
    – Cardboy0
    Commented Apr 28, 2022 at 20:28
0
$\begingroup$

Although its a nasty hack, you could patch the import function to work with blend text files like this:

#allows to import blender text files as python modules
def blender_import(name, *args, bimp=__import__):
    file = bpy.data.texts.get(name)
    if not file:
        file = bpy.data.texts.get("%s.py" % name)
    if file:
        sys.modules[name] = file.as_module()
    return bimp(name, *args)

if __builtins__["__import__"] != blender_import:
    __builtins__["__import__"] = blender_import

Just run it once and import will work for the blend session.

Its not necessary that Blend text files ends with .py and there are side effects like stacktraces wont be accurate if you have any errors.

For a better implementation please refer to:

https://www.tutorialspoint.com/python-import-function

https://docs.python.org/3/library/importlib.html#module-importlib

$\endgroup$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .