4
$\begingroup$

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.)

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

3 Answers 3

8
$\begingroup$

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

$\endgroup$
1
  • 1
    $\begingroup$ Perfect, yes. This is exactly what I am looking for. Thank you! $\endgroup$ Commented Apr 25, 2019 at 13:33
3
$\begingroup$

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. 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()
$\endgroup$
2
$\begingroup$

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.

$\endgroup$

You must log in to answer this question.

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