2
$\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$
  • 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$ – Jaroslav Jerryno Novotny Apr 25 at 7:42
  • 1
    $\begingroup$ Use random.seed(0) to set the seed. $\endgroup$ – Leander Apr 25 at 8:55
  • $\begingroup$ Hoping this question is about invoking vs executing operators (see answer below). $\endgroup$ – batFINGER Apr 25 at 11:22
  • $\begingroup$ Sorry, I misunderstood. I'll leave the comment for other users just in case. $\endgroup$ – Leander Apr 25 at 13:42
  • $\begingroup$ No problem, I just figured I should clarify. Thanks for looking. $\endgroup$ – slerfsterdn Apr 25 at 13:44
6
$\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
    $\begingroup$ Perfect, yes. This is exactly what I am looking for. Thank you! $\endgroup$ – slerfsterdn Apr 25 at 13:33
1
$\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$
1
$\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$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.