1
$\begingroup$

I am trying to make an addon that will randomize path variables. The system I have works by adding and subtracting boxes that contain an input for a data path.

First picture of the system.

It works fine except for one issue. Because I have to create properties for each of the variables (input box, min, max, randomize on render), all of the values are linked, and every input field, max, min, etc. is all the same:

Second picture with all boxes linked

So how would I fix this issue so that I am able to have independent variables that can be easily created or destroyed? Here is my code:

import bpy
from bpy.types import Panel, Operator
from bpy.props import *
import random as r

paths = 0

class add_data_path(Operator):
    bl_idname = "path.add"
    bl_label = "Add a data path to change"

    def execute(self, context):
        global paths
        paths += 1
        self.report({'INFO'}, 'Added path variable')
        return{'FINISHED'}

class subtract_data_path(Operator):
    bl_idname = "path.subtract"
    bl_label = "Get rid of a data path to change"

    def execute(self, context):
        global paths
        if paths > 0:
            paths -= 1
        self.report({'INFO'}, 'Removed path variable')
        return{'FINISHED'}

# Panel
class RANDOMIZER_UI(Panel):

    # Create a Panel in the Tool Shelf
    bl_label = "Randomizer"
    bl_idname = "RANDOMIZER_Manager"
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"
    bl_category = "Tool"
    bl_options = {"DEFAULT_CLOSED"}

    # Custom Properties
    text = bpy.props.StringProperty(name="Text:")

    # Draw
    def draw(self, context):
        Mybool = True

        layout = self.layout

        obj = context.object
        row = layout.row()
        row.label(text="Path Randomizations:")
        row = layout.row()
        row.operator("path.subtract", text="-")

        i = 0
        while i < paths:
            # Visible UI
            box = layout.box()
            row = box.row()
            row.use_property_split = True
            row.label(text="Path Variable:")
            row.prop(context.object, "varpath", text="")
            if context.object.varpath != "":
                row = box.row(align=True)
                row.prop(context.object, "minvar")
                row.prop(context.object, "maxvar")
                row = box.row(align=True)
                row.prop(context.object, "randonrender", text="Randomize On Render?")
            # Special Number
            i += 1

        row = layout.row()
        row.operator("path.add", text="+")

        RANDOMIZER_UI(self)

def register():
    bpy.utils.register_class(RANDOMIZER_UI)
    bpy.utils.register_class(add_data_path)
    bpy.utils.register_class(subtract_data_path)

    # Registering Variables
    bpy.types.Object.varpath = StringProperty(name="Variable Path", 
    description="Input field for the randomized value")
    bpy.types.Object.maxvar = FloatProperty(name="Max Value", 
    description="The maximum value for the variable path", default=1)
    bpy.types.Object.minvar = FloatProperty(name="Min Value", 
    description="The minimum value for the variable path", default=1)
    bpy.types.Object.randonrender = BoolProperty(name="Randomize On Render", 
    description="Determines whether or not the value is randomized upon rendering an image")

def unregister():
    bpy.utils.unregister_class(RANDOMIZER_UI)
    bpy.utils.unregister_class(add_data_path)
    bpy.utils.unregister_class(subtract_data_path)

    del(bpy.types.Object.varpath)
    del(bpy.types.Object.maxvar)
    del(bpy.types.Object.minvar)
    del(bpy.types.Object.randonrender)

if __name__ == "__main__":
    register()
```
$\endgroup$
  • $\begingroup$ Sorry, I just updated what I meant with a much more precise and detailed response. Try looking at it now. $\endgroup$ – Andrew Combs Dec 12 '19 at 0:14
  • $\begingroup$ @AndrewCombs what project is this for? Why do you need paths? How will this be helpful to future users? The last one is arguably less important but still something that this question is lacking. $\endgroup$ – user64742 Dec 12 '19 at 0:42
  • $\begingroup$ @TheGreatDuck This is a personal project to generate hyper-realistic datasets for ML algorithms without having to take pictures and or mark points by hand. particularly, right now I am working on a full body tracking system and now need the dataset. I need the paths for the different integers so that I can specify different values that will be randomly changed (why there is a min, max, and randomize on render). This isnt necessarily made to be a commercial project, but I am willing to release it if necessary. Hope that clears things up. $\endgroup$ – Andrew Combs Dec 12 '19 at 1:27
  • $\begingroup$ @AndrewCombs now just take that and integrate it into the body of your question. Then congradulations you wrote a semi-decent question. $\endgroup$ – user64742 Dec 12 '19 at 1:38
1
$\begingroup$

Use a Collection Property

enter image description here

Set up the properties.

The number of paths is made a scene property. When it is updated it adds or removes a node to each scene objects paths collection.

Put all the relevant path node info in a property group.

Add a collection of that type to the object type.

Easiest explained by way of example, run the script below,

import bpy

from bpy.types import PropertyGroup
from bpy.props import (FloatProperty,
                      CollectionProperty,
                      IntProperty,
                      BoolProperty,
                      )

class PathNode(PropertyGroup):
    minvar: FloatProperty()
    maxvar: FloatProperty()
    randomize: BoolProperty()

def adjust_paths(ob, paths):
    while len(ob.paths) > paths:
        ob.paths.remove(len(ob.paths) - 1)
    while len(ob.paths) < paths:
        ob.paths.add()

def update_paths(self, context):
    for o in self.objects:
        adjust_paths(o, self.paths)

classes = (PathNode,)

def register():
    for cls in classes:
        bpy.utils.register_class(cls)
    bpy.types.Scene.paths = IntProperty(
            min=0,
            soft_max=10,
            update=update_paths)
    bpy.types.Object.paths = CollectionProperty(type=PathNode)

if __name__ == "__main__":
    register()

A draw method, to add to layout code.

    def draw(self, context):
        layout = self.layout
        scene = context.scene
        obj = context.object
        layout.prop(scene, "paths")
        for node in obj.paths:
            row = layout.row()
            row.prop(node, "minvar")
            row.prop(node, "maxvar")
            row.prop(node, "randomize")

Note with scene.paths will need some type of "refresh" operator if a new object is added. A simple test is search for any object whose paths length doesn't match scene.paths

Alternatively the paths int could be associated on a per object basis so as each object can have a variable length collection.

This would involve changing update paths, since self is now an object, not the scene

def update_paths(self, context):
    adjust_paths(self, self.num_paths)

and instead of the scene property for path length, an object property instead.

    bpy.types.Object.num_paths = IntProperty(
            min=0,
            soft_max=10,
            update=update_paths)

and in draw method, the object property rather than the scene.

    layout.prop(obj, "num_paths")

Because of the update method, code below will make sure all objects in scene have 10 paths

for o in scene.objects:
    o.num_paths = 10

Documentation for CollectionProperty (CRUD methods)

Collections can also be laid out with bpy.types.UIList akin to materials and vertex groups.

Create an interface which is similar to the material list box

$\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.