OverviewFilesCommitsIssuesDocsReleasesPeers

hardops / ui / hops_helper / mods_data.py

1969 lines · python ref:
import bpy
from bpy.types import Panel
from ... utility import addon, version
from ... utility.geometry_nodes import compat

mods_dic = {
    'ARMATURE': 'MOD_ARMATURE',
    'ARRAY': 'MOD_ARRAY',
    'BEVEL': 'MOD_BEVEL',
    'BOOLEAN': 'MOD_BOOLEAN',
    'BUILD': 'MOD_BUILD',
    'MESH_CACHE': 'MOD_MESHDEFORM',
    'MESH_SEQUENCE_CACHE': 'MOD_MESHDEFORM',
    'CAST': 'MOD_CAST',
    'CLOTH': 'MOD_CLOTH',
    'COLLISION': 'MOD_PHYSICS',
    'CURVE': 'MOD_CURVE',
    'DECIMATE': 'MOD_DECIM',
    'DISPLACE': 'MOD_DISPLACE',
    'DYNAMIC_PAINT': 'MOD_DYNAMICPAINT',
    'EDGE_SPLIT': 'MOD_EDGESPLIT',
    'EXPLODE': 'MOD_EXPLODE',
    'FLUID_SIMULATION': 'MOD_FLUIDSIM',
    'HOOK': 'HOOK',
    'LAPLACIANDEFORM': 'MOD_MESHDEFORM',
    'LAPLACIANSMOOTH': 'MOD_SMOOTH',
    'LATTICE': 'MOD_LATTICE',
    'MASK': 'MOD_MASK',
    'MESH_DEFORM': 'MOD_MESHDEFORM',
    'MIRROR': 'MOD_MIRROR',
    'MULTIRES': 'MOD_MULTIRES',
    'NODES': 'NODETREE',
    'OCEAN': 'MOD_OCEAN',
    'PARTICLE_INSTANCE': 'MOD_PARTICLE_INSTANCE',
    'PARTICLE_SYSTEM': 'PARTICLE_DATA',
    'SCREW': 'MOD_SCREW',
    'SHRINKWRAP': 'MOD_SHRINKWRAP',
    'SIMPLE_DEFORM': 'MOD_SIMPLEDEFORM',
    'FLUID': 'MOD_FLUID',
    'SMOOTH': 'MOD_SMOOTH',
    'SOFT_BODY': 'MOD_SOFT',
    'SOLIDIFY': 'MOD_SOLIDIFY',
    'SUBSURF': 'MOD_SUBSURF',
    'SURFACE': 'MOD_MESHDEFORM',
    'SURFACE_DEFORM': 'MOD_MESHDEFORM',
    'UV_PROJECT': 'MOD_UVPROJECT',
    'WARP': 'MOD_WARP',
    'WAVE': 'MOD_WAVE',
    'REMESH': 'MOD_REMESH',
    'VERTEX_WEIGHT_EDIT': 'MOD_VERTEX_WEIGHT',
    'VERTEX_WEIGHT_MIX': 'MOD_VERTEX_WEIGHT',
    'VERTEX_WEIGHT_PROXIMITY': 'MOD_VERTEX_WEIGHT',
    'SKIN': 'MOD_SKIN',
    'TRIANGULATE': 'MOD_TRIANGULATE',
    'UV_WARP': 'MOD_UVPROJECT',
    'WIREFRAME': 'MOD_WIREFRAME',
    'WELD': 'AUTOMERGE_OFF',
    'DATA_TRANSFER': 'MOD_DATA_TRANSFER',
    'NORMAL_EDIT': 'MOD_NORMALEDIT',
    'CORRECTIVE_SMOOTH': 'MOD_SMOOTH',
    'WEIGHTED_NORMAL': 'MOD_NORMALEDIT'
}


class ModifierButtonsPanel:
    bl_space_type = 'PROPERTIES'
    bl_region_type = 'WINDOW'
    bl_context = "modifier"
    bl_options = {'HIDE_HEADER'}


