OverviewFilesCommitsIssuesDocsReleasesPeers

hardops / operators / nodes / nodify.py

102 lines · python ref:

import bpy
from ... utility import geometry_nodes, ct_modifier, addon

FILTER_PROP = {
    'debug_options',
    'execution_time',
    'is_override_data',
    'persistent_uid',
    'rna_type',
    'type',
    'use_pin_to_last'
}

# map default type to node group wrapper constructor
TYPE_MAP = {'BOOLEAN': geometry_nodes.NodeBoolean.from_object}

class HOPS_OT_Nodyify(bpy.types.Operator):
    bl_idname = 'hops.nodify'
    bl_label = 'Hops Nodify'
    bl_options = {'REGISTER', 'UNDO'}
    bl_description = '''Replace default modifiers with supported node implementations\n
Shift - Replace supported node groups with current version'''

    replace = False
    @classmethod
    def poll(cls, context):
        return addon.preference().property.workflow_modifier == 'NODES'

    def invoke(self, context, event):
        self.replace = event.shift

        return self.execute(context)


    def execute(self, context):
        supported_types = {'MESH'}
        objects = [o for o in context.selected_objects if o.type in supported_types]
        self.notify = lambda val: bpy.ops.hops.display_notification(info=val) if addon.preference().ui.Hops_extra_info else lambda val: None

        counter = 0

        type_func = {'BOOLEAN': geometry_nodes.NodeBoolean.get_or_load_node_group}

        if self.replace:
            type_mods = {'BOOLEAN': []}
            for obj in objects:
                for mod in obj.modifiers:
                    if mod.type != 'NODES': continue
                    if not mod.node_group: continue

                    if geometry_nodes.NodeBoolean.is_asset_node_group(mod.node_group):
                        if not geometry_nodes.NodeBoolean.is_valid_node_group(mod.node_group):
                            type_mods['BOOLEAN'].append(mod)
                            counter += 1

            for key, mods in type_mods.items():
                if not mods: continue

                fn = type_func[key]
                node_group  = fn()
                node_group.is_modifier = False # keep modifier dropdown clean
                node_group.asset_clear() # match ops behavior
                for mod in mods:
                    mod.node_group = node_group


            self.notify(F'Modifiers Reloaded: {counter}')

            return {'FINISHED'}

        for obj in objects:
            mod_types = []
            # BOOLEANS
            mod_types.append(('BOOLEAN', [m for m in obj.modifiers if m.type == 'BOOLEAN']))

            for t, mods in mod_types:
                wrapper_fn = TYPE_MAP[t]

                for mod in mods:
                    nodes = wrapper_fn(obj)

                    for prop in mod.bl_rna.properties:
                        if prop.is_hidden or prop.is_readonly: continue
                        if prop.identifier in FILTER_PROP: continue

                        # only copy matching props
                        if hasattr(nodes, prop.identifier):
                            setattr(nodes, prop.identifier, getattr(mod, prop.identifier))

                    ct_modifier.mod_swap(mod, nodes.modifier)

                    name = str(mod.name)
                    obj.modifiers.remove(mod)
                    nodes.name = name
                    counter += 1

            obj.update_tag()

        self.notify(F'Modifiers Converted: {counter}')

        return {'FINISHED'}