Have a look at the code from IFC CSV - I think it does exactly what you want. Essentially, after selecting a file, the filepath is saved to a string property:
The filepath is then shown in the UI:
'
Have a look at the code from IFC CSV - I think it does exactly what you want. Essentially, after selecting a file, the filepath is saved to a string property:
The filepath is then shown in the UI:
'
How does one bundle python dependencies in a Blender add-on?
Found this post
But when looking in the BlenderBIM add-on I think they do it differently and I think easier.?
import os
import sys
import site
bl_info = {
"name": "BlenderBIM",
"description": "Author, import, and export data using the Industry Foundation Classes schema",
"author": "IfcOpenShell Contributors",
"blender": (2, 80, 0),
"version": (0, 0, 211031),
"location": "File > Export, File > Import, Scene / Object / Material / Mesh Properties",
"tracker_url": "https://github.com/IfcOpenShell/IfcOpenShell/issues",
"category": "Import-Export",
}
if sys.modules.get("bpy", None):
# Process *.pth in /libs/site/packages to setup globally importable modules
# This is 3 levels deep as required by the static RPATH of ../../ from dependencies taken from Anaconda
site.addsitedir(os.path.join(os.path.dirname(os.path.realpath(__file__)), "libs", "site", "packages"))
import blenderbim.bim
def register():
blenderbim.bim.register()
def unregister():
blenderbim.bim.unregister()
Is there a possibility in Blender where you can 'freeze' the enviroment in which you developed so you can bundle the add-on with the dependencies?
Blenderbim bundles packages in the downloaded zip file (in libs/sites/packages). After all a python module is generally just a folder with python files that's installed in your python environment.
So you can do that and directly call it from your code like you would do any other submodule you created but on the other hand it's tricky because if the package has a critical update and you keep using an older version you risk introducing safety hazards in your addon.
You can also call pip
using the command line directly in your script to ensure you get the latest version. Also if the package is heavy it has the added benefit of not bloating your add-on size. There are several alternatives which are described there https://blender.stackexchange.com/questions/5287/using-3rd-party-python-modules
I just have to add for completeness that requiring access to internet is against Blender's philosophy. I don't really have an opinion on that but some people might object on needing to download a third party python module in addition to your add-on.
@Gorgious
Yes, it works!
I used the method you posted on that thread.
import subprocess
import sys
py_exec = str(sys.executable)
# ensure pip is installed
subprocess.call([py_exec, "-m", "ensurepip", "--user" ])
# update pip (not mandatory but highly recommended)
subprocess.call([py_exec, "-m", "pip", "install", "--upgrade", "pip" ])
# install packages
subprocess.call([py_exec,"-m", "pip", "install", f"--target={py_exec[:-14]}" + "lib", "scipy"])
What does f"--target={py_exec[:-14]}"
do exactly?
IIRC It's a hack to get the module installed in the local blender installation lib folder, otherwise by default it installed the module in my global windows python environment which I don't like and Blender doesnt recognize by default.
I made some checkboxes in my add-on, but I am don't understand how to tell in the class if the checkbox is checked to run a specific function.
@Coen you can use the update function to run once a change has been made. See https://docs.blender.org/api/current/bpy.props.html#update-example
it is common to, instead of defining the properties in the UI, define properties as a separate PropertyGroup. This property group is, when installing the add-on, registered on a certain namespace (for example the 'scene'), so you can access it globally. So for you that could be:
from bpy.props import BoolProperty, StringProperty, IntProperty, EnumProperty
from bpy.types import PropertyGroup
class ExcelProperties(PropertyGroup):
ifc_product: BoolProperty(name="IfcProduct", default=False)
ifc_building_story: BoolProperty(name="IfcBuildingStory", default=False)
name: BoolProperty(name="Name", default=False)
excel_path: stringProperty(name="Excel path", subtype='FILE_PATH')
Afterwards you register the property in some namespace:
def register():
bpy.types.Scene.excel_properties = bpy.props.PointerProperty(type=ExcelProperties)
Now, in the UI or in the Operator, you can access the properties in the execute/draw functions:
UI:
def draw(self, context):
excel_properties = context.scene.excel_properties
row = layout.row(align=True)
row.prop(excel_properties, "ifc_product")
Operator:
def execute(self, context):
excel_properties = context.scene.excel_properties
if excel_properties.ifc_product:
do_something()
It's quite nice because you can separate the properties from the ui and from the functions. BlenderBIM does this nicely (I use this too for my own add-ons, it might also be the standard structure, I don't know), by making the following structure for each package:
package_name:
ui.py
prop.py
operator.py
__init__.py (for registering all the classes from the three python files.
You can for example look at the GIS package in the BlenderBIM add-on: https://github.com/IfcOpenShell/IfcOpenShell/tree/v0.7.0/src/blenderbim/blenderbim/bim/module/gis (the other packages are built the same way but more extensive so this is a simple example)
@LaurensJN
@vpajic
Thanks for the help.
I use this method now, in the BlenderBIMXLSXPanel in the draw method put a row and colums for the ui
class BlenderBIMXLSXPanel(bpy.types.Panel):
"""Creates a Panel in the Object properties window"""
bl_label = "BlenderBIM .xlsx"
bl_idname = "OBJECT_PT_blenderbiMxlsxpanel" # this is not strictly necessary
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
bl_category = "Tools"
def draw(self, context):
scene = context.scene
layout = self.layout
col = layout.column(align=True)
row = col.row(align=True)
col.prop(scene, "my_ifcproduct")
then in register
def register():
bpy.types.Scene.my_ifcproduct = bpy.props.BoolProperty(name="IfcProduct",description="Export IfcProduct",default = True)
then in the WritetoXLSX class I can call the checkbox.
if context.scene.my_ifcproduct == True:
ifc_dictionary['IfcProduct'] = ifc_product_type_list
I think I need to group some of them, start to get a bit crowdy. And I hardcoded the sum formulas in the header, they don't work anymore. Because the colums move when the user decides to check and uncheck some of the exports needed. Need to find a method for this.
I like this layout, any suggestions for a good intuive UI are very welcome. GUI is not my strong suit.
My two cents, @Coen i think last ui is nice, with the possibility of hiding the checkboxes of the same group
You can create sub-panels :
See the last part of that answer : https://blender.stackexchange.com/a/155517/86891 or the very minimal amount of code there : https://wiki.blender.org/wiki/Reference/Release_Notes/2.80/Python_API/UI_API#Sub_Panels
note you can add a class attribute bl_order = 0/1/2/3 etc
if you want to force the ordering of the sub-panels. see https://docs.blender.org/api/current/bpy.types.Panel.html#bpy.types.Panel.bl_order
And use
`bl_options = {'DEFAULT_CLOSED'}` for the sub panels to be folded by default
You can try to add layout.prop(scene, "my_prop", toggle=True)
if it's a boolean to display as a toggle rather than a checkbox. Matter of taste https://blender.stackexchange.com/a/117791/86891
How do you bundle a Blender BIM add-on in a zip if the add-on has third party python dependencies?
Hmm I've actually never done that. Theoretically it should be as easy as copy / pasting the third party module and all its dependencies in a folder in the zip, and then access it like you would any other module you created in the addon folder. Blenderbim does that in libs/site/packages
These are the dependencies as where they are stored:
openpyxl 3.0.9 C:\Program Files\Blender Foundation\Blender 3.0\3.0\python\lib\openpyxl\__init__.py
pandas 1.3.5 C:\Program Files\Blender Foundation\Blender 3.0\3.0\python\lib\pandas\__init__.py
xlsxwriter 3.0.2 C:\Program Files\Blender Foundation\Blender 3.0\3.0\python\lib\xlsxwriter\__init__.py
I found this SO post that there are numerous ways of importing dependencies.
I copied pasted the modules openpyxl, pandas and xlsxwriter to a new relative folder
BlenderBIMOpenOfficeXML\lib\site\packages
Now I started reading that thread on Stack Overflow.
and I got completely lost...
You should be able to use from BlenderBIMOpenOfficeXML.lib.site.packages import openpyxl
.
Note python modules should be named in lower case, and I think it's not mandatory but you can use snake_case
like you would regular python modules.
I have never done that either, but the BlenderBIM add-on does that with a great deal of packages:
See https://github.com/IfcOpenShell/IfcOpenShell/blob/v0.7.0/src/blenderbim/blenderbim/init.py
If you open any BlenderBIM add-on folder, you see all packages can be found under /libs/site/packages. Therefore this line probably does the trick:
import site
site.addsitedir(os.path.join(os.path.dirname(os.path.realpath(__file__)), "libs", "site", "packages"))
If I am wrong, probably @Moult can explain this better :)
I used the method @LaurensJN described.
I added this to my script
import site
site.addsitedir(os.path.join(os.path.dirname(os.path.realpath(__file__)), "libs", "site", "packages"))
Then copy pasted the modules from the python site-packages to my own folder called with the following folder struture libs>sites>packages ( I have a vague memory of reading this somewhere in Blender documentation too)
I placed the openpyxl, pandas and xlsxwriter modules in there
did a clean install of Blender 3.0 and BlenderBIM on my old brick laptop.
validated where the imports were coming from with [module_name_here].__file__
to see where they are located.
And it works! :-D
They are now placed in
C:\Users\C.C.J. Claus\AppData\Roaming\Blender Foundation\Blender\3.0\scripts\addons\libs\site\packages\openpyxl\__init__.py
C:\Users\C.C.J. Claus\AppData\Roaming\Blender Foundation\Blender\3.0\scripts\addons\libs\site\packages\pandas\__init__.py
C:\Users\C.C.J. Claus\AppData\Roaming\Blender Foundation\Blender\3.0\scripts\addons\blenderbim\libs\site\packages\xlsxwriter\__init__.py
When I try to install the add-on Linux Ubuntu I get a specific pandas error. Going to look at that another time.
Has this been documented somewhere? This thread is gold.
EDIT: Never mind, I should read the thread more carefully, found it here:
https://wiki.osarch.org/index.php?title=Create_your_first_Blender_add-on
ui.py
prop.py
operator.py
init.py (for registering all the classes from the three python files.
Is this the 'default' stander add-on convention for Blender? all the BIM modules seem to be structured in this way
@Coen oh wow I forgot I wrote that :)
Nope there is AFAIK no convention on how to structure an add-on in Blender, apart from the mandatory dictionary with bl_info
somewhere in the file if it is a single script or in __init__.py
if it's a multi-file addon.
That being said as a general rule in programming if a class or a module is doing more than one thing, it's a good idea to split it up, and keep dependencies between modules to a minimum. Most beginner classes / tutorials will tell you about the SOLID principle which is an interesting mantra to try to follow, at least until you know when it makes sense to break the rules. When you'll know, you'll know. :)
The ui / prop / operator / tool separation makes sense because usually data storage, data manipulation, and data display can be done by independent systems so there is no reason they should be scripted in the same place. The great benefit is that it's easier to test, easier to debug, easier to extend and easier to swap when a new technology for either module emerges.
@Gorgious
Nope there is AFAIK no convention on how to structure an add-on in Blender, apart from the mandatory dictionary with bl_info somewhere in the file if it is a single script or in init.py if it's a multi-file addon.
How do I run a live development environment when I have three __init.py, prop.py, operator.py and ui.py files from VS Code?
I only need to register the init.py in Blender somewhere?
@vpajic said:
@Coen you can use the update function to run once a change has been made. See https://docs.blender.org/api/current/bpy.props.html#update-example
In which file should I put this update code? or just place it a sepertae python file with the add-on?
Hey @Coen do you have it over on github or such ? I can try giving you hints. I think there are fundamentally 3 types of addon strucutres.
If your addon does a single thing, you can write all your code in a single file and install that file as an addon.
If your addon does a few thing, you want to have a __init__.py
file with the mandatory addon dictionary so Blender knows how to set it up. Then you can have in the same folder your other features in different python files. You can import them with a simple from . import my_other_file
.
If your addon is expansive, you can structure it with folders, like you would files in your computer. You can then navigate through them with import my_addon_directory.my_secondary_directory.my_other_file
. You still need to have a __init__.py
file with the mandatory dictionary.
A lot of blender addons are available over on github, you can study how they're structure and see what you want to go for : https://github.com/topics/blender-addon
or even the official blender addons https://github.com/blender/blender-addons
@Gorgious
do you have it over on github or such ?
Yes, I think i've added you to my private repo, it's a work in progress of the blenderbim spreadsheet add-on completely rewritten with the ifcopenshell.api
What I want to do is to just change the code of the add-on from vs code and see instant changes in the ui of blender.
Login or Register to reply.