Module sverchok.data_structure

Functions

def Edg_pol_generate(prop)
Expand source code
def Edg_pol_generate(prop):
    edg_pol_out = []
    if len(prop[0][0]) == 2:
        e_type = 'edg'
    elif len(prop[0]) > 2:
        e_type = 'pol'
    for ob in prop:
        list_out = []
        for p in ob:
            list_out.append(p)
        edg_pol_out.append(list_out)
    # [ [(n1,n2,n3), (n1,n7,n9), p, p, p, p...], [...],... ] n = vertexindex
    return e_type, edg_pol_out
def Matrix_generate(prop)
Expand source code
def Matrix_generate(prop):
    """Generate Matrix() data from Sverchok data"""
    mat_out = []
    for matrix in prop:
        unit = Matrix()
        for k, m in enumerate(matrix):
            # [Matrix0, Matrix1, ... ]
            unit[k] = Vector(m)
        mat_out.append(unit)
    return mat_out

Generate Matrix() data from Sverchok data

def Matrix_listing(prop)
Expand source code
def Matrix_listing(prop):
    """Convert Matrix() into Sverchok data"""
    mat_out = []
    for matrix in prop:
        unit = []
        for m in matrix:
            # [Matrix0, Matrix1, ... ]
            unit.append(m[:])
        mat_out.append((unit))
    return mat_out

Convert Matrix() into Sverchok data

def Matrix_location(prop, to_list=False)
Expand source code
def Matrix_location(prop, to_list=False):
    """return a list of locations representing the translation of the matrices"""
    Vectors = []
    for p in prop:
        if to_list:
            Vectors.append(p.translation[:])
        else:
            Vectors.append(p.translation)
    return [Vectors]

return a list of locations representing the translation of the matrices

def Matrix_rotation(prop, to_list=False)
Expand source code
def Matrix_rotation(prop, to_list=False):
    """return (Vector, rotation) utility function for Matrix Destructor.
    if list is true the Vector() is decomposed into tuple format.
    """
    Vectors = []
    for p in prop:
        q = p.to_quaternion()
        if to_list:
            vec, angle = q.to_axis_angle()
            Vectors.append((vec[:], angle))
        else:
            Vectors.append(q.to_axis_angle())
    return [Vectors]

return (Vector, rotation) utility function for Matrix Destructor. if list is true the Vector() is decomposed into tuple format.

def Matrix_scale(prop, to_list=False)
Expand source code
def Matrix_scale(prop, to_list=False):
    """return a Vector()/list representing the scale factor of the matrices"""
    Vectors = []
    for p in prop:
        if to_list:
            Vectors.append(p.to_scale()[:])
        else:
            Vectors.append(p.to_scale())
    return [Vectors]

return a Vector()/list representing the scale factor of the matrices

def Vector_degenerate(prop)
Expand source code
def Vector_degenerate(prop):
    """return a simple list of values instead of Vector() objects"""
    return [[v[0:3] for v in obj] for obj in prop]

return a simple list of values instead of Vector() objects

def Vector_generate(prop)
Expand source code
def Vector_generate(prop):
    """return a list of Vector() objects from a standard Sverchok data"""
    return [[Vector(v) for v in obj] for obj in prop]

return a list of Vector() objects from a standard Sverchok data

def apply_mask(mask, lst)
Expand source code
def apply_mask(mask, lst):
    good, bad = [], []
    for m, item in zip(mask, lst):
        if m:
            good.append(item)
        else:
            bad.append(item)
    return good, bad
def calc_mask(subset_data, set_data, level=0, negate=False, ignore_order=True)
Expand source code
def calc_mask(subset_data, set_data, level=0, negate=False, ignore_order=True):
    """
    Calculate mask: for each item in set_data, return True if it is present in subset_data.
    The function can work at any specified level.

    subset_data: subset, for example [1]
    set_data: set, for example [1, 2, 3]
    level: 0 to check immediate members of set and subset; 1 to work with lists of lists and so on.
    negate: if True, then result will be negated (True if item of set is not present in subset).
    ignore_order: when comparing lists, ignore items order.

    Raises an exception if nesting level of input sets is less than specified level parameter.

    calc_mask([1], [1,2,3]) == [True, False, False])
    calc_mask([1], [1,2,3], negate=True) == [False, True, True]
    """
    if level == 0:
        if not isinstance(subset_data, (tuple, list)):
            raise Exception("Specified level is too high for given Subset")
        if not isinstance(set_data, (tuple, list)):
            raise Exception("Specified level is too high for given Set")

        if ignore_order and get_data_nesting_level(subset_data) > 1:
            if negate:
                return [set(item) not in map(set, subset_data) for item in set_data]
            else:
                return [set(item) in map(set, subset_data) for item in set_data]
        else:
            if negate:
                return [item not in subset_data for item in set_data]
            else:
                return [item in subset_data for item in set_data]
    else:
        sub_objects = match_long_repeat([subset_data, set_data])
        return [calc_mask(subset_item, set_item, level - 1, negate, ignore_order) for subset_item, set_item in zip(*sub_objects)]

Calculate mask: for each item in set_data, return True if it is present in subset_data. The function can work at any specified level.

subset_data: subset, for example [1] set_data: set, for example [1, 2, 3] level: 0 to check immediate members of set and subset; 1 to work with lists of lists and so on. negate: if True, then result will be negated (True if item of set is not present in subset). ignore_order: when comparing lists, ignore items order.

Raises an exception if nesting level of input sets is less than specified level parameter.

calc_mask([1], [1,2,3]) == [True, False, False]) calc_mask([1], [1,2,3], negate=True) == [False, True, True]

def changable_sockets(node, inputsocketname, outputsocketname)
Expand source code
def changable_sockets(node, inputsocketname, outputsocketname):
    """
    It changes types of output sockets according to type of socket (other)
    connected to given input socket name
    !!! It does not work if the node have outputs with the same names !!!
    If input socket is not connected or its type is equal to type of
    first output socket it does nothing
    arguments: node, name of socket to follow, list of socket to change
    """
    if not inputsocketname in node.inputs:
        # - node not initialized in sv_init yet,
        # - or socketname incorrect
        sv_logger.debug(f"changable_socket was called on {node.name} with a socket named {inputsocketname}, this socket does not exist")
        return

    in_socket = node.inputs[inputsocketname]
    ng = node.id_data
    if in_socket.links:
        in_other = get_other_socket(in_socket)
        if not in_other:
            return
        outputs = node.outputs
        s_type = in_other.bl_idname
        if s_type == 'SvDummySocket':
            return #
        if outputs[outputsocketname[0]].bl_idname != s_type:
            to_links = {}
            idx = {}
            # gather info
            for n in outputsocketname:
                out_socket = outputs[n]
                idx[n] = out_socket.index
                to_links[n] = [l.to_socket for l in out_socket.links]
            # add sockets and copy options
            for n in outputsocketname:
                out_socket = outputs[n]
                new_out_socket = outputs.new(s_type, n)
                new_out_socket.copy_options(out_socket)
            # remove sockets
            for n in outputsocketname[::-1]:
                old_sock_ind = idx[n]
                outputs.remove(outputs[old_sock_ind])
            # place sockets and return links
            for i, n in enumerate(outputsocketname):
                static_num = len(outputs) - len(outputsocketname)
                new_sock_ind = static_num + i
                outputs.move(new_sock_ind, idx[n])
                for to_socket in to_links[n]:
                    ng.links.new(to_socket, outputs[n])

