You have basically 2 options: modal operator or scene update handler.
Modal operator is what an interactive script would use, here is an example with over-commented code. It will create panel in tools under Misc. and the operator can be ended with ESC:
import bpy
# for random positioning of cubes
# so they dont stack one over other
from random import random
class Panel(bpy.types.Panel):
"""
This displays number_of_cubes property in tools panel
And can run a modal operator
"""
bl_label = "Custom Panel"
bl_space_type = 'VIEW_3D'
bl_region_type = 'TOOLS'
def draw(self, context):
# display cube count if modal operator running
# else display button to run it
layout = self.layout
if context.scene.watcher_running:
layout.prop(context.scene, "number_of_cubes")
else:
layout.operator("my_custom_op.cube_watcher")
class CubeWatcher(bpy.types.Operator):
"""This will run in background monitoring user actions"""
bl_idname = "my_custom_op.cube_watcher"
bl_label = "Cube Watcher"
# this function should be used to do stuff
# in our example to adjust the number of cubes
def execute(self, context):
# get cubes already present in scene
cube_list = [object for object in context.scene.objects if object.name.find("Cube") >= 0]
if len(cube_list) < context.scene.number_of_cubes:
# add cubes
for i in range(context.scene.number_of_cubes - len(cube_list)):
bpy.ops.mesh.primitive_cube_add(enter_editmode=False)
context.active_object.location = (5-10*random(), 5-10*random(), 5-10*random())
elif len(cube_list) > context.scene.number_of_cubes:
# remove excess cubes
for i in range(context.scene.number_of_cubes, len(cube_list)):
context.scene.objects.unlink(cube_list[i])
return
# this function will be called everytime user does something, even moves a mouse
# should be used to react to user input
def modal(self, context, event):
# when user interacts, adjust number of cubes
# this will react to all event types
# which is kinda inneficient but simple
self.execute(context)
# end modal operator when hitting ESC
if event.type == 'ESC':
# set the monitoring property to False
context.scene.watcher_running = False
return {'FINISHED'}
# all other events pass through to blender
return {'PASS_THROUGH'}
# this is run when this operator is called
# usually inicialization code is here
def invoke(self, context, event):
# this is important, it will run the modal function
# without this the operator would just execute and end
context.window_manager.modal_handler_add(self)
# set the monitoring property to True
context.scene.watcher_running = True
# run self to spawn cubes
self.execute(context)
return {'RUNNING_MODAL'}
def register():
# register classes so blender knows about them
bpy.utils.register_module(__name__)
# add a property to scene that will control number of cubes
bpy.types.Scene.number_of_cubes = bpy.props.IntProperty(default=0, min=0)
# add a property that will monitor if modal operator is running or not
bpy.types.Scene.watcher_running = bpy.props.BoolProperty(default=False)
def unregister():
# unregister classes
bpy.utils.unregister_module(__name__)
# remove properties
del bpy.types.Scene.number_of_cubes
del bpy.types.Scene.watcher_running
if __name__ == "__main__":
register()
A handler is a function added to some event, in this case before scene updates. This will produce similar result as code before but will be much more likely buggy or laggy. Next example is simple and does not handle any error cases. Use this only when you know what you are doing.
import bpy
from random import random
def cube_count_handler(scene):
"""This function will be run everytime before scene updates"""
cube_list = [object for object in scene.objects if object.name.find("Cube") >= 0]
if len(cube_list) < scene.number_of_cubes:
for i in range(scene.number_of_cubes - len(cube_list)):
loc = (5-10*random(), 5-10*random(), 5-10*random())
bpy.ops.mesh.primitive_cube_add(enter_editmode=False, location=loc)
elif len(cube_list) > scene.number_of_cubes:
for i in range(scene.number_of_cubes, len(cube_list)):
scene.objects.unlink(cube_list[i])
return
class PropPanel(bpy.types.Panel):
bl_label = "Custom Panel"
bl_space_type = 'VIEW_3D'
bl_region_type = 'TOOLS'
def draw(self, context):
layout = self.layout
layout.prop(context.scene, "number_of_cubes")
def register():
bpy.utils.register_module(__name__)
bpy.types.Scene.number_of_cubes = bpy.props.IntProperty(default=0, min=0)
# add handler if not in app.handlers
if cube_count_handler not in bpy.app.handlers.scene_update_pre:
bpy.app.handlers.scene_update_pre.append(cube_count_handler)
def unregister():
bpy.utils.unregister_module(__name__)
del bpy.types.Scene.number_of_cubes
if __name__ == "__main__":
register()