class DATA_PT_modifiers(ModifierButtonsPanel, Panel):
    bl_label = "Modifiers"

    @classmethod
    def poll(cls, context):
        ob = context.object
        return ob and ob.type not in {'GPENCIL', 'GREASEPENCIL'}

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

        ob = context.object

        layout.operator_menu_enum("object.modifier_add", "type")

        for md in ob.modifiers:
            box = layout.template_modifiers()
            if box:
                # match enum type to our functions, avoids a lookup table.
                getattr(self, md.type)(box, ob, md)

    # the mt.type enum is (ab)used for a lookup on function names
    # ...to avoid lengthy if statements
    # so each type must have a function here.

    def ARMATURE(self, layout, ob, md):
        layout.use_property_split = True
        col = layout.column()
        col.separator()

        col.prop(md, "object")
        row = col.row()
        row.prop_search(md, "vertex_group", ob, "vertex_groups", text="Vertex Group")
        sub = row.row(align=True)
        sub.active = bool(md.vertex_group)
        sub.prop(md, "invert_vertex_group", text="", icon='ARROW_LEFTRIGHT')

        col.prop(md, "use_deform_preserve_volume")
        col.prop(md, "use_multi_modifier")

        row = col.row(heading='Bind to')
        row.prop(md, "use_vertex_groups", text="Vertex Groups")
        col.prop(md, "use_bone_envelopes", text="Bone Envelopes")

        layout.separator()

    def ARRAY(self, layout, _ob, md):
        layout.use_property_split = True

        col = layout.column()
        col.separator()

        col.prop(md, "fit_type")

        if md.fit_type == 'FIXED_COUNT':
            col.prop(md, "count")
        elif md.fit_type == 'FIT_LENGTH':
            col.prop(md, "fit_length")
        elif md.fit_type == 'FIT_CURVE':
            col.prop(md, "curve")

        col.separator()

        col.prop(md, "start_cap")
        col.prop(md, "end_cap")

        col.separator()

        col.use_property_split = False
        col.prop(md, "use_relative_offset", text="Relative")
        if md.use_relative_offset:
            col.use_property_split = True
            col.prop(md, "relative_offset_displace", text="Factor")

        col.use_property_split = False
        col.prop(md, "use_constant_offset", text="Constant")
        if md.use_constant_offset:
            col.use_property_split = True
            col.prop(md, "constant_offset_displace", text="Distance")

        col.use_property_split = False
        col.prop(md, "use_merge_vertices", text="Merge")
        if md.use_merge_vertices:
            col.use_property_split = True
            col.prop(md, "use_merge_vertices_cap", text="First Last")
            col.prop(md, "merge_threshold", text="Distance")

        col.use_property_split = False
        col.prop(md, "use_object_offset")
        if md.use_object_offset:
            col.use_property_split = True
            col.prop(md, "offset_object", text="Object")

        col.use_property_split = False
        pref = bpy.context.scene.hops.modifiers
        col.prop(pref, "array1", text="UVs:")
        if pref.array1:
            col.use_property_split = True
            col.prop(md, "offset_u")
            col.prop(md, "offset_v")
        col.separator()

    def BEVEL(self, layout, ob, md):
        offset_type = md.offset_type

        col = layout.column()
        col.separator()
        col.row().prop(md, "affect", expand=True)

        layout.use_property_split = True

        col = layout.column()
        col.separator()

        col.prop(md, "offset_type")

        if offset_type == 'PERCENT':
            col.prop(md, "width_pct")
        else:
            offset_text = "Width"
            if offset_type == 'DEPTH':
                offset_text = "Depth"
            elif offset_type == 'OFFSET':
                offset_text = "Offset"
            col.prop(md, "width", text=offset_text)

        col.prop(md, "segments")

        col.separator()

        col.row().prop(md, "limit_method")
        if md.limit_method == 'ANGLE':
            col.prop(md, "angle_limit")
        elif md.limit_method == 'VGROUP':
            row = col.row()
            row.prop_search(md, "vertex_group", ob, "vertex_groups", text="Vertex Group")
            sub = row.row(align=True)
            sub.active = bool(md.vertex_group)
            sub.prop(md, "invert_vertex_group", text="", icon='ARROW_LEFTRIGHT')

        col.use_property_split = False
        pref = bpy.context.scene.hops.modifiers

        col.prop(pref, "bevel1", text="Profile:")
        if pref.bevel1:
            col.row().prop(md, "profile_type", expand=True)
            col.use_property_split = True

            if bpy.app.version < (2, 90, 0):
                col.row().prop(md, "use_custom_profile")
                row = col.row()
                row.enabled = md.use_custom_profile
                if md.use_custom_profile:
                    col.template_curveprofile(md, "custom_profile")
                    row2 = col.row(align=True)
                    op = row2.operator('hops.save_bevel_profile', text='Save Profile')
                    op.obj, op.mod = ob.name, md.name
                    op = row2.operator('hops.load_bevel_profile', text='Load Profile')
                    op.obj, op.mod = ob.name, md.name
            else:
                col.prop(md, "profile")
                if md.profile_type == 'CUSTOM':
                    col.template_curveprofile(md, "custom_profile")
                    row2 = col.row(align=True)
                    op = row2.operator('hops.save_bevel_profile', text='Save Profile')
                    op.obj, op.mod = ob.name, md.name
                    op = row2.operator('hops.load_bevel_profile', text='Load Profile')
                    op.obj, op.mod = ob.name, md.name

        col.use_property_split = False
        pref = bpy.context.scene.hops.modifiers
        col.prop(pref, "bevel2", text="Geometry:")
        if pref.bevel2:
            col.use_property_split = True
            col.row().prop(md, "miter_outer", text="Outer")
            col.row().prop(md, "miter_inner", text="Inner")
            if md.miter_inner in {'MITER_PATCH', 'MITER_ARC'}:
                col.row().prop(md, "spread")
            col.separator()

            col.row().prop(md, "vmesh_method")
            col.prop(md, "use_clamp_overlap")
            col.prop(md, "loop_slide")

        col.use_property_split = False
        pref = bpy.context.scene.hops.modifiers
        col.prop(pref, "bevel3", text="Shading:")
        if pref.bevel3:
            col.use_property_split = True
            col.prop(md, "harden_normals")
            col.separator()
            col.prop(md, "mark_seam")
            col.prop(md, "mark_sharp")
            col.prop(md, "material")
            col.prop(md, "face_strength_mode")
        col.separator()

    def BOOLEAN(self, layout, _ob, md):
        # layout.use_property_split = False
        col = layout.column()
        col.separator()
        row = col.row()
        row.prop(md, "operation", expand=True)

        layout.use_property_split = True
        col = layout.column()
        col.separator()
        if bpy.app.version > (2, 90, 0):
            col.prop(md, "operand_type")
        col.prop(md, "object")
        row = col.row()

        if bpy.app.version > (2, 90, 0):
            row.prop(md, "solver", expand=True)

            if md.solver == version.bsolver_fast():
                col.prop(md, "double_threshold")
            else:
                col.prop(md, "use_self")
        else:
            col.prop(md, "double_threshold")

        if bpy.app.debug:
            layout.prop(md, "debug_options")

        col.separator()

    def BUILD(self, layout, _ob, md):
        layout.use_property_split = True
        col = layout.column()
        col.separator()

        col.prop(md, "frame_start")
        col.prop(md, "frame_duration")
        col.prop(md, "use_reverse")

        col.separator()

        col.prop(md, "use_random_order")
        row = col.row()
        row.active = md.use_random_order
        row.prop(md, "seed")

    def MESH_CACHE(self, layout, _ob, md):
        layout.prop(md, "cache_format")
        layout.prop(md, "filepath")

        if md.cache_format == 'ABC':
            layout.prop(md, "sub_object")

        layout.label(text="Evaluation:")
        layout.prop(md, "factor", slider=True)
        layout.prop(md, "deform_mode")
        layout.prop(md, "interpolation")

        layout.label(text="Time Mapping:")

        row = layout.row()
        row.prop(md, "time_mode", expand=True)
        row = layout.row()
        row.prop(md, "play_mode", expand=True)
        if md.play_mode == 'SCENE':
            layout.prop(md, "frame_start")
            layout.prop(md, "frame_scale")
        else:
            time_mode = md.time_mode
            if time_mode == 'FRAME':
                layout.prop(md, "eval_frame")
            elif time_mode == 'TIME':
                layout.prop(md, "eval_time")
            elif time_mode == 'FACTOR':
                layout.prop(md, "eval_factor")

        layout.label(text="Axis Mapping:")
        split = layout.split(factor=0.5, align=True)
        split.alert = (md.forward_axis[-1] == md.up_axis[-1])
        split.label(text="Forward/Up Axis:")
        split.prop(md, "forward_axis", text="")
        split.prop(md, "up_axis", text="")
        split = layout.split(factor=0.5)
        split.label(text="Flip Axis:")
        row = split.row()
        row.prop(md, "flip_axis")

    def MESH_SEQUENCE_CACHE(self, layout, ob, md):
        layout.label(text="Cache File Properties:")
        box = layout.box()
        box.template_cache_file(md, "cache_file")

        cache_file = md.cache_file

        layout.label(text="Modifier Properties:")
        box = layout.box()

        if cache_file is not None:
            box.prop_search(md, "object_path", cache_file, "object_paths")

        if ob.type == 'MESH':
            box.row().prop(md, "read_data")

    def CAST(self, layout, ob, md):
        layout.use_property_split = True
        col = layout.column()
        col.separator()

        col.prop(md, "cast_type")
        row = col.row(align=True, heading='Axis')
        row.prop(md, "use_x", toggle=True)
        row.prop(md, "use_y", toggle=True)
        row.prop(md, "use_z", toggle=True)

        col.prop(md, "factor")
        col.prop(md, "radius")
        col.prop(md, "size")
        col.prop(md, "use_radius_as_size")


        row = col.row()
        row.prop_search(md, "vertex_group", ob, "vertex_groups", text="Vertex Group")
        row.prop(md, "invert_vertex_group", text="", icon='ARROW_LEFTRIGHT')
        col.prop(md, "object", text="Object")
        if md.object:
            col.prop(md, "use_transform")

    def CLOTH(self, layout, _ob, _md):
        layout.label(text="Settings are inside the Physics tab")

    def COLLISION(self, layout, _ob, _md):
        layout.label(text="Settings are inside the Physics tab")

    def CURVE(self, layout, ob, md):
        layout.use_property_split = True
        col = layout.column()
        col.separator()

        col.prop(md, "object")
        col.row().prop(md, "deform_axis")
        row = col.row()
        row.prop_search(md, "vertex_group", ob, "vertex_groups", text="Vertex Group")
        row.prop(md, "invert_vertex_group", text="", icon='ARROW_LEFTRIGHT')

    def DECIMATE(self, layout, ob, md):
        decimate_type = md.decimate_type

        layout.use_property_split = True
        col = layout.column()
        col.separator()

        col.prop(md, "decimate_type")

        if decimate_type == 'COLLAPSE':
            col.prop(md, "ratio")

            row = col.row(heading='Symmetry')
            row.prop(md, "use_symmetry", text="")
            row.prop(md, "symmetry_axis", expand=True)

            col.prop(md, "use_collapse_triangulate")

            row = col.row()
            row.prop_search(md, "vertex_group", ob, "vertex_groups", text="Vertex Group")
            sub = row.row(align=True)
            sub.active = bool(md.vertex_group)
            sub.prop(md, "invert_vertex_group", text="", icon='ARROW_LEFTRIGHT')
            col.prop(md, "vertex_group_factor")

        elif decimate_type == 'UNSUBDIV':
            col.prop(md, "iterations")

        else:
            col.prop(md, "angle_limit")
            col.prop(md, "delimit", expand=True)
            col.prop(md, "use_dissolve_boundaries")

        col.separator()
        col.label(text=f'Face Count: {md.face_count}')

    def DISPLACE(self, layout, ob, md):
        has_texture = (md.texture is not None)

        layout.use_property_split = True
        col = layout.column()
        col.separator()

        col.template_ID(md, "texture", new="texture.new")
        row = col.row()
        row.active = has_texture
        row.prop(md, "texture_coords", text='Coordinates')
        if md.texture_coords == 'OBJECT':
            col.prop(md, "texture_coords_object")
            obj = md.texture_coords_object
            if obj and obj.type == 'ARMATURE':
                col.prop_search(md, "texture_coords_bone", obj.data, "bones", text="Bone")
        elif md.texture_coords == 'UV' and ob.type == 'MESH':
            col.prop_search(md, "uv_layer", ob.data, "uv_layers", text="UV map")

        col.separator()
        col.prop(md, "direction")
        if md.direction in {'X', 'Y', 'Z', 'RGB_TO_XYZ'}:
            col.prop(md, "space")
        col.separator()

        col.prop(md, "strength")
        col.prop(md, "mid_level")
        row = col.row()
        row.prop_search(md, "vertex_group", ob, "vertex_groups", text="Vertex Group")
        sub = row.row(align=True)
        sub.active = bool(md.vertex_group)
        sub.prop(md, "invert_vertex_group", text="", icon='ARROW_LEFTRIGHT')

    def DYNAMIC_PAINT(self, layout, _ob, _md):
        layout.label(text="Settings are inside the Physics tab")

    def EDGE_SPLIT(self, layout, _ob, md):
        layout.use_property_split = True
        col = layout.column()
        col.separator()

        row = col.row(align=True, heading='Edge Angle')
        row.prop(md, "use_edge_angle", text="")
        row.prop(md, "split_angle")
        col.prop(md, "use_edge_sharp", text="Sharp Edges")

    def EXPLODE(self, layout, ob, md):
        split = layout.split()

        col = split.column()
        col.label(text="Vertex Group:")
        row = col.row(align=True)
        row.prop_search(md, "vertex_group", ob, "vertex_groups", text="")
        row.prop(md, "invert_vertex_group", text="", icon='ARROW_LEFTRIGHT')
        sub = col.column()
        sub.active = bool(md.vertex_group)
        sub.prop(md, "protect")
        col.label(text="Particle UV")
        col.prop_search(md, "particle_uv", ob.data, "uv_layers", text="")

        col = split.column()
        col.prop(md, "use_edge_cut")
        col.prop(md, "show_unborn")
        col.prop(md, "show_alive")
        col.prop(md, "show_dead")
        col.prop(md, "use_size")

        layout.operator("object.explode_refresh", text="Refresh")

    def FLUID_SIMULATION(self, layout, _ob, _md):
        layout.label(text="Settings are inside the Physics tab")

    def HOOK(self, layout, ob, md):
        use_falloff = (md.falloff_type != 'NONE')

        layout.use_property_split = True
        col = layout.column()
        col.separator()

        col.prop(md, "object")
        if md.object and md.object.type == 'ARMATURE':
            col.prop_search(md, "subtarget", md.object.data, "bones", text="Bone")
        row = col.row()
        row.prop_search(md, "vertex_group", ob, "vertex_groups", text="Vertex Group")
        row.prop(md, "invert_vertex_group", text="", icon='ARROW_LEFTRIGHT')
        col.prop(md, "strength", slider=True)

        if ob.mode == 'EDIT':
            row = col.row(align=True)
            row.operator("object.hook_reset", text="Reset")
            row.operator("object.hook_recenter", text="Recenter")

            row = col.row(align=True)
            row.operator("object.hook_select", text="Select")
            row.operator("object.hook_assign", text="Assign")

        col.use_property_split = False
        pref = bpy.context.scene.hops.modifiers
        col.prop(pref, "hook_falloff", text="Falloff")
        if pref.hook_falloff:
            col.use_property_split = True

            col.prop(md, "falloff_type")
            col.prop(md, "falloff_radius")

            if use_falloff:
                if md.falloff_type == 'CURVE':
                    col.template_curve_mapping(md, "falloff_curve")

            col.prop(md, "use_falloff_uniform")

    def LAPLACIANDEFORM(self, layout, ob, md):
        is_bind = md.is_bind

        layout.use_property_split = True
        col = layout.column()
        col.separator()

        col.prop(md, "iterations")

        row = col.row()
        row.enabled = not is_bind
        row.prop_search(md, "vertex_group", ob, "vertex_groups", text="Vertex Group")
        row.prop(md, "invert_vertex_group", text="", icon='ARROW_LEFTRIGHT')

        col.separator()

        row = col.row()
        row.enabled = bool(md.vertex_group)
        row.operator("object.laplaciandeform_bind", text="Unbind" if is_bind else "Bind").modifier = md.name

    def LAPLACIANSMOOTH(self, layout, ob, md):
        layout.use_property_split = True
        col = layout.column()
        col.separator()

        col.prop(md, "iterations")

        row = col.row(align=True, heading='Axis')
        row.prop(md, "use_x", toggle=True)
        row.prop(md, "use_y", toggle=True)
        row.prop(md, "use_z", toggle=True)

        col.prop(md, "lambda_factor")
        col.prop(md, "lambda_border")

        col.prop(md, "use_volume_preserve")
        col.prop(md, "use_normalized")

        row = col.row()
        row.prop_search(md, "vertex_group", ob, "vertex_groups", text="Vertex Group")
        row.prop(md, "invert_vertex_group", text="", icon='ARROW_LEFTRIGHT')

    def LATTICE(self, layout, ob, md):
        layout.use_property_split = True
        col = layout.column()
        col.separator()

        col.prop(md, "object")
        row = col.row()
        row.prop_search(md, "vertex_group", ob, "vertex_groups", text="Vertex Group")
        row.prop(md, "invert_vertex_group", text="", icon='ARROW_LEFTRIGHT')
        col.prop(md, "strength", slider=True)

    def MASK(self, layout, ob, md):
        layout.use_property_split = True
        col = layout.column()
        col.separator()

        row = col.row(align=True)
        row.prop(md, "mode", expand=True)

        if md.mode == 'ARMATURE':
            row = col.row()
            row.prop(md, "armature", text="Armature")
            sub = row.row(align=True)
            sub.active = bool(md.vertex_group)
            sub.prop(md, "invert_vertex_group", text="", icon='ARROW_LEFTRIGHT')
        elif md.mode == 'VERTEX_GROUP':
            row = col.row()
            row.prop_search(md, "vertex_group", ob, "vertex_groups", text="Vertex Group")
            sub = row.row(align=True)
            sub.active = bool(md.vertex_group)
            sub.prop(md, "invert_vertex_group", text="", icon='ARROW_LEFTRIGHT')

        col.use_property_split = True
        col.prop(md, "threshold")

    def MESH_DEFORM(self, layout, ob, md):
        is_bind = md.is_bound
        layout.use_property_split = True
        col = layout.column()
        col.separator()

        col.prop(md, "object")
        row = col.row()
        row.prop_search(md, "vertex_group", ob, "vertex_groups", text="Vertex Group")
        sub = row.row(align=True)
        sub.active = bool(md.vertex_group)
        sub.prop(md, "invert_vertex_group", text="", icon='ARROW_LEFTRIGHT')
        col.prop(md, "precision")
        col.prop(md, "use_dynamic_bind")

        layout.separator()
        layout.operator("object.meshdeform_bind", text="Unbind" if is_bind else "Bind").modifier = md.name


    def MIRROR(self, layout, _ob, md):
        axis_text = "XYZ"
        layout.use_property_split = True

        col = layout.column(align=True)
        col.separator()

        row = col.row(heading="Axis")
        for i, text in enumerate(axis_text):
            row.prop(md, "use_axis", text=text, index=i, toggle=True)

        row = col.row(heading="Bisect")
        for i, text in enumerate(axis_text):
            row.prop(md, "use_bisect_axis", text=text, index=i, toggle=True)

        row = col.row(heading="Flip")
        for i, text in enumerate(axis_text):
            row.prop(md, "use_bisect_flip_axis", text=text, index=i, toggle=True)

        layout.separator()

        col = layout.column()
        col.prop(md, "mirror_object", text="Mirror Object")
        col.prop(md, "use_clip", text="Clipping")
        row = col.row()

        row = col.row(heading="Merge")
        row.prop(md, "use_mirror_merge", text="")
        row.active = md.use_mirror_merge
        row.prop(md, "merge_threshold", text="")

        col.use_property_split = False
        pref = bpy.context.scene.hops.modifiers
        col.prop(pref, "mirrordata", text="Data:")
        if pref.mirrordata:
            col.use_property_split = True

            col.prop(md, "offset_u", text="Offset U")
            col.prop(md, "offset_v", text="V")

            row = col.row(align=True, heading="Mirror U")
            row.prop(md, "use_mirror_u", text="")
            row.active = md.use_mirror_u
            row.prop(md, "mirror_offset_u", text="")

            row = col.row(align=True, heading="V")
            row.prop(md, "use_mirror_v", text="")
            row.active = md.use_mirror_v
            row.prop(md, "mirror_offset_v", text="")

            col.prop(md, "use_mirror_vertex_groups", text="Vertex Groups")
            col.prop(md, "use_mirror_udim", text="Flip UDIM")

        col.separator()

    def MULTIRES(self, layout, ob, md):
        # Changing some of the properties can not be done once there is an
        # actual displacement stored for this multires modifier. This check
        # will disallow those properties from change.
        # This is a bit stupid check but should be sufficient for the usual
        # multires usage. It might become less strict and only disallow
        # modifications if there is CD_MDISPS layer, or if there is actual
        # non-zero displacement but such checks will be too slow to be done
        # on every redraw.

        layout.use_property_split = True
        col = layout.column(align=True)
        col.separator()

        col.prop(md, "sculpt_levels", text="Levels Sculpt")
        col.prop(md, "levels", text="Viewport")
        col.prop(md, "render_levels", text="Render")
        col.prop(md, "use_sculpt_base_mesh", text="Scupt Base Mesh")
        col.prop(md, "show_only_control_edges", text="Optimal Display")

        col.separator()
        col.use_property_split = False
        pref = bpy.context.scene.hops.modifiers
        col.prop(pref, "multires_subdivision", text="Subdivision:")
        if pref.multires_subdivision:
            col.use_property_split = True

            col.operator("object.multires_subdivide", text="Subdivide").modifier = md.name

            row = col.row(align=False)
            simple = row.operator("object.multires_subdivide", text="Simple")
            simple.modifier = md.name
            simple.mode = 'SIMPLE'
            linear = row.operator("object.multires_subdivide", text="Linear")
            linear.modifier = md.name
            linear.mode = 'LINEAR'

            col.separator()
            col.operator("object.multires_unsubdivide", text="Unsubdivide").modifier = md.name
            col.operator("object.multires_higher_levels_delete", text="Delete Higher").modifier = md.name

        col.separator()
        col.use_property_split = False
        pref = bpy.context.scene.hops.modifiers
        col.prop(pref, "multires_shape", text="Shape:")
        if pref.multires_shape:
            col.use_property_split = True
            row = col.row(align=False)
            row.operator("object.multires_reshape", text="Reshape").modifier = md.name
            row.operator("object.multires_base_apply", text="Apply Base").modifier = md.name

        col.separator()
        col.use_property_split = False
        pref = bpy.context.scene.hops.modifiers
        col.prop(pref, "multires_generate", text="Generate:")
        if pref.multires_generate:
            col.use_property_split = True
            col.operator("object.multires_rebuild_subdiv", text="Rebuild Subdivisions")
            col.operator_context = "EXEC_DEFAULT"
            col.operator("object.multires_external_save", text="Save External...")

        col.separator()

        # if md.total_levels == 0:
        #     col.operator("object.multires_rebuild_subdiv", text="Rebuild Subdivisions")
        #     col.separator()

        # if md.is_external:
        #     col.operator("object.multires_external_pack", text="Pack External")
        #     col.prop(md, "filepath", text="")
        # else:
        #     col.operator_context = "INVOKE_DEFAULT"
        #     col.operator("object.multires_external_save", text="Save External...")

        # col.separator()

        col.use_property_split = False
        pref = bpy.context.scene.hops.modifiers
        col.prop(pref, "multires_advanced", text="Advanced:")
        if pref.multires_advanced:
            col.use_property_split = True

            col.prop(md, "quality")
            col.prop(md, "uv_smooth", text="UV Smooth")
            col.prop(md, "boundary_smooth", text="Boundary Smooth")
            col.prop(md, "use_creases")
            col.prop(md, "use_custom_normals")

    def NODES(self, layout, ob, md):

        layout.use_property_split = True
        col = layout.column(align=True)
        col.separator()

        row = layout.row()
        if md and md.type == "NODES":
            row.template_ID(md, "node_group", new="node.new_geometry_node_group_assign")

        layout.separator()
        use_interface = bpy.app.version[0] > 3
        inputs = md.node_group.inputs if not use_interface else [s for s in md.node_group.interface.items_tree if s.item_type == 'SOCKET' and s.in_out == 'INPUT']
        _attr = 'type' if not use_interface else 'socket_type'
        _type = 'GEOMETRY' if not use_interface else 'NodeSocketGeometry'
        for input in inputs:
            if getattr(input, _attr) == _type: continue

            value_data, value_prop = compat.value_prop(md, input.identifier)
            if not value_data: continue

            row = layout.row(align=True)
            row.label(text=input.name)

            if compat.supports_attribute(md, input.identifier):

                op = row.operator("object.geometry_nodes_input_attribute_toggle", text='', icon='SPREADSHEET')
                if not use_interface:
                    op.prop_path = f"[\"{input.identifier}_use_attribute\"]"
                else:
                    # the operator resolves the socket by identifier, passing the name
                    # is a no-op up to 5.1 and crashes 5.2
                    op.input_name=input.identifier
                op.modifier_name = md.name

                if compat.get_use_attribute(md, input.identifier):
                    HOPS_MT_helper_node_attr_search.__class__.modifier_name = md.name
                    HOPS_MT_helper_node_attr_search.__class__.attribute_name = input.identifier
                    HOPS_MT_helper_node_attr_search.__class__.is_output = False

                    row.menu(HOPS_MT_helper_node_attr_search.bl_idname, text='', icon='DOWNARROW_HLT')
                    row.prop(*compat.attribute_name_prop(md, input.identifier), text='')
                else:
                    row.prop(value_data, value_prop, text='')

            else:
                row.prop(value_data, value_prop, text='')

            layout.separator()

        mod_pref = bpy.context.scene.hops.modifiers
        row = layout.split(align=True)
        row.use_property_split = False

        row.prop(mod_pref, 'node_outputs', text='Output Attributes')

        if not mod_pref.node_outputs: return

        outputs = getattr(md.node_group, 'outputs', None)

        if not outputs:
            outputs = [s for s in md.node_group.interface.items_tree if s.item_type == 'SOCKET' and s.in_out == 'OUTPUT']

        for output in outputs:
            attr_data, attr_prop = compat.output_attribute_name_prop(md, output.identifier)

            if not attr_data: continue

            row = layout.row(align=True)
            row.label(text=output.name)

            HOPS_MT_helper_node_attr_search.__class__.modifier_name = md.name
            HOPS_MT_helper_node_attr_search.__class__.attribute_name = output.identifier
            HOPS_MT_helper_node_attr_search.__class__.is_output = True
            row.menu(HOPS_MT_helper_node_attr_search.bl_idname, text='', icon='DOWNARROW_HLT')

            row.prop(attr_data, attr_prop, text='')

    def OCEAN(self, layout, _ob, md):
        if not bpy.app.build_options.mod_oceansim:
            layout.label(text="Built without OceanSim modifier")
            return

        layout.prop(md, "geometry_mode")

        if md.geometry_mode == 'GENERATE':
            row = layout.row()
            row.prop(md, "repeat_x")
            row.prop(md, "repeat_y")

        layout.separator()

        split = layout.split()

        col = split.column()
        col.prop(md, "time")
        col.prop(md, "depth")
        col.prop(md, "random_seed")

        col = split.column()
        col.prop(md, "resolution")
        col.prop(md, "size")
        col.prop(md, "spatial_size")

        layout.separator()

        layout.prop(md, "spectrum")

        if md.spectrum in {'TEXEL_MARSEN_ARSLOE', 'JONSWAP'}:
            split = layout.split()

            col = split.column()
            col.prop(md, "sharpen_peak_jonswap")

            col = split.column()
            col.prop(md, "fetch_jonswap")

        layout.label(text="Waves:")

        split = layout.split()

        col = split.column()
        col.prop(md, "choppiness")
        col.prop(md, "wave_scale", text="Scale")
        col.prop(md, "wave_scale_min")
        col.prop(md, "wind_velocity")

        col = split.column()
        col.prop(md, "wave_alignment", text="Alignment")
        sub = col.column()
        sub.active = (md.wave_alignment > 0.0)
        sub.prop(md, "wave_direction", text="Direction")
        sub.prop(md, "damping")

        layout.separator()

        layout.prop(md, "use_normals")

        split = layout.split()

        col = split.column()
        col.prop(md, "use_foam")
        sub = col.row()
        sub.active = md.use_foam
        sub.prop(md, "foam_coverage", text="Coverage")

        col = split.column()
        col.active = md.use_foam
        col.label(text="Foam Data Layer Name:")
        col.prop(md, "foam_layer_name", text="")

        layout.separator()

        if md.is_cached:
            layout.operator("object.ocean_bake", text="Delete Bake").free = True
        else:
            layout.operator("object.ocean_bake").free = False

        split = layout.split()
        split.enabled = not md.is_cached

        col = split.column(align=True)
        col.prop(md, "frame_start", text="Start")
        col.prop(md, "frame_end", text="End")

        col = split.column(align=True)
        col.label(text="Cache path:")
        col.prop(md, "filepath", text="")

        split = layout.split()
        split.enabled = not md.is_cached

        col = split.column()
        col.active = md.use_foam
        col.prop(md, "bake_foam_fade")

        col = split.column()

    def PARTICLE_INSTANCE(self, layout, ob, md):
        layout.prop(md, "object")
        if md.object:
            layout.prop_search(md, "particle_system", md.object, "particle_systems", text="Particle System")
        else:
            layout.prop(md, "particle_system_index", text="Particle System")

        split = layout.split()
        col = split.column()
        col.label(text="Create From:")
        layout.prop(md, "space", text="")
        col.prop(md, "use_normal")
        col.prop(md, "use_children")
        col.prop(md, "use_size")

        col = split.column()
        col.label(text="Show Particles When:")
        col.prop(md, "show_alive")
        col.prop(md, "show_unborn")
        col.prop(md, "show_dead")

        row = layout.row(align=True)
        row.prop(md, "particle_amount", text="Amount")
        row.prop(md, "particle_offset", text="Offset")

        row = layout.row(align=True)
        row.prop(md, "axis", expand=True)

        layout.separator()

        layout.prop(md, "use_path", text="Create Along Paths")

        col = layout.column()
        col.active = md.use_path
        col.prop(md, "use_preserve_shape")

        row = col.row(align=True)
        row.prop(md, "position", slider=True)
        row.prop(md, "random_position", text="Random", slider=True)
        row = col.row(align=True)
        row.prop(md, "rotation", slider=True)
        row.prop(md, "random_rotation", text="Random", slider=True)

        layout.separator()

        col = layout.column()
        col.prop_search(md, "index_layer_name", ob.data, "vertex_colors", text="Index Layer")
        col.prop_search(md, "value_layer_name", ob.data, "vertex_colors", text="Value Layer")

    def PARTICLE_SYSTEM(self, layout, _ob, _md):
        layout.label(text="Settings can be found inside the Particle context")

    def SCREW(self, layout, _ob, md):
        layout.use_property_split = True
        col = layout.column()
        col.separator()

        col.prop(md, "angle", text='Angle')
        col.prop(md, "screw_offset")
        col.prop(md, "iterations")
        col.separator()

        row = col.row(align=True, heading='Axis')
        row.prop(md, "axis", expand=True)
        col.prop(md, "object", text="Axis Object")
        col.prop(md, "use_object_screw_offset", text="Object Screw")
        col.separator()

        col.prop(md, "steps", text="Steps Viewport")
        col.prop(md, "render_steps", text="Render")
        col.separator()

        row = col.row(align=True, heading='Merge')
        row.prop(md, "use_merge_vertices", text="")
        row2 = row.row()
        row2.active = md.use_merge_vertices
        row2.prop(md, "merge_threshold", text="")
        col.separator()

        row = col.row(align=True, heading='Stretch UVs')
        row.prop(md, "use_stretch_u", text="U", toggle=True)
        row.prop(md, "use_stretch_v", text="V", toggle=True)

        col.separator()
        col.use_property_split = False
        pref = bpy.context.scene.hops.modifiers
        col.prop(pref, "screw_normals", text="Normals:")
        if pref.screw_normals:
            col.use_property_split = True
            col.prop(md, "use_smooth_shade")
            col.prop(md, "use_normal_calculate")
            col.prop(md, "use_normal_flip")

    def SHRINKWRAP(self, layout, ob, md):
        layout.use_property_split = True
        col = layout.column()
        col.separator()

        col.prop(md, "wrap_method")
        if md.wrap_method in {'PROJECT', 'NEAREST_SURFACEPOINT', 'TARGET_PROJECT'}:
            col.prop(md, "wrap_mode")

        if md.wrap_method == 'PROJECT':

            col.prop(md, "project_limit")
            col.prop(md, "subsurf_levels")

            row = col.row(align=True, heading='Axis')
            row.prop(md, "use_project_x", toggle=True)
            row.prop(md, "use_project_y", toggle=True)
            row.prop(md, "use_project_z", toggle=True)

            col.prop(md, "use_negative_direction")
            col.prop(md, "use_positive_direction")

            row = col.row(align=True, heading='Axis')
            row.prop(md, "cull_face", expand=True)

            col.prop(md, "use_invert_cull")

        col.prop(md, "target")

        if md.wrap_method == 'PROJECT':
            col.prop(md, "auxiliary_target")

        col.prop(md, "offset")
        row = col.row()
        row.prop_search(md, "vertex_group", ob, "vertex_groups", text="Vertex Group")
        row.prop(md, "invert_vertex_group", text="", icon='ARROW_LEFTRIGHT')

    def SIMPLE_DEFORM(self, layout, ob, md):

        layout.row().prop(md, "deform_method", expand=True)
        layout.use_property_split = True
        col = layout.column()
        col.separator()

        if md.deform_method in {'TAPER', 'STRETCH'}:
            col.prop(md, "factor")
        else:
            col.prop(md, "angle")

        col.prop(md, "origin")
        row = col.row()
        row.prop(md, "deform_axis", expand=True)

        col.use_property_split = False
        pref = bpy.context.scene.hops.modifiers
        col.prop(pref, "simpledeform_restrictions", text="Restrictions:")
        if pref.simpledeform_restrictions:
            col.use_property_split = True

            col.prop(md, "limits", slider=True)

            if md.deform_method in {'TAPER', 'STRETCH', 'TWIST'}:
                row = col.row(align=True, heading='Lock')
                deform_axis = md.deform_axis
                if deform_axis != 'X':
                    row.prop(md, "lock_x")
                if deform_axis != 'Y':
                    row.prop(md, "lock_y")
                if deform_axis != 'Z':
                    row.prop(md, "lock_z")

            row = col.row()
            row.prop_search(md, "vertex_group", ob, "vertex_groups", text="Vertex Group")
            row.prop(md, "invert_vertex_group", text="", icon='ARROW_LEFTRIGHT')

    def FLUID(self, layout, _ob, _md):
        layout.label(text="Settings are inside the Physics tab")

    def SMOOTH(self, layout, ob, md):
        layout.use_property_split = True
        col = layout.column()
        col.separator()

        row = col.row(align=True, heading='Axis')
        col.prop(md, "use_x", toggle=True)
        col.prop(md, "use_y", toggle=True)
        col.prop(md, "use_z", toggle=True)

        col.prop(md, "factor")
        col.prop(md, "iterations")
        col.label(text="Vertex Group:")
        row = col.row()
        row.prop_search(md, "vertex_group", ob, "vertex_groups", text="Vertex Group")
        row.prop(md, "invert_vertex_group", text="", icon='ARROW_LEFTRIGHT')

    def SOFT_BODY(self, layout, _ob, _md):
        layout.label(text="Settings are inside the Physics tab")

    def SOLIDIFY(self, layout, ob, md):
        layout.use_property_split = True
        col = layout.column()
        col.separator()

        col.prop(md, "solidify_mode", text='Mode')

        solidify_mode = md.solidify_mode

        if solidify_mode == 'NON_MANIFOLD':
            col.prop(md, "nonmanifold_thickness_mode")
            col.prop(md, "nonmanifold_boundary_mode")

        col.prop(md, "thickness")
        col.prop(md, "offset")
        if solidify_mode == 'NON_MANIFOLD':
            col.prop(md, "nonmanifold_merge_threshold")
        col.prop(md, "use_rim")
        col.prop(md, "use_rim_only")

        col.separator()

        # col.use_property_split = False
        row = col.row()
        row.prop_search(md, "vertex_group", ob, "vertex_groups", text="Vertex Groups")
        sub = row.row(align=True)
        sub.active = bool(md.vertex_group)
        sub.prop(md, "invert_vertex_group", text="", icon='ARROW_LEFTRIGHT')

        col.use_property_split = True
        col.prop(md, "thickness_vertex_group", text="Factor")
        if solidify_mode == 'NON_MANIFOLD':
            col.prop(md, "use_flat_faces")

        col.separator()

        col.use_property_split = False
        pref = bpy.context.scene.hops.modifiers
        col.prop(pref, "solidify_normals", text='Normals')
        if pref.solidify_normals:
            col.use_property_split = True
            col.prop(md, "use_flip_normals")
            if solidify_mode == 'EXTRUDE':
                col.prop(md, "use_even_offset")
                col.prop(md, "use_quality_normals")

        col.use_property_split = False
        pref = bpy.context.scene.hops.modifiers
        col.prop(pref, "solidify_materials", text='Materials')
        if pref.solidify_materials:
            col.use_property_split = True
            col.prop(md, "material_offset", text="Material Offset")
            col.prop(md, "material_offset_rim", text="Rim")

        col.use_property_split = False
        pref = bpy.context.scene.hops.modifiers
        col.prop(pref, "solidify_edgedata", text='Edge Data')
        if pref.solidify_edgedata:
            col.use_property_split = True
            if solidify_mode == 'EXTRUDE':
                col.label(text="Crease:")
                col.prop(md, "edge_crease_inner", text="Inner")
                col.prop(md, "edge_crease_outer", text="Outer")
                col.prop(md, "edge_crease_rim", text="Rim")
            col.prop(md, "bevel_convex")

        col.use_property_split = False
        pref = bpy.context.scene.hops.modifiers
        col.prop(pref, "solidify_thicknes", text='Thickness Clamp')
        if pref.solidify_thicknes:
            col.use_property_split = True
            col.prop(md, "thickness_clamp", text='Clamp')
            col.prop(md, "use_thickness_angle_clamp")

        col.use_property_split = False
        pref = bpy.context.scene.hops.modifiers
        col.prop(pref, "solidify_output", text='Output Vertex Groups')
        if pref.solidify_output:
            col.use_property_split = True
            row = col.row()
            row.prop_search(md, "shell_vertex_group", ob, "vertex_groups", text="Shell")
            row = col.row()
            row.prop_search(md, "rim_vertex_group", ob, "vertex_groups", text="Rim")

    def SUBSURF(self, layout, ob, md):
        from bpy import context
        layout.use_property_split = True
        col = layout.column()
        col.separator()

        col.prop(md, "subdivision_type", text='Type')
        col.separator()

        scene = context.scene
        engine = context.engine
        show_adaptive_options = (
            engine == 'CYCLES' and md == ob.modifiers[-1] and
            scene.cycles.feature_set == 'EXPERIMENTAL'
        )

        if show_adaptive_options:
            col.prop(ob.cycles, "use_adaptive_subdivision", text="Adaptive Subdivision")

        if show_adaptive_options and ob.cycles.use_adaptive_subdivision:
            col.prop(ob.cycles, "dicing_rate")
            render = max(scene.cycles.dicing_rate * ob.cycles.dicing_rate, 0.1)
            preview = max(scene.cycles.preview_dicing_rate * ob.cycles.dicing_rate, 0.1)
            col.label(text=f"Final Dicing Rate: Render {render:.2f} px, Preview {preview:.2f} px")
            col.separator()

        col.prop(md, "levels", text="Levels Viewport")
        col.prop(md, "render_levels", text="Render")
        col.separator()
        col.prop(md, "show_only_control_edges")

        col.separator()

        col.use_property_split = False
        pref = bpy.context.scene.hops.modifiers
        col.prop(pref, "subsurf_advanced", text="Advanced")
        if pref.subsurf_advanced:
            col.use_property_split = True

            col.prop(md, "use_limit_surface")
            col.prop(md, "quality")
            col.prop(md, "uv_smooth")
            col.prop(md, "boundary_smooth")
            col.prop(md, "use_creases")
            col.prop(md, "use_custom_normals")

    def SURFACE(self, layout, _ob, _md):
        layout.label(text="Settings are inside the Physics tab")

    def SURFACE_DEFORM(self, layout, _ob, md):
        layout.use_property_split = True
        col = layout.column()
        col.separator()

        col.prop(md, "target")

        col.prop(md, "falloff")
        col.prop(md, "strength")

        row = col.row()
        row.prop_search(md, "vertex_group", _ob, "vertex_groups", text="Vertex Group")
        row.prop(md, "invert_vertex_group", text="", icon='ARROW_LEFTRIGHT')

        row = col.row()
        row.operator("object.surfacedeform_bind", text="Unbind" if md.is_bound else "Bind").modifier = md.name

    def UV_PROJECT(self, layout, ob, md):
        layout.use_property_split = True
        col = layout.column()
        col.separator()

        col.prop_search(md, "uv_layer", ob.data, "uv_layers")
        col.separator()

        col.prop(md, "aspect_x", text="Aspect X")
        col.prop(md, "aspect_y", text="Y")
        col.separator()

        col.prop(md, "scale_x", text="Scale X")
        col.prop(md, "scale_y", text="Y")
        col.separator()

        col.prop(md, "projector_count", text="Projectors")
        for proj in md.projectors:
            col.prop(proj, "object")

    def WARP(self, layout, ob, md):
        use_falloff = (md.falloff_type != 'NONE')

        layout.use_property_split = True
        col = layout.column()
        col.separator()

        col.prop(md, "object_from")
        obj = md.object_from
        if obj and obj.type == 'ARMATURE':
            col.label(text="Bone:")
            col.prop_search(md, "bone_from", obj.data, "bones", text="Bone From")
        col.separator()

        col.prop(md, "object_to")
        if obj and obj.type == 'ARMATURE':
            col.label(text="Bone:")
            col.prop_search(md, "bone_to", obj.data, "bones", text="Bone To")
        col.separator()

        col.prop(md, "strength")
        col.prop(md, "use_volume_preserve")

        row = col.row()
        row.prop_search(md, "vertex_group", ob, "vertex_groups", text="Vertex Group")
        row.prop(md, "invert_vertex_group", text="", icon='ARROW_LEFTRIGHT')

        col.use_property_split = False
        pref = bpy.context.scene.hops.modifiers
        col.prop(pref, "warp_falloff", text="Falloff:")
        if pref.warp_falloff:
            col.use_property_split = True
            col.prop(md, "falloff_type")
            if use_falloff:
                col.prop(md, "falloff_radius")
            if md.falloff_type == 'CURVE':
                col.template_curve_mapping(md, "falloff_curve")

        col.use_property_split = False
        pref = bpy.context.scene.hops.modifiers
        col.prop(pref, "warp_textures", text="Texture:")
        if pref.warp_textures:
            col.use_property_split = True
            col.template_ID(md, "texture", new="texture.new")
            col.prop(md, "texture_coords")

            if md.texture_coords == 'OBJECT':
                col.prop(md, "texture_coords_object", text="Object")
                obj = md.texture_coords_object
                if obj and obj.type == 'ARMATURE':
                    col.prop_search(md, "texture_coords_bone", obj.data, "bones", text="Bone")
            elif md.texture_coords == 'UV' and ob.type == 'MESH':
                col.prop_search(md, "uv_layer", ob.data, "uv_layers")

    def WAVE(self, layout, ob, md):
        layout.use_property_split = True
        col = layout.column()
        col.separator()

        row = col.row(align=True, heading='Motion')
        row.prop(md, "use_x", toggle=True)
        row.prop(md, "use_y", toggle=True)

        col.prop(md, "use_cyclic")

        row = col.row(align=True, heading='Along Normals')
        row.active = md.use_normal
        row.prop(md, "use_normal", text="")
        row.prop(md, "use_normal_x", text="X", toggle=True)
        row.prop(md, "use_normal_y", text="Y", toggle=True)
        row.prop(md, "use_normal_z", text="Z", toggle=True)

        col.prop(md, "falloff_radius", text="Falloff")
        col.prop(md, "height", slider=True)
        col.prop(md, "width", slider=True)
        col.prop(md, "narrowness", slider=True)

        row = col.row()
        row.prop_search(md, "vertex_group", ob, "vertex_groups", text="Vertex Group")
        row.prop(md, "invert_vertex_group", text="", icon='ARROW_LEFTRIGHT')

        col.use_property_split = False
        pref = bpy.context.scene.hops.modifiers
        col.prop(pref, "wave_start", text="Start position")
        if pref.wave_start:
            col.use_property_split = True
            col.prop(md, "start_position_object", text="Object")
            col.prop(md, "start_position_x", text="Start position X")
            col.prop(md, "start_position_y", text="Y")

        col.use_property_split = False
        pref = bpy.context.scene.hops.modifiers
        col.prop(pref, "wave_time", text="Time")
        if pref.wave_time:
            col.use_property_split = True
            col.prop(md, "time_offset", slider=True)
            col.prop(md, "lifetime", slider=True)
            col.prop(md, "damping_time", slider=True)
            col.prop(md, "speed", slider=True)

        col.use_property_split = False
        pref = bpy.context.scene.hops.modifiers
        col.prop(pref, "wave_texture", text="Texture")
        if pref.wave_texture:
            col.use_property_split = True

            col.template_ID(md, "texture", new="texture.new")
            col.prop(md, "texture_coords")
            if md.texture_coords == 'UV' and ob.type == 'MESH':
                col.prop_search(md, "uv_layer", ob.data, "uv_layers")
            elif md.texture_coords == 'OBJECT':
                col.prop(md, "texture_coords_object")
                obj = md.texture_coords_object
                if obj and obj.type == 'ARMATURE':
                    col.prop_search(md, "texture_coords_bone", obj.data, "bones")

    def REMESH(self, layout, _ob, md):

        layout.separator()
        row = layout.row()

        row.prop(md, "mode", expand=True)

        col = layout.column(align=True)

        col.use_property_split = True
        col.separator()

        if md.mode == 'VOXEL':
            col.prop(md, "voxel_size")
            col.prop(md, "adaptivity")
        else:
            col.prop(md, "octree_depth")
            col.prop(md, "scale")

            if md.mode == 'SHARP':
                col.prop(md, "sharpness")

            col.prop(md, "use_remove_disconnected")
            col.prop(md, "threshold")

        col.prop(md, "use_smooth_shade")

    @staticmethod
    def vertex_weight_mask(layout, ob, md):
        layout.label(text="Influence/Mask Options:")

        split = layout.split(factor=0.4)
        split.label(text="Global Influence:")
        split.prop(md, "mask_constant", text="")

        if not md.mask_texture:
            split = layout.split(factor=0.4)
            split.label(text="Vertex Group Mask:")
            row = split.row(align=True)
            row.prop_search(md, "mask_vertex_group", ob, "vertex_groups", text="")
            row.prop(md, "invert_mask_vertex_group", text="", icon='ARROW_LEFTRIGHT')

        if not md.mask_vertex_group:
            split = layout.split(factor=0.4)
            split.label(text="Texture Mask:")
            split.template_ID(md, "mask_texture", new="texture.new")
            if md.mask_texture:
                split = layout.split()

                col = split.column()
                col.label(text="Texture Coordinates:")
                col.prop(md, "mask_tex_mapping", text="")

                col = split.column()
                col.label(text="Use Channel:")
                col.prop(md, "mask_tex_use_channel", text="")

                if md.mask_tex_mapping == 'OBJECT':
                    layout.prop(md, "mask_tex_map_object", text="Object")
                    obj = md.mask_tex_map_object
                    if obj and obj.type == 'ARMATURE':
                        layout.prop_search(md, "mask_tex_map_bone", obj.data, "bones", text="Bone")
                elif md.mask_tex_mapping == 'UV' and ob.type == 'MESH':
                    layout.prop_search(md, "mask_tex_uv_layer", ob.data, "uv_layers")

    def VERTEX_WEIGHT_EDIT(self, layout, ob, md):
        split = layout.split()

        col = split.column()
        col.label(text="Vertex Group:")
        col.prop_search(md, "vertex_group", ob, "vertex_groups", text="")

        col.label(text="Default Weight:")
        col.prop(md, "default_weight", text="")

        col = split.column()
        col.prop(md, "use_add")
        sub = col.column()
        sub.active = md.use_add
        sub.prop(md, "add_threshold")

        col = col.column()
        col.prop(md, "use_remove")
        sub = col.column()
        sub.active = md.use_remove
        sub.prop(md, "remove_threshold")

        layout.separator()

        row = layout.row(align=True)
        row.prop(md, "falloff_type")
        row.prop(md, "invert_falloff", text="", icon='ARROW_LEFTRIGHT')
        if md.falloff_type == 'CURVE':
            layout.template_curve_mapping(md, "map_curve")

        # Common mask options
        layout.separator()
        self.vertex_weight_mask(layout, ob, md)

    def VERTEX_WEIGHT_MIX(self, layout, ob, md):
        split = layout.split()

        col = split.column()
        col.label(text="Vertex Group A:")
        col.prop_search(md, "vertex_group_a", ob, "vertex_groups", text="")
        col.label(text="Default Weight A:")
        col.prop(md, "default_weight_a", text="")

        col.label(text="Mix Mode:")
        col.prop(md, "mix_mode", text="")

        col = split.column()
        col.label(text="Vertex Group B:")
        col.prop_search(md, "vertex_group_b", ob, "vertex_groups", text="")
        col.label(text="Default Weight B:")
        col.prop(md, "default_weight_b", text="")

        col.label(text="Mix Set:")
        col.prop(md, "mix_set", text="")

        # Common mask options
        layout.separator()
        self.vertex_weight_mask(layout, ob, md)

    def VERTEX_WEIGHT_PROXIMITY(self, layout, ob, md):
        split = layout.split()

        col = split.column()
        col.label(text="Vertex Group:")
        col.prop_search(md, "vertex_group", ob, "vertex_groups", text="")

        col = split.column()
        col.label(text="Target Object:")
        col.prop(md, "target", text="")

        split = layout.split()

        col = split.column()
        col.label(text="Distance:")
        col.prop(md, "proximity_mode", text="")
        if md.proximity_mode == 'GEOMETRY':
            col.row().prop(md, "proximity_geometry")

        col = split.column()
        col.label()
        col.prop(md, "min_dist")
        col.prop(md, "max_dist")

        layout.separator()
        row = layout.row(align=True)
        row.prop(md, "falloff_type")
        row.prop(md, "invert_falloff", text="", icon='ARROW_LEFTRIGHT')

        # Common mask options
        layout.separator()
        self.vertex_weight_mask(layout, ob, md)

    def SKIN(self, layout, _ob, md):
        layout.use_property_split = True
        col = layout.column()
        col.separator()

        col.prop(md, "branch_smoothing")
        row = col.row(align=True, heading='Symmetry')
        row.prop(md, "use_x_symmetry", toggle=True)
        row.prop(md, "use_y_symmetry", toggle=True)
        row.prop(md, "use_z_symmetry", toggle=True)
        col.prop(md, "use_smooth_shade")

        split = layout.split()
        col = split.column()
        col.operator("object.skin_armature_create", text="Create Armature").modifier = md.name
        col = split.column()
        col.operator("mesh.customdata_skin_add")

        col = layout.column()
        row = col.row(align=True)
        row.operator("object.skin_loose_mark_clear", text="Mark Loose").action = 'MARK'
        row.operator("object.skin_loose_mark_clear", text="Clear Loose").action = 'CLEAR'

        col.operator("object.skin_root_mark", text="Mark Root")
        col.operator("object.skin_radii_equalize", text="Equalize Radii")

    def TRIANGULATE(self, layout, _ob, md):
        layout.use_property_split = True
        col = layout.column()
        col.separator()

        col.prop(md, "quad_method")
        col.prop(md, "ngon_method")
        col.prop(md, "min_vertices")
        col.prop(md, "keep_custom_normals")

    def UV_WARP(self, layout, ob, md):
        layout.use_property_split = True
        col = layout.column()
        col.separator()

        col.prop_search(md, "uv_layer", ob.data, "uv_layers")

        col.prop(md, "center")
        col.separator()

        col.prop(md, "axis_u", text="Axis U")
        col.prop(md, "axis_v", text="V")
        col.separator()

        col.prop(md, "object_from")
        obj = md.object_from
        if obj and obj.type == 'ARMATURE':
            col.prop_search(md, "bone_from", obj.data, "bones", text="Bone from")

        col.prop(md, "object_to")
        obj = md.object_to
        if obj and obj.type == 'ARMATURE':
            col.prop_search(md, "bone_to", obj.data, "bones", text="Bone to")
        col.separator()

        row = col.row()
        row.prop_search(md, "vertex_group", ob, "vertex_groups", text="Vertex Group")
        row.prop(md, "invert_vertex_group", text="", icon='ARROW_LEFTRIGHT')

        col.use_property_split = False
        pref = bpy.context.scene.hops.modifiers
        col.prop(pref, "uvwarp_transform", text="Transform")
        if pref.uvwarp_transform:
            col.use_property_split = True

            col.prop(md, "offset", text="")
            col.prop(md, "scale", text="")
            col.prop(md, "rotation", text="")

    def WIREFRAME(self, layout, ob, md):
        has_vgroup = bool(md.vertex_group)
        layout.use_property_split = True
        col = layout.column()
        col.separator()

        col.prop(md, "thickness", text="Thickness")
        col.prop(md, "offset")
        col.separator()
        col.prop(md, "use_boundary", text="Boundary")
        col.prop(md, "use_replace", text="Replace Original")
        col.separator()
        row = col.row(heading='Thickness')
        row.prop(md, "use_even_offset", text="Even")
        col.prop(md, "use_relative_offset", text="Relative")
        col.separator()
        row = col.row(heading='Crease Edges')
        row.prop(md, "use_crease", text="")
        row.prop(md, "crease_weight", text="")
        col.prop(md, "material_offset", text="Material Offset")

        col.separator()

        col.use_property_split = False
        pref = bpy.context.scene.hops.modifiers
        col.prop(pref, "wireframe_vgroup", text="Profile:")
        if pref.wireframe_vgroup:
            col.use_property_split = True
            row = col.row()
            row.prop_search(md, "vertex_group", ob, "vertex_groups", text="Vertex Group")
            sub = row.row(align=True)
            sub.active = has_vgroup
            sub.prop(md, "invert_vertex_group", text="", icon='ARROW_LEFTRIGHT')
            row = col.row(align=True)
            row.active = has_vgroup
            row.prop(md, "thickness_vertex_group", text="Factor")

    def WELD(self, layout, ob, md):
        layout.use_property_split = True
        col = layout.column()
        col.separator()

        if hasattr(md, "mode"):
            col.prop(md, "mode")

        col.prop(md, "merge_threshold", text="Distance")
        if hasattr(md, 'max_interactions'):
            col.prop(md, "max_interactions")

        row = col.row()
        row.prop_search(md, "vertex_group", ob, "vertex_groups", text='Vertex Group')
        row.prop(md, "invert_vertex_group", text="", icon='ARROW_LEFTRIGHT')

    def DATA_TRANSFER(self, layout, ob, md):
        layout.use_property_split = True
        col = layout.column()
        col.separator()

        row = col.row(align=True)
        row.prop(md, "object", text='Source')
        row.prop(md, "use_object_transform", text="", icon='GROUP')

        col.prop(md, "mix_mode")
        col.prop(md, "mix_factor")

        row = col.row()
        row.prop_search(md, "vertex_group", ob, "vertex_groups", text="Vertex Group")
        sub = row.row(align=True)
        sub.active = bool(md.vertex_group)
        sub.prop(md, "invert_vertex_group", text="", icon='ARROW_LEFTRIGHT')
        col.operator("object.datalayout_transfer", text="Generate Data Layers")

        col.separator()

        col.use_property_split = False
        col.prop(md, "use_vert_data")
        if md.use_vert_data:
            row = col.row(align=True)
            row.prop(md, "data_types_verts", expand=True)
            col.use_property_split = True
            col.prop(md, "vert_mapping")

            pref = bpy.context.scene.hops.modifiers
            col.prop(pref, "datatransfer_vgroups", text="Vertex Groups")
            if pref.datatransfer_vgroups:

                col.prop(md, "layers_vgroup_select_src", text="Layer Selection")
                col.prop(md, "layers_vgroup_select_dst", text="Layer Mapping")

        col.use_property_split = False
        col.prop(md, "use_edge_data")
        if md.use_edge_data:
            row = col.row(align=True)
            row.prop(md, "data_types_edges", expand=True)
            col.use_property_split = True
            col.prop(md, "edge_mapping")

        col.use_property_split = False
        col.prop(md, "use_loop_data")
        if md.use_loop_data:
            row = col.row(align=True)
            row.prop(md, "data_types_loops", expand=True)
            col.use_property_split = True
            col.prop(md, "loop_mapping", text="Mapping")

            pref = bpy.context.scene.hops.modifiers
            col.prop(pref, "datatransfer_vcolors", text="Vertex Colors")
            if pref.datatransfer_vcolors:
                col.prop(md, "layers_vcol_select_src", text="Layer Selection")
                col.prop(md, "layers_vcol_select_dst", text="Layer Mapping")

            pref = bpy.context.scene.hops.modifiers
            col.prop(pref, "datatransfer_uv", text="UVs")
            if pref.datatransfer_uv:
                col.prop(md, "layers_uv_select_src", text="Layer Selection")
                col.prop(md, "layers_uv_select_dst", text="Layer Mapping")
                col.prop(md, "islands_precision")

        col.use_property_split = False
        col.prop(md, "use_poly_data")
        if md.use_poly_data:
            row = col.row(align=True)
            row.prop(md, "data_types_polys", expand=True)
            col.use_property_split = True
            col.prop(md, "poly_mapping")

        col.use_property_split = False
        pref = bpy.context.scene.hops.modifiers
        col.prop(pref, "datatransfer_toplogy", text="Topology Mapping")
        if pref.datatransfer_toplogy:
            col.use_property_split = True
            row = col.row(align=True, heading='Max Distance')
            row.prop(md, "use_max_distance", text="")
            row.prop(md, "max_distance",  text="")
            col.prop(md, "ray_radius")

    def NORMAL_EDIT(self, layout, ob, md):
        has_vgroup = bool(md.vertex_group)
        do_polynors_fix = not md.no_polynors_fix

        layout.use_property_split = True
        col = layout.column()
        col.separator()

        row = col.row()
        row.prop(md, "mode", expand=True)
        col.prop(md, "target")

        row = col.row(align=True)
        row.active = (md.mode == 'DIRECTIONAL')
        row.prop(md, "use_direction_parallel")

        col.use_property_split = False
        pref = bpy.context.scene.hops.modifiers
        col.prop(pref, "normaledit_mix", text="Mix")
        if pref.normaledit_mix:
            col.use_property_split = True

            col.prop(md, "mix_mode")
            col.prop(md, "mix_factor")
            row = col.row()
            row.prop_search(md, "vertex_group", ob, "vertex_groups", text="Vertex Group")
            sub = row.row(align=True)
            sub.active = has_vgroup
            sub.prop(md, "invert_vertex_group", text="", icon='ARROW_LEFTRIGHT')
            row = col.row(align=True)
            row.prop(md, "mix_limit")
            row.prop(md, "no_polynors_fix", text="", icon='UNLOCKED' if do_polynors_fix else 'LOCKED')

        col.use_property_split = False
        pref = bpy.context.scene.hops.modifiers
        col.prop(pref, "normaledit_offset", text="Offset")
        if pref.normaledit_offset:
            col.use_property_split = True
            col.prop(md, "offset")

    def CORRECTIVE_SMOOTH(self, layout, ob, md):
        is_bind = md.is_bind

        layout.use_property_split = True
        col = layout.column()
        col.separator()

        col.prop(md, "factor", text="Factor")
        col.prop(md, "iterations")
        col.prop(md, "scale")
        col.prop(md, "smooth_type")

        row = col.row()
        row.prop_search(md, "vertex_group", ob, "vertex_groups", text="Vertex Group")
        row.prop(md, "invert_vertex_group", text="", icon='ARROW_LEFTRIGHT')

        col.prop(md, "use_only_smooth")
        col.prop(md, "use_pin_boundary")

        col.prop(md, "rest_source")
        if md.rest_source == 'BIND':
            col.operator("object.correctivesmooth_bind", text="Unbind" if is_bind else "Bind").modifier = md.name

    def WEIGHTED_NORMAL(self, layout, ob, md):
        layout.use_property_split = True
        col = layout.column()
        col.separator()

        col.prop(md, "mode")
        col.prop(md, "weight")
        col.prop(md, "thresh", text="Threshold")
        col.prop(md, "keep_sharp")
        if hasattr(md, "face_influence"):
            col.prop(md, "face_influence")
        elif hasattr(md, "use_face_influence"):
            col.prop(md, "use_face_influence")

        row = col.row()
        row.prop_search(md, "vertex_group", ob, "vertex_groups", text="Vertex Group")
        row.active = bool(md.vertex_group)
        row.prop(md, "invert_vertex_group", text="", icon='ARROW_LEFTRIGHT')