It changes types of output sockets according to type of socket (other) connected to given input socket name !!! It does not work if the node have outputs with the same names !!! If input socket is not connected or its type is equal to type of first output socket it does nothing arguments: node, name of socket to follow, list of socket to change

def cross_indices_np(n)
Expand source code
def cross_indices_np(n):
    '''
    create list with all the indices pairs
    for n=3 outputs a numpy array with:
        [0,1]
        [0,2]
        [1,2]

    '''

    nu = np.sum(np.arange(n, dtype=np.int64))
    ind = np.zeros((nu, 2), dtype=np.int16)
    c = 0
    for i in range(n-1):
        l = n-i-1
        np_i = np.full(n-i-1, i, dtype=np.int32)
        np_j = np.arange(i+1, n, dtype=np.int32)
        np_a = np.stack((np_i, np_j), axis=-1)
        ind[c:c+l, :] = np_a
        c += l

    return ind

create list with all the indices pairs for n=3 outputs a numpy array with: [0,1] [0,2] [1,2]

def cycle_for_length(lst, count)
Expand source code
def cycle_for_length(lst, count):
    return list(islice(cycle(lst), count))
def dataCorrect(data, nominal_dept=2)
Expand source code
def dataCorrect(data, nominal_dept=2):
    """data from nesting to standard: TO container( objects( lists( floats, ), ), )
    """
    dept = levelsOflist(data)
    output = []
    if not dept: # for empty lists
        return []
    if dept < 2:
        return data #[dept, data]
    else:
        output = data_standard(data, dept, nominal_dept)
        return output

data from nesting to standard: TO container( objects( lists( floats, ), ), )

def dataCorrect_np(data, nominal_dept=2)
Expand source code
def dataCorrect_np(data, nominal_dept=2):
    """data from nesting to standard: TO container( objects( lists( floats, ), ), )
    """
    dept = levels_of_list_or_np(data)
    output = []
    if not dept: # for empty lists
        return []
    if dept < 2:
        return data #[dept, data]
    else:
        output = data_standard(data, dept, nominal_dept)
        return output

data from nesting to standard: TO container( objects( lists( floats, ), ), )

def dataSpoil(data, dept)
Expand source code
def dataSpoil(data, dept):
    """from standard data to initial levels: to nested lists
     container( objects( lists( nested_lists( floats, ), ), ), ) это невозможно!
    """
    __doc__ = 'preparing and making spoil'

    def Spoil(dat, dep):
        __doc__ = 'making spoil'
        out = []
        if dep:
            for d in dat:
                out.append([Spoil(d, dep-1)])
        else:
            out = dat
        return out
    lol = levelsOflist(data)
    if dept > lol:
        out = Spoil(data, dept-lol)
    else:
        out = data
    return out

from standard data to initial levels: to nested lists container( objects( lists( nested_lists( floats, ), ), ), ) это невозможно!

def data_standard(data, dept, nominal_dept)
Expand source code
def data_standard(data, dept, nominal_dept):
    """data from nesting to standard: TO container( objects( lists( floats, ), ), )"""
    deptl = dept - 1
    output = []
    for object in data:
        if deptl >= nominal_dept:
            output.extend(data_standard(object, deptl, nominal_dept))
        else:
            output.append(data)
            return output
    return output

data from nesting to standard: TO container( objects( lists( floats, ), ), )

def describe_data_shape(data)
Expand source code
def describe_data_shape(data):
    """
    Describe shape of data in human-readable form.
    Returns string.
    Can be used for debugging or for displaying information to user.
    Note: this method inspects only first element of each list/tuple,
    expecting they are all homogeneous (that is usually true in Sverchok).

    describe_data_shape(None) == 'Level 0: NoneType'
    describe_data_shape(1) == 'Level 0: int'
    describe_data_shape([]) == 'Level 1: list [0]'
    describe_data_shape([1]) == 'Level 1: list [1] of int'
    describe_data_shape([[(1,2,3)]]) == 'Level 3: list [1] of list [1] of tuple [3] of int'
    """

    nesting, descriptions = describe_data_shape_by_level(data)
    result = " of ".join(descriptions)
    return "Level {}: {}".format(nesting, result)

Describe shape of data in human-readable form. Returns string. Can be used for debugging or for displaying information to user. Note: this method inspects only first element of each list/tuple, expecting they are all homogeneous (that is usually true in Sverchok).

describe_data_shape(None) == 'Level 0: NoneType' describe_data_shape(1) == 'Level 0: int' describe_data_shape([]) == 'Level 1: list [0]' describe_data_shape([1]) == 'Level 1: list [1] of int' describe_data_shape([[(1,2,3)]]) == 'Level 3: list [1] of list [1] of tuple [3] of int'

def describe_data_shape_by_level(data, include_numpy_nesting=True)
Expand source code
def describe_data_shape_by_level(data, include_numpy_nesting=True):
    """
    Describe shape of data in human-readable form.
    Returns tuple:
    * data nesting level
    * list of descriptions of data shapes at each nesting level
    """
    def helper(data):
        if not isinstance(data, (list, tuple)):
            if isinstance(data, ndarray):
                if include_numpy_nesting:
                    nesting = len(data.shape)
                else:
                    nesting = 0
                return nesting, [type(data).__name__ + " of " + str(data.dtype) + " with shape " + str(data.shape)]
            return 0, [type(data).__name__]
        else:
            result = [f"{type(data).__name__} [{len(data)}]"]
            if len(data) > 0:
                child = data[0]
                child_nesting, child_result = helper(child)
                result = result + child_result
            else:
                child_nesting = 0
            return (child_nesting + 1), result

    nesting, result = helper(data)
    return nesting, result

Describe shape of data in human-readable form. Returns tuple: * data nesting level * list of descriptions of data shapes at each nesting level

def describe_data_structure(data,
data_types=(<class 'float'>, <class 'int'>, <class 'numpy.float64'>, <class 'numpy.int32'>, <class 'numpy.int64'>, <class 'str'>, <class 'Matrix'>))
Expand source code
def describe_data_structure(data, data_types=SIMPLE_DATA_TYPES):
    if isinstance(data, data_types):
        return "*"
    elif isinstance(data, (list, tuple)):
        if isinstance(data[0], data_types):
            return str(len(data)) + "*"
        else:
            rs = []
            for item in data:
                r = describe_data_structure(item, data_types)
                rs.append(str(r))
            rs = str(len(data)) + "[" + ", ".join(rs) + "]"
            return rs
    else:
        raise TypeError(f"Unexpected data type: {type(data)}")
def ensure_min_nesting(data,
target_level,
data_types=(<class 'float'>, <class 'int'>, <class 'numpy.float64'>, <class 'numpy.int32'>, <class 'numpy.int64'>, <class 'str'>, <class 'Matrix'>),
input_name=None)
Expand source code
def ensure_min_nesting(data, target_level, data_types=SIMPLE_DATA_TYPES, input_name=None):
    """
    data: number, or list of numbers, or list of lists, etc.
    target_level: minimum data nesting level required for further processing.
    data_types: list or tuple of types.
    input_name: name of input socket data was taken from. Optional. If specified,
        used for error reporting.

    Wraps data in so many [] as required to achieve target nesting level.
    If data already has too high nesting level the same data will be returned

    ensure_min_nesting(17, 0) == 17
    ensure_min_nesting(17, 1) == [17]
    ensure_min_nesting([17], 1) == [17]
    ensure_min_nesting([17], 2) == [[17]]
    ensure_min_nesting([(1,2,3)], 3) == [[(1,2,3)]]
    ensure_min_nesting([[[17]]], 1) => [[[17]]]
    """

    current_level = get_data_nesting_level(data, data_types)
    if current_level >= target_level:
        return data
    result = data
    for i in range(target_level - current_level):
        result = [result]
    return result

