OverviewFilesCommitsIssuesDocsReleasesPeers

hardops / utility / ct_modifier.py

483 lines · python ref:
import bpy
from ctypes import Structure, cast, POINTER, addressof, c_int, c_void_p, c_float, c_short
import enum, sys

class Link(Structure):
    pass

Link._fields_ = [
    ('next', POINTER(Link)),
    ('prev', POINTER(Link)),
]

class ListBase(Structure):
    pass

ListBase._fields_ = [
    ('first', POINTER(None)),
    ('last', POINTER(None)),
]

class ModifierData(Structure):
    pass

# Modifier header bidnings DNA_modifier_types.h
# Verified with 4.4
# Relevant data is compatible with down to 2.9
ModifierData._fields_ = [
    ('next', POINTER(ModifierData)),
    ('prev', POINTER(ModifierData)),
    ('type', c_int),
    ('mode', c_int),# irrelevant
    ('execution_time', c_float), # irrelevant; 4 bytes padding pre 3.5
    ('flag', c_short),
]

# use_pin_to_last flag bit
eModifierFlag_PinLast = 1 << 4


class MoveError(enum.Enum):
    IS_FIRST = 0
    IS_LAST = 1
    AT_INDEX = 2
    MOVE_BELOW_NON_DEFORM = 2 # mods requiers original data (eg. Multires) cannot be placed below destructive mods
    MOVE_ABOVE_REQ_ORIGINAL = 3 # mods is destructive and cannot be placed above mod requiring original data (e.g. Multires)
    MOVE_BELOW_eModifierFlag_PinLast = 4
    MOVE_eModifierFlag_PinLast = 5

def mod_move_down(mod: bpy.types.Modifier, ignore_pin=False, force=False) -> MoveError:
    '''Move modifier down without triggering modifier update\n
    If move failed returns MoveError, otherwise None\n
    ignore_pin - specifies if use_pin_to_last is respected; pin is removed in case of move\n
    force - moves without ensuring stack validity
    '''

    pt_mod_list = unsafe_mod_list_get(mod.id_data)
    pt_mod = cast(mod.as_pointer(), POINTER(ModifierData))

    if pt_mod.contents.next:
        if not force:
            if get_behavior(pt_mod.contents.type)[1]:
                n_type = pt_mod.contents.next.contents.type

                if behavior.Other == get_behavior(n_type)[0]:
                    return MoveError.MOVE_BELOW_NON_DEFORM

            # check if mismatching pin bit
            # move is legal when pins are matching
            mod_pin = pt_mod.contents.flag & eModifierFlag_PinLast
            next_pin = pt_mod.contents.next.contents.flag & eModifierFlag_PinLast
            if mod_pin != next_pin:
                if ignore_pin:
                    # in valid stack next has the pin
                    pt_mod.contents.next.contents.flag ^= next_pin # clear pin
                else:
                    return MoveError.MOVE_BELOW_eModifierFlag_PinLast

        unsafe_move_next(pt_mod, pt_mod_list)
        return

    return MoveError.IS_LAST


def mod_move_up (mod: bpy.types.Modifier, ignore_pin=False, force=False) -> MoveError:
    '''Move modifier up without triggering modifier update\n
    If move failed returns MoveError, otherwise None\n
    ignore_pin - specifies if use_pin_to_last is respected; pin is removed in case of move\n
    force - moves without ensuring stack validity
    '''

    pt_mod_list = unsafe_mod_list_get(mod.id_data)
    pt_mod = cast(mod.as_pointer(), POINTER(ModifierData))

    if pt_mod.contents.prev:
        if not force:
            if behavior.Other == get_behavior(pt_mod.contents.type)[0]:
                p_type = pt_mod.contents.prev.contents.type

                if get_behavior(p_type)[1]:
                    return MoveError.MOVE_ABOVE_REQ_ORIGINAL

            # check if mismatching pin bit
            # move is legal when pins are matching
            mod_pin = pt_mod.contents.flag & eModifierFlag_PinLast
            prev_pin = pt_mod.contents.prev.contents.flag & eModifierFlag_PinLast
            if mod_pin != prev_pin:
                if ignore_pin:
                    # in valid stack mod has the pin
                    pt_mod.contents.flag ^= mod_pin # clear pin
                else:
                    return MoveError.MOVE_BELOW_eModifierFlag_PinLast

        unsafe_move_prev(pt_mod, pt_mod_list)
        return

    return MoveError.IS_FIRST

