From dfd40c1d9b1d41ebb3c2f6e5aa7bc9ce4e539915 Mon Sep 17 00:00:00 2001 From: ruaridhg Date: Tue, 6 Jun 2023 09:55:35 +0100 Subject: [PATCH 01/58] define_prop init added --- randomiser/define_prop/__init__.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 randomiser/define_prop/__init__.py diff --git a/randomiser/define_prop/__init__.py b/randomiser/define_prop/__init__.py new file mode 100644 index 0000000..8051ea8 --- /dev/null +++ b/randomiser/define_prop/__init__.py @@ -0,0 +1,13 @@ +from . import operators, properties, ui + + +def register(): + properties.register() + ui.register() + operators.register() + + +def unregister(): + properties.unregister() + ui.unregister() + operators.unregister() From b5ecd54cbaf4bac56474b035e79b4a07c39533a4 Mon Sep 17 00:00:00 2001 From: ruaridhg Date: Tue, 6 Jun 2023 13:41:40 +0100 Subject: [PATCH 02/58] Properties.py imports collection of socket properties (geom-like) and property group of custom props --- randomiser/define_prop/properties.py | 56 ++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 randomiser/define_prop/properties.py diff --git a/randomiser/define_prop/properties.py b/randomiser/define_prop/properties.py new file mode 100644 index 0000000..042c2fa --- /dev/null +++ b/randomiser/define_prop/properties.py @@ -0,0 +1,56 @@ +import bpy + +from .property_classes import ( + collection_UD_socket_properties, +) + + +# --------------------------- +# Properties +class PropertiesUserDefined(bpy.types.PropertyGroup): + """ + Class holding the set of properties + for the random seed + + """ + + user_defined_prop = bpy.props.StringProperty() + + user_defined: user_defined_prop # type: ignore + + +# ------------------------------------ +# Register / unregister classes +# ------------------------------------ +list_classes_to_register = [ + PropertiesUserDefined, +] + + +def register(): + collection_UD_socket_properties.register() + + for cls in list_classes_to_register: + bpy.utils.register_class(cls) + + # Custom scene properties + bpy.types.Scene.custom = bpy.props.CollectionProperty( + type=PropertiesUserDefined + ) + bpy.types.Scene.custom_index = bpy.props.IntProperty() + + print("geometry properties registered") + + +def unregister(): + collection_UD_socket_properties.unregister() + + for cls in list_classes_to_register: + bpy.utils.unregister_class(cls) + + # del bpy.types.Scene.user_defined + + del bpy.types.Scene.custom + del bpy.types.Scene.custom_index + + print("geometry properties unregistered") From 0a0ea06e3b161bf1c1d1eedf077be83cbe2eeca7 Mon Sep 17 00:00:00 2001 From: ruaridhg Date: Tue, 6 Jun 2023 13:50:55 +0100 Subject: [PATCH 03/58] Combined string list operators with geom operators --- randomiser/define_prop/operators.py | 657 ++++++++++++++++++++++++++++ 1 file changed, 657 insertions(+) create mode 100644 randomiser/define_prop/operators.py diff --git a/randomiser/define_prop/operators.py b/randomiser/define_prop/operators.py new file mode 100644 index 0000000..c45daf8 --- /dev/null +++ b/randomiser/define_prop/operators.py @@ -0,0 +1,657 @@ +import random + +import bpy +import numpy as np +from bpy.app.handlers import persistent + +from .. import config, utils + + +class CUSTOM_OT_actions(bpy.types.Operator): + """Move items up and down, add and remove""" + + bl_idname = "custom.list_action" + bl_label = "List Actions" + bl_description = "Move items up and down, add and remove" + bl_options = {"REGISTER"} + + action_prop = bpy.props.EnumProperty( + items=( + ("UP", "Up", ""), + ("DOWN", "Down", ""), + ("REMOVE", "Remove", ""), + ("ADD", "Add", ""), + ) + ) + action: action_prop # type: ignore + + def invoke(self, context, event): + scn = context.scene + idx = scn.custom_index + + try: + item = scn.custom[idx] + except IndexError: + pass + else: + if self.action == "DOWN" and idx < len(scn.custom) - 1: + scn.custom[idx + 1].name + scn.custom.move(idx, idx + 1) + scn.custom += 1 + info = 'Item "%s" moved to position %d' % ( + item.name, + scn.custom + 1, + ) + self.report({"INFO"}, info) + + elif self.action == "UP" and idx >= 1: + scn.custom[idx - 1].name + scn.custom.move(idx, idx - 1) + scn.custom_index -= 1 + info = 'Item "%s" moved to position %d' % ( + item.name, + scn.custom_index + 1, + ) + self.report({"INFO"}, info) + + elif self.action == "REMOVE": + info = 'Item "%s" removed from list' % (scn.custom[idx].name) + scn.custom_index -= 1 + scn.custom.remove(idx) + self.report({"INFO"}, info) + + if self.action == "ADD": + item = scn.custom.add() + item.name = "Your Name" + item.id = len(scn.custom) + scn.custom_index = len(scn.custom) - 1 + info = '"%s" added to list' % (item.name) + self.report({"INFO"}, info) + return {"FINISHED"} + + +class CUSTOM_OT_printItems(bpy.types.Operator): + """Print all items and their properties to the console""" + + bl_idname = "custom.print_items" + bl_label = "Print Items to Console" + bl_description = "Print all items and their properties to the console" + bl_options = {"REGISTER", "UNDO"} + + reverse_order_prop = bpy.props.BoolProperty( + default=False, name="Reverse Order" + ) + reverse_order: reverse_order_prop # type: ignore + + @classmethod + def poll(cls, context): + return bool(context.scene.custom) + + def execute(self, context): + scn = context.scene + if self.reverse_order: + for i in range(scn.custom, -1, -1): + item = scn.custom[i] + print("Name:", item.name, "-", "ID:", item.user_defined) + else: + for item in scn.custom: + print("Name:", item.name, "-", "ID", item.user_defined) + return {"FINISHED"} + + +class CUSTOM_OT_clearList(bpy.types.Operator): + """Clear all items of the list""" + + bl_idname = "custom.clear_list" + bl_label = "Clear List" + bl_description = "Clear all items of the list" + bl_options = {"INTERNAL"} + + @classmethod + def poll(cls, context): + return bool(context.scene.custom) + + def invoke(self, context, event): + return context.window_manager.invoke_confirm(self, event) + + def execute(self, context): + if bool(context.scene.custom): + context.scene.custom.clear() + self.report({"INFO"}, "All items removed") + else: + self.report({"INFO"}, "Nothing to remove") + return {"FINISHED"} + + +# -------------------------------------------- +# Operator Randomise selected sockets +# across all Geometry node groups +# -------------------------------------------- +class RandomiseAllGeometryNodes(bpy.types.Operator): + """Randomise the selected output sockets + across all geometry node groups + + Parameters + ---------- + bpy : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ + + # metadata + bl_idname = ( + "node.randomise_all_geometry_sockets" # this is appended to bpy.ops. + ) + bl_label = "Randomise selected sockets" + bl_options = {"REGISTER", "UNDO"} + + @classmethod + def poll(cls, context): + """Determine whether the operator can be executed. + + The operator can only run if there are geometry node groups + in the collection. If it can't be executed, the + button will appear as disabled. + + + Parameters + ---------- + context : _type_ + _description_ + + Returns + ------- + boolean + number of geometry node groups in the collection + """ + + return len(context.scene.socket_props_per_gng.collection) > 0 + + def invoke(self, context, event): + """Initialise parmeters before executing the operator + + The invoke() function runs before executing the operator. + Here, we + - add the list of input nodes and collection of socket properties to + the operator (self), and + - unselect the randomisation toggle of the sockets of input nodes if + they are not linked to any other node + + Parameters + ---------- + context : bpy_types.Context + the context from which the operator is executed + event : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ + # add list of GNGs to operator self + # NOTE: this list should have been updated already, + # when drawing the panel + cs = context.scene + self.list_subpanel_gng_names = [ + gng.name for gng in cs.socket_props_per_gng.collection + ] + + # for every GNG: save sockets to randomise + self.sockets_to_randomise_per_gng = {} + for gng_str in self.list_subpanel_gng_names: + # get collection of socket properties for this GNG + # ATT socket properties do not include the actual socket object + if cs.socket_props_per_gng.collection[ + gng_str + ].update_sockets_collection: + print("Collection of geometry sockets updated") + + sockets_props_collection = cs.socket_props_per_gng.collection[ + gng_str + ].collection + + # get candidate sockets for this GNG + candidate_sockets = cs.socket_props_per_gng.collection[ + gng_str + ].candidate_sockets + + # if socket unlinked and randomisation toggle is True: + # modify socket props to set toggle to False + self.sockets_to_randomise_per_gng[gng_str] = [] + for sckt in candidate_sockets: + # get socket identifier string + sckt_id = sckt.node.name + "_" + sckt.name + + # if this socket is selected to randomise but it is unlinked: + # set randomisation toggle to False + if (not sckt.is_linked) and ( + sockets_props_collection[sckt_id].bool_randomise + ): + setattr( + sockets_props_collection[sckt_id], + "bool_randomise", + False, + ) + print( + f"Socket {sckt_id} from {gng_str} is unlinked:", + "randomisation toggle set to False", + ) + + # after modifying randomisation toggle + # save list of sockets to randomise to dict, + # with key = material + if sockets_props_collection[sckt_id].bool_randomise: + self.sockets_to_randomise_per_gng[gng_str].append(sckt) + + return self.execute(context) + + def execute(self, context): + """Execute the randomiser operator + + Randomise the selected output sockets between + their min and max values. + + Parameters + ---------- + context : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ + cs = context.scene + + # For every GNG with a subpanel + for gng_str in self.list_subpanel_gng_names: + # get collection of socket properties for this material + # NOTE: socket properties do not include the actual socket object + sockets_props_collection = cs.socket_props_per_gng.collection[ + gng_str + ].collection + + # Loop through the sockets to randomise + for sckt in self.sockets_to_randomise_per_gng[gng_str]: + socket_id = sckt.node.name + "_" + sckt.name + + # get min value for this socket + min_val = np.array( + getattr( + sockets_props_collection[socket_id], + "min_" + cs.socket_type_to_attr[type(sckt)], + ) + ) + + # get max value for this socket + max_val = np.array( + getattr( + sockets_props_collection[socket_id], + "max_" + cs.socket_type_to_attr[type(sckt)], + ) + ) + + # set default value + # if socket type is boolean + if type(sckt) == bpy.types.NodeSocketBool: + sckt.default_value = random.choice( + [bool(list(m_val)[0]) for m_val in [min_val, max_val]] + ) # 1d only + # TODO: change for a faster option? + # bool(random.getrandbits(1))F + # https://stackoverflow.com/questions/6824681/get-a-random-boolean-in-python + + # if socket type is int + elif type(sckt) == bpy.types.NodeSocketInt: + sckt.default_value = random.randint(max_val, min_val) + + # for all other socket types + else: + # if type of the socket is NodeSocketColor, + # and max_val < min_val: switch them before randomising + # NOTE: these are not switched in the display panel + # (this is intended) + if (type(sckt) == bpy.types.NodeSocketColor) and any( + max_val < min_val + ): + max_val_new = np.where( + max_val >= min_val, max_val, min_val + ) + min_val_new = np.where( + min_val < max_val, min_val, max_val + ) + + # TODO: is there a more elegant way? + # feels a bit clunky.... + max_val = max_val_new + min_val = min_val_new + + # assign randomised socket value + sckt.default_value = random.uniform(min_val, max_val) + + return {"FINISHED"} + + +# NOTE: without the persistent decorator, +# the function is removed from the handlers' list +# after it is first executed +@persistent +def randomise_geometry_nodes_per_frame(dummy): + bpy.ops.node.randomise_all_geometry_sockets("INVOKE_DEFAULT") + return + + +# ------------------------------- +# Operator: view graph per GNG +# ------------------------------- +class ViewNodeGraphOneGNG(bpy.types.Operator): + """Show node graph for the relevant + Geometry Node Group + + Parameters + ---------- + bpy : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ + + bl_idname = ( + "gng.view_graph" # this is appended to bpy.ops. + # NOTE: it will be overwritten for each instance of + # the operator + ) + bl_label = "View node graph for this Geometry node group" + bl_options = {"REGISTER", "UNDO"} + + @classmethod + def poll(cls, context): + """Determine whether the operator can be executed. + + This operator can only run if: + - its geometry node group is of geometry type, + and either: + - the associated geometry node group is linked to a modifier + of the currently active object, or + - the associated geometry node group is an inner node and its + root parent is a geometry node group linked to a modifier + of the currently active object. + + An inner node is a geometry node group defined inside + another geometry node group. The path of nodes to an inner + node is the list of group nodes that leads to the inner node. + Its root parent is the only parent node group in the path of nodes + without a parent. + + If the operator can't be executed, the button will appear as disabled. + + Parameters + ---------- + context : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ + + cs = context.scene + cob = context.object + + # get list of all GNGs + list_gngs = [ + gr.name for gr in bpy.data.node_groups if gr.type == "GEOMETRY" + ] + + # get geometry node group (GNG) of this subpanel + subpanel_gng = cs.socket_props_per_gng.collection[cls.subpanel_gng_idx] + + # get list of GNGs linked to modifiers of the active object + list_gngs_in_modifiers = utils.get_gngs_linked_to_modifiers(cob) + list_gngs_in_modifiers_names = [ + ng.name for ng in list_gngs_in_modifiers + ] + + # get list of (inner) GNGs whose root parent is a modfier-linked + # GNG + map_node_group_to_root_node_group = utils.get_map_inner_gngs( + list_gngs_in_modifiers, + ) + + # define condition to enable the operator + display_operator = ( + subpanel_gng.name + in list_gngs + # TODO: maybe this is not required here? + ) and ( + (subpanel_gng.name in list_gngs_in_modifiers_names) + or ( + subpanel_gng.name + in [gr.name for gr in map_node_group_to_root_node_group.keys()] + ) + ) + + return display_operator + + def invoke(self, context, event): + """Initialise parmeters before executing the operator + + The invoke() function runs before executing the operator. + Here, we add the subpanel's geometry node group name to + the operator self + + Parameters + ---------- + context : bpy_types.Context + the context from which the operator is executed + event : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ + + cs = context.scene + subpanel_gng = cs.socket_props_per_gng.collection[ + self.subpanel_gng_idx + ] + self.subpanel_gng_name = subpanel_gng.name + + return self.execute(context) + + def execute(self, context): + """Execute the 'view graph' operator. + + It shows the graph for the geometry node group (GNG) shown in the + subpanel's header. + + If the GNG associated to the subpanel is linked to a modifier of the + active object, then that modifier is set to active and the graph + is automatically updated. + + If the GNG associated to the subpanel is NOT linked to a modifier, but + it is an inner GNG of a modifier-linked GNG, then: + - the modifier of the root parent GNG is set as active, and the graph + is set to the root parent view + - the path to the inner GNG is computed + - the graph of the inner GNG is displayed, by recursively setting each + parent node as active and then executing the 'edit node' command. + + + If the GNG associated to the subpanel is NOT linked to a modifier, and + it is NOT an inner GNG of a modifier-linked GNG, then a new modifier + will be added to the currently active material and this subpanel's GNG + will be linked to it + + Parameters + ---------- + context : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ + + cob = context.object + + # find the modifier linked to this GNG, if exists + subpanel_modifier = utils.get_modifier_linked_to_gng( + self.subpanel_gng_name, cob + ) + + # get dict of inner GNGs + # the dict maps inner GNGs to a tuple made of its root parent GNG + # and its depth + map_inner_node_groups_to_root_parent = utils.get_map_inner_gngs( + [gr for gr in bpy.data.node_groups if gr.type == "GEOMETRY"] + ) + + # if there is a modifier linked to this GNG: set that + # modifier as active (this will change the displayed graph) + if subpanel_modifier: + bpy.ops.object.modifier_set_active(modifier=subpanel_modifier.name) + + # ensure graph is at top level + utils.set_gngs_graph_to_top_level( + bpy.data.node_groups[self.subpanel_gng_name] + ) + + # if there is no modifier linked to this GNG, + # but it is an inner GNG whose root parent is a modifier-linked GNG: + # set the modifier as active and navigate the graph to the + # inner GNG + elif not subpanel_modifier and ( + self.subpanel_gng_name + in [gr.name for gr in map_inner_node_groups_to_root_parent.keys()] + ): + # find the modifier linked to the (root) parent and set as active + # NOTE: if the root parent is not linked to a modifier, + # the operator will show as disabled + root_parent_node_group = map_inner_node_groups_to_root_parent[ + bpy.data.node_groups[self.subpanel_gng_name] + ][0] + + root_parent_modifier = utils.get_modifier_linked_to_gng( + root_parent_node_group.name, cob + ) + + bpy.ops.object.modifier_set_active( + modifier=root_parent_modifier.name + ) + + # compute the path to this subpanel's GNG + # from the parent root GNG (both ends inclusive) + path_to_gng = utils.get_path_to_gng( + bpy.data.node_groups[self.subpanel_gng_name] + ) + + # ensure we are at the top level in the graph + # (top level = parent root GNG) + utils.set_gngs_graph_to_top_level(root_parent_node_group) + + # navigate the graph to the desired GNG + # at every step: we set the target GNG as active and + # click 'edit group' + for i, _ in enumerate(path_to_gng[:-1]): + # get target GNG for this step and its parent + gng_parent = path_to_gng[i] + gng_target = path_to_gng[i + 1] + + # get selectable version of the target GNG + selectable_gng_target = ( + utils.get_selectable_node_for_node_group(gng_target) + ) + + # set target GNG as active + if gng_parent == root_parent_node_group: + root_parent_node_group.nodes.active = selectable_gng_target + else: + selectable_parent_gng = ( + utils.get_selectable_node_for_node_group(gng_parent) + ) + selectable_parent_gng.node_tree.nodes.active = ( + selectable_gng_target + ) + + # click 'edit group', i.e. go one level down in the graph + bpy.ops.node.group_edit(exit=False) + + # if there is no modifier linked to this GNG + # and it is not an inner GNG: create a new modifier + # for the currently active object and link the GNG to it + else: + # add a new 'Geometry nodes group' modifier + # (will set it as active) + bpy.ops.object.modifier_add(type="NODES") + new_modifier = bpy.context.object.modifiers.active + + # assign the subpanel's GNGto this modifier + new_modifier.node_group = bpy.data.node_groups[ + self.subpanel_gng_name + ] + + return {"FINISHED"} + + +# --------------------- +# Classes to register +# --------------------- +list_classes_to_register = [ + RandomiseAllGeometryNodes, + CUSTOM_OT_actions, + CUSTOM_OT_printItems, + CUSTOM_OT_clearList, +] + +for i in range(config.MAX_NUMBER_OF_SUBPANELS): + operator_i = type( + f"ViewNodeGraphOneGNG_subpanel_{i}", + ( + ViewNodeGraphOneGNG, + bpy.types.Operator, + ), + { + "bl_idname": f"node.view_graph_for_gng_{i}", + "bl_label": "", + "subpanel_gng_idx": i, + }, + ) + list_classes_to_register.append(operator_i) # type: ignore + + +# ----------------------------------------- +# Register and unregister functions +# ------------------------------------------ +def register(): + for cls in list_classes_to_register: + bpy.utils.register_class(cls) + + bpy.app.handlers.frame_change_pre.append( + randomise_geometry_nodes_per_frame + ) + + print("geometry operators registered") + + +def unregister(): + for cls in list_classes_to_register: + bpy.utils.unregister_class(cls) + + bpy.app.handlers.frame_change_pre.remove( + randomise_geometry_nodes_per_frame + ) + + print("geometry operators unregistered") From b292430cfc711b0098fe4526774abb27aa2f7c8d Mon Sep 17 00:00:00 2001 From: ruaridhg Date: Tue, 6 Jun 2023 13:54:28 +0100 Subject: [PATCH 04/58] Added custom props as example of UI list - remove after integrated --- randomiser/custom_props/__init__.py | 13 + .../collection_custom_properties.py | 223 +++++++++++ randomiser/custom_props/config.py | 17 + randomiser/custom_props/custom_properties.py | 219 +++++++++++ randomiser/custom_props/operators.py | 317 ++++++++++++++++ randomiser/custom_props/properties.py | 196 ++++++++++ randomiser/custom_props/ui.py | 358 ++++++++++++++++++ 7 files changed, 1343 insertions(+) create mode 100644 randomiser/custom_props/__init__.py create mode 100644 randomiser/custom_props/collection_custom_properties.py create mode 100644 randomiser/custom_props/config.py create mode 100644 randomiser/custom_props/custom_properties.py create mode 100644 randomiser/custom_props/operators.py create mode 100644 randomiser/custom_props/properties.py create mode 100644 randomiser/custom_props/ui.py diff --git a/randomiser/custom_props/__init__.py b/randomiser/custom_props/__init__.py new file mode 100644 index 0000000..8051ea8 --- /dev/null +++ b/randomiser/custom_props/__init__.py @@ -0,0 +1,13 @@ +from . import operators, properties, ui + + +def register(): + properties.register() + ui.register() + operators.register() + + +def unregister(): + properties.unregister() + ui.unregister() + operators.unregister() diff --git a/randomiser/custom_props/collection_custom_properties.py b/randomiser/custom_props/collection_custom_properties.py new file mode 100644 index 0000000..096f186 --- /dev/null +++ b/randomiser/custom_props/collection_custom_properties.py @@ -0,0 +1,223 @@ +import re + +import bpy + +from .. import utils +from .custom_properties import CustomProperties + + +# ----------------------------------------------------------------- +# Setter / getter methods for update_sockets_collection attribute +# ---------------------------------------------------------------- +def compute_sockets_sets(self): + # set of sockets in collection for this material + self.set_sckt_names_in_collection_of_props = set( + sck_p.name for sck_p in self.collection + ) + + # set of sockets in graph *for this material* ! + list_sckt_names_in_UIlist = [] + for sck in self.candidate_sockets: + # if socket comes from a node inside a group + # (TODO is there a better way to check whether the node is in a group?) + if sck.name in self.candidate_sockets: + list_sckt_names_in_UIlist.append("UI_list" + "_" + sck.name) + # if socket comes from an independent node + else: + list_sckt_names_in_UIlist.append("_" + sck.name) + + self.set_sckt_names_in_UIlist = set(list_sckt_names_in_UIlist) + + # set of sockets that are just in one of the two groups + self.set_of_sckt_names_in_one_only = ( + self.set_sckt_names_in_collection_of_props.symmetric_difference( + self.set_sckt_names_in_UIlist + ) + ) + + +def get_update_collection(self): + """Get function for the update_sockets_collection attribute + of the class ColCustomProperties + + It will run when the property value is 'get' and + it will update the collection of socket properties if required + + Returns + ------- + boolean + returns True if the collection of socket properties is updated, + otherwise it returns False + """ + # compute the different sets of sockets + compute_sockets_sets(self) + + # if there is a difference between + # sets of sockets in graph and in collection: + # edit the set of sockets in collection + # for this material with the latest data + if self.set_of_sckt_names_in_one_only: + set_update_collection(self, True) + return True # if returns True, it has been updated + else: + return False # if returns False, it hasn't + + +def set_update_collection(self, value): + """ + 'Set' function for the update_sockets_collection attribute + of the class ColCustomProperties. + + It will run when the property value is 'set'. + + It will update the collection of socket properties as follows: + - For the set of sockets that exist only in either + the collection or the graph: + - if the socket exists only in the collection: remove from + collection + - if the socket exists only in the node graph: add to collection + with initial values + - For the rest of sockets: leave untouched + + Parameters + ---------- + value : boolean + if True, the collection of socket properties is + overwritten to consider the latest data + """ + + if value: + # if the update fn is triggered directly and not via + # getter fn: compute sets + if not hasattr(self, "set_of_sckt_names_in_one_only"): + compute_sockets_sets(self) + + # update sockets that are only in either + # the collection set or the graph set + for sckt_name in self.set_of_sckt_names_in_one_only: + # - if the socket exists only in the collection: remove from + # collection + if sckt_name in self.set_sckt_names_in_collection_of_props: + self.collection.remove(self.collection.find(sckt_name)) + + # - if the socket exists only in the node graph: add to collection + # with initial values + if sckt_name in self.set_sckt_names_in_UIlist: + sckt_prop = self.collection.add() + sckt_prop.name = sckt_name + sckt_prop.bool_randomise = True + + # --------------------------- + # TODO: review - is this too hacky? + # get socket object for this socket name + # NOTE: my definition of socket name + # (node.name + _ + socket.name) + for s in self.candidate_sockets: + # build socket id from scratch + socket_id = s.node.name + "_" + s.name + if s.node.id_data.name in bpy.data.node_groups: + socket_id = s.node.id_data.name + "_" + socket_id + + if socket_id == sckt_name: + sckt = s + break + + # add min/max values + # for this socket type, get the name of the attribute + # holding the min/max properties + socket_attrib_str = bpy.context.scene.socket_type_to_attr[ + type(sckt) + ] + # for the shape of the array from the attribute name: + # extract last number between '_' and 'd/D' in the attribute + # name + n_dim = int( + re.findall(r"_(\d+)(?:d|D)", socket_attrib_str)[-1] + ) + # --------------------------- + + # get dict with initial min/max values for this socket type + ini_min_max_values = ( + bpy.context.scene.socket_type_to_ini_min_max[type(sckt)] + ) + + # assign initial value + for m_str in ["min", "max"]: + setattr( + sckt_prop, + m_str + "_" + socket_attrib_str, + (ini_min_max_values[m_str],) * n_dim, + ) + + +# ----------------------- +# ColCustomProperties +# --------------------- +class ColCustomProperties(bpy.types.PropertyGroup): + """Class holding the collection of socket properties and + a boolean property to update the collection if required + (for example, if new nodes are added) + + NOTE: we use the update_sockets_collection property as an + auxiliary property because the CollectionProperty has no update function + https://docs.blender.org/api/current/bpy.props.html#update-example + + """ + + # name of the material + name: bpy.props.StringProperty() # type: ignore + + # collection of socket properties + collection: bpy.props.CollectionProperty( # type: ignore + type=CustomProperties + ) + + # 'dummy' attribute to update collection of socket properties + update_custom_collection: bpy.props.BoolProperty( # type: ignore + default=False, + get=get_update_collection, + set=set_update_collection, + ) + + # -------------------------------- + # candidate sockets for this material + @property + def candidate_sockets(self): # getter method + """Get function for the candidate_sockets property + + We define candidate sockets as the set of output sockets + in input nodes, in the graph for the currently active + material. Input nodes are nodes with only output sockets + (i.e., no input sockets). + + It returns a list of sockets that are candidates for + the randomisation. + + + Returns + ------- + list + list of sockets in the input nodes in the graph + """ + # get list of input nodes for this material + # input nodes are defined as those: + # - with no input sockets + # - their name starts with random + # - and they can be independent or inside a node group + list_input_nodes = utils.get_material_nodes_to_randomise_all(self.name) + + # list of sockets + list_sockets = [out for nd in list_input_nodes for out in nd.outputs] + + return list_sockets + + +# ----------------------------------------- +# Register and unregister functions +# ------------------------------------------ +def register(): + bpy.utils.register_class(ColCustomProperties) + + +def unregister(): + bpy.utils.unregister_class(ColCustomProperties) diff --git a/randomiser/custom_props/config.py b/randomiser/custom_props/config.py new file mode 100644 index 0000000..239fd75 --- /dev/null +++ b/randomiser/custom_props/config.py @@ -0,0 +1,17 @@ +# Parameters shared across materials modules + + +# MAX_NUMBER_OF_SUBPANELS: upper limit for the expected +# number of *materials* in a scene. +# This number of subpanels will be defined as classes, but +# only those panels with index < total number of materials +# will be displayed. +MAX_NUMBER_OF_SUBPANELS = 100 + +# MAX_NUMBER_OF_SUBSUBPANELS: upper limit for the expected +# number of *group nodes in a single material*. +# A total of MAX_NUMBER_OF_SUBPANELS*MAX_NUMBER_OF_SUBSUBPANELS subsubpanels +# will be defined as classes, but only those panels with +# index < total number of group nodes per material +# will be displayed. +MAX_NUMBER_OF_SUBSUBPANELS = 100 diff --git a/randomiser/custom_props/custom_properties.py b/randomiser/custom_props/custom_properties.py new file mode 100644 index 0000000..5c2ee02 --- /dev/null +++ b/randomiser/custom_props/custom_properties.py @@ -0,0 +1,219 @@ +import bpy +import numpy as np + + +# ----------------------------------------- +# Bounds to SocketProperties +# ----------------------------------------- +def constrain_min_closure(m_str): + """Constain min value with closure + + Parameters + ---------- + m_str : str + string specifying the socket attribute (e.g., float_1d) + + Returns + ------- + _type_ + lambda function evaluated at the specified m_str + + """ + + def constrain_min(self, context, m_str): + """Constrain min value + + If min > max --> min is reset to max value + (i.e., no randomisation) + + Parameters + ---------- + context : _type_ + _description_ + m_str : str + string specifying the socket attribute (e.g., float_1d) + """ + # self is a 'SocketProperties' object + min_array = np.array(getattr(self, "min_" + m_str)) + max_array = np.array(getattr(self, "max_" + m_str)) + if any(min_array > max_array): + setattr( + self, + "min_" + m_str, + np.where(min_array > max_array, max_array, min_array), + ) + return + + return lambda slf, ctx: constrain_min(slf, ctx, m_str) + + +def constrain_max_closure(m_str): + """Constain max value with closure + + Parameters + ---------- + m_str : str + string specifying the socket attribute (e.g., float_1d) + + Returns + ------- + _type_ + lambda function evaluated at the specified m_str + + """ + + def constrain_max(self, context, m_str): + """Constrain max value + + if max < min --> max is reset to min value + (i.e., no randomisation) + + Parameters + ---------- + context : _type_ + _description_ + m_str : str + string specifying the socket attribute (e.g., float_1d) + """ + # self is a 'SocketProperties' object + min_array = np.array(getattr(self, "min_" + m_str)) + max_array = np.array(getattr(self, "max_" + m_str)) + if any(max_array < min_array): + setattr( + self, + "max_" + m_str, + np.where(max_array < min_array, min_array, max_array), + ) + return + + return lambda slf, ctx: constrain_max(slf, ctx, m_str) + + +def constrain_rgba_closure(m_str): + """Constain RGBA value with closure + + Parameters + ---------- + m_str : str + string specifying the socket attribute (e.g., float_1d) + + Returns + ------- + _type_ + lambda function evaluated at the specified m_str + + """ + + def constrain_rgba(self, context, min_or_max_full_str): + """Constrain RGBA value + + if RGBA socket: constrain values to be between 0 and 1 + + Parameters + ---------- + context : _type_ + _description_ + m_str : str + string specifying the socket attribute (e.g., float_1d) + """ + min_or_max_array = np.array(getattr(self, min_or_max_full_str)) + if any(min_or_max_array > 1.0) or any(min_or_max_array < 0.0): + setattr( + self, + min_or_max_full_str, + np.clip(min_or_max_array, 0.0, 1.0), + ) + return + + return lambda slf, ctx: constrain_rgba(slf, ctx, m_str) + + +# ----------------------- +# SocketProperties +# --------------------- +class CustomProperties(bpy.types.PropertyGroup): + """ + Class holding the set of properties + for a socket, namely: + - socket name, + - min/max values, and + - boolean for randomisation + + Because I think it is not possible to define attributes dynamically, + for now we define an attribute for each possible socket type + in the input nodes. These are all FloatVectors of different sizes. + The size is specified in the attribute's name: + - min/max_float_1d + - min/max_float_3d + - min/max_float_4d + - min/max_rgba_4d + + """ + + # TODO: how to set attributes dynamically? + # TODO: I don't really get why this type definition is also an assignment? + + # --------------------- + # name of the socket + # NOTE: if we make a Blender collection of this type of objects, + # we will be able to access them by name + name: bpy.props.StringProperty() # type: ignore + + # TODO: include the socket itself here to? + # socket: PointerProperty(type=bpy.types.NodeSocketStandard?) + + # --------------------- + # float 1d + float_1d_str = "float_1d" + min_float_1d: bpy.props.FloatVectorProperty( # type: ignore + size=1, update=constrain_min_closure(float_1d_str) + ) + + max_float_1d: bpy.props.FloatVectorProperty( # type: ignore + size=1, update=constrain_max_closure(float_1d_str) + ) + + # --------------------- + # float 3d + float_3d_str = "float_3d" + min_float_3d: bpy.props.FloatVectorProperty( # type: ignore + update=constrain_min_closure(float_3d_str) + ) + max_float_3d: bpy.props.FloatVectorProperty( # type: ignore + update=constrain_max_closure(float_3d_str) + ) + + # --------------------- + # float 4d + float_4d_str = "float_4d" + min_float_4d: bpy.props.FloatVectorProperty( # type: ignore + size=4, + update=constrain_min_closure(float_4d_str), + ) + max_float_4d: bpy.props.FloatVectorProperty( # type: ignore + size=4, update=constrain_max_closure(float_4d_str) + ) + + # --------------------- + # rgba + rgba_4d_str = "rgba_4d" + min_rgba_4d: bpy.props.FloatVectorProperty( # type: ignore + size=4, + update=constrain_rgba_closure("min_" + rgba_4d_str), # noqa + ) + max_rgba_4d: bpy.props.FloatVectorProperty( # type: ignore + size=4, update=constrain_rgba_closure("max_" + rgba_4d_str) # noqa + ) + + # --------------------- + # randomisation toggle + bool_randomise: bpy.props.BoolProperty() # type: ignore + + +# Register / unregister +def register(): + bpy.utils.register_class(CustomProperties) + + +def unregister(): + bpy.utils.unregister_class(CustomProperties) diff --git a/randomiser/custom_props/operators.py b/randomiser/custom_props/operators.py new file mode 100644 index 0000000..7801437 --- /dev/null +++ b/randomiser/custom_props/operators.py @@ -0,0 +1,317 @@ +from random import uniform + +import bpy +from bpy.app.handlers import persistent + +from .. import utils + + +# ------------------------------- +# Operator +# ------------------------------- +class AddCustomPropTolist(bpy.types.Operator): + # docstring shows as a tooltip for menu items and buttons. + """Randomise the position and orientation of the camera + + Parameters + ---------- + bpy : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ + + bl_idname = "opr.add_custom_prop_to_list" # appended to bpy.ops. + bl_label = "Add custom prop to list" + + bl_options = {"REGISTER", "UNDO"} + + # @classmethod + # def poll(cls, context): + # # check the context here + # return context.object is not None + + def execute(self, context): + """Execute the randomiser operator + + Randomise the position and rotation x,y,z components + of the camera between their min and max values. + + Parameters + ---------- + context : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ + + utils.add_custom_prop_to_custom_list(context) + + return {"FINISHED"} + + +class ApplyRandomCustom(bpy.types.Operator): + # docstring shows as a tooltip for menu items and buttons. + """Randomise the position and orientation of the camera + + Parameters + ---------- + bpy : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ + + bl_idname = "opr.apply_random_custom_prop" # appended to bpy.ops. + bl_label = "Apply random to custom prop" + + bl_options = {"REGISTER", "UNDO"} + + @classmethod + def poll(cls, context): + # check the context here + return context.object is not None + + def execute(self, context): + """Execute the randomiser operator + + Randomise the position and rotation x,y,z components + of the camera between their min and max values. + + Parameters + ---------- + context : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ + + cust_min = context.scene.custom_props.custom_min[0] + cust_max = context.scene.custom_props.custom_max[0] + cust_range = [cust_min, cust_max] + + rand_cust = context.scene.custom_props.bool_rand_cust + + custom_input = context.scene.custom_props.custom_input + custom_idx = context.scene.custom_props.custom_idx + + randomise_selected( + context, + cust_range, + rand_cust, + custom_input, + custom_idx, + ) + + return {"FINISHED"} + + # def invoke(self, context, event): + # return context.window_manager.invoke_props_dialog(self) + + +@persistent +def randomise_custom_per_frame(dummy): + bpy.ops.opr.apply_random_custom_prop("INVOKE_DEFAULT") + + return + + +# -------------------------------------------------- +# Randomise_selected function: + + +def randomise_selected( + context, cust_range, rand_cust, custom_input, custom_idx +): + """Generate random numbers between the range for x/y/z + directions in location and rotation + + Parameters + ---------- + bpy : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ + + if rand_cust: + custom_input.split(".") + # attr_string = list_string[len(list_string)-1] + # obj_string = custom_input.rsplit(".",1)[0] + + getattr(context.active_object, custom_input)[custom_idx] = uniform( + cust_range[0], cust_range[1] + ) + + else: # otherwise the values change under us + uniform(0.0, 0.0), uniform(0.0, 0.0), uniform(0.0, 0.0) + + +class CUSTOM_OT_actions(bpy.types.Operator): + """Move items up and down, add and remove""" + + bl_idname = "custom.list_action" + bl_label = "List Actions" + bl_description = "Move items up and down, add and remove" + bl_options = {"REGISTER"} + + action_prop = bpy.props.EnumProperty( + items=( + ("UP", "Up", ""), + ("DOWN", "Down", ""), + ("REMOVE", "Remove", ""), + ("ADD", "Add", ""), + ) + ) + action: action_prop # type: ignore + + def invoke(self, context, event): + scn = context.scene + idx = scn.custom_index + + try: + item = scn.custom[idx] + except IndexError: + pass + else: + if self.action == "DOWN" and idx < len(scn.custom) - 1: + scn.custom[idx + 1].name + scn.custom.move(idx, idx + 1) + scn.custom_index += 1 + info = 'Item "%s" moved to position %d' % ( + item.name, + scn.custom_index + 1, + ) + self.report({"INFO"}, info) + + elif self.action == "UP" and idx >= 1: + scn.custom[idx - 1].name + scn.custom.move(idx, idx - 1) + scn.custom_index -= 1 + info = 'Item "%s" moved to position %d' % ( + item.name, + scn.custom_index + 1, + ) + self.report({"INFO"}, info) + + elif self.action == "REMOVE": + info = 'Item "%s" removed from list' % (scn.custom[idx].name) + scn.custom_index -= 1 + scn.custom.remove(idx) + self.report({"INFO"}, info) + + if self.action == "ADD": + item = scn.custom.add() + item.name = "Your Name" + item.id = len(scn.custom) + scn.custom_index = len(scn.custom) - 1 + info = '"%s" added to list' % (item.name) + self.report({"INFO"}, info) + return {"FINISHED"} + + +class CUSTOM_OT_printItems(bpy.types.Operator): + """Print all items and their properties to the console""" + + bl_idname = "custom.print_items" + bl_label = "Print Items to Console" + bl_description = "Print all items and their properties to the console" + bl_options = {"REGISTER", "UNDO"} + + reverse_order_prop = bpy.props.BoolProperty( + default=False, name="Reverse Order" + ) + + reverse_order: reverse_order_prop # type: ignore + + @classmethod + def poll(cls, context): + return bool(context.scene.custom) + + def execute(self, context): + scn = context.scene + if self.reverse_order: + for i in range(scn.custom_index, -1, -1): + item = scn.custom[i] + print("Name:", item.name, "-", "ID:", item.id) + else: + for item in scn.custom: + print("Name:", item.name, "-", "ID", item.id) + return {"FINISHED"} + + +class CUSTOM_OT_clearList(bpy.types.Operator): + """Clear all items of the list""" + + bl_idname = "custom.clear_list" + bl_label = "Clear List" + bl_description = "Clear all items of the list" + bl_options = {"INTERNAL"} + + @classmethod + def poll(cls, context): + return bool(context.scene.custom) + + def invoke(self, context, event): + return context.window_manager.invoke_confirm(self, event) + + def execute(self, context): + if bool(context.scene.custom): + context.scene.custom.clear() + self.report({"INFO"}, "All items removed") + else: + self.report({"INFO"}, "Nothing to remove") + return {"FINISHED"} + + +# --------------------- +# Classes to register +# --------------------- +list_classes_to_register = [ + ApplyRandomCustom, + AddCustomPropTolist, + CUSTOM_OT_actions, + CUSTOM_OT_printItems, + CUSTOM_OT_clearList, +] + + +# ----------------------------------------- +# Register and unregister functions +# ------------------------------------------ +def register(): + """This is run when the add-on is enabled""" + + for cls in list_classes_to_register: + bpy.utils.register_class(cls) + + bpy.app.handlers.frame_change_pre.append(randomise_custom_per_frame) + + print("transform operators registered") + + +def unregister(): + """ + This is run when the add-on is disabled / Blender closes + """ + for cls in list_classes_to_register: + bpy.utils.unregister_class(cls) + + bpy.app.handlers.frame_change_pre.remove(randomise_custom_per_frame) + + print("transform operators unregistered") diff --git a/randomiser/custom_props/properties.py b/randomiser/custom_props/properties.py new file mode 100644 index 0000000..d1797b2 --- /dev/null +++ b/randomiser/custom_props/properties.py @@ -0,0 +1,196 @@ +import bpy +import numpy as np + + +# ----------------------------------------- +# Bounds to PropertiesApplyRandomTransform +# ----------------------------------------- +def constrain_min_closure(m_str): + """Constain min value with closure + + Parameters + ---------- + m_str : str + string specifying the socket attribute (e.g., float_1d) + + Returns + ------- + _type_ + lambda function evaluated at the specified m_str + + """ + + def constrain_min(self, context, m_str): + """Constrain min value + + If min > max --> min is reset to max value + (i.e., no randomisation) + + Parameters + ---------- + context : _type_ + _description_ + m_str : str + string specifying the socket attribute (e.g., float_1d) + """ + # self is a 'PropertiesApplyRandomTransform' object + min_array = np.array(getattr(self, m_str + "_min")) + max_array = np.array(getattr(self, m_str + "_max")) + if any(min_array > max_array): + setattr( + self, + m_str + "_min", + np.where(min_array > max_array, max_array, min_array), + ) + return + + return lambda slf, ctx: constrain_min(slf, ctx, m_str) + + +def constrain_max_closure(m_str): + """Constain max value with closure + + Parameters + ---------- + m_str : str + string specifying the socket attribute (e.g., float_1d) + + Returns + ------- + _type_ + lambda function evaluated at the specified m_str + + """ + + def constrain_max(self, context, m_str): + """Constrain max value + + if max < min --> max is reset to min value + (i.e., no randomisation) + + Parameters + ---------- + context : _type_ + _description_ + m_str : str + string specifying the socket attribute (e.g., float_1d) + """ + # self is a 'SocketProperties' object + min_array = np.array(getattr(self, m_str + "_min")) + max_array = np.array(getattr(self, m_str + "_max")) + if any(max_array < min_array): + setattr( + self, + m_str + "_max", + np.where(max_array < min_array, min_array, max_array), + ) + return + + return lambda slf, ctx: constrain_max(slf, ctx, m_str) + + +# --------------------------- +# Properties +class PropertiesCustomTransform(bpy.types.PropertyGroup): + """ + Class holding the set of properties + for the camera position and rotation: + - min/max values for x/y/z component of position and rotation, and + - boolean for delta position and rotation + - boolean for setting seed value + - integer for the actual seed value + + """ + + # Position min and max values + custom_input_prop = bpy.props.StringProperty(name="enter text") + custom_input: custom_input_prop # type: ignore + custom_min: bpy.props.FloatVectorProperty( # type: ignore + size=1, + step=100, # update=constrain_min_closure(custom_input) + ) # type: ignore + custom_max: bpy.props.FloatVectorProperty( # type: ignore + size=1, + step=100, # update=constrain_max_closure(custom_input) + ) # type: ignore + custom_idx: bpy.props.IntProperty(default=0) # type: ignore + + # BOOL + bool_rand_cust: bpy.props.BoolProperty(default=True) # type: ignore + + +class PropertiesCustomList(bpy.types.PropertyGroup): + custom_string_prop = bpy.props.StringProperty(default="camera.location") + custom_string: custom_string_prop # type: ignore + + +custom_string_prop = bpy.props.StringProperty(default="camera.location") + + +class CUSTOM_colorCollection(bpy.types.PropertyGroup): + # name: StringProperty() -> Instantiated by default + id_prop = bpy.props.IntProperty() + id: id_prop # type: ignore + + +# -------------------------------------------------- +# Register and unregister functions: +list_classes_to_register = [ + PropertiesCustomTransform, + PropertiesCustomList, + CUSTOM_colorCollection, +] + +list_context_scene_attr = ["socket_type_to_attr"] + + +def register(): + """This is run when the add-on is enabled""" + + for cls in list_classes_to_register: + bpy.utils.register_class(cls) + + if cls == PropertiesCustomTransform: + bpy.types.Scene.custom_props = bpy.props.PointerProperty( + type=PropertiesCustomTransform + ) + + if cls == PropertiesCustomList: + bpy.types.Scene.custom_list = bpy.props.PointerProperty( + type=PropertiesCustomList + ) + + for attr, attr_val in zip( + list_context_scene_attr, + [custom_string_prop], + ): + setattr(bpy.types.Scene, attr, attr_val) + + # Custom scene properties + if cls == CUSTOM_colorCollection: + bpy.types.Scene.custom = bpy.props.CollectionProperty( + type=CUSTOM_colorCollection + ) + bpy.types.Scene.custom_index = bpy.props.IntProperty() + print("transform properties registered") + + +def unregister(): + """ + This is run when the add-on is disabled / Blender closes + """ + for cls in list_classes_to_register: + bpy.utils.unregister_class(cls) + + del bpy.types.Scene.custom_props + del bpy.types.Scene.custom_list + + # delete the custom properties linked to bpy.context.scene + for attr in list_context_scene_attr: + if hasattr(bpy.types.Scene, attr): + delattr(bpy.types.Scene, attr) + + del bpy.types.Scene.custom + del bpy.types.Scene.custom_index + + print("transform properties unregistered") diff --git a/randomiser/custom_props/ui.py b/randomiser/custom_props/ui.py new file mode 100644 index 0000000..d67ec77 --- /dev/null +++ b/randomiser/custom_props/ui.py @@ -0,0 +1,358 @@ +import bpy + +from .. import utils + + +# ---------------------- +# Common sections +# --------------------- +class TemplatePanel(bpy.types.Panel): + bl_space_type = "NODE_EDITOR" + bl_region_type = "UI" + bl_category = "Randomiser" # this shows up as the tab name + + +# ---------------------- +# Main panel +# --------------------- + + +class CUSTOM_UL_items(bpy.types.UIList): + def draw_item( + self, + context, + layout, + data, + item, + icon, + active_data, + active_propname, + index, + ): + split = layout.split(factor=0.3) + split.label(text="Index: %d" % (index)) + custom_icon = "COLOR" + split.prop(item, "name", icon=custom_icon, emboss=False, text="") + + def invoke(self, context, event): + pass + + +class MainPanelRandomCustomProps(TemplatePanel, bpy.types.Panel): + """Class defining the panel for randomising + the camera transform + + """ + + bl_idname = "CUSTOM_PROPS_PT_mainpanel" + bl_label = "Randomise CUSTOM PROPS" + + def draw(self, context): + column = self.layout.column(align=True) + column.label( + text="Choose property to see available properties to randomise" + ) + column.prop(context.scene.custom_props, "custom_input", text="") + # column.operator("opr.add_custom_prop_to_list", + # text="Add to Custom List") + + layout = self.layout + scn = bpy.context.scene + + rows = 2 + row = self.layout.row() + row.template_list( + "CUSTOM_UL_items", + "", + scn, + "custom", + scn, + "custom_index", + rows=rows, + ) + + col = row.column(align=True) + col.operator( + "custom.list_action", icon="ZOOM_IN", text="" + ).action = "ADD" + col.operator( + "custom.list_action", icon="ZOOM_OUT", text="" + ).action = "REMOVE" + col.separator() + col.operator( + "custom.list_action", icon="TRIA_UP", text="" + ).action = "UP" + col.operator( + "custom.list_action", icon="TRIA_DOWN", text="" + ).action = "DOWN" + + row = layout.row() + col = row.column(align=True) + row = col.row(align=True) + row.operator( + "custom.print_items", icon="LINENUMBERS_ON" + ) # LINENUMBERS_OFF, ANIM + row = col.row(align=True) + row.operator("custom.clear_list", icon="X") + + +# --------------------------------------------------- +# Common layout for list of sockets to randomise +# ---------------------------------------------------- +def draw_sockets_list( + cs, + layout, + list_input_nodes, + sockets_props_collection, +): + # Define UI fields for every socket property + # NOTE: if I don't sort the input nodes, everytime one of the nodes is + # selected in the graph it moves to the bottom of the panel. + list_input_nodes_sorted = sorted(list_input_nodes, key=lambda x: x.name) + print(list_input_nodes_sorted) + for i_n, nd in enumerate(list_input_nodes_sorted): + row = layout.row() + + # if first node: add labels for + # name, min, max and randomisation toggle + if i_n == 0: + row_split = row.split() + col1 = row_split.column(align=True) + col2 = row_split.column(align=True) + col3 = row_split.column(align=True) + col4 = row_split.column(align=True) + col5 = row_split.column(align=True) + + col1.label(text=nd.name) + col2.label(text="") + col3.label(text="min") + col4.label(text="max") + col5.label(text="index") + + # # input node name + # col1.label(text=nd.name) + # col1.alignment = "CENTER" + + # # min label + # col3.alignment = "CENTER" + # col3.label(text="min") + + # # max label + # col4.alignment = "CENTER" + # col4.label(text="max") + + # if not first node: add just node name + else: + row.separator(factor=1.0) # add empty row before each node + row = layout.row() + + row.label(text=nd.name) + + # add sockets for this node in the subseq rows + for sckt in nd.outputs: + # split row in 5 columns + row = layout.row() + row_split = row.split() + col1 = row_split.column(align=True) + col2 = row_split.column(align=True) + col3 = row_split.column(align=True) + col4 = row_split.column(align=True) + col5 = row_split.column(align=True) + + # socket name + col1.alignment = "RIGHT" + col1.label(text=sckt.name) + + # socket current value + col2.prop( + sckt, + "default_value", + icon_only=True, + ) + col2.enabled = False # current value is not editable + + # socket min and max columns + socket_id = nd.name + "_" + sckt.name + if nd.id_data.name in bpy.data.node_groups: + socket_id = nd.id_data.name + "_" + socket_id + + # if socket is a color: format min/max as a color picker + # and an array (color picker doesn't include alpha value) + if type(sckt) == bpy.types.NodeSocketColor: + for m_str, col in zip(["min", "max"], [col3, col4]): + # color picker + col.template_color_picker( + sockets_props_collection[socket_id], + m_str + "_" + cs.socket_type_to_attr[type(sckt)], + ) + # array + for j, cl in enumerate(["R", "G", "B", "alpha"]): + col.prop( + sockets_props_collection[socket_id], + m_str + "_" + cs.socket_type_to_attr[type(sckt)], + icon_only=False, + text=cl, + index=j, + ) + # if socket is not color type: format as a regular property + else: + for m_str, col in zip(["min", "max"], [col3, col4]): + col.prop( + sockets_props_collection[socket_id], + m_str + "_" + cs.socket_type_to_attr[type(sckt)], + icon_only=True, + ) + + # randomisation toggle + col5.prop( + sockets_props_collection[socket_id], + "bool_randomise", + icon_only=True, + ) + + # def draw(self, context): + + # col1.prop(context.scene.custom_props, "custom_input", text="") + # # col1.enabled = False + + # # col1.label(context.scene.custom_props, "custom_input") + # # col1.enabled=True + + # col2.prop( + # context.scene.custom_props, + # "custom_min", + # icon_only=True, + # ) + + # col3.prop( + # context.scene.custom_props, + # "custom_max", + # icon_only=True, + # ) + + # col4.prop( + # context.scene.custom_props, + # "custom_idx", + # icon_only=True, + # ) + + # col5.prop( + # context.scene.custom_props, + # "bool_rand_cust", + # icon_only=True, + # ) + + # # Bool delta + # col = self.layout.column() + + # # Randomise button + # col.operator("opr.apply_random_custom_prop", text="Randomise") + + +# ------------------------------ +# Subpanel for each material +# ----------------------------- +class SubPanelRandomCustomProps(TemplatePanel, bpy.types.Panel): + """Class defining the panel for randomising + material node properties + + """ + + bl_idname = "CUSTOM_PROPS_PT_subpanel" + bl_parent_id = "CUSTOM_PROPS_PT_mainpanel" + bl_label = "" # title of the panel displayed to the user + # bl_options = {"DEFAULT_CLOSED"} + # https://docs.blender.org/api/master/bpy.types.Panel.html#bpy.types.Panel.bl_options + + @classmethod + def poll(cls, context): + cs = context.scene + + # force an update on the materials collection first + # the '.update_collection' attribute + # triggers the get function that checks if an update is + # required. If it is, the collection of sockets is updated + # and returns TRUE + if cs.socket_props_per_material.get_update_collection: + print("Collection of materials updated") + + # only display subpanels for which this is true + return cls.subpanel_custom_idx < len( + cs.socket_props_per_material.collection + ) + + def draw_header(self, context): + cs = context.scene + # TODO: maybe a dict? can order of materials change? + cs.socket_props_per_material.collection[self.subpanel_custom_idx] + + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False # No animation. + + # # For now: view graph button on top of material name + # layout.operator( + # f"node.view_graph_for_material_{self.subpanel_custom_idx}", + # text=subpanel_custom.name, + # emboss=True, + # ) + + def draw(self, context): + # get name of the material for this subpanel + cs = context.scene + subpanel_custom = cs.socket_props_per_material.collection[ + self.subpanel_custom_idx + ] + + # then force an update in the sockets per material + # subpanel_material_name = subpanel_material.name + if cs.socket_props_per_material.collection[ + subpanel_custom.name + ].update_sockets_collection: + print("Collection of sockets updated") + + # get (updated) collection of socket properties + # for the current material + sockets_props_collection = cs.socket_props_per_material.collection[ + subpanel_custom.name + ].collection + + # Get list of input nodes to randomise + # for this subpanel's material + list_input_nodes = utils.get_custom_props_to_randomise_indep( + subpanel_custom.name + ) + + draw_sockets_list( + cs, + self.layout, + list_input_nodes, + sockets_props_collection, + ) + + +# -------------------------------------------------- +# Register and unregister functions: +list_classes_to_register = [ + MainPanelRandomCustomProps, + CUSTOM_UL_items, +] + + +def register(): + """This is run when the add-on is enabled""" + + for cls in list_classes_to_register: + bpy.utils.register_class(cls) + + print("custom UI registered") + + +def unregister(): + """ + This is run when the add-on is disabled / Blender closes + """ + for cls in list_classes_to_register: + bpy.utils.unregister_class(cls) + + print("custom UI unregistered") From 0eae07e73093cd767ed47518bfe424c2f24a72ea Mon Sep 17 00:00:00 2001 From: ruaridhg Date: Tue, 6 Jun 2023 17:05:58 +0100 Subject: [PATCH 05/58] Skeleton of collection_UD, need to sort sockets and remove input nodes --- .../collection_UD_socket_properties.py | 251 ++++++++++++++++++ 1 file changed, 251 insertions(+) create mode 100644 randomiser/define_prop/property_classes/collection_UD_socket_properties.py diff --git a/randomiser/define_prop/property_classes/collection_UD_socket_properties.py b/randomiser/define_prop/property_classes/collection_UD_socket_properties.py new file mode 100644 index 0000000..c4cb0a5 --- /dev/null +++ b/randomiser/define_prop/property_classes/collection_UD_socket_properties.py @@ -0,0 +1,251 @@ +import re + +import bpy + +from ... import utils +from ...material.property_classes.socket_properties import SocketProperties + + +# ----------------------------------------------------------------- +# Setter / getter methods for update_sockets_collection attribute +# ---------------------------------------------------------------- +def compute_UD_sockets_sets(self): + """Compute the relevant sets of sockets for this specific + user defined property, and add them to self. + + These sets include: + - the set of sockets already in this GNG's collection + - the set of sockets present in the Blender graph (for this GNG) + - the set of sockets that are only in one of the two previous sets + + """ + + # set of sockets in collection for this GNG + self.set_sckt_names_in_collection_of_props = set( + sck_p.name for sck_p in self.collection + ) + + # set of sockets in graph for this GNG + list_sckt_names_in_graph = [ + "UD_" + sck.name for sck in self.candidate_sockets + ] + self.set_sckt_names_in_graph = set(list_sckt_names_in_graph) + + # set of sockets that are just in one of the two groups + self.set_of_sckt_names_in_one_only = ( + self.set_sckt_names_in_collection_of_props.symmetric_difference( + self.set_sckt_names_in_graph + ) + ) + + +def get_update_collection(self): + """Getter function for the update_sockets_collection attribute + of the collection of socket properties class (ColSocketProperties) + + It will run when the property value is 'get' and + it will update the collection of socket properties if required + + Returns + ------- + boolean + returns True if the collection of socket properties is updated, + otherwise it returns False + """ + # compute the different sets of sockets and add them to self + compute_UD_sockets_sets(self) + + # if there is a difference between + # sets of sockets in graph and in the collection: + # edit the set of sockets in the collection + if self.set_of_sckt_names_in_one_only: + set_update_collection(self, True) + return True + else: + return False + + +def set_update_collection(self, value): + """ + Setter function for the update_sockets_collection attribute + of the collection of socket properties class (ColSocketProperties) + + It will run when the property value is 'set'. + + It will update the collection of socket properties as follows: + - For the set of sockets that exist only in either + the collection or the graph: + - if the socket exists only in the collection: remove from + collection + - if the socket exists only in the node graph: add to collection + with initial values + - For the rest of sockets: leave untouched + + Parameters + ---------- + value : boolean + if True, the collection of socket properties is + overwritten to consider the latest data + """ + + if value: + # if the update function is triggered directly and not via + # the getter function: compute the sets here + if not hasattr(self, "set_of_sckt_names_in_one_only"): + compute_UD_sockets_sets(self) + + # update the sockets that are only in either + # the collection set or the graph + for sckt_name in self.set_of_sckt_names_in_one_only: + # if the socket exists only in the collection: remove from + # collection + if sckt_name in self.set_sckt_names_in_collection_of_props: + self.collection.remove(self.collection.find(sckt_name)) + + # if the socket exists only in the node graph: add to collection + # with initial values + if sckt_name in self.set_sckt_names_in_graph: + sckt_prop = self.collection.add() + sckt_prop.name = sckt_name + sckt_prop.bool_randomise = True + + # TODO: review - is this code block too hacky? + # --------------------------------------------- + # get socket object for this socket name + # NOTE: my definition of socket name + # (node.name + _ + socket.name) + for s in self.candidate_sockets: + # build socket id from scratch + socket_id = "UD_" + s.name + + if socket_id == sckt_name: + sckt = s + break + + # for this socket type, get the name of the attribute + # holding the min/max properties + socket_attrib_str = bpy.context.scene.socket_type_to_attr[ + type(sckt) + ] + + # extract last number between '_' and 'd/D' in the + # attribute name, to determine the shape of the array + # TODO: there is probably a nicer way to do this... + n_dim = int( + re.findall(r"_(\d+)(?:d|D)", socket_attrib_str)[-1] + ) + # --------------------------- + + # get dictionary with initial min/max values + # for this socket type + ini_min_max_values = ( + bpy.context.scene.socket_type_to_ini_min_max[type(sckt)] + ) + + # assign initial value + for m_str in ["min", "max"]: + setattr( + sckt_prop, + m_str + "_" + socket_attrib_str, + (ini_min_max_values[m_str],) * n_dim, + ) + + +# ---------------------------------------------- +# ColUDSocketProperties +# ---------------------------------------------- +class ColUDSocketProperties(bpy.types.PropertyGroup): + """Class holding the collection of socket properties from + geometry nodes and a boolean property to update the + collection if required (for example, if new nodes are added) + + NOTE: we use the update_sockets_collection property as an + auxiliary property because the CollectionProperty has no update function + https://docs.blender.org/api/current/bpy.props.html#update-example + + """ + + # name of the geometry node group (GNG) + name: bpy.props.StringProperty() # type: ignore + + # collection of socket properties for this GNG + collection: bpy.props.CollectionProperty( # type: ignore + type=SocketProperties + ) + + # helper attribute to update collection of socket properties + update_sockets_collection: bpy.props.BoolProperty( # type: ignore + default=False, + get=get_update_collection, + set=set_update_collection, + ) + + # candidate sockets for this GNG + @property + def candidate_sockets(self): # getter method + """Getter function for the candidate_sockets property + + We define candidate sockets as the set of output sockets + in input nodes, in the graph for the currently active + node group. Input nodes are nodes with only output sockets + (i.e., no input sockets). + + It returns a list of sockets that are candidates for + the randomisation. + + These are the output sockets of input nodes, excluding: + - sockets of type NodeSocketGeometry (input/output nodes) + - sockets that receive one of the available materials + (NodeSocketMaterial) + - sockets that receive one of the available objects + (NodeSocketObject) + - sockets that input a string + (NodeSocketString) + This is becase these sockets cannot be randomised between + min and max values + + Returns + ------- + list + list of sockets in the input nodes in the graph + """ + # get list of input nodes for this geometry node group (GNG) + list_input_nodes = utils.get_geometry_nodes_to_randomise(self.name) + + # get list of sockets that are candidate for randomisation + list_sockets = [ + out + for nd in list_input_nodes + for out in nd.outputs + if type(out) + not in [ + bpy.types.NodeSocketGeometry, + bpy.types.NodeSocketMaterial, + bpy.types.NodeSocketObject, + bpy.types.NodeSocketString, + ] + ] + + return list_sockets + + +# ----------------------------------------- +# Register and unregister functions +# ------------------------------------------ +def register(): + bpy.utils.register_class(ColUDSocketProperties) + + # make the property available via bpy.context.scene... + # (i.e., bpy.context.scene.socket_props_per_gng) + bpy.types.Scene.socket_props_per_gng = bpy.props.PointerProperty( + type=ColUDSocketProperties + ) + + +def unregister(): + bpy.utils.unregister_class(ColUDSocketProperties) + + # remove from bpy.context.scene... + attr_to_remove = "socket_props_per_gng" + if hasattr(bpy.types.Scene, attr_to_remove): + delattr(bpy.types.Scene, attr_to_remove) From 06bc975fef46067a350753799829c37bf08f04d8 Mon Sep 17 00:00:00 2001 From: ruaridhg Date: Tue, 6 Jun 2023 17:07:04 +0100 Subject: [PATCH 06/58] Geom skeleton copied, could use subpanel for UIlist --- randomiser/define_prop/ui.py | 287 +++++++++++++++++++++++++++++++++++ 1 file changed, 287 insertions(+) create mode 100644 randomiser/define_prop/ui.py diff --git a/randomiser/define_prop/ui.py b/randomiser/define_prop/ui.py new file mode 100644 index 0000000..0ba061f --- /dev/null +++ b/randomiser/define_prop/ui.py @@ -0,0 +1,287 @@ +import bpy + +from .. import config +from ..material.ui import TemplatePanel, draw_sockets_list + + +# ---------------------- +# Main panel +# --------------------- +class MainPanelRandomGeometryNodes(TemplatePanel): + """Parent panel to the geometry node groups' subpanels + + Parameters + ---------- + TemplatePanel : bpy.types.Panel + base panel containing parts of the metadata common + to all panels + + Returns + ------- + _type_ + _description_ + """ + + bl_idname = "NODE_GEOMETRY_PT_mainpanel" + bl_label = "Randomise GEOMETRY" + + @classmethod + def poll(cls, context): + """Determine whether the panel can be displayed. + + This panel is only displayed if there is an active object + + Parameters + ---------- + context : _type_ + _description_ + + Returns + ------- + boolean + True if there is an active object, False otherwise + """ + return context.object is not None + + def draw(self, context): + """Define the content to display in the main panel + + Parameters + ---------- + context : _type_ + _description_ + """ + column = self.layout.column(align=True) + column.label(text=("Add properties to randomise")) + + +# ------------------------------ +# Subpanel for each node group +# ----------------------------- +class SubPanelRandomGeometryNodes(TemplatePanel): + """Parent class for the geometry node groups' (GNG) + subpanels + + Parameters + ---------- + TemplatePanel : bpy.types.Panel + base panel containing parts of the metadata common + to all panels + + Returns + ------- + _type_ + _description_ + """ + + bl_idname = "UD_PROPS_PT_subpanel" + bl_parent_id = "UD_PROPS_PT_mainpanel" + bl_label = "" # title of the panel displayed to the user + bl_options = {"DEFAULT_CLOSED"} + # NOTE: other bl_options in the link below + # https://docs.blender.org/api/master/bpy.types.Panel.html#bpy.types.Panel.bl_options + + @classmethod + def poll(cls, context): + """Determine whether the GNG subpanel can be displayed. + + To display a subpanels, its index must be lower than the + total number of GNGs defined in the scene + + Parameters + ---------- + context : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ + cs = context.scene + + # force an update on the group nodes collection first + if cs.socket_props_per_gng.update_gngs_collection: + print("Collection of Geometry Node Groups updated") + + return cls.subpanel_gng_idx < len(cs.socket_props_per_gng.collection) + + def draw_header(self, context): + """Define header for the GNG subpanel + + The header shows the name of the associated geometry node group + (GNG) inside a button. The button is linked to the view-graph + operator. + + Parameters + ---------- + context : _type_ + _description_ + """ + cs = context.scene + + # get this subpanel's GNG + subpanel_gng = cs.socket_props_per_gng.collection[ + self.subpanel_gng_idx + ] + + # add view graph operator to layout + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False + layout.operator( + f"node.view_graph_for_gng_{self.subpanel_gng_idx}", + text=subpanel_gng.name, + emboss=True, + ) + + def draw(self, context): + """Define the content to display in the GNG subpanel + + Parameters + ---------- + context : _type_ + _description_ + """ + cs = context.scene + + # get this subpanel's GNG + subpanel_gng = cs.socket_props_per_gng.collection[ + self.subpanel_gng_idx + ] + + # force an update in the sockets for this GNG + if cs.socket_props_per_gng.collection[ + subpanel_gng.name + ].update_sockets_collection: + print("Collection of Geometry Node Groups updated") + + # get (updated) collection of socket props for this GNG + sockets_props_collection = cs.socket_props_per_gng.collection[ + subpanel_gng.name + ].collection + + # Get list of input nodes to randomise for this subpanel's GNG + list_parent_nodes_str = [ + sckt.name.split("_")[0] for sckt in sockets_props_collection + ] + + list_input_nodes = [ + bpy.data.node_groups[subpanel_gng.name].nodes[nd_str] + for nd_str in list_parent_nodes_str + ] + + # Draw sockets to randomise per input node, including their + # current value and min/max boundaries + draw_sockets_list( + cs, + self.layout, + list_input_nodes, + sockets_props_collection, + ) + + +# ------------------------------------------- +# Subpanel for the 'randomise-all' operator +# ------------------------------------------- +class SubPanelRandomGeometryOperator(TemplatePanel): + """Panel containing the 'randomise-all' button + + Parameters + ---------- + TemplatePanel : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ + + bl_idname = "NODE_GEOMETRY_PT_subpanel_operator" + bl_parent_id = "NODE_GEOMETRY_PT_mainpanel" + bl_label = "" # title of the panel displayed to the user + bl_options = {"HIDE_HEADER"} + + @classmethod + def poll(cls, context): + """Determine whether the panel can be displayed. + + This panel is only displayed if there is an active object + + Parameters + ---------- + context : _type_ + _description_ + + Returns + ------- + boolean + True if there is an active object, False otherwise + """ + return context.object is not None + + def draw(self, context): + """Define the content to display in the + randomise-all operator panel + + Parameters + ---------- + context : _type_ + _description_ + """ + column = self.layout.column(align=True) + column.operator( + "node.randomise_all_geometry_sockets", + text="Randomise", + ) + + +# ----------------------- +# Classes to register +# --------------------- + +# add Main panel to the list of classes to register +list_classes_to_register = [ + MainPanelRandomGeometryNodes, +] + +# Define (dynamically) a subpanel class for each Geometry Node Group (GNGs) +# and add them to the list of classes to register +# NOTE: because we don't know the number of GNGs that will be +# defined a priori, we define n=MAX_NUMBER_OF_SUBPANELS classes and +# assign an index to each of them. We will only display the subpanels +# whose index is lower than the total number of GNGs defined in the scene. +for i in range(config.MAX_NUMBER_OF_SUBPANELS): + # define subpanel class for GNG i + subpanel_class_i = type( + f"NODE_GEOMETRY_PT_subpanel_{i}", + (SubPanelRandomGeometryNodes,), + { + "bl_idname": f"NODE_GEOMETRY_PT_subpanel_{i}", + "subpanel_gng_idx": i, + }, + ) + # append to list of classes to register + list_classes_to_register.append(subpanel_class_i) # type: ignore + + +# add Subpanel with operator to the list +# NOTE: to render it as the last panel +# we add it as the last one to the list +list_classes_to_register.append(SubPanelRandomGeometryOperator) + + +# ----------------------------------------- +# Register and unregister functions +# ------------------------------------------ +def register(): + for cls in list_classes_to_register: + bpy.utils.register_class(cls) + print("geometry UI registered") + + +def unregister(): + for cls in list_classes_to_register: + bpy.utils.unregister_class(cls) + print("geometry UI unregistered") From cdb156f343fff714c6cc4256cf9bf75ef87868da Mon Sep 17 00:00:00 2001 From: ruaridhg Date: Wed, 7 Jun 2023 11:25:20 +0100 Subject: [PATCH 07/58] minor refactoring with combination of UIlist and geom code loading without errors --- randomiser/__init__.py | 6 +- randomiser/define_prop/operators.py | 30 +-- .../collection_UD_socket_properties.py | 23 +- randomiser/define_prop/ui.py | 203 +++++++++++++++--- 4 files changed, 211 insertions(+), 51 deletions(-) diff --git a/randomiser/__init__.py b/randomiser/__init__.py index 1af966d..49c2029 100644 --- a/randomiser/__init__.py +++ b/randomiser/__init__.py @@ -1,4 +1,4 @@ -from . import material, transform, geometry, seed, random_all +from . import material, transform, geometry, seed, random_all, define_prop bl_info = { "name": "Randomisations panel", @@ -17,7 +17,7 @@ def register(): seed.register() - transform.register() + # transform.register() material.register() geometry.register() random_all.register() @@ -25,7 +25,7 @@ def register(): def unregister(): seed.unregister() - transform.unregister() + # transform.unregister() material.unregister() geometry.unregister() random_all.unregister() diff --git a/randomiser/define_prop/operators.py b/randomiser/define_prop/operators.py index c45daf8..1128f73 100644 --- a/randomiser/define_prop/operators.py +++ b/randomiser/define_prop/operators.py @@ -2,7 +2,6 @@ import bpy import numpy as np -from bpy.app.handlers import persistent from .. import config, utils @@ -127,7 +126,8 @@ def execute(self, context): # Operator Randomise selected sockets # across all Geometry node groups # -------------------------------------------- -class RandomiseAllGeometryNodes(bpy.types.Operator): +##### REFACTOR +class RandomiseAllUDProps(bpy.types.Operator): """Randomise the selected output sockets across all geometry node groups @@ -340,15 +340,17 @@ def execute(self, context): # NOTE: without the persistent decorator, # the function is removed from the handlers' list # after it is first executed -@persistent -def randomise_geometry_nodes_per_frame(dummy): - bpy.ops.node.randomise_all_geometry_sockets("INVOKE_DEFAULT") - return +# @persistent +# def randomise_geometry_nodes_per_frame(dummy): +# bpy.ops.node.randomise_all_geometry_sockets("INVOKE_DEFAULT") +# return # ------------------------------- # Operator: view graph per GNG # ------------------------------- +#####REFACTOR for UIlist - needed for displaying subpanels +# in MAX_NUMBER_OF_SUBPANELS for loop class ViewNodeGraphOneGNG(bpy.types.Operator): """Show node graph for the relevant Geometry Node Group @@ -610,12 +612,14 @@ def execute(self, context): # Classes to register # --------------------- list_classes_to_register = [ - RandomiseAllGeometryNodes, + RandomiseAllUDProps, CUSTOM_OT_actions, CUSTOM_OT_printItems, CUSTOM_OT_clearList, ] +#####REFACTOR for UIlist - remove reliance on ViewNodeGraphOneGNG +# or is this needed for displaying subpanel? for i in range(config.MAX_NUMBER_OF_SUBPANELS): operator_i = type( f"ViewNodeGraphOneGNG_subpanel_{i}", @@ -639,9 +643,9 @@ def register(): for cls in list_classes_to_register: bpy.utils.register_class(cls) - bpy.app.handlers.frame_change_pre.append( - randomise_geometry_nodes_per_frame - ) + # bpy.app.handlers.frame_change_pre.append( + # randomise_geometry_nodes_per_frame + # ) print("geometry operators registered") @@ -650,8 +654,8 @@ def unregister(): for cls in list_classes_to_register: bpy.utils.unregister_class(cls) - bpy.app.handlers.frame_change_pre.remove( - randomise_geometry_nodes_per_frame - ) + # bpy.app.handlers.frame_change_pre.remove( + # randomise_geometry_nodes_per_frame + # ) print("geometry operators unregistered") diff --git a/randomiser/define_prop/property_classes/collection_UD_socket_properties.py b/randomiser/define_prop/property_classes/collection_UD_socket_properties.py index c4cb0a5..43e81eb 100644 --- a/randomiser/define_prop/property_classes/collection_UD_socket_properties.py +++ b/randomiser/define_prop/property_classes/collection_UD_socket_properties.py @@ -24,7 +24,8 @@ def compute_UD_sockets_sets(self): self.set_sckt_names_in_collection_of_props = set( sck_p.name for sck_p in self.collection ) - + #####REFACTOR TO WORK WITH UI LIST/REMOVE + # since don't need graphs for custom props? # set of sockets in graph for this GNG list_sckt_names_in_graph = [ "UD_" + sck.name for sck in self.candidate_sockets @@ -88,6 +89,8 @@ def set_update_collection(self, value): overwritten to consider the latest data """ + #####REFACTOR TO WORK WITH UI LIST/REMOVE + # since don't need graphs for custom props? if value: # if the update function is triggered directly and not via # the getter function: compute the sets here @@ -210,7 +213,9 @@ def candidate_sockets(self): # getter method list of sockets in the input nodes in the graph """ # get list of input nodes for this geometry node group (GNG) - list_input_nodes = utils.get_geometry_nodes_to_randomise(self.name) + list_input_nodes = utils.get_geometry_nodes_to_randomise( + self.name + ) #####can delete???? # get list of sockets that are candidate for randomisation list_sockets = [ @@ -224,7 +229,7 @@ def candidate_sockets(self): # getter method bpy.types.NodeSocketObject, bpy.types.NodeSocketString, ] - ] + ] #####refactor to sockets without input nodes return list_sockets @@ -237,15 +242,15 @@ def register(): # make the property available via bpy.context.scene... # (i.e., bpy.context.scene.socket_props_per_gng) - bpy.types.Scene.socket_props_per_gng = bpy.props.PointerProperty( - type=ColUDSocketProperties - ) + # bpy.types.Scene.socket_props_per_gng = bpy.props.PointerProperty( + # type=ColUDSocketProperties + # ) def unregister(): bpy.utils.unregister_class(ColUDSocketProperties) # remove from bpy.context.scene... - attr_to_remove = "socket_props_per_gng" - if hasattr(bpy.types.Scene, attr_to_remove): - delattr(bpy.types.Scene, attr_to_remove) + # attr_to_remove = "socket_props_per_gng" + # if hasattr(bpy.types.Scene, attr_to_remove): + # delattr(bpy.types.Scene, attr_to_remove) diff --git a/randomiser/define_prop/ui.py b/randomiser/define_prop/ui.py index 0ba061f..1fc92e8 100644 --- a/randomiser/define_prop/ui.py +++ b/randomiser/define_prop/ui.py @@ -1,13 +1,135 @@ import bpy from .. import config -from ..material.ui import TemplatePanel, draw_sockets_list +from ..material.ui import TemplatePanel # draw_sockets_list + + +# --------------------------------------------------- +# Common layout for list of sockets to randomise +# ---------------------------------------------------- +##### REFACTOR to removed list input nodes +def draw_sockets_list_UD( + cs, + layout, + list_input_nodes, + sockets_props_collection, +): + # Define UI fields for every socket property + # NOTE: if I don't sort the input nodes, everytime one of the nodes is + # selected in the graph it moves to the bottom of the panel. + list_input_nodes_sorted = sorted(list_input_nodes, key=lambda x: x.name) + for i_n, nd in enumerate(list_input_nodes_sorted): + row = layout.row() + + # if first node: add labels for + # name, min, max and randomisation toggle + if i_n == 0: + row_split = row.split() + col1 = row_split.column(align=True) + col2 = row_split.column(align=True) + col3 = row_split.column(align=True) + col4 = row_split.column(align=True) + col5 = row_split.column(align=True) + + # input node name + col1.label(text=nd.name) + col1.alignment = "CENTER" + + # min label + col3.alignment = "CENTER" + col3.label(text="min") + + # max label + col4.alignment = "CENTER" + col4.label(text="max") + + # if not first node: add just node name + else: + row.separator(factor=1.0) # add empty row before each node + row = layout.row() + + row.label(text=nd.name) + + # add sockets for this node in the subseq rows + for sckt in nd.outputs: + # split row in 5 columns + row = layout.row() + row_split = row.split() + col1 = row_split.column(align=True) + col2 = row_split.column(align=True) + col3 = row_split.column(align=True) + col4 = row_split.column(align=True) + col5 = row_split.column(align=True) + + # socket name + col1.alignment = "RIGHT" + col1.label(text=sckt.name) + + # socket current value + col2.prop( + sckt, + "default_value", + icon_only=True, + ) + col2.enabled = False # current value is not editable + + # socket min and max columns + socket_id = nd.name + "_" + sckt.name + if (nd.id_data.name in bpy.data.node_groups) and ( + bpy.data.node_groups[nd.id_data.name].type != "GEOMETRY" + ): # only for SHADER groups + socket_id = nd.id_data.name + "_" + socket_id + + # if socket is a color: format min/max as a color picker + # and an array (color picker doesn't include alpha value) + if type(sckt) == bpy.types.NodeSocketColor: + for m_str, col in zip(["min", "max"], [col3, col4]): + # color picker + col.template_color_picker( + sockets_props_collection[socket_id], + m_str + "_" + cs.socket_type_to_attr[type(sckt)], + ) + # array + for j, cl in enumerate(["R", "G", "B", "alpha"]): + col.prop( + sockets_props_collection[socket_id], + m_str + "_" + cs.socket_type_to_attr[type(sckt)], + icon_only=False, + text=cl, + index=j, + ) + # if socket is Boolean: add non-editable labels + elif type(sckt) == bpy.types.NodeSocketBool: + for m_str, col in zip(["min", "max"], [col3, col4]): + m_val = getattr( + sockets_props_collection[socket_id], + m_str + "_" + cs.socket_type_to_attr[type(sckt)], + ) + col.label(text=str(list(m_val)[0])) + + # if socket is not color type: format as a regular property + else: + for m_str, col in zip(["min", "max"], [col3, col4]): + col.prop( + sockets_props_collection[socket_id], + m_str + "_" + cs.socket_type_to_attr[type(sckt)], + icon_only=True, + ) + + # randomisation toggle + col5.prop( + sockets_props_collection[socket_id], + "bool_randomise", + icon_only=True, + ) # ---------------------- # Main panel # --------------------- -class MainPanelRandomGeometryNodes(TemplatePanel): +class MainPanelRandomUDNodes( + TemplatePanel +): # MainPanelRandomGeometryNodes(TemplatePanel): """Parent panel to the geometry node groups' subpanels Parameters @@ -22,8 +144,8 @@ class MainPanelRandomGeometryNodes(TemplatePanel): _description_ """ - bl_idname = "NODE_GEOMETRY_PT_mainpanel" - bl_label = "Randomise GEOMETRY" + bl_idname = "UD_PROPS_PT_mainpanel" # "NODE_GEOMETRY_PT_mainpanel" + bl_label = "Randomise UD" # "Randomise GEOMETRY" @classmethod def poll(cls, context): @@ -58,7 +180,9 @@ def draw(self, context): # ------------------------------ # Subpanel for each node group # ----------------------------- -class SubPanelRandomGeometryNodes(TemplatePanel): +class SubPanelRandomUD( + TemplatePanel +): # SubPanelRandomGeometryNodes(TemplatePanel): """Parent class for the geometry node groups' (GNG) subpanels @@ -103,10 +227,21 @@ def poll(cls, context): # force an update on the group nodes collection first if cs.socket_props_per_gng.update_gngs_collection: print("Collection of Geometry Node Groups updated") - - return cls.subpanel_gng_idx < len(cs.socket_props_per_gng.collection) - - def draw_header(self, context): + ######ointer that needs to be defined in collection_UD_sock_props? + # In redundant materials level (only need sockets) + + return cls.subpanel_gng_idx < len( + cs.socket_props_per_gng.collection + ) #####clc.subpanel defined in operators.py + # only display subpanels for which this is true + # return cls.subpanel_custom_idx < len( + # cs.socket_props_per_material.collection + # ) + + def draw_header( + self, context + ): # maybe needed for the name of custom props + # but no need graph to be displayed """Define header for the GNG subpanel The header shows the name of the associated geometry node group @@ -133,7 +268,8 @@ def draw_header(self, context): f"node.view_graph_for_gng_{self.subpanel_gng_idx}", text=subpanel_gng.name, emboss=True, - ) + ) #####operator defined once node.view_graph_for_gng + # - not needed for custom props? def draw(self, context): """Define the content to display in the GNG subpanel @@ -149,7 +285,7 @@ def draw(self, context): subpanel_gng = cs.socket_props_per_gng.collection[ self.subpanel_gng_idx ] - + #####NEED TO COMBINE THESE TWO PARTS INTO JUST SOCKETS # force an update in the sockets for this GNG if cs.socket_props_per_gng.collection[ subpanel_gng.name @@ -165,7 +301,7 @@ def draw(self, context): list_parent_nodes_str = [ sckt.name.split("_")[0] for sckt in sockets_props_collection ] - + #####REMOVE INPUT NODES FROM SOCKETS LIST FOR CUSTOM PROPS list_input_nodes = [ bpy.data.node_groups[subpanel_gng.name].nodes[nd_str] for nd_str in list_parent_nodes_str @@ -173,10 +309,10 @@ def draw(self, context): # Draw sockets to randomise per input node, including their # current value and min/max boundaries - draw_sockets_list( + draw_sockets_list_UD( cs, self.layout, - list_input_nodes, + list_input_nodes, ##### remove once refactored sockets_props_collection, ) @@ -184,7 +320,9 @@ def draw(self, context): # ------------------------------------------- # Subpanel for the 'randomise-all' operator # ------------------------------------------- -class SubPanelRandomGeometryOperator(TemplatePanel): +class SubPanelRandomUDOperator( + TemplatePanel +): # SubPanelRandomGeometryOperator(TemplatePanel): #RANDOMISATION """Panel containing the 'randomise-all' button Parameters @@ -198,8 +336,10 @@ class SubPanelRandomGeometryOperator(TemplatePanel): _description_ """ - bl_idname = "NODE_GEOMETRY_PT_subpanel_operator" - bl_parent_id = "NODE_GEOMETRY_PT_mainpanel" + bl_idname = ( + "UD_PT_subpanel_operator" # "NODE_GEOMETRY_PT_subpanel_operator" + ) + bl_parent_id = "UD_PROPS_PT_mainpanel" # "NODE_GEOMETRY_PT_mainpanel" bl_label = "" # title of the panel displayed to the user bl_options = {"HIDE_HEADER"} @@ -232,7 +372,7 @@ def draw(self, context): """ column = self.layout.column(align=True) column.operator( - "node.randomise_all_geometry_sockets", + "randomise_all_UD_sockets", text="Randomise", ) @@ -243,7 +383,7 @@ def draw(self, context): # add Main panel to the list of classes to register list_classes_to_register = [ - MainPanelRandomGeometryNodes, + MainPanelRandomUDNodes, ] # Define (dynamically) a subpanel class for each Geometry Node Group (GNGs) @@ -254,14 +394,23 @@ def draw(self, context): # whose index is lower than the total number of GNGs defined in the scene. for i in range(config.MAX_NUMBER_OF_SUBPANELS): # define subpanel class for GNG i + # subpanel_class_i = type( + # f"NODE_GEOMETRY_PT_subpanel_{i}", + # (SubPanelRandomGeometryNodes,), + # { + # "bl_idname": f"NODE_GEOMETRY_PT_subpanel_{i}", + # "subpanel_gng_idx": i, + # }, + # ) subpanel_class_i = type( - f"NODE_GEOMETRY_PT_subpanel_{i}", - (SubPanelRandomGeometryNodes,), + f"UD_PT_subpanel_{i}", + (SubPanelRandomUD,), { - "bl_idname": f"NODE_GEOMETRY_PT_subpanel_{i}", - "subpanel_gng_idx": i, + "bl_idname": f"UD_PT_subpanel_{i}", + "subpanel_gng_idx": i, ##### IN UI AND OPERATORS }, ) + # append to list of classes to register list_classes_to_register.append(subpanel_class_i) # type: ignore @@ -269,7 +418,9 @@ def draw(self, context): # add Subpanel with operator to the list # NOTE: to render it as the last panel # we add it as the last one to the list -list_classes_to_register.append(SubPanelRandomGeometryOperator) +list_classes_to_register.append( + SubPanelRandomUDOperator +) # SubPanelRandomGeometryOperator # ----------------------------------------- @@ -278,10 +429,10 @@ def draw(self, context): def register(): for cls in list_classes_to_register: bpy.utils.register_class(cls) - print("geometry UI registered") + print("UD props UI registered") def unregister(): for cls in list_classes_to_register: bpy.utils.unregister_class(cls) - print("geometry UI unregistered") + print("UD props UI unregistered") From 4c7e53f6561f4ed2e00c251115f18cf4452789b4 Mon Sep 17 00:00:00 2001 From: ruaridhg Date: Wed, 7 Jun 2023 14:52:13 +0100 Subject: [PATCH 08/58] Added UIlist as subpanel in UD panel --- randomiser/define_prop/operators.py | 6 +- randomiser/define_prop/properties.py | 84 ++- .../collection_UD_socket_properties.py | 2 +- randomiser/define_prop/ui.py | 388 ++++++++----- randomiser/utils.py | 511 ++++++++++++++++++ 5 files changed, 836 insertions(+), 155 deletions(-) create mode 100644 randomiser/utils.py diff --git a/randomiser/define_prop/operators.py b/randomiser/define_prop/operators.py index 1128f73..d207ca8 100644 --- a/randomiser/define_prop/operators.py +++ b/randomiser/define_prop/operators.py @@ -143,9 +143,7 @@ class RandomiseAllUDProps(bpy.types.Operator): """ # metadata - bl_idname = ( - "node.randomise_all_geometry_sockets" # this is appended to bpy.ops. - ) + bl_idname = "opr.randomise_all_ud_sockets" # this is appended to bpy.ops. bl_label = "Randomise selected sockets" bl_options = {"REGISTER", "UNDO"} @@ -169,7 +167,7 @@ def poll(cls, context): number of geometry node groups in the collection """ - return len(context.scene.socket_props_per_gng.collection) > 0 + #####return len(context.scene.socket_props_per_gng.collection) > 0 def invoke(self, context, event): """Initialise parmeters before executing the operator diff --git a/randomiser/define_prop/properties.py b/randomiser/define_prop/properties.py index 042c2fa..ce8fbd8 100644 --- a/randomiser/define_prop/properties.py +++ b/randomiser/define_prop/properties.py @@ -7,25 +7,58 @@ # --------------------------- # Properties -class PropertiesUserDefined(bpy.types.PropertyGroup): +class PropertiesCustomTransform(bpy.types.PropertyGroup): """ Class holding the set of properties - for the random seed + for the camera position and rotation: + - min/max values for x/y/z component of position and rotation, and + - boolean for delta position and rotation + - boolean for setting seed value + - integer for the actual seed value """ - user_defined_prop = bpy.props.StringProperty() + # Position min and max values + custom_input_prop = bpy.props.StringProperty(name="enter text") + custom_input: custom_input_prop # type: ignore + custom_min: bpy.props.FloatVectorProperty( # type: ignore + size=1, + step=100, # update=constrain_min_closure(custom_input) + ) # type: ignore + custom_max: bpy.props.FloatVectorProperty( # type: ignore + size=1, + step=100, # update=constrain_max_closure(custom_input) + ) # type: ignore + custom_idx: bpy.props.IntProperty(default=0) # type: ignore - user_defined: user_defined_prop # type: ignore + # BOOL + bool_rand_cust: bpy.props.BoolProperty(default=True) # type: ignore -# ------------------------------------ -# Register / unregister classes -# ------------------------------------ +class PropertiesCustomList(bpy.types.PropertyGroup): + custom_string_prop = bpy.props.StringProperty(default="camera.location") + custom_string: custom_string_prop # type: ignore + + +custom_string_prop = bpy.props.StringProperty(default="camera.location") + + +class CUSTOM_colorCollection(bpy.types.PropertyGroup): + # name: StringProperty() -> Instantiated by default + id_prop = bpy.props.IntProperty() + id: id_prop # type: ignore + + +# -------------------------------------------------- +# Register and unregister functions: list_classes_to_register = [ - PropertiesUserDefined, + PropertiesCustomTransform, + PropertiesCustomList, + CUSTOM_colorCollection, ] +list_context_scene_attr = ["socket_type_to_attr"] + def register(): collection_UD_socket_properties.register() @@ -33,13 +66,30 @@ def register(): for cls in list_classes_to_register: bpy.utils.register_class(cls) + if cls == PropertiesCustomTransform: + bpy.types.Scene.custom_props = bpy.props.PointerProperty( + type=PropertiesCustomTransform + ) + + if cls == PropertiesCustomList: + bpy.types.Scene.custom_list = bpy.props.PointerProperty( + type=PropertiesCustomList + ) + + for attr, attr_val in zip( + list_context_scene_attr, + [custom_string_prop], + ): + setattr(bpy.types.Scene, attr, attr_val) + # Custom scene properties - bpy.types.Scene.custom = bpy.props.CollectionProperty( - type=PropertiesUserDefined - ) + if cls == CUSTOM_colorCollection: + bpy.types.Scene.custom = bpy.props.CollectionProperty( + type=CUSTOM_colorCollection + ) bpy.types.Scene.custom_index = bpy.props.IntProperty() - print("geometry properties registered") + print("UD properties registered") def unregister(): @@ -48,9 +98,15 @@ def unregister(): for cls in list_classes_to_register: bpy.utils.unregister_class(cls) - # del bpy.types.Scene.user_defined + del bpy.types.Scene.custom_props + del bpy.types.Scene.custom_list + + # delete the custom properties linked to bpy.context.scene + for attr in list_context_scene_attr: + if hasattr(bpy.types.Scene, attr): + delattr(bpy.types.Scene, attr) del bpy.types.Scene.custom del bpy.types.Scene.custom_index - print("geometry properties unregistered") + print("UD properties unregistered") diff --git a/randomiser/define_prop/property_classes/collection_UD_socket_properties.py b/randomiser/define_prop/property_classes/collection_UD_socket_properties.py index 43e81eb..d8e5c45 100644 --- a/randomiser/define_prop/property_classes/collection_UD_socket_properties.py +++ b/randomiser/define_prop/property_classes/collection_UD_socket_properties.py @@ -215,7 +215,7 @@ def candidate_sockets(self): # getter method # get list of input nodes for this geometry node group (GNG) list_input_nodes = utils.get_geometry_nodes_to_randomise( self.name - ) #####can delete???? + ) #####need to get this list from UI # get list of sockets that are candidate for randomisation list_sockets = [ diff --git a/randomiser/define_prop/ui.py b/randomiser/define_prop/ui.py index 1fc92e8..1296684 100644 --- a/randomiser/define_prop/ui.py +++ b/randomiser/define_prop/ui.py @@ -1,9 +1,34 @@ import bpy -from .. import config from ..material.ui import TemplatePanel # draw_sockets_list +# --------------------------------------------------- +# Custom UIlist items +# ---------------------------------------------------- +class CUSTOM_UL_items(bpy.types.UIList): + print("hello UIlist") + + def draw_item( + self, + context, + layout, + data, + item, + icon, + active_data, + active_propname, + index, + ): + split = layout.split(factor=0.3) + split.label(text="Index: %d" % (index)) + custom_icon = "COLOR" + split.prop(item, "name", icon=custom_icon, emboss=False, text="") + + def invoke(self, context, event): + pass + + # --------------------------------------------------- # Common layout for list of sockets to randomise # ---------------------------------------------------- @@ -127,7 +152,7 @@ def draw_sockets_list_UD( # ---------------------- # Main panel # --------------------- -class MainPanelRandomUDNodes( +class MainPanelRandomUD( TemplatePanel ): # MainPanelRandomGeometryNodes(TemplatePanel): """Parent panel to the geometry node groups' subpanels @@ -177,20 +202,13 @@ def draw(self, context): column.label(text=("Add properties to randomise")) -# ------------------------------ -# Subpanel for each node group -# ----------------------------- -class SubPanelRandomUD( - TemplatePanel -): # SubPanelRandomGeometryNodes(TemplatePanel): - """Parent class for the geometry node groups' (GNG) - subpanels +class SubPanelUDUIlist(TemplatePanel): + """Panel containing the UI list Parameters ---------- - TemplatePanel : bpy.types.Panel - base panel containing parts of the metadata common - to all panels + TemplatePanel : _type_ + _description_ Returns ------- @@ -198,19 +216,16 @@ class SubPanelRandomUD( _description_ """ - bl_idname = "UD_PROPS_PT_subpanel" + bl_idname = "UD_PT_subpanel_UIlist" bl_parent_id = "UD_PROPS_PT_mainpanel" bl_label = "" # title of the panel displayed to the user - bl_options = {"DEFAULT_CLOSED"} - # NOTE: other bl_options in the link below - # https://docs.blender.org/api/master/bpy.types.Panel.html#bpy.types.Panel.bl_options + bl_options = {"HIDE_HEADER"} @classmethod def poll(cls, context): - """Determine whether the GNG subpanel can be displayed. + """Determine whether the panel can be displayed. - To display a subpanels, its index must be lower than the - total number of GNGs defined in the scene + This panel is only displayed if there is an active object Parameters ---------- @@ -219,102 +234,198 @@ def poll(cls, context): Returns ------- - _type_ - _description_ - """ - cs = context.scene - - # force an update on the group nodes collection first - if cs.socket_props_per_gng.update_gngs_collection: - print("Collection of Geometry Node Groups updated") - ######ointer that needs to be defined in collection_UD_sock_props? - # In redundant materials level (only need sockets) - - return cls.subpanel_gng_idx < len( - cs.socket_props_per_gng.collection - ) #####clc.subpanel defined in operators.py - # only display subpanels for which this is true - # return cls.subpanel_custom_idx < len( - # cs.socket_props_per_material.collection - # ) - - def draw_header( - self, context - ): # maybe needed for the name of custom props - # but no need graph to be displayed - """Define header for the GNG subpanel - - The header shows the name of the associated geometry node group - (GNG) inside a button. The button is linked to the view-graph - operator. - - Parameters - ---------- - context : _type_ - _description_ + boolean + True if there is an active object, False otherwise """ - cs = context.scene + return context.object is not None - # get this subpanel's GNG - subpanel_gng = cs.socket_props_per_gng.collection[ - self.subpanel_gng_idx - ] + def draw(self, context): + column = self.layout.column(align=True) + column.label( + text="Choose property to see available properties to randomise" + ) + column.prop(context.scene.custom_props, "custom_input", text="") + # column.operator("opr.add_custom_prop_to_list", + # text="Add to Custom List") - # add view graph operator to layout layout = self.layout - layout.use_property_split = True - layout.use_property_decorate = False - layout.operator( - f"node.view_graph_for_gng_{self.subpanel_gng_idx}", - text=subpanel_gng.name, - emboss=True, - ) #####operator defined once node.view_graph_for_gng - # - not needed for custom props? + scn = bpy.context.scene + + rows = 2 + row = self.layout.row() + row.template_list( + "CUSTOM_UL_items", + "", + scn, + "custom", + scn, + "custom_index", + rows=rows, + ) - def draw(self, context): - """Define the content to display in the GNG subpanel + col = row.column(align=True) + col.operator( + "custom.list_action", icon="ZOOM_IN", text="" + ).action = "ADD" + col.operator( + "custom.list_action", icon="ZOOM_OUT", text="" + ).action = "REMOVE" + col.separator() + col.operator( + "custom.list_action", icon="TRIA_UP", text="" + ).action = "UP" + col.operator( + "custom.list_action", icon="TRIA_DOWN", text="" + ).action = "DOWN" + + row = layout.row() + col = row.column(align=True) + row = col.row(align=True) + row.operator( + "custom.print_items", icon="LINENUMBERS_ON" + ) # LINENUMBERS_OFF, ANIM + row = col.row(align=True) + row.operator("custom.clear_list", icon="X") - Parameters - ---------- - context : _type_ - _description_ - """ - cs = context.scene - - # get this subpanel's GNG - subpanel_gng = cs.socket_props_per_gng.collection[ - self.subpanel_gng_idx - ] - #####NEED TO COMBINE THESE TWO PARTS INTO JUST SOCKETS - # force an update in the sockets for this GNG - if cs.socket_props_per_gng.collection[ - subpanel_gng.name - ].update_sockets_collection: - print("Collection of Geometry Node Groups updated") - - # get (updated) collection of socket props for this GNG - sockets_props_collection = cs.socket_props_per_gng.collection[ - subpanel_gng.name - ].collection - - # Get list of input nodes to randomise for this subpanel's GNG - list_parent_nodes_str = [ - sckt.name.split("_")[0] for sckt in sockets_props_collection - ] - #####REMOVE INPUT NODES FROM SOCKETS LIST FOR CUSTOM PROPS - list_input_nodes = [ - bpy.data.node_groups[subpanel_gng.name].nodes[nd_str] - for nd_str in list_parent_nodes_str - ] - - # Draw sockets to randomise per input node, including their - # current value and min/max boundaries - draw_sockets_list_UD( - cs, - self.layout, - list_input_nodes, ##### remove once refactored - sockets_props_collection, - ) + +# ------------------------------ +# Subpanel for each node group +# ----------------------------- +# class SubPanelRandomUD( +# TemplatePanel +# ): # SubPanelRandomGeometryNodes(TemplatePanel): +# """Parent class for the geometry node groups' (GNG) +# subpanels + +# Parameters +# ---------- +# TemplatePanel : bpy.types.Panel +# base panel containing parts of the metadata common +# to all panels + +# Returns +# ------- +# _type_ +# _description_ +# """ + +# bl_idname = "UD_PROPS_PT_subpanel" +# bl_parent_id = "UD_PROPS_PT_mainpanel" +# bl_label = "" # title of the panel displayed to the user +# bl_options = {"DEFAULT_CLOSED"} +# # NOTE: other bl_options in the link below +# # https://docs.blender.org/api/master/bpy.types.Panel.html#bpy.types.Panel.bl_options + +# @classmethod +# def poll(cls, context): +# """Determine whether the GNG subpanel can be displayed. + +# To display a subpanels, its index must be lower than the +# total number of GNGs defined in the scene + +# Parameters +# ---------- +# context : _type_ +# _description_ + +# Returns +# ------- +# _type_ +# _description_ +# """ +# cs = context.scene + +# # force an update on the group nodes collection first +# if cs.socket_props_per_gng.update_gngs_collection: +# print("Collection of Geometry Node Groups updated") +# ######pointer needs defined in collection_UD_sock_props? +# # In redundant materials level (only need sockets) + +# return cls.subpanel_gng_idx < len( +# cs.socket_props_per_gng.collection +# ) #####clc.subpanel defined in operators.py +# # only display subpanels for which this is true +# # return cls.subpanel_custom_idx < len( +# # cs.socket_props_per_material.collection +# # ) + +# def draw_header( +# self, context +# ): # maybe needed for the name of custom props +# # but no need graph to be displayed +# """Define header for the GNG subpanel + +# The header shows the name of the associated geometry node group +# (GNG) inside a button. The button is linked to the view-graph +# operator. + +# Parameters +# ---------- +# context : _type_ +# _description_ +# """ +# cs = context.scene + +# # get this subpanel's GNG +# subpanel_gng = cs.socket_props_per_gng.collection[ +# self.subpanel_gng_idx +# ] + +# # add view graph operator to layout +# layout = self.layout +# layout.use_property_split = True +# layout.use_property_decorate = False +# layout.operator( +# f"node.view_graph_for_gng_{self.subpanel_gng_idx}", +# text=subpanel_gng.name, +# emboss=True, +# ) #####operator defined once node.view_graph_for_gng +# # - not needed for custom props? + +# def draw(self, context): +# """Define the content to display in the GNG subpanel + +# Parameters +# ---------- +# context : _type_ +# _description_ +# """ +# cs = context.scene + +# # get this subpanel's GNG +# subpanel_gng = cs.socket_props_per_gng.collection[ +# self.subpanel_gng_idx +# ] +# #####NEED TO COMBINE THESE TWO PARTS INTO JUST SOCKETS +# # force an update in the sockets for this GNG +# if cs.socket_props_per_gng.collection[ +# subpanel_gng.name +# ].update_sockets_collection: +# print("Collection of Geometry Node Groups updated") + +# # get (updated) collection of socket props for this GNG +# sockets_props_collection = cs.socket_props_per_gng.collection[ +# subpanel_gng.name +# ].collection + +# # Get list of input nodes to randomise for this subpanel's GNG +# list_parent_nodes_str = [ +# sckt.name.split("_")[0] for sckt in sockets_props_collection +# ] +# #####REMOVE INPUT NODES FROM SOCKETS LIST FOR CUSTOM PROPS +# list_input_nodes = [ +# bpy.data.node_groups[subpanel_gng.name].nodes[nd_str] +# for nd_str in list_parent_nodes_str +# ] + +# # Draw sockets to randomise per input node, including their +# # current value and min/max boundaries +# draw_sockets_list_UD( +# cs, +# self.layout, +# list_input_nodes, ##### remove once refactored +# sockets_props_collection, +# ) # ------------------------------------------- @@ -372,7 +483,7 @@ def draw(self, context): """ column = self.layout.column(align=True) column.operator( - "randomise_all_UD_sockets", + "opr.randomise_all_ud_sockets", text="Randomise", ) @@ -383,7 +494,8 @@ def draw(self, context): # add Main panel to the list of classes to register list_classes_to_register = [ - MainPanelRandomUDNodes, + MainPanelRandomUD, + CUSTOM_UL_items, ] # Define (dynamically) a subpanel class for each Geometry Node Group (GNGs) @@ -392,35 +504,39 @@ def draw(self, context): # defined a priori, we define n=MAX_NUMBER_OF_SUBPANELS classes and # assign an index to each of them. We will only display the subpanels # whose index is lower than the total number of GNGs defined in the scene. -for i in range(config.MAX_NUMBER_OF_SUBPANELS): - # define subpanel class for GNG i - # subpanel_class_i = type( - # f"NODE_GEOMETRY_PT_subpanel_{i}", - # (SubPanelRandomGeometryNodes,), - # { - # "bl_idname": f"NODE_GEOMETRY_PT_subpanel_{i}", - # "subpanel_gng_idx": i, - # }, - # ) - subpanel_class_i = type( - f"UD_PT_subpanel_{i}", - (SubPanelRandomUD,), - { - "bl_idname": f"UD_PT_subpanel_{i}", - "subpanel_gng_idx": i, ##### IN UI AND OPERATORS - }, - ) - - # append to list of classes to register - list_classes_to_register.append(subpanel_class_i) # type: ignore +# for i in range(config.MAX_NUMBER_OF_SUBPANELS): +# # define subpanel class for GNG i +# # subpanel_class_i = type( +# # f"NODE_GEOMETRY_PT_subpanel_{i}", +# # (SubPanelRandomGeometryNodes,), +# # { +# # "bl_idname": f"NODE_GEOMETRY_PT_subpanel_{i}", +# # "subpanel_gng_idx": i, +# # }, +# # ) +# subpanel_class_i = type( +# f"UD_PT_subpanel_{i}", +# (SubPanelRandomUD,), +# { +# "bl_idname": f"UD_PT_subpanel_{i}", +# "subpanel_gng_idx": i, ##### IN UI AND OPERATORS +# }, +# ) + +# # append to list of classes to register +# list_classes_to_register.append(subpanel_class_i) # type: ignore # add Subpanel with operator to the list # NOTE: to render it as the last panel # we add it as the last one to the list -list_classes_to_register.append( - SubPanelRandomUDOperator -) # SubPanelRandomGeometryOperator +list_classes_to_register.append(SubPanelUDUIlist) +list_classes_to_register.append(SubPanelRandomUDOperator) + + +# SubPanelRandomUDOperator, +# SubPanelUDUIlist +# ) # ----------------------------------------- diff --git a/randomiser/utils.py b/randomiser/utils.py new file mode 100644 index 0000000..d58aab4 --- /dev/null +++ b/randomiser/utils.py @@ -0,0 +1,511 @@ +import bpy + +from . import config + + +def get_UD_sockets_to_randomise_from_list( + list_candidate_nodes: list, + node2randomise_prefix: str = config.DEFAULT_RANDOM_KEYWORD, +): + """Get list of nodes to randomise from list. + + Input nodes are defined as nodes with no input sockets. + The nodes to randomise are input nodes whose name starts + with 'node2randomise_prefix' (case insensitive). + + The 'artificial' nodes that show up inside a node group, usually named + 'Group input' or 'Group output' are excluded from the search. + + Parameters + ---------- + list_candidate_nodes : list + list of the candidate nodes to randomise + node2randomise_prefix : str, optional + prefix that identifies the nodes to randomise, by default 'random' + + Returns + ------- + list + list of the input nodes to randomise + """ + + # ensure list_candidate_nodes is unique + list_candidate_nodes = list(set(list_candidate_nodes)) + + # find input nodes that start with the random keyword + # excluding 'Group' artificial nodes + list_input_nodes = [ + nd + for nd in list_candidate_nodes + if len(nd.inputs) == 0 + and nd.name.lower().startswith(node2randomise_prefix.lower()) + and nd.type + not in [ + "GROUP_INPUT", + "GROUP_OUTPUT", + ] + ] + + return list_input_nodes + + +def get_nodes_to_randomise_from_list( + list_candidate_nodes: list, + node2randomise_prefix: str = config.DEFAULT_RANDOM_KEYWORD, +): + """Get list of nodes to randomise from list. + + Input nodes are defined as nodes with no input sockets. + The nodes to randomise are input nodes whose name starts + with 'node2randomise_prefix' (case insensitive). + + The 'artificial' nodes that show up inside a node group, usually named + 'Group input' or 'Group output' are excluded from the search. + + Parameters + ---------- + list_candidate_nodes : list + list of the candidate nodes to randomise + node2randomise_prefix : str, optional + prefix that identifies the nodes to randomise, by default 'random' + + Returns + ------- + list + list of the input nodes to randomise + """ + + # ensure list_candidate_nodes is unique + list_candidate_nodes = list(set(list_candidate_nodes)) + + # find input nodes that start with the random keyword + # excluding 'Group' artificial nodes + list_input_nodes = [ + nd + for nd in list_candidate_nodes + if len(nd.inputs) == 0 + and nd.name.lower().startswith(node2randomise_prefix.lower()) + and nd.type + not in [ + "GROUP_INPUT", + "GROUP_OUTPUT", + ] + ] + + return list_input_nodes + + +def get_material_nodes_to_randomise_indep( + material_str: str = "Material", + node2randomise_prefix: str = config.DEFAULT_RANDOM_KEYWORD, +): + """Get list of *independent* input nodes to randomise for a given material. + + Input nodes are defined as nodes with no input sockets. + The input nodes to randomise are identified because their + name is prefixed with node2randomise_prefix (case insensitive). + + Both independent nodes, and nodes inside a group are searched. + The 'artificial' nodes that show up inside a node group, usually named + 'Group input' or 'Group output' are excluded from the search. + + Parameters + ---------- + material_str : str, optional + name of the material, by default "Material" + node2randomise_prefix : str, optional + prefix that identifies the nodes to randomise, by default 'random' + + Returns + ------- + list + list of all *group* input nodes to randomise for this material + """ + + # list of nodes for current material + # not belonging to a group + list_material_nodes_indep = [] + for nd in bpy.data.materials[material_str].node_tree.nodes: + if nd.type != "GROUP": + list_material_nodes_indep.append(nd) + + # find input nodes that startwith random + list_input_nodes = get_nodes_to_randomise_from_list( + list_material_nodes_indep + ) + + return list_input_nodes + + +def get_material_nodes_to_randomise_group( + material_str: str = "Material", + node2randomise_prefix: str = config.DEFAULT_RANDOM_KEYWORD, +): + """Get list of *group* input nodes to randomise for a given material. + + Input nodes are defined as nodes with no input sockets. + The input nodes to randomise are identified because their + name is prefixed with node2randomise_prefix (case insensitive). + + Both independent nodes, and nodes inside a group are searched. + The 'artificial' nodes that show up inside a node group, usually named + 'Group input' or 'Group output' are excluded from the search. + + Parameters + ---------- + material_str : str, optional + name of the material, by default "Material" + node2randomise_prefix : str, optional + prefix that identifies the nodes to randomise, by default 'random' + + Returns + ------- + list + list of all *group* input nodes to randomise for this material + """ + + # list of nodes for current material + # belonging to a group + list_material_nodes_in_groups = [] + for nd in bpy.data.materials[material_str].node_tree.nodes: + if nd.type == "GROUP": + list_material_nodes_in_groups.extend( + nd.node_tree.nodes + ) # nodes inside groups + + # find input nodes that startwith random + # in any of those groups + # excluding 'Group' nodes + list_input_nodes = get_nodes_to_randomise_from_list( + list_material_nodes_in_groups + ) + + return list_input_nodes + + +def get_material_nodes_to_randomise_all( + material_str: str = "Material", + node2randomise_prefix: str = config.DEFAULT_RANDOM_KEYWORD, +): + """Get list of all input nodes to randomise for a given material. + + Input nodes are defined as nodes with no input sockets. + The input nodes to randomise are identified because their + name is prefixed with node2randomise_prefix (case insensitive). + + Both independent nodes, and nodes inside a group are searched. + The 'artificial' nodes that show up inside a node group, usually named + 'Group input' or 'Group output' are excluded from the search. + + Parameters + ---------- + material_str : str, optional + name of the material, by default "Material" + node2randomise_prefix : str, optional + prefix that identifies the nodes to randomise, by default 'random' + + Returns + ------- + list + list of the input nodes to randomise + """ + + # find input nodes that startwith random + # in any of those groups + # excluding 'Group' nodes + list_indep_input_nodes = get_material_nodes_to_randomise_indep( + material_str, node2randomise_prefix + ) + + list_group_input_nodes = get_material_nodes_to_randomise_group( + material_str, node2randomise_prefix + ) + + return list_indep_input_nodes + list_group_input_nodes + + +def get_geometry_nodes_to_randomise( + node_group_str: str = "Geometry Nodes", + node2randomise_prefix: str = config.DEFAULT_RANDOM_KEYWORD, +): + # find input nodes that start with random + # excluding 'Group Inpuyt/Output' nodes, + # and any nested Group nodes + # NOTE: nested Group nodes are included in bpy.data.node_groups + # of type 'GEOMETRY' + list_input_nodes = get_nodes_to_randomise_from_list( + [ + nd + for nd in bpy.data.node_groups[node_group_str].nodes + if nd.type != "GROUP" # exclude groups inside of groups + ], + node2randomise_prefix, + ) + + return list_input_nodes + + +def get_gngs_linked_to_modifiers(object): + """Get geometry node groups that are linked to + modifiers of the object + + NOTE: Shader node groups cannot be linked to modifiers, + So all node groups will be geometry node groups + + Parameters + ---------- + object : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ + + node_groups_linked_to_modifiers_of_object = [ + mod.node_group + for mod in object.modifiers + if hasattr(mod, "node_group") and hasattr(mod.node_group, "name") + ] + + return node_groups_linked_to_modifiers_of_object + + +def get_parent_of_gng( + node_group, +): + """Get immediate parent of geometry node group. + + Returns the node tree that the input geometry node group is + inside of. + + If the input geometry node group has no parent (i.e., it is a root node) + this function will return None for its parent + + Parameters + ---------- + node_group : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ + + list_all_gngs = [ + gr for gr in bpy.data.node_groups if gr.type == "GEOMETRY" + ] + + parent_gng = None + if node_group is not None: + for gr in list_all_gngs: + for nd in gr.nodes: + if ( + (hasattr(nd, "node_tree")) + and (hasattr(nd.node_tree, "name")) + and (nd.node_tree.name == node_group.name) + ): + parent_gng = gr + break + + return parent_gng # immediate parent + + +def get_root_of_gng(geometry_node_group): + """Get root parent of input geometry node group + + It returns the root parent (i.e., the parent node group whose + parent is None), for the input geometry node group + + Parameters + ---------- + geometry_node_group : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ + parent = get_parent_of_gng(geometry_node_group) + c = 1 # TODO: should this be 0? + while get_parent_of_gng(parent) is not None: + c += 1 + parent = get_parent_of_gng(parent) + + return (parent, c) + + +def get_map_inner_gngs( + list_candidate_root_node_groups, +): + """Compute dictionary that maps inner node groups of the + input root parent node groups, to a tuple made of + - the inner node group's root parent node group, + - the inner node group's depth + + The dictionary is computed for inner node groups whose root parents + are in the input list + + Parameters + ---------- + list_candidate_root_node_groups : _type_ + _description_ + """ + + list_node_groups = [ + gr for gr in bpy.data.node_groups if gr.type == "GEOMETRY" + ] + + map_node_group_to_root_node_group = { + gr: get_root_of_gng(gr) # tuple + for gr in list_node_groups + if get_root_of_gng(gr)[0] in list_candidate_root_node_groups + } + + return map_node_group_to_root_node_group + + +def get_selectable_node_for_node_group(geometry_node_group): + """Get node associated to a (inner) geometry node group + that allows for it to be selected + + An inner geometry node group will have a node associated with it + in the parent node tree. That node can be selected and set as active. + + Parameters + ---------- + geometry_node_group : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ + parent_node_group = get_parent_of_gng(geometry_node_group) + + selectable_node = None + if parent_node_group is not None: + for nd in parent_node_group.nodes: + if ( + nd.type == "GROUP" + and (hasattr(nd, "node_tree")) + and (hasattr(nd.node_tree, "name")) + and (nd.node_tree.name == geometry_node_group.name) + ): + selectable_node = nd + break + + return selectable_node + + +def get_path_to_gng(gng): + """Compute path of parent geometry group nodes up + to the input geometry group node + + Both the root parent node group and the input + geometry group node are included + + Parameters + ---------- + gng : _type_ + inner geometry group need of which we want to + obtain the path + + Returns + ------- + path_to_gng: list + a list of parent geometry group nodes up to the input one + """ + + parent = get_parent_of_gng(gng) + if parent is None: + path_to_gng = [] + else: + path_to_gng = [parent] + while get_parent_of_gng(parent) is not None: + parent = get_parent_of_gng(parent) + path_to_gng.append(parent) + + path_to_gng.reverse() + + path_to_gng.append(gng) + return path_to_gng + + +def get_max_depth(root_parent_node_group): + """Compute the maximum depth of any inner geometry group + for the given root parent node group + + A root parent node group is a node group whose parent is + None + + Parameters + ---------- + root_parent_node_group : _type_ + root parent node group of which we want to compute the + maximum depth + + Returns + ------- + max_depth : int + the depth of the innermost node group + for this root parent node group + """ + map_inner_gngs_of_modifier = get_map_inner_gngs([root_parent_node_group]) + max_depth = 0 + if map_inner_gngs_of_modifier: + max_depth = max([v[1] for k, v in map_inner_gngs_of_modifier.items()]) + + return max_depth + + +def get_modifier_linked_to_gng(gng_name, context_active_object): + """Get the modifier of the currently active object + linked to the input geometry node group (GNG) name + + If there are no modifiers in the currently active object + linked to the input GNG, it will return None. + + Parameters + ---------- + gng_name : str + name of the GNG of interest + context_active_object : _type_ + currently active object + + Returns + ------- + subpanel_modifier + modifier of the currently active object of type + 'Geometry nodes' linked to the input GNG + """ + subpanel_modifier = None + for mod in context_active_object.modifiers: + if ( + hasattr(mod, "node_group") + and (hasattr(mod.node_group, "name")) + and (gng_name == mod.node_group.name) + ): + subpanel_modifier = mod + break + return subpanel_modifier + + +def set_gngs_graph_to_top_level(root_parent_node_group): + """Reset the geometry nodes graph view to + the root parent geometry node group + + Parameters + ---------- + root_parent_node_group : _type_ + + """ + max_depth = get_max_depth(root_parent_node_group) + for i in range(max_depth): + bpy.ops.node.group_edit(exit=True) # with exit=True we go up one level + + return From 08c0549db4bb03d4237e80a13ef00453a3c2cdf2 Mon Sep 17 00:00:00 2001 From: ruaridhg Date: Wed, 7 Jun 2023 15:00:54 +0100 Subject: [PATCH 09/58] UIlist print clear list add and remove works --- randomiser/define_prop/operators.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/randomiser/define_prop/operators.py b/randomiser/define_prop/operators.py index d207ca8..61c83da 100644 --- a/randomiser/define_prop/operators.py +++ b/randomiser/define_prop/operators.py @@ -91,10 +91,10 @@ def execute(self, context): if self.reverse_order: for i in range(scn.custom, -1, -1): item = scn.custom[i] - print("Name:", item.name, "-", "ID:", item.user_defined) + print("Name:", item.name, "-", "ID:", item.id) else: for item in scn.custom: - print("Name:", item.name, "-", "ID", item.user_defined) + print("Name:", item.name, "-", "ID", item.id) return {"FINISHED"} From 94170fd4a8a90d04b21ac5ce773f0178c155ca53 Mon Sep 17 00:00:00 2001 From: ruaridhg Date: Wed, 7 Jun 2023 17:15:17 +0100 Subject: [PATCH 10/58] Introducing collection gngs as UD props equivalent --- randomiser/define_prop/operators.py | 25 +- randomiser/define_prop/properties.py | 65 ++- .../collection_UD_socket_properties.py | 16 +- .../property_classes/collection_gngs.py | 186 ++++++++ randomiser/define_prop/ui.py | 415 +++++++++--------- randomiser/utils.py | 3 +- 6 files changed, 455 insertions(+), 255 deletions(-) create mode 100644 randomiser/define_prop/property_classes/collection_gngs.py diff --git a/randomiser/define_prop/operators.py b/randomiser/define_prop/operators.py index 61c83da..7d4a47c 100644 --- a/randomiser/define_prop/operators.py +++ b/randomiser/define_prop/operators.py @@ -1,3 +1,4 @@ +import pdb import random import bpy @@ -195,9 +196,27 @@ def invoke(self, context, event): # NOTE: this list should have been updated already, # when drawing the panel cs = context.scene - self.list_subpanel_gng_names = [ - gng.name for gng in cs.socket_props_per_gng.collection - ] + pdb.set_trace() + self.list_subpanel_gng_names = [gng.name for gng in cs.custom_props] + # reverse_order_prop = bpy.props.BoolProperty( + # default=False, name="Reverse Order" + # ) + # reverse_order: reverse_order_prop # type: ignore + + # @classmethod + # def poll(cls, context): + # return bool(context.scene.custom) + + # def execute(self, context): + # scn = context.scene + # if self.reverse_order: + # for i in range(scn.custom, -1, -1): + # item = scn.custom[i] + # print("Name:", item.name, "-", "ID:", item.id) + # else: + # for item in scn.custom: + # print("Name:", item.name, "-", "ID", item.id) + # return {"FINISHED"} # for every GNG: save sockets to randomise self.sockets_to_randomise_per_gng = {} diff --git a/randomiser/define_prop/properties.py b/randomiser/define_prop/properties.py index ce8fbd8..68d8581 100644 --- a/randomiser/define_prop/properties.py +++ b/randomiser/define_prop/properties.py @@ -4,35 +4,34 @@ collection_UD_socket_properties, ) - # --------------------------- # Properties -class PropertiesCustomTransform(bpy.types.PropertyGroup): - """ - Class holding the set of properties - for the camera position and rotation: - - min/max values for x/y/z component of position and rotation, and - - boolean for delta position and rotation - - boolean for setting seed value - - integer for the actual seed value - - """ - - # Position min and max values - custom_input_prop = bpy.props.StringProperty(name="enter text") - custom_input: custom_input_prop # type: ignore - custom_min: bpy.props.FloatVectorProperty( # type: ignore - size=1, - step=100, # update=constrain_min_closure(custom_input) - ) # type: ignore - custom_max: bpy.props.FloatVectorProperty( # type: ignore - size=1, - step=100, # update=constrain_max_closure(custom_input) - ) # type: ignore - custom_idx: bpy.props.IntProperty(default=0) # type: ignore - - # BOOL - bool_rand_cust: bpy.props.BoolProperty(default=True) # type: ignore +# class PropertiesCustomTransform(bpy.types.PropertyGroup): +# """ +# Class holding the set of properties +# for the camera position and rotation: +# - min/max values for x/y/z component of position and rotation, and +# - boolean for delta position and rotation +# - boolean for setting seed value +# - integer for the actual seed value + +# """ + +# # Position min and max values +# custom_input_prop = bpy.props.StringProperty(name="enter text") +# custom_input: custom_input_prop # type: ignore +# custom_min: bpy.props.FloatVectorProperty( # type: ignore +# size=1, +# step=100, # update=constrain_min_closure(custom_input) +# ) # type: ignore +# custom_max: bpy.props.FloatVectorProperty( # type: ignore +# size=1, +# step=100, # update=constrain_max_closure(custom_input) +# ) # type: ignore +# custom_idx: bpy.props.IntProperty(default=0) # type: ignore + +# # BOOL +# bool_rand_cust: bpy.props.BoolProperty(default=True) # type: ignore class PropertiesCustomList(bpy.types.PropertyGroup): @@ -52,7 +51,7 @@ class CUSTOM_colorCollection(bpy.types.PropertyGroup): # -------------------------------------------------- # Register and unregister functions: list_classes_to_register = [ - PropertiesCustomTransform, + # PropertiesCustomTransform, PropertiesCustomList, CUSTOM_colorCollection, ] @@ -66,10 +65,10 @@ def register(): for cls in list_classes_to_register: bpy.utils.register_class(cls) - if cls == PropertiesCustomTransform: - bpy.types.Scene.custom_props = bpy.props.PointerProperty( - type=PropertiesCustomTransform - ) + # if cls == PropertiesCustomTransform: + # bpy.types.Scene.custom_props = bpy.props.PointerProperty( + # type=PropertiesCustomTransform + # ) if cls == PropertiesCustomList: bpy.types.Scene.custom_list = bpy.props.PointerProperty( @@ -98,7 +97,7 @@ def unregister(): for cls in list_classes_to_register: bpy.utils.unregister_class(cls) - del bpy.types.Scene.custom_props + # del bpy.types.Scene.custom_props del bpy.types.Scene.custom_list # delete the custom properties linked to bpy.context.scene diff --git a/randomiser/define_prop/property_classes/collection_UD_socket_properties.py b/randomiser/define_prop/property_classes/collection_UD_socket_properties.py index d8e5c45..6da2204 100644 --- a/randomiser/define_prop/property_classes/collection_UD_socket_properties.py +++ b/randomiser/define_prop/property_classes/collection_UD_socket_properties.py @@ -213,7 +213,7 @@ def candidate_sockets(self): # getter method list of sockets in the input nodes in the graph """ # get list of input nodes for this geometry node group (GNG) - list_input_nodes = utils.get_geometry_nodes_to_randomise( + list_input_nodes = utils.get_UD_sockets_to_randomise_from_list( self.name ) #####need to get this list from UI @@ -241,16 +241,16 @@ def register(): bpy.utils.register_class(ColUDSocketProperties) # make the property available via bpy.context.scene... - # (i.e., bpy.context.scene.socket_props_per_gng) - # bpy.types.Scene.socket_props_per_gng = bpy.props.PointerProperty( - # type=ColUDSocketProperties - # ) + # (i.e., bpy.context.scene.socket_props_per_gng) ##### + bpy.types.Scene.socket_props_per_UD = bpy.props.PointerProperty( + type=ColUDSocketProperties + ) def unregister(): bpy.utils.unregister_class(ColUDSocketProperties) # remove from bpy.context.scene... - # attr_to_remove = "socket_props_per_gng" - # if hasattr(bpy.types.Scene, attr_to_remove): - # delattr(bpy.types.Scene, attr_to_remove) + attr_to_remove = "socket_props_per_UD" + if hasattr(bpy.types.Scene, attr_to_remove): + delattr(bpy.types.Scene, attr_to_remove) diff --git a/randomiser/define_prop/property_classes/collection_gngs.py b/randomiser/define_prop/property_classes/collection_gngs.py new file mode 100644 index 0000000..15d67e5 --- /dev/null +++ b/randomiser/define_prop/property_classes/collection_gngs.py @@ -0,0 +1,186 @@ +import bpy + +from ... import config +from .collection_geom_socket_properties import ColGeomSocketProperties + + +# --------------------------------------------------- +# Collection of Geometry Node groups (GNGs) +# --------------------------------------------------- +def compute_node_groups_sets(self): + """Compute the relevant sets of geometry node groups (GNGs) and + add them to self. + + These sets include: + - the set of GNGs already in the collection + - the set of GNGs in the Blender scene / data structure + - the set of GNGs that are only in one of the two previous sets + + """ + # set of GNGs already in collection + self.set_node_groups_in_collection = set(gr.name for gr in self.collection) + + # set of node groups in Blender data structure + self.set_node_groups_in_data = set(gr.name for gr in self.candidate_gngs) + + # set of node groups in one of the sets only + self.set_node_groups_in_one_only = ( + self.set_node_groups_in_collection.symmetric_difference( + self.set_node_groups_in_data + ) + ) + + +def get_update_node_groups_collection(self): + """Getter function for the 'update_gngs_collection' + attribute. + + Checks if the collection of GNGs needs + to be updated, and updates it if required. + + The collection will need to be updated if there + are GNGs that have been added/deleted from the scene. + + Returns + ------- + boolean + returns True if the collection is updated, + otherwise it returns False + """ + # compute relevant GNG sets and add them to self + compute_node_groups_sets(self) + + # if there are node groups that exist only in the Blender + # data structure, or only in the collection: edit the collection + if self.set_node_groups_in_one_only: + set_update_node_groups_collection(self, True) + return True + else: + return False + + +def set_update_node_groups_collection(self, value): + """Setter function for the 'update_gngs_collection' + attribute + + Parameters + ---------- + value : _type_ + _description_ + """ + + # if update value is True + if value: + # if the update fn is triggered directly and not via + # the getter function: compute the required sets here + if not hasattr(self, "set_node_groups_in_one_only"): + compute_node_groups_sets(self) + + # for all node groups that are in one set only + for gr_name in self.set_node_groups_in_one_only: + # if only in collection: remove it from the collection + if gr_name in self.set_node_groups_in_collection: + self.collection.remove(self.collection.find(gr_name)) + + # if only in Blender data structure: add it to the collection + if gr_name in self.set_node_groups_in_data: + gr = self.collection.add() + gr.name = gr_name + + # TODO: do we need to sort collection of node groups? + # (otherwise their order is not guaranteed, this is relevant for + # indexing node groups via subpanel indices) + # it is not clear how to sort collection of properties... + # https://blender.stackexchange.com/questions/157562/sorting-collections-alphabetically-in-the-outliner + + +class ColGeomNodeGroups(bpy.types.PropertyGroup): + """Collection of Geometry Node Groups + + This class has two attributes and one property + - collection (attribute): holds the collection of GNGs + - update_gngs_collection (attribute): helper attribute to force updates on + the collection of GNGs + - candidate_gngs (property): returns the updated list of geometry node + groups defined in the scene + + This data will be made availabe via bpy.context.scene.socket_props_per_gng + + Parameters + ---------- + bpy : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ + + # collection of [collections of socket properties] (one per node group) + collection: bpy.props.CollectionProperty( # type: ignore + type=ColGeomSocketProperties # elements in the collection + ) + + # autopopulate collection of geometry node groups + update_gngs_collection: bpy.props.BoolProperty( # type: ignore + default=False, + get=get_update_node_groups_collection, + set=set_update_node_groups_collection, + ) + + # candidate geometry node groups + @property + def candidate_gngs(self): # getter method + """Return list of geometry node groups + with nodes that start with the random keyword inside them + + Returns + ------- + _type_ + _description_ + """ + # self is the collection of node groups + list_node_groups = [ + nd + for nd in bpy.data.node_groups + if nd.type == "GEOMETRY" + and ( + any( + [ + ni.name.lower().startswith( + config.DEFAULT_RANDOM_KEYWORD + ) + for ni in nd.nodes + ] + ) + ) + ] + # # sort by name + # list_node_groups = sorted( + # list_materials, + # key=lambda mat: mat.name.lower() + # ) + return list_node_groups + + +# ----------------------------------------- +# Register and unregister functions +# ------------------------------------------ +def register(): + bpy.utils.register_class(ColGeomNodeGroups) + + # make the property available via bpy.context.scene... + # (i.e., bpy.context.scene.socket_props_per_gng) + bpy.types.Scene.socket_props_per_gng = bpy.props.PointerProperty( + type=ColGeomNodeGroups + ) + + +def unregister(): + bpy.utils.unregister_class(ColGeomNodeGroups) + + # remove from bpy.context.scene... + attr_to_remove = "socket_props_per_gng" + if hasattr(bpy.types.Scene, attr_to_remove): + delattr(bpy.types.Scene, attr_to_remove) diff --git a/randomiser/define_prop/ui.py b/randomiser/define_prop/ui.py index 1296684..2a84b50 100644 --- a/randomiser/define_prop/ui.py +++ b/randomiser/define_prop/ui.py @@ -43,6 +43,7 @@ def draw_sockets_list_UD( # NOTE: if I don't sort the input nodes, everytime one of the nodes is # selected in the graph it moves to the bottom of the panel. list_input_nodes_sorted = sorted(list_input_nodes, key=lambda x: x.name) + print(list_input_nodes_sorted) for i_n, nd in enumerate(list_input_nodes_sorted): row = layout.row() @@ -51,10 +52,10 @@ def draw_sockets_list_UD( if i_n == 0: row_split = row.split() col1 = row_split.column(align=True) - col2 = row_split.column(align=True) + row_split.column(align=True) col3 = row_split.column(align=True) col4 = row_split.column(align=True) - col5 = row_split.column(align=True) + row_split.column(align=True) # input node name col1.label(text=nd.name) @@ -76,77 +77,77 @@ def draw_sockets_list_UD( row.label(text=nd.name) # add sockets for this node in the subseq rows - for sckt in nd.outputs: - # split row in 5 columns - row = layout.row() - row_split = row.split() - col1 = row_split.column(align=True) - col2 = row_split.column(align=True) - col3 = row_split.column(align=True) - col4 = row_split.column(align=True) - col5 = row_split.column(align=True) - - # socket name - col1.alignment = "RIGHT" - col1.label(text=sckt.name) - - # socket current value - col2.prop( - sckt, - "default_value", - icon_only=True, - ) - col2.enabled = False # current value is not editable - - # socket min and max columns - socket_id = nd.name + "_" + sckt.name - if (nd.id_data.name in bpy.data.node_groups) and ( - bpy.data.node_groups[nd.id_data.name].type != "GEOMETRY" - ): # only for SHADER groups - socket_id = nd.id_data.name + "_" + socket_id - - # if socket is a color: format min/max as a color picker - # and an array (color picker doesn't include alpha value) - if type(sckt) == bpy.types.NodeSocketColor: - for m_str, col in zip(["min", "max"], [col3, col4]): - # color picker - col.template_color_picker( - sockets_props_collection[socket_id], - m_str + "_" + cs.socket_type_to_attr[type(sckt)], - ) - # array - for j, cl in enumerate(["R", "G", "B", "alpha"]): - col.prop( - sockets_props_collection[socket_id], - m_str + "_" + cs.socket_type_to_attr[type(sckt)], - icon_only=False, - text=cl, - index=j, - ) - # if socket is Boolean: add non-editable labels - elif type(sckt) == bpy.types.NodeSocketBool: - for m_str, col in zip(["min", "max"], [col3, col4]): - m_val = getattr( - sockets_props_collection[socket_id], - m_str + "_" + cs.socket_type_to_attr[type(sckt)], - ) - col.label(text=str(list(m_val)[0])) - - # if socket is not color type: format as a regular property - else: - for m_str, col in zip(["min", "max"], [col3, col4]): - col.prop( - sockets_props_collection[socket_id], - m_str + "_" + cs.socket_type_to_attr[type(sckt)], - icon_only=True, - ) - - # randomisation toggle - col5.prop( - sockets_props_collection[socket_id], - "bool_randomise", - icon_only=True, - ) + # for sckt in nd.outputs: + # # split row in 5 columns + # row = layout.row() + # row_split = row.split() + # col1 = row_split.column(align=True) + # col2 = row_split.column(align=True) + # col3 = row_split.column(align=True) + # col4 = row_split.column(align=True) + # col5 = row_split.column(align=True) + + # # socket name + # col1.alignment = "RIGHT" + # col1.label(text=sckt.name) + + # # socket current value + # col2.prop( + # sckt, + # "default_value", + # icon_only=True, + # ) + # col2.enabled = False # current value is not editable + + # # socket min and max columns + # socket_id = nd.name + "_" + sckt.name + # if (nd.id_data.name in bpy.data.node_groups) and ( + # bpy.data.node_groups[nd.id_data.name].type != "GEOMETRY" + # ): # only for SHADER groups + # socket_id = nd.id_data.name + "_" + socket_id + + # # if socket is a color: format min/max as a color picker + # # and an array (color picker doesn't include alpha value) + # if type(sckt) == bpy.types.NodeSocketColor: + # for m_str, col in zip(["min", "max"], [col3, col4]): + # # color picker + # col.template_color_picker( + # sockets_props_collection[socket_id], + # m_str + "_" + cs.socket_type_to_attr[type(sckt)], + # ) + # # array + # for j, cl in enumerate(["R", "G", "B", "alpha"]): + # col.prop( + # sockets_props_collection[socket_id], + # m_str + "_" + cs.socket_type_to_attr[type(sckt)], + # icon_only=False, + # text=cl, + # index=j, + # ) + # # if socket is Boolean: add non-editable labels + # elif type(sckt) == bpy.types.NodeSocketBool: + # for m_str, col in zip(["min", "max"], [col3, col4]): + # m_val = getattr( + # sockets_props_collection[socket_id], + # m_str + "_" + cs.socket_type_to_attr[type(sckt)], + # ) + # col.label(text=str(list(m_val)[0])) + + # # if socket is not color type: format as a regular property + # else: + # for m_str, col in zip(["min", "max"], [col3, col4]): + # col.prop( + # sockets_props_collection[socket_id], + # m_str + "_" + cs.socket_type_to_attr[type(sckt)], + # icon_only=True, + # ) + + # # randomisation toggle + # col5.prop( + # sockets_props_collection[socket_id], + # "bool_randomise", + # icon_only=True, + # ) # ---------------------- @@ -244,7 +245,7 @@ def draw(self, context): column.label( text="Choose property to see available properties to randomise" ) - column.prop(context.scene.custom_props, "custom_input", text="") + # column.prop(context.scene.custom_props, "custom_input", text="") # column.operator("opr.add_custom_prop_to_list", # text="Add to Custom List") @@ -291,141 +292,137 @@ def draw(self, context): # ------------------------------ # Subpanel for each node group # ----------------------------- -# class SubPanelRandomUD( -# TemplatePanel -# ): # SubPanelRandomGeometryNodes(TemplatePanel): -# """Parent class for the geometry node groups' (GNG) -# subpanels - -# Parameters -# ---------- -# TemplatePanel : bpy.types.Panel -# base panel containing parts of the metadata common -# to all panels - -# Returns -# ------- -# _type_ -# _description_ -# """ - -# bl_idname = "UD_PROPS_PT_subpanel" -# bl_parent_id = "UD_PROPS_PT_mainpanel" -# bl_label = "" # title of the panel displayed to the user -# bl_options = {"DEFAULT_CLOSED"} -# # NOTE: other bl_options in the link below -# # https://docs.blender.org/api/master/bpy.types.Panel.html#bpy.types.Panel.bl_options - -# @classmethod -# def poll(cls, context): -# """Determine whether the GNG subpanel can be displayed. - -# To display a subpanels, its index must be lower than the -# total number of GNGs defined in the scene - -# Parameters -# ---------- -# context : _type_ -# _description_ - -# Returns -# ------- -# _type_ -# _description_ -# """ -# cs = context.scene - -# # force an update on the group nodes collection first -# if cs.socket_props_per_gng.update_gngs_collection: -# print("Collection of Geometry Node Groups updated") -# ######pointer needs defined in collection_UD_sock_props? -# # In redundant materials level (only need sockets) - -# return cls.subpanel_gng_idx < len( -# cs.socket_props_per_gng.collection -# ) #####clc.subpanel defined in operators.py -# # only display subpanels for which this is true -# # return cls.subpanel_custom_idx < len( -# # cs.socket_props_per_material.collection -# # ) - -# def draw_header( -# self, context -# ): # maybe needed for the name of custom props -# # but no need graph to be displayed -# """Define header for the GNG subpanel - -# The header shows the name of the associated geometry node group -# (GNG) inside a button. The button is linked to the view-graph -# operator. - -# Parameters -# ---------- -# context : _type_ -# _description_ -# """ -# cs = context.scene - -# # get this subpanel's GNG -# subpanel_gng = cs.socket_props_per_gng.collection[ -# self.subpanel_gng_idx -# ] - -# # add view graph operator to layout -# layout = self.layout -# layout.use_property_split = True -# layout.use_property_decorate = False -# layout.operator( -# f"node.view_graph_for_gng_{self.subpanel_gng_idx}", -# text=subpanel_gng.name, -# emboss=True, -# ) #####operator defined once node.view_graph_for_gng -# # - not needed for custom props? - -# def draw(self, context): -# """Define the content to display in the GNG subpanel - -# Parameters -# ---------- -# context : _type_ -# _description_ -# """ -# cs = context.scene - -# # get this subpanel's GNG -# subpanel_gng = cs.socket_props_per_gng.collection[ -# self.subpanel_gng_idx -# ] -# #####NEED TO COMBINE THESE TWO PARTS INTO JUST SOCKETS -# # force an update in the sockets for this GNG -# if cs.socket_props_per_gng.collection[ -# subpanel_gng.name -# ].update_sockets_collection: -# print("Collection of Geometry Node Groups updated") - -# # get (updated) collection of socket props for this GNG -# sockets_props_collection = cs.socket_props_per_gng.collection[ -# subpanel_gng.name -# ].collection - -# # Get list of input nodes to randomise for this subpanel's GNG -# list_parent_nodes_str = [ -# sckt.name.split("_")[0] for sckt in sockets_props_collection -# ] -# #####REMOVE INPUT NODES FROM SOCKETS LIST FOR CUSTOM PROPS -# list_input_nodes = [ -# bpy.data.node_groups[subpanel_gng.name].nodes[nd_str] -# for nd_str in list_parent_nodes_str -# ] - -# # Draw sockets to randomise per input node, including their -# # current value and min/max boundaries -# draw_sockets_list_UD( -# cs, -# self.layout, -# list_input_nodes, ##### remove once refactored -# sockets_props_collection, -# ) +class SubPanelRandomUD( + TemplatePanel +): # SubPanelRandomGeometryNodes(TemplatePanel): + """Parent class for the geometry node groups' (GNG) + subpanels + + Parameters + ---------- + TemplatePanel : bpy.types.Panel + base panel containing parts of the metadata common + to all panels + + Returns + ------- + _type_ + _description_ + """ + + bl_idname = "UD_PROPS_PT_subpanel" + bl_parent_id = "UD_PROPS_PT_mainpanel" + bl_label = "" # title of the panel displayed to the user + bl_options = {"DEFAULT_CLOSED"} + # NOTE: other bl_options in the link below + # https://docs.blender.org/api/master/bpy.types.Panel.html#bpy.types.Panel.bl_options + + @classmethod + def poll(cls, context): + """Determine whether the GNG subpanel can be displayed. + + To display a subpanels, its index must be lower than the + total number of GNGs defined in the scene + + Parameters + ---------- + context : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ + cs = context.scene + + # force an update on the group nodes collection first + if cs.socket_props_per_UD.update_gngs_collection: + print("Collection of Geometry Node Groups updated") + ######pointer needs defined in collection_UD_sock_props? + # In redundant materials level (only need sockets) + + return cls.subpanel_gng_idx < len( + cs.socket_props_per_UD.collection + ) #####clc.subpanel defined in operators.py + # only display subpanels for which this is true + # return cls.subpanel_custom_idx < len( + # cs.socket_props_per_material.collection + # ) + + def draw_header( + self, context + ): # maybe needed for the name of custom props + # but no need graph to be displayed + """Define header for the GNG subpanel + + The header shows the name of the associated geometry node group + (GNG) inside a button. The button is linked to the view-graph + operator. + + Parameters + ---------- + context : _type_ + _description_ + """ + cs = context.scene + + # get this subpanel's GNG + subpanel_gng = cs.socket_props_per_UD.collection[self.subpanel_gng_idx] + + # add view graph operator to layout + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False + layout.operator( + f"node.view_graph_for_gng_{self.subpanel_gng_idx}", + text=subpanel_gng.name, + emboss=True, + ) #####operator defined once node.view_graph_for_gng + # - not needed for custom props? + + def draw(self, context): + """Define the content to display in the GNG subpanel + + Parameters + ---------- + context : _type_ + _description_ + """ + cs = context.scene + + # get this subpanel's GNG + subpanel_gng = cs.socket_props_per_UD.collection[self.subpanel_gng_idx] + #####NEED TO COMBINE THESE TWO PARTS INTO JUST SOCKETS + # force an update in the sockets for this GNG + if cs.socket_props_per_UD.collection[ + subpanel_gng.name + ].update_sockets_collection: + print("Collection of Geometry Node Groups updated") + + # get (updated) collection of socket props for this GNG + sockets_props_collection = cs.socket_props_per_UD.collection[ + subpanel_gng.name + ].collection + + # Get list of input nodes to randomise for this subpanel's GNG + list_parent_nodes_str = [ + sckt.name.split("_")[0] for sckt in sockets_props_collection + ] + #####REMOVE INPUT NODES FROM SOCKETS LIST FOR CUSTOM PROPS + list_input_nodes = [ + bpy.data.node_groups[subpanel_gng.name].nodes[nd_str] + for nd_str in list_parent_nodes_str + ] + + # Draw sockets to randomise per input node, including their + # current value and min/max boundaries + draw_sockets_list_UD( + cs, + self.layout, + list_input_nodes, ##### remove once refactored + sockets_props_collection, + ) # ------------------------------------------- diff --git a/randomiser/utils.py b/randomiser/utils.py index d58aab4..c817eab 100644 --- a/randomiser/utils.py +++ b/randomiser/utils.py @@ -5,7 +5,6 @@ def get_UD_sockets_to_randomise_from_list( list_candidate_nodes: list, - node2randomise_prefix: str = config.DEFAULT_RANDOM_KEYWORD, ): """Get list of nodes to randomise from list. @@ -38,7 +37,7 @@ def get_UD_sockets_to_randomise_from_list( nd for nd in list_candidate_nodes if len(nd.inputs) == 0 - and nd.name.lower().startswith(node2randomise_prefix.lower()) + # and nd.name.lower().startswith(node2randomise_prefix.lower()) and nd.type not in [ "GROUP_INPUT", From 06d5ab6d0281f6d98543b2ade3e67d4943a7eb7e Mon Sep 17 00:00:00 2001 From: ruaridhg Date: Thu, 8 Jun 2023 07:14:32 +0100 Subject: [PATCH 11/58] Removed collection gngs - created branch off this one to include alternative --- .../property_classes/collection_gngs.py | 186 ------------------ 1 file changed, 186 deletions(-) delete mode 100644 randomiser/define_prop/property_classes/collection_gngs.py diff --git a/randomiser/define_prop/property_classes/collection_gngs.py b/randomiser/define_prop/property_classes/collection_gngs.py deleted file mode 100644 index 15d67e5..0000000 --- a/randomiser/define_prop/property_classes/collection_gngs.py +++ /dev/null @@ -1,186 +0,0 @@ -import bpy - -from ... import config -from .collection_geom_socket_properties import ColGeomSocketProperties - - -# --------------------------------------------------- -# Collection of Geometry Node groups (GNGs) -# --------------------------------------------------- -def compute_node_groups_sets(self): - """Compute the relevant sets of geometry node groups (GNGs) and - add them to self. - - These sets include: - - the set of GNGs already in the collection - - the set of GNGs in the Blender scene / data structure - - the set of GNGs that are only in one of the two previous sets - - """ - # set of GNGs already in collection - self.set_node_groups_in_collection = set(gr.name for gr in self.collection) - - # set of node groups in Blender data structure - self.set_node_groups_in_data = set(gr.name for gr in self.candidate_gngs) - - # set of node groups in one of the sets only - self.set_node_groups_in_one_only = ( - self.set_node_groups_in_collection.symmetric_difference( - self.set_node_groups_in_data - ) - ) - - -def get_update_node_groups_collection(self): - """Getter function for the 'update_gngs_collection' - attribute. - - Checks if the collection of GNGs needs - to be updated, and updates it if required. - - The collection will need to be updated if there - are GNGs that have been added/deleted from the scene. - - Returns - ------- - boolean - returns True if the collection is updated, - otherwise it returns False - """ - # compute relevant GNG sets and add them to self - compute_node_groups_sets(self) - - # if there are node groups that exist only in the Blender - # data structure, or only in the collection: edit the collection - if self.set_node_groups_in_one_only: - set_update_node_groups_collection(self, True) - return True - else: - return False - - -def set_update_node_groups_collection(self, value): - """Setter function for the 'update_gngs_collection' - attribute - - Parameters - ---------- - value : _type_ - _description_ - """ - - # if update value is True - if value: - # if the update fn is triggered directly and not via - # the getter function: compute the required sets here - if not hasattr(self, "set_node_groups_in_one_only"): - compute_node_groups_sets(self) - - # for all node groups that are in one set only - for gr_name in self.set_node_groups_in_one_only: - # if only in collection: remove it from the collection - if gr_name in self.set_node_groups_in_collection: - self.collection.remove(self.collection.find(gr_name)) - - # if only in Blender data structure: add it to the collection - if gr_name in self.set_node_groups_in_data: - gr = self.collection.add() - gr.name = gr_name - - # TODO: do we need to sort collection of node groups? - # (otherwise their order is not guaranteed, this is relevant for - # indexing node groups via subpanel indices) - # it is not clear how to sort collection of properties... - # https://blender.stackexchange.com/questions/157562/sorting-collections-alphabetically-in-the-outliner - - -class ColGeomNodeGroups(bpy.types.PropertyGroup): - """Collection of Geometry Node Groups - - This class has two attributes and one property - - collection (attribute): holds the collection of GNGs - - update_gngs_collection (attribute): helper attribute to force updates on - the collection of GNGs - - candidate_gngs (property): returns the updated list of geometry node - groups defined in the scene - - This data will be made availabe via bpy.context.scene.socket_props_per_gng - - Parameters - ---------- - bpy : _type_ - _description_ - - Returns - ------- - _type_ - _description_ - """ - - # collection of [collections of socket properties] (one per node group) - collection: bpy.props.CollectionProperty( # type: ignore - type=ColGeomSocketProperties # elements in the collection - ) - - # autopopulate collection of geometry node groups - update_gngs_collection: bpy.props.BoolProperty( # type: ignore - default=False, - get=get_update_node_groups_collection, - set=set_update_node_groups_collection, - ) - - # candidate geometry node groups - @property - def candidate_gngs(self): # getter method - """Return list of geometry node groups - with nodes that start with the random keyword inside them - - Returns - ------- - _type_ - _description_ - """ - # self is the collection of node groups - list_node_groups = [ - nd - for nd in bpy.data.node_groups - if nd.type == "GEOMETRY" - and ( - any( - [ - ni.name.lower().startswith( - config.DEFAULT_RANDOM_KEYWORD - ) - for ni in nd.nodes - ] - ) - ) - ] - # # sort by name - # list_node_groups = sorted( - # list_materials, - # key=lambda mat: mat.name.lower() - # ) - return list_node_groups - - -# ----------------------------------------- -# Register and unregister functions -# ------------------------------------------ -def register(): - bpy.utils.register_class(ColGeomNodeGroups) - - # make the property available via bpy.context.scene... - # (i.e., bpy.context.scene.socket_props_per_gng) - bpy.types.Scene.socket_props_per_gng = bpy.props.PointerProperty( - type=ColGeomNodeGroups - ) - - -def unregister(): - bpy.utils.unregister_class(ColGeomNodeGroups) - - # remove from bpy.context.scene... - attr_to_remove = "socket_props_per_gng" - if hasattr(bpy.types.Scene, attr_to_remove): - delattr(bpy.types.Scene, attr_to_remove) From e8134cc12b701f98d8e62e434d7387f1e82d1461 Mon Sep 17 00:00:00 2001 From: ruaridhg Date: Thu, 8 Jun 2023 07:12:45 +0100 Subject: [PATCH 12/58] Added collection_gngs.py --- .../property_classes/collection_gngs.py | 186 ++++++++++++++++++ 1 file changed, 186 insertions(+) create mode 100644 randomiser/define_prop/property_classes/collection_gngs.py diff --git a/randomiser/define_prop/property_classes/collection_gngs.py b/randomiser/define_prop/property_classes/collection_gngs.py new file mode 100644 index 0000000..95a606e --- /dev/null +++ b/randomiser/define_prop/property_classes/collection_gngs.py @@ -0,0 +1,186 @@ +import bpy + +from ... import config +from .collection_geom_socket_properties import ColGeomSocketProperties + + +# --------------------------------------------------- +# Collection of Geometry Node groups (GNGs) +# --------------------------------------------------- +def compute_node_groups_sets(self): + """Compute the relevant sets of geometry node groups (GNGs) and + add them to self. + + These sets include: + - the set of GNGs already in the collection + - the set of GNGs in the Blender scene / data structure + - the set of GNGs that are only in one of the two previous sets + + """ + # set of GNGs already in collection + self.set_node_groups_in_collection = set(gr.name for gr in self.collection) + + # set of node groups in Blender data structure + self.set_node_groups_in_data = set(gr.name for gr in self.candidate_gngs) + + # set of node groups in one of the sets only + self.set_node_groups_in_one_only = ( + self.set_node_groups_in_collection.symmetric_difference( + self.set_node_groups_in_data + ) + ) + + +def get_update_node_groups_collection(self): + """Getter function for the 'update_gngs_collection' + attribute. + + Checks if the collection of GNGs needs + to be updated, and updates it if required. + + The collection will need to be updated if there + are GNGs that have been added/deleted from the scene. + + Returns + ------- + boolean + returns True if the collection is updated, + otherwise it returns False + """ + # compute relevant GNG sets and add them to self + compute_node_groups_sets(self) + + # if there are node groups that exist only in the Blender + # data structure, or only in the collection: edit the collection + if self.set_node_groups_in_one_only: + set_update_node_groups_collection(self, True) + return True + else: + return False + + +def set_update_node_groups_collection(self, value): + """Setter function for the 'update_gngs_collection' + attribute + + Parameters + ---------- + value : _type_ + _description_ + """ + + # if update value is True + if value: + # if the update fn is triggered directly and not via + # the getter function: compute the required sets here + if not hasattr(self, "set_node_groups_in_one_only"): + compute_node_groups_sets(self) + + # for all node groups that are in one set only + for gr_name in self.set_node_groups_in_one_only: + # if only in collection: remove it from the collection + if gr_name in self.set_node_groups_in_collection: + self.collection.remove(self.collection.find(gr_name)) + + # if only in Blender data structure: add it to the collection + if gr_name in self.set_node_groups_in_data: + gr = self.collection.add() + gr.name = gr_name + + # TODO: do we need to sort collection of node groups? + # (otherwise their order is not guaranteed, this is relevant for + # indexing node groups via subpanel indices) + # it is not clear how to sort collection of properties... + # https://blender.stackexchange.com/questions/157562/sorting-collections-alphabetically-in-the-outliner + + +class ColGeomNodeGroups(bpy.types.PropertyGroup): + """Collection of Geometry Node Groups + + This class has two attributes and one property + - collection (attribute): holds the collection of GNGs + - update_gngs_collection (attribute): helper attribute to force updates on + the collection of GNGs + - candidate_gngs (property): returns the updated list of geometry node + groups defined in the scene + + This data will be made availabe via bpy.context.scene.socket_props_per_gng + + Parameters + ---------- + bpy : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ + + # collection of [collections of socket properties] (one per node group) + collection: bpy.props.CollectionProperty( # type: ignore + type=ColGeomSocketProperties # elements in the collection + ) + + # autopopulate collection of geometry node groups + update_gngs_collection: bpy.props.BoolProperty( # type: ignore + default=False, + get=get_update_node_groups_collection, + set=set_update_node_groups_collection, + ) + + # candidate geometry node groups + @property + def candidate_gngs(self): # getter method + """Return list of geometry node groups + with nodes that start with the random keyword inside them + + Returns + ------- + _type_ + _description_ + """ + # self is the collection of node groups + list_node_groups = [ + nd + for nd in bpy.data.node_groups + if nd.type == "GEOMETRY" + and ( + any( + [ + ni.name.lower().startswith( + config.DEFAULT_RANDOM_KEYWORD + ) + for ni in nd.nodes + ] + ) + ) + ] + # # sort by name + # list_node_groups = sorted( + # list_materials, + # key=lambda mat: mat.name.lower() + # ) + return list_node_groups + + +# ----------------------------------------- +# Register and unregister functions +# ------------------------------------------ +def register(): + bpy.utils.register_class(ColGeomNodeGroups) + + # make the property available via bpy.context.scene... + # (i.e., bpy.context.scene.socket_props_per_gng) + bpy.types.Scene.socket_props_per_gng = bpy.props.PointerProperty( + type=ColGeomNodeGroups + ) + + +def unregister(): + bpy.utils.unregister_class(ColGeomNodeGroups) + + # remove from bpy.context.scene... + attr_to_remove = "socket_props_per_UD_property" + if hasattr(bpy.types.Scene, attr_to_remove): + delattr(bpy.types.Scene, attr_to_remove) From 51027bc18107a85fd2da455a50dced3f7a8d377a Mon Sep 17 00:00:00 2001 From: ruaridhg Date: Thu, 8 Jun 2023 09:02:38 +0100 Subject: [PATCH 13/58] UIlist working and UD-sockets code in progress --- randomiser/define_prop/operators.py | 147 ++++----- ...lection_gngs.py => collection_UD_props.py} | 84 +++--- randomiser/define_prop/ui.py | 280 +++++++++--------- 3 files changed, 240 insertions(+), 271 deletions(-) rename randomiser/define_prop/property_classes/{collection_gngs.py => collection_UD_props.py} (65%) diff --git a/randomiser/define_prop/operators.py b/randomiser/define_prop/operators.py index 7d4a47c..9d24a06 100644 --- a/randomiser/define_prop/operators.py +++ b/randomiser/define_prop/operators.py @@ -1,10 +1,9 @@ -import pdb import random import bpy import numpy as np -from .. import config, utils +from .. import utils class CUSTOM_OT_actions(bpy.types.Operator): @@ -168,7 +167,7 @@ def poll(cls, context): number of geometry node groups in the collection """ - #####return len(context.scene.socket_props_per_gng.collection) > 0 + return len(context.scene.socket_props_per_UD.collection) > 0 def invoke(self, context, event): """Initialise parmeters before executing the operator @@ -196,74 +195,52 @@ def invoke(self, context, event): # NOTE: this list should have been updated already, # when drawing the panel cs = context.scene - pdb.set_trace() - self.list_subpanel_gng_names = [gng.name for gng in cs.custom_props] - # reverse_order_prop = bpy.props.BoolProperty( - # default=False, name="Reverse Order" - # ) - # reverse_order: reverse_order_prop # type: ignore - - # @classmethod - # def poll(cls, context): - # return bool(context.scene.custom) - - # def execute(self, context): - # scn = context.scene - # if self.reverse_order: - # for i in range(scn.custom, -1, -1): - # item = scn.custom[i] - # print("Name:", item.name, "-", "ID:", item.id) - # else: - # for item in scn.custom: - # print("Name:", item.name, "-", "ID", item.id) - # return {"FINISHED"} + self.list_subpanel_UD_props_names = [ + UD.name for UD in cs.socket_props_per_UD.collection + ] # for every GNG: save sockets to randomise - self.sockets_to_randomise_per_gng = {} - for gng_str in self.list_subpanel_gng_names: + self.sockets_to_randomise_per_UD = {} + for UD_str in self.list_subpanel_UD_props_names: # get collection of socket properties for this GNG # ATT socket properties do not include the actual socket object - if cs.socket_props_per_gng.collection[ - gng_str + if cs.socket_props_per_UD.collection[ + UD_str ].update_sockets_collection: - print("Collection of geometry sockets updated") + print("Collection of UD props sockets updated") - sockets_props_collection = cs.socket_props_per_gng.collection[ - gng_str - ].collection + cs.socket_props_per_UD.collection[UD_str].collection # get candidate sockets for this GNG - candidate_sockets = cs.socket_props_per_gng.collection[ - gng_str - ].candidate_sockets - - # if socket unlinked and randomisation toggle is True: - # modify socket props to set toggle to False - self.sockets_to_randomise_per_gng[gng_str] = [] - for sckt in candidate_sockets: - # get socket identifier string - sckt_id = sckt.node.name + "_" + sckt.name - - # if this socket is selected to randomise but it is unlinked: - # set randomisation toggle to False - if (not sckt.is_linked) and ( - sockets_props_collection[sckt_id].bool_randomise - ): - setattr( - sockets_props_collection[sckt_id], - "bool_randomise", - False, - ) - print( - f"Socket {sckt_id} from {gng_str} is unlinked:", - "randomisation toggle set to False", - ) - - # after modifying randomisation toggle - # save list of sockets to randomise to dict, - # with key = material - if sockets_props_collection[sckt_id].bool_randomise: - self.sockets_to_randomise_per_gng[gng_str].append(sckt) + cs.socket_props_per_UD.collection[UD_str].candidate_sockets + + # # if socket unlinked and randomisation toggle is True: + # # modify socket props to set toggle to False + # self.sockets_to_randomise_per_UD[UD_str] = [] + # for sckt in candidate_sockets: + # # get socket identifier string + # sckt_id = sckt.node.name + "_" + sckt.name + + # # if this socket is selected to randomise but it is unlinked: + # # set randomisation toggle to False + # if (not sckt.is_linked) and ( + # sockets_props_collection[sckt_id].bool_randomise + # ): + # setattr( + # sockets_props_collection[sckt_id], + # "bool_randomise", + # False, + # ) + # print( + # f"Socket {sckt_id} from {gng_str} is unlinked:", + # "randomisation toggle set to False", + # ) + + # # after modifying randomisation toggle + # # save list of sockets to randomise to dict, + # # with key = material + # if sockets_props_collection[sckt_id].bool_randomise: + # self.sockets_to_randomise_per_gng[gng_str].append(sckt) return self.execute(context) @@ -286,16 +263,16 @@ def execute(self, context): cs = context.scene # For every GNG with a subpanel - for gng_str in self.list_subpanel_gng_names: + for UD_str in self.list_subpanel_UD_names: # get collection of socket properties for this material # NOTE: socket properties do not include the actual socket object - sockets_props_collection = cs.socket_props_per_gng.collection[ - gng_str + sockets_props_collection = cs.socket_props_per_UD.collection[ + UD_str ].collection # Loop through the sockets to randomise - for sckt in self.sockets_to_randomise_per_gng[gng_str]: - socket_id = sckt.node.name + "_" + sckt.name + for sckt in self.sockets_to_randomise_per_UD[UD_str]: + socket_id = "sckt.node.name" + "_" + sckt.name # get min value for this socket min_val = np.array( @@ -315,7 +292,7 @@ def execute(self, context): # set default value # if socket type is boolean - if type(sckt) == bpy.types.NodeSocketBool: + if type(sckt) == bpy.types.BoolProperty: sckt.default_value = random.choice( [bool(list(m_val)[0]) for m_val in [min_val, max_val]] ) # 1d only @@ -324,7 +301,7 @@ def execute(self, context): # https://stackoverflow.com/questions/6824681/get-a-random-boolean-in-python # if socket type is int - elif type(sckt) == bpy.types.NodeSocketInt: + elif type(sckt) == bpy.types.IntProperty: sckt.default_value = random.randint(max_val, min_val) # for all other socket types @@ -636,21 +613,21 @@ def execute(self, context): ] #####REFACTOR for UIlist - remove reliance on ViewNodeGraphOneGNG -# or is this needed for displaying subpanel? -for i in range(config.MAX_NUMBER_OF_SUBPANELS): - operator_i = type( - f"ViewNodeGraphOneGNG_subpanel_{i}", - ( - ViewNodeGraphOneGNG, - bpy.types.Operator, - ), - { - "bl_idname": f"node.view_graph_for_gng_{i}", - "bl_label": "", - "subpanel_gng_idx": i, - }, - ) - list_classes_to_register.append(operator_i) # type: ignore +# # or is this needed for displaying subpanel? +# for i in range(config.MAX_NUMBER_OF_SUBPANELS): +# operator_i = type( +# f"ViewNodeGraphOneGNG_subpanel_{i}", +# ( +# # ViewNodeGraphOneGNG, +# bpy.types.Operator, +# ), +# { +# "bl_idname": f"node.view_graph_for_gng_{i}", +# "bl_label": "", +# "subpanel_gng_idx": i, +# }, +# ) +# list_classes_to_register.append(operator_i) # type: ignore # ----------------------------------------- diff --git a/randomiser/define_prop/property_classes/collection_gngs.py b/randomiser/define_prop/property_classes/collection_UD_props.py similarity index 65% rename from randomiser/define_prop/property_classes/collection_gngs.py rename to randomiser/define_prop/property_classes/collection_UD_props.py index 95a606e..f497479 100644 --- a/randomiser/define_prop/property_classes/collection_gngs.py +++ b/randomiser/define_prop/property_classes/collection_UD_props.py @@ -1,13 +1,12 @@ import bpy -from ... import config -from .collection_geom_socket_properties import ColGeomSocketProperties +from .collection_UD_socket_properties import ColUDSocketProperties # --------------------------------------------------- # Collection of Geometry Node groups (GNGs) # --------------------------------------------------- -def compute_node_groups_sets(self): +def compute_UD_props_sets(self): """Compute the relevant sets of geometry node groups (GNGs) and add them to self. @@ -18,20 +17,20 @@ def compute_node_groups_sets(self): """ # set of GNGs already in collection - self.set_node_groups_in_collection = set(gr.name for gr in self.collection) + self.set_UD_props_in_collection = set(UD.name for UD in self.collection) # set of node groups in Blender data structure - self.set_node_groups_in_data = set(gr.name for gr in self.candidate_gngs) + self.set_UD_props_in_data = set(UD.name for UD in self.candidate_UD_props) # set of node groups in one of the sets only - self.set_node_groups_in_one_only = ( - self.set_node_groups_in_collection.symmetric_difference( - self.set_node_groups_in_data + self.set_UD_props_in_one_only = ( + self.set_UD_props_in_collection.symmetric_difference( + self.set_UD_props_in_data ) ) -def get_update_node_groups_collection(self): +def get_update_UD_props_collection(self): """Getter function for the 'update_gngs_collection' attribute. @@ -48,18 +47,18 @@ def get_update_node_groups_collection(self): otherwise it returns False """ # compute relevant GNG sets and add them to self - compute_node_groups_sets(self) + compute_UD_props_sets(self) # if there are node groups that exist only in the Blender # data structure, or only in the collection: edit the collection - if self.set_node_groups_in_one_only: - set_update_node_groups_collection(self, True) + if self.set_UD_props_in_one_only: + set_update_UD_props_collection(self, True) return True else: return False -def set_update_node_groups_collection(self, value): +def set_update_UD_props_collection(self, value): """Setter function for the 'update_gngs_collection' attribute @@ -73,19 +72,19 @@ def set_update_node_groups_collection(self, value): if value: # if the update fn is triggered directly and not via # the getter function: compute the required sets here - if not hasattr(self, "set_node_groups_in_one_only"): - compute_node_groups_sets(self) + if not hasattr(self, "set_UD_props_in_one_only"): + compute_UD_props_sets(self) # for all node groups that are in one set only - for gr_name in self.set_node_groups_in_one_only: + for UD_name in self.set_UD_props_in_one_only: # if only in collection: remove it from the collection - if gr_name in self.set_node_groups_in_collection: - self.collection.remove(self.collection.find(gr_name)) + if UD_name in self.set_UD_props_in_collection: + self.collection.remove(self.collection.find(UD_name)) # if only in Blender data structure: add it to the collection - if gr_name in self.set_node_groups_in_data: - gr = self.collection.add() - gr.name = gr_name + if UD_name in self.set_UD_props_in_data: + UD = self.collection.add() + UD.name = UD_name # TODO: do we need to sort collection of node groups? # (otherwise their order is not guaranteed, this is relevant for @@ -94,7 +93,7 @@ def set_update_node_groups_collection(self, value): # https://blender.stackexchange.com/questions/157562/sorting-collections-alphabetically-in-the-outliner -class ColGeomNodeGroups(bpy.types.PropertyGroup): +class ColUDParentProps(bpy.types.PropertyGroup): """Collection of Geometry Node Groups This class has two attributes and one property @@ -119,19 +118,19 @@ class ColGeomNodeGroups(bpy.types.PropertyGroup): # collection of [collections of socket properties] (one per node group) collection: bpy.props.CollectionProperty( # type: ignore - type=ColGeomSocketProperties # elements in the collection + type=ColUDSocketProperties # elements in the collection ) # autopopulate collection of geometry node groups - update_gngs_collection: bpy.props.BoolProperty( # type: ignore + update_UD_props_collection: bpy.props.BoolProperty( # type: ignore default=False, - get=get_update_node_groups_collection, - set=set_update_node_groups_collection, + get=get_update_UD_props_collection, + set=set_update_UD_props_collection, ) # candidate geometry node groups @property - def candidate_gngs(self): # getter method + def candidate_UD_props(self): # getter method """Return list of geometry node groups with nodes that start with the random keyword inside them @@ -141,46 +140,37 @@ def candidate_gngs(self): # getter method _description_ """ # self is the collection of node groups - list_node_groups = [ - nd - for nd in bpy.data.node_groups - if nd.type == "GEOMETRY" - and ( - any( - [ - ni.name.lower().startswith( - config.DEFAULT_RANDOM_KEYWORD - ) - for ni in nd.nodes - ] - ) - ) + ##### REFACTOR for UIlist + list_UD_props = [ + UD + for UD in bpy.context.scene.custom + # if nd.type == "GEOMETRY" ] # # sort by name # list_node_groups = sorted( # list_materials, # key=lambda mat: mat.name.lower() # ) - return list_node_groups + return list_UD_props # ----------------------------------------- # Register and unregister functions # ------------------------------------------ def register(): - bpy.utils.register_class(ColGeomNodeGroups) + bpy.utils.register_class(ColUDParentProps) # make the property available via bpy.context.scene... # (i.e., bpy.context.scene.socket_props_per_gng) - bpy.types.Scene.socket_props_per_gng = bpy.props.PointerProperty( - type=ColGeomNodeGroups + bpy.types.Scene.socket_props_per_UD = bpy.props.PointerProperty( + type=ColUDParentProps ) def unregister(): - bpy.utils.unregister_class(ColGeomNodeGroups) + bpy.utils.unregister_class(ColUDParentProps) # remove from bpy.context.scene... - attr_to_remove = "socket_props_per_UD_property" + attr_to_remove = "socket_props_per_UD" if hasattr(bpy.types.Scene, attr_to_remove): delattr(bpy.types.Scene, attr_to_remove) diff --git a/randomiser/define_prop/ui.py b/randomiser/define_prop/ui.py index 2a84b50..b16c505 100644 --- a/randomiser/define_prop/ui.py +++ b/randomiser/define_prop/ui.py @@ -1,5 +1,6 @@ import bpy +from .. import config from ..material.ui import TemplatePanel # draw_sockets_list @@ -32,19 +33,20 @@ def invoke(self, context, event): # --------------------------------------------------- # Common layout for list of sockets to randomise # ---------------------------------------------------- -##### REFACTOR to removed list input nodes +##### REFACTOR for list input nodes as list UD props +# - commented out sockets at end of this func def draw_sockets_list_UD( cs, layout, - list_input_nodes, + list_UD_props, sockets_props_collection, ): # Define UI fields for every socket property # NOTE: if I don't sort the input nodes, everytime one of the nodes is # selected in the graph it moves to the bottom of the panel. - list_input_nodes_sorted = sorted(list_input_nodes, key=lambda x: x.name) - print(list_input_nodes_sorted) - for i_n, nd in enumerate(list_input_nodes_sorted): + list_UD_props_sorted = sorted(list_UD_props, key=lambda x: x.name) + print(list_UD_props_sorted) + for i_n, UD in enumerate(list_UD_props_sorted): row = layout.row() # if first node: add labels for @@ -58,7 +60,7 @@ def draw_sockets_list_UD( row_split.column(align=True) # input node name - col1.label(text=nd.name) + col1.label(text=UD.name) col1.alignment = "CENTER" # min label @@ -74,80 +76,80 @@ def draw_sockets_list_UD( row.separator(factor=1.0) # add empty row before each node row = layout.row() - row.label(text=nd.name) + row.label(text=UD.name) # add sockets for this node in the subseq rows - # for sckt in nd.outputs: - # # split row in 5 columns - # row = layout.row() - # row_split = row.split() - # col1 = row_split.column(align=True) - # col2 = row_split.column(align=True) - # col3 = row_split.column(align=True) - # col4 = row_split.column(align=True) - # col5 = row_split.column(align=True) - - # # socket name - # col1.alignment = "RIGHT" - # col1.label(text=sckt.name) - - # # socket current value - # col2.prop( - # sckt, - # "default_value", - # icon_only=True, - # ) - # col2.enabled = False # current value is not editable - - # # socket min and max columns - # socket_id = nd.name + "_" + sckt.name - # if (nd.id_data.name in bpy.data.node_groups) and ( - # bpy.data.node_groups[nd.id_data.name].type != "GEOMETRY" - # ): # only for SHADER groups - # socket_id = nd.id_data.name + "_" + socket_id - - # # if socket is a color: format min/max as a color picker - # # and an array (color picker doesn't include alpha value) - # if type(sckt) == bpy.types.NodeSocketColor: - # for m_str, col in zip(["min", "max"], [col3, col4]): - # # color picker - # col.template_color_picker( - # sockets_props_collection[socket_id], - # m_str + "_" + cs.socket_type_to_attr[type(sckt)], - # ) - # # array - # for j, cl in enumerate(["R", "G", "B", "alpha"]): - # col.prop( - # sockets_props_collection[socket_id], - # m_str + "_" + cs.socket_type_to_attr[type(sckt)], - # icon_only=False, - # text=cl, - # index=j, - # ) - # # if socket is Boolean: add non-editable labels - # elif type(sckt) == bpy.types.NodeSocketBool: - # for m_str, col in zip(["min", "max"], [col3, col4]): - # m_val = getattr( - # sockets_props_collection[socket_id], - # m_str + "_" + cs.socket_type_to_attr[type(sckt)], - # ) - # col.label(text=str(list(m_val)[0])) - - # # if socket is not color type: format as a regular property - # else: - # for m_str, col in zip(["min", "max"], [col3, col4]): - # col.prop( - # sockets_props_collection[socket_id], - # m_str + "_" + cs.socket_type_to_attr[type(sckt)], - # icon_only=True, - # ) - - # # randomisation toggle - # col5.prop( - # sockets_props_collection[socket_id], - # "bool_randomise", - # icon_only=True, - # ) + for sckt in UD.outputs: + # split row in 5 columns + row = layout.row() + row_split = row.split() + col1 = row_split.column(align=True) + col2 = row_split.column(align=True) + col3 = row_split.column(align=True) + col4 = row_split.column(align=True) + col5 = row_split.column(align=True) + + # socket name + col1.alignment = "RIGHT" + col1.label(text=sckt.name) + + # socket current value + col2.prop( + sckt, + "default_value", + icon_only=True, + ) + col2.enabled = False # current value is not editable + + # socket min and max columns + socket_id = UD.name + "_" + sckt.name + if (UD.id_data.name in bpy.data.node_groups) and ( + bpy.data.node_groups[UD.id_data.name].type != "GEOMETRY" + ): # only for SHADER groups + socket_id = UD.id_data.name + "_" + socket_id + + # if socket is a color: format min/max as a color picker + # and an array (color picker doesn't include alpha value) + if type(sckt) == bpy.types.NodeSocketColor: + for m_str, col in zip(["min", "max"], [col3, col4]): + # color picker + col.template_color_picker( + sockets_props_collection[socket_id], + m_str + "_" + cs.socket_type_to_attr[type(sckt)], + ) + # array + for j, cl in enumerate(["R", "G", "B", "alpha"]): + col.prop( + sockets_props_collection[socket_id], + m_str + "_" + cs.socket_type_to_attr[type(sckt)], + icon_only=False, + text=cl, + index=j, + ) + # if socket is Boolean: add non-editable labels + elif type(sckt) == bpy.types.NodeSocketBool: + for m_str, col in zip(["min", "max"], [col3, col4]): + m_val = getattr( + sockets_props_collection[socket_id], + m_str + "_" + cs.socket_type_to_attr[type(sckt)], + ) + col.label(text=str(list(m_val)[0])) + + # if socket is not color type: format as a regular property + else: + for m_str, col in zip(["min", "max"], [col3, col4]): + col.prop( + sockets_props_collection[socket_id], + m_str + "_" + cs.socket_type_to_attr[type(sckt)], + icon_only=True, + ) + + # randomisation toggle + col5.prop( + sockets_props_collection[socket_id], + "bool_randomise", + icon_only=True, + ) # ---------------------- @@ -222,23 +224,23 @@ class SubPanelUDUIlist(TemplatePanel): bl_label = "" # title of the panel displayed to the user bl_options = {"HIDE_HEADER"} - @classmethod - def poll(cls, context): - """Determine whether the panel can be displayed. + # @classmethod + # def poll(cls, context): + # """Determine whether the panel can be displayed. - This panel is only displayed if there is an active object + # This panel is only displayed if there is an active object - Parameters - ---------- - context : _type_ - _description_ + # Parameters + # ---------- + # context : _type_ + # _description_ - Returns - ------- - boolean - True if there is an active object, False otherwise - """ - return context.object is not None + # Returns + # ------- + # boolean + # True if there is an active object, False otherwise + # """ + # return context.object is not None def draw(self, context): column = self.layout.column(align=True) @@ -337,18 +339,14 @@ def poll(cls, context): cs = context.scene # force an update on the group nodes collection first - if cs.socket_props_per_UD.update_gngs_collection: - print("Collection of Geometry Node Groups updated") - ######pointer needs defined in collection_UD_sock_props? + if cs.socket_props_per_UD.update_sockets_collection: + print("Collection of UD props updated") # In redundant materials level (only need sockets) - return cls.subpanel_gng_idx < len( + return cls.subpanel_UD_idx < len( cs.socket_props_per_UD.collection - ) #####clc.subpanel defined in operators.py + ) # clc.subpanel defined in operators.py # only display subpanels for which this is true - # return cls.subpanel_custom_idx < len( - # cs.socket_props_per_material.collection - # ) def draw_header( self, context @@ -368,18 +366,18 @@ def draw_header( cs = context.scene # get this subpanel's GNG - subpanel_gng = cs.socket_props_per_UD.collection[self.subpanel_gng_idx] + cs.socket_props_per_UD.collection[self.subpanel_UD_idx] # add view graph operator to layout layout = self.layout layout.use_property_split = True layout.use_property_decorate = False - layout.operator( - f"node.view_graph_for_gng_{self.subpanel_gng_idx}", - text=subpanel_gng.name, - emboss=True, - ) #####operator defined once node.view_graph_for_gng - # - not needed for custom props? + # layout.operator( + # f"node.view_graph_for_gng_{self.subpanel_UD_prop_idx}", + # text=subpanel_UD_prop.name, + # emboss=True, + # ) #####operator defined once node.view_graph_for_gng + # # - not needed for custom props? def draw(self, context): """Define the content to display in the GNG subpanel @@ -392,27 +390,31 @@ def draw(self, context): cs = context.scene # get this subpanel's GNG - subpanel_gng = cs.socket_props_per_UD.collection[self.subpanel_gng_idx] - #####NEED TO COMBINE THESE TWO PARTS INTO JUST SOCKETS + subpanel_UD_prop = cs.socket_props_per_UD.collection[ + self.subpanel_UD_prop_idx + ] + # force an update in the sockets for this GNG if cs.socket_props_per_UD.collection[ - subpanel_gng.name + subpanel_UD_prop.name ].update_sockets_collection: - print("Collection of Geometry Node Groups updated") + print("Collection of UD props updated") # get (updated) collection of socket props for this GNG sockets_props_collection = cs.socket_props_per_UD.collection[ - subpanel_gng.name + subpanel_UD_prop.name ].collection # Get list of input nodes to randomise for this subpanel's GNG - list_parent_nodes_str = [ + list_parent_UD_str = [ sckt.name.split("_")[0] for sckt in sockets_props_collection ] - #####REMOVE INPUT NODES FROM SOCKETS LIST FOR CUSTOM PROPS - list_input_nodes = [ - bpy.data.node_groups[subpanel_gng.name].nodes[nd_str] - for nd_str in list_parent_nodes_str + + list_UD_props = [ + bpy.context.scene.custom[UD_str] + for UD_str in list_parent_UD_str + # bpy.data.node_groups[subpanel_gng.name].nodes[nd_str] + # for nd_str in list_parent_nodes_str ] # Draw sockets to randomise per input node, including their @@ -420,7 +422,7 @@ def draw(self, context): draw_sockets_list_UD( cs, self.layout, - list_input_nodes, ##### remove once refactored + list_UD_props, sockets_props_collection, ) @@ -501,27 +503,27 @@ def draw(self, context): # defined a priori, we define n=MAX_NUMBER_OF_SUBPANELS classes and # assign an index to each of them. We will only display the subpanels # whose index is lower than the total number of GNGs defined in the scene. -# for i in range(config.MAX_NUMBER_OF_SUBPANELS): -# # define subpanel class for GNG i -# # subpanel_class_i = type( -# # f"NODE_GEOMETRY_PT_subpanel_{i}", -# # (SubPanelRandomGeometryNodes,), -# # { -# # "bl_idname": f"NODE_GEOMETRY_PT_subpanel_{i}", -# # "subpanel_gng_idx": i, -# # }, -# # ) -# subpanel_class_i = type( -# f"UD_PT_subpanel_{i}", -# (SubPanelRandomUD,), -# { -# "bl_idname": f"UD_PT_subpanel_{i}", -# "subpanel_gng_idx": i, ##### IN UI AND OPERATORS -# }, -# ) - -# # append to list of classes to register -# list_classes_to_register.append(subpanel_class_i) # type: ignore +for i in range(config.MAX_NUMBER_OF_SUBPANELS): + # define subpanel class for GNG i + # subpanel_class_i = type( + # f"NODE_GEOMETRY_PT_subpanel_{i}", + # (SubPanelRandomGeometryNodes,), + # { + # "bl_idname": f"NODE_GEOMETRY_PT_subpanel_{i}", + # "subpanel_gng_idx": i, + # }, + # ) + subpanel_class_i = type( + f"UD_PT_subpanel_{i}", + (SubPanelRandomUD,), + { + "bl_idname": f"UD_PT_subpanel_{i}", + "subpanel_UD_idx": i, ##### IN UI AND OPERATORS + }, + ) + + # append to list of classes to register + list_classes_to_register.append(subpanel_class_i) # type: ignore # add Subpanel with operator to the list From de6ca72e389d8fbf46be49c96bcc8855f8f0f61f Mon Sep 17 00:00:00 2001 From: ruaridhg Date: Thu, 8 Jun 2023 10:22:10 +0100 Subject: [PATCH 14/58] UIlist not disappearing and adds subpanels --- randomiser/define_prop/operators.py | 297 ++---------------- randomiser/define_prop/properties.py | 2 + .../collection_UD_socket_properties.py | 11 - randomiser/define_prop/ui.py | 38 ++- 4 files changed, 43 insertions(+), 305 deletions(-) diff --git a/randomiser/define_prop/operators.py b/randomiser/define_prop/operators.py index 9d24a06..195d48e 100644 --- a/randomiser/define_prop/operators.py +++ b/randomiser/define_prop/operators.py @@ -3,7 +3,7 @@ import bpy import numpy as np -from .. import utils +from .. import config class CUSTOM_OT_actions(bpy.types.Operator): @@ -169,6 +169,8 @@ def poll(cls, context): return len(context.scene.socket_props_per_UD.collection) > 0 + ##### scene no attribute socket per UD + def invoke(self, context, event): """Initialise parmeters before executing the operator @@ -340,266 +342,7 @@ def execute(self, context): # return -# ------------------------------- -# Operator: view graph per GNG -# ------------------------------- -#####REFACTOR for UIlist - needed for displaying subpanels -# in MAX_NUMBER_OF_SUBPANELS for loop -class ViewNodeGraphOneGNG(bpy.types.Operator): - """Show node graph for the relevant - Geometry Node Group - - Parameters - ---------- - bpy : _type_ - _description_ - - Returns - ------- - _type_ - _description_ - """ - - bl_idname = ( - "gng.view_graph" # this is appended to bpy.ops. - # NOTE: it will be overwritten for each instance of - # the operator - ) - bl_label = "View node graph for this Geometry node group" - bl_options = {"REGISTER", "UNDO"} - - @classmethod - def poll(cls, context): - """Determine whether the operator can be executed. - - This operator can only run if: - - its geometry node group is of geometry type, - and either: - - the associated geometry node group is linked to a modifier - of the currently active object, or - - the associated geometry node group is an inner node and its - root parent is a geometry node group linked to a modifier - of the currently active object. - - An inner node is a geometry node group defined inside - another geometry node group. The path of nodes to an inner - node is the list of group nodes that leads to the inner node. - Its root parent is the only parent node group in the path of nodes - without a parent. - - If the operator can't be executed, the button will appear as disabled. - - Parameters - ---------- - context : _type_ - _description_ - - Returns - ------- - _type_ - _description_ - """ - - cs = context.scene - cob = context.object - - # get list of all GNGs - list_gngs = [ - gr.name for gr in bpy.data.node_groups if gr.type == "GEOMETRY" - ] - - # get geometry node group (GNG) of this subpanel - subpanel_gng = cs.socket_props_per_gng.collection[cls.subpanel_gng_idx] - - # get list of GNGs linked to modifiers of the active object - list_gngs_in_modifiers = utils.get_gngs_linked_to_modifiers(cob) - list_gngs_in_modifiers_names = [ - ng.name for ng in list_gngs_in_modifiers - ] - - # get list of (inner) GNGs whose root parent is a modfier-linked - # GNG - map_node_group_to_root_node_group = utils.get_map_inner_gngs( - list_gngs_in_modifiers, - ) - - # define condition to enable the operator - display_operator = ( - subpanel_gng.name - in list_gngs - # TODO: maybe this is not required here? - ) and ( - (subpanel_gng.name in list_gngs_in_modifiers_names) - or ( - subpanel_gng.name - in [gr.name for gr in map_node_group_to_root_node_group.keys()] - ) - ) - - return display_operator - - def invoke(self, context, event): - """Initialise parmeters before executing the operator - - The invoke() function runs before executing the operator. - Here, we add the subpanel's geometry node group name to - the operator self - - Parameters - ---------- - context : bpy_types.Context - the context from which the operator is executed - event : _type_ - _description_ - - Returns - ------- - _type_ - _description_ - """ - - cs = context.scene - subpanel_gng = cs.socket_props_per_gng.collection[ - self.subpanel_gng_idx - ] - self.subpanel_gng_name = subpanel_gng.name - - return self.execute(context) - - def execute(self, context): - """Execute the 'view graph' operator. - - It shows the graph for the geometry node group (GNG) shown in the - subpanel's header. - - If the GNG associated to the subpanel is linked to a modifier of the - active object, then that modifier is set to active and the graph - is automatically updated. - - If the GNG associated to the subpanel is NOT linked to a modifier, but - it is an inner GNG of a modifier-linked GNG, then: - - the modifier of the root parent GNG is set as active, and the graph - is set to the root parent view - - the path to the inner GNG is computed - - the graph of the inner GNG is displayed, by recursively setting each - parent node as active and then executing the 'edit node' command. - - - If the GNG associated to the subpanel is NOT linked to a modifier, and - it is NOT an inner GNG of a modifier-linked GNG, then a new modifier - will be added to the currently active material and this subpanel's GNG - will be linked to it - - Parameters - ---------- - context : _type_ - _description_ - - Returns - ------- - _type_ - _description_ - """ - - cob = context.object - - # find the modifier linked to this GNG, if exists - subpanel_modifier = utils.get_modifier_linked_to_gng( - self.subpanel_gng_name, cob - ) - - # get dict of inner GNGs - # the dict maps inner GNGs to a tuple made of its root parent GNG - # and its depth - map_inner_node_groups_to_root_parent = utils.get_map_inner_gngs( - [gr for gr in bpy.data.node_groups if gr.type == "GEOMETRY"] - ) - - # if there is a modifier linked to this GNG: set that - # modifier as active (this will change the displayed graph) - if subpanel_modifier: - bpy.ops.object.modifier_set_active(modifier=subpanel_modifier.name) - - # ensure graph is at top level - utils.set_gngs_graph_to_top_level( - bpy.data.node_groups[self.subpanel_gng_name] - ) - - # if there is no modifier linked to this GNG, - # but it is an inner GNG whose root parent is a modifier-linked GNG: - # set the modifier as active and navigate the graph to the - # inner GNG - elif not subpanel_modifier and ( - self.subpanel_gng_name - in [gr.name for gr in map_inner_node_groups_to_root_parent.keys()] - ): - # find the modifier linked to the (root) parent and set as active - # NOTE: if the root parent is not linked to a modifier, - # the operator will show as disabled - root_parent_node_group = map_inner_node_groups_to_root_parent[ - bpy.data.node_groups[self.subpanel_gng_name] - ][0] - - root_parent_modifier = utils.get_modifier_linked_to_gng( - root_parent_node_group.name, cob - ) - - bpy.ops.object.modifier_set_active( - modifier=root_parent_modifier.name - ) - - # compute the path to this subpanel's GNG - # from the parent root GNG (both ends inclusive) - path_to_gng = utils.get_path_to_gng( - bpy.data.node_groups[self.subpanel_gng_name] - ) - - # ensure we are at the top level in the graph - # (top level = parent root GNG) - utils.set_gngs_graph_to_top_level(root_parent_node_group) - - # navigate the graph to the desired GNG - # at every step: we set the target GNG as active and - # click 'edit group' - for i, _ in enumerate(path_to_gng[:-1]): - # get target GNG for this step and its parent - gng_parent = path_to_gng[i] - gng_target = path_to_gng[i + 1] - - # get selectable version of the target GNG - selectable_gng_target = ( - utils.get_selectable_node_for_node_group(gng_target) - ) - - # set target GNG as active - if gng_parent == root_parent_node_group: - root_parent_node_group.nodes.active = selectable_gng_target - else: - selectable_parent_gng = ( - utils.get_selectable_node_for_node_group(gng_parent) - ) - selectable_parent_gng.node_tree.nodes.active = ( - selectable_gng_target - ) - - # click 'edit group', i.e. go one level down in the graph - bpy.ops.node.group_edit(exit=False) - - # if there is no modifier linked to this GNG - # and it is not an inner GNG: create a new modifier - # for the currently active object and link the GNG to it - else: - # add a new 'Geometry nodes group' modifier - # (will set it as active) - bpy.ops.object.modifier_add(type="NODES") - new_modifier = bpy.context.object.modifiers.active - - # assign the subpanel's GNGto this modifier - new_modifier.node_group = bpy.data.node_groups[ - self.subpanel_gng_name - ] - - return {"FINISHED"} +##### Graph function removed - not needed? # --------------------- @@ -614,20 +357,20 @@ def execute(self, context): #####REFACTOR for UIlist - remove reliance on ViewNodeGraphOneGNG # # or is this needed for displaying subpanel? -# for i in range(config.MAX_NUMBER_OF_SUBPANELS): -# operator_i = type( -# f"ViewNodeGraphOneGNG_subpanel_{i}", -# ( -# # ViewNodeGraphOneGNG, -# bpy.types.Operator, -# ), -# { -# "bl_idname": f"node.view_graph_for_gng_{i}", -# "bl_label": "", -# "subpanel_gng_idx": i, -# }, -# ) -# list_classes_to_register.append(operator_i) # type: ignore +for i in range(config.MAX_NUMBER_OF_SUBPANELS): + operator_i = type( + f"ViewNodeGraphOneGNG_subpanel_{i}", + ( + # ViewNodeGraphOneGNG, + bpy.types.Operator, + ), + { + "bl_idname": f"node.view_graph_for_gng_{i}", + "bl_label": "", + "subpanel_gng_idx": i, + }, + ) + list_classes_to_register.append(operator_i) # type: ignore # ----------------------------------------- @@ -641,7 +384,7 @@ def register(): # randomise_geometry_nodes_per_frame # ) - print("geometry operators registered") + print("UD operators registered") def unregister(): @@ -652,4 +395,4 @@ def unregister(): # randomise_geometry_nodes_per_frame # ) - print("geometry operators unregistered") + print("UD operators unregistered") diff --git a/randomiser/define_prop/properties.py b/randomiser/define_prop/properties.py index 68d8581..2d8d0b9 100644 --- a/randomiser/define_prop/properties.py +++ b/randomiser/define_prop/properties.py @@ -1,6 +1,7 @@ import bpy from .property_classes import ( + collection_UD_props, collection_UD_socket_properties, ) @@ -61,6 +62,7 @@ class CUSTOM_colorCollection(bpy.types.PropertyGroup): def register(): collection_UD_socket_properties.register() + collection_UD_props.register() for cls in list_classes_to_register: bpy.utils.register_class(cls) diff --git a/randomiser/define_prop/property_classes/collection_UD_socket_properties.py b/randomiser/define_prop/property_classes/collection_UD_socket_properties.py index 6da2204..e4b2f34 100644 --- a/randomiser/define_prop/property_classes/collection_UD_socket_properties.py +++ b/randomiser/define_prop/property_classes/collection_UD_socket_properties.py @@ -240,17 +240,6 @@ def candidate_sockets(self): # getter method def register(): bpy.utils.register_class(ColUDSocketProperties) - # make the property available via bpy.context.scene... - # (i.e., bpy.context.scene.socket_props_per_gng) ##### - bpy.types.Scene.socket_props_per_UD = bpy.props.PointerProperty( - type=ColUDSocketProperties - ) - def unregister(): bpy.utils.unregister_class(ColUDSocketProperties) - - # remove from bpy.context.scene... - attr_to_remove = "socket_props_per_UD" - if hasattr(bpy.types.Scene, attr_to_remove): - delattr(bpy.types.Scene, attr_to_remove) diff --git a/randomiser/define_prop/ui.py b/randomiser/define_prop/ui.py index b16c505..1919bb0 100644 --- a/randomiser/define_prop/ui.py +++ b/randomiser/define_prop/ui.py @@ -224,23 +224,23 @@ class SubPanelUDUIlist(TemplatePanel): bl_label = "" # title of the panel displayed to the user bl_options = {"HIDE_HEADER"} - # @classmethod - # def poll(cls, context): - # """Determine whether the panel can be displayed. + @classmethod + def poll(cls, context): + """Determine whether the panel can be displayed. - # This panel is only displayed if there is an active object + This panel is only displayed if there is an active object - # Parameters - # ---------- - # context : _type_ - # _description_ + Parameters + ---------- + context : _type_ + _description_ - # Returns - # ------- - # boolean - # True if there is an active object, False otherwise - # """ - # return context.object is not None + Returns + ------- + boolean + True if there is an active object, False otherwise + """ + return context.object is not None def draw(self, context): column = self.layout.column(align=True) @@ -339,9 +339,12 @@ def poll(cls, context): cs = context.scene # force an update on the group nodes collection first - if cs.socket_props_per_UD.update_sockets_collection: + if cs.socket_props_per_UD.update_UD_props_collection: print("Collection of UD props updated") - # In redundant materials level (only need sockets) + ##### scene no attribute socket per UD + + # print(cls.subpanel_UD_idx) + # print(cs.socket_props_per_UD.collection) return cls.subpanel_UD_idx < len( cs.socket_props_per_UD.collection @@ -367,6 +370,7 @@ def draw_header( # get this subpanel's GNG cs.socket_props_per_UD.collection[self.subpanel_UD_idx] + ##### scene no attribute socket per UD # add view graph operator to layout layout = self.layout @@ -391,7 +395,7 @@ def draw(self, context): # get this subpanel's GNG subpanel_UD_prop = cs.socket_props_per_UD.collection[ - self.subpanel_UD_prop_idx + self.subpanel_UD_idx ] # force an update in the sockets for this GNG From 60336e4391f2e4f11327d00035299732324736b2 Mon Sep 17 00:00:00 2001 From: ruaridhg Date: Thu, 8 Jun 2023 11:24:10 +0100 Subject: [PATCH 15/58] removed input and outputs from nodes code --- .../collection_UD_socket_properties.py | 18 +++++++++--------- randomiser/utils.py | 12 ++++++------ 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/randomiser/define_prop/property_classes/collection_UD_socket_properties.py b/randomiser/define_prop/property_classes/collection_UD_socket_properties.py index e4b2f34..1cd0250 100644 --- a/randomiser/define_prop/property_classes/collection_UD_socket_properties.py +++ b/randomiser/define_prop/property_classes/collection_UD_socket_properties.py @@ -219,16 +219,16 @@ def candidate_sockets(self): # getter method # get list of sockets that are candidate for randomisation list_sockets = [ - out + nd for nd in list_input_nodes - for out in nd.outputs - if type(out) - not in [ - bpy.types.NodeSocketGeometry, - bpy.types.NodeSocketMaterial, - bpy.types.NodeSocketObject, - bpy.types.NodeSocketString, - ] + # for out in nd.outputs + # if type(out) + # not in [ + # bpy.types.NodeSocketGeometry, + # bpy.types.NodeSocketMaterial, + # bpy.types.NodeSocketObject, + # bpy.types.NodeSocketString, + # ] ] #####refactor to sockets without input nodes return list_sockets diff --git a/randomiser/utils.py b/randomiser/utils.py index c817eab..2e03ea2 100644 --- a/randomiser/utils.py +++ b/randomiser/utils.py @@ -36,13 +36,13 @@ def get_UD_sockets_to_randomise_from_list( list_input_nodes = [ nd for nd in list_candidate_nodes - if len(nd.inputs) == 0 + # if len(nd.inputs) == 0 # and nd.name.lower().startswith(node2randomise_prefix.lower()) - and nd.type - not in [ - "GROUP_INPUT", - "GROUP_OUTPUT", - ] + # and nd.type + # not in [ + # "GROUP_INPUT", + # "GROUP_OUTPUT", + # ] ] return list_input_nodes From 785dd3ddee67da5aa47083b4c2a54a3e0337ed00 Mon Sep 17 00:00:00 2001 From: ruaridhg Date: Thu, 8 Jun 2023 11:30:45 +0100 Subject: [PATCH 16/58] Removed old custom props folder --- randomiser/custom_props/__init__.py | 13 - .../collection_custom_properties.py | 223 ----------- randomiser/custom_props/config.py | 17 - randomiser/custom_props/custom_properties.py | 219 ----------- randomiser/custom_props/operators.py | 317 ---------------- randomiser/custom_props/properties.py | 196 ---------- randomiser/custom_props/ui.py | 358 ------------------ 7 files changed, 1343 deletions(-) delete mode 100644 randomiser/custom_props/__init__.py delete mode 100644 randomiser/custom_props/collection_custom_properties.py delete mode 100644 randomiser/custom_props/config.py delete mode 100644 randomiser/custom_props/custom_properties.py delete mode 100644 randomiser/custom_props/operators.py delete mode 100644 randomiser/custom_props/properties.py delete mode 100644 randomiser/custom_props/ui.py diff --git a/randomiser/custom_props/__init__.py b/randomiser/custom_props/__init__.py deleted file mode 100644 index 8051ea8..0000000 --- a/randomiser/custom_props/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -from . import operators, properties, ui - - -def register(): - properties.register() - ui.register() - operators.register() - - -def unregister(): - properties.unregister() - ui.unregister() - operators.unregister() diff --git a/randomiser/custom_props/collection_custom_properties.py b/randomiser/custom_props/collection_custom_properties.py deleted file mode 100644 index 096f186..0000000 --- a/randomiser/custom_props/collection_custom_properties.py +++ /dev/null @@ -1,223 +0,0 @@ -import re - -import bpy - -from .. import utils -from .custom_properties import CustomProperties - - -# ----------------------------------------------------------------- -# Setter / getter methods for update_sockets_collection attribute -# ---------------------------------------------------------------- -def compute_sockets_sets(self): - # set of sockets in collection for this material - self.set_sckt_names_in_collection_of_props = set( - sck_p.name for sck_p in self.collection - ) - - # set of sockets in graph *for this material* ! - list_sckt_names_in_UIlist = [] - for sck in self.candidate_sockets: - # if socket comes from a node inside a group - # (TODO is there a better way to check whether the node is in a group?) - if sck.name in self.candidate_sockets: - list_sckt_names_in_UIlist.append("UI_list" + "_" + sck.name) - # if socket comes from an independent node - else: - list_sckt_names_in_UIlist.append("_" + sck.name) - - self.set_sckt_names_in_UIlist = set(list_sckt_names_in_UIlist) - - # set of sockets that are just in one of the two groups - self.set_of_sckt_names_in_one_only = ( - self.set_sckt_names_in_collection_of_props.symmetric_difference( - self.set_sckt_names_in_UIlist - ) - ) - - -def get_update_collection(self): - """Get function for the update_sockets_collection attribute - of the class ColCustomProperties - - It will run when the property value is 'get' and - it will update the collection of socket properties if required - - Returns - ------- - boolean - returns True if the collection of socket properties is updated, - otherwise it returns False - """ - # compute the different sets of sockets - compute_sockets_sets(self) - - # if there is a difference between - # sets of sockets in graph and in collection: - # edit the set of sockets in collection - # for this material with the latest data - if self.set_of_sckt_names_in_one_only: - set_update_collection(self, True) - return True # if returns True, it has been updated - else: - return False # if returns False, it hasn't - - -def set_update_collection(self, value): - """ - 'Set' function for the update_sockets_collection attribute - of the class ColCustomProperties. - - It will run when the property value is 'set'. - - It will update the collection of socket properties as follows: - - For the set of sockets that exist only in either - the collection or the graph: - - if the socket exists only in the collection: remove from - collection - - if the socket exists only in the node graph: add to collection - with initial values - - For the rest of sockets: leave untouched - - Parameters - ---------- - value : boolean - if True, the collection of socket properties is - overwritten to consider the latest data - """ - - if value: - # if the update fn is triggered directly and not via - # getter fn: compute sets - if not hasattr(self, "set_of_sckt_names_in_one_only"): - compute_sockets_sets(self) - - # update sockets that are only in either - # the collection set or the graph set - for sckt_name in self.set_of_sckt_names_in_one_only: - # - if the socket exists only in the collection: remove from - # collection - if sckt_name in self.set_sckt_names_in_collection_of_props: - self.collection.remove(self.collection.find(sckt_name)) - - # - if the socket exists only in the node graph: add to collection - # with initial values - if sckt_name in self.set_sckt_names_in_UIlist: - sckt_prop = self.collection.add() - sckt_prop.name = sckt_name - sckt_prop.bool_randomise = True - - # --------------------------- - # TODO: review - is this too hacky? - # get socket object for this socket name - # NOTE: my definition of socket name - # (node.name + _ + socket.name) - for s in self.candidate_sockets: - # build socket id from scratch - socket_id = s.node.name + "_" + s.name - if s.node.id_data.name in bpy.data.node_groups: - socket_id = s.node.id_data.name + "_" + socket_id - - if socket_id == sckt_name: - sckt = s - break - - # add min/max values - # for this socket type, get the name of the attribute - # holding the min/max properties - socket_attrib_str = bpy.context.scene.socket_type_to_attr[ - type(sckt) - ] - # for the shape of the array from the attribute name: - # extract last number between '_' and 'd/D' in the attribute - # name - n_dim = int( - re.findall(r"_(\d+)(?:d|D)", socket_attrib_str)[-1] - ) - # --------------------------- - - # get dict with initial min/max values for this socket type - ini_min_max_values = ( - bpy.context.scene.socket_type_to_ini_min_max[type(sckt)] - ) - - # assign initial value - for m_str in ["min", "max"]: - setattr( - sckt_prop, - m_str + "_" + socket_attrib_str, - (ini_min_max_values[m_str],) * n_dim, - ) - - -# ----------------------- -# ColCustomProperties -# --------------------- -class ColCustomProperties(bpy.types.PropertyGroup): - """Class holding the collection of socket properties and - a boolean property to update the collection if required - (for example, if new nodes are added) - - NOTE: we use the update_sockets_collection property as an - auxiliary property because the CollectionProperty has no update function - https://docs.blender.org/api/current/bpy.props.html#update-example - - """ - - # name of the material - name: bpy.props.StringProperty() # type: ignore - - # collection of socket properties - collection: bpy.props.CollectionProperty( # type: ignore - type=CustomProperties - ) - - # 'dummy' attribute to update collection of socket properties - update_custom_collection: bpy.props.BoolProperty( # type: ignore - default=False, - get=get_update_collection, - set=set_update_collection, - ) - - # -------------------------------- - # candidate sockets for this material - @property - def candidate_sockets(self): # getter method - """Get function for the candidate_sockets property - - We define candidate sockets as the set of output sockets - in input nodes, in the graph for the currently active - material. Input nodes are nodes with only output sockets - (i.e., no input sockets). - - It returns a list of sockets that are candidates for - the randomisation. - - - Returns - ------- - list - list of sockets in the input nodes in the graph - """ - # get list of input nodes for this material - # input nodes are defined as those: - # - with no input sockets - # - their name starts with random - # - and they can be independent or inside a node group - list_input_nodes = utils.get_material_nodes_to_randomise_all(self.name) - - # list of sockets - list_sockets = [out for nd in list_input_nodes for out in nd.outputs] - - return list_sockets - - -# ----------------------------------------- -# Register and unregister functions -# ------------------------------------------ -def register(): - bpy.utils.register_class(ColCustomProperties) - - -def unregister(): - bpy.utils.unregister_class(ColCustomProperties) diff --git a/randomiser/custom_props/config.py b/randomiser/custom_props/config.py deleted file mode 100644 index 239fd75..0000000 --- a/randomiser/custom_props/config.py +++ /dev/null @@ -1,17 +0,0 @@ -# Parameters shared across materials modules - - -# MAX_NUMBER_OF_SUBPANELS: upper limit for the expected -# number of *materials* in a scene. -# This number of subpanels will be defined as classes, but -# only those panels with index < total number of materials -# will be displayed. -MAX_NUMBER_OF_SUBPANELS = 100 - -# MAX_NUMBER_OF_SUBSUBPANELS: upper limit for the expected -# number of *group nodes in a single material*. -# A total of MAX_NUMBER_OF_SUBPANELS*MAX_NUMBER_OF_SUBSUBPANELS subsubpanels -# will be defined as classes, but only those panels with -# index < total number of group nodes per material -# will be displayed. -MAX_NUMBER_OF_SUBSUBPANELS = 100 diff --git a/randomiser/custom_props/custom_properties.py b/randomiser/custom_props/custom_properties.py deleted file mode 100644 index 5c2ee02..0000000 --- a/randomiser/custom_props/custom_properties.py +++ /dev/null @@ -1,219 +0,0 @@ -import bpy -import numpy as np - - -# ----------------------------------------- -# Bounds to SocketProperties -# ----------------------------------------- -def constrain_min_closure(m_str): - """Constain min value with closure - - Parameters - ---------- - m_str : str - string specifying the socket attribute (e.g., float_1d) - - Returns - ------- - _type_ - lambda function evaluated at the specified m_str - - """ - - def constrain_min(self, context, m_str): - """Constrain min value - - If min > max --> min is reset to max value - (i.e., no randomisation) - - Parameters - ---------- - context : _type_ - _description_ - m_str : str - string specifying the socket attribute (e.g., float_1d) - """ - # self is a 'SocketProperties' object - min_array = np.array(getattr(self, "min_" + m_str)) - max_array = np.array(getattr(self, "max_" + m_str)) - if any(min_array > max_array): - setattr( - self, - "min_" + m_str, - np.where(min_array > max_array, max_array, min_array), - ) - return - - return lambda slf, ctx: constrain_min(slf, ctx, m_str) - - -def constrain_max_closure(m_str): - """Constain max value with closure - - Parameters - ---------- - m_str : str - string specifying the socket attribute (e.g., float_1d) - - Returns - ------- - _type_ - lambda function evaluated at the specified m_str - - """ - - def constrain_max(self, context, m_str): - """Constrain max value - - if max < min --> max is reset to min value - (i.e., no randomisation) - - Parameters - ---------- - context : _type_ - _description_ - m_str : str - string specifying the socket attribute (e.g., float_1d) - """ - # self is a 'SocketProperties' object - min_array = np.array(getattr(self, "min_" + m_str)) - max_array = np.array(getattr(self, "max_" + m_str)) - if any(max_array < min_array): - setattr( - self, - "max_" + m_str, - np.where(max_array < min_array, min_array, max_array), - ) - return - - return lambda slf, ctx: constrain_max(slf, ctx, m_str) - - -def constrain_rgba_closure(m_str): - """Constain RGBA value with closure - - Parameters - ---------- - m_str : str - string specifying the socket attribute (e.g., float_1d) - - Returns - ------- - _type_ - lambda function evaluated at the specified m_str - - """ - - def constrain_rgba(self, context, min_or_max_full_str): - """Constrain RGBA value - - if RGBA socket: constrain values to be between 0 and 1 - - Parameters - ---------- - context : _type_ - _description_ - m_str : str - string specifying the socket attribute (e.g., float_1d) - """ - min_or_max_array = np.array(getattr(self, min_or_max_full_str)) - if any(min_or_max_array > 1.0) or any(min_or_max_array < 0.0): - setattr( - self, - min_or_max_full_str, - np.clip(min_or_max_array, 0.0, 1.0), - ) - return - - return lambda slf, ctx: constrain_rgba(slf, ctx, m_str) - - -# ----------------------- -# SocketProperties -# --------------------- -class CustomProperties(bpy.types.PropertyGroup): - """ - Class holding the set of properties - for a socket, namely: - - socket name, - - min/max values, and - - boolean for randomisation - - Because I think it is not possible to define attributes dynamically, - for now we define an attribute for each possible socket type - in the input nodes. These are all FloatVectors of different sizes. - The size is specified in the attribute's name: - - min/max_float_1d - - min/max_float_3d - - min/max_float_4d - - min/max_rgba_4d - - """ - - # TODO: how to set attributes dynamically? - # TODO: I don't really get why this type definition is also an assignment? - - # --------------------- - # name of the socket - # NOTE: if we make a Blender collection of this type of objects, - # we will be able to access them by name - name: bpy.props.StringProperty() # type: ignore - - # TODO: include the socket itself here to? - # socket: PointerProperty(type=bpy.types.NodeSocketStandard?) - - # --------------------- - # float 1d - float_1d_str = "float_1d" - min_float_1d: bpy.props.FloatVectorProperty( # type: ignore - size=1, update=constrain_min_closure(float_1d_str) - ) - - max_float_1d: bpy.props.FloatVectorProperty( # type: ignore - size=1, update=constrain_max_closure(float_1d_str) - ) - - # --------------------- - # float 3d - float_3d_str = "float_3d" - min_float_3d: bpy.props.FloatVectorProperty( # type: ignore - update=constrain_min_closure(float_3d_str) - ) - max_float_3d: bpy.props.FloatVectorProperty( # type: ignore - update=constrain_max_closure(float_3d_str) - ) - - # --------------------- - # float 4d - float_4d_str = "float_4d" - min_float_4d: bpy.props.FloatVectorProperty( # type: ignore - size=4, - update=constrain_min_closure(float_4d_str), - ) - max_float_4d: bpy.props.FloatVectorProperty( # type: ignore - size=4, update=constrain_max_closure(float_4d_str) - ) - - # --------------------- - # rgba - rgba_4d_str = "rgba_4d" - min_rgba_4d: bpy.props.FloatVectorProperty( # type: ignore - size=4, - update=constrain_rgba_closure("min_" + rgba_4d_str), # noqa - ) - max_rgba_4d: bpy.props.FloatVectorProperty( # type: ignore - size=4, update=constrain_rgba_closure("max_" + rgba_4d_str) # noqa - ) - - # --------------------- - # randomisation toggle - bool_randomise: bpy.props.BoolProperty() # type: ignore - - -# Register / unregister -def register(): - bpy.utils.register_class(CustomProperties) - - -def unregister(): - bpy.utils.unregister_class(CustomProperties) diff --git a/randomiser/custom_props/operators.py b/randomiser/custom_props/operators.py deleted file mode 100644 index 7801437..0000000 --- a/randomiser/custom_props/operators.py +++ /dev/null @@ -1,317 +0,0 @@ -from random import uniform - -import bpy -from bpy.app.handlers import persistent - -from .. import utils - - -# ------------------------------- -# Operator -# ------------------------------- -class AddCustomPropTolist(bpy.types.Operator): - # docstring shows as a tooltip for menu items and buttons. - """Randomise the position and orientation of the camera - - Parameters - ---------- - bpy : _type_ - _description_ - - Returns - ------- - _type_ - _description_ - """ - - bl_idname = "opr.add_custom_prop_to_list" # appended to bpy.ops. - bl_label = "Add custom prop to list" - - bl_options = {"REGISTER", "UNDO"} - - # @classmethod - # def poll(cls, context): - # # check the context here - # return context.object is not None - - def execute(self, context): - """Execute the randomiser operator - - Randomise the position and rotation x,y,z components - of the camera between their min and max values. - - Parameters - ---------- - context : _type_ - _description_ - - Returns - ------- - _type_ - _description_ - """ - - utils.add_custom_prop_to_custom_list(context) - - return {"FINISHED"} - - -class ApplyRandomCustom(bpy.types.Operator): - # docstring shows as a tooltip for menu items and buttons. - """Randomise the position and orientation of the camera - - Parameters - ---------- - bpy : _type_ - _description_ - - Returns - ------- - _type_ - _description_ - """ - - bl_idname = "opr.apply_random_custom_prop" # appended to bpy.ops. - bl_label = "Apply random to custom prop" - - bl_options = {"REGISTER", "UNDO"} - - @classmethod - def poll(cls, context): - # check the context here - return context.object is not None - - def execute(self, context): - """Execute the randomiser operator - - Randomise the position and rotation x,y,z components - of the camera between their min and max values. - - Parameters - ---------- - context : _type_ - _description_ - - Returns - ------- - _type_ - _description_ - """ - - cust_min = context.scene.custom_props.custom_min[0] - cust_max = context.scene.custom_props.custom_max[0] - cust_range = [cust_min, cust_max] - - rand_cust = context.scene.custom_props.bool_rand_cust - - custom_input = context.scene.custom_props.custom_input - custom_idx = context.scene.custom_props.custom_idx - - randomise_selected( - context, - cust_range, - rand_cust, - custom_input, - custom_idx, - ) - - return {"FINISHED"} - - # def invoke(self, context, event): - # return context.window_manager.invoke_props_dialog(self) - - -@persistent -def randomise_custom_per_frame(dummy): - bpy.ops.opr.apply_random_custom_prop("INVOKE_DEFAULT") - - return - - -# -------------------------------------------------- -# Randomise_selected function: - - -def randomise_selected( - context, cust_range, rand_cust, custom_input, custom_idx -): - """Generate random numbers between the range for x/y/z - directions in location and rotation - - Parameters - ---------- - bpy : _type_ - _description_ - - Returns - ------- - _type_ - _description_ - """ - - if rand_cust: - custom_input.split(".") - # attr_string = list_string[len(list_string)-1] - # obj_string = custom_input.rsplit(".",1)[0] - - getattr(context.active_object, custom_input)[custom_idx] = uniform( - cust_range[0], cust_range[1] - ) - - else: # otherwise the values change under us - uniform(0.0, 0.0), uniform(0.0, 0.0), uniform(0.0, 0.0) - - -class CUSTOM_OT_actions(bpy.types.Operator): - """Move items up and down, add and remove""" - - bl_idname = "custom.list_action" - bl_label = "List Actions" - bl_description = "Move items up and down, add and remove" - bl_options = {"REGISTER"} - - action_prop = bpy.props.EnumProperty( - items=( - ("UP", "Up", ""), - ("DOWN", "Down", ""), - ("REMOVE", "Remove", ""), - ("ADD", "Add", ""), - ) - ) - action: action_prop # type: ignore - - def invoke(self, context, event): - scn = context.scene - idx = scn.custom_index - - try: - item = scn.custom[idx] - except IndexError: - pass - else: - if self.action == "DOWN" and idx < len(scn.custom) - 1: - scn.custom[idx + 1].name - scn.custom.move(idx, idx + 1) - scn.custom_index += 1 - info = 'Item "%s" moved to position %d' % ( - item.name, - scn.custom_index + 1, - ) - self.report({"INFO"}, info) - - elif self.action == "UP" and idx >= 1: - scn.custom[idx - 1].name - scn.custom.move(idx, idx - 1) - scn.custom_index -= 1 - info = 'Item "%s" moved to position %d' % ( - item.name, - scn.custom_index + 1, - ) - self.report({"INFO"}, info) - - elif self.action == "REMOVE": - info = 'Item "%s" removed from list' % (scn.custom[idx].name) - scn.custom_index -= 1 - scn.custom.remove(idx) - self.report({"INFO"}, info) - - if self.action == "ADD": - item = scn.custom.add() - item.name = "Your Name" - item.id = len(scn.custom) - scn.custom_index = len(scn.custom) - 1 - info = '"%s" added to list' % (item.name) - self.report({"INFO"}, info) - return {"FINISHED"} - - -class CUSTOM_OT_printItems(bpy.types.Operator): - """Print all items and their properties to the console""" - - bl_idname = "custom.print_items" - bl_label = "Print Items to Console" - bl_description = "Print all items and their properties to the console" - bl_options = {"REGISTER", "UNDO"} - - reverse_order_prop = bpy.props.BoolProperty( - default=False, name="Reverse Order" - ) - - reverse_order: reverse_order_prop # type: ignore - - @classmethod - def poll(cls, context): - return bool(context.scene.custom) - - def execute(self, context): - scn = context.scene - if self.reverse_order: - for i in range(scn.custom_index, -1, -1): - item = scn.custom[i] - print("Name:", item.name, "-", "ID:", item.id) - else: - for item in scn.custom: - print("Name:", item.name, "-", "ID", item.id) - return {"FINISHED"} - - -class CUSTOM_OT_clearList(bpy.types.Operator): - """Clear all items of the list""" - - bl_idname = "custom.clear_list" - bl_label = "Clear List" - bl_description = "Clear all items of the list" - bl_options = {"INTERNAL"} - - @classmethod - def poll(cls, context): - return bool(context.scene.custom) - - def invoke(self, context, event): - return context.window_manager.invoke_confirm(self, event) - - def execute(self, context): - if bool(context.scene.custom): - context.scene.custom.clear() - self.report({"INFO"}, "All items removed") - else: - self.report({"INFO"}, "Nothing to remove") - return {"FINISHED"} - - -# --------------------- -# Classes to register -# --------------------- -list_classes_to_register = [ - ApplyRandomCustom, - AddCustomPropTolist, - CUSTOM_OT_actions, - CUSTOM_OT_printItems, - CUSTOM_OT_clearList, -] - - -# ----------------------------------------- -# Register and unregister functions -# ------------------------------------------ -def register(): - """This is run when the add-on is enabled""" - - for cls in list_classes_to_register: - bpy.utils.register_class(cls) - - bpy.app.handlers.frame_change_pre.append(randomise_custom_per_frame) - - print("transform operators registered") - - -def unregister(): - """ - This is run when the add-on is disabled / Blender closes - """ - for cls in list_classes_to_register: - bpy.utils.unregister_class(cls) - - bpy.app.handlers.frame_change_pre.remove(randomise_custom_per_frame) - - print("transform operators unregistered") diff --git a/randomiser/custom_props/properties.py b/randomiser/custom_props/properties.py deleted file mode 100644 index d1797b2..0000000 --- a/randomiser/custom_props/properties.py +++ /dev/null @@ -1,196 +0,0 @@ -import bpy -import numpy as np - - -# ----------------------------------------- -# Bounds to PropertiesApplyRandomTransform -# ----------------------------------------- -def constrain_min_closure(m_str): - """Constain min value with closure - - Parameters - ---------- - m_str : str - string specifying the socket attribute (e.g., float_1d) - - Returns - ------- - _type_ - lambda function evaluated at the specified m_str - - """ - - def constrain_min(self, context, m_str): - """Constrain min value - - If min > max --> min is reset to max value - (i.e., no randomisation) - - Parameters - ---------- - context : _type_ - _description_ - m_str : str - string specifying the socket attribute (e.g., float_1d) - """ - # self is a 'PropertiesApplyRandomTransform' object - min_array = np.array(getattr(self, m_str + "_min")) - max_array = np.array(getattr(self, m_str + "_max")) - if any(min_array > max_array): - setattr( - self, - m_str + "_min", - np.where(min_array > max_array, max_array, min_array), - ) - return - - return lambda slf, ctx: constrain_min(slf, ctx, m_str) - - -def constrain_max_closure(m_str): - """Constain max value with closure - - Parameters - ---------- - m_str : str - string specifying the socket attribute (e.g., float_1d) - - Returns - ------- - _type_ - lambda function evaluated at the specified m_str - - """ - - def constrain_max(self, context, m_str): - """Constrain max value - - if max < min --> max is reset to min value - (i.e., no randomisation) - - Parameters - ---------- - context : _type_ - _description_ - m_str : str - string specifying the socket attribute (e.g., float_1d) - """ - # self is a 'SocketProperties' object - min_array = np.array(getattr(self, m_str + "_min")) - max_array = np.array(getattr(self, m_str + "_max")) - if any(max_array < min_array): - setattr( - self, - m_str + "_max", - np.where(max_array < min_array, min_array, max_array), - ) - return - - return lambda slf, ctx: constrain_max(slf, ctx, m_str) - - -# --------------------------- -# Properties -class PropertiesCustomTransform(bpy.types.PropertyGroup): - """ - Class holding the set of properties - for the camera position and rotation: - - min/max values for x/y/z component of position and rotation, and - - boolean for delta position and rotation - - boolean for setting seed value - - integer for the actual seed value - - """ - - # Position min and max values - custom_input_prop = bpy.props.StringProperty(name="enter text") - custom_input: custom_input_prop # type: ignore - custom_min: bpy.props.FloatVectorProperty( # type: ignore - size=1, - step=100, # update=constrain_min_closure(custom_input) - ) # type: ignore - custom_max: bpy.props.FloatVectorProperty( # type: ignore - size=1, - step=100, # update=constrain_max_closure(custom_input) - ) # type: ignore - custom_idx: bpy.props.IntProperty(default=0) # type: ignore - - # BOOL - bool_rand_cust: bpy.props.BoolProperty(default=True) # type: ignore - - -class PropertiesCustomList(bpy.types.PropertyGroup): - custom_string_prop = bpy.props.StringProperty(default="camera.location") - custom_string: custom_string_prop # type: ignore - - -custom_string_prop = bpy.props.StringProperty(default="camera.location") - - -class CUSTOM_colorCollection(bpy.types.PropertyGroup): - # name: StringProperty() -> Instantiated by default - id_prop = bpy.props.IntProperty() - id: id_prop # type: ignore - - -# -------------------------------------------------- -# Register and unregister functions: -list_classes_to_register = [ - PropertiesCustomTransform, - PropertiesCustomList, - CUSTOM_colorCollection, -] - -list_context_scene_attr = ["socket_type_to_attr"] - - -def register(): - """This is run when the add-on is enabled""" - - for cls in list_classes_to_register: - bpy.utils.register_class(cls) - - if cls == PropertiesCustomTransform: - bpy.types.Scene.custom_props = bpy.props.PointerProperty( - type=PropertiesCustomTransform - ) - - if cls == PropertiesCustomList: - bpy.types.Scene.custom_list = bpy.props.PointerProperty( - type=PropertiesCustomList - ) - - for attr, attr_val in zip( - list_context_scene_attr, - [custom_string_prop], - ): - setattr(bpy.types.Scene, attr, attr_val) - - # Custom scene properties - if cls == CUSTOM_colorCollection: - bpy.types.Scene.custom = bpy.props.CollectionProperty( - type=CUSTOM_colorCollection - ) - bpy.types.Scene.custom_index = bpy.props.IntProperty() - print("transform properties registered") - - -def unregister(): - """ - This is run when the add-on is disabled / Blender closes - """ - for cls in list_classes_to_register: - bpy.utils.unregister_class(cls) - - del bpy.types.Scene.custom_props - del bpy.types.Scene.custom_list - - # delete the custom properties linked to bpy.context.scene - for attr in list_context_scene_attr: - if hasattr(bpy.types.Scene, attr): - delattr(bpy.types.Scene, attr) - - del bpy.types.Scene.custom - del bpy.types.Scene.custom_index - - print("transform properties unregistered") diff --git a/randomiser/custom_props/ui.py b/randomiser/custom_props/ui.py deleted file mode 100644 index d67ec77..0000000 --- a/randomiser/custom_props/ui.py +++ /dev/null @@ -1,358 +0,0 @@ -import bpy - -from .. import utils - - -# ---------------------- -# Common sections -# --------------------- -class TemplatePanel(bpy.types.Panel): - bl_space_type = "NODE_EDITOR" - bl_region_type = "UI" - bl_category = "Randomiser" # this shows up as the tab name - - -# ---------------------- -# Main panel -# --------------------- - - -class CUSTOM_UL_items(bpy.types.UIList): - def draw_item( - self, - context, - layout, - data, - item, - icon, - active_data, - active_propname, - index, - ): - split = layout.split(factor=0.3) - split.label(text="Index: %d" % (index)) - custom_icon = "COLOR" - split.prop(item, "name", icon=custom_icon, emboss=False, text="") - - def invoke(self, context, event): - pass - - -class MainPanelRandomCustomProps(TemplatePanel, bpy.types.Panel): - """Class defining the panel for randomising - the camera transform - - """ - - bl_idname = "CUSTOM_PROPS_PT_mainpanel" - bl_label = "Randomise CUSTOM PROPS" - - def draw(self, context): - column = self.layout.column(align=True) - column.label( - text="Choose property to see available properties to randomise" - ) - column.prop(context.scene.custom_props, "custom_input", text="") - # column.operator("opr.add_custom_prop_to_list", - # text="Add to Custom List") - - layout = self.layout - scn = bpy.context.scene - - rows = 2 - row = self.layout.row() - row.template_list( - "CUSTOM_UL_items", - "", - scn, - "custom", - scn, - "custom_index", - rows=rows, - ) - - col = row.column(align=True) - col.operator( - "custom.list_action", icon="ZOOM_IN", text="" - ).action = "ADD" - col.operator( - "custom.list_action", icon="ZOOM_OUT", text="" - ).action = "REMOVE" - col.separator() - col.operator( - "custom.list_action", icon="TRIA_UP", text="" - ).action = "UP" - col.operator( - "custom.list_action", icon="TRIA_DOWN", text="" - ).action = "DOWN" - - row = layout.row() - col = row.column(align=True) - row = col.row(align=True) - row.operator( - "custom.print_items", icon="LINENUMBERS_ON" - ) # LINENUMBERS_OFF, ANIM - row = col.row(align=True) - row.operator("custom.clear_list", icon="X") - - -# --------------------------------------------------- -# Common layout for list of sockets to randomise -# ---------------------------------------------------- -def draw_sockets_list( - cs, - layout, - list_input_nodes, - sockets_props_collection, -): - # Define UI fields for every socket property - # NOTE: if I don't sort the input nodes, everytime one of the nodes is - # selected in the graph it moves to the bottom of the panel. - list_input_nodes_sorted = sorted(list_input_nodes, key=lambda x: x.name) - print(list_input_nodes_sorted) - for i_n, nd in enumerate(list_input_nodes_sorted): - row = layout.row() - - # if first node: add labels for - # name, min, max and randomisation toggle - if i_n == 0: - row_split = row.split() - col1 = row_split.column(align=True) - col2 = row_split.column(align=True) - col3 = row_split.column(align=True) - col4 = row_split.column(align=True) - col5 = row_split.column(align=True) - - col1.label(text=nd.name) - col2.label(text="") - col3.label(text="min") - col4.label(text="max") - col5.label(text="index") - - # # input node name - # col1.label(text=nd.name) - # col1.alignment = "CENTER" - - # # min label - # col3.alignment = "CENTER" - # col3.label(text="min") - - # # max label - # col4.alignment = "CENTER" - # col4.label(text="max") - - # if not first node: add just node name - else: - row.separator(factor=1.0) # add empty row before each node - row = layout.row() - - row.label(text=nd.name) - - # add sockets for this node in the subseq rows - for sckt in nd.outputs: - # split row in 5 columns - row = layout.row() - row_split = row.split() - col1 = row_split.column(align=True) - col2 = row_split.column(align=True) - col3 = row_split.column(align=True) - col4 = row_split.column(align=True) - col5 = row_split.column(align=True) - - # socket name - col1.alignment = "RIGHT" - col1.label(text=sckt.name) - - # socket current value - col2.prop( - sckt, - "default_value", - icon_only=True, - ) - col2.enabled = False # current value is not editable - - # socket min and max columns - socket_id = nd.name + "_" + sckt.name - if nd.id_data.name in bpy.data.node_groups: - socket_id = nd.id_data.name + "_" + socket_id - - # if socket is a color: format min/max as a color picker - # and an array (color picker doesn't include alpha value) - if type(sckt) == bpy.types.NodeSocketColor: - for m_str, col in zip(["min", "max"], [col3, col4]): - # color picker - col.template_color_picker( - sockets_props_collection[socket_id], - m_str + "_" + cs.socket_type_to_attr[type(sckt)], - ) - # array - for j, cl in enumerate(["R", "G", "B", "alpha"]): - col.prop( - sockets_props_collection[socket_id], - m_str + "_" + cs.socket_type_to_attr[type(sckt)], - icon_only=False, - text=cl, - index=j, - ) - # if socket is not color type: format as a regular property - else: - for m_str, col in zip(["min", "max"], [col3, col4]): - col.prop( - sockets_props_collection[socket_id], - m_str + "_" + cs.socket_type_to_attr[type(sckt)], - icon_only=True, - ) - - # randomisation toggle - col5.prop( - sockets_props_collection[socket_id], - "bool_randomise", - icon_only=True, - ) - - # def draw(self, context): - - # col1.prop(context.scene.custom_props, "custom_input", text="") - # # col1.enabled = False - - # # col1.label(context.scene.custom_props, "custom_input") - # # col1.enabled=True - - # col2.prop( - # context.scene.custom_props, - # "custom_min", - # icon_only=True, - # ) - - # col3.prop( - # context.scene.custom_props, - # "custom_max", - # icon_only=True, - # ) - - # col4.prop( - # context.scene.custom_props, - # "custom_idx", - # icon_only=True, - # ) - - # col5.prop( - # context.scene.custom_props, - # "bool_rand_cust", - # icon_only=True, - # ) - - # # Bool delta - # col = self.layout.column() - - # # Randomise button - # col.operator("opr.apply_random_custom_prop", text="Randomise") - - -# ------------------------------ -# Subpanel for each material -# ----------------------------- -class SubPanelRandomCustomProps(TemplatePanel, bpy.types.Panel): - """Class defining the panel for randomising - material node properties - - """ - - bl_idname = "CUSTOM_PROPS_PT_subpanel" - bl_parent_id = "CUSTOM_PROPS_PT_mainpanel" - bl_label = "" # title of the panel displayed to the user - # bl_options = {"DEFAULT_CLOSED"} - # https://docs.blender.org/api/master/bpy.types.Panel.html#bpy.types.Panel.bl_options - - @classmethod - def poll(cls, context): - cs = context.scene - - # force an update on the materials collection first - # the '.update_collection' attribute - # triggers the get function that checks if an update is - # required. If it is, the collection of sockets is updated - # and returns TRUE - if cs.socket_props_per_material.get_update_collection: - print("Collection of materials updated") - - # only display subpanels for which this is true - return cls.subpanel_custom_idx < len( - cs.socket_props_per_material.collection - ) - - def draw_header(self, context): - cs = context.scene - # TODO: maybe a dict? can order of materials change? - cs.socket_props_per_material.collection[self.subpanel_custom_idx] - - layout = self.layout - layout.use_property_split = True - layout.use_property_decorate = False # No animation. - - # # For now: view graph button on top of material name - # layout.operator( - # f"node.view_graph_for_material_{self.subpanel_custom_idx}", - # text=subpanel_custom.name, - # emboss=True, - # ) - - def draw(self, context): - # get name of the material for this subpanel - cs = context.scene - subpanel_custom = cs.socket_props_per_material.collection[ - self.subpanel_custom_idx - ] - - # then force an update in the sockets per material - # subpanel_material_name = subpanel_material.name - if cs.socket_props_per_material.collection[ - subpanel_custom.name - ].update_sockets_collection: - print("Collection of sockets updated") - - # get (updated) collection of socket properties - # for the current material - sockets_props_collection = cs.socket_props_per_material.collection[ - subpanel_custom.name - ].collection - - # Get list of input nodes to randomise - # for this subpanel's material - list_input_nodes = utils.get_custom_props_to_randomise_indep( - subpanel_custom.name - ) - - draw_sockets_list( - cs, - self.layout, - list_input_nodes, - sockets_props_collection, - ) - - -# -------------------------------------------------- -# Register and unregister functions: -list_classes_to_register = [ - MainPanelRandomCustomProps, - CUSTOM_UL_items, -] - - -def register(): - """This is run when the add-on is enabled""" - - for cls in list_classes_to_register: - bpy.utils.register_class(cls) - - print("custom UI registered") - - -def unregister(): - """ - This is run when the add-on is disabled / Blender closes - """ - for cls in list_classes_to_register: - bpy.utils.unregister_class(cls) - - print("custom UI unregistered") From 8fc3febd6440cf942e0e1081a00832754ab6b665 Mon Sep 17 00:00:00 2001 From: ruaridhg Date: Thu, 8 Jun 2023 13:13:21 +0100 Subject: [PATCH 17/58] custom + custom_idx in properties.py works but needs moving into other property_classes --- randomiser/define_prop/operators.py | 19 ------ randomiser/define_prop/properties.py | 63 ------------------- .../collection_UD_socket_properties.py | 1 + 3 files changed, 1 insertion(+), 82 deletions(-) diff --git a/randomiser/define_prop/operators.py b/randomiser/define_prop/operators.py index 195d48e..c4fbf9e 100644 --- a/randomiser/define_prop/operators.py +++ b/randomiser/define_prop/operators.py @@ -3,8 +3,6 @@ import bpy import numpy as np -from .. import config - class CUSTOM_OT_actions(bpy.types.Operator): """Move items up and down, add and remove""" @@ -355,23 +353,6 @@ def execute(self, context): CUSTOM_OT_clearList, ] -#####REFACTOR for UIlist - remove reliance on ViewNodeGraphOneGNG -# # or is this needed for displaying subpanel? -for i in range(config.MAX_NUMBER_OF_SUBPANELS): - operator_i = type( - f"ViewNodeGraphOneGNG_subpanel_{i}", - ( - # ViewNodeGraphOneGNG, - bpy.types.Operator, - ), - { - "bl_idname": f"node.view_graph_for_gng_{i}", - "bl_label": "", - "subpanel_gng_idx": i, - }, - ) - list_classes_to_register.append(operator_i) # type: ignore - # ----------------------------------------- # Register and unregister functions diff --git a/randomiser/define_prop/properties.py b/randomiser/define_prop/properties.py index 2d8d0b9..5c12cf1 100644 --- a/randomiser/define_prop/properties.py +++ b/randomiser/define_prop/properties.py @@ -5,43 +5,6 @@ collection_UD_socket_properties, ) -# --------------------------- -# Properties -# class PropertiesCustomTransform(bpy.types.PropertyGroup): -# """ -# Class holding the set of properties -# for the camera position and rotation: -# - min/max values for x/y/z component of position and rotation, and -# - boolean for delta position and rotation -# - boolean for setting seed value -# - integer for the actual seed value - -# """ - -# # Position min and max values -# custom_input_prop = bpy.props.StringProperty(name="enter text") -# custom_input: custom_input_prop # type: ignore -# custom_min: bpy.props.FloatVectorProperty( # type: ignore -# size=1, -# step=100, # update=constrain_min_closure(custom_input) -# ) # type: ignore -# custom_max: bpy.props.FloatVectorProperty( # type: ignore -# size=1, -# step=100, # update=constrain_max_closure(custom_input) -# ) # type: ignore -# custom_idx: bpy.props.IntProperty(default=0) # type: ignore - -# # BOOL -# bool_rand_cust: bpy.props.BoolProperty(default=True) # type: ignore - - -class PropertiesCustomList(bpy.types.PropertyGroup): - custom_string_prop = bpy.props.StringProperty(default="camera.location") - custom_string: custom_string_prop # type: ignore - - -custom_string_prop = bpy.props.StringProperty(default="camera.location") - class CUSTOM_colorCollection(bpy.types.PropertyGroup): # name: StringProperty() -> Instantiated by default @@ -52,8 +15,6 @@ class CUSTOM_colorCollection(bpy.types.PropertyGroup): # -------------------------------------------------- # Register and unregister functions: list_classes_to_register = [ - # PropertiesCustomTransform, - PropertiesCustomList, CUSTOM_colorCollection, ] @@ -67,22 +28,6 @@ def register(): for cls in list_classes_to_register: bpy.utils.register_class(cls) - # if cls == PropertiesCustomTransform: - # bpy.types.Scene.custom_props = bpy.props.PointerProperty( - # type=PropertiesCustomTransform - # ) - - if cls == PropertiesCustomList: - bpy.types.Scene.custom_list = bpy.props.PointerProperty( - type=PropertiesCustomList - ) - - for attr, attr_val in zip( - list_context_scene_attr, - [custom_string_prop], - ): - setattr(bpy.types.Scene, attr, attr_val) - # Custom scene properties if cls == CUSTOM_colorCollection: bpy.types.Scene.custom = bpy.props.CollectionProperty( @@ -99,14 +44,6 @@ def unregister(): for cls in list_classes_to_register: bpy.utils.unregister_class(cls) - # del bpy.types.Scene.custom_props - del bpy.types.Scene.custom_list - - # delete the custom properties linked to bpy.context.scene - for attr in list_context_scene_attr: - if hasattr(bpy.types.Scene, attr): - delattr(bpy.types.Scene, attr) - del bpy.types.Scene.custom del bpy.types.Scene.custom_index diff --git a/randomiser/define_prop/property_classes/collection_UD_socket_properties.py b/randomiser/define_prop/property_classes/collection_UD_socket_properties.py index 1cd0250..ac1fcc2 100644 --- a/randomiser/define_prop/property_classes/collection_UD_socket_properties.py +++ b/randomiser/define_prop/property_classes/collection_UD_socket_properties.py @@ -24,6 +24,7 @@ def compute_UD_sockets_sets(self): self.set_sckt_names_in_collection_of_props = set( sck_p.name for sck_p in self.collection ) + # pdb.set_trace() #####REFACTOR TO WORK WITH UI LIST/REMOVE # since don't need graphs for custom props? # set of sockets in graph for this GNG From bc4475a3d6e57867f278d5275f97b9b0b17890fb Mon Sep 17 00:00:00 2001 From: ruaridhg Date: Fri, 9 Jun 2023 07:45:14 +0100 Subject: [PATCH 18/58] changed UD props collection from UD socket properties to materials.socket --- randomiser/define_prop/operators.py | 4 +- .../property_classes/collection_UD_props.py | 11 +- randomiser/define_prop/ui.py | 159 +++++++++--------- 3 files changed, 84 insertions(+), 90 deletions(-) diff --git a/randomiser/define_prop/operators.py b/randomiser/define_prop/operators.py index c4fbf9e..13677c7 100644 --- a/randomiser/define_prop/operators.py +++ b/randomiser/define_prop/operators.py @@ -167,8 +167,6 @@ def poll(cls, context): return len(context.scene.socket_props_per_UD.collection) > 0 - ##### scene no attribute socket per UD - def invoke(self, context, event): """Initialise parmeters before executing the operator @@ -340,7 +338,7 @@ def execute(self, context): # return -##### Graph function removed - not needed? +# Graph function removed - not needed? # --------------------- diff --git a/randomiser/define_prop/property_classes/collection_UD_props.py b/randomiser/define_prop/property_classes/collection_UD_props.py index f497479..b75eb19 100644 --- a/randomiser/define_prop/property_classes/collection_UD_props.py +++ b/randomiser/define_prop/property_classes/collection_UD_props.py @@ -1,6 +1,6 @@ import bpy -from .collection_UD_socket_properties import ColUDSocketProperties +from ...material.property_classes.socket_properties import SocketProperties # --------------------------------------------------- @@ -116,11 +116,14 @@ class ColUDParentProps(bpy.types.PropertyGroup): _description_ """ - # collection of [collections of socket properties] (one per node group) + # # collection of [collections of socket properties] (one per node group) + # collection: bpy.props.CollectionProperty( # type: ignore + # type=ColUDSocketProperties # elements in the collection + # ) + collection: bpy.props.CollectionProperty( # type: ignore - type=ColUDSocketProperties # elements in the collection + type=SocketProperties ) - # autopopulate collection of geometry node groups update_UD_props_collection: bpy.props.BoolProperty( # type: ignore default=False, diff --git a/randomiser/define_prop/ui.py b/randomiser/define_prop/ui.py index 1919bb0..86af36e 100644 --- a/randomiser/define_prop/ui.py +++ b/randomiser/define_prop/ui.py @@ -78,78 +78,75 @@ def draw_sockets_list_UD( row.label(text=UD.name) - # add sockets for this node in the subseq rows - for sckt in UD.outputs: - # split row in 5 columns - row = layout.row() - row_split = row.split() - col1 = row_split.column(align=True) - col2 = row_split.column(align=True) - col3 = row_split.column(align=True) - col4 = row_split.column(align=True) - col5 = row_split.column(align=True) - - # socket name - col1.alignment = "RIGHT" - col1.label(text=sckt.name) - - # socket current value - col2.prop( - sckt, - "default_value", - icon_only=True, - ) - col2.enabled = False # current value is not editable - - # socket min and max columns - socket_id = UD.name + "_" + sckt.name - if (UD.id_data.name in bpy.data.node_groups) and ( - bpy.data.node_groups[UD.id_data.name].type != "GEOMETRY" - ): # only for SHADER groups - socket_id = UD.id_data.name + "_" + socket_id - - # if socket is a color: format min/max as a color picker - # and an array (color picker doesn't include alpha value) - if type(sckt) == bpy.types.NodeSocketColor: - for m_str, col in zip(["min", "max"], [col3, col4]): - # color picker - col.template_color_picker( - sockets_props_collection[socket_id], - m_str + "_" + cs.socket_type_to_attr[type(sckt)], - ) - # array - for j, cl in enumerate(["R", "G", "B", "alpha"]): - col.prop( - sockets_props_collection[socket_id], - m_str + "_" + cs.socket_type_to_attr[type(sckt)], - icon_only=False, - text=cl, - index=j, - ) - # if socket is Boolean: add non-editable labels - elif type(sckt) == bpy.types.NodeSocketBool: - for m_str, col in zip(["min", "max"], [col3, col4]): - m_val = getattr( - sockets_props_collection[socket_id], - m_str + "_" + cs.socket_type_to_attr[type(sckt)], - ) - col.label(text=str(list(m_val)[0])) - - # if socket is not color type: format as a regular property - else: - for m_str, col in zip(["min", "max"], [col3, col4]): - col.prop( - sockets_props_collection[socket_id], - m_str + "_" + cs.socket_type_to_attr[type(sckt)], - icon_only=True, - ) - - # randomisation toggle - col5.prop( - sockets_props_collection[socket_id], - "bool_randomise", - icon_only=True, - ) + row = layout.row() + row_split = row.split() + col1 = row_split.column(align=True) + col2 = row_split.column(align=True) + col3 = row_split.column(align=True) + col4 = row_split.column(align=True) + row_split.column(align=True) + + # UD prop name + col1.alignment = "RIGHT" + col1.label(text="value") # text=sckt.name) + + # socket current value + col2.prop( + UD, + "default_value", + icon_only=True, + ) + col2.enabled = False # current value is not editable + + # # socket min and max columns + # socket_id = UD.name + "_" + sckt.name + # if (UD.id_data.name in bpy.data.node_groups) and ( + # bpy.data.node_groups[UD.id_data.name].type != "GEOMETRY" + # ): # only for SHADER groups + # socket_id = UD.id_data.name + "_" + socket_id + + # # if socket is a color: format min/max as a color picker + # # and an array (color picker doesn't include alpha value) + # if type(sckt) == bpy.types.NodeSocketColor: + # for m_str, col in zip(["min", "max"], [col3, col4]): + # # color picker + # col.template_color_picker( + # sockets_props_collection[socket_id], + # m_str + "_" + cs.socket_type_to_attr[type(sckt)], + # ) + # # array + # for j, cl in enumerate(["R", "G", "B", "alpha"]): + # col.prop( + # sockets_props_collection[socket_id], + # m_str + "_" + cs.socket_type_to_attr[type(sckt)], + # icon_only=False, + # text=cl, + # index=j, + # ) + # # if socket is Boolean: add non-editable labels + # elif type(sckt) == bpy.types.NodeSocketBool: + # for m_str, col in zip(["min", "max"], [col3, col4]): + # m_val = getattr( + # sockets_props_collection[socket_id], + # m_str + "_" + cs.socket_type_to_attr[type(sckt)], + # ) + # col.label(text=str(list(m_val)[0])) + + # # if socket is not color type: format as a regular property + # else: + # for m_str, col in zip(["min", "max"], [col3, col4]): + # col.prop( + # sockets_props_collection[socket_id], + # m_str + "_" + cs.socket_type_to_attr[type(sckt)], + # icon_only=True, + # ) + + # # randomisation toggle + # col5.prop( + # sockets_props_collection[socket_id], + # "bool_randomise", + # icon_only=True, + # ) # ---------------------- @@ -341,10 +338,6 @@ def poll(cls, context): # force an update on the group nodes collection first if cs.socket_props_per_UD.update_UD_props_collection: print("Collection of UD props updated") - ##### scene no attribute socket per UD - - # print(cls.subpanel_UD_idx) - # print(cs.socket_props_per_UD.collection) return cls.subpanel_UD_idx < len( cs.socket_props_per_UD.collection @@ -370,7 +363,6 @@ def draw_header( # get this subpanel's GNG cs.socket_props_per_UD.collection[self.subpanel_UD_idx] - ##### scene no attribute socket per UD # add view graph operator to layout layout = self.layout @@ -380,7 +372,7 @@ def draw_header( # f"node.view_graph_for_gng_{self.subpanel_UD_prop_idx}", # text=subpanel_UD_prop.name, # emboss=True, - # ) #####operator defined once node.view_graph_for_gng + # ) #operator defined once node.view_graph_for_gng # # - not needed for custom props? def draw(self, context): @@ -410,16 +402,17 @@ def draw(self, context): ].collection # Get list of input nodes to randomise for this subpanel's GNG - list_parent_UD_str = [ - sckt.name.split("_")[0] for sckt in sockets_props_collection - ] + [sckt.name.split("_")[0] for sckt in sockets_props_collection] list_UD_props = [ - bpy.context.scene.custom[UD_str] - for UD_str in list_parent_UD_str + UD_str + for UD_str in bpy.context.scene.custom + # bpy.context.scene.custom[UD_str] + # for UD_str in list_parent_UD_str # bpy.data.node_groups[subpanel_gng.name].nodes[nd_str] # for nd_str in list_parent_nodes_str ] + print("list_UD_props =======", list_UD_props) # Draw sockets to randomise per input node, including their # current value and min/max boundaries From f01eb36832dfe9bb891800e9b4d7de648ec71b43 Mon Sep 17 00:00:00 2001 From: ruaridhg Date: Fri, 9 Jun 2023 11:53:18 +0100 Subject: [PATCH 19/58] UD property can appear in subplanel when hardcoded camera.location --- randomiser/config.py | 4 + randomiser/define_prop/operators.py | 4 +- randomiser/define_prop/properties.py | 17 +- .../property_classes/collection_UD_props.py | 19 +- .../collection_UD_socket_properties.py | 314 +++++++++++++----- randomiser/define_prop/ui.py | 215 ++++++++---- 6 files changed, 429 insertions(+), 144 deletions(-) diff --git a/randomiser/config.py b/randomiser/config.py index 8c8f632..db229a1 100644 --- a/randomiser/config.py +++ b/randomiser/config.py @@ -38,6 +38,10 @@ bpy.types.NodeSocketBool: "bool_1d", } +MAP_PROPS_TO_ATTR = { + bpy.props.FloatVectorProperty: "float_3d", +} + # NOTE: if the property is a float vector of size (1,n) # the initial min/max values specified here apply to all n dimensions # TODO: should we change this to allow different values per dimension? diff --git a/randomiser/define_prop/operators.py b/randomiser/define_prop/operators.py index 13677c7..7aa0c01 100644 --- a/randomiser/define_prop/operators.py +++ b/randomiser/define_prop/operators.py @@ -59,7 +59,7 @@ def invoke(self, context, event): if self.action == "ADD": item = scn.custom.add() - item.name = "Your Name" + item.name = "bpy.context.scene.camera.location" item.id = len(scn.custom) scn.custom_index = len(scn.custom) - 1 info = '"%s" added to list' % (item.name) @@ -197,7 +197,7 @@ def invoke(self, context, event): UD.name for UD in cs.socket_props_per_UD.collection ] - # for every GNG: save sockets to randomise + # for every GNG:f save sockets to randomise self.sockets_to_randomise_per_UD = {} for UD_str in self.list_subpanel_UD_props_names: # get collection of socket properties for this GNG diff --git a/randomiser/define_prop/properties.py b/randomiser/define_prop/properties.py index 5c12cf1..ca1f1e7 100644 --- a/randomiser/define_prop/properties.py +++ b/randomiser/define_prop/properties.py @@ -1,5 +1,6 @@ import bpy +from .. import config from .property_classes import ( collection_UD_props, collection_UD_socket_properties, @@ -18,13 +19,22 @@ class CUSTOM_colorCollection(bpy.types.PropertyGroup): CUSTOM_colorCollection, ] -list_context_scene_attr = ["socket_type_to_attr"] +dict_context_scene_attr = { + "UD_prop_to_attr": config.MAP_PROPS_TO_ATTR, + "socket_type_to_ini_min_max": config.MAP_SOCKET_TYPE_TO_INI_MIN_MAX, +} +##### Not needed? bpy.props.BoolProperty directly def register(): collection_UD_socket_properties.register() collection_UD_props.register() + # link global Python variables to bpy.context.scene + # if I use setattr: attribute must exist first right? + for attr_ky, attr_val in dict_context_scene_attr.items(): + setattr(bpy.types.Scene, attr_ky, attr_val) + for cls in list_classes_to_register: bpy.utils.register_class(cls) @@ -47,4 +57,9 @@ def unregister(): del bpy.types.Scene.custom del bpy.types.Scene.custom_index + # delete the custom properties linked to bpy.context.scene + for attr_ky in dict_context_scene_attr.keys(): + if hasattr(bpy.types.Scene, attr_ky): + delattr(bpy.types.Scene, attr_ky) + print("UD properties unregistered") diff --git a/randomiser/define_prop/property_classes/collection_UD_props.py b/randomiser/define_prop/property_classes/collection_UD_props.py index b75eb19..5bd1954 100644 --- a/randomiser/define_prop/property_classes/collection_UD_props.py +++ b/randomiser/define_prop/property_classes/collection_UD_props.py @@ -1,6 +1,6 @@ import bpy -from ...material.property_classes.socket_properties import SocketProperties +from ..property_classes.collection_UD_socket_properties import SocketProperties # --------------------------------------------------- @@ -131,6 +131,9 @@ class ColUDParentProps(bpy.types.PropertyGroup): set=set_update_UD_props_collection, ) + ##### CHECK IF VALUE IS PROPERTY HERE FIRST + # - make sure working for correct string first + # candidate geometry node groups @property def candidate_UD_props(self): # getter method @@ -143,12 +146,24 @@ def candidate_UD_props(self): # getter method _description_ """ # self is the collection of node groups - ##### REFACTOR for UIlist list_UD_props = [ UD for UD in bpy.context.scene.custom + # nd + # for nd in bpy.data.node_groups # if nd.type == "GEOMETRY" + # and ( + # any( + # [ + # ni.name.lower().startswith( + # config.DEFAULT_RANDOM_KEYWORD + # ) + # for ni in nd.nodes + # ] + # ) + # ) ] + # print("type list_UD_props ========== ", type(list_UD_props[0])) # # sort by name # list_node_groups = sorted( # list_materials, diff --git a/randomiser/define_prop/property_classes/collection_UD_socket_properties.py b/randomiser/define_prop/property_classes/collection_UD_socket_properties.py index ac1fcc2..3921e6e 100644 --- a/randomiser/define_prop/property_classes/collection_UD_socket_properties.py +++ b/randomiser/define_prop/property_classes/collection_UD_socket_properties.py @@ -1,14 +1,14 @@ import re import bpy - -from ... import utils -from ...material.property_classes.socket_properties import SocketProperties - +import numpy as np # ----------------------------------------------------------------- # Setter / getter methods for update_sockets_collection attribute # ---------------------------------------------------------------- + + +##### REFACTOR def compute_UD_sockets_sets(self): """Compute the relevant sets of sockets for this specific user defined property, and add them to self. @@ -155,92 +155,252 @@ def set_update_collection(self, value): ) -# ---------------------------------------------- -# ColUDSocketProperties -# ---------------------------------------------- -class ColUDSocketProperties(bpy.types.PropertyGroup): - """Class holding the collection of socket properties from - geometry nodes and a boolean property to update the - collection if required (for example, if new nodes are added) +# ----------------------------------------- +# Bounds to SocketProperties +# ----------------------------------------- + + +def constrain_min_closure(m_str): + """Constain min value with closure - NOTE: we use the update_sockets_collection property as an - auxiliary property because the CollectionProperty has no update function - https://docs.blender.org/api/current/bpy.props.html#update-example + Parameters + ---------- + m_str : str + string specifying the socket attribute (e.g., float_1d) + + Returns + ------- + _type_ + lambda function evaluated at the specified m_str """ - # name of the geometry node group (GNG) + def constrain_min(self, context, m_str): + """Constrain min value + + If min > max --> min is reset to max value + (i.e., no randomisation) + + Parameters + ---------- + context : _type_ + _description_ + m_str : str + string specifying the socket attribute (e.g., float_1d) + """ + # self is a 'SocketProperties' object + min_array = np.array(getattr(self, "min_" + m_str)) + max_array = np.array(getattr(self, "max_" + m_str)) + if any(min_array > max_array): + setattr( + self, + "min_" + m_str, + np.where(min_array > max_array, max_array, min_array), + ) + return + + return lambda slf, ctx: constrain_min(slf, ctx, m_str) + + +def constrain_max_closure(m_str): + """Constain max value with closure + + Parameters + ---------- + m_str : str + string specifying the socket attribute (e.g., float_1d) + + Returns + ------- + _type_ + lambda function evaluated at the specified m_str + + """ + + def constrain_max(self, context, m_str): + """Constrain max value + + if max < min --> max is reset to min value + (i.e., no randomisation) + + Parameters + ---------- + context : _type_ + _description_ + m_str : str + string specifying the socket attribute (e.g., float_1d) + """ + # self is a 'SocketProperties' object + min_array = np.array(getattr(self, "min_" + m_str)) + max_array = np.array(getattr(self, "max_" + m_str)) + if any(max_array < min_array): + setattr( + self, + "max_" + m_str, + np.where(max_array < min_array, min_array, max_array), + ) + return + + return lambda slf, ctx: constrain_max(slf, ctx, m_str) + + +def constrain_rgba_closure(m_str): + """Constain RGBA value with closure + + Parameters + ---------- + m_str : str + string specifying the socket attribute (e.g., float_1d) + + Returns + ------- + _type_ + lambda function evaluated at the specified m_str + + """ + + def constrain_rgba(self, context, min_or_max_full_str): + """Constrain RGBA value + + if RGBA socket: constrain values to be between 0 and 1 + + Parameters + ---------- + context : _type_ + _description_ + m_str : str + string specifying the socket attribute (e.g., float_1d) + """ + min_or_max_array = np.array(getattr(self, min_or_max_full_str)) + if any(min_or_max_array > 1.0) or any(min_or_max_array < 0.0): + setattr( + self, + min_or_max_full_str, + np.clip(min_or_max_array, 0.0, 1.0), + ) + return + + return lambda slf, ctx: constrain_rgba(slf, ctx, m_str) + + +# ----------------------- +# SocketProperties +# --------------------- +class SocketProperties(bpy.types.PropertyGroup): + """ + Class holding the set of properties + for a socket, namely: + - socket name, + - min/max values, and + - boolean for randomisation + + Because I think it is not possible to define attributes dynamically, + for now we define an attribute for each possible socket type + in the input nodes. These are all FloatVectors of different sizes. + The size is specified in the attribute's name: + - min/max_float_1d + - min/max_float_3d + - min/max_float_4d + - min/max_rgba_4d + + """ + + # TODO: how to set attributes dynamically? + # TODO: I don't really get why this type definition is also an assignment? + + # # collection of socket properties for this GNG + # collection: bpy.props.CollectionProperty() # type: ignore + + # # helper attribute to update collection of socket properties + # update_sockets_collection: bpy.props.BoolProperty( # type: ignore + # default=False, + # get=get_update_collection, + # set=set_update_collection, + # ) + + # --------------------- + # name of the socket + # NOTE: if we make a Blender collection of this type of objects, + # we will be able to access them by name name: bpy.props.StringProperty() # type: ignore - # collection of socket properties for this GNG - collection: bpy.props.CollectionProperty( # type: ignore - type=SocketProperties + # TODO: include the socket itself here to? + # socket: PointerProperty(type=bpy.types.NodeSocketStandard?) + + # --------------------- + # float 1d + float_1d_str = "float_1d" + min_float_1d: bpy.props.FloatVectorProperty( # type: ignore + size=1, update=constrain_min_closure(float_1d_str) ) - # helper attribute to update collection of socket properties - update_sockets_collection: bpy.props.BoolProperty( # type: ignore - default=False, - get=get_update_collection, - set=set_update_collection, + max_float_1d: bpy.props.FloatVectorProperty( # type: ignore + size=1, update=constrain_max_closure(float_1d_str) ) - # candidate sockets for this GNG - @property - def candidate_sockets(self): # getter method - """Getter function for the candidate_sockets property - - We define candidate sockets as the set of output sockets - in input nodes, in the graph for the currently active - node group. Input nodes are nodes with only output sockets - (i.e., no input sockets). - - It returns a list of sockets that are candidates for - the randomisation. - - These are the output sockets of input nodes, excluding: - - sockets of type NodeSocketGeometry (input/output nodes) - - sockets that receive one of the available materials - (NodeSocketMaterial) - - sockets that receive one of the available objects - (NodeSocketObject) - - sockets that input a string - (NodeSocketString) - This is becase these sockets cannot be randomised between - min and max values - - Returns - ------- - list - list of sockets in the input nodes in the graph - """ - # get list of input nodes for this geometry node group (GNG) - list_input_nodes = utils.get_UD_sockets_to_randomise_from_list( - self.name - ) #####need to get this list from UI - - # get list of sockets that are candidate for randomisation - list_sockets = [ - nd - for nd in list_input_nodes - # for out in nd.outputs - # if type(out) - # not in [ - # bpy.types.NodeSocketGeometry, - # bpy.types.NodeSocketMaterial, - # bpy.types.NodeSocketObject, - # bpy.types.NodeSocketString, - # ] - ] #####refactor to sockets without input nodes - - return list_sockets + # --------------------- + # float 3d + float_3d_str = "float_3d" + min_float_3d: bpy.props.FloatVectorProperty( # type: ignore + update=constrain_min_closure(float_3d_str) + ) + max_float_3d: bpy.props.FloatVectorProperty( # type: ignore + update=constrain_max_closure(float_3d_str) + ) + # --------------------- + # float 4d + float_4d_str = "float_4d" + min_float_4d: bpy.props.FloatVectorProperty( # type: ignore + size=4, + update=constrain_min_closure(float_4d_str), + ) + max_float_4d: bpy.props.FloatVectorProperty( # type: ignore + size=4, update=constrain_max_closure(float_4d_str) + ) -# ----------------------------------------- -# Register and unregister functions -# ------------------------------------------ + # --------------------- + # rgba + rgba_4d_str = "rgba_4d" + min_rgba_4d: bpy.props.FloatVectorProperty( # type: ignore + size=4, + update=constrain_rgba_closure("min_" + rgba_4d_str), # noqa + ) + max_rgba_4d: bpy.props.FloatVectorProperty( # type: ignore + size=4, update=constrain_rgba_closure("max_" + rgba_4d_str) # noqa + ) + + # ---------------------------- + # int_1d + int_1d_str = "int_1d" + min_int_1d: bpy.props.IntVectorProperty( # type: ignore + size=1, update=constrain_min_closure(int_1d_str) + ) + + max_int_1d: bpy.props.IntVectorProperty( # type: ignore + size=1, update=constrain_max_closure(int_1d_str) + ) + + # ---------------------------- + # bool_1d + # bool_1d_str = "bool_1d" + min_bool_1d: bpy.props.BoolVectorProperty( # type: ignore + size=1, + ) + + max_bool_1d: bpy.props.BoolVectorProperty( # type: ignore + size=1, + ) + + # --------------------- + # randomisation toggle + bool_randomise: bpy.props.BoolProperty() # type: ignore + + +# Register / unregister def register(): - bpy.utils.register_class(ColUDSocketProperties) + bpy.utils.register_class(SocketProperties) def unregister(): - bpy.utils.unregister_class(ColUDSocketProperties) + bpy.utils.unregister_class(SocketProperties) diff --git a/randomiser/define_prop/ui.py b/randomiser/define_prop/ui.py index 86af36e..053cd65 100644 --- a/randomiser/define_prop/ui.py +++ b/randomiser/define_prop/ui.py @@ -40,6 +40,7 @@ def draw_sockets_list_UD( layout, list_UD_props, sockets_props_collection, + list_parents_node_str, ): # Define UI fields for every socket property # NOTE: if I don't sort the input nodes, everytime one of the nodes is @@ -91,12 +92,12 @@ def draw_sockets_list_UD( col1.label(text="value") # text=sckt.name) # socket current value - col2.prop( - UD, - "default_value", - icon_only=True, - ) - col2.enabled = False # current value is not editable + # col2.prop( + # UD, + # "default_value", #####Default value not found + # icon_only=True, + # ) + # col2.enabled = False # current value is not editable # # socket min and max columns # socket_id = UD.name + "_" + sckt.name @@ -105,48 +106,120 @@ def draw_sockets_list_UD( # ): # only for SHADER groups # socket_id = UD.id_data.name + "_" + socket_id - # # if socket is a color: format min/max as a color picker - # # and an array (color picker doesn't include alpha value) - # if type(sckt) == bpy.types.NodeSocketColor: - # for m_str, col in zip(["min", "max"], [col3, col4]): - # # color picker - # col.template_color_picker( - # sockets_props_collection[socket_id], - # m_str + "_" + cs.socket_type_to_attr[type(sckt)], - # ) - # # array - # for j, cl in enumerate(["R", "G", "B", "alpha"]): - # col.prop( - # sockets_props_collection[socket_id], - # m_str + "_" + cs.socket_type_to_attr[type(sckt)], - # icon_only=False, - # text=cl, - # index=j, - # ) - # # if socket is Boolean: add non-editable labels - # elif type(sckt) == bpy.types.NodeSocketBool: - # for m_str, col in zip(["min", "max"], [col3, col4]): - # m_val = getattr( - # sockets_props_collection[socket_id], - # m_str + "_" + cs.socket_type_to_attr[type(sckt)], - # ) - # col.label(text=str(list(m_val)[0])) - - # # if socket is not color type: format as a regular property - # else: - # for m_str, col in zip(["min", "max"], [col3, col4]): - # col.prop( - # sockets_props_collection[socket_id], - # m_str + "_" + cs.socket_type_to_attr[type(sckt)], - # icon_only=True, - # ) - - # # randomisation toggle + # if socket is a color: format min/max as a color picker + # and an array (color picker doesn't include alpha value) + sckt = UD.name + if type(sckt) == bpy.types.NodeSocketColor: + for m_str, col in zip(["min", "max"], [col3, col4]): + # color picker + col.template_color_picker( + sockets_props_collection, # [socket_id], + m_str + "_" + cs.socket_type_to_attr[type(sckt)], + ) + # array + for j, cl in enumerate(["R", "G", "B", "alpha"]): + col.prop( + sockets_props_collection, # [socket_id], + m_str + "_" + cs.socket_type_to_attr[type(sckt)], + icon_only=False, + text=cl, + index=j, + ) + # if socket is Boolean: add non-editable labels + elif type(UD.name) == bpy.types.NodeSocketBool: + for m_str, col in zip(["min", "max"], [col3, col4]): + m_val = getattr( + sockets_props_collection, # [socket_id], + m_str + "_" + cs.socket_type_to_attr[type(sckt)], + ) + col.label(text=str(list(m_val)[0])) + + # if socket is not color type: format as a regular property + + ##### REFACTOR EASY CASE FIRST + # (may not need other cases) + else: # bpy.types.NodeSocketBool: + # Object.location bpy.props.FloatVector + print( + "TESTING STR TO BPY OBJ::::: ", + type(bpy.data.scenes["Scene"].camera.location), + ) + for m_str, col in zip(["min", "max"], [col3, col4]): + # # example usage (requires path_resolve) + # value_set(bpy.context.object, + # 'modifiers["Subsurf"].levels', 2) + + # # example usage (uses simple setattr) + # value_set(bpy.context.object, 'location', (1, 2, 3)) + + print( + "TEST NEW DEF = ", + print( + "TESTING STR TO BPY OBJ::::: ", + attr_get_type(bpy.context.scene, "camera.location"), + ), + ) + print("m_str is ", m_str) + print("UD = ", UD.name) + print("type(UD.name) = ", type(UD.name)) + col.prop( + sockets_props_collection, # [socket_id], + m_str + "_float_1d", + # + "_" + cs.UD_prop_to_attr[prop(UD)] + # m_str + "_", type(list_parents_node_str), + #'float_1d', #cs.socket_type_to_attr[type(sckt)], + # type(sckt) == bpy.types.NodeSocketColor: + # property not found: SocketProperties.min_ + icon_only=True, + ) + # np.array(getattr(self, m_str + "_min")) + # getattr(context.scene.camera, location)[0] + # e.g. min_float_1d so m_str + "_" + float_1d + + # randomisation toggle # col5.prop( - # sockets_props_collection[socket_id], - # "bool_randomise", - # icon_only=True, - # ) + col2.prop( + sockets_props_collection, # [socket_id], + "bool_randomise", + icon_only=True, + ) + + +def value_set(obj, path, value): + if "." in path: + # gives us: ('modifiers["Subsurf"]', 'levels') + path_prop, path_attr = path.rsplit(".", 1) + + # same as: prop = obj.modifiers["Subsurf"] + prop = obj.path_resolve(path_prop) + else: + prop = obj + # single attribute such as name, location... etc + path_attr = path + + # same as: prop.levels = value + setattr(prop, path_attr, value) + + +def attr_get_type(obj, path): + if "." in path: + # gives us: ('modifiers["Subsurf"]', 'levels') + path_prop, path_attr = path.rsplit(".", 1) + + # same as: prop = obj.modifiers["Subsurf"] + prop = obj.path_resolve(path_prop) + else: + prop = obj + # single attribute such as name, location... etc + path_attr = path + + # same as: prop.levels = value + print("prop = ", prop) + print("path_attr = ", path_attr) + # pdb.set_trace() + + return type(getattr(prop, path_attr)) + # setattr(prop, path_attr, value) # ---------------------- @@ -335,13 +408,13 @@ def poll(cls, context): """ cs = context.scene + ##### CHECK VALID PROPERTY # force an update on the group nodes collection first if cs.socket_props_per_UD.update_UD_props_collection: print("Collection of UD props updated") - return cls.subpanel_UD_idx < len( - cs.socket_props_per_UD.collection - ) # clc.subpanel defined in operators.py + return cls.subpanel_UD_idx < len(cs.socket_props_per_UD.collection) + # clc.subpanel defined in operators.py # only display subpanels for which this is true def draw_header( @@ -386,23 +459,40 @@ def draw(self, context): cs = context.scene # get this subpanel's GNG - subpanel_UD_prop = cs.socket_props_per_UD.collection[ - self.subpanel_UD_idx - ] + cs.socket_props_per_UD.collection[self.subpanel_UD_idx] - # force an update in the sockets for this GNG - if cs.socket_props_per_UD.collection[ - subpanel_UD_prop.name - ].update_sockets_collection: - print("Collection of UD props updated") + # print("subpanel_UD_prop = ", subpanel_UD_prop) + # print("self.subpanel_UD_idx = ", self.subpanel_UD_idx) - # get (updated) collection of socket props for this GNG + # # force an update ( ##### check string is valid property - + # # checked elsewhere?) + # if cs.socket_props_per_UD.collection[ # same as subpanel_UD_prop + # self.subpanel_UD_idx + # ].update_sockets_collection: + # print("Collection of UD props updated") + + # get (updated) collection of chosen propr for this subpanel sockets_props_collection = cs.socket_props_per_UD.collection[ - subpanel_UD_prop.name - ].collection + self.subpanel_UD_idx + ] # .collection + + print("sockets_props_collection = ", sockets_props_collection) + print(" and name ========== ", sockets_props_collection.name) + + # # Get list of input nodes to randomise for this subpanel's GNG + # [sckt.name.split("_")[0] for sckt in sockets_props_collection] # Get list of input nodes to randomise for this subpanel's GNG - [sckt.name.split("_")[0] for sckt in sockets_props_collection] + list_parent_nodes_str = sockets_props_collection.name.rsplit(".", 1) + print("list_parent_nodes_str = ", list_parent_nodes_str) + # list_parent_nodes_str = [ + # sckt.name.split("_")[0] for sckt in sockets_props_collection + # ] + + # list_UD_props = [ + # bpy.data.node_groups[self.subpanel_UD.name].nodes[nd_str] + # for nd_str in list_parent_nodes_str + # ] list_UD_props = [ UD_str @@ -421,6 +511,7 @@ def draw(self, context): self.layout, list_UD_props, sockets_props_collection, + list_parent_nodes_str, ) From e072a9b9e35acf60389390136d42c4f41d7875e1 Mon Sep 17 00:00:00 2001 From: ruaridhg Date: Fri, 9 Jun 2023 14:41:11 +0100 Subject: [PATCH 20/58] Map props to attr for vector float3d working with attr_get_type --- randomiser/config.py | 13 +++++- randomiser/define_prop/properties.py | 2 +- randomiser/define_prop/ui.py | 59 +++++++++++----------------- 3 files changed, 36 insertions(+), 38 deletions(-) diff --git a/randomiser/config.py b/randomiser/config.py index db229a1..90169e0 100644 --- a/randomiser/config.py +++ b/randomiser/config.py @@ -1,6 +1,7 @@ # Parameters shared across materials modules import bpy import numpy as np +from mathutils import Vector # MAX_NUMBER_OF_SUBPANELS: upper limit for the expected # number of *materials* in a scene. @@ -39,7 +40,17 @@ } MAP_PROPS_TO_ATTR = { - bpy.props.FloatVectorProperty: "float_3d", + # bpy.types.NodeSocketFloat: "float_1d" + # bpy.props.FloatVectorProperty size=1, + Vector: "float_3d", # bpy.props.FloatVectorProperty size=3, + # bpy.types.NodeSocketInt: "int_1d" + # bpy.props.IntProperty, + # bpy.types.NodeSocketColor: "rgba_4d", # "float_4d", if + # bpy.types.NodeSocketBool: "bool_1d", elif +} + +MAP_PROPS_TO_INI_MIN_MAX = { + Vector: {"min": -np.inf, "max": np.inf}, } # NOTE: if the property is a float vector of size (1,n) diff --git a/randomiser/define_prop/properties.py b/randomiser/define_prop/properties.py index ca1f1e7..d3def42 100644 --- a/randomiser/define_prop/properties.py +++ b/randomiser/define_prop/properties.py @@ -21,7 +21,7 @@ class CUSTOM_colorCollection(bpy.types.PropertyGroup): dict_context_scene_attr = { "UD_prop_to_attr": config.MAP_PROPS_TO_ATTR, - "socket_type_to_ini_min_max": config.MAP_SOCKET_TYPE_TO_INI_MIN_MAX, + "UD_prop_to_ini_min_max": config.MAP_PROPS_TO_INI_MIN_MAX, } ##### Not needed? bpy.props.BoolProperty directly diff --git a/randomiser/define_prop/ui.py b/randomiser/define_prop/ui.py index 053cd65..d6b1226 100644 --- a/randomiser/define_prop/ui.py +++ b/randomiser/define_prop/ui.py @@ -141,9 +141,14 @@ def draw_sockets_list_UD( else: # bpy.types.NodeSocketBool: # Object.location bpy.props.FloatVector print( - "TESTING STR TO BPY OBJ::::: ", + "TESTING STR TO BPY OBJ (hardcoded)::::: ", type(bpy.data.scenes["Scene"].camera.location), ) + + print( + "TESTING STR TO BPY OBJ (final version)::::: ", + attr_get_type(bpy.context.scene, "camera.location"), + ), for m_str, col in zip(["min", "max"], [col3, col4]): # # example usage (requires path_resolve) # value_set(bpy.context.object, @@ -152,24 +157,25 @@ def draw_sockets_list_UD( # # example usage (uses simple setattr) # value_set(bpy.context.object, 'location', (1, 2, 3)) - print( - "TEST NEW DEF = ", - print( - "TESTING STR TO BPY OBJ::::: ", - attr_get_type(bpy.context.scene, "camera.location"), - ), - ) - print("m_str is ", m_str) - print("UD = ", UD.name) - print("type(UD.name) = ", type(UD.name)) + # print( + # "TEST NEW DEF = ", + # print( + # "TESTING STR TO BPY OBJ::::: ", + # attr_get_type(bpy.context.scene, "camera.location"), + # ), + # ) + # print("m_str is ", m_str) + # print("UD = ", UD.name) + # print("type(UD.name) = ", type(UD.name)) + # print('TEST MAPPING = ', cs.UD_prop_to_attr[ + # attr_get_type(bpy.context.scene, "camera.location")]) col.prop( sockets_props_collection, # [socket_id], - m_str + "_float_1d", - # + "_" + cs.UD_prop_to_attr[prop(UD)] - # m_str + "_", type(list_parents_node_str), - #'float_1d', #cs.socket_type_to_attr[type(sckt)], - # type(sckt) == bpy.types.NodeSocketColor: - # property not found: SocketProperties.min_ + m_str + + "_" + + cs.UD_prop_to_attr[ + attr_get_type(bpy.context.scene, "camera.location") + ], icon_only=True, ) # np.array(getattr(self, m_str + "_min")) @@ -185,22 +191,6 @@ def draw_sockets_list_UD( ) -def value_set(obj, path, value): - if "." in path: - # gives us: ('modifiers["Subsurf"]', 'levels') - path_prop, path_attr = path.rsplit(".", 1) - - # same as: prop = obj.modifiers["Subsurf"] - prop = obj.path_resolve(path_prop) - else: - prop = obj - # single attribute such as name, location... etc - path_attr = path - - # same as: prop.levels = value - setattr(prop, path_attr, value) - - def attr_get_type(obj, path): if "." in path: # gives us: ('modifiers["Subsurf"]', 'levels') @@ -214,9 +204,6 @@ def attr_get_type(obj, path): path_attr = path # same as: prop.levels = value - print("prop = ", prop) - print("path_attr = ", path_attr) - # pdb.set_trace() return type(getattr(prop, path_attr)) # setattr(prop, path_attr, value) From 71c1e7848dfa0b88314cac16cfe27b09498cfb0a Mon Sep 17 00:00:00 2001 From: ruaridhg Date: Fri, 9 Jun 2023 15:02:20 +0100 Subject: [PATCH 21/58] able to split and pass in attribute_only_str --- randomiser/define_prop/ui.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/randomiser/define_prop/ui.py b/randomiser/define_prop/ui.py index d6b1226..5706acd 100644 --- a/randomiser/define_prop/ui.py +++ b/randomiser/define_prop/ui.py @@ -40,7 +40,7 @@ def draw_sockets_list_UD( layout, list_UD_props, sockets_props_collection, - list_parents_node_str, + attritube_only_str, ): # Define UI fields for every socket property # NOTE: if I don't sort the input nodes, everytime one of the nodes is @@ -174,7 +174,7 @@ def draw_sockets_list_UD( m_str + "_" + cs.UD_prop_to_attr[ - attr_get_type(bpy.context.scene, "camera.location") + attr_get_type(bpy.context.scene, attritube_only_str) ], icon_only=True, ) @@ -470,8 +470,14 @@ def draw(self, context): # [sckt.name.split("_")[0] for sckt in sockets_props_collection] # Get list of input nodes to randomise for this subpanel's GNG - list_parent_nodes_str = sockets_props_collection.name.rsplit(".", 1) - print("list_parent_nodes_str = ", list_parent_nodes_str) + full_str = sockets_props_collection.name + len_path = len(full_str.rsplit(".", config.MAX_NUMBER_OF_SUBPANELS)) + list_parent_nodes_str = full_str.rsplit(".", len_path - 3) + attritube_only_str = full_str.replace( + list_parent_nodes_str[0] + ".", "" + ) + + print("list_parent_nodes_str = ", attritube_only_str) # list_parent_nodes_str = [ # sckt.name.split("_")[0] for sckt in sockets_props_collection # ] @@ -498,7 +504,7 @@ def draw(self, context): self.layout, list_UD_props, sockets_props_collection, - list_parent_nodes_str, + attritube_only_str, ) From b343e226ed31f233f42354810b08425c35902efc Mon Sep 17 00:00:00 2001 From: ruaridhg Date: Fri, 9 Jun 2023 15:34:52 +0100 Subject: [PATCH 22/58] Tidy up before checking string is valid property --- randomiser/config.py | 5 +++++ randomiser/define_prop/operators.py | 2 +- randomiser/define_prop/properties.py | 1 - randomiser/define_prop/ui.py | 8 +++----- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/randomiser/config.py b/randomiser/config.py index 90169e0..cc30e9f 100644 --- a/randomiser/config.py +++ b/randomiser/config.py @@ -50,7 +50,12 @@ } MAP_PROPS_TO_INI_MIN_MAX = { + # bpy.types.NodeSocketFloat: {"min": -np.inf, "max": np.inf}, Vector: {"min": -np.inf, "max": np.inf}, + # bpy.types.NodeSocketInt: { + # "min": int(-1000), # -2147483648 + # "max": int(1000), # 2147483647 + # }, # ---- not sure this will work? } # NOTE: if the property is a float vector of size (1,n) diff --git a/randomiser/define_prop/operators.py b/randomiser/define_prop/operators.py index 7aa0c01..e3d61d3 100644 --- a/randomiser/define_prop/operators.py +++ b/randomiser/define_prop/operators.py @@ -124,7 +124,7 @@ def execute(self, context): # Operator Randomise selected sockets # across all Geometry node groups # -------------------------------------------- -##### REFACTOR +##### REFACTOR - remove to replace with randomise all? class RandomiseAllUDProps(bpy.types.Operator): """Randomise the selected output sockets across all geometry node groups diff --git a/randomiser/define_prop/properties.py b/randomiser/define_prop/properties.py index d3def42..23f0413 100644 --- a/randomiser/define_prop/properties.py +++ b/randomiser/define_prop/properties.py @@ -23,7 +23,6 @@ class CUSTOM_colorCollection(bpy.types.PropertyGroup): "UD_prop_to_attr": config.MAP_PROPS_TO_ATTR, "UD_prop_to_ini_min_max": config.MAP_PROPS_TO_INI_MIN_MAX, } -##### Not needed? bpy.props.BoolProperty directly def register(): diff --git a/randomiser/define_prop/ui.py b/randomiser/define_prop/ui.py index 5706acd..3e5e669 100644 --- a/randomiser/define_prop/ui.py +++ b/randomiser/define_prop/ui.py @@ -33,8 +33,6 @@ def invoke(self, context, event): # --------------------------------------------------- # Common layout for list of sockets to randomise # ---------------------------------------------------- -##### REFACTOR for list input nodes as list UD props -# - commented out sockets at end of this func def draw_sockets_list_UD( cs, layout, @@ -451,8 +449,8 @@ def draw(self, context): # print("subpanel_UD_prop = ", subpanel_UD_prop) # print("self.subpanel_UD_idx = ", self.subpanel_UD_idx) - # # force an update ( ##### check string is valid property - - # # checked elsewhere?) + # # force an update + # (##### CHECK VALID PROPERTY orchecked elsewhere?) # if cs.socket_props_per_UD.collection[ # same as subpanel_UD_prop # self.subpanel_UD_idx # ].update_sockets_collection: @@ -599,7 +597,7 @@ def draw(self, context): (SubPanelRandomUD,), { "bl_idname": f"UD_PT_subpanel_{i}", - "subpanel_UD_idx": i, ##### IN UI AND OPERATORS + "subpanel_UD_idx": i, # IN UI AND OPERATORS }, ) From 9c69b051ec31109965db12df5aecf7b581c6b5ca Mon Sep 17 00:00:00 2001 From: ruaridhg Date: Mon, 12 Jun 2023 14:40:14 +0100 Subject: [PATCH 23/58] fake property ranch does not appear in subpanel - list_UD_props modified --- randomiser/define_prop/operators.py | 2 +- .../property_classes/collection_UD_props.py | 6 ++ randomiser/define_prop/ui.py | 102 ++++++++++-------- 3 files changed, 66 insertions(+), 44 deletions(-) diff --git a/randomiser/define_prop/operators.py b/randomiser/define_prop/operators.py index e3d61d3..8b55eb7 100644 --- a/randomiser/define_prop/operators.py +++ b/randomiser/define_prop/operators.py @@ -59,7 +59,7 @@ def invoke(self, context, event): if self.action == "ADD": item = scn.custom.add() - item.name = "bpy.context.scene.camera.location" + item.name = "bpy.context.scene.camera.ranch" item.id = len(scn.custom) scn.custom_index = len(scn.custom) - 1 info = '"%s" added to list' % (item.name) diff --git a/randomiser/define_prop/property_classes/collection_UD_props.py b/randomiser/define_prop/property_classes/collection_UD_props.py index 5bd1954..c7ef22c 100644 --- a/randomiser/define_prop/property_classes/collection_UD_props.py +++ b/randomiser/define_prop/property_classes/collection_UD_props.py @@ -1,6 +1,7 @@ import bpy from ..property_classes.collection_UD_socket_properties import SocketProperties +from ..ui import attr_get_type, get_attr_only_str # --------------------------------------------------- @@ -145,10 +146,15 @@ def candidate_UD_props(self): # getter method _type_ _description_ """ + + # get_attr_only_strbpy.context.scene.custom # self is the collection of node groups list_UD_props = [ UD for UD in bpy.context.scene.custom + if attr_get_type(bpy.context.scene, get_attr_only_str(UD.name))[1] + != "dummy" + # if attr_get_type(bpy.context.scene,UD)[2] != 'dummy' # nd # for nd in bpy.data.node_groups # if nd.type == "GEOMETRY" diff --git a/randomiser/define_prop/ui.py b/randomiser/define_prop/ui.py index 3e5e669..3c1bd80 100644 --- a/randomiser/define_prop/ui.py +++ b/randomiser/define_prop/ui.py @@ -38,7 +38,7 @@ def draw_sockets_list_UD( layout, list_UD_props, sockets_props_collection, - attritube_only_str, + attribute_only_str, ): # Define UI fields for every socket property # NOTE: if I don't sort the input nodes, everytime one of the nodes is @@ -148,32 +148,12 @@ def draw_sockets_list_UD( attr_get_type(bpy.context.scene, "camera.location"), ), for m_str, col in zip(["min", "max"], [col3, col4]): - # # example usage (requires path_resolve) - # value_set(bpy.context.object, - # 'modifiers["Subsurf"].levels', 2) - - # # example usage (uses simple setattr) - # value_set(bpy.context.object, 'location', (1, 2, 3)) - - # print( - # "TEST NEW DEF = ", - # print( - # "TESTING STR TO BPY OBJ::::: ", - # attr_get_type(bpy.context.scene, "camera.location"), - # ), - # ) - # print("m_str is ", m_str) - # print("UD = ", UD.name) - # print("type(UD.name) = ", type(UD.name)) - # print('TEST MAPPING = ', cs.UD_prop_to_attr[ - # attr_get_type(bpy.context.scene, "camera.location")]) + attr_type = attr_get_type( + bpy.context.scene, attribute_only_str + )[0] col.prop( sockets_props_collection, # [socket_id], - m_str - + "_" - + cs.UD_prop_to_attr[ - attr_get_type(bpy.context.scene, attritube_only_str) - ], + m_str + "_" + cs.UD_prop_to_attr[attr_type], icon_only=True, ) # np.array(getattr(self, m_str + "_min")) @@ -203,10 +183,25 @@ def attr_get_type(obj, path): # same as: prop.levels = value - return type(getattr(prop, path_attr)) + try: + action = getattr(prop, path_attr) + except Exception: + print("Property does not exist") + action = "dummy" + # action = getattr(prop, path_attr) + + return type(action), action, prop, path_attr # setattr(prop, path_attr, value) +def get_attr_only_str(full_str): + len_path = len(full_str.rsplit(".", config.MAX_NUMBER_OF_SUBPANELS)) + list_parent_nodes_str = full_str.rsplit(".", len_path - 3) + attribute_only_str = full_str.replace(list_parent_nodes_str[0] + ".", "") + + return attribute_only_str + + # ---------------------- # Main panel # --------------------- @@ -398,9 +393,34 @@ def poll(cls, context): if cs.socket_props_per_UD.update_UD_props_collection: print("Collection of UD props updated") - return cls.subpanel_UD_idx < len(cs.socket_props_per_UD.collection) - # clc.subpanel defined in operators.py - # only display subpanels for which this is true + if cls.subpanel_UD_idx < len(cs.socket_props_per_UD.collection): + # pdb.set_trace() + sockets_props_collection = cs.socket_props_per_UD.collection[ + cls.subpanel_UD_idx + ] + + full_str = sockets_props_collection.name + attribute_only_str = get_attr_only_str(full_str) + prop_type, action, prop, path_attr = attr_get_type( + bpy.context.scene, attribute_only_str + ) + + print("prop_type", prop_type) + print("action", action) + print("prop", prop) + print("path_attr", path_attr) + + else: + action = "dummy" + # bpy.ops.custom.list_action(action='UP') + # pdb.set_trace() + # print('action',action) + + return action != "dummy" + + # , getattr(prop, path_attr, None) + # clc.subpanel defined in operators.py + # only display subpanels for which this is true def draw_header( self, context @@ -451,10 +471,10 @@ def draw(self, context): # # force an update # (##### CHECK VALID PROPERTY orchecked elsewhere?) - # if cs.socket_props_per_UD.collection[ # same as subpanel_UD_prop + # if cs.socket_props_per_UD[ # self.subpanel_UD_idx - # ].update_sockets_collection: - # print("Collection of UD props updated") + # ].update_collection: + # print("Collection of UD properties updated") # get (updated) collection of chosen propr for this subpanel sockets_props_collection = cs.socket_props_per_UD.collection[ @@ -471,23 +491,19 @@ def draw(self, context): full_str = sockets_props_collection.name len_path = len(full_str.rsplit(".", config.MAX_NUMBER_OF_SUBPANELS)) list_parent_nodes_str = full_str.rsplit(".", len_path - 3) - attritube_only_str = full_str.replace( + attribute_only_str = full_str.replace( list_parent_nodes_str[0] + ".", "" ) - print("list_parent_nodes_str = ", attritube_only_str) - # list_parent_nodes_str = [ - # sckt.name.split("_")[0] for sckt in sockets_props_collection - # ] - - # list_UD_props = [ - # bpy.data.node_groups[self.subpanel_UD.name].nodes[nd_str] - # for nd_str in list_parent_nodes_str - # ] + print("list_parent_nodes_str = ", attribute_only_str) list_UD_props = [ UD_str for UD_str in bpy.context.scene.custom + if attr_get_type( + bpy.context.scene, get_attr_only_str(UD_str.name) + )[1] + != "dummy" # bpy.context.scene.custom[UD_str] # for UD_str in list_parent_UD_str # bpy.data.node_groups[subpanel_gng.name].nodes[nd_str] @@ -502,7 +518,7 @@ def draw(self, context): self.layout, list_UD_props, sockets_props_collection, - attritube_only_str, + attribute_only_str, ) From 6f13b204acdfabd1cc4d15c059834f3fb087bc9c Mon Sep 17 00:00:00 2001 From: ruaridhg Date: Mon, 12 Jun 2023 15:08:46 +0100 Subject: [PATCH 24/58] Multiple Vector3 properties can be added --- .../property_classes/collection_UD_props.py | 15 +++++++++++++-- randomiser/define_prop/ui.py | 2 +- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/randomiser/define_prop/property_classes/collection_UD_props.py b/randomiser/define_prop/property_classes/collection_UD_props.py index c7ef22c..82b28fe 100644 --- a/randomiser/define_prop/property_classes/collection_UD_props.py +++ b/randomiser/define_prop/property_classes/collection_UD_props.py @@ -20,9 +20,17 @@ def compute_UD_props_sets(self): # set of GNGs already in collection self.set_UD_props_in_collection = set(UD.name for UD in self.collection) + for UD in self.collection: + print("self.collection !!!!!!!!!! ", UD.name) + # set of node groups in Blender data structure self.set_UD_props_in_data = set(UD.name for UD in self.candidate_UD_props) + for UD in self.candidate_UD_props: + print("self.candidate_UD_props !!!!!!!!!! ", UD.name) + + # pdb.set_trace() + # set of node groups in one of the sets only self.set_UD_props_in_one_only = ( self.set_UD_props_in_collection.symmetric_difference( @@ -152,8 +160,11 @@ def candidate_UD_props(self): # getter method list_UD_props = [ UD for UD in bpy.context.scene.custom - if attr_get_type(bpy.context.scene, get_attr_only_str(UD.name))[1] - != "dummy" + if ( + attr_get_type(bpy.context.scene, get_attr_only_str(UD.name))[1] + != "dummy" + ) + # != "dummy" # if attr_get_type(bpy.context.scene,UD)[2] != 'dummy' # nd # for nd in bpy.data.node_groups diff --git a/randomiser/define_prop/ui.py b/randomiser/define_prop/ui.py index 3c1bd80..def7881 100644 --- a/randomiser/define_prop/ui.py +++ b/randomiser/define_prop/ui.py @@ -186,7 +186,7 @@ def attr_get_type(obj, path): try: action = getattr(prop, path_attr) except Exception: - print("Property does not exist") + # print("Property does not exist") action = "dummy" # action = getattr(prop, path_attr) From 2ba13de774e90558f0f91df0d5b12144562417d0 Mon Sep 17 00:00:00 2001 From: ruaridhg Date: Mon, 12 Jun 2023 16:15:52 +0100 Subject: [PATCH 25/58] Added float 1d to attr_type, need to fix subpanels per attr_type --- randomiser/config.py | 2 ++ randomiser/define_prop/ui.py | 18 ++++++++---------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/randomiser/config.py b/randomiser/config.py index cc30e9f..74efc24 100644 --- a/randomiser/config.py +++ b/randomiser/config.py @@ -43,6 +43,7 @@ # bpy.types.NodeSocketFloat: "float_1d" # bpy.props.FloatVectorProperty size=1, Vector: "float_3d", # bpy.props.FloatVectorProperty size=3, + float: "float_1d", # bpy.types.NodeSocketInt: "int_1d" # bpy.props.IntProperty, # bpy.types.NodeSocketColor: "rgba_4d", # "float_4d", if @@ -52,6 +53,7 @@ MAP_PROPS_TO_INI_MIN_MAX = { # bpy.types.NodeSocketFloat: {"min": -np.inf, "max": np.inf}, Vector: {"min": -np.inf, "max": np.inf}, + float: {"min": -np.inf, "max": np.inf}, # bpy.types.NodeSocketInt: { # "min": int(-1000), # -2147483648 # "max": int(1000), # 2147483647 diff --git a/randomiser/define_prop/ui.py b/randomiser/define_prop/ui.py index def7881..49a8370 100644 --- a/randomiser/define_prop/ui.py +++ b/randomiser/define_prop/ui.py @@ -39,6 +39,7 @@ def draw_sockets_list_UD( list_UD_props, sockets_props_collection, attribute_only_str, + full_str, ): # Define UI fields for every socket property # NOTE: if I don't sort the input nodes, everytime one of the nodes is @@ -137,20 +138,15 @@ def draw_sockets_list_UD( ##### REFACTOR EASY CASE FIRST # (may not need other cases) else: # bpy.types.NodeSocketBool: - # Object.location bpy.props.FloatVector - print( - "TESTING STR TO BPY OBJ (hardcoded)::::: ", - type(bpy.data.scenes["Scene"].camera.location), - ) - - print( - "TESTING STR TO BPY OBJ (final version)::::: ", - attr_get_type(bpy.context.scene, "camera.location"), - ), for m_str, col in zip(["min", "max"], [col3, col4]): attr_type = attr_get_type( bpy.context.scene, attribute_only_str )[0] + print( + "sockets_props_collection ???????", + sockets_props_collection, + ) + print("type ??????????? ", attribute_only_str, attr_type) col.prop( sockets_props_collection, # [socket_id], m_str + "_" + cs.UD_prop_to_attr[attr_type], @@ -172,6 +168,7 @@ def draw_sockets_list_UD( def attr_get_type(obj, path): if "." in path: # gives us: ('modifiers["Subsurf"]', 'levels') + # len_path = len(full_str.rsplit(".", config.MAX_NUMBER_OF_SUBPANELS)) path_prop, path_attr = path.rsplit(".", 1) # same as: prop = obj.modifiers["Subsurf"] @@ -519,6 +516,7 @@ def draw(self, context): list_UD_props, sockets_props_collection, attribute_only_str, + full_str, ) From a950355611b51583f1e4b81668355dbdd855901b Mon Sep 17 00:00:00 2001 From: ruaridhg Date: Tue, 12 Sep 2023 14:57:56 +0100 Subject: [PATCH 26/58] Added bool and int data types UD prop - int min-max needs fixing --- randomiser/config.py | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/randomiser/config.py b/randomiser/config.py index 74efc24..40045fa 100644 --- a/randomiser/config.py +++ b/randomiser/config.py @@ -39,11 +39,29 @@ bpy.types.NodeSocketBool: "bool_1d", } +# NOTE: if the property is a float vector of size (1,n) +# the initial min/max values specified here apply to all n dimensions +# TODO: should we change this to allow different values per dimension? +# (in that case the mapping should probably be from attribute name) +MAP_SOCKET_TYPE_TO_INI_MIN_MAX = { + bpy.types.NodeSocketFloat: {"min": -np.inf, "max": np.inf}, + bpy.types.NodeSocketVector: {"min": -np.inf, "max": np.inf}, + bpy.types.NodeSocketColor: {"min": 0.0, "max": 1.0}, + bpy.types.NodeSocketInt: { + "min": int(-1000), # -2147483648 + "max": int(1000), # 2147483647 + }, # ---- not sure this will work? + bpy.types.NodeSocketBool: {"min": False, "max": True}, +} + + MAP_PROPS_TO_ATTR = { # bpy.types.NodeSocketFloat: "float_1d" # bpy.props.FloatVectorProperty size=1, Vector: "float_3d", # bpy.props.FloatVectorProperty size=3, float: "float_1d", + int: "int_1d", + bool: "bool_1d", # bpy.types.NodeSocketInt: "int_1d" # bpy.props.IntProperty, # bpy.types.NodeSocketColor: "rgba_4d", # "float_4d", if @@ -54,23 +72,10 @@ # bpy.types.NodeSocketFloat: {"min": -np.inf, "max": np.inf}, Vector: {"min": -np.inf, "max": np.inf}, float: {"min": -np.inf, "max": np.inf}, + int: {"min": int(-10), "max": int(10)}, + bool: {"min": False, "max": True}, # bpy.types.NodeSocketInt: { # "min": int(-1000), # -2147483648 # "max": int(1000), # 2147483647 # }, # ---- not sure this will work? } - -# NOTE: if the property is a float vector of size (1,n) -# the initial min/max values specified here apply to all n dimensions -# TODO: should we change this to allow different values per dimension? -# (in that case the mapping should probably be from attribute name) -MAP_SOCKET_TYPE_TO_INI_MIN_MAX = { - bpy.types.NodeSocketFloat: {"min": -np.inf, "max": np.inf}, - bpy.types.NodeSocketVector: {"min": -np.inf, "max": np.inf}, - bpy.types.NodeSocketColor: {"min": 0.0, "max": 1.0}, - bpy.types.NodeSocketInt: { - "min": int(-1000), # -2147483648 - "max": int(1000), # 2147483647 - }, # ---- not sure this will work? - bpy.types.NodeSocketBool: {"min": False, "max": True}, -} From f52a24fd7104af275bd47ce6c8daa64e0057996d Mon Sep 17 00:00:00 2001 From: ruaridhg Date: Thu, 14 Sep 2023 15:05:45 +0100 Subject: [PATCH 27/58] Fixed duplicated subpanels min-max vals for UD props - only names duplicated --- randomiser/define_prop/properties.py | 3 +- .../property_classes/collection_UD_props.py | 11 +- randomiser/define_prop/ui.py | 248 +++++++++--------- 3 files changed, 137 insertions(+), 125 deletions(-) diff --git a/randomiser/define_prop/properties.py b/randomiser/define_prop/properties.py index 23f0413..5f16f39 100644 --- a/randomiser/define_prop/properties.py +++ b/randomiser/define_prop/properties.py @@ -42,7 +42,8 @@ def register(): bpy.types.Scene.custom = bpy.props.CollectionProperty( type=CUSTOM_colorCollection ) - bpy.types.Scene.custom_index = bpy.props.IntProperty() + + bpy.types.Scene.custom_index = bpy.props.IntProperty() print("UD properties registered") diff --git a/randomiser/define_prop/property_classes/collection_UD_props.py b/randomiser/define_prop/property_classes/collection_UD_props.py index 82b28fe..e98a403 100644 --- a/randomiser/define_prop/property_classes/collection_UD_props.py +++ b/randomiser/define_prop/property_classes/collection_UD_props.py @@ -20,17 +20,18 @@ def compute_UD_props_sets(self): # set of GNGs already in collection self.set_UD_props_in_collection = set(UD.name for UD in self.collection) - for UD in self.collection: - print("self.collection !!!!!!!!!! ", UD.name) + # for UD in self.collection: + ##print("self.collection !!!!!!!!!! ", UD.name) # set of node groups in Blender data structure self.set_UD_props_in_data = set(UD.name for UD in self.candidate_UD_props) - for UD in self.candidate_UD_props: - print("self.candidate_UD_props !!!!!!!!!! ", UD.name) + # for UD in self.candidate_UD_props: + ## print("self.candidate_UD_props !!!!!!!!!! ", UD.name) # pdb.set_trace() + ### REMOVE???? # set of node groups in one of the sets only self.set_UD_props_in_one_only = ( self.set_UD_props_in_collection.symmetric_difference( @@ -77,6 +78,7 @@ def set_update_UD_props_collection(self, value): _description_ """ + ##### ISSUE WITH DUPLICATION????? # if update value is True if value: # if the update fn is triggered directly and not via @@ -87,6 +89,7 @@ def set_update_UD_props_collection(self, value): # for all node groups that are in one set only for UD_name in self.set_UD_props_in_one_only: # if only in collection: remove it from the collection + if UD_name in self.set_UD_props_in_collection: self.collection.remove(self.collection.find(UD_name)) diff --git a/randomiser/define_prop/ui.py b/randomiser/define_prop/ui.py index 49a8370..430de41 100644 --- a/randomiser/define_prop/ui.py +++ b/randomiser/define_prop/ui.py @@ -44,125 +44,124 @@ def draw_sockets_list_UD( # Define UI fields for every socket property # NOTE: if I don't sort the input nodes, everytime one of the nodes is # selected in the graph it moves to the bottom of the panel. - list_UD_props_sorted = sorted(list_UD_props, key=lambda x: x.name) - print(list_UD_props_sorted) - for i_n, UD in enumerate(list_UD_props_sorted): - row = layout.row() - - # if first node: add labels for - # name, min, max and randomisation toggle - if i_n == 0: - row_split = row.split() - col1 = row_split.column(align=True) - row_split.column(align=True) - col3 = row_split.column(align=True) - col4 = row_split.column(align=True) - row_split.column(align=True) - - # input node name - col1.label(text=UD.name) - col1.alignment = "CENTER" - - # min label - col3.alignment = "CENTER" - col3.label(text="min") - - # max label - col4.alignment = "CENTER" - col4.label(text="max") - - # if not first node: add just node name - else: - row.separator(factor=1.0) # add empty row before each node - row = layout.row() - - row.label(text=UD.name) - - row = layout.row() - row_split = row.split() - col1 = row_split.column(align=True) - col2 = row_split.column(align=True) - col3 = row_split.column(align=True) - col4 = row_split.column(align=True) - row_split.column(align=True) - - # UD prop name - col1.alignment = "RIGHT" - col1.label(text="value") # text=sckt.name) - - # socket current value - # col2.prop( - # UD, - # "default_value", #####Default value not found - # icon_only=True, - # ) - # col2.enabled = False # current value is not editable - - # # socket min and max columns - # socket_id = UD.name + "_" + sckt.name - # if (UD.id_data.name in bpy.data.node_groups) and ( - # bpy.data.node_groups[UD.id_data.name].type != "GEOMETRY" - # ): # only for SHADER groups - # socket_id = UD.id_data.name + "_" + socket_id - - # if socket is a color: format min/max as a color picker - # and an array (color picker doesn't include alpha value) - sckt = UD.name - if type(sckt) == bpy.types.NodeSocketColor: - for m_str, col in zip(["min", "max"], [col3, col4]): - # color picker - col.template_color_picker( - sockets_props_collection, # [socket_id], - m_str + "_" + cs.socket_type_to_attr[type(sckt)], - ) - # array - for j, cl in enumerate(["R", "G", "B", "alpha"]): - col.prop( - sockets_props_collection, # [socket_id], - m_str + "_" + cs.socket_type_to_attr[type(sckt)], - icon_only=False, - text=cl, - index=j, - ) - # if socket is Boolean: add non-editable labels - elif type(UD.name) == bpy.types.NodeSocketBool: - for m_str, col in zip(["min", "max"], [col3, col4]): - m_val = getattr( - sockets_props_collection, # [socket_id], - m_str + "_" + cs.socket_type_to_attr[type(sckt)], - ) - col.label(text=str(list(m_val)[0])) - - # if socket is not color type: format as a regular property - - ##### REFACTOR EASY CASE FIRST - # (may not need other cases) - else: # bpy.types.NodeSocketBool: - for m_str, col in zip(["min", "max"], [col3, col4]): - attr_type = attr_get_type( - bpy.context.scene, attribute_only_str - )[0] - print( - "sockets_props_collection ???????", - sockets_props_collection, - ) - print("type ??????????? ", attribute_only_str, attr_type) + list_UD_props_sorted = list_UD_props + # sorted(list_UD_props, key=lambda x: x.name) + print("IN draw_sockets_list_UD ==== ", list_UD_props_sorted) + # for i_n, UD_str in enumerate(list_UD_props_sorted): + row = layout.row() + + # if first node: add labels for + # name, min, max and randomisation toggle + # if i_n == 0: + row_split = row.split() + col1 = row_split.column(align=True) + row_split.column(align=True) + col3 = row_split.column(align=True) + col4 = row_split.column(align=True) + row_split.column(align=True) + + # input node name + col1.label(text=list_UD_props_sorted) # UD.name + col1.alignment = "CENTER" + + # min label + col3.alignment = "CENTER" + col3.label(text="min") + + # max label + col4.alignment = "CENTER" + col4.label(text="max") + + # if not first node: add just node name + # else: + # row.separator(factor=1.0) # add empty row before each node + # row = layout.row() + + # row.label(text=UD_str) #UD.name + + row = layout.row() + row_split = row.split() + col1 = row_split.column(align=True) + col2 = row_split.column(align=True) + col3 = row_split.column(align=True) + col4 = row_split.column(align=True) + row_split.column(align=True) + + # UD prop name + col1.alignment = "RIGHT" + col1.label(text="value") # text=sckt.name) + + # socket current value + # col2.prop( + # UD, + # "default_value", #####Default value not found + # icon_only=True, + # ) + # col2.enabled = False # current value is not editable + + # # socket min and max columns + # socket_id = UD.name + "_" + sckt.name + # if (UD.id_data.name in bpy.data.node_groups) and ( + # bpy.data.node_groups[UD.id_data.name].type != "GEOMETRY" + # ): # only for SHADER groups + # socket_id = UD.id_data.name + "_" + socket_id + + # if socket is a color: format min/max as a color picker + # and an array (color picker doesn't include alpha value) + sckt = list_UD_props_sorted # UD.name + if type(sckt) == bpy.types.NodeSocketColor: + for m_str, col in zip(["min", "max"], [col3, col4]): + # color picker + col.template_color_picker( + sockets_props_collection, # [socket_id], + m_str + "_" + cs.socket_type_to_attr[type(sckt)], + ) + # array + for j, cl in enumerate(["R", "G", "B", "alpha"]): col.prop( sockets_props_collection, # [socket_id], - m_str + "_" + cs.UD_prop_to_attr[attr_type], - icon_only=True, + m_str + "_" + cs.socket_type_to_attr[type(sckt)], + icon_only=False, + text=cl, + index=j, ) - # np.array(getattr(self, m_str + "_min")) - # getattr(context.scene.camera, location)[0] - # e.g. min_float_1d so m_str + "_" + float_1d - - # randomisation toggle - # col5.prop( - col2.prop( - sockets_props_collection, # [socket_id], - "bool_randomise", - icon_only=True, - ) + # if socket is Boolean: add non-editable labels + elif type(sckt) == bpy.types.NodeSocketBool: + for m_str, col in zip(["min", "max"], [col3, col4]): + m_val = getattr( + sockets_props_collection, # [socket_id], + m_str + "_" + cs.socket_type_to_attr[type(sckt)], + ) + col.label(text=str(list(m_val)[0])) + + # if socket is not color type: format as a regular property + + ##### REFACTOR EASY CASE FIRST + # (may not need other cases) + else: # bpy.types.NodeSocketBool: + for m_str, col in zip(["min", "max"], [col3, col4]): + attr_type = attr_get_type(bpy.context.scene, attribute_only_str)[0] + print( + "sockets_props_collection ???????", + sockets_props_collection, + ) + print("type ??????????? ", attribute_only_str, attr_type) + col.prop( + sockets_props_collection, # [socket_id], + m_str + "_" + cs.UD_prop_to_attr[attr_type], + icon_only=True, + ) + # np.array(getattr(self, m_str + "_min")) + # getattr(context.scene.camera, location)[0] + # e.g. min_float_1d so m_str + "_" + float_1d + + # randomisation toggle + # col5.prop( + col2.prop( + sockets_props_collection, # [socket_id], + "bool_randomise", + icon_only=True, + ) def attr_get_type(obj, path): @@ -494,9 +493,10 @@ def draw(self, context): print("list_parent_nodes_str = ", attribute_only_str) - list_UD_props = [ + # full_list = [prop.name for prop in list(C.scene.custom)] + list_all_UD_props = [ UD_str - for UD_str in bpy.context.scene.custom + for UD_str in list(bpy.context.scene.custom) if attr_get_type( bpy.context.scene, get_attr_only_str(UD_str.name) )[1] @@ -506,14 +506,22 @@ def draw(self, context): # bpy.data.node_groups[subpanel_gng.name].nodes[nd_str] # for nd_str in list_parent_nodes_str ] - print("list_UD_props =======", list_UD_props) + print("list_all_UD_props ====== ", list_all_UD_props) + print( + "bpy.context.scene.custom_index == ", + bpy.context.scene.custom_index, + ) + list_current_UD_props = list_all_UD_props[ + bpy.context.scene.custom_index + ].name + print("list_current_UD_props =======", list_current_UD_props) # Draw sockets to randomise per input node, including their # current value and min/max boundaries draw_sockets_list_UD( cs, self.layout, - list_UD_props, + list_current_UD_props, sockets_props_collection, attribute_only_str, full_str, From 64c9a78e4e4925afdfa777ed61d41b583a33de52 Mon Sep 17 00:00:00 2001 From: ruaridhg Date: Fri, 15 Sep 2023 16:04:41 +0100 Subject: [PATCH 28/58] obj_path_resolve_test.py added and if-else in ui.py --- .../define_prop/obj_path_resolve_test.py | 142 ++++++++++++++++++ randomiser/define_prop/ui.py | 12 +- 2 files changed, 151 insertions(+), 3 deletions(-) create mode 100644 randomiser/define_prop/obj_path_resolve_test.py diff --git a/randomiser/define_prop/obj_path_resolve_test.py b/randomiser/define_prop/obj_path_resolve_test.py new file mode 100644 index 0000000..75cd820 --- /dev/null +++ b/randomiser/define_prop/obj_path_resolve_test.py @@ -0,0 +1,142 @@ +import bpy + +# %% +full_str = "bpy.data.objects['Cube'].location" +# path_prop, path_attr = full_str.rsplit("[]", 1) +x = full_str.split(".") +# print(path_prop) +for i in x: + print(i) + if "[" in i: + ii = i.split("[") + fiinal = ii[1] + fiinal = fiinal[:-1] + print("fiinal= ", fiinal) + +print(type(fiinal)) +print(fiinal) + +# %% + + +def attr_get_type(obj, path): + if "." in path: + # gives us: ('modifiers["Subsurf"]', 'levels') + # len_path = len(full_str.rsplit(".", config.MAX_NUMBER_OF_SUBPANELS)) + path_prop, path_attr = path.rsplit(".", 1) + + print("if statement ==== ") + print( + "FROM rsplit . path_prop for resolve = ", + path_prop, + " and path_attr for getattr = ", + path_attr, + ) + print("obj used for path_resolve = ", obj) + + # same as: prop = obj.modifiers["Subsurf"] + prop = obj.path_resolve(path_prop) + print("prop from path_resolve for get_attr = ", prop) + else: + print("else statement ==== ") + prop = obj + print("prop = obj ", prop) + # single attribute such as name, location... etc + path_attr = path + print("path_attr = path ", path_attr) + + # same as: prop.levels = value + + try: + action = getattr(prop, path_attr) + except Exception: + # print("Property does not exist") + action = "dummy" + # action = getattr(prop, path_attr) + + return type(action), action, prop, path_attr + # setattr(prop, path_attr, value) + + +print("CAMERA.LOCATION !!!!!!!!!!!!!!!") +print("INPUTS ======") +attr_str = "camera.location" +print("bpy.context.scene as obj and str = ", attr_str) +action_type, action, prop, path_attr = attr_get_type( + bpy.context.scene, attr_str +) +print("OUTPUTS ===== ") +print("type(action) = ", action_type) +print("action = ", action) +print("prop = ", prop) +print("path_attr = ", path_attr) + +print("FRAME_CURRENT !!!!!!!!!!!!!!!") +print("INPUTS ======") +attr_str = "frame_current" +print("bpy.context.scene as obj and str = ", attr_str) +action_type, action, prop, path_attr = attr_get_type( + bpy.context.scene, attr_str +) +print("OUTPUTS ===== ") +print("type(action) = ", action_type) +print("action = ", action) +print("prop = ", prop) +print("path_attr = ", path_attr) + + +# bpy.context.scene.objects['Cube'].collision.absorption +# bpy.data.objects['Cube'].location +# bpy.data.objects['Cube'].rotation_euler.x +print("bpy.data.objects[Cube].location !!!!!!!!!!!!!!!") +print("INPUTS ======") +# attr_str = 'location' +# attr_str = 'rotation_euler.x' +attr_str = "collision.absorption" +print("bpy.context.scene as obj and str = ", attr_str) +action_type, action, prop, path_attr = attr_get_type( + bpy.data.objects["Cube"], attr_str +) +print("OUTPUTS ===== ") +print("type(action) = ", action_type) +print("action = ", action) +print("prop = ", prop) +print("path_attr = ", path_attr) + +# def attr_get_type(obj, path): +# type_object='objectsdfas' + +# if type_object=='objdfdsects': + +# print("[Cube] HERE") + +# elif "." in path: + +# path_prop, path_attr = path.rsplit(".", 1) + + +# prop = obj.path_resolve(path_prop) + +# elif type_object=='objects': + +# pdb.set_trace + +# prop = obj + + +# path_attr = path + + +# + +# try: + +# action = getattr(prop, path_attr) + +# except Exception: + +# action = "dummy" + +# + +# return type(action), action, prop, path_attr diff --git a/randomiser/define_prop/ui.py b/randomiser/define_prop/ui.py index 430de41..cabca4a 100644 --- a/randomiser/define_prop/ui.py +++ b/randomiser/define_prop/ui.py @@ -397,9 +397,15 @@ def poll(cls, context): full_str = sockets_props_collection.name attribute_only_str = get_attr_only_str(full_str) - prop_type, action, prop, path_attr = attr_get_type( - bpy.context.scene, attribute_only_str - ) + + if "bpy.context.scene" in full_str: + prop_type, action, prop, path_attr = attr_get_type( + bpy.context.scene, attribute_only_str + ) + elif "bpy.data.objects" in full_str: + prop_type, action, prop, path_attr = attr_get_type( + bpy.data.objects["Cube"], attribute_only_str + ) print("prop_type", prop_type) print("action", action) From a03f55a129f6a09e1d86235bee3520af984abbf6 Mon Sep 17 00:00:00 2001 From: ruaridhg Date: Mon, 25 Sep 2023 17:40:01 +0100 Subject: [PATCH 29/58] Fixed utils error --- randomiser/__init__.py | 8 +- randomiser/utils.py | 510 ----------------------------------------- 2 files changed, 5 insertions(+), 513 deletions(-) delete mode 100644 randomiser/utils.py diff --git a/randomiser/__init__.py b/randomiser/__init__.py index 49c2029..a16590c 100644 --- a/randomiser/__init__.py +++ b/randomiser/__init__.py @@ -1,4 +1,4 @@ -from . import material, transform, geometry, seed, random_all, define_prop +from . import material, transform, geometry, seed, define_prop # random_all bl_info = { "name": "Randomisations panel", @@ -20,7 +20,8 @@ def register(): # transform.register() material.register() geometry.register() - random_all.register() + define_prop.register() + # random_all.register() def unregister(): @@ -28,7 +29,8 @@ def unregister(): # transform.unregister() material.unregister() geometry.unregister() - random_all.unregister() + define_prop.unregister() + # random_all.unregister() if __name__ == "__main__": diff --git a/randomiser/utils.py b/randomiser/utils.py deleted file mode 100644 index 2e03ea2..0000000 --- a/randomiser/utils.py +++ /dev/null @@ -1,510 +0,0 @@ -import bpy - -from . import config - - -def get_UD_sockets_to_randomise_from_list( - list_candidate_nodes: list, -): - """Get list of nodes to randomise from list. - - Input nodes are defined as nodes with no input sockets. - The nodes to randomise are input nodes whose name starts - with 'node2randomise_prefix' (case insensitive). - - The 'artificial' nodes that show up inside a node group, usually named - 'Group input' or 'Group output' are excluded from the search. - - Parameters - ---------- - list_candidate_nodes : list - list of the candidate nodes to randomise - node2randomise_prefix : str, optional - prefix that identifies the nodes to randomise, by default 'random' - - Returns - ------- - list - list of the input nodes to randomise - """ - - # ensure list_candidate_nodes is unique - list_candidate_nodes = list(set(list_candidate_nodes)) - - # find input nodes that start with the random keyword - # excluding 'Group' artificial nodes - list_input_nodes = [ - nd - for nd in list_candidate_nodes - # if len(nd.inputs) == 0 - # and nd.name.lower().startswith(node2randomise_prefix.lower()) - # and nd.type - # not in [ - # "GROUP_INPUT", - # "GROUP_OUTPUT", - # ] - ] - - return list_input_nodes - - -def get_nodes_to_randomise_from_list( - list_candidate_nodes: list, - node2randomise_prefix: str = config.DEFAULT_RANDOM_KEYWORD, -): - """Get list of nodes to randomise from list. - - Input nodes are defined as nodes with no input sockets. - The nodes to randomise are input nodes whose name starts - with 'node2randomise_prefix' (case insensitive). - - The 'artificial' nodes that show up inside a node group, usually named - 'Group input' or 'Group output' are excluded from the search. - - Parameters - ---------- - list_candidate_nodes : list - list of the candidate nodes to randomise - node2randomise_prefix : str, optional - prefix that identifies the nodes to randomise, by default 'random' - - Returns - ------- - list - list of the input nodes to randomise - """ - - # ensure list_candidate_nodes is unique - list_candidate_nodes = list(set(list_candidate_nodes)) - - # find input nodes that start with the random keyword - # excluding 'Group' artificial nodes - list_input_nodes = [ - nd - for nd in list_candidate_nodes - if len(nd.inputs) == 0 - and nd.name.lower().startswith(node2randomise_prefix.lower()) - and nd.type - not in [ - "GROUP_INPUT", - "GROUP_OUTPUT", - ] - ] - - return list_input_nodes - - -def get_material_nodes_to_randomise_indep( - material_str: str = "Material", - node2randomise_prefix: str = config.DEFAULT_RANDOM_KEYWORD, -): - """Get list of *independent* input nodes to randomise for a given material. - - Input nodes are defined as nodes with no input sockets. - The input nodes to randomise are identified because their - name is prefixed with node2randomise_prefix (case insensitive). - - Both independent nodes, and nodes inside a group are searched. - The 'artificial' nodes that show up inside a node group, usually named - 'Group input' or 'Group output' are excluded from the search. - - Parameters - ---------- - material_str : str, optional - name of the material, by default "Material" - node2randomise_prefix : str, optional - prefix that identifies the nodes to randomise, by default 'random' - - Returns - ------- - list - list of all *group* input nodes to randomise for this material - """ - - # list of nodes for current material - # not belonging to a group - list_material_nodes_indep = [] - for nd in bpy.data.materials[material_str].node_tree.nodes: - if nd.type != "GROUP": - list_material_nodes_indep.append(nd) - - # find input nodes that startwith random - list_input_nodes = get_nodes_to_randomise_from_list( - list_material_nodes_indep - ) - - return list_input_nodes - - -def get_material_nodes_to_randomise_group( - material_str: str = "Material", - node2randomise_prefix: str = config.DEFAULT_RANDOM_KEYWORD, -): - """Get list of *group* input nodes to randomise for a given material. - - Input nodes are defined as nodes with no input sockets. - The input nodes to randomise are identified because their - name is prefixed with node2randomise_prefix (case insensitive). - - Both independent nodes, and nodes inside a group are searched. - The 'artificial' nodes that show up inside a node group, usually named - 'Group input' or 'Group output' are excluded from the search. - - Parameters - ---------- - material_str : str, optional - name of the material, by default "Material" - node2randomise_prefix : str, optional - prefix that identifies the nodes to randomise, by default 'random' - - Returns - ------- - list - list of all *group* input nodes to randomise for this material - """ - - # list of nodes for current material - # belonging to a group - list_material_nodes_in_groups = [] - for nd in bpy.data.materials[material_str].node_tree.nodes: - if nd.type == "GROUP": - list_material_nodes_in_groups.extend( - nd.node_tree.nodes - ) # nodes inside groups - - # find input nodes that startwith random - # in any of those groups - # excluding 'Group' nodes - list_input_nodes = get_nodes_to_randomise_from_list( - list_material_nodes_in_groups - ) - - return list_input_nodes - - -def get_material_nodes_to_randomise_all( - material_str: str = "Material", - node2randomise_prefix: str = config.DEFAULT_RANDOM_KEYWORD, -): - """Get list of all input nodes to randomise for a given material. - - Input nodes are defined as nodes with no input sockets. - The input nodes to randomise are identified because their - name is prefixed with node2randomise_prefix (case insensitive). - - Both independent nodes, and nodes inside a group are searched. - The 'artificial' nodes that show up inside a node group, usually named - 'Group input' or 'Group output' are excluded from the search. - - Parameters - ---------- - material_str : str, optional - name of the material, by default "Material" - node2randomise_prefix : str, optional - prefix that identifies the nodes to randomise, by default 'random' - - Returns - ------- - list - list of the input nodes to randomise - """ - - # find input nodes that startwith random - # in any of those groups - # excluding 'Group' nodes - list_indep_input_nodes = get_material_nodes_to_randomise_indep( - material_str, node2randomise_prefix - ) - - list_group_input_nodes = get_material_nodes_to_randomise_group( - material_str, node2randomise_prefix - ) - - return list_indep_input_nodes + list_group_input_nodes - - -def get_geometry_nodes_to_randomise( - node_group_str: str = "Geometry Nodes", - node2randomise_prefix: str = config.DEFAULT_RANDOM_KEYWORD, -): - # find input nodes that start with random - # excluding 'Group Inpuyt/Output' nodes, - # and any nested Group nodes - # NOTE: nested Group nodes are included in bpy.data.node_groups - # of type 'GEOMETRY' - list_input_nodes = get_nodes_to_randomise_from_list( - [ - nd - for nd in bpy.data.node_groups[node_group_str].nodes - if nd.type != "GROUP" # exclude groups inside of groups - ], - node2randomise_prefix, - ) - - return list_input_nodes - - -def get_gngs_linked_to_modifiers(object): - """Get geometry node groups that are linked to - modifiers of the object - - NOTE: Shader node groups cannot be linked to modifiers, - So all node groups will be geometry node groups - - Parameters - ---------- - object : _type_ - _description_ - - Returns - ------- - _type_ - _description_ - """ - - node_groups_linked_to_modifiers_of_object = [ - mod.node_group - for mod in object.modifiers - if hasattr(mod, "node_group") and hasattr(mod.node_group, "name") - ] - - return node_groups_linked_to_modifiers_of_object - - -def get_parent_of_gng( - node_group, -): - """Get immediate parent of geometry node group. - - Returns the node tree that the input geometry node group is - inside of. - - If the input geometry node group has no parent (i.e., it is a root node) - this function will return None for its parent - - Parameters - ---------- - node_group : _type_ - _description_ - - Returns - ------- - _type_ - _description_ - """ - - list_all_gngs = [ - gr for gr in bpy.data.node_groups if gr.type == "GEOMETRY" - ] - - parent_gng = None - if node_group is not None: - for gr in list_all_gngs: - for nd in gr.nodes: - if ( - (hasattr(nd, "node_tree")) - and (hasattr(nd.node_tree, "name")) - and (nd.node_tree.name == node_group.name) - ): - parent_gng = gr - break - - return parent_gng # immediate parent - - -def get_root_of_gng(geometry_node_group): - """Get root parent of input geometry node group - - It returns the root parent (i.e., the parent node group whose - parent is None), for the input geometry node group - - Parameters - ---------- - geometry_node_group : _type_ - _description_ - - Returns - ------- - _type_ - _description_ - """ - parent = get_parent_of_gng(geometry_node_group) - c = 1 # TODO: should this be 0? - while get_parent_of_gng(parent) is not None: - c += 1 - parent = get_parent_of_gng(parent) - - return (parent, c) - - -def get_map_inner_gngs( - list_candidate_root_node_groups, -): - """Compute dictionary that maps inner node groups of the - input root parent node groups, to a tuple made of - - the inner node group's root parent node group, - - the inner node group's depth - - The dictionary is computed for inner node groups whose root parents - are in the input list - - Parameters - ---------- - list_candidate_root_node_groups : _type_ - _description_ - """ - - list_node_groups = [ - gr for gr in bpy.data.node_groups if gr.type == "GEOMETRY" - ] - - map_node_group_to_root_node_group = { - gr: get_root_of_gng(gr) # tuple - for gr in list_node_groups - if get_root_of_gng(gr)[0] in list_candidate_root_node_groups - } - - return map_node_group_to_root_node_group - - -def get_selectable_node_for_node_group(geometry_node_group): - """Get node associated to a (inner) geometry node group - that allows for it to be selected - - An inner geometry node group will have a node associated with it - in the parent node tree. That node can be selected and set as active. - - Parameters - ---------- - geometry_node_group : _type_ - _description_ - - Returns - ------- - _type_ - _description_ - """ - parent_node_group = get_parent_of_gng(geometry_node_group) - - selectable_node = None - if parent_node_group is not None: - for nd in parent_node_group.nodes: - if ( - nd.type == "GROUP" - and (hasattr(nd, "node_tree")) - and (hasattr(nd.node_tree, "name")) - and (nd.node_tree.name == geometry_node_group.name) - ): - selectable_node = nd - break - - return selectable_node - - -def get_path_to_gng(gng): - """Compute path of parent geometry group nodes up - to the input geometry group node - - Both the root parent node group and the input - geometry group node are included - - Parameters - ---------- - gng : _type_ - inner geometry group need of which we want to - obtain the path - - Returns - ------- - path_to_gng: list - a list of parent geometry group nodes up to the input one - """ - - parent = get_parent_of_gng(gng) - if parent is None: - path_to_gng = [] - else: - path_to_gng = [parent] - while get_parent_of_gng(parent) is not None: - parent = get_parent_of_gng(parent) - path_to_gng.append(parent) - - path_to_gng.reverse() - - path_to_gng.append(gng) - return path_to_gng - - -def get_max_depth(root_parent_node_group): - """Compute the maximum depth of any inner geometry group - for the given root parent node group - - A root parent node group is a node group whose parent is - None - - Parameters - ---------- - root_parent_node_group : _type_ - root parent node group of which we want to compute the - maximum depth - - Returns - ------- - max_depth : int - the depth of the innermost node group - for this root parent node group - """ - map_inner_gngs_of_modifier = get_map_inner_gngs([root_parent_node_group]) - max_depth = 0 - if map_inner_gngs_of_modifier: - max_depth = max([v[1] for k, v in map_inner_gngs_of_modifier.items()]) - - return max_depth - - -def get_modifier_linked_to_gng(gng_name, context_active_object): - """Get the modifier of the currently active object - linked to the input geometry node group (GNG) name - - If there are no modifiers in the currently active object - linked to the input GNG, it will return None. - - Parameters - ---------- - gng_name : str - name of the GNG of interest - context_active_object : _type_ - currently active object - - Returns - ------- - subpanel_modifier - modifier of the currently active object of type - 'Geometry nodes' linked to the input GNG - """ - subpanel_modifier = None - for mod in context_active_object.modifiers: - if ( - hasattr(mod, "node_group") - and (hasattr(mod.node_group, "name")) - and (gng_name == mod.node_group.name) - ): - subpanel_modifier = mod - break - return subpanel_modifier - - -def set_gngs_graph_to_top_level(root_parent_node_group): - """Reset the geometry nodes graph view to - the root parent geometry node group - - Parameters - ---------- - root_parent_node_group : _type_ - - """ - max_depth = get_max_depth(root_parent_node_group) - for i in range(max_depth): - bpy.ops.node.group_edit(exit=True) # with exit=True we go up one level - - return From 502036f4c963507645c96547c316db087af0fb71 Mon Sep 17 00:00:00 2001 From: ruaridhg Date: Wed, 27 Sep 2023 16:16:34 +0100 Subject: [PATCH 30/58] Testing bug fix for int min-max custom prop --- randomiser/Test_int_min_max.py | 56 ++++++ .../collection_UD_socket_properties.py | 174 +++++++++++++++++- randomiser/test_setattr.py | 46 +++++ 3 files changed, 272 insertions(+), 4 deletions(-) create mode 100644 randomiser/Test_int_min_max.py create mode 100644 randomiser/test_setattr.py diff --git a/randomiser/Test_int_min_max.py b/randomiser/Test_int_min_max.py new file mode 100644 index 0000000..ba82dac --- /dev/null +++ b/randomiser/Test_int_min_max.py @@ -0,0 +1,56 @@ +from array import array + +import bpy +import numpy as np + +selfie = bpy.data.scenes["Scene"].socket_props_per_UD.collection[0] +m_str = "int_1d" + +min_int_np = np.int64(1) +max_int_np = np.int64(3) + +print("min_int_np = ", min_int_np) + +min_int_blender = int(min_int_np) +print("min_int_blender = ", min_int_blender) + +# min_array = getattr(self, "min_" + m_str) +# max_array = getattr(self, "max_" + m_str) +min_array = np.array(getattr(selfie, "min_" + m_str)) +max_array = np.array(getattr(selfie, "max_" + m_str)) + +# print("MIN CLOSURE min_array ", type(min_array)) +# MAX RECURSION DEPTH EXCEEDED WHILE CALLING A PYTHON OBJECT +# print("MIN CLOSURE max_array ", type(max_array)) + +# min_array = ast.literal_eval(str(min_array)) +# max_array = ast.literal_eval(str(max_array)) +# min_array = np.array(min_array) +# max_array = np.array(max_array) + +# min_array = [pyt_int.item() for pyt_int in min_array] +# max_array = [pyt_int.item() for pyt_int in max_array] + +# print("MIN CLOSURE min_array FIRST", type(min_array[0])) +# print("MIN CLOSURE max_array FIRST", type(max_array[0])) + +# min_array = np.asarray(min_array,dtype="int") +# max_array = np.asarray(max_array,dtype="int") +min_array = array("i", min_array) +max_array = array("i", max_array) +# min_array = np.array(min_array) +# max_array = np.array(max_array) + +# print("MIN CLOSURE min_array ", type(min_array)) +# print("MIN CLOSURE max_array ", type(max_array)) + +# print("MIN CLOSURE min_array FIRST", type(min_array[0])) +# print("MIN CLOSURE max_array FIRST", type(max_array[0])) +print(min_array > max_array) + +cond_min = [min > max for min, max in zip(min_array, max_array)] +# if (min_array > max_array).all(): +if any(cond_min): + cond = np.where(cond_min, max_array, min_array) +# print('np.where result = ', cond) +# print('np.where type = ', type(cond)) diff --git a/randomiser/define_prop/property_classes/collection_UD_socket_properties.py b/randomiser/define_prop/property_classes/collection_UD_socket_properties.py index 3921e6e..20a0565 100644 --- a/randomiser/define_prop/property_classes/collection_UD_socket_properties.py +++ b/randomiser/define_prop/property_classes/collection_UD_socket_properties.py @@ -1,4 +1,5 @@ import re +from array import array import bpy import numpy as np @@ -191,7 +192,17 @@ def constrain_min(self, context, m_str): # self is a 'SocketProperties' object min_array = np.array(getattr(self, "min_" + m_str)) max_array = np.array(getattr(self, "max_" + m_str)) + # min_array.tolist() + # max_array.tolist() + print("MIN CLOSURE min_array ", type(min_array)) + print("MIN CLOSURE max_array ", type(max_array)) + + print("MIN CLOSURE min_array FIRST", type(min_array[0])) + print("MIN CLOSURE max_array FIRST", type(max_array[0])) if any(min_array > max_array): + where_cond = np.where(min_array > max_array, max_array, min_array) + print("np.where", where_cond) + print("np.where type = ", type(where_cond)) setattr( self, "min_" + m_str, @@ -233,6 +244,8 @@ def constrain_max(self, context, m_str): # self is a 'SocketProperties' object min_array = np.array(getattr(self, "min_" + m_str)) max_array = np.array(getattr(self, "max_" + m_str)) + print("MAX CLOSURE min_array ", min_array) + print("MAX CLOSURE max_array ", max_array) if any(max_array < min_array): setattr( self, @@ -244,6 +257,149 @@ def constrain_max(self, context, m_str): return lambda slf, ctx: constrain_max(slf, ctx, m_str) +def constrain_min_closure_int(m_str): + """Constain min value with closure + + Parameters + ---------- + m_str : str + string specifying the socket attribute (e.g., int_1d) + + Returns + ------- + _type_ + lambda function evaluated at the specified m_str + + """ + + def constrain_min(self, context, m_str): + """Constrain min value + + If min > max --> min is reset to max value + (i.e., no randomisation) + + Parameters + ---------- + context : _type_ + _description_ + m_str : str + string specifying the socket attribute (e.g., int_1d) + """ + # self is a 'SocketProperties' object + # min_array = getattr(self, "min_" + m_str) + # max_array = getattr(self, "max_" + m_str) + min_array = np.array(getattr(self, "min_" + m_str)) + max_array = np.array(getattr(self, "max_" + m_str)) + + # print("MIN CLOSURE min_array ", type(min_array)) + # MAX RECURSION DEPTH EXCEEDED WHILE CALLING A PYTHON OBJECT + # print("MIN CLOSURE max_array ", type(max_array)) + + # min_array = ast.literal_eval(str(min_array)) + # max_array = ast.literal_eval(str(max_array)) + # min_array = np.array(min_array) + # max_array = np.array(max_array) + + # min_array = [pyt_int.item() for pyt_int in min_array] + # max_array = [pyt_int.item() for pyt_int in max_array] + + # print("MIN CLOSURE min_array FIRST", type(min_array[0])) + # print("MIN CLOSURE max_array FIRST", type(max_array[0])) + + # min_array = np.asarray(min_array,dtype="int") + # max_array = np.asarray(max_array,dtype="int") + min_array = array("i", min_array) + max_array = array("i", max_array) + # min_array = np.array(min_array) + # max_array = np.array(max_array) + + # print("MIN CLOSURE min_array ", type(min_array)) + # print("MIN CLOSURE max_array ", type(max_array)) + + # print("MIN CLOSURE min_array FIRST", type(min_array[0])) + # print("MIN CLOSURE max_array FIRST", type(max_array[0])) + # print(min_array > max_array) + + cond_min = [min > max for min, max in zip(min_array, max_array)] + # if (min_array > max_array).all(): + if any(cond_min): + cond = np.where(cond_min, max_array, min_array) + print("np.where result = ", cond) + print("np.where type = ", type(cond)) + + setattr( + self, + "min_" + m_str, + getattr(self, "max_" + m_str), + ) + + # try: + # setattr( + # self, + # "min_" + m_str, + # int(min_array[0]), + # ) + # except: + # print("int(min_array[0]) DID NOT WORK") + + # try: + # setattr( + # self, + # "min_" + m_str, + # getattr(self, "max_" + m_str), + # ) + # except: + # print('getattr(self, "min_" + m_str) DID NOT WORK') + + return + + return lambda slf, ctx: constrain_min(slf, ctx, m_str) + + +def constrain_max_closure_int(m_str): + """Constain max value with closure + + Parameters + ---------- + m_str : str + string specifying the socket attribute (e.g., float_1d) + + Returns + ------- + _type_ + lambda function evaluated at the specified m_str + + """ + + def constrain_max(self, context, m_str): + """Constrain max value + + if max < min --> max is reset to min value + (i.e., no randomisation) + + Parameters + ---------- + context : _type_ + _description_ + m_str : str + string specifying the socket attribute (e.g., float_1d) + """ + # self is a 'SocketProperties' object + min_array = np.array(getattr(self, "min_" + m_str)) + max_array = np.array(getattr(self, "max_" + m_str)) + + cond_max = [max < min for max, min in zip(max_array, min_array)] + if any(cond_max): + setattr( + self, + "max_" + m_str, + getattr(self, "min_" + m_str), + ) + return + + return lambda slf, ctx: constrain_max(slf, ctx, m_str) + + def constrain_rgba_closure(m_str): """Constain RGBA value with closure @@ -372,14 +528,24 @@ class SocketProperties(bpy.types.PropertyGroup): # ---------------------------- # int_1d + # int_1d_str = "int_1d" + # min_int_1d: bpy.props.IntVectorProperty( # type: ignore + # size=1, update=constrain_min_closure(int_1d_str) + # ) + + # max_int_1d: bpy.props.IntVectorProperty( # type: ignore + # size=1, update=constrain_max_closure(int_1d_str) + # ) int_1d_str = "int_1d" - min_int_1d: bpy.props.IntVectorProperty( # type: ignore - size=1, update=constrain_min_closure(int_1d_str) + min_int_1d_PROP = bpy.props.IntVectorProperty( # type: ignore + size=1, update=constrain_min_closure_int(int_1d_str) ) + min_int_1d: min_int_1d_PROP # type: ignore - max_int_1d: bpy.props.IntVectorProperty( # type: ignore - size=1, update=constrain_max_closure(int_1d_str) + max_int_1d_PROP = bpy.props.IntVectorProperty( # type: ignore + size=1, update=constrain_max_closure_int(int_1d_str) ) + max_int_1d: max_int_1d_PROP # type: ignore # ---------------------------- # bool_1d diff --git a/randomiser/test_setattr.py b/randomiser/test_setattr.py new file mode 100644 index 0000000..c6624c2 --- /dev/null +++ b/randomiser/test_setattr.py @@ -0,0 +1,46 @@ +import bpy + +bpy.data.scenes["Scene"].socket_props_per_UD.collection[0].min_int_1d[0] +selfie = bpy.data.scenes["Scene"].socket_props_per_UD.collection[0] +min_blend = selfie.min_int_1d[0] + +m_str = "int_1d" +print("min_blend value", min_blend) +print("min_blend type ==== ", type(min_blend)) + +getattr_val = getattr(selfie, "min_" + m_str) +print(getattr_val) +print("gettr_val type ==== ", type(getattr_val)) +print(getattr_val[0]) +print("getattr_val[0] type ===== ", type(getattr_val[0])) + + +# try: +# setattr( +# selfie, +# "min_" + m_str, +# min_blend, +# ) +# print("min_blend WORKING!!!!!!! type = ", type(min_blend)) +# except: +# print("min_blend NOT WORKING--- type = ", type(min_blend)) +# try: +# setattr( +# selfie, +# "min_" + m_str, +# getattr_val, +# ) +# print("getattr_val WORKING!!!!!!!!!!!! type = ", type(getattr_val)) +# except: +# print("getattr_val NOT WORKING--- type = ", type(getattr_val)) + + +# try: +# setattr( +# selfie, +# "min_" + m_str, +# getattr_val[0], +# ) +# print("getattr_val[0] WORKING!!!!!!! type = ", type(getattr_val[0])) +# except: +# print("getattr_val[0] NOT WORKING--- type = ", type(getattr_val[0])) From af0ec017880edb3a365318e09a6f1dbdca0dd2c9 Mon Sep 17 00:00:00 2001 From: ruaridhg Date: Wed, 27 Sep 2023 16:19:29 +0100 Subject: [PATCH 31/58] Set min-max for int in config.py --- randomiser/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/randomiser/config.py b/randomiser/config.py index 40045fa..9b10cf4 100644 --- a/randomiser/config.py +++ b/randomiser/config.py @@ -72,7 +72,7 @@ # bpy.types.NodeSocketFloat: {"min": -np.inf, "max": np.inf}, Vector: {"min": -np.inf, "max": np.inf}, float: {"min": -np.inf, "max": np.inf}, - int: {"min": int(-10), "max": int(10)}, + int: {"min": int(-2147483648), "max": int(2147483647)}, bool: {"min": False, "max": True}, # bpy.types.NodeSocketInt: { # "min": int(-1000), # -2147483648 From de48ea26eee77f51b7b4028c383f27fd7465433c Mon Sep 17 00:00:00 2001 From: ruaridhg Date: Wed, 27 Sep 2023 16:47:09 +0100 Subject: [PATCH 32/58] branch off rmg/define_prop - commented out update_sockets_collection in operators.py --- randomiser/define_prop/operators.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/randomiser/define_prop/operators.py b/randomiser/define_prop/operators.py index 8b55eb7..9a56a9b 100644 --- a/randomiser/define_prop/operators.py +++ b/randomiser/define_prop/operators.py @@ -202,10 +202,10 @@ def invoke(self, context, event): for UD_str in self.list_subpanel_UD_props_names: # get collection of socket properties for this GNG # ATT socket properties do not include the actual socket object - if cs.socket_props_per_UD.collection[ - UD_str - ].update_sockets_collection: - print("Collection of UD props sockets updated") + # if cs.socket_props_per_UD.collection[ + # UD_str + # ].update_sockets_collection: + # print("Collection of UD props sockets updated") cs.socket_props_per_UD.collection[UD_str].collection From af028b8626d7a9ed16365e3abdcfe3b4ee133353 Mon Sep 17 00:00:00 2001 From: ruaridhg Date: Thu, 28 Sep 2023 15:56:51 +0100 Subject: [PATCH 33/58] Added functionality to define_prop randomise button for int --- randomiser/define_prop/operators.py | 356 +++++++++++++++++++++------- 1 file changed, 265 insertions(+), 91 deletions(-) diff --git a/randomiser/define_prop/operators.py b/randomiser/define_prop/operators.py index 9a56a9b..60635db 100644 --- a/randomiser/define_prop/operators.py +++ b/randomiser/define_prop/operators.py @@ -3,6 +3,58 @@ import bpy import numpy as np +from .ui import attr_get_type, get_attr_only_str + + +def attr_set_val(obj, path, min_val, max_val, UD_type): + if "." in path: + # gives us: ('modifiers["Subsurf"]', 'levels') + # len_path = len(full_str.rsplit(".", config.MAX_NUMBER_OF_SUBPANELS)) + path_prop, path_attr = path.rsplit(".", 1) + + # same as: prop = obj.modifiers["Subsurf"] + prop = obj.path_resolve(path_prop) + else: + prop = obj + # single attribute such as name, location... etc + path_attr = path + + # same as: prop.levels = value + + try: + getattr(prop, path_attr) + except Exception: + # print("Property does not exist") + pass + # action = getattr(prop, path_attr) + + print("attr_set_val type ========================= ", UD_type) + # if UD_type==float: + # min_array = np.array(getattr(self, "min_" + m_str)) + # max_array = np.array(getattr(self, "max_" + m_str)) + + if UD_type == float: + print("HELLO FLOAT!!!!!!!") + # if rand_posx: + # getattr(context.scene.camera, value_str)[0] = uniform( + # loc_x_range[0], loc_x_range[1] + # ) + + # if rand_rotz: + # rand_z = uniform(rot_z_range[0], rot_z_range[1]) + # else: + # rand_z = uniform(0.0, 0.0) + + # vec = Vector([rand_x, rand_y, rand_z]) + + # bpy.data.objects["Camera"].rotation_euler[0] = vec[0] + else: + print("HELLO INTEGER!!!!!!!!!!!!!!!!!!!!") + value = random.randint(min_val, max_val) + # value = 1 + + setattr(prop, path_attr, value) + class CUSTOM_OT_actions(bpy.types.Operator): """Move items up and down, add and remove""" @@ -192,53 +244,65 @@ def invoke(self, context, event): # add list of GNGs to operator self # NOTE: this list should have been updated already, # when drawing the panel + + print("INVOKE!!!! ") cs = context.scene self.list_subpanel_UD_props_names = [ UD.name for UD in cs.socket_props_per_UD.collection ] - - # for every GNG:f save sockets to randomise + # for every GNG: save sockets to randomise self.sockets_to_randomise_per_UD = {} for UD_str in self.list_subpanel_UD_props_names: - # get collection of socket properties for this GNG - # ATT socket properties do not include the actual socket object # if cs.socket_props_per_UD.collection[ # UD_str - # ].update_sockets_collection: - # print("Collection of UD props sockets updated") + # ].update_UD_props_collection: + # print("Collection of UD props updated") - cs.socket_props_per_UD.collection[UD_str].collection + # sockets_props_collection = cs.socket_props_per_gng.collection[ + # gng_str + # ].collection # get candidate sockets for this GNG - cs.socket_props_per_UD.collection[UD_str].candidate_sockets + # candidate_sockets = cs.socket_props_per_UD.collection[ + # UD_str + # ].candidate_sockets - # # if socket unlinked and randomisation toggle is True: - # # modify socket props to set toggle to False - # self.sockets_to_randomise_per_UD[UD_str] = [] + #### REFACTOR to invalid prop instead of unlinked geom sckt + #### OPTION 1 for loop witin for loop cand_sockets + # if socket unlinked and randomisation toggle is True: + # modify socket props to set toggle to False + # self.socket_props_per_UD[UD_str] = [] # for sckt in candidate_sockets: - # # get socket identifier string - # sckt_id = sckt.node.name + "_" + sckt.name - - # # if this socket is selected to randomise but it is unlinked: - # # set randomisation toggle to False - # if (not sckt.is_linked) and ( - # sockets_props_collection[sckt_id].bool_randomise - # ): - # setattr( - # sockets_props_collection[sckt_id], - # "bool_randomise", - # False, - # ) - # print( - # f"Socket {sckt_id} from {gng_str} is unlinked:", - # "randomisation toggle set to False", - # ) - - # # after modifying randomisation toggle - # # save list of sockets to randomise to dict, - # # with key = material - # if sockets_props_collection[sckt_id].bool_randomise: - # self.sockets_to_randomise_per_gng[gng_str].append(sckt) + # print('sck for loop ===== ', sckt) + # if cs.socket_props_per_UD.collection[UD_str].bool_randomise: + # self.sockets_to_randomise_per_UD[UD_str].append(sckt) + + #### REFACTOR to invalid prop instead of unlinked geom sckt + #### OPTION 2 skip for loop for cand_sockets + # if this UD is selected to randomise but it is invalid property: + # set randomisation toggle to False + # if (not sckt.is_linked) and ( + # sockets_props_collection[sckt_id].bool_randomise + # ): + # setattr( + # sockets_props_collection[sckt_id], + # "bool_randomise", + # False, + # ) + # print( + # f"Socket {sckt_id} from {gng_str} is unlinked:", + # "randomisation toggle set to False", + # ) + + self.sockets_to_randomise_per_UD[UD_str] = [] + sckt = cs.socket_props_per_UD.collection[UD_str].name + if cs.socket_props_per_UD.collection[UD_str].bool_randomise: + self.sockets_to_randomise_per_UD[UD_str].append(sckt) + + print( + "INVOKE ==== sockets to randomise ", + list(self.sockets_to_randomise_per_UD), + ) return self.execute(context) @@ -260,74 +324,184 @@ def execute(self, context): """ cs = context.scene - # For every GNG with a subpanel - for UD_str in self.list_subpanel_UD_names: + # For every GNG with a subpanel - REFACTORING BASED ON NEW CODE + print( + "EXECUTE list_subpanel_prop_names ==== ", + self.list_subpanel_UD_props_names, + ) + for UD_str in self.list_subpanel_UD_props_names: # get collection of socket properties for this material # NOTE: socket properties do not include the actual socket object sockets_props_collection = cs.socket_props_per_UD.collection[ UD_str - ].collection - - # Loop through the sockets to randomise - for sckt in self.sockets_to_randomise_per_UD[UD_str]: - socket_id = "sckt.node.name" + "_" + sckt.name - - # get min value for this socket - min_val = np.array( - getattr( - sockets_props_collection[socket_id], - "min_" + cs.socket_type_to_attr[type(sckt)], - ) + ] + + # #### Modify to set_attr !!!!!!! + full_str = sockets_props_collection.name + attribute_only_str = get_attr_only_str(full_str) + + if "bpy.context.scene" in full_str: + attr_type = attr_get_type( + bpy.context.scene, attribute_only_str + )[0] + elif "bpy.data.objects" in full_str: + attr_type = attr_get_type( + bpy.data.objects["Cube"], attribute_only_str + )[0] + + # get min value for this socket + min_val = np.array( + getattr( + sockets_props_collection, + "min_" + cs.UD_prop_to_attr[attr_type], ) + ) - # get max value for this socket - max_val = np.array( - getattr( - sockets_props_collection[socket_id], - "max_" + cs.socket_type_to_attr[type(sckt)], - ) + # get max value for this socket + max_val = np.array( + getattr( + sockets_props_collection, + "max_" + cs.UD_prop_to_attr[attr_type], + ) + ) + + if "bpy.context.scene" in full_str: + attr_set_val( + bpy.context.scene, + attribute_only_str, + min_val, + max_val, + attr_type, + ) + elif "bpy.data.objects" in full_str: + attr_set_val( + bpy.data.objects["Cube"], + attribute_only_str, + min_val, + max_val, + attr_type, ) - # set default value - # if socket type is boolean - if type(sckt) == bpy.types.BoolProperty: - sckt.default_value = random.choice( - [bool(list(m_val)[0]) for m_val in [min_val, max_val]] - ) # 1d only - # TODO: change for a faster option? - # bool(random.getrandbits(1))F - # https://stackoverflow.com/questions/6824681/get-a-random-boolean-in-python - - # if socket type is int - elif type(sckt) == bpy.types.IntProperty: - sckt.default_value = random.randint(max_val, min_val) - - # for all other socket types - else: - # if type of the socket is NodeSocketColor, - # and max_val < min_val: switch them before randomising - # NOTE: these are not switched in the display panel - # (this is intended) - if (type(sckt) == bpy.types.NodeSocketColor) and any( - max_val < min_val - ): - max_val_new = np.where( - max_val >= min_val, max_val, min_val - ) - min_val_new = np.where( - min_val < max_val, min_val, max_val - ) - - # TODO: is there a more elegant way? - # feels a bit clunky.... - max_val = max_val_new - min_val = min_val_new - - # assign randomised socket value - sckt.default_value = random.uniform(min_val, max_val) + # # get min value for this socket + # min_val = np.array( + # getattr( + # sockets_props_collection[socket_id], + # "min_" + cs.socket_type_to_attr[type(sckt)], + # ) + # ) + + # # get max value for this socket + # max_val = np.array( + # getattr( + # sockets_props_collection[socket_id], + # "max_" + cs.socket_type_to_attr[type(sckt)], + # ) + # ) + + # # set default value + # # if socket type is boolean + # #### WHERE IS DEFAULT VALUE - ADD TO UI.PY TO APPEAR ON PANEL + # if type(sckt) == bpy.types.BoolProperty: + # sckt.default_value = random.choice( + # [bool(list(m_val)[0]) for m_val in [min_val, max_val]] + # ) # 1d only + # # TODO: change for a faster option? + # # bool(random.getrandbits(1))F + # # https://stackoverflow.com/questions/6824681/get-a-random-boolean-in-python + + # # if socket type is int + # elif type(sckt) == bpy.types.IntProperty: + # sckt.default_value = random.randint(max_val, min_val) + + # # for all other socket types + # else: + # # if type of the socket is NodeSocketColor, + # # and max_val < min_val: switch them before randomising + # # NOTE: these are not switched in the display panel + # # (this is intended) + # if (type(sckt) == bpy.types.NodeSocketColor) and any( + # max_val < min_val + # ): + # max_val_new = np.where( + # max_val >= min_val, max_val, min_val + # ) + # min_val_new = np.where( + # min_val < max_val, min_val, max_val + # ) + + # # TODO: is there a more elegant way? + # # feels a bit clunky.... + # max_val = max_val_new + # min_val = min_val_new + + # # assign randomised socket value + # sckt.default_value = random.uniform(min_val, max_val) return {"FINISHED"} + # # sckt = list_UD_props_sorted # UD.name + # sckt = sockets_props_collection.name + # # Loop through the sockets to randomise + # for sckt in self.sockets_to_randomise_per_UD[UD_str]: + # socket_id = "sckt.node.name" + "_" + sckt.name + + # # get min value for this socket + # min_val = np.array( + # getattr( + # sockets_props_collection[socket_id], + # "min_" + cs.socket_type_to_attr[type(sckt)], + # ) + # ) + + # # get max value for this socket + # max_val = np.array( + # getattr( + # sockets_props_collection[socket_id], + # "max_" + cs.socket_type_to_attr[type(sckt)], + # ) + # ) + + # # set default value + # # if socket type is boolean + # if type(sckt) == bpy.types.BoolProperty: + # sckt.default_value = random.choice( + # [bool(list(m_val)[0]) for m_val in + # [min_val, max_val]] + # ) # 1d only + # # TODO: change for a faster option? + # # bool(random.getrandbits(1))F + # # https://stackoverflow.com/questions/6824681/get-a-random-boolean-in-python + + # # if socket type is int + # elif type(sckt) == bpy.types.IntProperty: + # sckt.default_value = random.randint(max_val, min_val) + + # # for all other socket types + # else: + # # if type of the socket is NodeSocketColor, + # # and max_val < min_val: switch them before randomising + # # NOTE: these are not switched in the display panel + # # (this is intended) + # if (type(sckt) == bpy.types.NodeSocketColor) and any( + # max_val < min_val + # ): + # max_val_new = np.where( + # max_val >= min_val, max_val, min_val + # ) + # min_val_new = np.where( + # min_val < max_val, min_val, max_val + # ) + + # # TODO: is there a more elegant way? + # # feels a bit clunky.... + # max_val = max_val_new + # min_val = min_val_new + + # # assign randomised socket value + # sckt.default_value = random.uniform(min_val, max_val) + + # return {"FINISHED"} + # NOTE: without the persistent decorator, # the function is removed from the handlers' list From d5ba2f543f2d8e1abe381c593d2d06ca1a2654d0 Mon Sep 17 00:00:00 2001 From: ruaridhg Date: Thu, 28 Sep 2023 16:00:12 +0100 Subject: [PATCH 34/58] Brought in test.yml from main --- .github/workflows/test.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 96f57af..45ab97c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -32,8 +32,5 @@ jobs: run: | python -m pip install --upgrade pip pip install pre-commit cookiecutter pyyaml pytest pytest-cov toml - - name: Test with pytest - run: | - pytest - name: Report coverage to codecov uses: codecov/codecov-action@v2 From fdf7c7d9233c5b571668a6a9c70b85dfacb4b5b4 Mon Sep 17 00:00:00 2001 From: ruaridhg Date: Thu, 28 Sep 2023 16:02:44 +0100 Subject: [PATCH 35/58] Commented out print statements in ui.py --- randomiser/define_prop/ui.py | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/randomiser/define_prop/ui.py b/randomiser/define_prop/ui.py index cabca4a..e14bdbd 100644 --- a/randomiser/define_prop/ui.py +++ b/randomiser/define_prop/ui.py @@ -46,7 +46,7 @@ def draw_sockets_list_UD( # selected in the graph it moves to the bottom of the panel. list_UD_props_sorted = list_UD_props # sorted(list_UD_props, key=lambda x: x.name) - print("IN draw_sockets_list_UD ==== ", list_UD_props_sorted) + # print("IN draw_sockets_list_UD ==== ", list_UD_props_sorted) # for i_n, UD_str in enumerate(list_UD_props_sorted): row = layout.row() @@ -141,11 +141,11 @@ def draw_sockets_list_UD( else: # bpy.types.NodeSocketBool: for m_str, col in zip(["min", "max"], [col3, col4]): attr_type = attr_get_type(bpy.context.scene, attribute_only_str)[0] - print( - "sockets_props_collection ???????", - sockets_props_collection, - ) - print("type ??????????? ", attribute_only_str, attr_type) + # print( + # "sockets_props_collection ???????", + # sockets_props_collection, + # ) + # print("type ??????????? ", attribute_only_str, attr_type) col.prop( sockets_props_collection, # [socket_id], m_str + "_" + cs.UD_prop_to_attr[attr_type], @@ -483,8 +483,8 @@ def draw(self, context): self.subpanel_UD_idx ] # .collection - print("sockets_props_collection = ", sockets_props_collection) - print(" and name ========== ", sockets_props_collection.name) + # print("sockets_props_collection = ", sockets_props_collection) + # print(" and name ========== ", sockets_props_collection.name) # # Get list of input nodes to randomise for this subpanel's GNG # [sckt.name.split("_")[0] for sckt in sockets_props_collection] @@ -497,7 +497,7 @@ def draw(self, context): list_parent_nodes_str[0] + ".", "" ) - print("list_parent_nodes_str = ", attribute_only_str) + # print("list_parent_nodes_str = ", attribute_only_str) # full_list = [prop.name for prop in list(C.scene.custom)] list_all_UD_props = [ @@ -512,15 +512,15 @@ def draw(self, context): # bpy.data.node_groups[subpanel_gng.name].nodes[nd_str] # for nd_str in list_parent_nodes_str ] - print("list_all_UD_props ====== ", list_all_UD_props) - print( - "bpy.context.scene.custom_index == ", - bpy.context.scene.custom_index, - ) + # print("list_all_UD_props ====== ", list_all_UD_props) + # print( + # "bpy.context.scene.custom_index == ", + # bpy.context.scene.custom_index, + # ) list_current_UD_props = list_all_UD_props[ bpy.context.scene.custom_index ].name - print("list_current_UD_props =======", list_current_UD_props) + # print("list_current_UD_props =======", list_current_UD_props) # Draw sockets to randomise per input node, including their # current value and min/max boundaries From 54b793084904533e65af84c40482d14ce5dc0435 Mon Sep 17 00:00:00 2001 From: ruaridhg Date: Thu, 28 Sep 2023 16:19:47 +0100 Subject: [PATCH 36/58] Fixed naming of each subpanel --- randomiser/define_prop/ui.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/randomiser/define_prop/ui.py b/randomiser/define_prop/ui.py index e14bdbd..acd0af6 100644 --- a/randomiser/define_prop/ui.py +++ b/randomiser/define_prop/ui.py @@ -61,7 +61,8 @@ def draw_sockets_list_UD( row_split.column(align=True) # input node name - col1.label(text=list_UD_props_sorted) # UD.name + print(list_UD_props_sorted) + col1.label(text=sockets_props_collection.name) # UD.name col1.alignment = "CENTER" # min label From c4daa37a7fe9f21867095adbcc93b53202045651 Mon Sep 17 00:00:00 2001 From: ruaridhg Date: Thu, 28 Sep 2023 16:33:57 +0100 Subject: [PATCH 37/58] Commented out attempt at default value not found fix --- randomiser/define_prop/ui.py | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/randomiser/define_prop/ui.py b/randomiser/define_prop/ui.py index acd0af6..850428d 100644 --- a/randomiser/define_prop/ui.py +++ b/randomiser/define_prop/ui.py @@ -83,9 +83,10 @@ def draw_sockets_list_UD( row = layout.row() row_split = row.split() col1 = row_split.column(align=True) - col2 = row_split.column(align=True) + row_split.column(align=True) col3 = row_split.column(align=True) col4 = row_split.column(align=True) + col5 = row_split.column(align=True) row_split.column(align=True) # UD prop name @@ -93,11 +94,23 @@ def draw_sockets_list_UD( col1.label(text="value") # text=sckt.name) # socket current value - # col2.prop( - # UD, - # "default_value", #####Default value not found - # icon_only=True, - # ) + # if "bpy.context.scene" in full_str: + # col2.prop( + # getattr( + # bpy.context.scene, + # attribute_only_str + # ) + # # sockets_props_collection, + # # "default_value", #####Default value not found + # # icon_only=True, + # ) + # elif "bpy.data.objects" in full_str: + # col2.prop( + # getattr( + # bpy.data.objects["Cube"], + # attribute_only_str + # ) + # ) # col2.enabled = False # current value is not editable # # socket min and max columns @@ -157,8 +170,7 @@ def draw_sockets_list_UD( # e.g. min_float_1d so m_str + "_" + float_1d # randomisation toggle - # col5.prop( - col2.prop( + col5.prop( sockets_props_collection, # [socket_id], "bool_randomise", icon_only=True, From 4ea8084f571091a43ada16dbcb8a31a000a7a783 Mon Sep 17 00:00:00 2001 From: ruaridhg Date: Fri, 29 Sep 2023 16:56:27 +0100 Subject: [PATCH 38/58] bpy.context.scene.objects['Cube'].collision.absorption case subpanel added --- randomiser/__init__.py | 8 +- .../define_prop/obj_path_resolve_test.py | 148 ++++++++---------- .../property_classes/collection_UD_props.py | 61 +++++--- randomiser/define_prop/ui.py | 109 ++++++++++--- 4 files changed, 201 insertions(+), 125 deletions(-) diff --git a/randomiser/__init__.py b/randomiser/__init__.py index a16590c..3756b41 100644 --- a/randomiser/__init__.py +++ b/randomiser/__init__.py @@ -18,8 +18,8 @@ def register(): seed.register() # transform.register() - material.register() - geometry.register() + # material.register() + # geometry.register() define_prop.register() # random_all.register() @@ -27,8 +27,8 @@ def register(): def unregister(): seed.unregister() # transform.unregister() - material.unregister() - geometry.unregister() + # material.unregister() + # geometry.unregister() define_prop.unregister() # random_all.unregister() diff --git a/randomiser/define_prop/obj_path_resolve_test.py b/randomiser/define_prop/obj_path_resolve_test.py index 75cd820..40d6861 100644 --- a/randomiser/define_prop/obj_path_resolve_test.py +++ b/randomiser/define_prop/obj_path_resolve_test.py @@ -1,5 +1,3 @@ -import bpy - # %% full_str = "bpy.data.objects['Cube'].location" # path_prop, path_attr = full_str.rsplit("[]", 1) @@ -58,85 +56,77 @@ def attr_get_type(obj, path): # setattr(prop, path_attr, value) -print("CAMERA.LOCATION !!!!!!!!!!!!!!!") -print("INPUTS ======") -attr_str = "camera.location" -print("bpy.context.scene as obj and str = ", attr_str) -action_type, action, prop, path_attr = attr_get_type( - bpy.context.scene, attr_str -) -print("OUTPUTS ===== ") -print("type(action) = ", action_type) -print("action = ", action) -print("prop = ", prop) -print("path_attr = ", path_attr) - -print("FRAME_CURRENT !!!!!!!!!!!!!!!") -print("INPUTS ======") -attr_str = "frame_current" -print("bpy.context.scene as obj and str = ", attr_str) -action_type, action, prop, path_attr = attr_get_type( - bpy.context.scene, attr_str -) -print("OUTPUTS ===== ") -print("type(action) = ", action_type) -print("action = ", action) -print("prop = ", prop) -print("path_attr = ", path_attr) - - -# bpy.context.scene.objects['Cube'].collision.absorption -# bpy.data.objects['Cube'].location -# bpy.data.objects['Cube'].rotation_euler.x -print("bpy.data.objects[Cube].location !!!!!!!!!!!!!!!") -print("INPUTS ======") -# attr_str = 'location' +# print("CAMERA.LOCATION !!!!!!!!!!!!!!!") +# print("INPUTS ======") +# attr_str = "camera.location" +# print("bpy.context.scene as obj and str = ", attr_str) +# action_type, action, prop, path_attr = attr_get_type( +# bpy.context.scene, attr_str +# ) +# print("OUTPUTS ===== ") +# print("type(action) = ", action_type) +# print("action = ", action) +# print("prop = ", prop) +# print("path_attr = ", path_attr) + +##print("FRAME_CURRENT !!!!!!!!!!!!!!!") +##print("INPUTS ======") +##attr_str = "frame_current" +##print("bpy.context.scene as obj and str = ", attr_str) +##action_type, action, prop, path_attr = attr_get_type( +## bpy.context.scene, attr_str +##) +##print("OUTPUTS ===== ") +##print("type(action) = ", action_type) +##print("action = ", action) +##print("prop = ", prop) +##print("path_attr = ", path_attr) + + +## bpy.context.scene.objects['Cube'].collision.absorption +## bpy.data.objects['Cube'].location +## bpy.data.objects['Cube'].rotation_euler.x +# print("bpy.data.objects[Cube] + string !!!!!!!!!!!!!!!") +# print("INPUTS ======") +##attr_str = 'location' # attr_str = 'rotation_euler.x' -attr_str = "collision.absorption" -print("bpy.context.scene as obj and str = ", attr_str) -action_type, action, prop, path_attr = attr_get_type( - bpy.data.objects["Cube"], attr_str -) -print("OUTPUTS ===== ") -print("type(action) = ", action_type) -print("action = ", action) -print("prop = ", prop) -print("path_attr = ", path_attr) - -# def attr_get_type(obj, path): -# type_object='objectsdfas' - -# if type_object=='objdfdsects': - -# print("[Cube] HERE") - -# elif "." in path: - -# path_prop, path_attr = path.rsplit(".", 1) - - -# prop = obj.path_resolve(path_prop) - -# elif type_object=='objects': - -# pdb.set_trace - -# prop = obj - - -# path_attr = path - - -# - -# try: - -# action = getattr(prop, path_attr) +##attr_str = "collision.absorption" +# print("bpy.data.objects[Cube] as obj and str = ", attr_str) +# action_type, action, prop, path_attr = attr_get_type( +# bpy.data.objects["Cube"], attr_str +# ) +# print("OUTPUTS ===== ") +# print("type(action) = ", action_type) +# print("action = ", action) +# print("prop = ", prop) +# print("path_attr = ", path_attr) + + +def get_attr_only_str(full_str): + if "[" in full_str: + len_path = len(full_str.rsplit(".", 100)) - 1 + print("len_path = ", len_path) + list_parent_nodes_str = full_str.rsplit(".", len_path - 3) + print("list_parent_nodes_str = ", list_parent_nodes_str) + attribute_only_str = full_str.replace( + list_parent_nodes_str[0] + ".", "" + ) + else: + len_path = len(full_str.rsplit(".", 100)) + print("len_path = ", len_path) + list_parent_nodes_str = full_str.rsplit(".", len_path - 3) + print("list_parent_nodes_str = ", list_parent_nodes_str) + attribute_only_str = full_str.replace( + list_parent_nodes_str[0] + ".", "" + ) -# except Exception: + return attribute_only_str -# action = "dummy" -# +full_str = "bpy.context.scene.objects['Cube'].collision.absorption" +attr_only_str = get_attr_only_str(full_str) +print("attr_only_str = ", attr_only_str) -# return type(action), action, prop, path_attr +# full_str='bpy.context.scene.camera.location' +# attr_only_str=get_attr_only_str(full_str) +# print('attr_only_str = ', attr_only_str) diff --git a/randomiser/define_prop/property_classes/collection_UD_props.py b/randomiser/define_prop/property_classes/collection_UD_props.py index e98a403..4acda9f 100644 --- a/randomiser/define_prop/property_classes/collection_UD_props.py +++ b/randomiser/define_prop/property_classes/collection_UD_props.py @@ -160,29 +160,48 @@ def candidate_UD_props(self): # getter method # get_attr_only_strbpy.context.scene.custom # self is the collection of node groups - list_UD_props = [ - UD - for UD in bpy.context.scene.custom - if ( + list_UD_props = [] + for UD in bpy.context.scene.custom: + if "[" in UD.name: + print("ERROR ======= UD.name", UD.name) + print("ERROR ======== attr_str", get_attr_only_str(UD.name)) + if ( + attr_get_type( + bpy.data.objects["Cube"], get_attr_only_str(UD.name) + )[1] + != "dummy" + ): + list_UD_props.append(UD) + elif ( attr_get_type(bpy.context.scene, get_attr_only_str(UD.name))[1] != "dummy" - ) - # != "dummy" - # if attr_get_type(bpy.context.scene,UD)[2] != 'dummy' - # nd - # for nd in bpy.data.node_groups - # if nd.type == "GEOMETRY" - # and ( - # any( - # [ - # ni.name.lower().startswith( - # config.DEFAULT_RANDOM_KEYWORD - # ) - # for ni in nd.nodes - # ] - # ) - # ) - ] + ): + list_UD_props.append(UD) + + # list_UD_props = [ + # UD + # for UD in bpy.context.scene.custom + # if ( + # attr_get_type(bpy.context.scene, + # get_attr_only_str(UD.name))[1] + # != "dummy" + # ) + # # != "dummy" + # # if attr_get_type(bpy.context.scene,UD)[2] != 'dummy' + # # nd + # # for nd in bpy.data.node_groups + # # if nd.type == "GEOMETRY" + # # and ( + # # any( + # # [ + # # ni.name.lower().startswith( + # # config.DEFAULT_RANDOM_KEYWORD + # # ) + # # for ni in nd.nodes + # # ] + # # ) + # # ) + # ] # print("type list_UD_props ========== ", type(list_UD_props[0])) # # sort by name # list_node_groups = sorted( diff --git a/randomiser/define_prop/ui.py b/randomiser/define_prop/ui.py index 850428d..2d0d940 100644 --- a/randomiser/define_prop/ui.py +++ b/randomiser/define_prop/ui.py @@ -154,12 +154,44 @@ def draw_sockets_list_UD( # (may not need other cases) else: # bpy.types.NodeSocketBool: for m_str, col in zip(["min", "max"], [col3, col4]): - attr_type = attr_get_type(bpy.context.scene, attribute_only_str)[0] + print( + "DRAW SOCKETS LIST sockets_props_collection.name ====== ", + sockets_props_collection.name, + ) + if "[" in sockets_props_collection.name: + print( + "DRAW SOCKETS LIST sockets_props_collection.name ====== ", + sockets_props_collection.name, + ) + print( + "DRAW SOCKETS LIST attribute_only_str ====== ", + attribute_only_str, + ) + attr_type = attr_get_type( + bpy.context.scene.objects["Cube"], attribute_only_str + )[0] + print("DRAW SOCKETS LIST attr_type ====== ", attr_type) + else: + print( + "DRAW SOCKETS LIST sockets_props_collection.name ====== ", + sockets_props_collection.name, + ) + print( + "DRAW SOCKETS LIST attribute_only_str ====== ", + attribute_only_str, + ) + attr_type = attr_get_type( + bpy.context.scene, attribute_only_str + )[0] + print("DRAW SOCKETS LIST attr_type ====== ", attr_type) + # print( # "sockets_props_collection ???????", # sockets_props_collection, # ) # print("type ??????????? ", attribute_only_str, attr_type) + + print("ERROR HERE in line 172 of ui.py") col.prop( sockets_props_collection, # [socket_id], m_str + "_" + cs.UD_prop_to_attr[attr_type], @@ -178,13 +210,26 @@ def draw_sockets_list_UD( def attr_get_type(obj, path): + # if '[' in path: + # print(' [ is in path') + if "." in path: # gives us: ('modifiers["Subsurf"]', 'levels') # len_path = len(full_str.rsplit(".", config.MAX_NUMBER_OF_SUBPANELS)) path_prop, path_attr = path.rsplit(".", 1) + print("if statement ==== ") + print( + "FROM rsplit . path_prop for resolve = ", + path_prop, + " and path_attr for getattr = ", + path_attr, + ) + print("obj used for path_resolve = ", obj) + # same as: prop = obj.modifiers["Subsurf"] prop = obj.path_resolve(path_prop) + print("prop from path_resolve for get_attr = ", prop) else: prop = obj # single attribute such as name, location... etc @@ -204,7 +249,12 @@ def attr_get_type(obj, path): def get_attr_only_str(full_str): - len_path = len(full_str.rsplit(".", config.MAX_NUMBER_OF_SUBPANELS)) + if "[" in full_str: + mod = 1 + else: + mod = 0 + + len_path = len(full_str.rsplit(".", config.MAX_NUMBER_OF_SUBPANELS)) - mod list_parent_nodes_str = full_str.rsplit(".", len_path - 3) attribute_only_str = full_str.replace(list_parent_nodes_str[0] + ".", "") @@ -414,11 +464,11 @@ def poll(cls, context): if "bpy.context.scene" in full_str: prop_type, action, prop, path_attr = attr_get_type( bpy.context.scene, attribute_only_str - ) + )[0] elif "bpy.data.objects" in full_str: prop_type, action, prop, path_attr = attr_get_type( bpy.data.objects["Cube"], attribute_only_str - ) + )[0] print("prop_type", prop_type) print("action", action) @@ -504,27 +554,44 @@ def draw(self, context): # Get list of input nodes to randomise for this subpanel's GNG full_str = sockets_props_collection.name - len_path = len(full_str.rsplit(".", config.MAX_NUMBER_OF_SUBPANELS)) - list_parent_nodes_str = full_str.rsplit(".", len_path - 3) - attribute_only_str = full_str.replace( - list_parent_nodes_str[0] + ".", "" - ) + attribute_only_str = get_attr_only_str(full_str) + + # len_path = len(full_str.rsplit(".", config.MAX_NUMBER_OF_SUBPANELS)) + # list_parent_nodes_str = full_str.rsplit(".", len_path - 3) + # attribute_only_str = full_str.replace( + # list_parent_nodes_str[0] + ".", "" + # ) # print("list_parent_nodes_str = ", attribute_only_str) # full_list = [prop.name for prop in list(C.scene.custom)] - list_all_UD_props = [ - UD_str - for UD_str in list(bpy.context.scene.custom) - if attr_get_type( - bpy.context.scene, get_attr_only_str(UD_str.name) - )[1] - != "dummy" - # bpy.context.scene.custom[UD_str] - # for UD_str in list_parent_UD_str - # bpy.data.node_groups[subpanel_gng.name].nodes[nd_str] - # for nd_str in list_parent_nodes_str - ] + list_all_UD_props = [] + for UD_str in bpy.context.scene.custom: + print("ERROR ====== UD_str", UD_str) + if "[" in UD_str.name: + if ( + attr_get_type( + bpy.data.objects["Cube"], + get_attr_only_str(UD_str.name), + )[1] + != "dummy" + ): + list_all_UD_props.append(UD_str) + elif ( + attr_get_type( + bpy.context.scene, get_attr_only_str(UD_str.name) + )[1] + != "dummy" + ): + list_all_UD_props.append(UD_str) + # list_all_UD_props = [ + # UD_str + # for UD_str in list(bpy.context.scene.custom) + # if attr_get_type( + # bpy.context.scene, get_attr_only_str(UD_str.name) + # )[1] + # != "dummy" + # ] # print("list_all_UD_props ====== ", list_all_UD_props) # print( # "bpy.context.scene.custom_index == ", From f3f0145762936f3451ebb1207ee60a50384fa97c Mon Sep 17 00:00:00 2001 From: ruaridhg Date: Mon, 2 Oct 2023 16:02:40 +0100 Subject: [PATCH 39/58] mod used in get_attr_only_str --- randomiser/define_prop/ui.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/randomiser/define_prop/ui.py b/randomiser/define_prop/ui.py index 2d0d940..d8906fc 100644 --- a/randomiser/define_prop/ui.py +++ b/randomiser/define_prop/ui.py @@ -242,6 +242,10 @@ def attr_get_type(obj, path): except Exception: # print("Property does not exist") action = "dummy" + prop = "dummy" + path_attr = "dummy" + print(action, prop, path_attr) + print(type(action)) # action = getattr(prop, path_attr) return type(action), action, prop, path_attr @@ -249,7 +253,9 @@ def attr_get_type(obj, path): def get_attr_only_str(full_str): - if "[" in full_str: + if "data" in full_str: + mod = 0 + elif "[" in full_str: mod = 1 else: mod = 0 @@ -592,7 +598,7 @@ def draw(self, context): # )[1] # != "dummy" # ] - # print("list_all_UD_props ====== ", list_all_UD_props) + print("list_all_UD_props ====== ", list_all_UD_props) # print( # "bpy.context.scene.custom_index == ", # bpy.context.scene.custom_index, From 4b12faf67fbd537fc3c08a6bd9a403a81d3cac31 Mon Sep 17 00:00:00 2001 From: ruaridhg Date: Tue, 3 Oct 2023 17:17:05 +0100 Subject: [PATCH 40/58] obj_path_resolve_test.py now includes randomising --- install_randomiser.sh | 2 +- .../define_prop/obj_path_resolve_test.py | 220 ++++++++++++++++-- randomiser/define_prop/operators.py | 7 + randomiser/define_prop/ui.py | 1 + 4 files changed, 214 insertions(+), 16 deletions(-) diff --git a/install_randomiser.sh b/install_randomiser.sh index fb10f7c..8982069 100755 --- a/install_randomiser.sh +++ b/install_randomiser.sh @@ -4,4 +4,4 @@ source ~/.bash_profile # zip randomiser, launch blender and install+enable zip randomiser.zip -FS -r randomiser/ -blender sample.blend --python install_and_enable_addons.py -- ./randomiser.zip +blender define_prop.blend --python install_and_enable_addons.py -- ./randomiser.zip diff --git a/randomiser/define_prop/obj_path_resolve_test.py b/randomiser/define_prop/obj_path_resolve_test.py index 40d6861..a688697 100644 --- a/randomiser/define_prop/obj_path_resolve_test.py +++ b/randomiser/define_prop/obj_path_resolve_test.py @@ -1,3 +1,11 @@ +import random + +import bpy +import numpy as np + +# from random import random +from mathutils import Vector + # %% full_str = "bpy.data.objects['Cube'].location" # path_prop, path_attr = full_str.rsplit("[]", 1) @@ -103,22 +111,20 @@ def attr_get_type(obj, path): def get_attr_only_str(full_str): - if "[" in full_str: - len_path = len(full_str.rsplit(".", 100)) - 1 - print("len_path = ", len_path) - list_parent_nodes_str = full_str.rsplit(".", len_path - 3) - print("list_parent_nodes_str = ", list_parent_nodes_str) - attribute_only_str = full_str.replace( - list_parent_nodes_str[0] + ".", "" - ) + if "data" in full_str: + mod = 0 + elif "[" in full_str: + mod = 1 else: - len_path = len(full_str.rsplit(".", 100)) - print("len_path = ", len_path) - list_parent_nodes_str = full_str.rsplit(".", len_path - 3) - print("list_parent_nodes_str = ", list_parent_nodes_str) - attribute_only_str = full_str.replace( - list_parent_nodes_str[0] + ".", "" - ) + mod = 0 + + len_path = len(full_str.rsplit(".", 100)) - mod + print("len_path = ", len_path) + + list_parent_nodes_str = full_str.rsplit(".", len_path - 3) + print("list_parent_nodes_str = ", list_parent_nodes_str) + + attribute_only_str = full_str.replace(list_parent_nodes_str[0] + ".", "") return attribute_only_str @@ -130,3 +136,187 @@ def get_attr_only_str(full_str): # full_str='bpy.context.scene.camera.location' # attr_only_str=get_attr_only_str(full_str) # print('attr_only_str = ', attr_only_str) + + +def attr_set_val(obj, path, min_val, max_val, UD_type): + if "." in path: + # gives us: ('modifiers["Subsurf"]', 'levels') + # len_path = len(full_str.rsplit(".", config.MAX_NUMBER_OF_SUBPANELS)) + path_prop, path_attr = path.rsplit(".", 1) + + # same as: prop = obj.modifiers["Subsurf"] + prop = obj.path_resolve(path_prop) + else: + prop = obj + # single attribute such as name, location... etc + path_attr = path + + # same as: prop.levels = value + + try: + getattr(prop, path_attr) + except Exception: + # print("Property does not exist") + pass + # action = getattr(prop, path_attr) + + print("attr_set_val type ========================= ", UD_type) + # if UD_type==float: + # min_array = np.array(getattr(self, "min_" + m_str)) + # max_array = np.array(getattr(self, "max_" + m_str)) + + if UD_type == float: + print("HELLO 1D FLOAT!!!!!!!") + # if rand_posx: + # getattr(context.scene.camera, value_str)[0] = uniform( + # loc_x_range[0], loc_x_range[1] + # ) + + # if rand_rotz: + # rand_z = uniform(rot_z_range[0], rot_z_range[1]) + # else: + # rand_z = uniform(0.0, 0.0) + + # vec = Vector([rand_x, rand_y, rand_z]) + + # bpy.data.objects["Camera"].rotation_euler[0] = vec[0] + + value = random.uniform(min_val, max_val) + print(value) + elif UD_type == Vector: + print("HELLO 3D VECTOR FLOAT!!!!!") + value = random.uniform(min_val, max_val) + print(value) + else: + print("HELLO INTEGER!!!!!!!!!!!!!!!!!!!!") + value = random.randint(min_val, max_val) + print(value) + + setattr(prop, path_attr, value) + + +#### camera.location +full_str = "bpy.context.scene.camera.location" +attr_only_str = get_attr_only_str(full_str) +print("attr_only_str = ", attr_only_str) + +path_prop, path_attr = "camera.location".rsplit(".", 1) +prop = bpy.context.scene.path_resolve(path_prop) +action = getattr(prop, path_attr) +print("prop = ", prop) +print("action getattr = ", action) + +prop_type, action, prop, path_attr = attr_get_type( + bpy.context.scene, attr_only_str +) + + +min_val = np.array( + getattr( + bpy.context.scene.socket_props_per_UD.collection[0], + "min_float_3d", + ) +) + +max_val = np.array( + getattr( + bpy.context.scene.socket_props_per_UD.collection[0], + "max_float_3d", + ) +) +print(min_val) +print(max_val) +print(prop_type) +# min_val = bpy.context.scene.socket_props_per_UD.collection[0].min_float_1d[0] +# max_val = bpy.context.scene.socket_props_per_UD.collection[0].max_float_1d[0] +attr_set_val( + bpy.context.scene, + attr_only_str, + min_val, + max_val, + prop_type, +) + + +#### frame_current +full_str = "bpy.context.scene.frame_current" +attr_only_str = get_attr_only_str(full_str) +print("attr_only_str = ", attr_only_str) + +prop = bpy.context.scene +# single attribute such as name, location... etc +path_attr = "frame_current" +action = getattr(prop, path_attr) +print("prop = ", prop) +print("action getattr = ", action) + +prop_type, action, prop, path_attr = attr_get_type( + bpy.context.scene, attr_only_str +) + + +min_val = np.array( + getattr( + bpy.context.scene.socket_props_per_UD.collection[1], + "min_int_1d", + ) +) + +max_val = np.array( + getattr( + bpy.context.scene.socket_props_per_UD.collection[1], + "max_int_1d", + ) +) +print(min_val) +print(max_val) +print(prop_type) +# min_val = bpy.context.scene.socket_props_per_UD.collection[0].min_float_1d[0] +# max_val = bpy.context.scene.socket_props_per_UD.collection[0].max_float_1d[0] +attr_set_val( + bpy.context.scene, + attr_only_str, + min_val, + max_val, + prop_type, +) + +#### collision.absorption +full_str = "bpy.context.scene.objects['Cube'].collision.absorption" +attr_only_str = get_attr_only_str(full_str) +print("attr_only_str = ", attr_only_str) + +path_prop, path_attr = "collision.absorption".rsplit(".", 1) +prop = bpy.context.scene.objects["Cube"].path_resolve(path_prop) +action = getattr(prop, path_attr) +print("prop = ", prop) +print("action getattr = ", action) + +prop_type, action, prop, path_attr = attr_get_type( + bpy.data.objects["Cube"], attr_only_str +) + + +min_val = getattr( + bpy.context.scene.socket_props_per_UD.collection[2], + "min_float_1d", +) + + +max_val = np.array( + getattr( + bpy.context.scene.socket_props_per_UD.collection[2], + "max_float_1d", + ) +) +print(min_val) +print(max_val) +print(prop_type) + +attr_set_val( + bpy.data.objects["Cube"], + attr_only_str, + min_val, + max_val, + prop_type, +) diff --git a/randomiser/define_prop/operators.py b/randomiser/define_prop/operators.py index 60635db..449cec0 100644 --- a/randomiser/define_prop/operators.py +++ b/randomiser/define_prop/operators.py @@ -2,6 +2,7 @@ import bpy import numpy as np +from mathutils import Vector from .ui import attr_get_type, get_attr_only_str @@ -48,6 +49,8 @@ def attr_set_val(obj, path, min_val, max_val, UD_type): # vec = Vector([rand_x, rand_y, rand_z]) # bpy.data.objects["Camera"].rotation_euler[0] = vec[0] + elif UD_type == Vector: + print("HELLO 3D VECTOR FLOAT!!!!!") else: print("HELLO INTEGER!!!!!!!!!!!!!!!!!!!!") value = random.randint(min_val, max_val) @@ -366,6 +369,8 @@ def execute(self, context): ) if "bpy.context.scene" in full_str: + print("bpy.context.scene") + print("OPS EXECUTE attribute_only_str ", attribute_only_str) attr_set_val( bpy.context.scene, attribute_only_str, @@ -374,6 +379,8 @@ def execute(self, context): attr_type, ) elif "bpy.data.objects" in full_str: + print("bpy.context.scene") + print("OPS EXECUTE attribute_only_str ", attribute_only_str) attr_set_val( bpy.data.objects["Cube"], attribute_only_str, diff --git a/randomiser/define_prop/ui.py b/randomiser/define_prop/ui.py index d8906fc..41a4387 100644 --- a/randomiser/define_prop/ui.py +++ b/randomiser/define_prop/ui.py @@ -466,6 +466,7 @@ def poll(cls, context): full_str = sockets_props_collection.name attribute_only_str = get_attr_only_str(full_str) + print("SUBPANEL poll attribute_only_str ", attribute_only_str) if "bpy.context.scene" in full_str: prop_type, action, prop, path_attr = attr_get_type( From ebf9420ccca7f4cc421c6fd5e1e1c154a2834e51 Mon Sep 17 00:00:00 2001 From: ruaridhg Date: Wed, 4 Oct 2023 17:23:44 +0100 Subject: [PATCH 41/58] Can randomise all UD_props and hardcoded Cube str replaced with idx --- .../define_prop/obj_path_resolve_test.py | 70 ++++++++++------ randomiser/define_prop/operators.py | 63 ++++++++------ .../property_classes/collection_UD_props.py | 31 ++++++- randomiser/define_prop/ui.py | 84 +++++++++++++++++-- 4 files changed, 187 insertions(+), 61 deletions(-) diff --git a/randomiser/define_prop/obj_path_resolve_test.py b/randomiser/define_prop/obj_path_resolve_test.py index a688697..289b445 100644 --- a/randomiser/define_prop/obj_path_resolve_test.py +++ b/randomiser/define_prop/obj_path_resolve_test.py @@ -2,25 +2,27 @@ import bpy import numpy as np - -# from random import random from mathutils import Vector + # %% -full_str = "bpy.data.objects['Cube'].location" -# path_prop, path_attr = full_str.rsplit("[]", 1) -x = full_str.split(".") -# print(path_prop) -for i in x: - print(i) - if "[" in i: - ii = i.split("[") - fiinal = ii[1] - fiinal = fiinal[:-1] - print("fiinal= ", fiinal) +def get_obj_str(full_str): + full_str = "bpy.data.objects['Cube'].location" + # path_prop, path_attr = full_str.rsplit("[]", 1) + x = full_str.split(".") + # print(path_prop) + for i in x: + print(i) + if "[" in i: + ii = i.split("[") + fiinal = ii[1] + fiinal = fiinal[:-1] + print("fiinal= ", fiinal) + + print(type(fiinal)) + print(fiinal) + return fiinal -print(type(fiinal)) -print(fiinal) # %% @@ -210,7 +212,6 @@ def attr_set_val(obj, path, min_val, max_val, UD_type): bpy.context.scene, attr_only_str ) - min_val = np.array( getattr( bpy.context.scene.socket_props_per_UD.collection[0], @@ -254,7 +255,6 @@ def attr_set_val(obj, path, min_val, max_val, UD_type): bpy.context.scene, attr_only_str ) - min_val = np.array( getattr( bpy.context.scene.socket_props_per_UD.collection[1], @@ -313,10 +313,32 @@ def attr_set_val(obj, path, min_val, max_val, UD_type): print(max_val) print(prop_type) -attr_set_val( - bpy.data.objects["Cube"], - attr_only_str, - min_val, - max_val, - prop_type, -) +objects_in_scene = [] +for i, key in enumerate(bpy.data.objects): + print(i) + print(key.name) + objects_in_scene.append(key.name) + + +if "[" in full_str: + # obj=bpy.data.objects.get(fiinal) + obj_str = get_obj_str(full_str) + + print("obj_str = ", obj_str) + for i, obj in enumerate(objects_in_scene): + print(obj) + print(i) + # regex=re.compile(r'^test-\d+$') + + if obj in obj_str: + print("Yay found cube") + print(i) + idx = i + + attr_set_val( + bpy.data.objects[idx], + attr_only_str, + min_val, + max_val, + prop_type, + ) diff --git a/randomiser/define_prop/operators.py b/randomiser/define_prop/operators.py index 449cec0..11ba5e8 100644 --- a/randomiser/define_prop/operators.py +++ b/randomiser/define_prop/operators.py @@ -4,7 +4,7 @@ import numpy as np from mathutils import Vector -from .ui import attr_get_type, get_attr_only_str +from .ui import attr_get_type, get_attr_only_str, get_obj_str def attr_set_val(obj, path, min_val, max_val, UD_type): @@ -30,31 +30,19 @@ def attr_set_val(obj, path, min_val, max_val, UD_type): # action = getattr(prop, path_attr) print("attr_set_val type ========================= ", UD_type) - # if UD_type==float: - # min_array = np.array(getattr(self, "min_" + m_str)) - # max_array = np.array(getattr(self, "max_" + m_str)) if UD_type == float: - print("HELLO FLOAT!!!!!!!") - # if rand_posx: - # getattr(context.scene.camera, value_str)[0] = uniform( - # loc_x_range[0], loc_x_range[1] - # ) - - # if rand_rotz: - # rand_z = uniform(rot_z_range[0], rot_z_range[1]) - # else: - # rand_z = uniform(0.0, 0.0) - - # vec = Vector([rand_x, rand_y, rand_z]) - - # bpy.data.objects["Camera"].rotation_euler[0] = vec[0] + print("HELLO 1D FLOAT!!!!!!!") + value = random.uniform(min_val, max_val) + print(value) elif UD_type == Vector: print("HELLO 3D VECTOR FLOAT!!!!!") + value = random.uniform(min_val, max_val) + print(value) else: print("HELLO INTEGER!!!!!!!!!!!!!!!!!!!!") value = random.randint(min_val, max_val) - # value = 1 + print(value) setattr(prop, path_attr, value) @@ -343,13 +331,32 @@ def execute(self, context): full_str = sockets_props_collection.name attribute_only_str = get_attr_only_str(full_str) - if "bpy.context.scene" in full_str: + print("ATTRIBUTE ONLY STRING ======== ", attribute_only_str) + + objects_in_scene = [] + for key in bpy.data.objects: + objects_in_scene.append(key.name) + + if "[" in full_str: + print("bpy.context.scene") + print("OPS EXECUTE attribute_only_str ", attribute_only_str) + obj_str = get_obj_str(full_str) + # print(obj_str) + + for i, obj in enumerate(objects_in_scene): + # regex=re.compile(r'^test-\d+$') + + if obj in obj_str: + print("Yay found cube") + + idx = i + attr_type = attr_get_type( - bpy.context.scene, attribute_only_str + bpy.data.objects[idx], attribute_only_str )[0] - elif "bpy.data.objects" in full_str: + elif "bpy.context.scene" in full_str: attr_type = attr_get_type( - bpy.data.objects["Cube"], attribute_only_str + bpy.context.scene, attribute_only_str )[0] # get min value for this socket @@ -368,21 +375,23 @@ def execute(self, context): ) ) - if "bpy.context.scene" in full_str: + if "[" in full_str: print("bpy.context.scene") print("OPS EXECUTE attribute_only_str ", attribute_only_str) + attr_set_val( - bpy.context.scene, + bpy.data.objects[idx], attribute_only_str, min_val, max_val, attr_type, ) - elif "bpy.data.objects" in full_str: + + elif "bpy.context.scene" in full_str: print("bpy.context.scene") print("OPS EXECUTE attribute_only_str ", attribute_only_str) attr_set_val( - bpy.data.objects["Cube"], + bpy.context.scene, attribute_only_str, min_val, max_val, diff --git a/randomiser/define_prop/property_classes/collection_UD_props.py b/randomiser/define_prop/property_classes/collection_UD_props.py index 4acda9f..5844c15 100644 --- a/randomiser/define_prop/property_classes/collection_UD_props.py +++ b/randomiser/define_prop/property_classes/collection_UD_props.py @@ -1,7 +1,7 @@ import bpy from ..property_classes.collection_UD_socket_properties import SocketProperties -from ..ui import attr_get_type, get_attr_only_str +from ..ui import attr_get_type, get_attr_only_str, get_obj_str # --------------------------------------------------- @@ -161,13 +161,35 @@ def candidate_UD_props(self): # getter method # get_attr_only_strbpy.context.scene.custom # self is the collection of node groups list_UD_props = [] + objects_in_scene = [] + for i, key in enumerate(bpy.data.objects): + # print(i) + # print(key.name) + objects_in_scene.append(key.name) for UD in bpy.context.scene.custom: if "[" in UD.name: - print("ERROR ======= UD.name", UD.name) - print("ERROR ======== attr_str", get_attr_only_str(UD.name)) + # print("ERROR ======= UD.name", UD.name) + # print("ERROR ======== attr_str", get_attr_only_str(UD.name)) + obj_str = get_obj_str(UD.name) + # print(obj_str) + + for i, obj in enumerate(objects_in_scene): + # regex=re.compile(r'^test-\d+$') + + if obj in obj_str: + print("Yay found cube") + idx = i + + # if ( + # attr_get_type( + # bpy.data.objects["Cube"], get_attr_only_str(UD.name) + # )[1] + # != "dummy" + # ): + if ( attr_get_type( - bpy.data.objects["Cube"], get_attr_only_str(UD.name) + bpy.data.objects[idx], get_attr_only_str(UD.name) )[1] != "dummy" ): @@ -208,6 +230,7 @@ def candidate_UD_props(self): # getter method # list_materials, # key=lambda mat: mat.name.lower() # ) + print(list_UD_props) return list_UD_props diff --git a/randomiser/define_prop/ui.py b/randomiser/define_prop/ui.py index 41a4387..29993b2 100644 --- a/randomiser/define_prop/ui.py +++ b/randomiser/define_prop/ui.py @@ -153,6 +153,10 @@ def draw_sockets_list_UD( ##### REFACTOR EASY CASE FIRST # (may not need other cases) else: # bpy.types.NodeSocketBool: + objects_in_scene = [] + for key in bpy.data.objects: + objects_in_scene.append(key.name) + for m_str, col in zip(["min", "max"], [col3, col4]): print( "DRAW SOCKETS LIST sockets_props_collection.name ====== ", @@ -167,8 +171,22 @@ def draw_sockets_list_UD( "DRAW SOCKETS LIST attribute_only_str ====== ", attribute_only_str, ) + + obj_str = get_obj_str(sockets_props_collection.name) + # print(obj_str) + + for i, obj in enumerate(objects_in_scene): + # regex=re.compile(r'^test-\d+$') + + if obj in obj_str: + print("Yay found cube") + + idx = i + # attr_type = attr_get_type( + # bpy.context.scene.objects["Cube"], attribute_only_str + # )[0] attr_type = attr_get_type( - bpy.context.scene.objects["Cube"], attribute_only_str + bpy.data.objects[idx], attribute_only_str )[0] print("DRAW SOCKETS LIST attr_type ====== ", attr_type) else: @@ -209,6 +227,18 @@ def draw_sockets_list_UD( ) +def get_obj_str(full_str): + x = full_str.split(".") + # print(path_prop) + for i in x: + if "[" in i: + ii = i.split("[") + object_str = ii[1] + object_str = object_str[:-1] + + return object_str + + def attr_get_type(obj, path): # if '[' in path: # print(' [ is in path') @@ -468,13 +498,32 @@ def poll(cls, context): attribute_only_str = get_attr_only_str(full_str) print("SUBPANEL poll attribute_only_str ", attribute_only_str) - if "bpy.context.scene" in full_str: + objects_in_scene = [] + for key in bpy.data.objects: + objects_in_scene.append(key.name) + + if "[" in full_str: + obj_str = get_obj_str(full_str) + # print(obj_str) + + for i, obj in enumerate(objects_in_scene): + # regex=re.compile(r'^test-\d+$') + + if obj in obj_str: + print("Yay found ", obj) + + idx = i + prop_type, action, prop, path_attr = attr_get_type( - bpy.context.scene, attribute_only_str + bpy.data.objects[idx], attribute_only_str )[0] - elif "bpy.data.objects" in full_str: + # prop_type, action, prop, path_attr = attr_get_type( + # bpy.data.objects["Cube"], attribute_only_str + # )[0] + + elif "bpy.context.scene" in full_str: prop_type, action, prop, path_attr = attr_get_type( - bpy.data.objects["Cube"], attribute_only_str + bpy.context.scene, attribute_only_str )[0] print("prop_type", prop_type) @@ -575,10 +624,33 @@ def draw(self, context): list_all_UD_props = [] for UD_str in bpy.context.scene.custom: print("ERROR ====== UD_str", UD_str) + objects_in_scene = [] + for key in bpy.data.objects: + objects_in_scene.append(key.name) + if "[" in UD_str.name: + obj_str = get_obj_str(UD_str.name) + # print(obj_str) + + for i, obj in enumerate(objects_in_scene): + # regex=re.compile(r'^test-\d+$') + + if obj in obj_str: + print("Yay found cube") + + idx = i + + # if ( + # attr_get_type( + # bpy.data.objects["Cube"], + # get_attr_only_str(UD_str.name), + # )[1] + # != "dummy" + # ): + if ( attr_get_type( - bpy.data.objects["Cube"], + bpy.data.objects[idx], get_attr_only_str(UD_str.name), )[1] != "dummy" From f9605ecff0894a66ae5d3b4a9245b65b4d5516c5 Mon Sep 17 00:00:00 2001 From: ruaridhg Date: Wed, 4 Oct 2023 17:29:01 +0100 Subject: [PATCH 42/58] Added previous seed code to operators execute similar to other panels --- randomiser/define_prop/operators.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/randomiser/define_prop/operators.py b/randomiser/define_prop/operators.py index 11ba5e8..9d7ae27 100644 --- a/randomiser/define_prop/operators.py +++ b/randomiser/define_prop/operators.py @@ -1,4 +1,5 @@ import random +from random import seed import bpy import numpy as np @@ -315,6 +316,13 @@ def execute(self, context): """ cs = context.scene + previous_seed = cs.seed_properties.seed_previous + current_seed = cs.seed_properties.seed + seed_enabled = cs.seed_properties.seed_toggle + if (previous_seed != current_seed) and (seed_enabled is True): + seed(current_seed) + cs.seed_properties.seed_previous = current_seed + # For every GNG with a subpanel - REFACTORING BASED ON NEW CODE print( "EXECUTE list_subpanel_prop_names ==== ", From b420f6dc7060e6bb6039a5af04dbc1c7e37cfd76 Mon Sep 17 00:00:00 2001 From: ruaridhg Date: Wed, 4 Oct 2023 18:18:09 +0100 Subject: [PATCH 43/58] Define_props bool randomise working --- randomiser/define_prop/operators.py | 9 ++-- .../property_classes/collection_UD_props.py | 4 +- randomiser/define_prop/ui.py | 42 +++++++++---------- 3 files changed, 28 insertions(+), 27 deletions(-) diff --git a/randomiser/define_prop/operators.py b/randomiser/define_prop/operators.py index 9d7ae27..380db8e 100644 --- a/randomiser/define_prop/operators.py +++ b/randomiser/define_prop/operators.py @@ -244,6 +244,7 @@ def invoke(self, context, event): ] # for every GNG: save sockets to randomise self.sockets_to_randomise_per_UD = {} + self.sockets_to_randomise_per_UD = [] for UD_str in self.list_subpanel_UD_props_names: # if cs.socket_props_per_UD.collection[ # UD_str @@ -286,10 +287,9 @@ def invoke(self, context, event): # "randomisation toggle set to False", # ) - self.sockets_to_randomise_per_UD[UD_str] = [] sckt = cs.socket_props_per_UD.collection[UD_str].name if cs.socket_props_per_UD.collection[UD_str].bool_randomise: - self.sockets_to_randomise_per_UD[UD_str].append(sckt) + self.sockets_to_randomise_per_UD.append(sckt) print( "INVOKE ==== sockets to randomise ", @@ -326,9 +326,10 @@ def execute(self, context): # For every GNG with a subpanel - REFACTORING BASED ON NEW CODE print( "EXECUTE list_subpanel_prop_names ==== ", - self.list_subpanel_UD_props_names, + self.sockets_to_randomise_per_UD, ) - for UD_str in self.list_subpanel_UD_props_names: + # for UD_str in self.list_subpanel_UD_props_names: + for UD_str in self.sockets_to_randomise_per_UD: # get collection of socket properties for this material # NOTE: socket properties do not include the actual socket object sockets_props_collection = cs.socket_props_per_UD.collection[ diff --git a/randomiser/define_prop/property_classes/collection_UD_props.py b/randomiser/define_prop/property_classes/collection_UD_props.py index 5844c15..112c938 100644 --- a/randomiser/define_prop/property_classes/collection_UD_props.py +++ b/randomiser/define_prop/property_classes/collection_UD_props.py @@ -177,7 +177,7 @@ def candidate_UD_props(self): # getter method # regex=re.compile(r'^test-\d+$') if obj in obj_str: - print("Yay found cube") + # print("Yay found cube") idx = i # if ( @@ -230,7 +230,7 @@ def candidate_UD_props(self): # getter method # list_materials, # key=lambda mat: mat.name.lower() # ) - print(list_UD_props) + # print(list_UD_props) return list_UD_props diff --git a/randomiser/define_prop/ui.py b/randomiser/define_prop/ui.py index 29993b2..30c153e 100644 --- a/randomiser/define_prop/ui.py +++ b/randomiser/define_prop/ui.py @@ -61,7 +61,7 @@ def draw_sockets_list_UD( row_split.column(align=True) # input node name - print(list_UD_props_sorted) + # print(list_UD_props_sorted) col1.label(text=sockets_props_collection.name) # UD.name col1.alignment = "CENTER" @@ -179,7 +179,7 @@ def draw_sockets_list_UD( # regex=re.compile(r'^test-\d+$') if obj in obj_str: - print("Yay found cube") + # print("Yay found cube") idx = i # attr_type = attr_get_type( @@ -248,18 +248,18 @@ def attr_get_type(obj, path): # len_path = len(full_str.rsplit(".", config.MAX_NUMBER_OF_SUBPANELS)) path_prop, path_attr = path.rsplit(".", 1) - print("if statement ==== ") - print( - "FROM rsplit . path_prop for resolve = ", - path_prop, - " and path_attr for getattr = ", - path_attr, - ) - print("obj used for path_resolve = ", obj) + # print("if statement ==== ") + # print( + # "FROM rsplit . path_prop for resolve = ", + # path_prop, + # " and path_attr for getattr = ", + # path_attr, + # ) + # print("obj used for path_resolve = ", obj) # same as: prop = obj.modifiers["Subsurf"] prop = obj.path_resolve(path_prop) - print("prop from path_resolve for get_attr = ", prop) + # print("prop from path_resolve for get_attr = ", prop) else: prop = obj # single attribute such as name, location... etc @@ -274,8 +274,8 @@ def attr_get_type(obj, path): action = "dummy" prop = "dummy" path_attr = "dummy" - print(action, prop, path_attr) - print(type(action)) + # print(action, prop, path_attr) + # print(type(action)) # action = getattr(prop, path_attr) return type(action), action, prop, path_attr @@ -510,7 +510,7 @@ def poll(cls, context): # regex=re.compile(r'^test-\d+$') if obj in obj_str: - print("Yay found ", obj) + # print("Yay found ", obj) idx = i @@ -526,10 +526,10 @@ def poll(cls, context): bpy.context.scene, attribute_only_str )[0] - print("prop_type", prop_type) - print("action", action) - print("prop", prop) - print("path_attr", path_attr) + # print("prop_type", prop_type) + # print("action", action) + # print("prop", prop) + # print("path_attr", path_attr) else: action = "dummy" @@ -623,7 +623,7 @@ def draw(self, context): # full_list = [prop.name for prop in list(C.scene.custom)] list_all_UD_props = [] for UD_str in bpy.context.scene.custom: - print("ERROR ====== UD_str", UD_str) + # print("ERROR ====== UD_str", UD_str) objects_in_scene = [] for key in bpy.data.objects: objects_in_scene.append(key.name) @@ -636,7 +636,7 @@ def draw(self, context): # regex=re.compile(r'^test-\d+$') if obj in obj_str: - print("Yay found cube") + # print("Yay found cube") idx = i @@ -671,7 +671,7 @@ def draw(self, context): # )[1] # != "dummy" # ] - print("list_all_UD_props ====== ", list_all_UD_props) + # print("list_all_UD_props ====== ", list_all_UD_props) # print( # "bpy.context.scene.custom_index == ", # bpy.context.scene.custom_index, From b57293bf236f180c24fdcb5a01cb84f1a3077a2c Mon Sep 17 00:00:00 2001 From: ruaridhg Date: Mon, 9 Oct 2023 14:24:36 +0100 Subject: [PATCH 44/58] Added UD examples that don't work to test file --- .../define_prop/obj_path_resolve_test.py | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/randomiser/define_prop/obj_path_resolve_test.py b/randomiser/define_prop/obj_path_resolve_test.py index 289b445..df0c299 100644 --- a/randomiser/define_prop/obj_path_resolve_test.py +++ b/randomiser/define_prop/obj_path_resolve_test.py @@ -342,3 +342,50 @@ def attr_set_val(obj, path, min_val, max_val, UD_type): max_val, prop_type, ) + + +####Tom examples - bpy.data.objects["Cube"].location[0] +# bpy.data.objects["Cube"].rotation_euler +full_str = "bpy.data.cameras['Camera'].dof.aperture_fstop" +attr_only_str = get_attr_only_str(full_str) +print("attr_only_str = ", attr_only_str) + + +path_prop, path_attr = "dof.aperture_fstop".rsplit(".", 1) +prop = bpy.data.cameras["Camera"].path_resolve(path_prop) +action = getattr(prop, path_attr) +print("prop = ", prop) +print("action getattr = ", action) + +prop_type, action, prop, path_attr = attr_get_type( + bpy.data.cameras["Camera"], attr_only_str +) + + +# min_val = np.array( +# getattr( +# bpy.context.scene.socket_props_per_UD.collection[0], +# "min_float_3d", +# ) +# ) + +# max_val = np.array( +# getattr( +# bpy.context.scene.socket_props_per_UD.collection[0], +# "max_float_3d", +# ) +# ) +# print(min_val) +# print(max_val) +# print(prop_type) +## min_val = bpy.context.scene.socket_props_per_UD. +# collection[0].min_float_1d[0] +## max_val = bpy.context.scene.socket_props_per_UD. +# collection[0].max_float_1d[0] +# attr_set_val( +# bpy.context.scene, +# attr_only_str, +# min_val, +# max_val, +# prop_type, +# ) From 402f7dbb9c2fb5d6cb835402517121f1146d64fb Mon Sep 17 00:00:00 2001 From: ruaridhg Date: Mon, 9 Oct 2023 17:03:13 +0100 Subject: [PATCH 45/58] Removed redundant up/down arrows from UD props panel --- randomiser/define_prop/operators.py | 44 ++++++++++++++--------------- randomiser/define_prop/ui.py | 14 ++++----- 2 files changed, 28 insertions(+), 30 deletions(-) diff --git a/randomiser/define_prop/operators.py b/randomiser/define_prop/operators.py index 380db8e..703c6e5 100644 --- a/randomiser/define_prop/operators.py +++ b/randomiser/define_prop/operators.py @@ -53,13 +53,11 @@ class CUSTOM_OT_actions(bpy.types.Operator): bl_idname = "custom.list_action" bl_label = "List Actions" - bl_description = "Move items up and down, add and remove" + bl_description = "Add and remove items" bl_options = {"REGISTER"} action_prop = bpy.props.EnumProperty( items=( - ("UP", "Up", ""), - ("DOWN", "Down", ""), ("REMOVE", "Remove", ""), ("ADD", "Add", ""), ) @@ -75,27 +73,27 @@ def invoke(self, context, event): except IndexError: pass else: - if self.action == "DOWN" and idx < len(scn.custom) - 1: - scn.custom[idx + 1].name - scn.custom.move(idx, idx + 1) - scn.custom += 1 - info = 'Item "%s" moved to position %d' % ( - item.name, - scn.custom + 1, - ) - self.report({"INFO"}, info) - - elif self.action == "UP" and idx >= 1: - scn.custom[idx - 1].name - scn.custom.move(idx, idx - 1) - scn.custom_index -= 1 - info = 'Item "%s" moved to position %d' % ( - item.name, - scn.custom_index + 1, - ) - self.report({"INFO"}, info) + # if self.action == "DOWN" and idx < len(scn.custom) - 1: + # scn.custom[idx + 1].name + # scn.custom.move(idx, idx + 1) + # scn.custom += 1 + # info = 'Item "%s" moved to position %d' % ( + # item.name, + # scn.custom + 1, + # ) + # self.report({"INFO"}, info) + + # elif self.action == "UP" and idx >= 1: + # scn.custom[idx - 1].name + # scn.custom.move(idx, idx - 1) + # scn.custom_index -= 1 + # info = 'Item "%s" moved to position %d' % ( + # item.name, + # scn.custom_index + 1, + # ) + # self.report({"INFO"}, info) - elif self.action == "REMOVE": + if self.action == "REMOVE": info = 'Item "%s" removed from list' % (scn.custom[idx].name) scn.custom_index -= 1 scn.custom.remove(idx) diff --git a/randomiser/define_prop/ui.py b/randomiser/define_prop/ui.py index 30c153e..13aa673 100644 --- a/randomiser/define_prop/ui.py +++ b/randomiser/define_prop/ui.py @@ -418,13 +418,13 @@ def draw(self, context): col.operator( "custom.list_action", icon="ZOOM_OUT", text="" ).action = "REMOVE" - col.separator() - col.operator( - "custom.list_action", icon="TRIA_UP", text="" - ).action = "UP" - col.operator( - "custom.list_action", icon="TRIA_DOWN", text="" - ).action = "DOWN" + # col.separator() + # col.operator( + # "custom.list_action", icon="TRIA_UP", text="" + # ).action = "UP" + # col.operator( + # "custom.list_action", icon="TRIA_DOWN", text="" + # ).action = "DOWN" row = layout.row() col = row.column(align=True) From 7838d44099797922d39915ddb79b970c23cba12c Mon Sep 17 00:00:00 2001 From: ruaridhg Date: Thu, 12 Oct 2023 16:19:55 +0100 Subject: [PATCH 46/58] Fixed bpy.data.cameras[Camera].dof.aperture_fstop example --- randomiser/define_prop/operators.py | 39 +++++++---- .../property_classes/collection_UD_props.py | 29 +++++--- randomiser/define_prop/ui.py | 66 +++++++++++++------ 3 files changed, 95 insertions(+), 39 deletions(-) diff --git a/randomiser/define_prop/operators.py b/randomiser/define_prop/operators.py index 703c6e5..9864bf7 100644 --- a/randomiser/define_prop/operators.py +++ b/randomiser/define_prop/operators.py @@ -354,13 +354,20 @@ def execute(self, context): # regex=re.compile(r'^test-\d+$') if obj in obj_str: - print("Yay found cube") + current_obj = obj + # print("Found ", current_obj) idx = i - attr_type = attr_get_type( - bpy.data.objects[idx], attribute_only_str - )[0] + if "Camera" in current_obj: + attr_type = attr_get_type( + bpy.data.cameras[idx], attribute_only_str + )[0] + else: + attr_type = attr_get_type( + bpy.data.objects[idx], attribute_only_str + )[0] + elif "bpy.context.scene" in full_str: attr_type = attr_get_type( bpy.context.scene, attribute_only_str @@ -386,13 +393,23 @@ def execute(self, context): print("bpy.context.scene") print("OPS EXECUTE attribute_only_str ", attribute_only_str) - attr_set_val( - bpy.data.objects[idx], - attribute_only_str, - min_val, - max_val, - attr_type, - ) + if "Camera" in full_str: + attr_set_val( + bpy.data.cameras[idx], + attribute_only_str, + min_val, + max_val, + attr_type, + ) + + else: + attr_set_val( + bpy.data.objects[idx], + attribute_only_str, + min_val, + max_val, + attr_type, + ) elif "bpy.context.scene" in full_str: print("bpy.context.scene") diff --git a/randomiser/define_prop/property_classes/collection_UD_props.py b/randomiser/define_prop/property_classes/collection_UD_props.py index 112c938..5454e9b 100644 --- a/randomiser/define_prop/property_classes/collection_UD_props.py +++ b/randomiser/define_prop/property_classes/collection_UD_props.py @@ -177,7 +177,8 @@ def candidate_UD_props(self): # getter method # regex=re.compile(r'^test-\d+$') if obj in obj_str: - # print("Yay found cube") + current_obj = obj + # print("Found ", current_obj) idx = i # if ( @@ -186,14 +187,26 @@ def candidate_UD_props(self): # getter method # )[1] # != "dummy" # ): + print("CAMERA??????", current_obj) + print("Camera") + if "Camera" in current_obj: + if ( + attr_get_type( + bpy.data.cameras[idx], get_attr_only_str(UD.name) + )[1] + != "dummy" + ): + list_UD_props.append(UD) + + else: + if ( + attr_get_type( + bpy.data.objects[idx], get_attr_only_str(UD.name) + )[1] + != "dummy" + ): + list_UD_props.append(UD) - if ( - attr_get_type( - bpy.data.objects[idx], get_attr_only_str(UD.name) - )[1] - != "dummy" - ): - list_UD_props.append(UD) elif ( attr_get_type(bpy.context.scene, get_attr_only_str(UD.name))[1] != "dummy" diff --git a/randomiser/define_prop/ui.py b/randomiser/define_prop/ui.py index 13aa673..4e894dd 100644 --- a/randomiser/define_prop/ui.py +++ b/randomiser/define_prop/ui.py @@ -179,15 +179,21 @@ def draw_sockets_list_UD( # regex=re.compile(r'^test-\d+$') if obj in obj_str: - # print("Yay found cube") + current_obj = obj + # print("Found ", current_obj) idx = i # attr_type = attr_get_type( # bpy.context.scene.objects["Cube"], attribute_only_str # )[0] - attr_type = attr_get_type( - bpy.data.objects[idx], attribute_only_str - )[0] + if "Camera" in current_obj: + attr_type = attr_get_type( + bpy.data.cameras[idx], attribute_only_str + )[0] + else: + attr_type = attr_get_type( + bpy.data.objects[idx], attribute_only_str + )[0] print("DRAW SOCKETS LIST attr_type ====== ", attr_type) else: print( @@ -510,16 +516,23 @@ def poll(cls, context): # regex=re.compile(r'^test-\d+$') if obj in obj_str: - # print("Yay found ", obj) + current_obj = obj + # print("Found ", current_obj) idx = i - prop_type, action, prop, path_attr = attr_get_type( - bpy.data.objects[idx], attribute_only_str - )[0] - # prop_type, action, prop, path_attr = attr_get_type( - # bpy.data.objects["Cube"], attribute_only_str - # )[0] + if "Camera" in current_obj: + prop_type, action, prop, path_attr = attr_get_type( + bpy.data.cameras[idx], attribute_only_str + )[0] + + else: + prop_type, action, prop, path_attr = attr_get_type( + bpy.data.objects[idx], attribute_only_str + )[0] + # prop_type, action, prop, path_attr = attr_get_type( + # bpy.data.objects["Cube"], attribute_only_str + # )[0] elif "bpy.context.scene" in full_str: prop_type, action, prop, path_attr = attr_get_type( @@ -636,7 +649,8 @@ def draw(self, context): # regex=re.compile(r'^test-\d+$') if obj in obj_str: - # print("Yay found cube") + current_obj = obj + # print("Found ", current_obj) idx = i @@ -648,14 +662,26 @@ def draw(self, context): # != "dummy" # ): - if ( - attr_get_type( - bpy.data.objects[idx], - get_attr_only_str(UD_str.name), - )[1] - != "dummy" - ): - list_all_UD_props.append(UD_str) + if "Camera" in current_obj: + if ( + attr_get_type( + bpy.data.cameras[idx], + get_attr_only_str(UD_str.name), + )[1] + != "dummy" + ): + list_all_UD_props.append(UD_str) + + else: + if ( + attr_get_type( + bpy.data.objects[idx], + get_attr_only_str(UD_str.name), + )[1] + != "dummy" + ): + list_all_UD_props.append(UD_str) + elif ( attr_get_type( bpy.context.scene, get_attr_only_str(UD_str.name) From a0e4513ab250abf3fe346e15316a79020cec7c4a Mon Sep 17 00:00:00 2001 From: ruaridhg Date: Fri, 13 Oct 2023 16:23:02 +0100 Subject: [PATCH 47/58] bpy.data.objects[Cube].rotation_euler now working --- randomiser/config.py | 6 ++++-- randomiser/define_prop/operators.py | 8 +++++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/randomiser/config.py b/randomiser/config.py index 9b10cf4..adf2322 100644 --- a/randomiser/config.py +++ b/randomiser/config.py @@ -1,7 +1,7 @@ # Parameters shared across materials modules import bpy import numpy as np -from mathutils import Vector +from mathutils import Euler, Vector # MAX_NUMBER_OF_SUBPANELS: upper limit for the expected # number of *materials* in a scene. @@ -16,7 +16,7 @@ # will be defined as classes, but only those panels with # index < total number of group nodes per material # will be displayed. -MAX_NUMBER_OF_SUBSUBPANELS = 100 +MAX_NUMBER_OF_SUBSUBPANELS = 10 # Keyword to search for in nodes' names, to identify nodes to randomise @@ -62,6 +62,7 @@ float: "float_1d", int: "int_1d", bool: "bool_1d", + Euler: "euler", # bpy.types.NodeSocketInt: "int_1d" # bpy.props.IntProperty, # bpy.types.NodeSocketColor: "rgba_4d", # "float_4d", if @@ -74,6 +75,7 @@ float: {"min": -np.inf, "max": np.inf}, int: {"min": int(-2147483648), "max": int(2147483647)}, bool: {"min": False, "max": True}, + Euler: {"min": -np.inf, "max": np.inf}, # bpy.types.NodeSocketInt: { # "min": int(-1000), # -2147483648 # "max": int(1000), # 2147483647 diff --git a/randomiser/define_prop/operators.py b/randomiser/define_prop/operators.py index 9864bf7..7f213ac 100644 --- a/randomiser/define_prop/operators.py +++ b/randomiser/define_prop/operators.py @@ -3,7 +3,7 @@ import bpy import numpy as np -from mathutils import Vector +from mathutils import Euler, Vector from .ui import attr_get_type, get_attr_only_str, get_obj_str @@ -40,6 +40,12 @@ def attr_set_val(obj, path, min_val, max_val, UD_type): print("HELLO 3D VECTOR FLOAT!!!!!") value = random.uniform(min_val, max_val) print(value) + elif UD_type == Euler: + print("HELLO Euler!!!!!") + deg2rad = np.pi / 180 + value = random.uniform(min_val, max_val) + value = value * deg2rad + print(value) else: print("HELLO INTEGER!!!!!!!!!!!!!!!!!!!!") value = random.randint(min_val, max_val) From 6da257963656014cf11d6d6d4518bcb9649e1010 Mon Sep 17 00:00:00 2001 From: ruaridhg Date: Fri, 13 Oct 2023 16:37:26 +0100 Subject: [PATCH 48/58] euler property added to UD sckt props --- .../property_classes/collection_UD_socket_properties.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/randomiser/define_prop/property_classes/collection_UD_socket_properties.py b/randomiser/define_prop/property_classes/collection_UD_socket_properties.py index 20a0565..f9206b2 100644 --- a/randomiser/define_prop/property_classes/collection_UD_socket_properties.py +++ b/randomiser/define_prop/property_classes/collection_UD_socket_properties.py @@ -504,6 +504,14 @@ class SocketProperties(bpy.types.PropertyGroup): update=constrain_max_closure(float_3d_str) ) + euler_str = "euler" + min_euler: bpy.props.FloatVectorProperty( # type: ignore + update=constrain_min_closure(euler_str) + ) + max_euler: bpy.props.FloatVectorProperty( # type: ignore + update=constrain_max_closure(euler_str) + ) + # --------------------- # float 4d float_4d_str = "float_4d" From 0ce628f741ad008c1b075383052bb31840c353fa Mon Sep 17 00:00:00 2001 From: ruaridhg Date: Fri, 13 Oct 2023 17:06:12 +0100 Subject: [PATCH 49/58] test file updated --- .../define_prop/obj_path_resolve_test.py | 156 +++++++++++++++--- 1 file changed, 134 insertions(+), 22 deletions(-) diff --git a/randomiser/define_prop/obj_path_resolve_test.py b/randomiser/define_prop/obj_path_resolve_test.py index df0c299..f9b9136 100644 --- a/randomiser/define_prop/obj_path_resolve_test.py +++ b/randomiser/define_prop/obj_path_resolve_test.py @@ -1,4 +1,5 @@ import random +import re import bpy import numpy as np @@ -28,38 +29,81 @@ def get_obj_str(full_str): def attr_get_type(obj, path): - if "." in path: - # gives us: ('modifiers["Subsurf"]', 'levels') - # len_path = len(full_str.rsplit(".", config.MAX_NUMBER_OF_SUBPANELS)) - path_prop, path_attr = path.rsplit(".", 1) + # if '[' in path: + # print(' [ is in path') + + a = re.findall("\[(.*?)\]", path) + if a: + nums = list(map(int, a[0].split(","))) + print(nums) + print(type(str(nums[0]))) + else: + nums = [] - print("if statement ==== ") - print( - "FROM rsplit . path_prop for resolve = ", - path_prop, - " and path_attr for getattr = ", - path_attr, - ) - print("obj used for path_resolve = ", obj) + print("len", len(nums)) + + if len(nums) > 0: + print("Number is ", nums) + print("type(Number) is ", type(nums[0])) + + num_str = str(nums[0]) + + tmp_UD_name = path + tmp_UD_name = tmp_UD_name.replace("[" + num_str + "]", "") + new_attribute_only_str = path.replace("[" + num_str + "]", "") + + print("new_attribute_only_str", new_attribute_only_str) + path = new_attribute_only_str + + if "." in path: + # gives us: ('modifiers["Subsurf"]', 'levels') + path_prop, path_attr = path.rsplit(".", 1) + + # same as: prop = obj.modifiers["Subsurf"] + prop = obj.path_resolve(path_prop) + else: + prop = obj + # single attribute such as name, location... etc + path_attr = path - # same as: prop = obj.modifiers["Subsurf"] - prop = obj.path_resolve(path_prop) - print("prop from path_resolve for get_attr = ", prop) else: - print("else statement ==== ") - prop = obj - print("prop = obj ", prop) - # single attribute such as name, location... etc - path_attr = path - print("path_attr = path ", path_attr) + if "." in path: + # gives us: ('modifiers["Subsurf"]', 'levels') + path_prop, path_attr = path.rsplit(".", 1) + + # same as: prop = obj.modifiers["Subsurf"] + prop = obj.path_resolve(path_prop) + else: + prop = obj + # single attribute such as name, location... etc + path_attr = path # same as: prop.levels = value + print("prop = ", prop) + print("path_attr = ", path_attr) + try: - action = getattr(prop, path_attr) + # action = getattr(prop, path_attr) + # print('action = ', action) + # print('type(action) = ', type(action)) + if len(nums) > 0: + action = getattr(prop, path_attr) + print("action = ", action) + action = action[0] + print("action = ", action) + print("type(action) = ", type(action)) + else: + action = getattr(prop, path_attr) + print("action = ", action) + print("type(action) = ", type(action)) except Exception: # print("Property does not exist") action = "dummy" + prop = "dummy" + path_attr = "dummy" + # print(action, prop, path_attr) + # print(type(action)) # action = getattr(prop, path_attr) return type(action), action, prop, path_attr @@ -362,6 +406,24 @@ def attr_set_val(obj, path, min_val, max_val, UD_type): ) +full_str = "bpy.data.objects['Cube'].location[0]" +attr_only_str = get_attr_only_str(full_str) +print("attr_only_str = ", attr_only_str) + + +path_prop = "location" +path_attr = "location[0]" +prop = bpy.data.objects[1].path_resolve("location") +print(prop) +action = prop[0] +# print("prop = ", prop) +# print("action getattr = ", action) + + +attr_type = attr_get_type(bpy.data.objects[idx], attr_only_str)[0] + +print("attr_type", attr_type) + # min_val = np.array( # getattr( # bpy.context.scene.socket_props_per_UD.collection[0], @@ -389,3 +451,53 @@ def attr_set_val(obj, path, min_val, max_val, UD_type): # max_val, # prop_type, # ) + +# import re +# objects_in_scene=[] +# for i, key in enumerate(bpy.data.objects): +# # print(i) +# # print(key.name) +# objects_in_scene.append(key.name) + +# UD_name = "bpy.data.objects['Cube'].location[0]" +# print("ERROR ======= UD.name", UD_name) +# tmp_attr = get_attr_only_str(UD_name) +# print("ERROR ======== attr_str", tmp_attr) +# a = re.findall('\[(.*?)\]', tmp_attr) +# if a: +# nums = list(map(int, a[0].split(','))) +# print(nums) +# print(type(str(nums[0]))) +# else: +# nums = [] +# +# print("len", len(nums)) + +# if len(nums)>0: +# print('Number is ', nums) +# print('type(Number) is ', type(nums[0])) +# +# num_str=str(nums[0]) +# +# attribute_only_str = tmp_attr.replace("[" + num_str + "]", "") +# +# print("new_UD_name", attribute_only_str) +# +# obj_str = get_obj_str(UD_name) +# print("obj_str", obj_str) +# +# +# else: +# obj_str = get_obj_str(UD_name) +# print("obj_str", obj_str) +# +# for i, obj in enumerate(objects_in_scene): +# # regex=re.compile(r'^test-\d+$') + +# if obj in obj_str: +# current_obj = obj +# print("Found ", current_obj) +# idx = i + +# print("location[0]??????", current_obj) +# %% From 03f542255b8891543c5b4a438ce96a390a8e5ea8 Mon Sep 17 00:00:00 2001 From: ruaridhg Date: Fri, 13 Oct 2023 17:38:00 +0100 Subject: [PATCH 50/58] Added check_idx_test file --- randomiser/define_prop/check_idx_test.py | 275 +++++++++++++++++++++++ 1 file changed, 275 insertions(+) create mode 100644 randomiser/define_prop/check_idx_test.py diff --git a/randomiser/define_prop/check_idx_test.py b/randomiser/define_prop/check_idx_test.py new file mode 100644 index 0000000..5a7b01b --- /dev/null +++ b/randomiser/define_prop/check_idx_test.py @@ -0,0 +1,275 @@ +import random +import re + +import bpy +from mathutils import Vector + + +def check_idx(full_str): + tmp_attr = get_attr_only_str(full_str) + print("ERROR ======== attr_str", tmp_attr) + a = re.findall("\[(.*?)\]", tmp_attr) + if a: + nums = list(map(int, a[0].split(","))) + # print(nums) + # print(type(str(nums[0]))) + else: + nums = [] + + # print("len", len(nums)) + + if len(nums) > 0: + # print('Number is ', nums) + # print('type(Number) is ', type(nums[0])) + + num_str = str(nums[0]) + + tmp_UD_name = full_str + tmp_UD_name = tmp_UD_name.replace("[" + num_str + "]", "") + new_attribute_only_str = tmp_attr.replace("[" + num_str + "]", "") + + print("new_attribute_only_str", new_attribute_only_str) + + obj_str = get_obj_str(tmp_UD_name) + print("obj_str", obj_str) + + else: + obj_str = get_obj_str(full_str) + new_attribute_only_str = [] + + return obj_str, nums, new_attribute_only_str + + +# %% +def get_obj_str(full_str): + full_str = "bpy.data.objects['Cube'].location" + # path_prop, path_attr = full_str.rsplit("[]", 1) + x = full_str.split(".") + # print(path_prop) + for i in x: + print(i) + if "[" in i: + ii = i.split("[") + fiinal = ii[1] + fiinal = fiinal[:-1] + print("fiinal= ", fiinal) + + print(type(fiinal)) + print(fiinal) + return fiinal + + +# %% + + +####what did this used to look like??????? +def attr_get_type(obj, path): + # if '[' in path: + # print(' [ is in path') + + check_idx(path)[0] + nums = check_idx(path)[1] + new_attribute_only_str = check_idx(path)[2] + + if len(nums) > 0: + print("-----ENTERED LENS------") + + path = new_attribute_only_str + + if "." in path: + # gives us: ('modifiers["Subsurf"]', 'levels') + path_prop, path_attr = path.rsplit(".", 1) + + # same as: prop = obj.modifiers["Subsurf"] + prop = obj.path_resolve(path_prop) + else: + prop = obj + # single attribute such as name, location... etc + path_attr = path + + # same as: prop.levels = value + + print("prop = ", prop) + print("path_attr = ", path_attr) + + try: + if len(nums) > 0: + action = getattr(prop, path_attr) + print("action = ", action) + action = action[0] + print("action = ", action) + print("type(action) = ", type(action)) + else: + action = getattr(prop, path_attr) + print("action = ", action) + print("type(action) = ", type(action)) + except Exception: + # print("Property does not exist") + action = "dummy" + prop = "dummy" + path_attr = "dummy" + # print(action, prop, path_attr) + # print(type(action)) + # action = getattr(prop, path_attr) + + return type(action), action, prop, path_attr + # setattr(prop, path_attr, value) + + +def get_attr_only_str(full_str): + if "data" in full_str: + mod = 0 + elif "[" in full_str: + mod = 1 + else: + mod = 0 + + len_path = len(full_str.rsplit(".", 100)) - mod + print("len_path = ", len_path) + + list_parent_nodes_str = full_str.rsplit(".", len_path - 3) + print("list_parent_nodes_str = ", list_parent_nodes_str) + + attribute_only_str = full_str.replace(list_parent_nodes_str[0] + ".", "") + + return attribute_only_str + + +def attr_set_val(obj, path, min_val, max_val, UD_type): + if "." in path: + # gives us: ('modifiers["Subsurf"]', 'levels') + # len_path = len(full_str.rsplit(".", config.MAX_NUMBER_OF_SUBPANELS)) + path_prop, path_attr = path.rsplit(".", 1) + + # same as: prop = obj.modifiers["Subsurf"] + prop = obj.path_resolve(path_prop) + else: + prop = obj + # single attribute such as name, location... etc + path_attr = path + + # same as: prop.levels = value + + try: + getattr(prop, path_attr) + except Exception: + # print("Property does not exist") + pass + # action = getattr(prop, path_attr) + + print("attr_set_val type ========================= ", UD_type) + # if UD_type==float: + # min_array = np.array(getattr(self, "min_" + m_str)) + # max_array = np.array(getattr(self, "max_" + m_str)) + + if UD_type == float: + print("HELLO 1D FLOAT!!!!!!!") + # if rand_posx: + # getattr(context.scene.camera, value_str)[0] = uniform( + # loc_x_range[0], loc_x_range[1] + # ) + + # if rand_rotz: + # rand_z = uniform(rot_z_range[0], rot_z_range[1]) + # else: + # rand_z = uniform(0.0, 0.0) + + # vec = Vector([rand_x, rand_y, rand_z]) + + # bpy.data.objects["Camera"].rotation_euler[0] = vec[0] + + value = random.uniform(min_val, max_val) + print(value) + elif UD_type == Vector: + print("HELLO 3D VECTOR FLOAT!!!!!") + value = random.uniform(min_val, max_val) + print(value) + else: + print("HELLO INTEGER!!!!!!!!!!!!!!!!!!!!") + value = random.randint(min_val, max_val) + print(value) + + setattr(prop, path_attr, value) + + +##### frame_current +# full_str = "bpy.context.scene.frame_current" +# attr_only_str = get_attr_only_str(full_str) +# print("attr_only_str = ", attr_only_str) + +# prop = bpy.context.scene +## single attribute such as name, location... etc +# path_attr = "frame_current" +# action = getattr(prop, path_attr) +# print("prop = ", prop) +# print("action getattr = ", action) + +# prop_type, action, prop, path_attr = attr_get_type( +# bpy.context.scene, attr_only_str +# ) + +# min_val = np.array( +# getattr( +# bpy.context.scene.socket_props_per_UD.collection[1], +# "min_int_1d", +# ) +# ) + +# max_val = np.array( +# getattr( +# bpy.context.scene.socket_props_per_UD.collection[1], +# "max_int_1d", +# ) +# ) +# print(min_val) +# print(max_val) +# print(prop_type) +## min_val = bpy.context.scene. +# socket_props_per_UD.collection[0].min_float_1d[0] +## max_val = bpy.context.scene. +# socket_props_per_UD.collection[0].max_float_1d[0] +# attr_set_val( +# bpy.context.scene, +# attr_only_str, +# min_val, +# max_val, +# prop_type, +# ) + + +#### bpy.data.objects['Cube'].location[0] +#### bpy.context.scene.camera.location[0] + +full_str = "bpy.data.objects['Cube'].location[0]" +attr_only_str = get_attr_only_str(full_str) +print("attr_only_str = ", attr_only_str) + +prop = bpy.data.objects["Cube"] +# single attribute such as name, location... etc +# path_attr = "location[0]" +# action = getattr(prop, path_attr) +# print("prop = ", prop) +# print("action getattr = ", action) + +prop_type, action, prop, path_attr = attr_get_type( + bpy.context.scene, attr_only_str +) + +print("-----OUTPUTS attr_get_type------") +print("prop_type", prop_type) +print("action", action) +print("prop", prop) +print("path_attr", path_attr) + + +#### frame_current +full_str = "bpy.context.scene.frame_current" +attr_only_str = get_attr_only_str(full_str) +print("attr_only_str = ", attr_only_str) + +prop = bpy.context.scene +print("-----OUTPUTS attr_get_type------") +print("prop_type", prop_type) +print("action", action) +print("prop", prop) +print("path_attr", path_attr) From 622231c535981525254f40cdf7ba61f98e6e4f31 Mon Sep 17 00:00:00 2001 From: ruaridhg Date: Mon, 16 Oct 2023 15:23:55 +0100 Subject: [PATCH 51/58] Removed redundant tests - kept per_frame placeholders --- .../test_installing_and_enabling.py | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/tests/test_integration/test_installing_and_enabling.py b/tests/test_integration/test_installing_and_enabling.py index ceba23a..14ef4d5 100644 --- a/tests/test_integration/test_installing_and_enabling.py +++ b/tests/test_integration/test_installing_and_enabling.py @@ -210,14 +210,6 @@ def test_random_seed(): assert bpy.data.objects["Camera"].location[0] == first_run[idx] -def test_bool_delta_position(): - pass - - -def test_bool_delta_rotation(): - pass - - def test_per_frame(): """ Test if we can replicate a sequence of @@ -304,10 +296,6 @@ def test_randomiser_geometry(): ) -def test_modifier_act_on_object(): - pass - - def test_random_seed_geometry(): """Test whether changing the seed works by checking random numbers are the same after setting the same seed.""" @@ -473,6 +461,10 @@ def test_random_seed_materials(): ) +def test_per_frame_materials(): + pass + + # modified from the pytest-blender docs def test_install_and_enable_2(install_addons_from_dir, uninstall_addons): # install and enable addons from directory From f4b2320abb75d8ad622f265949befe7239e314c7 Mon Sep 17 00:00:00 2001 From: ruaridhg Date: Mon, 16 Oct 2023 15:45:16 +0100 Subject: [PATCH 52/58] Tidied up attr_set_val print statements --- randomiser/define_prop/operators.py | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/randomiser/define_prop/operators.py b/randomiser/define_prop/operators.py index 7f213ac..ca19e17 100644 --- a/randomiser/define_prop/operators.py +++ b/randomiser/define_prop/operators.py @@ -30,26 +30,20 @@ def attr_set_val(obj, path, min_val, max_val, UD_type): pass # action = getattr(prop, path_attr) - print("attr_set_val type ========================= ", UD_type) - if UD_type == float: - print("HELLO 1D FLOAT!!!!!!!") value = random.uniform(min_val, max_val) - print(value) + print("1D float = ", value) elif UD_type == Vector: - print("HELLO 3D VECTOR FLOAT!!!!!") value = random.uniform(min_val, max_val) - print(value) + print("3D Vector float = ", value) elif UD_type == Euler: - print("HELLO Euler!!!!!") deg2rad = np.pi / 180 value = random.uniform(min_val, max_val) value = value * deg2rad - print(value) + print("Euler = ", value) else: - print("HELLO INTEGER!!!!!!!!!!!!!!!!!!!!") value = random.randint(min_val, max_val) - print(value) + print("Integer = ", value) setattr(prop, path_attr, value) From 7c991319e1a5356347bb96277dc68ba442d9e75a Mon Sep 17 00:00:00 2001 From: ruaridhg Date: Mon, 16 Oct 2023 16:20:39 +0100 Subject: [PATCH 53/58] Tidy up code/remove print statements --- randomiser/define_prop/operators.py | 203 +-------------- .../property_classes/collection_UD_props.py | 58 +---- .../collection_UD_socket_properties.py | 87 ------- randomiser/define_prop/ui.py | 231 ++---------------- 4 files changed, 28 insertions(+), 551 deletions(-) diff --git a/randomiser/define_prop/operators.py b/randomiser/define_prop/operators.py index ca19e17..d01cbb7 100644 --- a/randomiser/define_prop/operators.py +++ b/randomiser/define_prop/operators.py @@ -28,8 +28,8 @@ def attr_set_val(obj, path, min_val, max_val, UD_type): except Exception: # print("Property does not exist") pass - # action = getattr(prop, path_attr) + print("obj = ", path_attr) if UD_type == float: value = random.uniform(min_val, max_val) print("1D float = ", value) @@ -235,7 +235,6 @@ def invoke(self, context, event): # NOTE: this list should have been updated already, # when drawing the panel - print("INVOKE!!!! ") cs = context.scene self.list_subpanel_UD_props_names = [ UD.name for UD in cs.socket_props_per_UD.collection @@ -244,56 +243,10 @@ def invoke(self, context, event): self.sockets_to_randomise_per_UD = {} self.sockets_to_randomise_per_UD = [] for UD_str in self.list_subpanel_UD_props_names: - # if cs.socket_props_per_UD.collection[ - # UD_str - # ].update_UD_props_collection: - # print("Collection of UD props updated") - - # sockets_props_collection = cs.socket_props_per_gng.collection[ - # gng_str - # ].collection - - # get candidate sockets for this GNG - # candidate_sockets = cs.socket_props_per_UD.collection[ - # UD_str - # ].candidate_sockets - - #### REFACTOR to invalid prop instead of unlinked geom sckt - #### OPTION 1 for loop witin for loop cand_sockets - # if socket unlinked and randomisation toggle is True: - # modify socket props to set toggle to False - # self.socket_props_per_UD[UD_str] = [] - # for sckt in candidate_sockets: - # print('sck for loop ===== ', sckt) - # if cs.socket_props_per_UD.collection[UD_str].bool_randomise: - # self.sockets_to_randomise_per_UD[UD_str].append(sckt) - - #### REFACTOR to invalid prop instead of unlinked geom sckt - #### OPTION 2 skip for loop for cand_sockets - # if this UD is selected to randomise but it is invalid property: - # set randomisation toggle to False - # if (not sckt.is_linked) and ( - # sockets_props_collection[sckt_id].bool_randomise - # ): - # setattr( - # sockets_props_collection[sckt_id], - # "bool_randomise", - # False, - # ) - # print( - # f"Socket {sckt_id} from {gng_str} is unlinked:", - # "randomisation toggle set to False", - # ) - sckt = cs.socket_props_per_UD.collection[UD_str].name if cs.socket_props_per_UD.collection[UD_str].bool_randomise: self.sockets_to_randomise_per_UD.append(sckt) - print( - "INVOKE ==== sockets to randomise ", - list(self.sockets_to_randomise_per_UD), - ) - return self.execute(context) def execute(self, context): @@ -321,42 +274,27 @@ def execute(self, context): seed(current_seed) cs.seed_properties.seed_previous = current_seed - # For every GNG with a subpanel - REFACTORING BASED ON NEW CODE - print( - "EXECUTE list_subpanel_prop_names ==== ", - self.sockets_to_randomise_per_UD, - ) - # for UD_str in self.list_subpanel_UD_props_names: + # For every UD prop with a subpanel for UD_str in self.sockets_to_randomise_per_UD: - # get collection of socket properties for this material + # get collection of socket properties for this UD prop # NOTE: socket properties do not include the actual socket object sockets_props_collection = cs.socket_props_per_UD.collection[ UD_str ] - # #### Modify to set_attr !!!!!!! full_str = sockets_props_collection.name attribute_only_str = get_attr_only_str(full_str) - print("ATTRIBUTE ONLY STRING ======== ", attribute_only_str) - objects_in_scene = [] for key in bpy.data.objects: objects_in_scene.append(key.name) if "[" in full_str: - print("bpy.context.scene") - print("OPS EXECUTE attribute_only_str ", attribute_only_str) obj_str = get_obj_str(full_str) - # print(obj_str) for i, obj in enumerate(objects_in_scene): - # regex=re.compile(r'^test-\d+$') - if obj in obj_str: current_obj = obj - # print("Found ", current_obj) - idx = i if "Camera" in current_obj: @@ -390,9 +328,6 @@ def execute(self, context): ) if "[" in full_str: - print("bpy.context.scene") - print("OPS EXECUTE attribute_only_str ", attribute_only_str) - if "Camera" in full_str: attr_set_val( bpy.data.cameras[idx], @@ -412,8 +347,6 @@ def execute(self, context): ) elif "bpy.context.scene" in full_str: - print("bpy.context.scene") - print("OPS EXECUTE attribute_only_str ", attribute_only_str) attr_set_val( bpy.context.scene, attribute_only_str, @@ -422,138 +355,8 @@ def execute(self, context): attr_type, ) - # # get min value for this socket - # min_val = np.array( - # getattr( - # sockets_props_collection[socket_id], - # "min_" + cs.socket_type_to_attr[type(sckt)], - # ) - # ) - - # # get max value for this socket - # max_val = np.array( - # getattr( - # sockets_props_collection[socket_id], - # "max_" + cs.socket_type_to_attr[type(sckt)], - # ) - # ) - - # # set default value - # # if socket type is boolean - # #### WHERE IS DEFAULT VALUE - ADD TO UI.PY TO APPEAR ON PANEL - # if type(sckt) == bpy.types.BoolProperty: - # sckt.default_value = random.choice( - # [bool(list(m_val)[0]) for m_val in [min_val, max_val]] - # ) # 1d only - # # TODO: change for a faster option? - # # bool(random.getrandbits(1))F - # # https://stackoverflow.com/questions/6824681/get-a-random-boolean-in-python - - # # if socket type is int - # elif type(sckt) == bpy.types.IntProperty: - # sckt.default_value = random.randint(max_val, min_val) - - # # for all other socket types - # else: - # # if type of the socket is NodeSocketColor, - # # and max_val < min_val: switch them before randomising - # # NOTE: these are not switched in the display panel - # # (this is intended) - # if (type(sckt) == bpy.types.NodeSocketColor) and any( - # max_val < min_val - # ): - # max_val_new = np.where( - # max_val >= min_val, max_val, min_val - # ) - # min_val_new = np.where( - # min_val < max_val, min_val, max_val - # ) - - # # TODO: is there a more elegant way? - # # feels a bit clunky.... - # max_val = max_val_new - # min_val = min_val_new - - # # assign randomised socket value - # sckt.default_value = random.uniform(min_val, max_val) - return {"FINISHED"} - # # sckt = list_UD_props_sorted # UD.name - # sckt = sockets_props_collection.name - # # Loop through the sockets to randomise - # for sckt in self.sockets_to_randomise_per_UD[UD_str]: - # socket_id = "sckt.node.name" + "_" + sckt.name - - # # get min value for this socket - # min_val = np.array( - # getattr( - # sockets_props_collection[socket_id], - # "min_" + cs.socket_type_to_attr[type(sckt)], - # ) - # ) - - # # get max value for this socket - # max_val = np.array( - # getattr( - # sockets_props_collection[socket_id], - # "max_" + cs.socket_type_to_attr[type(sckt)], - # ) - # ) - - # # set default value - # # if socket type is boolean - # if type(sckt) == bpy.types.BoolProperty: - # sckt.default_value = random.choice( - # [bool(list(m_val)[0]) for m_val in - # [min_val, max_val]] - # ) # 1d only - # # TODO: change for a faster option? - # # bool(random.getrandbits(1))F - # # https://stackoverflow.com/questions/6824681/get-a-random-boolean-in-python - - # # if socket type is int - # elif type(sckt) == bpy.types.IntProperty: - # sckt.default_value = random.randint(max_val, min_val) - - # # for all other socket types - # else: - # # if type of the socket is NodeSocketColor, - # # and max_val < min_val: switch them before randomising - # # NOTE: these are not switched in the display panel - # # (this is intended) - # if (type(sckt) == bpy.types.NodeSocketColor) and any( - # max_val < min_val - # ): - # max_val_new = np.where( - # max_val >= min_val, max_val, min_val - # ) - # min_val_new = np.where( - # min_val < max_val, min_val, max_val - # ) - - # # TODO: is there a more elegant way? - # # feels a bit clunky.... - # max_val = max_val_new - # min_val = min_val_new - - # # assign randomised socket value - # sckt.default_value = random.uniform(min_val, max_val) - - # return {"FINISHED"} - - -# NOTE: without the persistent decorator, -# the function is removed from the handlers' list -# after it is first executed -# @persistent -# def randomise_geometry_nodes_per_frame(dummy): -# bpy.ops.node.randomise_all_geometry_sockets("INVOKE_DEFAULT") -# return - - -# Graph function removed - not needed? - # --------------------- # Classes to register diff --git a/randomiser/define_prop/property_classes/collection_UD_props.py b/randomiser/define_prop/property_classes/collection_UD_props.py index 5454e9b..4f5c39b 100644 --- a/randomiser/define_prop/property_classes/collection_UD_props.py +++ b/randomiser/define_prop/property_classes/collection_UD_props.py @@ -20,18 +20,9 @@ def compute_UD_props_sets(self): # set of GNGs already in collection self.set_UD_props_in_collection = set(UD.name for UD in self.collection) - # for UD in self.collection: - ##print("self.collection !!!!!!!!!! ", UD.name) - # set of node groups in Blender data structure self.set_UD_props_in_data = set(UD.name for UD in self.candidate_UD_props) - # for UD in self.candidate_UD_props: - ## print("self.candidate_UD_props !!!!!!!!!! ", UD.name) - - # pdb.set_trace() - - ### REMOVE???? # set of node groups in one of the sets only self.set_UD_props_in_one_only = ( self.set_UD_props_in_collection.symmetric_difference( @@ -161,34 +152,20 @@ def candidate_UD_props(self): # getter method # get_attr_only_strbpy.context.scene.custom # self is the collection of node groups list_UD_props = [] + objects_in_scene = [] for i, key in enumerate(bpy.data.objects): - # print(i) - # print(key.name) objects_in_scene.append(key.name) + for UD in bpy.context.scene.custom: if "[" in UD.name: - # print("ERROR ======= UD.name", UD.name) - # print("ERROR ======== attr_str", get_attr_only_str(UD.name)) obj_str = get_obj_str(UD.name) - # print(obj_str) for i, obj in enumerate(objects_in_scene): - # regex=re.compile(r'^test-\d+$') - if obj in obj_str: current_obj = obj - # print("Found ", current_obj) idx = i - # if ( - # attr_get_type( - # bpy.data.objects["Cube"], get_attr_only_str(UD.name) - # )[1] - # != "dummy" - # ): - print("CAMERA??????", current_obj) - print("Camera") if "Camera" in current_obj: if ( attr_get_type( @@ -213,37 +190,6 @@ def candidate_UD_props(self): # getter method ): list_UD_props.append(UD) - # list_UD_props = [ - # UD - # for UD in bpy.context.scene.custom - # if ( - # attr_get_type(bpy.context.scene, - # get_attr_only_str(UD.name))[1] - # != "dummy" - # ) - # # != "dummy" - # # if attr_get_type(bpy.context.scene,UD)[2] != 'dummy' - # # nd - # # for nd in bpy.data.node_groups - # # if nd.type == "GEOMETRY" - # # and ( - # # any( - # # [ - # # ni.name.lower().startswith( - # # config.DEFAULT_RANDOM_KEYWORD - # # ) - # # for ni in nd.nodes - # # ] - # # ) - # # ) - # ] - # print("type list_UD_props ========== ", type(list_UD_props[0])) - # # sort by name - # list_node_groups = sorted( - # list_materials, - # key=lambda mat: mat.name.lower() - # ) - # print(list_UD_props) return list_UD_props diff --git a/randomiser/define_prop/property_classes/collection_UD_socket_properties.py b/randomiser/define_prop/property_classes/collection_UD_socket_properties.py index f9206b2..9e662b3 100644 --- a/randomiser/define_prop/property_classes/collection_UD_socket_properties.py +++ b/randomiser/define_prop/property_classes/collection_UD_socket_properties.py @@ -192,17 +192,8 @@ def constrain_min(self, context, m_str): # self is a 'SocketProperties' object min_array = np.array(getattr(self, "min_" + m_str)) max_array = np.array(getattr(self, "max_" + m_str)) - # min_array.tolist() - # max_array.tolist() - print("MIN CLOSURE min_array ", type(min_array)) - print("MIN CLOSURE max_array ", type(max_array)) - print("MIN CLOSURE min_array FIRST", type(min_array[0])) - print("MIN CLOSURE max_array FIRST", type(max_array[0])) if any(min_array > max_array): - where_cond = np.where(min_array > max_array, max_array, min_array) - print("np.where", where_cond) - print("np.where type = ", type(where_cond)) setattr( self, "min_" + m_str, @@ -244,8 +235,6 @@ def constrain_max(self, context, m_str): # self is a 'SocketProperties' object min_array = np.array(getattr(self, "min_" + m_str)) max_array = np.array(getattr(self, "max_" + m_str)) - print("MAX CLOSURE min_array ", min_array) - print("MAX CLOSURE max_array ", max_array) if any(max_array < min_array): setattr( self, @@ -286,71 +275,20 @@ def constrain_min(self, context, m_str): string specifying the socket attribute (e.g., int_1d) """ # self is a 'SocketProperties' object - # min_array = getattr(self, "min_" + m_str) - # max_array = getattr(self, "max_" + m_str) min_array = np.array(getattr(self, "min_" + m_str)) max_array = np.array(getattr(self, "max_" + m_str)) - # print("MIN CLOSURE min_array ", type(min_array)) - # MAX RECURSION DEPTH EXCEEDED WHILE CALLING A PYTHON OBJECT - # print("MIN CLOSURE max_array ", type(max_array)) - - # min_array = ast.literal_eval(str(min_array)) - # max_array = ast.literal_eval(str(max_array)) - # min_array = np.array(min_array) - # max_array = np.array(max_array) - - # min_array = [pyt_int.item() for pyt_int in min_array] - # max_array = [pyt_int.item() for pyt_int in max_array] - - # print("MIN CLOSURE min_array FIRST", type(min_array[0])) - # print("MIN CLOSURE max_array FIRST", type(max_array[0])) - - # min_array = np.asarray(min_array,dtype="int") - # max_array = np.asarray(max_array,dtype="int") min_array = array("i", min_array) max_array = array("i", max_array) - # min_array = np.array(min_array) - # max_array = np.array(max_array) - - # print("MIN CLOSURE min_array ", type(min_array)) - # print("MIN CLOSURE max_array ", type(max_array)) - - # print("MIN CLOSURE min_array FIRST", type(min_array[0])) - # print("MIN CLOSURE max_array FIRST", type(max_array[0])) - # print(min_array > max_array) cond_min = [min > max for min, max in zip(min_array, max_array)] - # if (min_array > max_array).all(): if any(cond_min): - cond = np.where(cond_min, max_array, min_array) - print("np.where result = ", cond) - print("np.where type = ", type(cond)) - setattr( self, "min_" + m_str, getattr(self, "max_" + m_str), ) - # try: - # setattr( - # self, - # "min_" + m_str, - # int(min_array[0]), - # ) - # except: - # print("int(min_array[0]) DID NOT WORK") - - # try: - # setattr( - # self, - # "min_" + m_str, - # getattr(self, "max_" + m_str), - # ) - # except: - # print('getattr(self, "min_" + m_str) DID NOT WORK') - return return lambda slf, ctx: constrain_min(slf, ctx, m_str) @@ -461,28 +399,12 @@ class SocketProperties(bpy.types.PropertyGroup): """ - # TODO: how to set attributes dynamically? - # TODO: I don't really get why this type definition is also an assignment? - - # # collection of socket properties for this GNG - # collection: bpy.props.CollectionProperty() # type: ignore - - # # helper attribute to update collection of socket properties - # update_sockets_collection: bpy.props.BoolProperty( # type: ignore - # default=False, - # get=get_update_collection, - # set=set_update_collection, - # ) - # --------------------- # name of the socket # NOTE: if we make a Blender collection of this type of objects, # we will be able to access them by name name: bpy.props.StringProperty() # type: ignore - # TODO: include the socket itself here to? - # socket: PointerProperty(type=bpy.types.NodeSocketStandard?) - # --------------------- # float 1d float_1d_str = "float_1d" @@ -536,14 +458,6 @@ class SocketProperties(bpy.types.PropertyGroup): # ---------------------------- # int_1d - # int_1d_str = "int_1d" - # min_int_1d: bpy.props.IntVectorProperty( # type: ignore - # size=1, update=constrain_min_closure(int_1d_str) - # ) - - # max_int_1d: bpy.props.IntVectorProperty( # type: ignore - # size=1, update=constrain_max_closure(int_1d_str) - # ) int_1d_str = "int_1d" min_int_1d_PROP = bpy.props.IntVectorProperty( # type: ignore size=1, update=constrain_min_closure_int(int_1d_str) @@ -557,7 +471,6 @@ class SocketProperties(bpy.types.PropertyGroup): # ---------------------------- # bool_1d - # bool_1d_str = "bool_1d" min_bool_1d: bpy.props.BoolVectorProperty( # type: ignore size=1, ) diff --git a/randomiser/define_prop/ui.py b/randomiser/define_prop/ui.py index 4e894dd..caae1c1 100644 --- a/randomiser/define_prop/ui.py +++ b/randomiser/define_prop/ui.py @@ -1,15 +1,13 @@ import bpy from .. import config -from ..material.ui import TemplatePanel # draw_sockets_list +from ..material.ui import TemplatePanel # --------------------------------------------------- # Custom UIlist items # ---------------------------------------------------- class CUSTOM_UL_items(bpy.types.UIList): - print("hello UIlist") - def draw_item( self, context, @@ -45,14 +43,8 @@ def draw_sockets_list_UD( # NOTE: if I don't sort the input nodes, everytime one of the nodes is # selected in the graph it moves to the bottom of the panel. list_UD_props_sorted = list_UD_props - # sorted(list_UD_props, key=lambda x: x.name) - # print("IN draw_sockets_list_UD ==== ", list_UD_props_sorted) - # for i_n, UD_str in enumerate(list_UD_props_sorted): row = layout.row() - # if first node: add labels for - # name, min, max and randomisation toggle - # if i_n == 0: row_split = row.split() col1 = row_split.column(align=True) row_split.column(align=True) @@ -61,8 +53,7 @@ def draw_sockets_list_UD( row_split.column(align=True) # input node name - # print(list_UD_props_sorted) - col1.label(text=sockets_props_collection.name) # UD.name + col1.label(text=sockets_props_collection.name) col1.alignment = "CENTER" # min label @@ -73,13 +64,6 @@ def draw_sockets_list_UD( col4.alignment = "CENTER" col4.label(text="max") - # if not first node: add just node name - # else: - # row.separator(factor=1.0) # add empty row before each node - # row = layout.row() - - # row.label(text=UD_str) #UD.name - row = layout.row() row_split = row.split() col1 = row_split.column(align=True) @@ -93,33 +77,6 @@ def draw_sockets_list_UD( col1.alignment = "RIGHT" col1.label(text="value") # text=sckt.name) - # socket current value - # if "bpy.context.scene" in full_str: - # col2.prop( - # getattr( - # bpy.context.scene, - # attribute_only_str - # ) - # # sockets_props_collection, - # # "default_value", #####Default value not found - # # icon_only=True, - # ) - # elif "bpy.data.objects" in full_str: - # col2.prop( - # getattr( - # bpy.data.objects["Cube"], - # attribute_only_str - # ) - # ) - # col2.enabled = False # current value is not editable - - # # socket min and max columns - # socket_id = UD.name + "_" + sckt.name - # if (UD.id_data.name in bpy.data.node_groups) and ( - # bpy.data.node_groups[UD.id_data.name].type != "GEOMETRY" - # ): # only for SHADER groups - # socket_id = UD.id_data.name + "_" + socket_id - # if socket is a color: format min/max as a color picker # and an array (color picker doesn't include alpha value) sckt = list_UD_props_sorted # UD.name @@ -150,42 +107,20 @@ def draw_sockets_list_UD( # if socket is not color type: format as a regular property - ##### REFACTOR EASY CASE FIRST - # (may not need other cases) - else: # bpy.types.NodeSocketBool: + else: objects_in_scene = [] for key in bpy.data.objects: objects_in_scene.append(key.name) for m_str, col in zip(["min", "max"], [col3, col4]): - print( - "DRAW SOCKETS LIST sockets_props_collection.name ====== ", - sockets_props_collection.name, - ) if "[" in sockets_props_collection.name: - print( - "DRAW SOCKETS LIST sockets_props_collection.name ====== ", - sockets_props_collection.name, - ) - print( - "DRAW SOCKETS LIST attribute_only_str ====== ", - attribute_only_str, - ) - obj_str = get_obj_str(sockets_props_collection.name) - # print(obj_str) for i, obj in enumerate(objects_in_scene): - # regex=re.compile(r'^test-\d+$') - if obj in obj_str: current_obj = obj - # print("Found ", current_obj) - idx = i - # attr_type = attr_get_type( - # bpy.context.scene.objects["Cube"], attribute_only_str - # )[0] + if "Camera" in current_obj: attr_type = attr_get_type( bpy.data.cameras[idx], attribute_only_str @@ -194,40 +129,20 @@ def draw_sockets_list_UD( attr_type = attr_get_type( bpy.data.objects[idx], attribute_only_str )[0] - print("DRAW SOCKETS LIST attr_type ====== ", attr_type) else: - print( - "DRAW SOCKETS LIST sockets_props_collection.name ====== ", - sockets_props_collection.name, - ) - print( - "DRAW SOCKETS LIST attribute_only_str ====== ", - attribute_only_str, - ) attr_type = attr_get_type( bpy.context.scene, attribute_only_str )[0] - print("DRAW SOCKETS LIST attr_type ====== ", attr_type) - # print( - # "sockets_props_collection ???????", - # sockets_props_collection, - # ) - # print("type ??????????? ", attribute_only_str, attr_type) - - print("ERROR HERE in line 172 of ui.py") col.prop( - sockets_props_collection, # [socket_id], + sockets_props_collection, m_str + "_" + cs.UD_prop_to_attr[attr_type], icon_only=True, ) - # np.array(getattr(self, m_str + "_min")) - # getattr(context.scene.camera, location)[0] - # e.g. min_float_1d so m_str + "_" + float_1d # randomisation toggle col5.prop( - sockets_props_collection, # [socket_id], + sockets_props_collection, "bool_randomise", icon_only=True, ) @@ -235,7 +150,6 @@ def draw_sockets_list_UD( def get_obj_str(full_str): x = full_str.split(".") - # print(path_prop) for i in x: if "[" in i: ii = i.split("[") @@ -246,26 +160,13 @@ def get_obj_str(full_str): def attr_get_type(obj, path): - # if '[' in path: - # print(' [ is in path') - if "." in path: # gives us: ('modifiers["Subsurf"]', 'levels') # len_path = len(full_str.rsplit(".", config.MAX_NUMBER_OF_SUBPANELS)) path_prop, path_attr = path.rsplit(".", 1) - # print("if statement ==== ") - # print( - # "FROM rsplit . path_prop for resolve = ", - # path_prop, - # " and path_attr for getattr = ", - # path_attr, - # ) - # print("obj used for path_resolve = ", obj) - # same as: prop = obj.modifiers["Subsurf"] prop = obj.path_resolve(path_prop) - # print("prop from path_resolve for get_attr = ", prop) else: prop = obj # single attribute such as name, location... etc @@ -280,12 +181,8 @@ def attr_get_type(obj, path): action = "dummy" prop = "dummy" path_attr = "dummy" - # print(action, prop, path_attr) - # print(type(action)) - # action = getattr(prop, path_attr) return type(action), action, prop, path_attr - # setattr(prop, path_attr, value) def get_attr_only_str(full_str): @@ -502,7 +399,6 @@ def poll(cls, context): full_str = sockets_props_collection.name attribute_only_str = get_attr_only_str(full_str) - print("SUBPANEL poll attribute_only_str ", attribute_only_str) objects_in_scene = [] for key in bpy.data.objects: @@ -510,62 +406,39 @@ def poll(cls, context): if "[" in full_str: obj_str = get_obj_str(full_str) - # print(obj_str) for i, obj in enumerate(objects_in_scene): - # regex=re.compile(r'^test-\d+$') - if obj in obj_str: current_obj = obj - # print("Found ", current_obj) - idx = i if "Camera" in current_obj: - prop_type, action, prop, path_attr = attr_get_type( - bpy.data.cameras[idx], attribute_only_str - )[0] + action = attr_get_type( + bpy.data.cameras[idx], + get_attr_only_str(attribute_only_str), + )[1] else: - prop_type, action, prop, path_attr = attr_get_type( - bpy.data.objects[idx], attribute_only_str - )[0] - # prop_type, action, prop, path_attr = attr_get_type( - # bpy.data.objects["Cube"], attribute_only_str - # )[0] + action = attr_get_type( + bpy.data.objects[idx], + get_attr_only_str(attribute_only_str), + )[1] elif "bpy.context.scene" in full_str: - prop_type, action, prop, path_attr = attr_get_type( - bpy.context.scene, attribute_only_str - )[0] - - # print("prop_type", prop_type) - # print("action", action) - # print("prop", prop) - # print("path_attr", path_attr) + action = attr_get_type(bpy.context.scene, attribute_only_str)[ + 1 + ] else: action = "dummy" - # bpy.ops.custom.list_action(action='UP') - # pdb.set_trace() - # print('action',action) return action != "dummy" - # , getattr(prop, path_attr, None) - # clc.subpanel defined in operators.py - # only display subpanels for which this is true - - def draw_header( - self, context - ): # maybe needed for the name of custom props - # but no need graph to be displayed - """Define header for the GNG subpanel - - The header shows the name of the associated geometry node group - (GNG) inside a button. The button is linked to the view-graph - operator. + def draw_header(self, context): + """Define header for the UD subpanel + The header shows the name of the associated user defined + property Parameters ---------- context : _type_ @@ -580,12 +453,6 @@ def draw_header( layout = self.layout layout.use_property_split = True layout.use_property_decorate = False - # layout.operator( - # f"node.view_graph_for_gng_{self.subpanel_UD_prop_idx}", - # text=subpanel_UD_prop.name, - # emboss=True, - # ) #operator defined once node.view_graph_for_gng - # # - not needed for custom props? def draw(self, context): """Define the content to display in the GNG subpanel @@ -600,68 +467,29 @@ def draw(self, context): # get this subpanel's GNG cs.socket_props_per_UD.collection[self.subpanel_UD_idx] - # print("subpanel_UD_prop = ", subpanel_UD_prop) - # print("self.subpanel_UD_idx = ", self.subpanel_UD_idx) - - # # force an update - # (##### CHECK VALID PROPERTY orchecked elsewhere?) - # if cs.socket_props_per_UD[ - # self.subpanel_UD_idx - # ].update_collection: - # print("Collection of UD properties updated") - - # get (updated) collection of chosen propr for this subpanel + # get (updated) collection of chosen prop for this subpanel sockets_props_collection = cs.socket_props_per_UD.collection[ self.subpanel_UD_idx ] # .collection - # print("sockets_props_collection = ", sockets_props_collection) - # print(" and name ========== ", sockets_props_collection.name) - - # # Get list of input nodes to randomise for this subpanel's GNG - # [sckt.name.split("_")[0] for sckt in sockets_props_collection] - # Get list of input nodes to randomise for this subpanel's GNG full_str = sockets_props_collection.name attribute_only_str = get_attr_only_str(full_str) - # len_path = len(full_str.rsplit(".", config.MAX_NUMBER_OF_SUBPANELS)) - # list_parent_nodes_str = full_str.rsplit(".", len_path - 3) - # attribute_only_str = full_str.replace( - # list_parent_nodes_str[0] + ".", "" - # ) - - # print("list_parent_nodes_str = ", attribute_only_str) - - # full_list = [prop.name for prop in list(C.scene.custom)] list_all_UD_props = [] for UD_str in bpy.context.scene.custom: - # print("ERROR ====== UD_str", UD_str) objects_in_scene = [] for key in bpy.data.objects: objects_in_scene.append(key.name) if "[" in UD_str.name: obj_str = get_obj_str(UD_str.name) - # print(obj_str) for i, obj in enumerate(objects_in_scene): - # regex=re.compile(r'^test-\d+$') - if obj in obj_str: current_obj = obj - # print("Found ", current_obj) - idx = i - # if ( - # attr_get_type( - # bpy.data.objects["Cube"], - # get_attr_only_str(UD_str.name), - # )[1] - # != "dummy" - # ): - if "Camera" in current_obj: if ( attr_get_type( @@ -689,23 +517,10 @@ def draw(self, context): != "dummy" ): list_all_UD_props.append(UD_str) - # list_all_UD_props = [ - # UD_str - # for UD_str in list(bpy.context.scene.custom) - # if attr_get_type( - # bpy.context.scene, get_attr_only_str(UD_str.name) - # )[1] - # != "dummy" - # ] - # print("list_all_UD_props ====== ", list_all_UD_props) - # print( - # "bpy.context.scene.custom_index == ", - # bpy.context.scene.custom_index, - # ) + list_current_UD_props = list_all_UD_props[ bpy.context.scene.custom_index ].name - # print("list_current_UD_props =======", list_current_UD_props) # Draw sockets to randomise per input node, including their # current value and min/max boundaries From 42e4cdc0d1826c7b030f6d3738f6e76d42eddbca Mon Sep 17 00:00:00 2001 From: ruaridhg Date: Mon, 16 Oct 2023 16:23:35 +0100 Subject: [PATCH 54/58] Changed back to sample.blend file --- install_randomiser.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install_randomiser.sh b/install_randomiser.sh index 8982069..fb10f7c 100755 --- a/install_randomiser.sh +++ b/install_randomiser.sh @@ -4,4 +4,4 @@ source ~/.bash_profile # zip randomiser, launch blender and install+enable zip randomiser.zip -FS -r randomiser/ -blender define_prop.blend --python install_and_enable_addons.py -- ./randomiser.zip +blender sample.blend --python install_and_enable_addons.py -- ./randomiser.zip From bf65aed65409b281fc6bc2f2a040acc416612320 Mon Sep 17 00:00:00 2001 From: ruaridhg Date: Mon, 16 Oct 2023 16:25:15 +0100 Subject: [PATCH 55/58] Register all panels --- randomiser/__init__.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/randomiser/__init__.py b/randomiser/__init__.py index 3756b41..9ade7a4 100644 --- a/randomiser/__init__.py +++ b/randomiser/__init__.py @@ -17,20 +17,20 @@ def register(): seed.register() - # transform.register() - # material.register() - # geometry.register() + transform.register() + material.register() + geometry.register() define_prop.register() - # random_all.register() + random_all.register() def unregister(): seed.unregister() - # transform.unregister() - # material.unregister() - # geometry.unregister() + transform.unregister() + material.unregister() + geometry.unregister() define_prop.unregister() - # random_all.unregister() + random_all.unregister() if __name__ == "__main__": From e37fa0d1dd77f6457c81021e5163d222583cb973 Mon Sep 17 00:00:00 2001 From: ruaridhg Date: Wed, 18 Oct 2023 15:13:09 +0100 Subject: [PATCH 56/58] Register all panels and tidy up config --- randomisation_seed.sh | 2 +- randomiser/__init__.py | 2 +- randomiser/config.py | 21 +++++---------------- 3 files changed, 7 insertions(+), 18 deletions(-) diff --git a/randomisation_seed.sh b/randomisation_seed.sh index 10fcfd8..bc1290d 100644 --- a/randomisation_seed.sh +++ b/randomisation_seed.sh @@ -3,4 +3,4 @@ source ~/.bash_profile # zip randomiser, launch blender and install+enable zip randomiser.zip -FS -r randomiser/ -blender random_all.blend --python install_and_enable_addons.py -- ./randomiser.zip --seed 32 --input ./input_parameters.json --output ./transform_geom_mat_test.json +blender random_all.blend --python install_and_enable_addons.py -- ./randomiser.zip --seed 32 --input ./input_bounds.json --output ./output_randomisations_per_frame1697116725.310647.json diff --git a/randomiser/__init__.py b/randomiser/__init__.py index 9ade7a4..6d5496f 100644 --- a/randomiser/__init__.py +++ b/randomiser/__init__.py @@ -1,4 +1,4 @@ -from . import material, transform, geometry, seed, define_prop # random_all +from . import material, transform, geometry, seed, define_prop, random_all bl_info = { "name": "Randomisations panel", diff --git a/randomiser/config.py b/randomiser/config.py index adf2322..7aeca95 100644 --- a/randomiser/config.py +++ b/randomiser/config.py @@ -16,7 +16,7 @@ # will be defined as classes, but only those panels with # index < total number of group nodes per material # will be displayed. -MAX_NUMBER_OF_SUBSUBPANELS = 10 +MAX_NUMBER_OF_SUBSUBPANELS = 100 # Keyword to search for in nodes' names, to identify nodes to randomise @@ -48,36 +48,25 @@ bpy.types.NodeSocketVector: {"min": -np.inf, "max": np.inf}, bpy.types.NodeSocketColor: {"min": 0.0, "max": 1.0}, bpy.types.NodeSocketInt: { - "min": int(-1000), # -2147483648 - "max": int(1000), # 2147483647 - }, # ---- not sure this will work? + "min": int(-2147483648), + "max": int(2147483647), + }, bpy.types.NodeSocketBool: {"min": False, "max": True}, } MAP_PROPS_TO_ATTR = { - # bpy.types.NodeSocketFloat: "float_1d" - # bpy.props.FloatVectorProperty size=1, - Vector: "float_3d", # bpy.props.FloatVectorProperty size=3, + Vector: "float_3d", float: "float_1d", int: "int_1d", bool: "bool_1d", Euler: "euler", - # bpy.types.NodeSocketInt: "int_1d" - # bpy.props.IntProperty, - # bpy.types.NodeSocketColor: "rgba_4d", # "float_4d", if - # bpy.types.NodeSocketBool: "bool_1d", elif } MAP_PROPS_TO_INI_MIN_MAX = { - # bpy.types.NodeSocketFloat: {"min": -np.inf, "max": np.inf}, Vector: {"min": -np.inf, "max": np.inf}, float: {"min": -np.inf, "max": np.inf}, int: {"min": int(-2147483648), "max": int(2147483647)}, bool: {"min": False, "max": True}, Euler: {"min": -np.inf, "max": np.inf}, - # bpy.types.NodeSocketInt: { - # "min": int(-1000), # -2147483648 - # "max": int(1000), # 2147483647 - # }, # ---- not sure this will work? } From 1f8677429216a696526ab871b3dfd8227ac1261d Mon Sep 17 00:00:00 2001 From: ruaridhg Date: Wed, 18 Oct 2023 15:14:38 +0100 Subject: [PATCH 57/58] Remove test files --- ...in_max_inputs_geom_only_test_in_Blender.py | 235 ---- ...min_max_inputs_mat_only_test_in_Blender.py | 245 ---- randomiser/Test_int_min_max.py | 56 - randomiser/define_prop/check_idx_test.py | 275 ---- .../define_prop/obj_path_resolve_test.py | 503 ------- ..._parameter_outputs_geom_test_in_Blender.py | 1161 ----------------- .../save_parameter_outputs_test_in_Blender.py | 67 - randomiser/test_setattr.py | 46 - 8 files changed, 2588 deletions(-) delete mode 100644 randomiser/Import_min_max_inputs_geom_only_test_in_Blender.py delete mode 100644 randomiser/Import_min_max_inputs_mat_only_test_in_Blender.py delete mode 100644 randomiser/Test_int_min_max.py delete mode 100644 randomiser/define_prop/check_idx_test.py delete mode 100644 randomiser/define_prop/obj_path_resolve_test.py delete mode 100644 randomiser/save_parameter_outputs_geom_test_in_Blender.py delete mode 100644 randomiser/save_parameter_outputs_test_in_Blender.py delete mode 100644 randomiser/test_setattr.py diff --git a/randomiser/Import_min_max_inputs_geom_only_test_in_Blender.py b/randomiser/Import_min_max_inputs_geom_only_test_in_Blender.py deleted file mode 100644 index 31bc1cd..0000000 --- a/randomiser/Import_min_max_inputs_geom_only_test_in_Blender.py +++ /dev/null @@ -1,235 +0,0 @@ -import json -import re - -import bpy - -out_path_to_file = "./transform_geom_mat_test.json" -with open(out_path_to_file, "r") as in_file_obj: - text = in_file_obj.read() - # convert the text into a dictionary - out_data = json.loads(text) - -print(out_data["geometry"]) - -geom_out_dict = out_data["geometry"] - -counter_dict = 0 -for n, keys in geom_out_dict.items(): - # print(n) - # print(keys) - counter_dict = +counter_dict - if "Values" in str(keys): - print("ONLY NEEDED n ", n) - print("ONLY NEEDED keys ", keys) - -for obj in bpy.data.objects: - if "Cube" in str(obj): - active_obj = obj - elif "Sphere" in str(obj): - active_obj = obj -# obj = bpy.data.objects[3] #Sphere -bpy.context.view_layer.objects.active = obj -bpy.context.scene.socket_props_per_gng.update_gngs_collection -bpy.ops.node.randomise_all_geometry_sockets("INVOKE_DEFAULT") - -## set range for randomise in blender properties -# bpy.data.scenes["Scene"].socket_props_per_gng.collection[0].collection[ -# 0 -# ].max_float_1d[0] = upper_bound -# bpy.data.scenes["Scene"].socket_props_per_gng.collection[0].collection[ -# 0 -# ].min_float_1d[0] = lower_bound - -#### GEOMETRY -# bpy.data.scenes["Scene"].frame_current = 0 - -### GEOMETRY -# collection[N].collection[S] -# [N] = 0, 1, 2 for each node group (even NG within NG) -# [S] = 0, 1 etc. for each socket within node group -# Actual Geom values followed by Min_Max -# Node group called "Geometry Nodes" - -all_geom_dict = {} -cs = bpy.context.scene - -counter = 0 -all_geom_dict["min"] = 1 -all_geom_dict["max"] = 5 -for gng_idx in range(len(cs.socket_props_per_gng.collection)): - # for gng in cs.socket_props_per_gng.collection: - - # get this subpanel's GNG - subpanel_gng = cs.socket_props_per_gng.collection[gng_idx] - print(subpanel_gng.name) - - cs.socket_props_per_gng.collection[subpanel_gng.name].update_input_json - - # force an update in the sockets for this GNG - cs.socket_props_per_gng.collection[ - subpanel_gng.name - ].update_sockets_collection - print("TEST Collection of Geometry Node Groups updated") - - sckt_prop = subpanel_gng.collection - - for sckt in subpanel_gng.collection: - # geom_current = {} - print("sckt in sckt_prop = ", sckt) - print(type(sckt)) - tmp_sck = sckt.name - print(sckt.name) - - # if "_Value" in tmp_sck: - # tmp_sck=tmp_sck.replace("_Value", "") - # print(tmp_sck) - # print(type(tmp_sck)) - - # sckt_cand = subpanel_gng.candidate_sockets - # - for s in subpanel_gng.candidate_sockets: - # build socket id from scratch - socket_id = s.node.name + "_" + s.name - print("socket_id ===== ") - print(socket_id) - - if socket_id == tmp_sck: - sckt_val = s - break - - # for this socket type, get the name of the attribute - # holding the min/max properties - socket_attrib_str = bpy.context.scene.socket_type_to_attr[ - type(sckt_val) - ] - - # extract last number between '_' and 'd/D' in the - # attribute name, to determine the shape of the array - # TODO: there is probably a nicer way to do this... - n_dim = int(re.findall(r"_(\d+)(?:d|D)", socket_attrib_str)[-1]) - # --------------------------- - - # get dictionary with initial min/max values - # for this socket type - # ini_min_max_values = ( - # bpy.context.scene.socket_type_to_ini_min_max[type(sckt_val)] - # ) - - ini_min_max_values = all_geom_dict - - print(ini_min_max_values) - print(sckt_prop) - print(socket_attrib_str) - print(n_dim) - - # assign initial value - for m_str in ["min", "max"]: - setattr( - sckt, # sckt_prop, - m_str + "_" + socket_attrib_str, - (ini_min_max_values[m_str],) * n_dim, - ) - - #### CODE HAPPENS AFTER INITIAL VALUES SET SO NOT NEEDED? -# # get (updated) collection of socket props for this GNG -# sockets_props_collection = cs.socket_props_per_gng.collection[ -# subpanel_gng.name -# ].collection -# print(sockets_props_collection) - - -# list_parent_nodes_str = [ -# sckt.name.split("_")[0] for sckt in sockets_props_collection -# ] -# list_input_nodes = [ -# bpy.data.node_groups[subpanel_gng.name].nodes[nd_str] -# for nd_str in list_parent_nodes_str -# ] - -# list_input_nodes_sorted = sorted( -# list_input_nodes, key=lambda x: x.name -# ) - - -## # if socket is not color type: format as a regular property -## else: -## for m_str in zip(["min", "max"]): -### bpy.data.scenes["Scene"].socket_props_per_gng. -# collection[0].collection[ -### 0 -###].max_float_1d[0] -## sockets_props_collection[socket_id] -## col.prop( -## sockets_props_collection[socket_id], -## m_str + "_" + cs.socket_type_to_attr[type(sckt)], -## icon_only=True, -## ) -# -# for i_n, nd in enumerate(list_input_nodes_sorted): -# # add sockets for this node in the subseq rows -# for sckt in nd.outputs: -# print("i_n", i_n) -# print("nd", nd) -# print( -# getattr( -# sckt, -# "default_value", -# ) -# ) - -# for gng_idx in range(len(cs.socket_props_per_gng.collection)): -# # get this subpanel's GNG -# subpanel_gng = cs.socket_props_per_gng.collection[gng_idx] -# tmp_GNG = subpanel_gng.name -# print(tmp_GNG) - -# sockets_props_collection = cs.socket_props_per_gng.collection[ -# subpanel_gng.name -# ].collection - -# list_parent_nodes_str = [ -# sckt.name.split("_")[0] for sckt in sockets_props_collection -# ] -# list_input_nodes = [ -# bpy.data.node_groups[subpanel_gng.name].nodes[nd_str] -# for nd_str in list_parent_nodes_str -# ] - -# list_input_nodes_sorted = sorted( -# list_input_nodes, key=lambda x: x.name -# ) -# for i_n, nd in enumerate(list_input_nodes_sorted): -# # add sockets for this node in the subseq rows -# for sckt in nd.outputs: -# print("i_n", i_n) -# print("nd", nd) -# print( -# getattr( -# sckt, -# "default_value", -# ) -# ) - -# tmp_values = [] -# for idx in range(tot_frame_no): -# bpy.app.handlers.frame_change_pre[0]("dummy") -# bpy.data.scenes["Scene"].frame_current = idx -# bpy.ops.node.randomise_all_geometry_sockets( -# "INVOKE_DEFAULT" -# ) # issue -# # w/ this being called so often - -# # might need moved to diff for loop? -# tmp_values.append( -# getattr( -# sckt, -# "default_value", -# ) -# ) - -# print(tmp_values) -# tmp_sck = nd.name -# all_geom_dict[tmp_GNG] = tmp_sck -# GNG_sck_values_str = tmp_GNG + tmp_sck -# GNG_sck_values_str = "Values " + GNG_sck_values_str -# print(GNG_sck_values_str) -# all_geom_dict[GNG_sck_values_str] = tmp_values diff --git a/randomiser/Import_min_max_inputs_mat_only_test_in_Blender.py b/randomiser/Import_min_max_inputs_mat_only_test_in_Blender.py deleted file mode 100644 index 21c706f..0000000 --- a/randomiser/Import_min_max_inputs_mat_only_test_in_Blender.py +++ /dev/null @@ -1,245 +0,0 @@ -import json -import re - -import bpy - -out_path_to_file = "./transform_geom_mat_test.json" -with open(out_path_to_file, "r") as in_file_obj: - text = in_file_obj.read() - # convert the text into a dictionary - out_data = json.loads(text) - -print(out_data["materials"]) - -geom_out_dict = out_data["materials"] - -counter_dict = 0 -for n, keys in geom_out_dict.items(): - # print(n) - # print(keys) - counter_dict = +counter_dict - if "Values" in str(keys): - print("ONLY NEEDED n ", n) - print("ONLY NEEDED keys ", keys) - -for obj in bpy.data.objects: - if "Cube" in str(obj): - active_obj = obj - elif "Sphere" in str(obj): - active_obj = obj -# obj = bpy.data.objects[3] #Sphere -bpy.context.view_layer.objects.active = obj -bpy.context.scene.socket_props_per_material.update_materials_collection -bpy.ops.node.randomise_all_material_sockets("INVOKE_DEFAULT") - -## set range for randomise in blender properties -# bpy.data.scenes["Scene"].socket_props_per_gng.collection[0].collection[ -# 0 -# ].max_float_1d[0] = upper_bound -# bpy.data.scenes["Scene"].socket_props_per_gng.collection[0].collection[ -# 0 -# ].min_float_1d[0] = lower_bound - -#### GEOMETRY -# bpy.data.scenes["Scene"].frame_current = 0 - -### GEOMETRY -# collection[N].collection[S] -# [N] = 0, 1, 2 for each node group (even NG within NG) -# [S] = 0, 1 etc. for each socket within node group -# Actual Geom values followed by Min_Max -# Node group called "Geometry Nodes" - -all_geom_dict = {} -cs = bpy.context.scene - -counter = 0 -all_geom_dict["min"] = 0.1 -all_geom_dict["max"] = 0.9 -for gng_idx in range(len(cs.socket_props_per_material.collection)): - # for gng in cs.socket_props_per_gng.collection: - - # get this subpanel's GNG - subpanel_gng = cs.socket_props_per_material.collection[gng_idx] - print(subpanel_gng.name) - - # force an update in the sockets for this GNG - cs.socket_props_per_material.collection[ - subpanel_gng.name - ].update_sockets_collection - print("TEST Collection of MAT Node Groups updated") - - sckt_prop = subpanel_gng.collection - - for sckt in subpanel_gng.collection: - # geom_current = {} - print("sckt in sckt_prop = ", sckt) - print(type(sckt)) - tmp_sck = sckt.name - print(sckt.name) - - # if "_Value" in tmp_sck: - # tmp_sck=tmp_sck.replace("_Value", "") - # print(tmp_sck) - # print(type(tmp_sck)) - - # sckt_cand = subpanel_gng.candidate_sockets - # - for s in subpanel_gng.candidate_sockets: - # build socket id from scratch - socket_id = s.node.name + "_" + s.name - print("socket_id ===== ") - print(socket_id) - - if s.node.id_data.name in bpy.data.node_groups: - socket_id = s.node.id_data.name + "_" + socket_id - - if socket_id == tmp_sck: - sckt_val = s - break - - print("sckt_val ====== ", sckt_val) - - # for this socket type, get the name of the attribute - # holding the min/max properties - socket_attrib_str = bpy.context.scene.socket_type_to_attr[ - type(sckt_val) - ] - - # extract last number between '_' and 'd/D' in the - # attribute name, to determine the shape of the array - # TODO: there is probably a nicer way to do this... - n_dim = int(re.findall(r"_(\d+)(?:d|D)", socket_attrib_str)[-1]) - # --------------------------- - - # get dictionary with initial min/max values - # for this socket type - # ini_min_max_values = ( - # bpy.context.scene.socket_type_to_ini_min_max[type(sckt_val)] - # ) - - ini_min_max_values = all_geom_dict - - print(ini_min_max_values) - print(sckt_prop) - print(socket_attrib_str) - print(n_dim) - - if "_Value" in tmp_sck: - tmp_sck = tmp_sck.replace("_Value", "") - print(tmp_sck) - GNG_sck_values_str = subpanel_gng.name + tmp_sck - GNG_sck_values_str = "Values " + GNG_sck_values_str - print(GNG_sck_values_str) - - # assign initial value - for m_str in ["min", "max"]: - setattr( - sckt, # sckt_prop, - m_str + "_" + socket_attrib_str, - (ini_min_max_values[m_str],) * n_dim, - ) - - #### CODE HAPPENS AFTER INITIAL VALUES SET SO NOT NEEDED? -# # get (updated) collection of socket props for this GNG -# sockets_props_collection = cs.socket_props_per_gng.collection[ -# subpanel_gng.name -# ].collection -# print(sockets_props_collection) - - -# list_parent_nodes_str = [ -# sckt.name.split("_")[0] for sckt in sockets_props_collection -# ] -# list_input_nodes = [ -# bpy.data.node_groups[subpanel_gng.name].nodes[nd_str] -# for nd_str in list_parent_nodes_str -# ] - -# list_input_nodes_sorted = sorted( -# list_input_nodes, key=lambda x: x.name -# ) - - -## # if socket is not color type: format as a regular property -## else: -## for m_str in zip(["min", "max"]): -### bpy.data.scenes["Scene"]. -# socket_props_per_gng.collection[0].collection[ -### 0 -###].max_float_1d[0] -## sockets_props_collection[socket_id] -## col.prop( -## sockets_props_collection[socket_id], -## m_str + "_" + cs.socket_type_to_attr[type(sckt)], -## icon_only=True, -## ) -# -# for i_n, nd in enumerate(list_input_nodes_sorted): -# # add sockets for this node in the subseq rows -# for sckt in nd.outputs: -# print("i_n", i_n) -# print("nd", nd) -# print( -# getattr( -# sckt, -# "default_value", -# ) -# ) - -# for gng_idx in range(len(cs.socket_props_per_gng.collection)): -# # get this subpanel's GNG -# subpanel_gng = cs.socket_props_per_gng.collection[gng_idx] -# tmp_GNG = subpanel_gng.name -# print(tmp_GNG) - -# sockets_props_collection = cs.socket_props_per_gng.collection[ -# subpanel_gng.name -# ].collection - -# list_parent_nodes_str = [ -# sckt.name.split("_")[0] for sckt in sockets_props_collection -# ] -# list_input_nodes = [ -# bpy.data.node_groups[subpanel_gng.name].nodes[nd_str] -# for nd_str in list_parent_nodes_str -# ] - -# list_input_nodes_sorted = sorted( -# list_input_nodes, key=lambda x: x.name -# ) -# for i_n, nd in enumerate(list_input_nodes_sorted): -# # add sockets for this node in the subseq rows -# for sckt in nd.outputs: -# print("i_n", i_n) -# print("nd", nd) -# print( -# getattr( -# sckt, -# "default_value", -# ) -# ) - -# tmp_values = [] -# for idx in range(tot_frame_no): -# bpy.app.handlers.frame_change_pre[0]("dummy") -# bpy.data.scenes["Scene"].frame_current = idx -# bpy.ops.node.randomise_all_geometry_sockets( -# "INVOKE_DEFAULT" -# ) # issue -# # w/ this being called so often - -# # might need moved to diff for loop? -# tmp_values.append( -# getattr( -# sckt, -# "default_value", -# ) -# ) - -# print(tmp_values) -# tmp_sck = nd.name -# all_geom_dict[tmp_GNG] = tmp_sck -# GNG_sck_values_str = tmp_GNG + tmp_sck -# GNG_sck_values_str = "Values " + GNG_sck_values_str -# print(GNG_sck_values_str) -# all_geom_dict[GNG_sck_values_str] = tmp_values diff --git a/randomiser/Test_int_min_max.py b/randomiser/Test_int_min_max.py deleted file mode 100644 index ba82dac..0000000 --- a/randomiser/Test_int_min_max.py +++ /dev/null @@ -1,56 +0,0 @@ -from array import array - -import bpy -import numpy as np - -selfie = bpy.data.scenes["Scene"].socket_props_per_UD.collection[0] -m_str = "int_1d" - -min_int_np = np.int64(1) -max_int_np = np.int64(3) - -print("min_int_np = ", min_int_np) - -min_int_blender = int(min_int_np) -print("min_int_blender = ", min_int_blender) - -# min_array = getattr(self, "min_" + m_str) -# max_array = getattr(self, "max_" + m_str) -min_array = np.array(getattr(selfie, "min_" + m_str)) -max_array = np.array(getattr(selfie, "max_" + m_str)) - -# print("MIN CLOSURE min_array ", type(min_array)) -# MAX RECURSION DEPTH EXCEEDED WHILE CALLING A PYTHON OBJECT -# print("MIN CLOSURE max_array ", type(max_array)) - -# min_array = ast.literal_eval(str(min_array)) -# max_array = ast.literal_eval(str(max_array)) -# min_array = np.array(min_array) -# max_array = np.array(max_array) - -# min_array = [pyt_int.item() for pyt_int in min_array] -# max_array = [pyt_int.item() for pyt_int in max_array] - -# print("MIN CLOSURE min_array FIRST", type(min_array[0])) -# print("MIN CLOSURE max_array FIRST", type(max_array[0])) - -# min_array = np.asarray(min_array,dtype="int") -# max_array = np.asarray(max_array,dtype="int") -min_array = array("i", min_array) -max_array = array("i", max_array) -# min_array = np.array(min_array) -# max_array = np.array(max_array) - -# print("MIN CLOSURE min_array ", type(min_array)) -# print("MIN CLOSURE max_array ", type(max_array)) - -# print("MIN CLOSURE min_array FIRST", type(min_array[0])) -# print("MIN CLOSURE max_array FIRST", type(max_array[0])) -print(min_array > max_array) - -cond_min = [min > max for min, max in zip(min_array, max_array)] -# if (min_array > max_array).all(): -if any(cond_min): - cond = np.where(cond_min, max_array, min_array) -# print('np.where result = ', cond) -# print('np.where type = ', type(cond)) diff --git a/randomiser/define_prop/check_idx_test.py b/randomiser/define_prop/check_idx_test.py deleted file mode 100644 index 5a7b01b..0000000 --- a/randomiser/define_prop/check_idx_test.py +++ /dev/null @@ -1,275 +0,0 @@ -import random -import re - -import bpy -from mathutils import Vector - - -def check_idx(full_str): - tmp_attr = get_attr_only_str(full_str) - print("ERROR ======== attr_str", tmp_attr) - a = re.findall("\[(.*?)\]", tmp_attr) - if a: - nums = list(map(int, a[0].split(","))) - # print(nums) - # print(type(str(nums[0]))) - else: - nums = [] - - # print("len", len(nums)) - - if len(nums) > 0: - # print('Number is ', nums) - # print('type(Number) is ', type(nums[0])) - - num_str = str(nums[0]) - - tmp_UD_name = full_str - tmp_UD_name = tmp_UD_name.replace("[" + num_str + "]", "") - new_attribute_only_str = tmp_attr.replace("[" + num_str + "]", "") - - print("new_attribute_only_str", new_attribute_only_str) - - obj_str = get_obj_str(tmp_UD_name) - print("obj_str", obj_str) - - else: - obj_str = get_obj_str(full_str) - new_attribute_only_str = [] - - return obj_str, nums, new_attribute_only_str - - -# %% -def get_obj_str(full_str): - full_str = "bpy.data.objects['Cube'].location" - # path_prop, path_attr = full_str.rsplit("[]", 1) - x = full_str.split(".") - # print(path_prop) - for i in x: - print(i) - if "[" in i: - ii = i.split("[") - fiinal = ii[1] - fiinal = fiinal[:-1] - print("fiinal= ", fiinal) - - print(type(fiinal)) - print(fiinal) - return fiinal - - -# %% - - -####what did this used to look like??????? -def attr_get_type(obj, path): - # if '[' in path: - # print(' [ is in path') - - check_idx(path)[0] - nums = check_idx(path)[1] - new_attribute_only_str = check_idx(path)[2] - - if len(nums) > 0: - print("-----ENTERED LENS------") - - path = new_attribute_only_str - - if "." in path: - # gives us: ('modifiers["Subsurf"]', 'levels') - path_prop, path_attr = path.rsplit(".", 1) - - # same as: prop = obj.modifiers["Subsurf"] - prop = obj.path_resolve(path_prop) - else: - prop = obj - # single attribute such as name, location... etc - path_attr = path - - # same as: prop.levels = value - - print("prop = ", prop) - print("path_attr = ", path_attr) - - try: - if len(nums) > 0: - action = getattr(prop, path_attr) - print("action = ", action) - action = action[0] - print("action = ", action) - print("type(action) = ", type(action)) - else: - action = getattr(prop, path_attr) - print("action = ", action) - print("type(action) = ", type(action)) - except Exception: - # print("Property does not exist") - action = "dummy" - prop = "dummy" - path_attr = "dummy" - # print(action, prop, path_attr) - # print(type(action)) - # action = getattr(prop, path_attr) - - return type(action), action, prop, path_attr - # setattr(prop, path_attr, value) - - -def get_attr_only_str(full_str): - if "data" in full_str: - mod = 0 - elif "[" in full_str: - mod = 1 - else: - mod = 0 - - len_path = len(full_str.rsplit(".", 100)) - mod - print("len_path = ", len_path) - - list_parent_nodes_str = full_str.rsplit(".", len_path - 3) - print("list_parent_nodes_str = ", list_parent_nodes_str) - - attribute_only_str = full_str.replace(list_parent_nodes_str[0] + ".", "") - - return attribute_only_str - - -def attr_set_val(obj, path, min_val, max_val, UD_type): - if "." in path: - # gives us: ('modifiers["Subsurf"]', 'levels') - # len_path = len(full_str.rsplit(".", config.MAX_NUMBER_OF_SUBPANELS)) - path_prop, path_attr = path.rsplit(".", 1) - - # same as: prop = obj.modifiers["Subsurf"] - prop = obj.path_resolve(path_prop) - else: - prop = obj - # single attribute such as name, location... etc - path_attr = path - - # same as: prop.levels = value - - try: - getattr(prop, path_attr) - except Exception: - # print("Property does not exist") - pass - # action = getattr(prop, path_attr) - - print("attr_set_val type ========================= ", UD_type) - # if UD_type==float: - # min_array = np.array(getattr(self, "min_" + m_str)) - # max_array = np.array(getattr(self, "max_" + m_str)) - - if UD_type == float: - print("HELLO 1D FLOAT!!!!!!!") - # if rand_posx: - # getattr(context.scene.camera, value_str)[0] = uniform( - # loc_x_range[0], loc_x_range[1] - # ) - - # if rand_rotz: - # rand_z = uniform(rot_z_range[0], rot_z_range[1]) - # else: - # rand_z = uniform(0.0, 0.0) - - # vec = Vector([rand_x, rand_y, rand_z]) - - # bpy.data.objects["Camera"].rotation_euler[0] = vec[0] - - value = random.uniform(min_val, max_val) - print(value) - elif UD_type == Vector: - print("HELLO 3D VECTOR FLOAT!!!!!") - value = random.uniform(min_val, max_val) - print(value) - else: - print("HELLO INTEGER!!!!!!!!!!!!!!!!!!!!") - value = random.randint(min_val, max_val) - print(value) - - setattr(prop, path_attr, value) - - -##### frame_current -# full_str = "bpy.context.scene.frame_current" -# attr_only_str = get_attr_only_str(full_str) -# print("attr_only_str = ", attr_only_str) - -# prop = bpy.context.scene -## single attribute such as name, location... etc -# path_attr = "frame_current" -# action = getattr(prop, path_attr) -# print("prop = ", prop) -# print("action getattr = ", action) - -# prop_type, action, prop, path_attr = attr_get_type( -# bpy.context.scene, attr_only_str -# ) - -# min_val = np.array( -# getattr( -# bpy.context.scene.socket_props_per_UD.collection[1], -# "min_int_1d", -# ) -# ) - -# max_val = np.array( -# getattr( -# bpy.context.scene.socket_props_per_UD.collection[1], -# "max_int_1d", -# ) -# ) -# print(min_val) -# print(max_val) -# print(prop_type) -## min_val = bpy.context.scene. -# socket_props_per_UD.collection[0].min_float_1d[0] -## max_val = bpy.context.scene. -# socket_props_per_UD.collection[0].max_float_1d[0] -# attr_set_val( -# bpy.context.scene, -# attr_only_str, -# min_val, -# max_val, -# prop_type, -# ) - - -#### bpy.data.objects['Cube'].location[0] -#### bpy.context.scene.camera.location[0] - -full_str = "bpy.data.objects['Cube'].location[0]" -attr_only_str = get_attr_only_str(full_str) -print("attr_only_str = ", attr_only_str) - -prop = bpy.data.objects["Cube"] -# single attribute such as name, location... etc -# path_attr = "location[0]" -# action = getattr(prop, path_attr) -# print("prop = ", prop) -# print("action getattr = ", action) - -prop_type, action, prop, path_attr = attr_get_type( - bpy.context.scene, attr_only_str -) - -print("-----OUTPUTS attr_get_type------") -print("prop_type", prop_type) -print("action", action) -print("prop", prop) -print("path_attr", path_attr) - - -#### frame_current -full_str = "bpy.context.scene.frame_current" -attr_only_str = get_attr_only_str(full_str) -print("attr_only_str = ", attr_only_str) - -prop = bpy.context.scene -print("-----OUTPUTS attr_get_type------") -print("prop_type", prop_type) -print("action", action) -print("prop", prop) -print("path_attr", path_attr) diff --git a/randomiser/define_prop/obj_path_resolve_test.py b/randomiser/define_prop/obj_path_resolve_test.py deleted file mode 100644 index f9b9136..0000000 --- a/randomiser/define_prop/obj_path_resolve_test.py +++ /dev/null @@ -1,503 +0,0 @@ -import random -import re - -import bpy -import numpy as np -from mathutils import Vector - - -# %% -def get_obj_str(full_str): - full_str = "bpy.data.objects['Cube'].location" - # path_prop, path_attr = full_str.rsplit("[]", 1) - x = full_str.split(".") - # print(path_prop) - for i in x: - print(i) - if "[" in i: - ii = i.split("[") - fiinal = ii[1] - fiinal = fiinal[:-1] - print("fiinal= ", fiinal) - - print(type(fiinal)) - print(fiinal) - return fiinal - - -# %% - - -def attr_get_type(obj, path): - # if '[' in path: - # print(' [ is in path') - - a = re.findall("\[(.*?)\]", path) - if a: - nums = list(map(int, a[0].split(","))) - print(nums) - print(type(str(nums[0]))) - else: - nums = [] - - print("len", len(nums)) - - if len(nums) > 0: - print("Number is ", nums) - print("type(Number) is ", type(nums[0])) - - num_str = str(nums[0]) - - tmp_UD_name = path - tmp_UD_name = tmp_UD_name.replace("[" + num_str + "]", "") - new_attribute_only_str = path.replace("[" + num_str + "]", "") - - print("new_attribute_only_str", new_attribute_only_str) - path = new_attribute_only_str - - if "." in path: - # gives us: ('modifiers["Subsurf"]', 'levels') - path_prop, path_attr = path.rsplit(".", 1) - - # same as: prop = obj.modifiers["Subsurf"] - prop = obj.path_resolve(path_prop) - else: - prop = obj - # single attribute such as name, location... etc - path_attr = path - - else: - if "." in path: - # gives us: ('modifiers["Subsurf"]', 'levels') - path_prop, path_attr = path.rsplit(".", 1) - - # same as: prop = obj.modifiers["Subsurf"] - prop = obj.path_resolve(path_prop) - else: - prop = obj - # single attribute such as name, location... etc - path_attr = path - - # same as: prop.levels = value - - print("prop = ", prop) - print("path_attr = ", path_attr) - - try: - # action = getattr(prop, path_attr) - # print('action = ', action) - # print('type(action) = ', type(action)) - if len(nums) > 0: - action = getattr(prop, path_attr) - print("action = ", action) - action = action[0] - print("action = ", action) - print("type(action) = ", type(action)) - else: - action = getattr(prop, path_attr) - print("action = ", action) - print("type(action) = ", type(action)) - except Exception: - # print("Property does not exist") - action = "dummy" - prop = "dummy" - path_attr = "dummy" - # print(action, prop, path_attr) - # print(type(action)) - # action = getattr(prop, path_attr) - - return type(action), action, prop, path_attr - # setattr(prop, path_attr, value) - - -# print("CAMERA.LOCATION !!!!!!!!!!!!!!!") -# print("INPUTS ======") -# attr_str = "camera.location" -# print("bpy.context.scene as obj and str = ", attr_str) -# action_type, action, prop, path_attr = attr_get_type( -# bpy.context.scene, attr_str -# ) -# print("OUTPUTS ===== ") -# print("type(action) = ", action_type) -# print("action = ", action) -# print("prop = ", prop) -# print("path_attr = ", path_attr) - -##print("FRAME_CURRENT !!!!!!!!!!!!!!!") -##print("INPUTS ======") -##attr_str = "frame_current" -##print("bpy.context.scene as obj and str = ", attr_str) -##action_type, action, prop, path_attr = attr_get_type( -## bpy.context.scene, attr_str -##) -##print("OUTPUTS ===== ") -##print("type(action) = ", action_type) -##print("action = ", action) -##print("prop = ", prop) -##print("path_attr = ", path_attr) - - -## bpy.context.scene.objects['Cube'].collision.absorption -## bpy.data.objects['Cube'].location -## bpy.data.objects['Cube'].rotation_euler.x -# print("bpy.data.objects[Cube] + string !!!!!!!!!!!!!!!") -# print("INPUTS ======") -##attr_str = 'location' -# attr_str = 'rotation_euler.x' -##attr_str = "collision.absorption" -# print("bpy.data.objects[Cube] as obj and str = ", attr_str) -# action_type, action, prop, path_attr = attr_get_type( -# bpy.data.objects["Cube"], attr_str -# ) -# print("OUTPUTS ===== ") -# print("type(action) = ", action_type) -# print("action = ", action) -# print("prop = ", prop) -# print("path_attr = ", path_attr) - - -def get_attr_only_str(full_str): - if "data" in full_str: - mod = 0 - elif "[" in full_str: - mod = 1 - else: - mod = 0 - - len_path = len(full_str.rsplit(".", 100)) - mod - print("len_path = ", len_path) - - list_parent_nodes_str = full_str.rsplit(".", len_path - 3) - print("list_parent_nodes_str = ", list_parent_nodes_str) - - attribute_only_str = full_str.replace(list_parent_nodes_str[0] + ".", "") - - return attribute_only_str - - -full_str = "bpy.context.scene.objects['Cube'].collision.absorption" -attr_only_str = get_attr_only_str(full_str) -print("attr_only_str = ", attr_only_str) - -# full_str='bpy.context.scene.camera.location' -# attr_only_str=get_attr_only_str(full_str) -# print('attr_only_str = ', attr_only_str) - - -def attr_set_val(obj, path, min_val, max_val, UD_type): - if "." in path: - # gives us: ('modifiers["Subsurf"]', 'levels') - # len_path = len(full_str.rsplit(".", config.MAX_NUMBER_OF_SUBPANELS)) - path_prop, path_attr = path.rsplit(".", 1) - - # same as: prop = obj.modifiers["Subsurf"] - prop = obj.path_resolve(path_prop) - else: - prop = obj - # single attribute such as name, location... etc - path_attr = path - - # same as: prop.levels = value - - try: - getattr(prop, path_attr) - except Exception: - # print("Property does not exist") - pass - # action = getattr(prop, path_attr) - - print("attr_set_val type ========================= ", UD_type) - # if UD_type==float: - # min_array = np.array(getattr(self, "min_" + m_str)) - # max_array = np.array(getattr(self, "max_" + m_str)) - - if UD_type == float: - print("HELLO 1D FLOAT!!!!!!!") - # if rand_posx: - # getattr(context.scene.camera, value_str)[0] = uniform( - # loc_x_range[0], loc_x_range[1] - # ) - - # if rand_rotz: - # rand_z = uniform(rot_z_range[0], rot_z_range[1]) - # else: - # rand_z = uniform(0.0, 0.0) - - # vec = Vector([rand_x, rand_y, rand_z]) - - # bpy.data.objects["Camera"].rotation_euler[0] = vec[0] - - value = random.uniform(min_val, max_val) - print(value) - elif UD_type == Vector: - print("HELLO 3D VECTOR FLOAT!!!!!") - value = random.uniform(min_val, max_val) - print(value) - else: - print("HELLO INTEGER!!!!!!!!!!!!!!!!!!!!") - value = random.randint(min_val, max_val) - print(value) - - setattr(prop, path_attr, value) - - -#### camera.location -full_str = "bpy.context.scene.camera.location" -attr_only_str = get_attr_only_str(full_str) -print("attr_only_str = ", attr_only_str) - -path_prop, path_attr = "camera.location".rsplit(".", 1) -prop = bpy.context.scene.path_resolve(path_prop) -action = getattr(prop, path_attr) -print("prop = ", prop) -print("action getattr = ", action) - -prop_type, action, prop, path_attr = attr_get_type( - bpy.context.scene, attr_only_str -) - -min_val = np.array( - getattr( - bpy.context.scene.socket_props_per_UD.collection[0], - "min_float_3d", - ) -) - -max_val = np.array( - getattr( - bpy.context.scene.socket_props_per_UD.collection[0], - "max_float_3d", - ) -) -print(min_val) -print(max_val) -print(prop_type) -# min_val = bpy.context.scene.socket_props_per_UD.collection[0].min_float_1d[0] -# max_val = bpy.context.scene.socket_props_per_UD.collection[0].max_float_1d[0] -attr_set_val( - bpy.context.scene, - attr_only_str, - min_val, - max_val, - prop_type, -) - - -#### frame_current -full_str = "bpy.context.scene.frame_current" -attr_only_str = get_attr_only_str(full_str) -print("attr_only_str = ", attr_only_str) - -prop = bpy.context.scene -# single attribute such as name, location... etc -path_attr = "frame_current" -action = getattr(prop, path_attr) -print("prop = ", prop) -print("action getattr = ", action) - -prop_type, action, prop, path_attr = attr_get_type( - bpy.context.scene, attr_only_str -) - -min_val = np.array( - getattr( - bpy.context.scene.socket_props_per_UD.collection[1], - "min_int_1d", - ) -) - -max_val = np.array( - getattr( - bpy.context.scene.socket_props_per_UD.collection[1], - "max_int_1d", - ) -) -print(min_val) -print(max_val) -print(prop_type) -# min_val = bpy.context.scene.socket_props_per_UD.collection[0].min_float_1d[0] -# max_val = bpy.context.scene.socket_props_per_UD.collection[0].max_float_1d[0] -attr_set_val( - bpy.context.scene, - attr_only_str, - min_val, - max_val, - prop_type, -) - -#### collision.absorption -full_str = "bpy.context.scene.objects['Cube'].collision.absorption" -attr_only_str = get_attr_only_str(full_str) -print("attr_only_str = ", attr_only_str) - -path_prop, path_attr = "collision.absorption".rsplit(".", 1) -prop = bpy.context.scene.objects["Cube"].path_resolve(path_prop) -action = getattr(prop, path_attr) -print("prop = ", prop) -print("action getattr = ", action) - -prop_type, action, prop, path_attr = attr_get_type( - bpy.data.objects["Cube"], attr_only_str -) - - -min_val = getattr( - bpy.context.scene.socket_props_per_UD.collection[2], - "min_float_1d", -) - - -max_val = np.array( - getattr( - bpy.context.scene.socket_props_per_UD.collection[2], - "max_float_1d", - ) -) -print(min_val) -print(max_val) -print(prop_type) - -objects_in_scene = [] -for i, key in enumerate(bpy.data.objects): - print(i) - print(key.name) - objects_in_scene.append(key.name) - - -if "[" in full_str: - # obj=bpy.data.objects.get(fiinal) - obj_str = get_obj_str(full_str) - - print("obj_str = ", obj_str) - for i, obj in enumerate(objects_in_scene): - print(obj) - print(i) - # regex=re.compile(r'^test-\d+$') - - if obj in obj_str: - print("Yay found cube") - print(i) - idx = i - - attr_set_val( - bpy.data.objects[idx], - attr_only_str, - min_val, - max_val, - prop_type, - ) - - -####Tom examples - bpy.data.objects["Cube"].location[0] -# bpy.data.objects["Cube"].rotation_euler -full_str = "bpy.data.cameras['Camera'].dof.aperture_fstop" -attr_only_str = get_attr_only_str(full_str) -print("attr_only_str = ", attr_only_str) - - -path_prop, path_attr = "dof.aperture_fstop".rsplit(".", 1) -prop = bpy.data.cameras["Camera"].path_resolve(path_prop) -action = getattr(prop, path_attr) -print("prop = ", prop) -print("action getattr = ", action) - -prop_type, action, prop, path_attr = attr_get_type( - bpy.data.cameras["Camera"], attr_only_str -) - - -full_str = "bpy.data.objects['Cube'].location[0]" -attr_only_str = get_attr_only_str(full_str) -print("attr_only_str = ", attr_only_str) - - -path_prop = "location" -path_attr = "location[0]" -prop = bpy.data.objects[1].path_resolve("location") -print(prop) -action = prop[0] -# print("prop = ", prop) -# print("action getattr = ", action) - - -attr_type = attr_get_type(bpy.data.objects[idx], attr_only_str)[0] - -print("attr_type", attr_type) - -# min_val = np.array( -# getattr( -# bpy.context.scene.socket_props_per_UD.collection[0], -# "min_float_3d", -# ) -# ) - -# max_val = np.array( -# getattr( -# bpy.context.scene.socket_props_per_UD.collection[0], -# "max_float_3d", -# ) -# ) -# print(min_val) -# print(max_val) -# print(prop_type) -## min_val = bpy.context.scene.socket_props_per_UD. -# collection[0].min_float_1d[0] -## max_val = bpy.context.scene.socket_props_per_UD. -# collection[0].max_float_1d[0] -# attr_set_val( -# bpy.context.scene, -# attr_only_str, -# min_val, -# max_val, -# prop_type, -# ) - -# import re -# objects_in_scene=[] -# for i, key in enumerate(bpy.data.objects): -# # print(i) -# # print(key.name) -# objects_in_scene.append(key.name) - -# UD_name = "bpy.data.objects['Cube'].location[0]" -# print("ERROR ======= UD.name", UD_name) -# tmp_attr = get_attr_only_str(UD_name) -# print("ERROR ======== attr_str", tmp_attr) -# a = re.findall('\[(.*?)\]', tmp_attr) -# if a: -# nums = list(map(int, a[0].split(','))) -# print(nums) -# print(type(str(nums[0]))) -# else: -# nums = [] -# -# print("len", len(nums)) - -# if len(nums)>0: -# print('Number is ', nums) -# print('type(Number) is ', type(nums[0])) -# -# num_str=str(nums[0]) -# -# attribute_only_str = tmp_attr.replace("[" + num_str + "]", "") -# -# print("new_UD_name", attribute_only_str) -# -# obj_str = get_obj_str(UD_name) -# print("obj_str", obj_str) -# -# -# else: -# obj_str = get_obj_str(UD_name) -# print("obj_str", obj_str) -# -# for i, obj in enumerate(objects_in_scene): -# # regex=re.compile(r'^test-\d+$') - -# if obj in obj_str: -# current_obj = obj -# print("Found ", current_obj) -# idx = i - -# print("location[0]??????", current_obj) -# %% diff --git a/randomiser/save_parameter_outputs_geom_test_in_Blender.py b/randomiser/save_parameter_outputs_geom_test_in_Blender.py deleted file mode 100644 index e4de128..0000000 --- a/randomiser/save_parameter_outputs_geom_test_in_Blender.py +++ /dev/null @@ -1,1161 +0,0 @@ -import json -import pathlib -from random import seed - -# from .utils import nodes2rand -import bpy - - -def get_material_nodes_to_randomise_all( - material_str: str = "Material", - node2randomise_prefix: str = "random", -): - """Get list of all input nodes to randomise for a given material. - - Input nodes are defined as nodes with no input sockets. - The input nodes to randomise are identified because their - name is prefixed with node2randomise_prefix (case insensitive). - - Both independent nodes, and nodes inside a group are searched. - The 'artificial' nodes that show up inside a node group, usually named - 'Group input' or 'Group output' are excluded from the search. - - Parameters - ---------- - material_str : str, optional - name of the material, by default "Material" - node2randomise_prefix : str, optional - prefix that identifies the nodes to randomise, by default 'random' - - Returns - ------- - list - list of the input nodes to randomise - """ - - # find input nodes that startwith random - # in any of those groups - # excluding 'Group' nodes - list_indep_input_nodes = get_material_nodes_to_randomise_indep( - material_str, node2randomise_prefix - ) - - list_group_input_nodes = get_material_nodes_to_randomise_group( - material_str, node2randomise_prefix - ) - - return list_indep_input_nodes + list_group_input_nodes - - -def get_node_group_parent_of_node_group(node_group): - # input node group could be a material, in which case the - # node group parent is none - parent_ng = None - - if node_group is not None and (type(node_group) != bpy.types.Material): - list_all_ngs = [ - gr for gr in bpy.data.node_groups if gr.type == node_group.type - ] - - for gr in list_all_ngs: - for nd in gr.nodes: - if ( - (hasattr(nd, "node_tree")) - and (hasattr(nd.node_tree, "name")) - and (nd.node_tree.name == node_group.name) - ): - parent_ng = gr - break - - return parent_ng # immediate parent - - -def get_parent_of_gng( - node_group, -): - """Get immediate parent of geometry node group. - - Returns the node tree that the input geometry node group is - inside of. - - If the input geometry node group has no parent (i.e., it is a root node) - this function will return None for its parent - - Parameters - ---------- - node_group : _type_ - _description_ - - Returns - ------- - _type_ - _description_ - """ - - # the parent of a GNG can only be another GNG, or None - return get_node_group_parent_of_node_group(node_group) - - -def get_parent_of_sng(node_group): - """Get immediate parent of shader node group. - - Returns the node tree that the input shader node group is - inside of. The top parent of a shader node group would be a material. - - The parent of a material is None. The parent of a node group - that is not linked to a material is also None? - - Parameters - ---------- - node_group : _type_ - _description_ - - Returns - ------- - _type_ - _description_ - """ - # the parent of a SNG can be another SNG, a Material, or None - # input node group could be a material, in which case the parent is none - - # check if parent is a node group - parent_sng = get_node_group_parent_of_node_group(node_group) - - # if the input node group is NOT a material: - # check further if parent is a material - if ( - (parent_sng is None) - and (node_group is not None) - and (type(node_group) is not bpy.types.Material) - ): - list_all_materials = [ - mat for mat in bpy.data.materials if mat.use_nodes - ] - # TODO: is it possible that a material has no node_tree? - # (I think maybe, if use_nodes is set to False) - # TODO: use candidate_materials property? - for mat in list_all_materials: - for nd in mat.node_tree.nodes: - if ( - (nd.type == "GROUP") - and (hasattr(nd, "node_tree")) - and (hasattr(nd.node_tree, "name")) - and (nd.node_tree.name == node_group.name) - ): - parent_sng = mat - break - - return parent_sng # immediate parent - - -def get_parent_of_ng(node_group): - # Select the function to compute the parent of the - # node group based on the node group type - if (type(node_group) == bpy.types.Material) or ( - node_group.type == "SHADER" - ): - return get_parent_of_sng(node_group) - elif node_group.type == "GEOMETRY": - return get_parent_of_gng(node_group) - - -def get_root_and_depth_of_ng(node_group): - # TODO: combine this with path? - - # compute root node group: this is the node group in - # the path whose parent is None. For shader node groups, - # it will be the material - parent = get_parent_of_ng(node_group) - depth = 0 - if parent is None: - return (parent, depth) - else: - depth = 1 - while get_parent_of_ng(parent) is not None: - depth += 1 - parent = get_parent_of_ng(parent) - - return (parent, depth) - - -def get_map_inner_ngs_given_roots( - list_candidate_roots, -): - """Compute dictionary that maps inner node groups to a tuple made of - - the inner node group's root parent (a node group, a material, or None) - - the inner node group's depth - - The dictionary is computed for inner node groups whose root parents - are in the input list - - Parameters - ---------- - list_candidate_root_node_groups : _type_ - list of node groups - """ - # TODO: check in inputs are inded roots and print warning if not? - list_node_groups = [ - gr for gr in bpy.data.node_groups # if gr.type == "GEOMETRY" - ] - - map_node_group_to_root = {} - for gr in list_node_groups: - root_parent, depth = get_root_and_depth_of_ng(gr) - if root_parent in list_candidate_roots: - map_node_group_to_root[gr] = (root_parent, depth) # tuple - - return map_node_group_to_root - - -def get_path_to_ng(node_group): - """Compute path of parent group nodes up - to the input group node - - Both the root parent and the input - group node are included, except if the root parent is None - - Parameters - ---------- - gng : _type_ - inner geometry group need of which we want to - obtain the path - - Returns - ------- - path_to_ng: list - a list of parent geometry group nodes up to the input one - """ - parent = get_parent_of_ng(node_group) - if parent is None: - path_to_ng = [] # why empty rather than [None]? - else: - path_to_ng = [parent] - while get_parent_of_ng(parent) is not None: - parent = get_parent_of_ng(parent) - path_to_ng.append(parent) - - path_to_ng.reverse() - - path_to_ng.append(node_group) - return path_to_ng - - -def get_max_depth_of_root(root_parent_node_group): - """Compute the maximum depth of any inner geometry group - for the given root parent node group - - A root parent node group is a node group or a Material whose - parent is None - - Parameters - ---------- - root_parent_node_group : _type_ - root parent node group of which we want to compute the - maximum depth - - Returns - ------- - max_depth : int - the depth of the innermost node group - for this root parent node group - """ - map_inner_ngs = get_map_inner_ngs_given_roots([root_parent_node_group]) - - max_depth = 0 - if map_inner_ngs: - max_depth = max([v[1] for _, v in map_inner_ngs.items()]) - - return max_depth - - -def get_material_nodes_to_randomise_group( - material_str: str = "Material", - node2randomise_prefix: str = "random", -): - """Get list of *group* input nodes to randomise for a given material. - - Input nodes are defined as nodes with no input sockets. - The input nodes to randomise are identified because their - name is prefixed with node2randomise_prefix (case insensitive). - - Both independent nodes, and nodes inside a group are searched. - The 'artificial' nodes that show up inside a node group, usually named - 'Group input' or 'Group output' are excluded from the search. - - Parameters - ---------- - material_str : str, optional - name of the material, by default "Material" - node2randomise_prefix : str, optional - prefix that identifies the nodes to randomise, by default 'random' - - Returns - ------- - list - list of all *group* input nodes to randomise for this material - """ - - # list of nodes for current material - # belonging to a group; the group can be any levels deep - - # list of inner node groups for this material - map_inner_node_groups = get_map_inner_ngs_given_roots( - [bpy.data.materials[material_str]] - ) - list_inner_node_groups = list(map_inner_node_groups.keys()) - - # list of all material nodes inside a group - list_material_nodes_in_groups = [] - for ng in list_inner_node_groups: - list_material_nodes_in_groups.extend(ng.nodes) # nodes inside groups - - # find input nodes that startwith random - # in any of those groups - # excluding 'Group' nodes - list_input_nodes = get_nodes_to_randomise_from_list( - list_material_nodes_in_groups - ) - - return list_input_nodes - - -### Called by get_material_nodes_to_randomise_indep -def get_nodes_to_randomise_from_list( - list_candidate_nodes: list, - node2randomise_prefix: str = "random", -): - """Get list of nodes to randomise from list. - - Input nodes are defined as nodes with no input sockets. - The nodes to randomise are input nodes whose name starts - with 'node2randomise_prefix' (case insensitive). - - The 'artificial' nodes that show up inside a node group, usually named - 'Group input' or 'Group output' are excluded from the search. - - Parameters - ---------- - list_candidate_nodes : list - list of the candidate nodes to randomise - node2randomise_prefix : str, optional - prefix that identifies the nodes to randomise, by default 'random' - - Returns - ------- - list - list of the input nodes to randomise - """ - - # ensure list_candidate_nodes is unique - list_candidate_nodes = list(set(list_candidate_nodes)) - - # find input nodes that start with the random keyword - # excluding 'Group' artificial nodes - list_input_nodes = [ - nd - for nd in list_candidate_nodes - if len(nd.inputs) == 0 - and nd.name.lower().startswith(node2randomise_prefix.lower()) - and nd.type - not in [ - "GROUP_INPUT", - "GROUP_OUTPUT", - ] - ] - - return list_input_nodes - - -##### Function called by main code -def get_material_nodes_to_randomise_indep( - material_str: str = "Material", - node2randomise_prefix: str = "random", -): - """Get list of *independent* input nodes to randomise for a given material. - - Input nodes are defined as nodes with no input sockets. - The input nodes to randomise are identified because their - name is prefixed with node2randomise_prefix (case insensitive). - - Both independent nodes, and nodes inside a group are searched. - The 'artificial' nodes that show up inside a node group, usually named - 'Group input' or 'Group output' are excluded from the search. - - Parameters - ---------- - material_str : str, optional - name of the material, by default "Material" - node2randomise_prefix : str, optional - prefix that identifies the nodes to randomise, by default 'random' - - Returns - ------- - list - list of all *group* input nodes to randomise for this material - """ - - # list of nodes for current material - # not belonging to a group - list_material_nodes_indep = [] - for nd in bpy.data.materials[material_str].node_tree.nodes: - if nd.type != "GROUP": - list_material_nodes_indep.append(nd) - - # find input nodes that startwith random - list_input_nodes = get_nodes_to_randomise_from_list( - list_material_nodes_indep - ) - - return list_input_nodes - - -### GEOMETRY -# collection[N].collection[S] -# [N] = 0, 1, 2 for each node group (even NG within NG) -# [S] = 0, 1 etc. for each socket within node group -# Actual Geom values followed by Min_Max -# Node group called "Geometry Nodes" -bpy.data.node_groups["Geometry Nodes"].nodes["RandomConeDepth"].outputs[ - 0 -].default_value -bpy.data.scenes["Scene"].socket_props_per_gng.collection[0].collection[ - 0 -].min_float_1d[0] -bpy.data.scenes["Scene"].socket_props_per_gng.collection[0].collection[ - 0 -].max_float_1d[0] - -bpy.data.node_groups["Geometry Nodes"].nodes["RandomRadiusBottom"].outputs[ - 0 -].default_value -bpy.data.scenes["Scene"].socket_props_per_gng.collection[0].collection[ - 1 -].min_float_1d[0] -bpy.data.scenes["Scene"].socket_props_per_gng.collection[0].collection[ - 1 -].max_float_1d[0] - - -# Node group called "NodeGroup" which is associated -# with "Geometry Nodes" node group -bpy.data.node_groups["NodeGroup"].nodes["RandomConeDepth.001"].outputs[ - 0 -].default_value -bpy.data.scenes["Scene"].socket_props_per_gng.collection[1].collection[ - 0 -].min_float_1d[0] -bpy.data.scenes["Scene"].socket_props_per_gng.collection[1].collection[ - 0 -].max_float_1d[0] - -bpy.data.node_groups["NodeGroup"].nodes["RandomRadiusBottom.001"].outputs[ - 0 -].default_value -bpy.data.scenes["Scene"].socket_props_per_gng.collection[1].collection[ - 1 -].min_float_1d[0] -bpy.data.scenes["Scene"].socket_props_per_gng.collection[1].collection[ - 1 -].max_float_1d[0] - -# Separate node group called "Geometry Nodes.001" -bpy.data.node_groups["Geometry Nodes.001"].nodes["RandomSize"].outputs[ - 0 -].default_value -bpy.data.scenes["Scene"].socket_props_per_gng.collection[2].collection[ - 0 -].min_float_1d[0] -bpy.data.scenes["Scene"].socket_props_per_gng.collection[2].collection[ - 0 -].max_float_1d[0] - - -### MATERIALS -# collection[N].collection[S] -# [N] = 0 "Material.001" -# [S] = 0 RandomMetallic, = 1 RandomBaseRGB -# [N] = 1 "Material" and "NodeGroup.001" -# [S] = 0 "RandomMetallic.001", 1 = RandomMetallic -# [S] = 2 "RandomBaseRGB.001", 3 RandomBaseRGB -# Actual Mat values followed by Min_Max -# Material called "Material" containing node_tree.nodes -bpy.data.materials["Material"].node_tree.nodes["RandomMetallic"].outputs[ - 0 -].default_value -bpy.data.scenes["Scene"].socket_props_per_material.collection[1].collection[ - 1 -].min_float_1d[0] -bpy.data.scenes["Scene"].socket_props_per_material.collection[1].collection[ - 1 -].max_float_1d[0] - -bpy.data.materials["Material"].node_tree.nodes["RandomBaseRGB"].outputs[ - 0 -].default_value -# bpy.data.scenes['Scene'].socket_props_per_material.collection[1].collection[3].min_rgba_4d[0-3] -# bpy.data.scenes['Scene'].socket_props_per_material.collection[1].collection[3].max_rgba_4d[0-3] - - -# Node group called "NodeGroup.001" which is associated -# with "Material" material -bpy.data.node_groups["NodeGroup.001"].nodes["RandomMetallic.001"].outputs[ - 0 -].default_value -bpy.data.scenes["Scene"].socket_props_per_material.collection[1].collection[ - 0 -].min_float_1d[0] -bpy.data.scenes["Scene"].socket_props_per_material.collection[1].collection[ - 0 -].max_float_1d[0] - -bpy.data.node_groups["NodeGroup.001"].nodes["RandomBaseRGB.001"].outputs[ - 0 -].default_value -# bpy.data.scenes['Scene'].socket_props_per_material.collection[1].collection[2].min_rgba_4d[0-3] -# bpy.data.scenes['Scene'].socket_props_per_material.collection[1].collection[2].max_rgba_4d[0-3] - - -# Separate material called "Material.001" containing node_tree.nodes -bpy.data.materials["Material.001"].node_tree.nodes["RandomMetallic"].outputs[ - 0 -].default_value -bpy.data.scenes["Scene"].socket_props_per_material.collection[0].collection[ - 0 -].min_float_1d[0] -bpy.data.scenes["Scene"].socket_props_per_material.collection[0].collection[ - 0 -].max_float_1d[0] - -bpy.data.materials["Material.001"].node_tree.nodes["RandomBaseRGB"].outputs[ - 0 -].default_value -# bpy.data.scenes['Scene'].socket_props_per_material.collection[0].collection[1].min_rgba_4d[0-3] -# bpy.data.scenes['Scene'].socket_props_per_material.collection[0].collection[1].max_rgba_4d[0-3] - - -### ALL -if bpy.data.scenes["Scene"].seed_properties.seed_toggle: # = True - seed(bpy.data.scenes["Scene"].seed_properties.seed) -tot_frame_no = bpy.context.scene.rand_all_properties.tot_frame_no - -### TRANSFORMS -bpy.data.scenes["Scene"].frame_current = 0 - -x_pos_vals = [] -y_pos_vals = [] -z_pos_vals = [] - - -x_rot_vals = [] -y_rot_vals = [] -z_rot_vals = [] - -if bpy.context.scene.randomise_camera_props.bool_delta: - loc_value_str = "delta_location" - value_str = "delta_rotation_euler" -else: - loc_value_str = "location" - value_str = "rotation_euler" - - -geom_single_test = [] -mat_single_test = [] -for idx in range(tot_frame_no): - bpy.app.handlers.frame_change_pre[0]("dummy") - bpy.data.scenes["Scene"].frame_current = idx - - x_pos_vals.append(getattr(bpy.context.scene.camera, loc_value_str)[0]) - y_pos_vals.append(getattr(bpy.context.scene.camera, loc_value_str)[1]) - z_pos_vals.append(getattr(bpy.context.scene.camera, loc_value_str)[2]) - - x_rot_vals.append(getattr(bpy.context.scene.camera, value_str)[0]) - y_rot_vals.append(getattr(bpy.context.scene.camera, value_str)[1]) - z_rot_vals.append(getattr(bpy.context.scene.camera, value_str)[2]) - - bpy.ops.node.randomise_all_geometry_sockets("INVOKE_DEFAULT") - geom_single_test.append( - bpy.data.node_groups[0] - .nodes["RandomConeDepth"] - .outputs[0] - .default_value - ) - - bpy.ops.node.randomise_all_material_sockets("INVOKE_DEFAULT") - mat_single_test.append( - bpy.data.materials["Material"] - .node_tree.nodes["RandomMetallic"] - .outputs[0] - .default_value - ) - - -### GEOMETRY -bpy.data.scenes["Scene"].frame_current = 0 -# geom ={'0': []} -# print(geom) -# for i in range(len(bpy.context.scene.socket_props_per_gng.collection)): -# if i>=1: -# geom[str(i)]=[] - -# print(geom) - -# mat ={'0': []} -# for i in range(len(bpy.context.scene.socket_props_per_material.collection)): -# if i>=1: -# mat[str(i)]=[] - -# all_geom=[] -# bpy.data.scenes["Scene"].socket_props_per_gng.collection[0].name -# bpy.data.scenes["Scene"].socket_props_per_gng.collection[0].collection[0].name -all_geom_dict = {} -cs = bpy.context.scene -for gng_idx in range(len(cs.socket_props_per_gng.collection)): - # get this subpanel's GNG - subpanel_gng = cs.socket_props_per_gng.collection[gng_idx] - tmp_GNG = subpanel_gng.name - print(tmp_GNG) - - sockets_props_collection = cs.socket_props_per_gng.collection[ - subpanel_gng.name - ].collection - - list_parent_nodes_str = [ - sckt.name.split("_")[0] for sckt in sockets_props_collection - ] - list_input_nodes = [ - bpy.data.node_groups[subpanel_gng.name].nodes[nd_str] - for nd_str in list_parent_nodes_str - ] - - list_input_nodes_sorted = sorted(list_input_nodes, key=lambda x: x.name) - for i_n, nd in enumerate(list_input_nodes_sorted): - # add sockets for this node in the subseq rows - for sckt in nd.outputs: - print("i_n", i_n) - print("nd", nd) - print( - getattr( - sckt, - "default_value", - ) - ) - - tmp_values = [] - for idx in range(tot_frame_no): - bpy.app.handlers.frame_change_pre[0]("dummy") - bpy.data.scenes["Scene"].frame_current = idx - bpy.ops.node.randomise_all_geometry_sockets( - "INVOKE_DEFAULT" - ) # issue - # w/ this being called so often - - # might need moved to diff for loop? - tmp_values.append( - getattr( - sckt, - "default_value", - ) - ) - - print(tmp_values) - tmp_sck = nd.name - all_geom_dict[tmp_GNG] = tmp_sck - GNG_sck_values_str = tmp_GNG + tmp_sck - GNG_sck_values_str = "Values " + GNG_sck_values_str - print(GNG_sck_values_str) - all_geom_dict[GNG_sck_values_str] = tmp_values - - # for sck_idx in range(len(subpanel_gng.collection)): - # tmp_sck = subpanel_gng.collection[sck_idx].name - # if "_Value" in tmp_sck: - # tmp_sck=tmp_sck.replace("_Value", "") - - # print(tmp_sck) - # tmp_values = [] - - # for idx in range(tot_frame_no): - # bpy.app.handlers.frame_change_pre[0]("dummy") - # bpy.data.scenes["Scene"].frame_current = idx - # bpy.ops.node.randomise_all_geometry_sockets("INVOKE_DEFAULT") # issue - # w/ this being called so often - might need moved to diff for loop? - # tmp_values.append( - # bpy.data.node_groups[tmp_GNG] - # .nodes[tmp_sck] - # .outputs[0] - # .default_value - # ) - - # print(tmp_values) - # all_geom_dict[tmp_GNG] = tmp_sck - # GNG_sck_values_str = tmp_GNG + tmp_sck - # GNG_sck_values_str = 'Values ' + GNG_sck_values_str - # print(GNG_sck_values_str) - # all_geom_dict[GNG_sck_values_str] = tmp_values - - -print(all_geom_dict) - -### MATERIALS -bpy.data.scenes["Scene"].frame_current = 0 -all_mat_dict = {} -cs = bpy.context.scene -for mat_idx in range(len(cs.socket_props_per_material.collection)): - # get this subpanel's GNG - subpanel_material = cs.socket_props_per_material.collection[mat_idx] - tmp_mat = subpanel_material.name - print(tmp_mat) - - list_input_nodes = get_material_nodes_to_randomise_indep( - subpanel_material.name - ) - - list_nodes2rand_in_groups = get_material_nodes_to_randomise_group( - subpanel_material.name - ) - - list_input_nodes_all = get_material_nodes_to_randomise_all( - subpanel_material.name - ) - - print("list_input_nodes ====== ", list_input_nodes) - print("list nodes2rand in groups ===== ", list_nodes2rand_in_groups) - print("list_input_nodes_all ===== ", list_input_nodes_all) - - list_input_nodes_sorted = sorted( - list_input_nodes_all, key=lambda x: x.name - ) - for i_n, nd in enumerate(list_input_nodes_sorted): - # add sockets for this node in the subseq rows - for sckt in nd.outputs: - print(nd.name) - print( - getattr( - sckt, - "default_value", - ) - ) - - test_attr = getattr( - sckt, - "default_value", - ) - print(str(test_attr)) - - if "NodeSocketColor" not in str(test_attr): - print("NODESOCKETCOLOR", str(test_attr)) - tmp_values = [] - for idx in range(tot_frame_no): - bpy.app.handlers.frame_change_pre[0]("dummy") - bpy.data.scenes["Scene"].frame_current = idx - bpy.ops.node.randomise_all_material_sockets( - "INVOKE_DEFAULT" - ) # issue - # w/ this being called so often - - # might need moved to diff for loop? - tmp_values.append( - getattr( - sckt, - "default_value", - ) - ) - - print(tmp_values) - tmp_sck = nd.name - all_mat_dict[tmp_mat] = tmp_sck - MAT_sck_values_str = tmp_mat + tmp_sck - MAT_sck_values_str = "Values " + MAT_sck_values_str - print(MAT_sck_values_str) - all_mat_dict[MAT_sck_values_str] = tmp_values - -# print(all_mat_dict) - -data = { - "location_str": loc_value_str, - "loc_x": x_pos_vals, - "loc_y": y_pos_vals, - "loc_z": z_pos_vals, - "rotation_str": value_str, - "rot_x": x_rot_vals, - "rot_y": y_rot_vals, - "rot_z": z_rot_vals, - "geometry": all_geom_dict, - "materials": all_mat_dict, -} -# print(data) -path_to_file = pathlib.Path.home() / "tmp" / "transform_geom_mat_test.json" -print(path_to_file) - -with open(path_to_file, "w") as out_file_obj: - # convert the dictionary into text - text = json.dumps(data, indent=4) - # write the text into the file - out_file_obj.write(text) - - -# path_to_file = pathlib.Path.home() / "tmp" / "input_parameters.json" -#### TODO check file exists -# with open(path_to_file, "r") as in_file_obj: -# text = in_file_obj.read() -# # convert the text into a dictionary -# data = json.loads(text) - - -#### SUBPANEL materials - - -# ##### Function called by main code -# def get_material_nodes_to_randomise_indep( -# material_str: str = "Material", -# node2randomise_prefix: str = "random", -# ): -# """Get list of *independent* input nodes to -# randomise for a given material. - -# Input nodes are defined as nodes with no input sockets. -# The input nodes to randomise are identified because their -# name is prefixed with node2randomise_prefix (case insensitive). - -# Both independent nodes, and nodes inside a group are searched. -# The 'artificial' nodes that show up inside a node group, usually named -# 'Group input' or 'Group output' are excluded from the search. - -# Parameters -# ---------- -# material_str : str, optional -# name of the material, by default "Material" -# node2randomise_prefix : str, optional -# prefix that identifies the nodes to randomise, by default 'random' - -# Returns -# ------- -# list -# list of all *group* input nodes to randomise for this material -# """ - -# # list of nodes for current material -# # not belonging to a group -# list_material_nodes_indep = [] -# for nd in bpy.data.materials[material_str].node_tree.nodes: -# if nd.type != "GROUP": -# list_material_nodes_indep.append(nd) - -# # find input nodes that startwith random -# list_input_nodes = get_nodes_to_randomise_from_list( -# list_material_nodes_indep -# ) - -# return list_input_nodes - - -# ### Called by get_material_nodes_to_randomise_indep -# def get_nodes_to_randomise_from_list( -# list_candidate_nodes: list, -# node2randomise_prefix: str = "random", -# ): -# """Get list of nodes to randomise from list. - -# Input nodes are defined as nodes with no input sockets. -# The nodes to randomise are input nodes whose name starts -# with 'node2randomise_prefix' (case insensitive). - -# The 'artificial' nodes that show up inside a node group, usually named -# 'Group input' or 'Group output' are excluded from the search. - -# Parameters -# ---------- -# list_candidate_nodes : list -# list of the candidate nodes to randomise -# node2randomise_prefix : str, optional -# prefix that identifies the nodes to randomise, by default 'random' - -# Returns -# ------- -# list -# list of the input nodes to randomise -# """ - -# # ensure list_candidate_nodes is unique -# list_candidate_nodes = list(set(list_candidate_nodes)) - -# # find input nodes that start with the random keyword -# # excluding 'Group' artificial nodes -# list_input_nodes = [ -# nd -# for nd in list_candidate_nodes -# if len(nd.inputs) == 0 -# and nd.name.lower().startswith(node2randomise_prefix.lower()) -# and nd.type -# not in [ -# "GROUP_INPUT", -# "GROUP_OUTPUT", -# ] -# ] - -# return list_input_nodes - - -#### MAIN CODE TO REPLICATE FROM ui.py Get material nodes -# (in our case want to automate node socket, no need to draw socket) -# Get list of input nodes to randomise -# for this subpanel's material -cs = bpy.context.scene -subpanel_material = cs.socket_props_per_material.collection[0] -list_input_nodes = get_material_nodes_to_randomise_indep( - subpanel_material.name -) -# list_input_nodes = nodes2rand.get_material_nodes_to_randomise_indep( -# subpanel_material.name -# ) - - -def draw_sockets_list( - cs, - list_input_nodes, -): - # Define UI fields for every socket property - # NOTE: if I don't sort the input nodes, everytime one of the nodes is - # selected in the graph it moves to the bottom of the panel. - list_input_nodes_sorted = sorted(list_input_nodes, key=lambda x: x.name) - for i_n, nd in enumerate(list_input_nodes_sorted): - # add sockets for this node in the subseq rows - for sckt in nd.outputs: - print( - getattr( - sckt, - "default_value", - ) - ) - - -# col1.label(text=sckt.name) - -#### MAIN CODE TO REPLICATE ui.py GEOM -cs = bpy.context.scene - -# get this subpanel's GNG -subpanel_gng = cs.socket_props_per_gng.collection[0] # self.subpanel_gng_idx - -# get (updated) collection of socket props for this GNG -sockets_props_collection = cs.socket_props_per_gng.collection[ - subpanel_gng.name -].collection - -# Get list of input nodes to randomise for this subpanel's GNG -list_parent_nodes_str = [ - sckt.name.split("_")[0] for sckt in sockets_props_collection -] - -list_input_nodes = [ - bpy.data.node_groups[subpanel_gng.name].nodes[nd_str] - for nd_str in list_parent_nodes_str -] - -# Draw sockets to randomise per input node, including their -# current value and min/max boundaries -draw_sockets_list( - cs, - list_input_nodes, -) - - -##### NEW CODE refactoring code from above -print("NEW CODE material starts here") -cs = bpy.context.scene -subpanel_material = cs.socket_props_per_material.collection[0] -list_input_nodes = get_material_nodes_to_randomise_indep( - subpanel_material.name -) - -list_input_nodes_sorted = sorted(list_input_nodes, key=lambda x: x.name) -for i_n, nd in enumerate(list_input_nodes_sorted): - # add sockets for this node in the subseq rows - for sckt in nd.outputs: - print( - getattr( - sckt, - "default_value", - ) - ) - -print(len(list_input_nodes_sorted)) - -print("NEW CODE geometry starts here") -cs = bpy.context.scene -# get this subpanel's GNG -subpanel_gng = cs.socket_props_per_gng.collection[0] # self.subpanel_gng_idx -# get (updated) collection of socket props for this GNG -sockets_props_collection = cs.socket_props_per_gng.collection[ - subpanel_gng.name -].collection -# Get list of input nodes to randomise for this subpanel's GNG -list_parent_nodes_str = [ - sckt.name.split("_")[0] for sckt in sockets_props_collection -] -list_input_nodes = [ - bpy.data.node_groups[subpanel_gng.name].nodes[nd_str] - for nd_str in list_parent_nodes_str -] - -list_input_nodes_sorted = sorted(list_input_nodes, key=lambda x: x.name) -for i_n, nd in enumerate(list_input_nodes_sorted): - # add sockets for this node in the subseq rows - for sckt in nd.outputs: - print( - getattr( - sckt, - "default_value", - ) - ) - -print(len(list_input_nodes_sorted)) - - -# socket current value -# col2.prop( -# sckt, -# "default_value", -# icon_only=True, -# ) -# col2.enabled = False # current value is not editable - - -#### SUBSUBPANEL -# def draw(self, context): -# cs = bpy.context.scene - -# # then force an update in the sockets per material -# # TODO: do I need this? it should update when drawing the subpanel -# if cs.socket_props_per_material.collection[ -# self.subpanel_material_str -# ].update_sockets_collection: -# print("Collection of sockets updated") - -# # get (updated) collection of socket properties -# # for the current material -# sockets_props_collection = cs.socket_props_per_material.collection[ -# self.subpanel_material_str -# ].collection - -## Get list of input nodes to randomise -## for this subpanel's material -## only nodes inside groups! -## keep only nodes inside this group! -# list_input_nodes = [ -# nd -# for nd in self.list_nodes2rand_in_groups -# if nd.id_data.name == self.group_node_name -# ] - -# def draw_sockets_list( -# cs, -# layout, -# list_input_nodes, -# sockets_props_collection, -# ): -# # Define UI fields for every socket property -# # NOTE: if I don't sort the input nodes, everytime one of the nodes is -# # selected in the graph it moves to the bottom of the panel. -# list_input_nodes_sorted = sorted(list_input_nodes, key=lambda x: x.name) -# for i_n, nd in enumerate(list_input_nodes_sorted): -# row = layout.row() - -# # if first node: add labels for -# # name, min, max and randomisation toggle -# if i_n == 0: -# row_split = row.split() -# col1 = row_split.column(align=True) -# col2 = row_split.column(align=True) -# col3 = row_split.column(align=True) -# col4 = row_split.column(align=True) -# col5 = row_split.column(align=True) - -# # input node name -# col1.label(text=nd.name) -# col1.alignment = "CENTER" - -# # min label -# col3.alignment = "CENTER" -# col3.label(text="min") - -# # max label -# col4.alignment = "CENTER" -# col4.label(text="max") - -# # if not first node: add just node name -# else: -# row.separator(factor=1.0) # add empty row before each node -# row = layout.row() - -# row.label(text=nd.name) - -# # add sockets for this node in the subseq rows -# for sckt in nd.outputs: -# # split row in 5 columns -# row = layout.row() -# row_split = row.split() -# col1 = row_split.column(align=True) -# col2 = row_split.column(align=True) -# col3 = row_split.column(align=True) -# col4 = row_split.column(align=True) -# col5 = row_split.column(align=True) - -# # socket name -# col1.alignment = "RIGHT" -# col1.label(text=sckt.name) - -# # socket current value -# col2.prop( -# sckt, -# "default_value", -# icon_only=True, -# ) -# col2.enabled = False # current value is not editable - -# # socket min and max columns -# socket_id = nd.name + "_" + sckt.name -# if (nd.id_data.name in bpy.data.node_groups) and ( -# bpy.data.node_groups[nd.id_data.name].type == "SHADER" -# ): # only for SHADER groups -# socket_id = nd.id_data.name + "_" + socket_id - -# # if socket is a color: format min/max as a color picker -# # and an array (color picker doesn't include alpha value) -# if type(sckt) == bpy.types.NodeSocketColor: -# for m_str, col in zip(["min", "max"], [col3, col4]): -# # color picker -# col.template_color_picker( -# sockets_props_collection[socket_id], -# m_str + "_" + cs.socket_type_to_attr[type(sckt)], -# ) -# # array -# for j, cl in enumerate(["R", "G", "B", "alpha"]): -# col.prop( -# sockets_props_collection[socket_id], -# m_str + "_" + cs.socket_type_to_attr[type(sckt)], -# icon_only=False, -# text=cl, -# index=j, -# ) -# # if socket is Boolean: add non-editable labels -# elif type(sckt) == bpy.types.NodeSocketBool: -# for m_str, col in zip(["min", "max"], [col3, col4]): -# m_val = getattr( -# sockets_props_collection[socket_id], -# m_str + "_" + cs.socket_type_to_attr[type(sckt)], -# ) -# col.label(text=str(list(m_val)[0])) - -# # if socket is not color type: format as a regular property -# else: -# for m_str, col in zip(["min", "max"], [col3, col4]): -# col.prop( -# sockets_props_collection[socket_id], -# m_str + "_" + cs.socket_type_to_attr[type(sckt)], -# icon_only=True, -# ) - -# # randomisation toggle -# col5.prop( -# sockets_props_collection[socket_id], -# "bool_randomise", -# icon_only=True, -# ) diff --git a/randomiser/save_parameter_outputs_test_in_Blender.py b/randomiser/save_parameter_outputs_test_in_Blender.py deleted file mode 100644 index b44e819..0000000 --- a/randomiser/save_parameter_outputs_test_in_Blender.py +++ /dev/null @@ -1,67 +0,0 @@ -from random import seed - -import bpy - -if bpy.data.scenes["Scene"].seed_properties.seed_toggle: # = True - seed(bpy.data.scenes["Scene"].seed_properties.seed) - -bpy.data.scenes["Scene"].frame_current = 0 -tot_frame_no = bpy.context.scene.rand_all_properties.tot_frame_no -x_pos_vals = [] -y_pos_vals = [] -z_pos_vals = [] - - -x_rot_vals = [] -y_rot_vals = [] -z_rot_vals = [] - -if bpy.context.scene.randomise_camera_props.bool_delta: - loc_value_str = "delta_location" - value_str = "delta_rotation_euler" -else: - loc_value_str = "location" - value_str = "rotation_euler" - - -for idx in range(tot_frame_no): - bpy.app.handlers.frame_change_pre[0]("dummy") - bpy.data.scenes["Scene"].frame_current = idx - - x_pos_vals.append(getattr(bpy.context.scene.camera, loc_value_str)[0]) - y_pos_vals.append(getattr(bpy.context.scene.camera, loc_value_str)[1]) - z_pos_vals.append(getattr(bpy.context.scene.camera, loc_value_str)[2]) - - x_rot_vals.append(getattr(bpy.context.scene.camera, value_str)[0]) - y_rot_vals.append(getattr(bpy.context.scene.camera, value_str)[1]) - z_rot_vals.append(getattr(bpy.context.scene.camera, value_str)[2]) - -data = { - "location_str": loc_value_str, - "loc_x": x_pos_vals, - "loc_y": y_pos_vals, - "loc_z": z_pos_vals, - "rotation_str": value_str, - "rot_x": x_rot_vals, - "rot_y": y_rot_vals, - "rot_z": z_rot_vals, -} -print(data) -# path_to_file = pathlib.Path.home() / "tmp" / "transform_test.json" -# print(path_to_file) - -# with open(path_to_file, "w") as out_file_obj: -# # convert the dictionary into text -# text = json.dumps(data, indent=4) -# # write the text into the file -# out_file_obj.write(text) - - -# path_to_file = pathlib.Path.home() / "tmp" / "input_parameters.json" -#### TODO check file exists -# with open(path_to_file, "r") as in_file_obj: -# text = in_file_obj.read() -# # convert the text into a dictionary -# data = json.loads(text) - -## first_run = data["transform_x"] diff --git a/randomiser/test_setattr.py b/randomiser/test_setattr.py deleted file mode 100644 index c6624c2..0000000 --- a/randomiser/test_setattr.py +++ /dev/null @@ -1,46 +0,0 @@ -import bpy - -bpy.data.scenes["Scene"].socket_props_per_UD.collection[0].min_int_1d[0] -selfie = bpy.data.scenes["Scene"].socket_props_per_UD.collection[0] -min_blend = selfie.min_int_1d[0] - -m_str = "int_1d" -print("min_blend value", min_blend) -print("min_blend type ==== ", type(min_blend)) - -getattr_val = getattr(selfie, "min_" + m_str) -print(getattr_val) -print("gettr_val type ==== ", type(getattr_val)) -print(getattr_val[0]) -print("getattr_val[0] type ===== ", type(getattr_val[0])) - - -# try: -# setattr( -# selfie, -# "min_" + m_str, -# min_blend, -# ) -# print("min_blend WORKING!!!!!!! type = ", type(min_blend)) -# except: -# print("min_blend NOT WORKING--- type = ", type(min_blend)) -# try: -# setattr( -# selfie, -# "min_" + m_str, -# getattr_val, -# ) -# print("getattr_val WORKING!!!!!!!!!!!! type = ", type(getattr_val)) -# except: -# print("getattr_val NOT WORKING--- type = ", type(getattr_val)) - - -# try: -# setattr( -# selfie, -# "min_" + m_str, -# getattr_val[0], -# ) -# print("getattr_val[0] WORKING!!!!!!! type = ", type(getattr_val[0])) -# except: -# print("getattr_val[0] NOT WORKING--- type = ", type(getattr_val[0])) From e4a7ed1b4551a7c097f650cb1703c3a0219a14af Mon Sep 17 00:00:00 2001 From: ruaridhg Date: Wed, 18 Oct 2023 15:23:44 +0100 Subject: [PATCH 58/58] working define_prop brought into branch --- randomiser/define_prop/operators.py | 72 ++++------- randomiser/define_prop/properties.py | 1 - .../property_classes/collection_UD_props.py | 71 +++++------ .../collection_UD_socket_properties.py | 88 ++++++-------- randomiser/define_prop/ui.py | 115 ++++++------------ 5 files changed, 132 insertions(+), 215 deletions(-) diff --git a/randomiser/define_prop/operators.py b/randomiser/define_prop/operators.py index d01cbb7..894325d 100644 --- a/randomiser/define_prop/operators.py +++ b/randomiser/define_prop/operators.py @@ -3,6 +3,7 @@ import bpy import numpy as np +from bpy.app.handlers import persistent from mathutils import Euler, Vector from .ui import attr_get_type, get_attr_only_str, get_obj_str @@ -11,7 +12,6 @@ def attr_set_val(obj, path, min_val, max_val, UD_type): if "." in path: # gives us: ('modifiers["Subsurf"]', 'levels') - # len_path = len(full_str.rsplit(".", config.MAX_NUMBER_OF_SUBPANELS)) path_prop, path_attr = path.rsplit(".", 1) # same as: prop = obj.modifiers["Subsurf"] @@ -73,26 +73,6 @@ def invoke(self, context, event): except IndexError: pass else: - # if self.action == "DOWN" and idx < len(scn.custom) - 1: - # scn.custom[idx + 1].name - # scn.custom.move(idx, idx + 1) - # scn.custom += 1 - # info = 'Item "%s" moved to position %d' % ( - # item.name, - # scn.custom + 1, - # ) - # self.report({"INFO"}, info) - - # elif self.action == "UP" and idx >= 1: - # scn.custom[idx - 1].name - # scn.custom.move(idx, idx - 1) - # scn.custom_index -= 1 - # info = 'Item "%s" moved to position %d' % ( - # item.name, - # scn.custom_index + 1, - # ) - # self.report({"INFO"}, info) - if self.action == "REMOVE": info = 'Item "%s" removed from list' % (scn.custom[idx].name) scn.custom_index -= 1 @@ -101,7 +81,7 @@ def invoke(self, context, event): if self.action == "ADD": item = scn.custom.add() - item.name = "bpy.context.scene.camera.ranch" + item.name = "bpy.objects.data['Cube'].location" item.id = len(scn.custom) scn.custom_index = len(scn.custom) - 1 info = '"%s" added to list' % (item.name) @@ -163,13 +143,12 @@ def execute(self, context): # -------------------------------------------- -# Operator Randomise selected sockets -# across all Geometry node groups +# Operator Randomise selected +# User Defined Properties # -------------------------------------------- -##### REFACTOR - remove to replace with randomise all? class RandomiseAllUDProps(bpy.types.Operator): - """Randomise the selected output sockets - across all geometry node groups + """Randomise the selected + User Defined Properties Parameters ---------- @@ -183,7 +162,7 @@ class RandomiseAllUDProps(bpy.types.Operator): """ # metadata - bl_idname = "opr.randomise_all_ud_sockets" # this is appended to bpy.ops. + bl_idname = "node.randomise_all_ud_sockets" # this is appended to bpy.ops. bl_label = "Randomise selected sockets" bl_options = {"REGISTER", "UNDO"} @@ -191,7 +170,7 @@ class RandomiseAllUDProps(bpy.types.Operator): def poll(cls, context): """Determine whether the operator can be executed. - The operator can only run if there are geometry node groups + The operator can only run if there are user defined properties in the collection. If it can't be executed, the button will appear as disabled. @@ -204,7 +183,7 @@ def poll(cls, context): Returns ------- boolean - number of geometry node groups in the collection + number of user defined properties in the collection """ return len(context.scene.socket_props_per_UD.collection) > 0 @@ -214,10 +193,8 @@ def invoke(self, context, event): The invoke() function runs before executing the operator. Here, we - - add the list of input nodes and collection of socket properties to - the operator (self), and - - unselect the randomisation toggle of the sockets of input nodes if - they are not linked to any other node + - add the list of user defined properties and collection of + user defined properties with associated info to the operator (self) Parameters ---------- @@ -231,7 +208,7 @@ def invoke(self, context, event): _type_ _description_ """ - # add list of GNGs to operator self + # add list of UD props to operator self # NOTE: this list should have been updated already, # when drawing the panel @@ -239,7 +216,7 @@ def invoke(self, context, event): self.list_subpanel_UD_props_names = [ UD.name for UD in cs.socket_props_per_UD.collection ] - # for every GNG: save sockets to randomise + # for every UD prop: save name of UD prop self.sockets_to_randomise_per_UD = {} self.sockets_to_randomise_per_UD = [] for UD_str in self.list_subpanel_UD_props_names: @@ -252,7 +229,7 @@ def invoke(self, context, event): def execute(self, context): """Execute the randomiser operator - Randomise the selected output sockets between + Randomise the selected UD props between their min and max values. Parameters @@ -311,7 +288,7 @@ def execute(self, context): bpy.context.scene, attribute_only_str )[0] - # get min value for this socket + # get min value for this UD prop min_val = np.array( getattr( sockets_props_collection, @@ -319,7 +296,7 @@ def execute(self, context): ) ) - # get max value for this socket + # get max value for this UD prop max_val = np.array( getattr( sockets_props_collection, @@ -358,6 +335,15 @@ def execute(self, context): return {"FINISHED"} +# NOTE: without the persistent decorator, +# the function is removed from the handlers' list +# after it is first executed +@persistent +def randomise_UD_props_per_frame(dummy): + bpy.ops.node.randomise_all_ud_sockets("INVOKE_DEFAULT") + return + + # --------------------- # Classes to register # --------------------- @@ -376,9 +362,7 @@ def register(): for cls in list_classes_to_register: bpy.utils.register_class(cls) - # bpy.app.handlers.frame_change_pre.append( - # randomise_geometry_nodes_per_frame - # ) + bpy.app.handlers.frame_change_pre.append(randomise_UD_props_per_frame) print("UD operators registered") @@ -387,8 +371,6 @@ def unregister(): for cls in list_classes_to_register: bpy.utils.unregister_class(cls) - # bpy.app.handlers.frame_change_pre.remove( - # randomise_geometry_nodes_per_frame - # ) + bpy.app.handlers.frame_change_pre.remove(randomise_UD_props_per_frame) print("UD operators unregistered") diff --git a/randomiser/define_prop/properties.py b/randomiser/define_prop/properties.py index 5f16f39..0fc0091 100644 --- a/randomiser/define_prop/properties.py +++ b/randomiser/define_prop/properties.py @@ -30,7 +30,6 @@ def register(): collection_UD_props.register() # link global Python variables to bpy.context.scene - # if I use setattr: attribute must exist first right? for attr_ky, attr_val in dict_context_scene_attr.items(): setattr(bpy.types.Scene, attr_ky, attr_val) diff --git a/randomiser/define_prop/property_classes/collection_UD_props.py b/randomiser/define_prop/property_classes/collection_UD_props.py index 4f5c39b..f93f316 100644 --- a/randomiser/define_prop/property_classes/collection_UD_props.py +++ b/randomiser/define_prop/property_classes/collection_UD_props.py @@ -5,25 +5,25 @@ # --------------------------------------------------- -# Collection of Geometry Node groups (GNGs) +# Collection of UD props # --------------------------------------------------- def compute_UD_props_sets(self): - """Compute the relevant sets of geometry node groups (GNGs) and + """Compute the relevant sets of UD props and add them to self. These sets include: - - the set of GNGs already in the collection - - the set of GNGs in the Blender scene / data structure - - the set of GNGs that are only in one of the two previous sets + - the set of UD props already in the collection + - the set of UD props in the Blender scene / data structure + - the set of UD props that are only in one of the two previous sets """ - # set of GNGs already in collection + # set of UD props already in collection self.set_UD_props_in_collection = set(UD.name for UD in self.collection) - # set of node groups in Blender data structure + # set of UD props in Blender data structure self.set_UD_props_in_data = set(UD.name for UD in self.candidate_UD_props) - # set of node groups in one of the sets only + # set of UD props in one of the sets only self.set_UD_props_in_one_only = ( self.set_UD_props_in_collection.symmetric_difference( self.set_UD_props_in_data @@ -32,14 +32,14 @@ def compute_UD_props_sets(self): def get_update_UD_props_collection(self): - """Getter function for the 'update_gngs_collection' + """Getter function for the 'update_UD_props_collection' attribute. - Checks if the collection of GNGs needs + Checks if the collection of UD props needs to be updated, and updates it if required. The collection will need to be updated if there - are GNGs that have been added/deleted from the scene. + are UD props that have been added/deleted from the scene. Returns ------- @@ -47,10 +47,10 @@ def get_update_UD_props_collection(self): returns True if the collection is updated, otherwise it returns False """ - # compute relevant GNG sets and add them to self + # compute relevant UD props sets and add them to self compute_UD_props_sets(self) - # if there are node groups that exist only in the Blender + # if there are UD props that exist only in the Blender # data structure, or only in the collection: edit the collection if self.set_UD_props_in_one_only: set_update_UD_props_collection(self, True) @@ -60,7 +60,7 @@ def get_update_UD_props_collection(self): def set_update_UD_props_collection(self, value): - """Setter function for the 'update_gngs_collection' + """Setter function for the 'update_UD_props_collection' attribute Parameters @@ -69,7 +69,6 @@ def set_update_UD_props_collection(self, value): _description_ """ - ##### ISSUE WITH DUPLICATION????? # if update value is True if value: # if the update fn is triggered directly and not via @@ -77,7 +76,7 @@ def set_update_UD_props_collection(self, value): if not hasattr(self, "set_UD_props_in_one_only"): compute_UD_props_sets(self) - # for all node groups that are in one set only + # for all UD props that are in one set only for UD_name in self.set_UD_props_in_one_only: # if only in collection: remove it from the collection @@ -89,24 +88,19 @@ def set_update_UD_props_collection(self, value): UD = self.collection.add() UD.name = UD_name - # TODO: do we need to sort collection of node groups? - # (otherwise their order is not guaranteed, this is relevant for - # indexing node groups via subpanel indices) - # it is not clear how to sort collection of properties... - # https://blender.stackexchange.com/questions/157562/sorting-collections-alphabetically-in-the-outliner - class ColUDParentProps(bpy.types.PropertyGroup): - """Collection of Geometry Node Groups + """Collection of UD props This class has two attributes and one property - - collection (attribute): holds the collection of GNGs - - update_gngs_collection (attribute): helper attribute to force updates on - the collection of GNGs - - candidate_gngs (property): returns the updated list of geometry node - groups defined in the scene + - collection (attribute): holds the collection of UD props + - update_UD_props_collection (attribute): helper attribute + to force updates on + the collection of UD props + - candidate_UD_props (property): returns the updated list of UD props + defined in the scene - This data will be made availabe via bpy.context.scene.socket_props_per_gng + This data will be made availabe via bpy.context.scene.socket_props_per_UD Parameters ---------- @@ -119,29 +113,21 @@ class ColUDParentProps(bpy.types.PropertyGroup): _description_ """ - # # collection of [collections of socket properties] (one per node group) - # collection: bpy.props.CollectionProperty( # type: ignore - # type=ColUDSocketProperties # elements in the collection - # ) - + # # collection of user defined properties collection: bpy.props.CollectionProperty( # type: ignore type=SocketProperties ) - # autopopulate collection of geometry node groups + # autopopulate collection of user defined properties update_UD_props_collection: bpy.props.BoolProperty( # type: ignore default=False, get=get_update_UD_props_collection, set=set_update_UD_props_collection, ) - ##### CHECK IF VALUE IS PROPERTY HERE FIRST - # - make sure working for correct string first - - # candidate geometry node groups + # candidate UD props @property def candidate_UD_props(self): # getter method - """Return list of geometry node groups - with nodes that start with the random keyword inside them + """Return list of UD props from UIlist Returns ------- @@ -150,7 +136,7 @@ def candidate_UD_props(self): # getter method """ # get_attr_only_strbpy.context.scene.custom - # self is the collection of node groups + # self is the collection of UD props list_UD_props = [] objects_in_scene = [] @@ -200,7 +186,6 @@ def register(): bpy.utils.register_class(ColUDParentProps) # make the property available via bpy.context.scene... - # (i.e., bpy.context.scene.socket_props_per_gng) bpy.types.Scene.socket_props_per_UD = bpy.props.PointerProperty( type=ColUDParentProps ) diff --git a/randomiser/define_prop/property_classes/collection_UD_socket_properties.py b/randomiser/define_prop/property_classes/collection_UD_socket_properties.py index 9e662b3..56f05cb 100644 --- a/randomiser/define_prop/property_classes/collection_UD_socket_properties.py +++ b/randomiser/define_prop/property_classes/collection_UD_socket_properties.py @@ -9,32 +9,28 @@ # ---------------------------------------------------------------- -##### REFACTOR def compute_UD_sockets_sets(self): - """Compute the relevant sets of sockets for this specific - user defined property, and add them to self. + """Compute the relevant sets of UD props and add them to self. These sets include: - - the set of sockets already in this GNG's collection - - the set of sockets present in the Blender graph (for this GNG) - - the set of sockets that are only in one of the two previous sets + - the set of UD props already in the UD props collection + - the set of UD props present in the candidate sockets + - the set of UD props that are only in one of the two previous sets """ - # set of sockets in collection for this GNG + # set of UD props in collection for this GNG self.set_sckt_names_in_collection_of_props = set( sck_p.name for sck_p in self.collection ) - # pdb.set_trace() - #####REFACTOR TO WORK WITH UI LIST/REMOVE - # since don't need graphs for custom props? - # set of sockets in graph for this GNG + + # should be renamed candidate UD props list_sckt_names_in_graph = [ "UD_" + sck.name for sck in self.candidate_sockets ] self.set_sckt_names_in_graph = set(list_sckt_names_in_graph) - # set of sockets that are just in one of the two groups + # set of UD props that are just in one of the two groups self.set_of_sckt_names_in_one_only = ( self.set_sckt_names_in_collection_of_props.symmetric_difference( self.set_sckt_names_in_graph @@ -44,23 +40,23 @@ def compute_UD_sockets_sets(self): def get_update_collection(self): """Getter function for the update_sockets_collection attribute - of the collection of socket properties class (ColSocketProperties) + of the collection of UD properties class (SocketProperties) It will run when the property value is 'get' and - it will update the collection of socket properties if required + it will update the collection of UD properties if required Returns ------- boolean - returns True if the collection of socket properties is updated, + returns True if the collection of UD properties is updated, otherwise it returns False """ - # compute the different sets of sockets and add them to self + # compute the different sets of UD props and add them to self compute_UD_sockets_sets(self) # if there is a difference between - # sets of sockets in graph and in the collection: - # edit the set of sockets in the collection + # sets of UD props in graph and in the collection: + # edit the set of UD props in the collection if self.set_of_sckt_names_in_one_only: set_update_collection(self, True) return True @@ -71,63 +67,58 @@ def get_update_collection(self): def set_update_collection(self, value): """ Setter function for the update_sockets_collection attribute - of the collection of socket properties class (ColSocketProperties) + of the collection of UD properties class (SocketProperties) It will run when the property value is 'set'. - It will update the collection of socket properties as follows: - - For the set of sockets that exist only in either + It will update the collection of UD properties as follows: + - For the set of UD props that exist only in either the collection or the graph: - - if the socket exists only in the collection: remove from + - if the UD prop exists only in the collection: remove from collection - - if the socket exists only in the node graph: add to collection + - if the UD propexists only in the node graph: add to collection with initial values - For the rest of sockets: leave untouched Parameters ---------- value : boolean - if True, the collection of socket properties is + if True, the collection of UDproperties is overwritten to consider the latest data """ - #####REFACTOR TO WORK WITH UI LIST/REMOVE - # since don't need graphs for custom props? if value: # if the update function is triggered directly and not via # the getter function: compute the sets here if not hasattr(self, "set_of_sckt_names_in_one_only"): compute_UD_sockets_sets(self) - # update the sockets that are only in either + # update the UD props that are only in either # the collection set or the graph for sckt_name in self.set_of_sckt_names_in_one_only: - # if the socket exists only in the collection: remove from + # if the UD prop exists only in the collection: remove from # collection if sckt_name in self.set_sckt_names_in_collection_of_props: self.collection.remove(self.collection.find(sckt_name)) - # if the socket exists only in the node graph: add to collection + # if the UD prop exists only in the node graph: add to collection # with initial values if sckt_name in self.set_sckt_names_in_graph: sckt_prop = self.collection.add() sckt_prop.name = sckt_name sckt_prop.bool_randomise = True - # TODO: review - is this code block too hacky? # --------------------------------------------- - # get socket object for this socket name - # NOTE: my definition of socket name - # (node.name + _ + socket.name) + # get UD prop object for this UD prop name for s in self.candidate_sockets: - # build socket id from scratch + # build UD prop id from scratch socket_id = "UD_" + s.name if socket_id == sckt_name: sckt = s break - # for this socket type, get the name of the attribute + # for this UD prop type, get the name of the attribute # holding the min/max properties socket_attrib_str = bpy.context.scene.socket_type_to_attr[ type(sckt) @@ -135,14 +126,13 @@ def set_update_collection(self, value): # extract last number between '_' and 'd/D' in the # attribute name, to determine the shape of the array - # TODO: there is probably a nicer way to do this... n_dim = int( re.findall(r"_(\d+)(?:d|D)", socket_attrib_str)[-1] ) # --------------------------- # get dictionary with initial min/max values - # for this socket type + # for this UD props type ini_min_max_values = ( bpy.context.scene.socket_type_to_ini_min_max[type(sckt)] ) @@ -167,7 +157,7 @@ def constrain_min_closure(m_str): Parameters ---------- m_str : str - string specifying the socket attribute (e.g., float_1d) + string specifying the UD propattribute (e.g., float_1d) Returns ------- @@ -187,7 +177,7 @@ def constrain_min(self, context, m_str): context : _type_ _description_ m_str : str - string specifying the socket attribute (e.g., float_1d) + string specifying the UD propattribute (e.g., float_1d) """ # self is a 'SocketProperties' object min_array = np.array(getattr(self, "min_" + m_str)) @@ -210,7 +200,7 @@ def constrain_max_closure(m_str): Parameters ---------- m_str : str - string specifying the socket attribute (e.g., float_1d) + string specifying the UD propattribute (e.g., float_1d) Returns ------- @@ -230,7 +220,7 @@ def constrain_max(self, context, m_str): context : _type_ _description_ m_str : str - string specifying the socket attribute (e.g., float_1d) + string specifying the UD propattribute (e.g., float_1d) """ # self is a 'SocketProperties' object min_array = np.array(getattr(self, "min_" + m_str)) @@ -252,7 +242,7 @@ def constrain_min_closure_int(m_str): Parameters ---------- m_str : str - string specifying the socket attribute (e.g., int_1d) + string specifying the UD propattribute (e.g., int_1d) Returns ------- @@ -272,7 +262,7 @@ def constrain_min(self, context, m_str): context : _type_ _description_ m_str : str - string specifying the socket attribute (e.g., int_1d) + string specifying the UD propattribute (e.g., int_1d) """ # self is a 'SocketProperties' object min_array = np.array(getattr(self, "min_" + m_str)) @@ -300,7 +290,7 @@ def constrain_max_closure_int(m_str): Parameters ---------- m_str : str - string specifying the socket attribute (e.g., float_1d) + string specifying the UD propattribute (e.g., float_1d) Returns ------- @@ -320,7 +310,7 @@ def constrain_max(self, context, m_str): context : _type_ _description_ m_str : str - string specifying the socket attribute (e.g., float_1d) + string specifying the UD propattribute (e.g., float_1d) """ # self is a 'SocketProperties' object min_array = np.array(getattr(self, "min_" + m_str)) @@ -344,7 +334,7 @@ def constrain_rgba_closure(m_str): Parameters ---------- m_str : str - string specifying the socket attribute (e.g., float_1d) + string specifying the UD propattribute (e.g., float_1d) Returns ------- @@ -363,7 +353,7 @@ def constrain_rgba(self, context, min_or_max_full_str): context : _type_ _description_ m_str : str - string specifying the socket attribute (e.g., float_1d) + string specifying the UD propattribute (e.g., float_1d) """ min_or_max_array = np.array(getattr(self, min_or_max_full_str)) if any(min_or_max_array > 1.0) or any(min_or_max_array < 0.0): @@ -384,12 +374,12 @@ class SocketProperties(bpy.types.PropertyGroup): """ Class holding the set of properties for a socket, namely: - - socket name, + - UD propname, - min/max values, and - boolean for randomisation Because I think it is not possible to define attributes dynamically, - for now we define an attribute for each possible socket type + for now we define an attribute for each possible UD proptype in the input nodes. These are all FloatVectors of different sizes. The size is specified in the attribute's name: - min/max_float_1d diff --git a/randomiser/define_prop/ui.py b/randomiser/define_prop/ui.py index caae1c1..37adaae 100644 --- a/randomiser/define_prop/ui.py +++ b/randomiser/define_prop/ui.py @@ -29,7 +29,7 @@ def invoke(self, context, event): # --------------------------------------------------- -# Common layout for list of sockets to randomise +# Common layout for list of UD props to randomise # ---------------------------------------------------- def draw_sockets_list_UD( cs, @@ -39,9 +39,7 @@ def draw_sockets_list_UD( attribute_only_str, full_str, ): - # Define UI fields for every socket property - # NOTE: if I don't sort the input nodes, everytime one of the nodes is - # selected in the graph it moves to the bottom of the panel. + # Define UI fields for every user defined property list_UD_props_sorted = list_UD_props row = layout.row() @@ -77,36 +75,35 @@ def draw_sockets_list_UD( col1.alignment = "RIGHT" col1.label(text="value") # text=sckt.name) - # if socket is a color: format min/max as a color picker + # if UD prop is a color: format min/max as a color picker # and an array (color picker doesn't include alpha value) - sckt = list_UD_props_sorted # UD.name + sckt = list_UD_props_sorted if type(sckt) == bpy.types.NodeSocketColor: for m_str, col in zip(["min", "max"], [col3, col4]): # color picker col.template_color_picker( - sockets_props_collection, # [socket_id], + sockets_props_collection, m_str + "_" + cs.socket_type_to_attr[type(sckt)], ) # array for j, cl in enumerate(["R", "G", "B", "alpha"]): col.prop( - sockets_props_collection, # [socket_id], + sockets_props_collection, m_str + "_" + cs.socket_type_to_attr[type(sckt)], icon_only=False, text=cl, index=j, ) - # if socket is Boolean: add non-editable labels + # if UD prop is Boolean: add non-editable labels elif type(sckt) == bpy.types.NodeSocketBool: for m_str, col in zip(["min", "max"], [col3, col4]): m_val = getattr( - sockets_props_collection, # [socket_id], + sockets_props_collection, m_str + "_" + cs.socket_type_to_attr[type(sckt)], ) col.label(text=str(list(m_val)[0])) - # if socket is not color type: format as a regular property - + # if UD prop is not color type: format as a regular property else: objects_in_scene = [] for key in bpy.data.objects: @@ -162,7 +159,6 @@ def get_obj_str(full_str): def attr_get_type(obj, path): if "." in path: # gives us: ('modifiers["Subsurf"]', 'levels') - # len_path = len(full_str.rsplit(".", config.MAX_NUMBER_OF_SUBPANELS)) path_prop, path_attr = path.rsplit(".", 1) # same as: prop = obj.modifiers["Subsurf"] @@ -203,10 +199,8 @@ def get_attr_only_str(full_str): # ---------------------- # Main panel # --------------------- -class MainPanelRandomUD( - TemplatePanel -): # MainPanelRandomGeometryNodes(TemplatePanel): - """Parent panel to the geometry node groups' subpanels +class MainPanelRandomUD(TemplatePanel): + """Parent panel to the user defined properties subpanels Parameters ---------- @@ -220,8 +214,8 @@ class MainPanelRandomUD( _description_ """ - bl_idname = "UD_PROPS_PT_mainpanel" # "NODE_GEOMETRY_PT_mainpanel" - bl_label = "Randomise UD" # "Randomise GEOMETRY" + bl_idname = "UD_PROPS_PT_mainpanel" + bl_label = "Randomise UD" @classmethod def poll(cls, context): @@ -295,9 +289,6 @@ def draw(self, context): column.label( text="Choose property to see available properties to randomise" ) - # column.prop(context.scene.custom_props, "custom_input", text="") - # column.operator("opr.add_custom_prop_to_list", - # text="Add to Custom List") layout = self.layout scn = bpy.context.scene @@ -321,31 +312,20 @@ def draw(self, context): col.operator( "custom.list_action", icon="ZOOM_OUT", text="" ).action = "REMOVE" - # col.separator() - # col.operator( - # "custom.list_action", icon="TRIA_UP", text="" - # ).action = "UP" - # col.operator( - # "custom.list_action", icon="TRIA_DOWN", text="" - # ).action = "DOWN" row = layout.row() col = row.column(align=True) row = col.row(align=True) - row.operator( - "custom.print_items", icon="LINENUMBERS_ON" - ) # LINENUMBERS_OFF, ANIM + row.operator("custom.print_items", icon="LINENUMBERS_ON") row = col.row(align=True) row.operator("custom.clear_list", icon="X") # ------------------------------ -# Subpanel for each node group +# Subpanel for each user defined property # ----------------------------- -class SubPanelRandomUD( - TemplatePanel -): # SubPanelRandomGeometryNodes(TemplatePanel): - """Parent class for the geometry node groups' (GNG) +class SubPanelRandomUD(TemplatePanel): + """Parent class for the user defined properties subpanels Parameters @@ -369,10 +349,12 @@ class SubPanelRandomUD( @classmethod def poll(cls, context): - """Determine whether the GNG subpanel can be displayed. + """Determine whether the UD props subpanel can be displayed. - To display a subpanels, its index must be lower than the - total number of GNGs defined in the scene + To display a subpanel, its index must be lower than the + total number of UD props defined in the scene and + the property must be a valid property i.e. not a dummy + property Parameters ---------- @@ -386,13 +368,11 @@ def poll(cls, context): """ cs = context.scene - ##### CHECK VALID PROPERTY - # force an update on the group nodes collection first + # force an update on the UD props collection first if cs.socket_props_per_UD.update_UD_props_collection: print("Collection of UD props updated") if cls.subpanel_UD_idx < len(cs.socket_props_per_UD.collection): - # pdb.set_trace() sockets_props_collection = cs.socket_props_per_UD.collection[ cls.subpanel_UD_idx ] @@ -446,16 +426,15 @@ def draw_header(self, context): """ cs = context.scene - # get this subpanel's GNG + # get this subpanel's UD prop cs.socket_props_per_UD.collection[self.subpanel_UD_idx] - # add view graph operator to layout layout = self.layout layout.use_property_split = True layout.use_property_decorate = False def draw(self, context): - """Define the content to display in the GNG subpanel + """Define the content to display in the UD prop subpanel Parameters ---------- @@ -464,15 +443,15 @@ def draw(self, context): """ cs = context.scene - # get this subpanel's GNG + # get this subpanel's UD prop cs.socket_props_per_UD.collection[self.subpanel_UD_idx] # get (updated) collection of chosen prop for this subpanel sockets_props_collection = cs.socket_props_per_UD.collection[ self.subpanel_UD_idx - ] # .collection + ] - # Get list of input nodes to randomise for this subpanel's GNG + # Get list of UD props to randomise for this subpanel's UD prop full_str = sockets_props_collection.name attribute_only_str = get_attr_only_str(full_str) @@ -522,8 +501,8 @@ def draw(self, context): bpy.context.scene.custom_index ].name - # Draw sockets to randomise per input node, including their - # current value and min/max boundaries + # Draw UD props to randomise including their + # min/max boundaries draw_sockets_list_UD( cs, self.layout, @@ -537,9 +516,7 @@ def draw(self, context): # ------------------------------------------- # Subpanel for the 'randomise-all' operator # ------------------------------------------- -class SubPanelRandomUDOperator( - TemplatePanel -): # SubPanelRandomGeometryOperator(TemplatePanel): #RANDOMISATION +class SubPanelRandomUDOperator(TemplatePanel): """Panel containing the 'randomise-all' button Parameters @@ -553,10 +530,8 @@ class SubPanelRandomUDOperator( _description_ """ - bl_idname = ( - "UD_PT_subpanel_operator" # "NODE_GEOMETRY_PT_subpanel_operator" - ) - bl_parent_id = "UD_PROPS_PT_mainpanel" # "NODE_GEOMETRY_PT_mainpanel" + bl_idname = "UD_PT_subpanel_operator" + bl_parent_id = "UD_PROPS_PT_mainpanel" bl_label = "" # title of the panel displayed to the user bl_options = {"HIDE_HEADER"} @@ -589,7 +564,7 @@ def draw(self, context): """ column = self.layout.column(align=True) column.operator( - "opr.randomise_all_ud_sockets", + "node.randomise_all_ud_sockets", text="Randomise", ) @@ -604,28 +579,19 @@ def draw(self, context): CUSTOM_UL_items, ] -# Define (dynamically) a subpanel class for each Geometry Node Group (GNGs) +# Define (dynamically) a subpanel class for each UD prop # and add them to the list of classes to register -# NOTE: because we don't know the number of GNGs that will be +# NOTE: because we don't know the number of UD props that will be # defined a priori, we define n=MAX_NUMBER_OF_SUBPANELS classes and # assign an index to each of them. We will only display the subpanels -# whose index is lower than the total number of GNGs defined in the scene. +# whose index is lower than the total number of UD props defined in the scene. for i in range(config.MAX_NUMBER_OF_SUBPANELS): - # define subpanel class for GNG i - # subpanel_class_i = type( - # f"NODE_GEOMETRY_PT_subpanel_{i}", - # (SubPanelRandomGeometryNodes,), - # { - # "bl_idname": f"NODE_GEOMETRY_PT_subpanel_{i}", - # "subpanel_gng_idx": i, - # }, - # ) subpanel_class_i = type( f"UD_PT_subpanel_{i}", (SubPanelRandomUD,), { "bl_idname": f"UD_PT_subpanel_{i}", - "subpanel_UD_idx": i, # IN UI AND OPERATORS + "subpanel_UD_idx": i, }, ) @@ -640,11 +606,6 @@ def draw(self, context): list_classes_to_register.append(SubPanelRandomUDOperator) -# SubPanelRandomUDOperator, -# SubPanelUDUIlist -# ) - - # ----------------------------------------- # Register and unregister functions # ------------------------------------------