data: number, or list of numbers, or list of lists, etc. target_level: minimum data nesting level required for further processing. data_types: list or tuple of types. input_name: name of input socket data was taken from. Optional. If specified, used for error reporting.

Wraps data in so many [] as required to achieve target nesting level. If data already has too high nesting level the same data will be returned

ensure_min_nesting(17, 0) == 17 ensure_min_nesting(17, 1) == [17] ensure_min_nesting([17], 1) == [17] ensure_min_nesting([17], 2) == [[17]] ensure_min_nesting([(1,2,3)], 3) == [[(1,2,3)]] ensure_min_nesting([[[17]]], 1) => [[[17]]]

def ensure_nesting_level(data,
target_level,
data_types=(<class 'float'>, <class 'int'>, <class 'numpy.float64'>, <class 'numpy.int32'>, <class 'numpy.int64'>, <class 'str'>, <class 'Matrix'>),
input_name=None)
Expand source code
def ensure_nesting_level(data, target_level, data_types=SIMPLE_DATA_TYPES, input_name=None):
    """
    data: number, or list of numbers, or list of lists, etc.
    target_level: data nesting level required for further processing.
    data_types: list or tuple of types.
    input_name: name of input socket data was taken from. Optional. If specified,
        used for error reporting.

    Wraps data in so many [] as required to achieve target nesting level.
    Raises an exception, if data already has too high nesting level.

    ensure_nesting_level(17, 0) == 17
    ensure_nesting_level(17, 1) == [17]
    ensure_nesting_level([17], 1) == [17]
    ensure_nesting_level([17], 2) == [[17]]
    ensure_nesting_level([(1,2,3)], 3) == [[(1,2,3)]]
    ensure_nesting_level([[[17]]], 1) => exception
    """

    current_level = get_data_nesting_level(data, data_types)
    if current_level > target_level:
        if input_name is None:
            raise TypeError("ensure_nesting_level: input data already has nesting level of {}. Required level was {}.".format(current_level, target_level))
        else:
            raise TypeError("Input data in socket {} already has nesting level of {}. Required level was {}.".format(input_name, current_level, target_level))
    result = data
    for i in range(target_level - current_level):
        result = [result]
    return result

data: number, or list of numbers, or list of lists, etc. target_level: data nesting level required for further processing. data_types: list or tuple of types. input_name: name of input socket data was taken from. Optional. If specified, used for error reporting.

Wraps data in so many [] as required to achieve target nesting level. Raises an exception, if data already has too high nesting level.

ensure_nesting_level(17, 0) == 17 ensure_nesting_level(17, 1) == [17] ensure_nesting_level([17], 1) == [17] ensure_nesting_level([17], 2) == [[17]] ensure_nesting_level([(1,2,3)], 3) == [[(1,2,3)]] ensure_nesting_level([[[17]]], 1) => exception

def enum_item(s)
Expand source code
def enum_item(s):
    """return a list usable in enum property from a list with one value"""
    return [(no_space(i), i, "") for i in s]

return a list usable in enum property from a list with one value

def enum_item_4(s)
Expand source code
def enum_item_4(s):
    """return a 4*n list usable in enum property from a list with one value"""
    return [(no_space(n), n, '', i) for i, n in enumerate(s)]

return a 4*n list usable in enum property from a list with one value

def enum_item_5(s, icons)
Expand source code
def enum_item_5(s, icons):
    """return a 4*n list usable in enum property from a list with one value"""
    return [(no_space(n), n, '', icon, i) for i, (n, icon) in enumerate(zip(s, icons))]

return a 4*n list usable in enum property from a list with one value

def extend_blender_class(cls)
Expand source code
def extend_blender_class(cls):
    """
    It is class decorator for adding extra logic into base Blender classes
    Decorated class should have the same name as Blender class
    Take into account that this decorator does not delete anything onto reload event
    """
    bl_class = getattr(bpy.types, cls.__name__)
    for base_cls in chain([cls], cls.__bases__):
        # https://docs.python.org/3/howto/annotations.html#accessing-the-annotations-dict-of-an-object-in-python-3-9-and-older
        # avoiding getting inherited annotations
        if '__annotations__' in base_cls.__dict__:
            for name, prop in base_cls.__dict__['__annotations__'].items():
                setattr(bl_class, name, prop)
        for key in (key for key in dir(base_cls) if not key.startswith('_')):
            setattr(bl_class, key, getattr(base_cls, key))
    return cls

It is class decorator for adding extra logic into base Blender classes Decorated class should have the same name as Blender class Take into account that this decorator does not delete anything onto reload event

def fixed_iter(data, iter_number, fill_value=0)
Expand source code
def fixed_iter(data, iter_number, fill_value=0):
    """
    Creates iterator for given data which will be yielded iter_number times
    If data is shorter then iter_number last element will be cycled
    If data is empty [fill_value] list will be used instead
    """
    last_index = -1
    for i, item in zip(range(iter_number), data):
        yield item
        last_index = i
        fill_value = item

    if last_index + 1 < iter_number:
        for i, item in zip(range(iter_number - (last_index + 1)), cycle([fill_value])):
            yield item

Creates iterator for given data which will be yielded iter_number times If data is shorter then iter_number last element will be cycled If data is empty [fill_value] list will be used instead

def flat_iter(data)
Expand source code
def flat_iter(data):
    """[1, [2, 3, [4]], 5] -> 1, 2, 3, 4, 5 """
    if isinstance(data, str):
        yield data
        return
    try:
        for v in data:
            yield from flat_iter(v)
    except TypeError:
        yield data

[1, [2, 3, [4]], 5] -> 1, 2, 3, 4, 5

def flatten_data(data,
target_level=1,
data_types=(<class 'float'>, <class 'int'>, <class 'numpy.float64'>, <class 'numpy.int32'>, <class 'numpy.int64'>, <class 'str'>, <class 'Matrix'>))
Expand source code
def flatten_data(data, target_level=1, data_types=SIMPLE_DATA_TYPES):
    """
    Reduce nesting level of `data` to `target_level`, by concatenating nested sub-lists.
    Raises an exception if nesting level is already less than `target_level`.
    Refer to data_structure_tests.py for examples.
    """
    current_level = get_data_nesting_level(data, data_types)
    if current_level < target_level:
        raise TypeError(f"Can't flatten data to level {target_level}: data already have level {current_level}")
    elif current_level == target_level:
        return data
    else:
        result = []
        for item in data:
            result.extend(flatten_data(item, target_level=target_level, data_types=data_types))
        return result

Reduce nesting level of data to target_level, by concatenating nested sub-lists. Raises an exception if nesting level is already less than target_level. Refer to data_structure_tests.py for examples.

