4

I'm displaying N-instances of a Collection Property (depending on the amount of selected objects) — the values are floats, default values are 100/N, and I want to be able to recalculate values of other properties when one of the instance is changed so that a sum would be always 100 — basically I want to have something like influence between objects limited by 100(%).

Here's how it looks: in this example two other fields should be 25 and 25

enter image description here

I tried to use the update function and change "other values" (instances that aren't the active) but I guess since every instance uses the same function Blender goes in infinite recursion and crashes. Maybe my approach is wrong and I should do this in a different way?

import bpy
import math

def update_float(self,context): other_props = [prop for prop in context.scene.my_custom_set if prop.name != self.name] rest = 100 - self.value for prop in other_props: prop.value = rest / len(context.selected_objects) # crashes blender

class SceneSettingItem(bpy.types.PropertyGroup): name: bpy.props.StringProperty(name="Obj Name", default="") value: bpy.props.FloatProperty(name="Z coord", default=0, max=100, min=0, update=update_float)

class SimpleOperator2(bpy.types.Operator): """""" bl_idname = "object.simple_operator_sk" bl_label = "Collection Property Test 2" bl_options = {'REGISTER', 'UNDO'}

def draw(self, context):
    layout = self.layout

    for my_item in context.scene.my_custom_set:
        row = layout.row()
        row.prop(my_item, "value", text=f"Prop for {my_item.name}")

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

def invoke(self, context, event):
    context.scene.my_custom_set.clear()
    for ob in context.selected_objects:
        my_item = context.scene.my_custom_set.add()
        my_item.name = ob.name
        my_item.value = math.ceil(
            10000 / len(context.selected_objects))/100

    return context.window_manager.invoke_props_dialog(self)


def register(): bpy.utils.register_class(SimpleOperator2) bpy.utils.register_class(SceneSettingItem) bpy.types.Scene.my_custom_set = bpy.props.CollectionProperty(type=SceneSettingItem)

def unregister(): del bpy.types.Scene.my_custom_set bpy.utils.unregister_class(SimpleOperator2) bpy.utils.unregister_class(SceneSettingItem)

if name == "main": register()

Gorgious
  • 30,723
  • 2
  • 44
  • 101
Sergey Kritskiy
  • 895
  • 5
  • 15

1 Answers1

6

Set the custom property.

Internal "get/set" function of property?

Why does an update function loop when string property is reset?

When a property defined via bpy.props is set to non default, it is saved on the object as a custom property, eg setting ob.foo = 33 is equivalent of ob["foo"] = 33. Setting the property data does not fire the update,

... hence can set the others by custom property.

Have removed name since it is added to a collection item by default

In the method self is the instance of the collection item, can test against this rather than name. self.id_data is the scene, since this is the ID object the collection is defined on.

import bpy
import math

def update_float(self, context): other_props = [prop for prop in self.id_data.my_custom_set if prop != self] v = (100 - self.value) / len(other_props) for p in other_props: p["value"] = v

class SceneSettingItem(bpy.types.PropertyGroup): value: bpy.props.FloatProperty(name="Z coord", default=0, max=100, min=0, update=update_float)

class SimpleOperator2(bpy.types.Operator): """""" bl_idname = "object.simple_operator_sk" bl_label = "Collection Property Test 2" bl_options = {'REGISTER', 'UNDO'}

def draw(self, context):
    layout = self.layout

    for my_item in context.scene.my_custom_set:
        row = layout.row()
        row.prop(my_item, "value", text=f"Prop for {my_item.name}")

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

def invoke(self, context, event):
    context.scene.my_custom_set.clear()
    for ob in context.selected_objects:
        my_item = context.scene.my_custom_set.add()
        my_item.name = ob.name
        my_item.value = math.ceil(
            10000 / len(context.selected_objects))/100

    return context.window_manager.invoke_props_dialog(self)


def register(): bpy.utils.register_class(SimpleOperator2) bpy.utils.register_class(SceneSettingItem) bpy.types.Scene.my_custom_set = bpy.props.CollectionProperty(type=SceneSettingItem)

def unregister(): del bpy.types.Scene.my_custom_set bpy.utils.unregister_class(SimpleOperator2) bpy.utils.unregister_class(SceneSettingItem)

if name == "main": register() bpy.ops.object.simple_operator_sk('INVOKE_DEFAULT')

PS. Will be issues with number of selected objects being one or less, resolve by polling panel and or operator as true only when 2 or more objects are selected.

Poll method for custom panel

batFINGER
  • 84,216
  • 10
  • 108
  • 233
  • 2
    To be knowledgeable AND a great teacher is a rare skill :) – Gorgious Jun 04 '21 at 15:51
  • 1
    Thankyou, appreciated. Added comment to your answer, re this being the long winded version & speculated piccies get the bickies. Please don't feel obliged to delete on my account, ... and ditto back to you :} – batFINGER Jun 05 '21 at 07:02
  • As always, thank you, great answer. I'm having an issue that maybe should be for a different question: after I press Ok the values in the Adjust Last Operation window reset to default and don't change anymore: is there a trick to get those working..? – Sergey Kritskiy Jun 06 '21 at 06:27
  • 1
    Wondered about that never did get around to testing. Added the link re getter setter. In general the properties seen in modal popup are operator props, not scene or object. Would consider doing it differently than a modal popup, big fan of panel popovers in 2.8+. For the most part whatever is required in the execute method of operator could be done via the update method.(or getter setter) The redo last operation is effectively replaced by the panel of scene properties (consider instead using window manager).. The UI can test if the selection has changed and reoffer the populate button. – batFINGER Jun 06 '21 at 07:13
  • 1
    ** to put any ID object property in a modal popup (or alter via UI for that matter) with UNDO / REDO will likely act similar. The scene's state on undo stack. The operator added the collection to scene. Example of update button https://blender.stackexchange.com/questions/141179/how-to-populate-uilist-with-all-material-slot-in-scene-2-8/141207#141207 Please elaborate more on detail of usage? and how or why a modal popup is required? – batFINGER Jun 06 '21 at 07:34