def mod_move_to_index(mod: bpy.types.Modifier, index: int, ignore_pin=False, force=False) -> MoveError:
    '''Move modifier to specified index without triggering modifier update.\n
    Negative indices are treated as move to last.
    If move failed returns MoveError, otherwise None.
    ignore_pin - specifies if use_pin_to_last is respected; pin is removed in case of move\n
    force - moves without ensuring stack validity
    '''

    m_index = mod.id_data.modifiers.find(mod.name)
    if m_index == index : return MoveError.AT_INDEX
    if index < 0: index = sys.maxsize

    if m_index > index:
        while m_index > index:
            m_index -= 1
            res = mod_move_up(mod, ignore_pin=ignore_pin, force=force)
            if res:
                return res

    else:
        while m_index < index:
            m_index += 1
            res = mod_move_down(mod, ignore_pin=ignore_pin, force=force)
            if res:
                return res

def mod_move_last(mod: bpy.types.Modifier, ignore_pin=False, force=False) -> MoveError:
    '''Move modifier to the bottom of the stack without triggering modifier update.\n
    If move failed returns MoveError, otherwise None.
    ignore_pin - specifies if use_pin_to_last is respected; pin is removed in case of move\n
    force - moves without ensuring stack validity
    '''
    while True:
        res = mod_move_down(mod, ignore_pin=ignore_pin, force=force)
        if res:
            return res

def mod_move_first(mod: bpy.types.Modifier, ignore_pin=False, force=False) -> MoveError:
    '''Move modifier to the top of the stack without triggering modifier update.\n
    If move failed returns MoveError, otherwise None.
    ignore_pin - specifies if use_pin_to_last is respected; pin is removed in case of move\n
    force - moves without ensuring stack validity
    '''
    while True:
        res = mod_move_up(mod, ignore_pin=ignore_pin, force=force)
        if res:
            return res

def mod_swap (mod1: bpy.types.Modifier, mod2: bpy.types.Modifier):
    '''Swap modifier positions and their pins without triggering modifier update\n
    Does not ensure stack validity\n
    '''
    if mod1.id_data != mod2.id_data: raise Exception('Modifiers must belong to same object')

    pt_mod_list = unsafe_mod_list_get(mod1.id_data)
    pt_mod1 = cast(mod1.as_pointer(), POINTER(ModifierData))
    pt_mod2 = cast(mod2.as_pointer(), POINTER(ModifierData))

    mod1_pin = pt_mod1.contents.flag & eModifierFlag_PinLast
    mod2_pin = pt_mod2.contents.flag & eModifierFlag_PinLast

    # swap pins
    pt_mod1.contents.flag ^= mod1_pin
    pt_mod1.contents.flag |= mod2_pin

    pt_mod2.contents.flag ^= mod2_pin
    pt_mod2.contents.flag |= mod1_pin

    unsafe_swap(pt_mod1, pt_mod2, pt_mod_list)

def set_pin(mod:bpy.types.Modifier, val):
    '''Set use_pin_to_last without triggering stack update'''
    pt_mod = cast(mod.as_pointer(), POINTER(ModifierData))

    mod_pin = pt_mod.contents.flag & eModifierFlag_PinLast
    pt_mod.contents.flag ^= mod_pin
    pt_mod.contents.flag |= int(bool(val)) * eModifierFlag_PinLast

def update_object(obj: bpy.types.Object, context, update_ui=True):
    '''Update object geometery after modifier move\n
    update_ui - specifies if UI (Properties, N panel) should be updated
    '''
    obj.update_tag()
    obj.modifiers.update()

    if update_ui:
        for area in context.screen.areas:
            area.tag_redraw()