def fullList(l, count)
Expand source code
def fullList(l, count):
    """extends list l so len is at least count if needed with the
    last element of l"""
    n = len(l)
    if n == count:
        return
    d = count - n
    if d > 0:
        l.extend([l[-1] for a in range(d)])
    return

extends list l so len is at least count if needed with the last element of l

def fullList_deep_copy(l, count)
Expand source code
def fullList_deep_copy(l, count):
    """the same that full list function but
    it have correct work with objects such as lists."""
    d = count - len(l)
    if d > 0:
        l.extend([copy.deepcopy(l[-1]) for _ in range(d)])
    return

the same that full list function but it have correct work with objects such as lists.

def fullList_np(l, count)
Expand source code
def fullList_np(l, count):
    """extends list l so len is at least count if needed with the
    last element of l"""
    n = len(l)
    if n == count:
        return
    d = count - n
    if d > 0:
        try:
            l.extend([l[-1] for a in range(d)])
        except:
            l = numpy_full_list(l, n)
    else:
        l = l[:count]

extends list l so len is at least count if needed with the last element of l

def get_data_nesting_level(data,
data_types=(<class 'float'>, <class 'int'>, <class 'numpy.float64'>, <class 'numpy.int32'>, <class 'numpy.int64'>, <class 'str'>, <class 'Matrix'>),
search_first_data=False)
Expand source code
def get_data_nesting_level(data, data_types=SIMPLE_DATA_TYPES, search_first_data=False):
    """
    data: number, or list of numbers, or list of lists, etc.
    data_types: list or tuple of types.

    Detect nesting level of actual data.
    "Actual" data is detected by belonging to one of data_types.
    This method searches only for first instance of "actual data",
    so it does not support cases when different elements of source
    list have different nesting.
    Returns integer.
    Raises an exception if at some point it encounters element
    which is not a tuple, list, or one of data_types.

    get_data_nesting_level(1) == 0
    get_data_nesting_level([]) == 1
    get_data_nesting_level([1]) == 1
    get_data_nesting_level([[(1,2,3)]]) == 3
    """

    def helper(data, recursion_depth):
        """ Needed only for better error reporting. """
        if isinstance(data, data_types):
            return (0, 0)
        elif isinstance(data, (list, tuple, ndarray)):
            if len(data) == 0:
                return (1, -1)
            else:
                if search_first_data==False:
                    res = helper(data[0], recursion_depth+1)
                else:
                    for I, data_I in enumerate(data):
                        res = helper(data_I, recursion_depth+1)
                        if res[1]==0:
                            return (res[0]+1, res[1] )
                #return helper(data[0], recursion_depth+1) + 1
                return (res[0]+ 1, res[1] )
        elif data is None:
            raise TypeError("get_data_nesting_level: encountered None at nesting level {}".format(recursion_depth))
        else:
            #unknown class. Return 0 level
            return (0, -1)

    res = helper(data, 0)
    return res[0] 

data: number, or list of numbers, or list of lists, etc. data_types: list or tuple of types.

Detect nesting level of actual data. "Actual" data is detected by belonging to one of data_types. This method searches only for first instance of "actual data", so it does not support cases when different elements of source list have different nesting. Returns integer. Raises an exception if at some point it encounters element which is not a tuple, list, or one of data_types.

get_data_nesting_level(1) == 0 get_data_nesting_level([]) == 1 get_data_nesting_level([1]) == 1 get_data_nesting_level([[(1,2,3)]]) == 3

def get_edge_list(n)
Expand source code
def get_edge_list(n):
    """
    Get the list of n edges connecting n+1 vertices.

    e.g. [[0, 1], [1, 2], ... , [n-1, n]]

    NOTE: This uses an "edge cache" to accelerate the edge list generation.
    The cache is extended automatically as needed to satisfy the largest number
    of edges within the node tree and it is shared by all nodes using this method.
    """
    update_edge_cache(n) # make sure the edge cache is large enough
    return _edgeCache["main"][:n] # return a subset list of the edge cache

Get the list of n edges connecting n+1 vertices.

e.g. [[0, 1], [1, 2], … , [n-1, n]]

NOTE: This uses an "edge cache" to accelerate the edge list generation. The cache is extended automatically as needed to satisfy the largest number of edges within the node tree and it is shared by all nodes using this method.

def get_edge_loop(n)
Expand source code
def get_edge_loop(n):
    """
    Get the loop list of n edges connecting n vertices.

    e.g. [[0, 1], [1, 2], ... , [n-2, n-1], [n-1, 0]]

    NOTE: This uses an "edge cache" to accelerate the edge list generation.
    The cache is extended automatically as needed to satisfy the largest number
    of edges within the node tree and it is shared by all nodes using this method.
    """
    nn = n - 1
    update_edge_cache(nn) # make sure the edge cache is large enough
    return _edgeCache["main"][:nn] + [[nn, 0]]

Get the loop list of n edges connecting n vertices.

e.g. [[0, 1], [1, 2], … , [n-2, n-1], [n-1, 0]]

NOTE: This uses an "edge cache" to accelerate the edge list generation. The cache is extended automatically as needed to satisfy the largest number of edges within the node tree and it is shared by all nodes using this method.

def get_other_socket(socket)
Expand source code
def get_other_socket(socket):
    """
    Get next real upstream socket.
    This should be expanded to support wifi nodes also.
    Will return None if there isn't a another socket connect
    so no need to check socket.links
    """
    if not socket.is_linked:
        return None
    if not socket.is_output:
        if socket.links:
            other = socket.links[0].from_socket
        else:
            return None
    else:
        other = socket.links[0].to_socket

    if other.node.bl_idname == 'NodeReroute':
        if not socket.is_output:
            return get_other_socket(other.node.inputs[0])
        else:
            return get_other_socket(other.node.outputs[0])
    else:  #other.node.bl_idname == 'WifiInputNode':
        return other

Get next real upstream socket. This should be expanded to support wifi nodes also. Will return None if there isn't a another socket connect so no need to check socket.links

def graft_data(data,
item_level=1,
wrap_level=1,
data_types=(<class 'float'>, <class 'int'>, <class 'numpy.float64'>, <class 'numpy.int32'>, <class 'numpy.int64'>, <class 'str'>, <class 'Matrix'>))
Expand source code
def graft_data(data, item_level=1, wrap_level=1, data_types=SIMPLE_DATA_TYPES):
    """
    For each nested item of the list, which has it's own nesting level of `target_level`,
    wrap that item into a pair of [].
    For example, with item_level==0, this means wrap each number in the nested list
    (however deep this number is nested) into pair of [].
    Refer to data_structure_tests.py for examples.
    """
    def wrap(item):
        for i in range(wrap_level):
            item = [item]
        return item

    def helper(data):
        current_level = get_data_nesting_level(data, data_types)
        if current_level == item_level:
            return wrap(data)
        else:
            result = [helper(item) for item in data]
            return result

    return helper(data)

For each nested item of the list, which has it's own nesting level of target_level, wrap that item into a pair of []. For example, with item_level==0, this means wrap each number in the nested list (however deep this number is nested) into pair of []. Refer to data_structure_tests.py for examples.

def handle_check(handle, prop)
Expand source code
def handle_check(handle, prop):
    if handle in handle_check and \
            prop == handle_check[handle]['prop']:
        return True
    return False
def handle_delete(handle)
Expand source code
def handle_delete(handle):
    if handle in temp_handle:
        del temp_handle[handle]
