# How to control randomization during Operator life-cycle?

I am working on a Python script that uses some randomization and some parameters to procedurally generate geometry. However each time I adjust any of the Operator parameters, the entire script is re-run and all the random aspects re-randomized.

My script would have a more useful workflow if I could somehow randomize once on initialization and allow the adjustment of parameters without re-randomizing. However it would be nice to add a button or something to trigger re-random instead of using undo/redo.

What is a good way to accomplish this?

(This is not a question about how to seed the PRNG.)

• I'm voting to close this question as off-topic because it's not about Blender but about seeding random values in Python. – Jaroslav Jerryno Novotny Apr 25 at 7:42
• Use random.seed(0) to set the seed. – Leander Apr 25 at 8:55
• Hoping this question is about invoking vs executing operators (see answer below). – batFINGER Apr 25 at 11:22
• Sorry, I misunderstood. I'll leave the comment for other users just in case. – Leander Apr 25 at 13:42
• No problem, I just figured I should clarify. Thanks for looking. – slerfsterdn Apr 25 at 13:44

## 3 Answers

Set in invoke method.

If I understand correctly you wish to only set the random seed once when the operator is invoked.

The method is employed in this answer to only find the loose parts of a mesh once.

Altering properties only fires the execute method.

Adding a button to self layout.operator("test.random") in draw method will re-invoke and set a new random.

Here is simple operator example. Invoking sets the random property.

import bpy
import bmesh
from random import random

from bpy.props import (
FloatProperty,
)

class TEST_OT_random(bpy.types.Operator):
"""Add a simple box mesh"""
bl_idname = "test.random"
bl_label = "Random"
bl_options = {'REGISTER', 'UNDO'}
random = random()

width: FloatProperty(
name="Width",
description="Box Width",
min=0.01, max=100.0,
default=1.0,
)
height: FloatProperty(
name="Height",
description="Box Height",
min=0.01, max=100.0,
default=1.0,
)

def invoke(self, context, event):
self.random = random()
print("invoke", self.random)
return {'FINISHED'}
# or to run after invoke
return self.execute(context)

def execute(self, context):

print(
"execute:",
self.width,
self.height,
self.random,
)

return {'FINISHED'}

def draw(self, context):
layout = self.layout
col = layout.column()
col.prop(self, "width")
col.prop(self, "height")
col.operator("test.random", text="Re-random")

def register():
bpy.utils.register_class(TEST_OT_random)

def unregister():
bpy.utils.unregister_class(TEST_OT_random)

if __name__ == "__main__":
register()

# test calls
bpy.ops.test.random()
bpy.ops.test.random(width=3)
bpy.ops.test.random('INVOKE_DEFAULT')


Test run output

execute: 1.0 1.0 0.6802742102341347
execute: 3.0 1.0 0.6802742102341347
invoke 0.5433648186966845


Running from UI, sliding the width property

invoke 0.9656296014544301
execute: 1.059999942779541 1.0 0.9656296014544301
execute: 1.1200000047683716 1.0 0.9656296014544301
execute: 1.2999999523162842 1.0 0.9656296014544301
execute: 1.3299999237060547 1.0 0.9656296014544301
execute: 1.3600000143051147 1.0 0.9656296014544301
execute: 1.3299999237060547 1.0 0.9656296014544301


aternatively, remove invoke method above to have a random seed set only when addon is registered

• Perfect, yes. This is exactly what I am looking for. Thank you! – slerfsterdn Apr 25 at 13:33

If you don't need security related randomness, just grab the actual timestamp, which delivers a fraction of the actual second after the seconds, and is changed every time your script runs.

print (datetime.datetime.now()) will yield something like 2019-04-25 09:21:07.610605.

I have learned a little bit more about Blender's API, and I have created a sample add-on that allows the user to edit the seed and deliberately re-seed when they choose by clicking a button.

The add-on creates a Panel in the Tool shelf and uses two Operators to accomplish its tasks.

import bpy
import logging
import random
import math
import time

bl_info = {
"name": "Rand Test",
"blender": (2, 79, 0),
"category": "Object"
}

# Shared function to perform re-seeding,
# which means storing a new timestamp in the seedHex scene property
def doReseed(context):
curTime = int(math.floor(time.time() * 1000))
context.scene.seedHex = hex(curTime)

# Operator to generate a new seed
class ReseedOperator(bpy.types.Operator):
bl_idname = "object.reseed_operator"
bl_label = "Reseed"
bl_description = "Reseed PRNG"
bl_options = {'REGISTER', 'UNDO'}

def execute(self, context):
doReseed(context)
return {'FINISHED'}

# Operator to create a random number of cubes
class RandTestOperator(bpy.types.Operator):
bl_idname = "object.rand_test_operator"
bl_label = "Do random stuff"
bl_description = "Demonstrate using the seed"
bl_options = {'REGISTER', 'UNDO'}

def execute(self, context):
try:
seed = int(context.scene.seedHex, 16)
except:
# if the seed is not valid, make a new one
doReseed(context)
seed = int(context.scene.seedHex, 16)

# use the seed value to actually seed the RNG just before use
random.seed(seed)

# create some randomized objects
numCubes = random.randint(1, 10)
for i in range(numCubes):
height = random.uniform(0, 2)
bpy.ops.mesh.primitive_cube_add(location=(i * 3, 0, height))
context.active_object.select = True

return {'FINISHED'}

# Panel interface
class RandTestPanel(bpy.types.Panel):
bl_label = "Rand test"
bl_idname = "rand_test_panel"
bl_space_type = "VIEW_3D"
bl_region_type = "TOOLS"
bl_category = "Tools"
bl_context = (("objectmode"))

def draw(self, context):
layout = self.layout
# ideally, input to the seedHex property would be restricted to a certain number of hex digits
# but here we just accept any string at all and catch conversion exceptions later
layout.prop(context.scene, "seedHex")
layout.operator(ReseedOperator.bl_idname)
layout.operator(RandTestOperator.bl_idname)

def register():
bpy.utils.register_module(__name__)

# the seedHex property is registered independently of the Operators and Panel
bpy.types.Scene.seedHex = bpy.props.StringProperty(
name = "Seed",
description = "Random seed",
default = ""
)

def unregister():
del bpy.types.Scene.seedHex
bpy.utils.unregister_module(__name__)

if __name__ == "__main__":
register()