import bpy
# Opens a web browser and prints the filepath to console.
#(To see console.. Window >>> Toggle system console
class OpenBrowser(bpy.types.Operator):
bl_idname = "open.browser"
bl_label = "Minimum code to open browser & get filepath"
filepath = bpy.props.StringProperty(subtype="FILE_PATH")
#somewhere to remember the address of the file
def execute(self, context):
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)
#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.browser('INVOKE_DEFAULT')
That's it.
Only read on if you want more info on building a UI panel.
Building a Blender User interface using python scripting
The individual commands to create buttons, input sliders and file browsers only work if put into the structure that Blender requires. If used on their own the best result is an error message from the syntax checker and the worst is that Blender crashes.
Creating a new panel in which the user can see data, change data, or select a file requires code that mimics the independent panel.
The panel is described as an object and the code that creates it and responds to it is put in one place also described as an object.
This collection of panel related code is defined as a class.
To code a class, that reserved word 'class' is written all lower case followed by a name that means something to you, but which won't matter much to Blender. The convention is to write it in CamelCaps to help recognise class names in code.
After the lower case class and the CamelCaps user name comes the reserved standard method of telling Blender that this is where the user interface panel is defined and handled. The code for telling Blender 'hey this is a UI panel' is (bpy.types.Panel)
class UIBuild (bpy.types.Panel):
[Note: UPPERCASE and lowercase matter in the Python code language. The above works, but the following both produce a hard to spot syntax error failure....
Class UIBuild (bpy.types.Panel): class UIBuild (bpy.types.panel):
Also remember the colon. ]
Blender needs your Panel class to have some standard information including which window and which existing panel it is to be attached to.
bl_label = "Music baked Concert Style" #Printed out on panel
bl_idname = "ui.build" #Internal Blender name lower case one dot
bl_space_type = 'VIEW_3D' #The window to use
bl_region_type = 'TOOLS' # Left hand T panel
bl_category = 'ConcertStyle' #User created tab -default is: Misc
That would create a panel with: Music baked Concert Style :printed across the top, but the code will trigger an error because Blender expects to find more than this.
The buttons, labels, variable sliders and filebrowser opening are drawn onto the panel in a reserved word function called: draw(self, context): :that has to be coded within the class Panel.
def draw(self,context):
is the standard function in which you code what the user is going to see and be able to click on in a panel in one of the windows.
To draw some text onto the panel
self.layout.row.label(text="This will be written on the panel")
Text plus an icon:
self.layout.row.label(text="Select audio track to load",icon="SPEAKER")
A single clickable button, with text and updating data value & an icon:
self.layout.row.operator( text=Message ,icon="NLA")
(To get the variable data to be printed on the button the above needs to be preceeded with a definition of that Message such as
Message="Click here to load this instrumental track. Tracks="+str(Tracks)
Where Tracks is a number and is converted into a string by str(Tracks) which is then added on to the actual string. It can then be printed on the button and can be updated automatically if the variable changes.
)
With multiple buttons each of which is to call different code there is the need to make the buttons distinguisable. That requires a separate function that defines buttons as having some added feature like a reference number attached to them. Then the code that reacts to button clicks can check which button was clicked. (See below)
Clickable items drawn on the panel are made by that word operator .
When the mouse hovers over a clickable button the draw(self,context) function is called and the buttons are redrawn. That is how and when the displayed data updates. (No hover, no update)
Whenever a button is clicked Blender looks for a function within class Panel to react to that click.
First it looks for a function called
invoke(self,context,event)
invoke(self,context,event):
is a standard function which automatically runs whenever a user defined button is clicked.
If there is only a single button, whatever code is written within
invoke(self,context,event):
runs to react to that button, but if there is more than 1 button the buttons need to be identifiable so that different actions can be triggerred by each. (See below)
If the button is to open a filebrowser the command is:
context.window_manager.fileselect_add(self)
(If used on its own outside of invoke(self,context,event) is likely to crash Blender.)
To keep the browser window open and Blender not crashing the invoke function has to also send back a message about how it should be running: return{'RUNNING_MODAL'}.
invoke(self,context,event):
context.window_manager.fileselect_add(self)
return{'RUNNING_MODAL'}
(If there are two buttons, no matter what you want the second one to do it is going to open the file browser.) (For solution see below)
If Blender doesn't find an invoke() function it will settle for an execute() function, but usually execute is where it looks when values are changed by the user.
execute(self,context)
Called when user changes a value in the panel rather than clicking a button.
Within draw(self,context): to draw an input for numbers with sliders the command is:
well actually there isn't a command for that. It gets complicated. First the variable has to be named in a formal declaration of its type and some other detals. After that the command to use is:
layout.prop(context.scene, 'TotalNotes') #TotalNotes was part of the declaration
layout.prop(context.scene, 'ObjectXScale') # ObjectXScale is the internal ID for this variable.
Blender uses that single command, together with the declaration of the variable, to create a titled numerical input with up and down arrows, click-to-enter dialogue of the relevant type such as integer (whole numbers), float (numbers with fractions), vector (3 number system)
(See below for how to declare the variable)
What can be done in each function:
Draw()
The expand/collapse triangle is standard
Text title and an icon which is displayed at top
Text and icons in rest of panel
Clickable buttons with text and icon
Clickable file browser
Integer, float or vector input boxes with sliders
Layout of these items in rows, columns and sizes.
Invoke(self,context,event)
Check which button has been clicked.
Decide what function to call in response to that button
Return something to a selected function to carry out whatever that function does.
Open file browser
To react differently to different buttons the buttons have to be different.
A way of defining buttons to be different is to define them as having an identifying reference number attached to them. With an identifying number it is possible to react differently to each button.
class OBJECT_OT_Button(bpy.types.Operator):
bl_idname = "but.ton"
bl_label = "Click bait"
number = bpy.props.IntProperty() #Buttons of type "but.ton" can store a number and be identified
Within draw(self,context): the command to draw a but.ton type of button is
self.layout.row.operator("but.ton", text="I am a but.ton not a number!" ).number=1 #Choose an integer
[To work Blender has to be told about this definition. Use the command bpy.utils.register_class(OBJECT_OT_Button) ]
To display a variable value on the UI panel and get changes from the user, first the variable has to be declared.
Declaring a variable so it can be used to create an input dialogue on the panel:
bpy.types.Scene.ObjectXScale = FloatProperty( #declares another addition to the data structure
name = "NotesXScale", #name is displayed on panel
description = "Size of the note indicator.", #tooltip to be displayed
default = .5, #the amount displayed in the panel
min = .01, #slider won't go below this
max = 5) #slider won't go above this
bpy.types.Scene.ObjectYScale = IntProperty( #declares another addition to the data structure name = "Amplification-Y-IMPORTANT ", #name is displayed on panel
description = " It amplifies the effect of the audio", #tooltip
default = 200, #the amount displayed in the panel
min = 10, #slider won't go below this
max = 1000) #slider won't go above this
There are other types, but the layout is similar.
Small print warnings:
Copying parts of Blender's IU can lead to errors because they are set up to use the existing data structure and if used in that state they will mess with things. Example: if you copy the render panel's open browserr button anything it does is going to also change the render output destination.
Also, that browser button does not call INVOKE
MENU If running a script rather than an addon the menu will be added to every time the script it run.
If run as an addon the menu is only added to once.
Turning a script into an addon IS NOT as easy as many claim. Just putting the header on probably means failure.