def handle_read(handle)
Expand source code
def handle_read(handle):
    if not (handle in temp_handle):
        return (False, [])
    return (True, temp_handle[handle]['prop'])
def handle_write(handle, prop)
Expand source code
def handle_write(handle, prop):
    handle_delete(handle)

    temp_handle[handle] = {"prop" : prop}
def has_element(pol_edge)
Expand source code
def has_element(pol_edge):
    if pol_edge is None:
        return False
    if len(pol_edge) > 0 and hasattr(pol_edge[0], '__len__') and len(pol_edge[0]) > 0:
        return True
    return False
def invert_index_list(indexes, length)
Expand source code
def invert_index_list(indexes, length):
    '''
    Inverts indexes list
    indexes: List[Int] of Ndarray flat numpy array
    length: Int. Length of the base list
    '''
    mask = np_ones(length, dtype='bool')
    mask[indexes] = False
    inverted_indexes = np_arange(length)[mask]
    return inverted_indexes

Inverts indexes list indexes: List[Int] of Ndarray flat numpy array length: Int. Length of the base list

def is_ultimately(data, data_types)
Expand source code
def is_ultimately(data, data_types):
    """
    Check if data is a nested list / tuple / array
    which ultimately consists of items of data_types.
    """
    if isinstance(data, (list, tuple, ndarray)):
        return is_ultimately(data[0], data_types)
    return isinstance(data, data_types)

Check if data is a nested list / tuple / array which ultimately consists of items of data_types.

def levelsOflist(lst)
Expand source code
def levelsOflist(lst):
    """calc list nesting only in countainment level integer"""
    level = 1
    for n in lst:
        if n and isinstance(n, (list, tuple)):
            level += levelsOflist(n)
        return level
    return 0

calc list nesting only in countainment level integer

def levels_of_list_or_np(lst)
Expand source code
def levels_of_list_or_np(lst):
    """calc list nesting only in countainment level integer"""
    level = 1
    for n in lst:
        if isinstance(n, (list, tuple)):
            level += levels_of_list_or_np(n)
        elif isinstance(n, (ndarray)):
            level += len(n.shape)

        return level
    return 0

calc list nesting only in countainment level integer

def list_levels_adjust(data,
instructions,
data_types=(<class 'float'>, <class 'int'>, <class 'numpy.float64'>, <class 'numpy.int32'>, <class 'numpy.int64'>, <class 'str'>, <class 'Matrix'>))
Expand source code
def list_levels_adjust(data, instructions, data_types=SIMPLE_DATA_TYPES):
    data_level = get_data_nesting_level(data, data_types + (ndarray,))
    if len(instructions) < data_level+1:
        raise Exception(f"Number of instructions ({len(instructions)}) is less than data nesting level {data_level} + 1")

    def process(data, instruction, level):
        result = data
        if level + 1 < data_level and instruction.flatten:
            result = sum(result, [])
        if instruction.wrap:
            result = [result]
        #print(f"II: {level}/{data_level}, {instruction}, {data} => {result}")
        return result

    def helper(data, instructions, level):
        if level == data_level:
            items = process(data, instructions[0], level)
        else:
            sub_items = [helper(item, instructions[1:], level+1) for item in data]
            items = process(sub_items, instructions[0], level)
            #print(f"?? {level}/{data_level}, {data} => {sub_items} => {items}")
        return items

    return helper(data, instructions, 0)
def make_cyclers(lists)
Expand source code
def make_cyclers(lists):

    cycle = itertools.cycle
    out =[]
    for l in lists:
        out.append(cycle(l))
    return out
def make_repeaters(lists)
Expand source code
def make_repeaters(lists):
    chain = itertools.chain
    repeat = itertools.repeat
    out =[]
    for l in lists:
        out.append(chain(l, repeat(l[-1])))

    return out
def map_at_level(function,
data,
item_level=0,
data_types=(<class 'float'>, <class 'int'>, <class 'numpy.float64'>, <class 'numpy.int32'>, <class 'numpy.int64'>, <class 'str'>, <class 'Matrix'>))
Expand source code
def map_at_level(function, data, item_level=0, data_types=SIMPLE_DATA_TYPES):
    """
    Given a nested list of object, apply `function` to each sub-list of items.
    Nesting structure of the result will be simpler than such of the input:
    most nested levels (`item_level` of them) will be eliminated.
    Refer to data_structure_tests.py for examples.
    """
    current_level = get_data_nesting_level(data, data_types)
    if current_level == item_level:
        return function(data)
    else:
        return [map_at_level(function, item, item_level, data_types) for item in data]

Given a nested list of object, apply function to each sub-list of items. Nesting structure of the result will be simpler than such of the input: most nested levels (item_level of them) will be eliminated. Refer to data_structure_tests.py for examples.

def map_recursive(fn,
data,
data_types=(<class 'float'>, <class 'int'>, <class 'numpy.float64'>, <class 'numpy.int32'>, <class 'numpy.int64'>, <class 'str'>, <class 'Matrix'>))
Expand source code
def map_recursive(fn, data, data_types=SIMPLE_DATA_TYPES):
    """
    Given a nested list of items, apply `fn` to each of these items.
    Nesting structure of the result will be the same as in the input.
    """
    def helper(data, level):
        if isinstance(data, data_types):
            return fn(data)
        elif isinstance(data, (list, tuple)):
            return [helper(item, level+1) for item in data]
        else:
            raise TypeError(f"Encountered unknown data of type {type(data)} at nesting level #{level}")
    return helper(data, 0)

Given a nested list of items, apply fn to each of these items. Nesting structure of the result will be the same as in the input.

def map_unzip_recursirve(fn,
data,
data_types=(<class 'float'>, <class 'int'>, <class 'numpy.float64'>, <class 'numpy.int32'>, <class 'numpy.int64'>, <class 'str'>, <class 'Matrix'>))
Expand source code
def map_unzip_recursirve(fn, data, data_types=SIMPLE_DATA_TYPES):
    """
    Given a nested list of items, apply `fn` to each of these items.
    This method expects that `fn` will return a tuple (or list) of results.
    After applying `fn` to each of items of data, "unzip" the result, so that
    each item of result of `fn` would be in a separate nested list.
    Nesting structure of each of items of the result of this method will be
    the same as nesting structure of input data.
    Refer to data_structure_tests.py for examples.
    """
    def helper(data, level):
        if isinstance(data, data_types):
            return fn(data)
        elif isinstance(data, (list, tuple)):
            results = [helper(item, level+1) for item in data]
            return transpose_list(results)
        else:
            raise TypeError(f"Encountered unknown data of type {type(data)} at nesting level #{level}")
    return helper(data, 0)

Given a nested list of items, apply fn to each of these items. This method expects that fn will return a tuple (or list) of results. After applying fn to each of items of data, "unzip" the result, so that each item of result of fn would be in a separate nested list. Nesting structure of each of items of the result of this method will be the same as nesting structure of input data. Refer to data_structure_tests.py for examples.

def match_cross(lsts)
Expand source code
def match_cross(lsts):
    """ return cross matched lists
    [[1,2], [5,6,7]] -> [[1,1,1,2,2,2], [5,6,7,5,6,7]]
    """
    return list(map(list, zip(*itertools.product(*lsts))))