def unsafe_move_next(pt_link: Link, pt_list: ListBase):
    '''Move link to next position. Assumes link is in the list, not null and can be moved\n
    All arguments are pointer objects!'''

    link = pt_link.contents
    next = link.next.contents
    list = pt_list.contents

    if not next.next:
        list.last = addressof(link)

    else:
        next.next.contents.prev = pt_link

    if not link.prev:
        list.first = addressof(next)
    else:
        link.prev.contents.next.contents = next

    link.next = next.next
    next.prev = link.prev

    link.prev.contents = next
    next.next = pt_link

def unsafe_move_prev(pt_link: Link, pt_list: ListBase):
    '''Move link to previous position. Assumes link is in the list, not null and can be moved\n
    All arguments are pointer objects!'''

    link = pt_link.contents
    prev = link.prev.contents
    list = pt_list.contents

    if not prev.prev:
        list.first = addressof(link)

    else:
        prev.prev.contents.next.contents = link

    if not link.next:
        list.last = addressof(prev)
    else:
        link.next.contents.prev.contents = prev

    link.prev = prev.prev
    prev.next = link.next

    link.next.contents = prev
    prev.prev = pt_link

def unsafe_move_before(pt_link: Link, pt_target: Link, pt_list: ListBase):
    '''Move Link before position of the target, moving target forward\n
    Assumes both links are in the list, not null and are not the same link!\n
    All arguments are pointer objects!'''

    link = pt_link.contents
    target = pt_target.contents
    list = pt_list.contents

    if link.next:
        link.next.contents.prev = link.prev

    else:
        list.last = cast(link.prev, c_void_p)

    if link.prev:
        link.prev.contents.next = link.next

    else:
        list.first = cast(link.next, c_void_p)

    if target.prev:
        target.prev.contents.next = pt_link

    else:
        list.first = cast(pt_link, c_void_p)

    link.prev = target.prev
    link.next = pt_target
    target.prev = pt_link

def unsafe_move_after(pt_link: Link, pt_target: Link, pt_list: ListBase):
    '''Move Link after position of the target, moving target forward\n
    Assumes both links are in the list, not null and are not the same link!\n
    All arguments are pointer objects!'''

    link = pt_link.contents
    target = pt_target.contents
    list = pt_list.contents

    if link.next:
        link.next.contents.prev = link.prev

    else:
        list.last = cast(link.prev, c_void_p)

    if link.prev:
        link.prev.contents.next = link.next

    else:
        list.first = cast(link.next, c_void_p)

    if target.next:
        target.next.contents.prev = pt_link

    else:
        list.last = cast(pt_link, c_void_p)

    link.next = target.next
    link.prev = pt_target
    target.next = pt_link

def unsafe_swap(pt_link_a: Link, pt_link_b: Link, pt_list: ListBase):
    '''Swap two list links. Assumes both links are in the list and neither is null\n
    All arguments are pointer objects!'''

    pt_link_a = cast(pt_link_a, POINTER(Link))
    pt_link_b = cast(pt_link_b, POINTER(Link))
    link_a = pt_link_a.contents
    link_b = pt_link_b.contents
    list = pt_list.contents

    link_a_copy = Link(link_a.next, link_a.prev)

    if cast(link_a.next, c_void_p).value == cast(pt_link_b, c_void_p).value:
        link_a.next = link_b.next
        link_a.prev = pt_link_b

        link_b.next = pt_link_a
        link_b.prev = link_a_copy.prev

    elif cast(link_a.prev, c_void_p).value == cast(pt_link_b, c_void_p).value:
        link_a.next = pt_link_b
        link_a.prev = link_b.prev

        link_b.next = link_a_copy.next
        link_b.prev = pt_link_a

    else:
        link_a.prev = link_b.prev
        link_a.next = link_b.next

        link_b.prev = link_a_copy.prev
        link_b.next = link_a_copy.next

    # update neighbours
    if link_a.next:
        link_a.next.contents.prev.contents = link_a

    else:
        list.last = addressof(link_a)

    if link_a.prev:
        link_a.prev.contents.next.contents = link_a

    else:
        list.first = addressof(link_a)

    if link_b.next:
        link_b.next.contents.prev.contents = link_b

    else:
        list.last = addressof(link_b)

    if link_b.prev:
        link_b.prev.contents.next.contents = link_b

    else:
        list.first = addressof(link_b)