class HOPS_MT_helper_node_attr_search(bpy.types.Menu):
    bl_label = 'Attribute Search'
    bl_idname = "HOPS_MT_helper_attr_search"

    modifier_name: str
    attribute_name: str # socket identifier
    is_output: bool

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

        layout.row().label(text='Builtin')
        for str in ('shade_smooth', 'crease', 'position', 'material_index'):
            row = layout.row()
            self.draw_operator(row, str)

        layout.separator()

        obj = context.object

        layout.row().label(text='UV')
        for uv in obj.data.uv_layers:
            row = layout.row()
            self.draw_operator(row, uv.name)


        layout.row().label(text='Custom')
        for uv in obj.data.uv_layers:
            row = layout.row()
            self.draw_operator(row, uv.name)

    def draw_operator(self, layout, attribute):
        op = layout.operator(HOPS_OT_helper_node_attr_set.bl_idname, text=attribute)
        op.modifier = self.__class__.modifier_name
        op.attribute = self.__class__.attribute_name
        op.is_output = getattr(self.__class__, 'is_output', False)
        op.value = attribute

class HOPS_OT_helper_node_attr_set(bpy.types.Operator):
    bl_label = ''
    bl_idname = 'hops.helper_node_attr_set'

    modifier: bpy.props.StringProperty()
    attribute: bpy.props.StringProperty() # socket identifier
    value: bpy.props.StringProperty()
    is_output: bpy.props.BoolProperty()

    def execute(self, context):
        obj = bpy.context.object
        mod = obj.modifiers[self.modifier]

        if self.is_output:
            compat.set_output_attribute_name(mod, self.attribute, self.value)

        else:
            compat.set_attribute_name(mod, self.attribute, self.value)

        for area in context.screen.areas:
            if area.type == 'PROPERTIES':
                area.tag_redraw()

        obj.data.update()

        return {'FINISHED'}