return cross matched lists [[1,2], [5,6,7]] -> [[1,1,1,2,2,2], [5,6,7,5,6,7]]

def match_cross2(lsts)
Expand source code
def match_cross2(lsts):
    """ return cross matched lists
    [[1,2], [5,6,7]] ->[[1, 2, 1, 2, 1, 2], [5, 5, 6, 6, 7, 7]]
    """
    return list(reversed(list(map(list, zip(*itertools.product(*reversed(lsts)))))))

return cross matched lists [[1,2], [5,6,7]] ->[[1, 2, 1, 2, 1, 2], [5, 5, 6, 6, 7, 7]]

def match_long_cycle(lsts)
Expand source code
def match_long_cycle(lsts):
    """return matched list, cycling the shorter lists
    longest list matching, cycle [[1,2,3,4,5] ,[10,11]] -> [[1,2,3,4,5] ,[10,11,10,11,10]]
    """
    max_l = 0
    tmp = []
    for l in lsts:
        max_l = max(max_l, len(l))
    for l in lsts:
        if len(l) == max_l:
            tmp.append(l)
        else:
            tmp.append(itertools.cycle(l))
    return list(map(list, zip(*zip(*tmp))))

return matched list, cycling the shorter lists longest list matching, cycle [[1,2,3,4,5] ,[10,11]] -> [[1,2,3,4,5] ,[10,11,10,11,10]]

def match_long_repeat(lsts)
Expand source code
def match_long_repeat(lsts):
    """return matched list, using the last value to fill lists as needed
    longest list matching [[1,2,3,4,5], [10,11]] -> [[1,2,3,4,5], [10,11,11,11,11]]
    
    lists passed into this function are not modified, it produces non-deep copies and extends those.
    """
    max_l = 0
    tmp = []
    for l in lsts:
        if not hasattr(l, '__len__'):
            raise TypeError(f"Cannot perform data matching: input of type {type(l)} is not a list or tuple, but an atomic object")
        max_l = max(max_l, len(l))
    for l in lsts:
        if len(l) == max_l:
            tmp.append(l)
        else:
            tmp.append(repeat_last(l))
    return list(map(list, zip(*zip(*tmp))))

return matched list, using the last value to fill lists as needed longest list matching [[1,2,3,4,5], [10,11]] -> [[1,2,3,4,5], [10,11,11,11,11]]

lists passed into this function are not modified, it produces non-deep copies and extends those.

def match_short(lsts)
Expand source code
def match_short(lsts):
    """return lists of equal length using the Shortest list to decides length
    Shortest list decides output length [[1,2,3,4,5], [10,11]] -> [[1,2], [10, 11]]
    """
    return list(map(list, zip(*zip(*lsts))))

return lists of equal length using the Shortest list to decides length Shortest list decides output length [[1,2,3,4,5], [10,11]] -> [[1,2], [10, 11]]

def matrixdef(orig, loc, scale, rot, angle, vec_angle=[[]])
Expand source code
def matrixdef(orig, loc, scale, rot, angle, vec_angle=[[]]):
    modif = []
    for i, de in enumerate(orig):
        ma = de.copy()

        if loc[0]:
            k = min(len(loc[0])-1, i)
            mat_tran = de.Translation(loc[0][k])
            ma = ma @ mat_tran

        if vec_angle[0] and rot[0]:
            k = min(len(rot[0])-1, i)
            a = min(len(vec_angle[0])-1, i)

            vec_a = vec_angle[0][a].normalized()
            vec_b = rot[0][k].normalized()

            mat_rot = vec_b.rotation_difference(vec_a).to_matrix().to_4x4()
            ma = ma @ mat_rot

        elif rot[0]:
            k = min(len(rot[0])-1, i)
            a = min(len(angle[0])-1, i)
            mat_rot = de.Rotation(radians(angle[0][a]), 4, rot[0][k].normalized())
            ma = ma @ mat_rot

        if scale[0]:
            k = min(len(scale[0])-1, i)
            scale2 = scale[0][k]
            id_m = Matrix.Identity(4)
            for j in range(3):
                id_m[j][j] = scale2[j]
            ma = ma @ id_m

        modif.append(ma)
    return modif
def multi_socket(node, min=1, start=0, breck=False, out_count=None)
Expand source code
def multi_socket(node, min=1, start=0, breck=False, out_count=None):
    '''
     min - integer, minimal number of sockets, at list 1 needed
     start - integer, starting socket.
     breck - boolean, adding bracket to name of socket x[0] x[1] x[2] etc
     output - integer, deal with output, if>0 counts number of outputs multi sockets
     base name added in separated node in self.base_name = 'some_name', i.e. 'x', 'data'
     node.multi_socket_type - type of socket, as .bl_idname

    '''
    #probably incorrect state due or init or change of inputs
    # do nothing
    ng = node.id_data

    if min < 1:
        min = 1
    if out_count is None:
        if not node.inputs:
            return
        if node.inputs[-1].links:
            length = start + len(node.inputs)
            if breck:
                name = node.base_name + '[' + str(length) + ']'
            else:
                name = node.base_name + str(length)
            node.inputs.new(node.multi_socket_type, name)
        else:
            while len(node.inputs) > min and not node.inputs[-2].links:
                node.inputs.remove(node.inputs[-1])
    elif isinstance(out_count, int):
        lenod = len(node.outputs)
        if out_count > 30:
            out_count = 30
        if lenod < out_count:
            while len(node.outputs) < out_count:
                length = start + len(node.outputs)
                if breck:
                    name = node.base_name + '[' + str(length)+ ']'
                else:
                    name = node.base_name + str(length)
                node.outputs.new(node.multi_socket_type, name)
        else:
            while len(node.outputs) > out_count:
                node.outputs.remove(node.outputs[-1])

min - integer, minimal number of sockets, at list 1 needed start - integer, starting socket. breck - boolean, adding bracket to name of socket x[0] x[1] x[2] etc output - integer, deal with output, if>0 counts number of outputs multi sockets base name added in separated node in self.base_name = 'some_name', i.e. 'x', 'data' node.multi_socket_type - type of socket, as .bl_idname

def no_space(s)
Expand source code
def no_space(s):
    return s.replace(' ', '_')
def node_id(node)
Expand source code
def node_id(node):
    """return a stable hash for the lifetime of the node
    needs StringProperty called n_id in the node
    """
    return node.node_id

return a stable hash for the lifetime of the node needs StringProperty called n_id in the node

def numpy_full_list(array, desired_length)
Expand source code
def numpy_full_list(array, desired_length):
    '''retuns array with desired length by repeating last item'''
    if not isinstance(array, ndarray):
        array = np_array(array)

    length_diff = desired_length - array.shape[0]

    if length_diff > 0:
        new_part = np_repeat(array[np_newaxis, -1], length_diff, axis=0)
        return np_concatenate((array, new_part))[:desired_length]
    return array[:desired_length]

retuns array with desired length by repeating last item

def numpy_full_list_cycle(array, desired_length)
Expand source code
def numpy_full_list_cycle(array, desired_length):
    '''retuns array with desired length by cycling'''

    length_diff = desired_length - array.shape[0]
    if length_diff > 0:
        if length_diff < array.shape[0]:

            return np_concatenate((array, array[:length_diff]))

        new_part = np_repeat(array, ceil(length_diff / array.shape[0]), axis=0)
        if len(array.shape) > 1:
            shape = (ceil(length_diff / array.shape[0]), 1)
        else:
            shape = ceil(length_diff / array.shape[0])
        new_part = np_tile(array, shape)
        return np_concatenate((array, new_part[:length_diff]))

    return array[:desired_length]