MOD_OFFSET = 0
def unsafe_mod_list_get(obj: bpy.types.Object) -> ListBase:
    '''Get pointer to ListBase struct from the object. Object must have at least 1 modifier.'''

    global MOD_OFFSET
    object_ptr = obj.as_pointer()

    if MOD_OFFSET:
        return cast(object_ptr + MOD_OFFSET, POINTER(ListBase))

    # poke object memory to find byteoffset from object pointer to modifier list, which is just pointers to frst and last mod

    current_ptr = object_ptr
    fmod_ptr = obj.modifiers[0].as_pointer()
    lmod_ptr = obj.modifiers[-1].as_pointer()
    mod_list = None

    while True:
        lst = cast(current_ptr, POINTER(ListBase))

        if lst.contents.first == fmod_ptr and lst.contents.last == lmod_ptr:
            mod_list = lst
            MOD_OFFSET = current_ptr - object_ptr
            break

        current_ptr += 1

    return mod_list

class behavior(enum.Enum):
    Other = 0
    OnlyDeform = 1


def get_behavior(mod_type: int):
    if mod_type > len(MODIFIER_TYPES) - 1 or mod_type < 0:
        return (behavior.Other, False)

    return MODIFIER_TYPES[mod_type]


MODIFIER_TYPES = [
    (behavior.Other,  False)         , #ModifierType_eModifierType_None: ModifierType = 0;
    (behavior.Other,  False)         , #ModifierType_eModifierType_Subsurf: ModifierType = 1;
    (behavior.OnlyDeform,  False)         , #ModifierType_eModifierType_Lattice: ModifierType = 2;
    (behavior.OnlyDeform,  False)         , #ModifierType_eModifierType_Curve: ModifierType = 3;
    (behavior.Other,  False)         , #ModifierType_eModifierType_Build: ModifierType = 4;
    (behavior.Other,  False)         , #ModifierType_eModifierType_Mirror: ModifierType = 5;
    (behavior.Other,  False)         , #ModifierType_eModifierType_Decimate: ModifierType = 6;
    (behavior.OnlyDeform,  False)         , #ModifierType_eModifierType_Wave: ModifierType = 7;
    (behavior.OnlyDeform,  False)         , #ModifierType_eModifierType_Armature: ModifierType = 8;
    (behavior.OnlyDeform,  False)         , #ModifierType_eModifierType_Hook: ModifierType = 9;
    (behavior.OnlyDeform,  True)         , #ModifierType_eModifierType_Softbody: ModifierType = 10;
    (behavior.Other,  False)         , #ModifierType_eModifierType_Boolean: ModifierType = 11;
    (behavior.Other,  False)         , #ModifierType_eModifierType_Array: ModifierType = 12;
    (behavior.Other,  False)         , #ModifierType_eModifierType_EdgeSplit: ModifierType = 13;
    (behavior.OnlyDeform,  False)         , #ModifierType_eModifierType_Displace: ModifierType = 14;
    (behavior.Other,  False)         , #ModifierType_eModifierType_UVProject: ModifierType = 15;
    (behavior.Other,  False)         , #ModifierType_eModifierType_Smooth: ModifierType = 16;
    (behavior.OnlyDeform,  False)         , #ModifierType_eModifierType_Cast: ModifierType = 17;
    (behavior.OnlyDeform,  False)         , #ModifierType_eModifierType_MeshDeform: ModifierType = 18;
    (behavior.Other,  False)         , #ModifierType_eModifierType_ParticleSystem: ModifierType = 19;
    (behavior.OnlyDeform,  False)         , #ModifierType_eModifierType_ParticleInstance: ModifierType = 20;
    (behavior.Other,  False)         , #ModifierType_eModifierType_Explode: ModifierType = 21;
    (behavior.OnlyDeform,  False)         , #ModifierType_eModifierType_Cloth: ModifierType = 22;
    (behavior.OnlyDeform,  False)         , #ModifierType_eModifierType_Collision: ModifierType = 23;
    (behavior.Other,  False)         , #ModifierType_eModifierType_Bevel: ModifierType = 24;
    (behavior.OnlyDeform,  False)         , #ModifierType_eModifierType_Shrinkwrap: ModifierType = 25;
    (behavior.Other,  False)         , #ModifierType_eModifierType_Fluidsim: ModifierType = 26; ?there us noe .c file for it
    (behavior.Other,  False)         , #ModifierType_eModifierType_Mask: ModifierType = 27;
    (behavior.Other,  False)         , #ModifierType_eModifierType_SimpleDeform: ModifierType = 28;
    (behavior.Other,  True)          , #ModifierType_eModifierType_Multires: ModifierType = 29;
    (behavior.OnlyDeform,  False)         , #ModifierType_eModifierType_Surface: ModifierType = 30;
    (behavior.Other,  False)         , #ModifierType_eModifierType_Smoke: ModifierType = 31; DEPRECATED! for array padding only
    (behavior.OnlyDeform,  False)         , #ModifierType_eModifierType_ShapeKey: ModifierType = 32;
    (behavior.Other,  False)         , #ModifierType_eModifierType_Solidify: ModifierType = 33;
    (behavior.Other,  False)         , #ModifierType_eModifierType_Screw: ModifierType = 34;
    (behavior.OnlyDeform,  False)         , #ModifierType_eModifierType_Warp: ModifierType = 35;
    (behavior.Other,  False)         , #ModifierType_eModifierType_WeightVGEdit: ModifierType = 36;
    (behavior.Other,  False)         , #ModifierType_eModifierType_WeightVGMix: ModifierType = 37;
    (behavior.Other,  False)         , #ModifierType_eModifierType_WeightVGProximity: ModifierType = 38;
    (behavior.Other,  False)         , #ModifierType_eModifierType_Ocean: ModifierType = 39;
    (behavior.Other,  False)         , #ModifierType_eModifierType_DynamicPaint: ModifierType = 40;
    (behavior.Other,  False)         , #ModifierType_eModifierType_Remesh: ModifierType = 41;
    (behavior.Other,  False)         , #ModifierType_eModifierType_Skin: ModifierType = 42;
    (behavior.OnlyDeform,  False)         , #ModifierType_eModifierType_LaplacianSmooth: ModifierType = 43;
    (behavior.Other,  False)         , #ModifierType_eModifierType_Triangulate: ModifierType = 44;
    (behavior.Other,  False)         , #ModifierType_eModifierType_UVWarp: ModifierType = 45;
    (behavior.OnlyDeform,  False)         , #ModifierType_eModifierType_MeshCache: ModifierType = 46;
    (behavior.OnlyDeform,  False)         , #ModifierType_eModifierType_LaplacianDeform: ModifierType = 47;
    (behavior.Other,  False)         , #ModifierType_eModifierType_Wireframe: ModifierType = 48;
    (behavior.Other,  False)         , #ModifierType_eModifierType_DataTransfer: ModifierType = 49;
    (behavior.Other,  False)         , #ModifierType_eModifierType_NormalEdit: ModifierType = 50;
    (behavior.OnlyDeform,  False)         , #ModifierType_eModifierType_CorrectiveSmooth: ModifierType = 51;
    (behavior.Other,  False)         , #ModifierType_eModifierType_MeshSequenceCache: ModifierType = 52;
    (behavior.OnlyDeform,  False)         , #ModifierType_eModifierType_SurfaceDeform: ModifierType = 53;
    (behavior.Other,  False)         , #ModifierType_eModifierType_WeightedNormal: ModifierType = 54;
    (behavior.Other,  False)         , #ModifierType_eModifierType_Weld: ModifierType = 55;
    (behavior.Other,  False)         , #ModifierType_eModifierType_Fluid: ModifierType = 56;
    (behavior.Other,  False)         , #ModifierType_eModifierType_links: ModifierType = 57;
    (behavior.Other,  False)         , #ModifierType_eModifierType_MeshToVolume: ModifierType = 58;
    (behavior.Other,  False)         , #ModifierType_eModifierType_VolumeDisplace: ModifierType = 59;
    (behavior.Other,  False)         , #ModifierType_eModifierType_VolumeToMesh: ModifierType = 60;
    (behavior.Other,  False)         , #ModifierType_NUM_MODIFIER_TYPES: ModifierType = 61;
]