retuns array with desired length by cycling

def numpy_match_long_cycle(list_of_arrays)
Expand source code
def numpy_match_long_cycle(list_of_arrays):
    '''match numpy arrays length by cycling over the array'''
    out = []
    maxl = 0
    for array in list_of_arrays:
        maxl = max(maxl, array.shape[0])
    for array in list_of_arrays:
        length_diff = maxl - array.shape[0]
        if length_diff > 0:
            if length_diff < array.shape[0]:

                array = np_concatenate((array, array[:length_diff]))
            else:
                new_part = np_repeat(array, ceil(length_diff / array.shape[0]), axis=0)
                if len(array.shape) > 1:
                    shape = (ceil(length_diff / array.shape[0]), 1)
                else:
                    shape = ceil(length_diff / array.shape[0])
                new_part = np_tile(array, shape)
                array = np_concatenate((array, new_part[:length_diff]))
        out.append(array)
    return out

match numpy arrays length by cycling over the array

def numpy_match_long_repeat(list_of_arrays)
Expand source code
def numpy_match_long_repeat(list_of_arrays):
    '''match numpy arrays length by repeating last one'''
    out = []
    maxl = 0
    for array in list_of_arrays:
        maxl = max(maxl, array.shape[0])
    for array in list_of_arrays:
        length_diff = maxl - array.shape[0]
        if length_diff > 0:
            new_part = np_repeat(array[np_newaxis, -1], length_diff, axis=0)
            array = np_concatenate((array, new_part))
        out.append(array)
    return out

match numpy arrays length by repeating last one

def numpy_match_short(list_of_arrays)
Expand source code
def numpy_match_short(list_of_arrays):
    '''match numpy arrays length by cutting the longer arrays'''
    out = []
    minl = list_of_arrays[0].shape[0]
    for array in list_of_arrays:
        minl = min(minl, array.shape[0])
    for array in list_of_arrays:
        length_diff = array.shape[0] - minl
        if length_diff > 0:
            array = array[:minl]
        out.append(array)
    return out

match numpy arrays length by cutting the longer arrays

def partition(p, lst)
Expand source code
def partition(p, lst):
    good, bad = [], []
    for item in lst:
        if p(item):
            good.append(item)
        else:
            bad.append(item)
    return good, bad
def post_load_call(function)
Expand source code
def post_load_call(function):  # better place would be in handlers module but import cyclic error
    """
    Usage: if you need function which should be called each time when blender is lunched
    or new file is opened use this decorator
    Limitation: the function should not get any properties because it will be called by handler
    """
    post_load_call.registered_functions.append(function)

    @wraps(function)
    def wrapper():
        return function()

    return wrapper

Usage: if you need function which should be called each time when blender is lunched or new file is opened use this decorator Limitation: the function should not get any properties because it will be called by handler

def repeat_last(lst)
Expand source code
def repeat_last(lst):
    """
    creates an infinite iterator the first each element in lst
    and then keep repeating the last element,
    use with terminating input
    """
    last = [lst[-1]] if len(lst) else []  # len(lst) in case of numpy arrays
    yield from chain(lst, cycle(last))

creates an infinite iterator the first each element in lst and then keep repeating the last element, use with terminating input

def repeat_last_for_length(lst, count, deepcopy=False)
Expand source code
def repeat_last_for_length(lst, count, deepcopy=False):
    """
    Repeat last item of the list enough times
    for result's length to be equal to `count`.

    repeat_last_for_length(None, n) = None
    repeat_last_for_length([], n) = []
    repeat_last_for_length([1,2], 4) = [1, 2, 2, 2]
    """
    if not lst:
        return lst
    if len(lst) >= count:
        return lst[:count]
    n = len(lst)
    x = lst[-1]
    result = lst[:]
    if deepcopy:
        for i in range(count - n):
            result.append(copy.deepcopy(x))
    else:
        for i in range(count - n):
            result.append(x)

    return result

Repeat last item of the list enough times for result's length to be equal to count.

repeat_last_for_length(None, n) = None repeat_last_for_length([], n) = [] repeat_last_for_length([1,2], 4) = [1, 2, 2, 2]

def replace_socket(socket, new_type, new_name=None, new_pos=None)
Expand source code
def replace_socket(socket, new_type, new_name=None, new_pos=None):
    """
    Replace a socket with a socket of new_type and keep links

    is_linked attribute of replaced socket will be False
    whether it is connected or not - https://developer.blender.org/T82318
    """
    # note: it seems impossible to replace one socket more efficient

    socket_name = new_name or socket.name
    socket_pos = new_pos or socket.index
    ng = socket.id_data

    if socket.is_output:
        outputs = socket.node.outputs
        to_sockets = [l.to_socket for l in socket.links]

        new_socket = outputs.new(new_type, socket_name)
        new_socket.copy_options(socket)
        outputs.remove(socket)
        outputs.move(len(outputs)-1, socket_pos)

        for to_socket in to_sockets:
            link = ng.links.new(new_socket, to_socket)
            link.is_valid = True

    else:
        inputs = socket.node.inputs
        from_socket = socket.links[0].from_socket if socket.is_linked else None

        identifier = socket.identifier  # It's important for Geo Nodes Viewer
        inputs.remove(socket)
        new_socket = inputs.new(new_type, socket_name, identifier=identifier)
        inputs.move(len(inputs)-1, socket_pos)

        if from_socket:
            link = ng.links.new(from_socket, new_socket)
            link.is_valid = True

    return new_socket

Replace a socket with a socket of new_type and keep links

is_linked attribute of replaced socket will be False whether it is connected or not - https://developer.blender.org/T82318

def rotate_list(l, y=1)
Expand source code
def rotate_list(l, y=1):
    """
    "Rotate" list by shifting it's items towards the end and putting last items to the beginning.
    For example,

    rotate_list([1, 2, 3]) = [2, 3, 1]
    rotate_list([1, 2, 3], y=2) = [3, 1, 2]
    """
    if len(l) == 0:
        return l
    if y == 0:
        return l
    y = y % len(l)
    return list(l[y:]) + list(l[:y])

"Rotate" list by shifting it's items towards the end and putting last items to the beginning. For example,

rotate_list([1, 2, 3]) = [2, 3, 1] rotate_list([1, 2, 3], y=2) = [3, 1, 2]

def second_as_first_cycle(F, S)
Expand source code
def second_as_first_cycle(F, S):
    if len(F) > len(S):
        return list(map(list, zip(*zip(*[F, itertools.cycle(S)]))))[1]
    else:
        return S
def socket_id(socket)
Expand source code
def socket_id(socket):
    """return an usable and semi stable hash"""
    return socket.socket_id

return an usable and semi stable hash

def split_by_count(iterable, n, fillvalue=None)
Expand source code
def split_by_count(iterable, n, fillvalue=None):
    "Collect data into fixed-length chunks or blocks"
    # grouper('ABCDEFG', 3, 'x') --> ABC DEF Gxx"
    args = [iter(iterable)] * n
    return list(map(list, zip_longest(*args, fillvalue=fillvalue)))

Collect data into fixed-length chunks or blocks

def sv_lambda(**kwargs)
Expand source code
def sv_lambda(**kwargs):
    """üsage: (like a named tuple)

    structure = sv_lambda(keys=20, color=(1,0,0,0))

    print(structure.keys)
    print(structure.color)

    useful for passing a parameter to a function that expects to be able to do a dot lookup
    on the parameter, for instance a function that normally accepts "self" or "node", but the
    function only really looks up one or two..etc parameters.
    """
    dummy = lambda: None
    for k, v in kwargs.items():
        setattr(dummy, k, v)
    return dummy

üsage: (like a named tuple)

structure = sv_lambda(keys=20, color=(1,0,0,0))

print(structure.keys) print(structure.color)

useful for passing a parameter to a function that expects to be able to do a dot lookup on the parameter, for instance a function that normally accepts "self" or "node", but the function only really looks up one or two..etc parameters.

def sv_zip(*iterables)
Expand source code
def sv_zip(*iterables):
    """zip('ABCD', 'xy') --> Ax By
    like standard zip but list instead of tuple
    """
    iterators = [iter(it) for it in iterables]
    sentinel = object() # use internal sentinel
    while iterators:
        result = []
        for it in iterators:
            elem = next(it, sentinel)
            if elem is sentinel:
                return
            result.append(elem)
        yield result

zip('ABCD', 'xy') –> Ax By like standard zip but list instead of tuple

def transpose_list(lst)
Expand source code
def transpose_list(lst):
    """
    Transpose a list of lists.

    transpose_list([[1,2], [3,4]]) == [[1,3], [2, 4]]
    """
    return list(map(list, zip(*lst)))

Transpose a list of lists.

transpose_list([[1,2], [3,4]]) == [[1,3], [2, 4]]

def unwrap_data(data, unwrap_level=1, socket=None)
Expand source code
def unwrap_data(data, unwrap_level=1, socket=None):
    socket_msg = "" if socket is None else f" in socket {socket.label or socket.name}"

    def unwrap(lst, level):
        if not isinstance(lst, (list, tuple, ndarray)):
            raise Exception(f"Cannot unwrap data: Data at level {level} is an atomic object, not a list {socket_msg}")
        n = len(lst)
        if n == 0:
            raise Exception(f"Cannot unwrap data: Data at level {level} is an empty list {socket_msg}")
        elif n > 1:
            raise Exception(f"Cannot unwrap data: Data at level {level} contains {n} objects instead of one {socket_msg}")
        else:
            return lst[0]

    for level in range(unwrap_level):
        data = unwrap(data, level)
    return data
def unzip_dict_recursive(data, item_type=builtins.dict, to_dict=None)
Expand source code
def unzip_dict_recursive(data, item_type=dict, to_dict=None):
    """
    Given a nested list of dictionaries, return a dictionary of nested lists.
    Nesting structure of each of values of resulting dictionary will be similar to
    nesting structure of input data, only at the deepest level, instead of dictionaries
    you will have their values.

    inputs:
    * data: nested list of dictionaries.
    * item_type: allows to use arbitrary class instead of standard python's dict.
    * to_dict: a function which translates data item into python's dict (or
      another class with the same interface). Identity by default.

    output: dictionary of nested lists.

    Refer to data_structure_tests.py for examples.
    """

    if to_dict is None:
        to_dict = lambda d: d

    def helper(data):
        current_level = get_data_nesting_level(data, data_types=(item_type,))
        if current_level == 0:
            return to_dict(data)
        elif current_level == 1:
            result = defaultdict(list)
            for dct in data:
                dct = to_dict(dct)
                for key, value in dct.items():
                    result[key].append(value)
            return result
        else:
            result = defaultdict(list)
            for item in data:
                sub_result = helper(item)
                for key, value in sub_result.items():
                    result[key].append(value)
            return result

    return helper(data)

Given a nested list of dictionaries, return a dictionary of nested lists. Nesting structure of each of values of resulting dictionary will be similar to nesting structure of input data, only at the deepest level, instead of dictionaries you will have their values.

inputs: * data: nested list of dictionaries. * item_type: allows to use arbitrary class instead of standard python's dict. * to_dict: a function which translates data item into python's dict (or another class with the same interface). Identity by default.

output: dictionary of nested lists.

Refer to data_structure_tests.py for examples.

def updateNode(self, context)
Expand source code
def updateNode(self, context):
    """
    When a node has changed state and need to call a partial update.
    For example a user exposed bpy.prop
    """
    self.process_node(context)

When a node has changed state and need to call a partial update. For example a user exposed bpy.prop

def update_edge_cache(n)
Expand source code
def update_edge_cache(n):
    """
    Extend the edge list cache to contain at least n edges.

    NOTE: This is called by the get_edge_list to make sure the edge cache is large
    enough, but it can also be called preemptively by the nodes prior to making
    multiple calls to get_edge_list in order to pre-augment the cache to a known
    size and thus accellearate the subsequent calls to get_edge_list as they
    will not have to augment the cache with every call.
    """
    m = len(_edgeCache["main"])  # current number of edges in the edge cache
    if n > m: # requested #edges < cached #edges ? => extend the cache
        _edgeCache["main"].extend([[m + i, m + i + 1] for i in range(n - m)])

Extend the edge list cache to contain at least n edges.

NOTE: This is called by the get_edge_list to make sure the edge cache is large enough, but it can also be called preemptively by the nodes prior to making multiple calls to get_edge_list in order to pre-augment the cache to a known size and thus accellearate the subsequent calls to get_edge_list as they will not have to augment the cache with every call.

def update_with_kwargs(update_function, **kwargs)
Expand source code
def update_with_kwargs(update_function, **kwargs):
    """
    You can wrap property update function for adding extra key arguments to it, like this:

    def update_prop(self, context, extra_arg=None):
        print(extra_arg)

    node_prop_name: bpy.props.BoolProperty(update=update_with_kwargs(update_prop, extra_arg='node_prop_name'))
    """

    # https://docs.python.org/3/library/functools.html#functools.partial
    @wraps(update_function)
    def handel_update_call(node, context):
        update_function(node, context, **handel_update_call.extra_args)

    handel_update_call.extra_args = dict()
    for attr_name, data in kwargs.items():
        handel_update_call.extra_args[attr_name] = data

    return handel_update_call

You can wrap property update function for adding extra key arguments to it, like this:

def update_prop(self, context, extra_arg=None): print(extra_arg)

node_prop_name: bpy.props.BoolProperty(update=update_with_kwargs(update_prop, extra_arg='node_prop_name'))

def wrap_data(data, wrap_level=1)
Expand source code
def wrap_data(data, wrap_level=1):
    for i in range(wrap_level):
        data = [data]
    return data
def zip_long_repeat(*lists)
Expand source code
def zip_long_repeat(*lists):
    objects = match_long_repeat(lists)
    return zip(*objects)

Classes

class SvListLevelAdjustment (flatten=False, wrap=False)
Expand source code
class SvListLevelAdjustment(object):
    def __init__(self, flatten=False, wrap=False):
        self.flatten = flatten
        self.wrap = wrap

    def __repr__(self):
        return f"<Flatten={self.flatten}, Wrap={self.wrap}>"
class classproperty (fget)
Expand source code
def __get__(self, owner_self, owner_cls):
    return self.fget(owner_cls)