CM3D2 Converter.common

   1import os
   2import re
   3import math
   4import struct
   5import shutil
   6import bpy
   7import bmesh
   8import mathutils
   9from . import fileutil
  10from . import compat
  11from . import cm3d2_data
  12
  13# アドオン情報
  14bl_info = {}
  15ADDON_NAME = "CM3D2 Converter"
  16BASE_PATH_TEX = "Assets/texture/texture/"
  17BRANCH = "bl_28"
  18URL_REPOS = "https://github.com/luvoid/Blender-CM3D2-Converter/"
  19URL_ATOM = URL_REPOS + "commits/{branch}.atom"
  20URL_MODULE = URL_REPOS + "archive/{branch}.zip"
  21KISS_ICON = None
  22PREFS = None
  23preview_collections = {}
  24texpath_dict = {}
  25
  26
  27re_png = re.compile(r"\.[Pp][Nn][Gg](\.\d{3})?$")
  28re_serial = re.compile(r"(\.\d{3})$")
  29re_prefix = re.compile(r"^[\/\.]*")
  30re_path_prefix = re.compile(r"^assets/", re.I)
  31re_ext_png = re.compile(r"\.png$", re.I)
  32re_bone1 = re.compile(r"([_ ])\*([_ ].*)\.([rRlL])$")
  33re_bone2 = re.compile(r"([_ ])([rRlL])([_ ].*)$")
  34
  35
  36# このアドオンの設定値群を呼び出す
  37def preferences():
  38    global PREFS
  39    if PREFS is None:
  40        PREFS = compat.get_prefs(bpy.context).addons[__package__].preferences
  41    return PREFS
  42
  43
  44def kiss_icon():
  45    global KISS_ICON
  46    if KISS_ICON is None:
  47        KISS_ICON = preview_collections['main']['KISS'].icon_id
  48    return KISS_ICON
  49
  50
  51# データ名末尾の「.001」などを削除
  52def remove_serial_number(name, enable=True):
  53    return re_serial.sub('', name) if enable else name
  54
  55
  56# データ名末尾の「.001」などが含まれるか判定
  57def has_serial_number(name):
  58    return re_serial.search(name) is not None
  59
  60
  61# 文字列の左右端から空白を削除
  62def line_trim(line, enable=True):
  63    return line.strip('  \t\r\n') if enable else line
  64
  65
  66# CM3D2専用ファイル用の文字列書き込み
  67def write_str(file, raw_str):
  68    b_str = format(len(raw_str.encode('utf-8')), 'b')
  69    for i in range(9):
  70        if 7 < len(b_str):
  71            file.write(struct.pack('<B', int("1" + b_str[-7:], 2)))
  72            b_str = b_str[:-7]
  73        else:
  74            file.write(struct.pack('<B', int(b_str, 2)))
  75            break
  76    file.write(raw_str.encode('utf-8'))
  77
  78def pack_str(buffer, raw_str):
  79    b_str = format(len(raw_str.encode('utf-8')), 'b')
  80    for i in range(9):
  81        if 7 < len(b_str):
  82            buffer = buffer + struct.pack('<B', int("1" + b_str[-7:], 2))
  83            b_str = b_str[:-7]
  84        else:
  85            buffer = buffer + struct.pack('<B', int(b_str, 2))
  86            break
  87    buffer = buffer + raw_str.encode('utf-8')
  88    return buffer
  89
  90
  91# CM3D2専用ファイル用の文字列読み込み
  92def read_str(file, total_b=""):
  93    for i in range(9):
  94        b_str = format(struct.unpack('<B', file.read(1))[0], '08b')
  95        total_b = b_str[1:] + total_b
  96        if b_str[0] == '0':
  97            break
  98    return file.read(int(total_b, 2)).decode('utf-8')
  99
 100
 101# ボーン/ウェイト名を Blender → CM3D2
 102def encode_bone_name(name, enable=True):
 103    return re.sub(r'([_ ])\*([_ ].*)\.([rRlL])$', r'\1\3\2', name) if enable and name.count('*') == 1 else name
 104
 105
 106# ボーン/ウェイト名を CM3D2 → Blender
 107def decode_bone_name(name, enable=True):
 108    return re.sub(r'([_ ])([rRlL])([_ ].*)$', r'\1*\3.\2', name) if enable else name
 109
 110
 111# CM3D2用マテリアルを設定に合わせて装飾
 112def decorate_material_old(mate, enable=True, me=None, mate_index=-1):
 113    if not compat.IS_LEGACY or not enable or 'shader1' not in mate:
 114        return
 115
 116    shader = mate['shader1']
 117    if 'CM3D2/Man' == shader:
 118        mate.use_shadeless = True
 119        mate.diffuse_color = (0, 1, 1)
 120    elif 'CM3D2/Mosaic' == shader:
 121        mate.use_transparency = True
 122        mate.transparency_method = 'RAYTRACE'
 123        mate.alpha = 0.25
 124        mate.raytrace_transparency.ior = 2
 125    elif 'CM3D2_Debug/Debug_CM3D2_Normal2Color' == shader:
 126        mate.use_tangent_shading = True
 127        mate.diffuse_color = (0.5, 0.5, 1)
 128
 129    else:
 130        if '/Toony_' in shader:
 131            mate.diffuse_shader = 'TOON'
 132            mate.diffuse_toon_smooth = 0.01
 133            mate.diffuse_toon_size = 1.2
 134        if 'Trans' in shader:
 135            mate.use_transparency = True
 136            mate.alpha = 0.0
 137            mate.texture_slots[0].use_map_alpha = True
 138        if 'Unlit/' in shader:
 139            mate.emit = 0.5
 140        if '_NoZ' in shader:
 141            mate.offset_z = 9999
 142
 143    is_colored = False
 144    is_textured = [False, False, False, False]
 145    rimcolor, rimpower, rimshift = mathutils.Color((1, 1, 1)), 0.0, 0.0
 146    for slot in mate.texture_slots:
 147        if not slot or not slot.texture:
 148            continue
 149
 150        tex = slot.texture
 151        tex_name = remove_serial_number(tex.name)
 152        slot.use_map_color_diffuse = False
 153
 154        if tex_name == '_MainTex':
 155            slot.use_map_color_diffuse = True
 156            img = getattr(tex, 'image')
 157            if img and len(img.pixels):
 158                if me:
 159                    color = mathutils.Color(get_image_average_color_uv(img, me, mate_index)[:3])
 160                else:
 161                    color = mathutils.Color(get_image_average_color(img)[:3])
 162                mate.diffuse_color = color
 163                is_colored = True
 164
 165        elif tex_name == '_RimColor':
 166            rimcolor = slot.color[:]
 167            if not is_colored:
 168                mate.diffuse_color = slot.color[:]
 169                mate.diffuse_color.v += 0.5
 170
 171        elif tex_name == '_Shininess':
 172            mate.specular_intensity = slot.diffuse_color_factor
 173
 174        elif tex_name == '_RimPower':
 175            rimpower = slot.diffuse_color_factor
 176
 177        elif tex_name == '_RimShift':
 178            rimshift = slot.diffuse_color_factor
 179
 180        for index, name in enumerate(['_MainTex', '_ToonRamp', '_ShadowTex', '_ShadowRateToon']):
 181            if tex_name == name:
 182                img = getattr(tex, 'image')
 183                if img and len(tex.image.pixels):
 184                    is_textured[index] = tex
 185
 186        set_texture_color(slot)
 187
 188    # よりオリジナルに近く描画するノード作成
 189    if all(is_textured):
 190        mate.use_nodes = True
 191        mate.use_shadeless = True
 192
 193        node_tree = mate.node_tree
 194        for node in node_tree.nodes[:]:
 195            node_tree.nodes.remove(node)
 196
 197        mate_node = node_tree.nodes.new('ShaderNodeExtendedMaterial')
 198        mate_node.location = (0, 0)
 199        mate_node.material = mate
 200
 201        if "CM3D2 Shade" in bpy.context.blend_data.materials:
 202            shade_mate = bpy.context.blend_data.materials["CM3D2 Shade"]
 203        else:
 204            shade_mate = bpy.context.blend_data.materials.new("CM3D2 Shade")
 205        shade_mate.diffuse_color = (1, 1, 1)
 206        shade_mate.diffuse_intensity = 1
 207        shade_mate.specular_intensity = 1
 208        shade_mate_node = node_tree.nodes.new('ShaderNodeExtendedMaterial')
 209        shade_mate_node.location = (234.7785, -131.8243)
 210        shade_mate_node.material = shade_mate
 211
 212        toon_node = node_tree.nodes.new('ShaderNodeValToRGB')
 213        toon_node.location = (571.3662, -381.0965)
 214        toon_img = is_textured[1].image
 215        toon_w, toon_h = toon_img.size[0], toon_img.size[1]
 216        for i in range(32 - 2):
 217            toon_node.color_ramp.elements.new(0.0)
 218        for i in range(32):
 219            pos = i / (32 - 1)
 220            toon_node.color_ramp.elements[i].position = pos
 221            x = int((toon_w / (32 - 1)) * i)
 222            pixel_index = x * toon_img.channels
 223            toon_node.color_ramp.elements[i].color = toon_img.pixels[pixel_index: pixel_index + 4]
 224        toon_node.color_ramp.interpolation = 'EASE'
 225
 226        shadow_rate_node = node_tree.nodes.new('ShaderNodeValToRGB')
 227        shadow_rate_node.location = (488.2785, 7.8446)
 228        shadow_rate_img = is_textured[3].image
 229        shadow_rate_w, shadow_rate_h = shadow_rate_img.size[0], shadow_rate_img.size[1]
 230        for i in range(32 - 2):
 231            shadow_rate_node.color_ramp.elements.new(0.0)
 232        for i in range(32):
 233            pos = i / (32 - 1)
 234            shadow_rate_node.color_ramp.elements[i].position = pos
 235            x = int((shadow_rate_w / (32)) * i)
 236            pixel_index = x * shadow_rate_img.channels
 237            shadow_rate_node.color_ramp.elements[i].color = shadow_rate_img.pixels[pixel_index: pixel_index + 4]
 238        shadow_rate_node.color_ramp.interpolation = 'EASE'
 239
 240        geometry_node = node_tree.nodes.new('ShaderNodeGeometry')
 241        geometry_node.location = (323.4597, -810.8045)
 242
 243        shadow_texture_node = node_tree.nodes.new('ShaderNodeTexture')
 244        shadow_texture_node.location = (626.0117, -666.0227)
 245        shadow_texture_node.texture = is_textured[2]
 246
 247        invert_node = node_tree.nodes.new('ShaderNodeInvert')
 248        invert_node.location = (805.6814, -132.9144)
 249
 250        shadow_mix_node = node_tree.nodes.new('ShaderNodeMixRGB')
 251        shadow_mix_node.location = (1031.2714, -201.5598)
 252
 253        toon_mix_node = node_tree.nodes.new('ShaderNodeMixRGB')
 254        toon_mix_node.location = (1257.5538, -308.8037)
 255        toon_mix_node.blend_type = 'MULTIPLY'
 256        toon_mix_node.inputs[0].default_value = 1.0
 257
 258        specular_mix_node = node_tree.nodes.new('ShaderNodeMixRGB')
 259        specular_mix_node.location = (1473.2079, -382.7421)
 260        specular_mix_node.blend_type = 'SCREEN'
 261        specular_mix_node.inputs[0].default_value = mate.specular_intensity
 262
 263        normal_node = node_tree.nodes.new('ShaderNodeNormal')
 264        normal_node.location = (912.1372, -590.8748)
 265
 266        rim_ramp_node = node_tree.nodes.new('ShaderNodeValToRGB')
 267        rim_ramp_node.location = (1119.0664, -570.0284)
 268        rim_ramp_node.color_ramp.elements[0].color = list(rimcolor[:]) + [1.0]
 269        rim_ramp_node.color_ramp.elements[0].position = rimshift
 270        rim_ramp_node.color_ramp.elements[1].color = (0, 0, 0, 1)
 271        rim_ramp_node.color_ramp.elements[1].position = (rimshift) + ((1.0 - (rimpower * 0.03333)) * 0.5)
 272
 273        rim_power_node = node_tree.nodes.new('ShaderNodeHueSaturation')
 274        rim_power_node.location = (1426.6332, -575.6142)
 275        # rim_power_node.inputs[2].default_value = rimpower * 0.1
 276
 277        rim_mix_node = node_tree.nodes.new('ShaderNodeMixRGB')
 278        rim_mix_node.location = (1724.7024, -451.9624)
 279        rim_mix_node.blend_type = 'ADD'
 280
 281        out_node = node_tree.nodes.new('ShaderNodeOutput')
 282        out_node.location = (1957.4023, -480.5365)
 283
 284        node_tree.links.new(shadow_mix_node.inputs[1], mate_node.outputs[0])
 285        node_tree.links.new(shadow_rate_node.inputs[0], shade_mate_node.outputs[3])
 286        node_tree.links.new(invert_node.inputs[1], shadow_rate_node.outputs[0])
 287        node_tree.links.new(shadow_mix_node.inputs[0], invert_node.outputs[0])
 288        node_tree.links.new(toon_node.inputs[0], shade_mate_node.outputs[3])
 289        node_tree.links.new(shadow_texture_node.inputs[0], geometry_node.outputs[4])
 290        node_tree.links.new(shadow_mix_node.inputs[2], shadow_texture_node.outputs[1])
 291        node_tree.links.new(toon_node.inputs[0], shade_mate_node.outputs[3])
 292        node_tree.links.new(toon_mix_node.inputs[1], shadow_mix_node.outputs[0])
 293        node_tree.links.new(toon_mix_node.inputs[2], toon_node.outputs[0])
 294        node_tree.links.new(specular_mix_node.inputs[1], toon_mix_node.outputs[0])
 295        node_tree.links.new(specular_mix_node.inputs[2], shade_mate_node.outputs[4])
 296        node_tree.links.new(normal_node.inputs[0], mate_node.outputs[2])
 297        node_tree.links.new(rim_ramp_node.inputs[0], normal_node.outputs[1])
 298        node_tree.links.new(rim_power_node.inputs[4], rim_ramp_node.outputs[0])
 299        node_tree.links.new(rim_mix_node.inputs[2], rim_power_node.outputs[0])
 300        node_tree.links.new(rim_mix_node.inputs[0], shadow_rate_node.outputs[0])
 301        node_tree.links.new(rim_mix_node.inputs[1], specular_mix_node.outputs[0])
 302        node_tree.links.new(out_node.inputs[0], rim_mix_node.outputs[0])
 303        node_tree.links.new(out_node.inputs[1], mate_node.outputs[1])
 304
 305        for node in node_tree.nodes[:]:
 306            compat.set_select(node, False)
 307        node_tree.nodes.active = mate_node
 308        node_tree.nodes.active.select = True
 309
 310    else:
 311        mate.use_nodes = False
 312        mate.use_shadeless = False
 313
 314# CM3D2用マテリアルを設定に合わせて装飾
 315def decorate_material(mate, enable=True, me=None, mate_index=-1):
 316    if not enable or 'shader1' not in mate:
 317        return
 318    if compat.IS_LEGACY:
 319        return decorate_material_old(mate, enable=enable, me=me, mate_index=mate_index)
 320
 321    # luvoid : set properties of the mate
 322    shader = mate['shader1']
 323    mate.preview_render_type  = 'FLAT'
 324    mate.blend_method         = 'BLEND' if 'Trans' in shader else 'OPAQUE'
 325    mate.use_backface_culling = 'Outline' not in shader
 326
 327    # luvoid : create cm3d2 shader node group and material output node
 328    cmnode = None
 329    if not compat.IS_LEGACY:
 330        mate.use_nodes = True
 331        #cm3d2_data.clear_nodes(mate.node_tree.nodes)
 332        cmtree = bpy.data.node_groups.get('CM3D2 Shader')
 333        if not cmtree:
 334            blend_path = os.path.join(os.path.dirname(__file__), "append_data.blend")
 335            with bpy.data.libraries.load(blend_path) as (data_from, data_to):
 336                data_to.node_groups = ['CM3D2 Shader']
 337            cmtree = data_to.node_groups[0]
 338        cmnode = mate.node_tree.nodes.new('ShaderNodeGroup')
 339        cmnode.node_tree = cmtree
 340        matout = mate.node_tree.nodes.new('ShaderNodeOutputMaterial')
 341        matout.location = (300,0)
 342        mate.node_tree.links.new(matout.inputs.get('Surface'), cmnode.outputs.get('Surface'))
 343
 344    for key, node in mate.node_tree.nodes.items():
 345        if not key.startswith('_'):
 346            continue
 347        if type(node) == bpy.types.ShaderNodeTexImage:
 348            # luvoid : attatch tex node to cmnode sockets
 349            socket = cmnode.inputs.get(key+" Color")
 350            if socket:
 351                mate.node_tree.links.new(socket, node.outputs.get('Color'))
 352            socket = cmnode.inputs.get(key+" Alpha")
 353            if socket:
 354                mate.node_tree.links.new(socket, node.outputs.get('Alpha'))
 355        else:
 356            # luvoid : attatch color/float node to cmnode socket
 357            input_socket = cmnode.inputs.get(key)
 358            output_socket = node.outputs.get('Color') or node.outputs.get('Value')
 359            if input_socket and output_socket:
 360                mate.node_tree.links.new(input_socket, output_socket)
 361
 362    
 363
 364
 365# 画像のおおよその平均色を取得
 366def get_image_average_color(img, sample_count=10):
 367    if not len(img.pixels):
 368        return mathutils.Color([0, 0, 0])
 369
 370    pixel_count = img.size[0] * img.size[1]
 371    channels = img.channels
 372
 373    max_s = 0.0
 374    max_s_color, average_color = mathutils.Color([0, 0, 0]), mathutils.Color([0, 0, 0])
 375    seek_interval = pixel_count / sample_count
 376    for sample_index in range(sample_count):
 377
 378        index = int(seek_interval * sample_index) * channels
 379        color = mathutils.Color(img.pixels[index: index + 3])
 380        average_color += color
 381        if max_s < color.s:
 382            max_s_color, max_s = color, color.s
 383
 384    average_color /= sample_count
 385    output_color = (average_color + max_s_color) / 2
 386    output_color.s *= 1.5
 387    return max_s_color
 388
 389
 390# 画像のおおよその平均色を取得 (UV版)
 391def get_image_average_color_uv(img, me=None, mate_index=-1, sample_count=10):
 392    if not len(img.pixels): return mathutils.Color([0, 0, 0])
 393
 394    img_width, img_height, img_channel = img.size[0], img.size[1], img.channels
 395
 396    bm = bmesh.new()
 397    bm.from_mesh(me)
 398    uv_lay = bm.loops.layers.uv.active
 399    uvs = [l[uv_lay].uv[:] for f in bm.faces if f.material_index == mate_index for l in f.loops]
 400    bm.free()
 401
 402    if len(uvs) <= sample_count:
 403        return get_image_average_color(img)
 404
 405    average_color = mathutils.Color([0, 0, 0])
 406    max_s = 0.0
 407    max_s_color = mathutils.Color([0, 0, 0])
 408    seek_interval = len(uvs) / sample_count
 409    for sample_index in range(sample_count):
 410
 411        uv_index = int(seek_interval * sample_index)
 412        x, y = uvs[uv_index]
 413
 414        x = math.modf(x)[0]
 415        if x < 0.0:
 416            x += 1.0
 417        y = math.modf(y)[0]
 418        if y < 0.0:
 419            y += 1.0
 420
 421        x, y = int(x * img_width), int(y * img_height)
 422
 423        pixel_index = ((y * img_width) + x) * img_channel
 424        color = mathutils.Color(img.pixels[pixel_index: pixel_index + 3])
 425
 426        average_color += color
 427        if max_s < color.s:
 428            max_s_color, max_s = color, color.s
 429
 430    average_color /= sample_count
 431    output_color = (average_color + max_s_color) / 2
 432    output_color.s *= 1.5
 433    return output_color
 434
 435
 436def get_cm3d2_dir():
 437    try:
 438        import winreg
 439        with winreg.OpenKey(winreg.HKEY_CURRENT_USER, r'Software\KISS\カスタムメイド3D2') as key:
 440            return winreg.QueryValueEx(key, 'InstallPath')[0]
 441    except:
 442        return None
 443
 444
 445def get_com3d2_dir():
 446    try:
 447        import winreg
 448        with winreg.OpenKey(winreg.HKEY_CURRENT_USER, r'Software\KISS\カスタムオーダーメイド3D2') as key:
 449            return winreg.QueryValueEx(key, 'InstallPath')[0]
 450    except:
 451        return None
 452
 453
 454# CM3D2のインストールフォルダを取得+α
 455def default_cm3d2_dir(base_dir, file_name, new_ext):
 456    if not base_dir:
 457        prefs = preferences()
 458        if prefs.cm3d2_path:
 459            base_dir = os.path.join(prefs.cm3d2_path, "GameData", "*." + new_ext)
 460        else:
 461            base_dir = get_cm3d2_dir()
 462            if base_dir is None:
 463                base_dir = get_com3d2_dir()
 464
 465            if base_dir:
 466                prefs.cm3d2_path = base_dir
 467                base_dir = os.path.join(base_dir, "GameData", "*." + new_ext)
 468
 469        if base_dir is None:
 470            base_dir = "."
 471
 472    if file_name:
 473        base_dir = os.path.join(os.path.split(base_dir)[0], file_name)
 474    base_dir = os.path.splitext(base_dir)[0] + "." + new_ext
 475    return base_dir
 476
 477
 478# 一時ファイル書き込みと自動バックアップを行うファイルオブジェクトを返す
 479def open_temporary(filepath, mode, is_backup=False):
 480    backup_ext = preferences().backup_ext
 481    if is_backup and backup_ext:
 482        backup_filepath = filepath + '.' + backup_ext
 483    else:
 484        backup_filepath = None
 485    return fileutil.TemporaryFileWriter(filepath, mode, backup_filepath=backup_filepath)
 486
 487
 488# ファイルを上書きするならバックアップ処理
 489def file_backup(filepath, enable=True):
 490    backup_ext = preferences().backup_ext
 491    if enable and backup_ext and os.path.exists(filepath):
 492        shutil.copyfile(filepath, filepath + "." + backup_ext)
 493
 494
 495# サブフォルダを再帰的に検索してリスト化
 496def find_tex_all_files(dir):
 497    for root, dirs, files in os.walk(dir):
 498        for f in files:
 499            ext = os.path.splitext(f)[1].lower()
 500            if ext == ".tex" or ext == ".png":
 501                yield os.path.join(root, f)
 502
 503
 504# テクスチャ置き場のパスのリストを返す
 505def get_default_tex_paths():
 506    prefs = preferences()
 507    default_paths = [prefs.default_tex_path0, prefs.default_tex_path1, prefs.default_tex_path2, prefs.default_tex_path3]
 508    if not any(default_paths):
 509        target_dirs = []
 510        cm3d2_dir = prefs.cm3d2_path
 511        if not cm3d2_dir:
 512            cm3d2_dir = get_cm3d2_dir()
 513
 514        if cm3d2_dir:
 515            target_dirs.append(os.path.join(cm3d2_dir, "GameData", "texture"))
 516            target_dirs.append(os.path.join(cm3d2_dir, "GameData", "texture2"))
 517            target_dirs.append(os.path.join(cm3d2_dir, "Sybaris", "GameData"))
 518            target_dirs.append(os.path.join(cm3d2_dir, "Mod"))
 519
 520        # com3d2_dir = prefs.com3d2_path
 521        # if not com3d2_dir:
 522        #     com3d2_dir = get_cm3d2_dir()
 523        # if com3d2_dir:
 524        #     target_dirs.append(os.path.join(com3d2_dir, "GameData", "parts"))
 525        #     target_dirs.append(os.path.join(com3d2_dir, "GameData", "parts2"))
 526        #     target_dirs.append(os.path.join(com3d2_dir, "MOD"))
 527
 528        tex_dirs = [path for path in target_dirs if os.path.isdir(path)]
 529
 530        for index, path in enumerate(tex_dirs):
 531            setattr(prefs, 'default_tex_path' + str(index), path)
 532    else:
 533        tex_dirs = [getattr(prefs, 'default_tex_path' + str(i)) for i in range(4) if getattr(prefs, 'default_tex_path' + str(i))]
 534    return tex_dirs
 535
 536
 537# テクスチャ置き場の全ファイルを返す
 538def get_tex_storage_files():
 539    files = []
 540    tex_dirs = get_default_tex_paths()
 541    for tex_dir in tex_dirs:
 542        tex_dir = bpy.path.abspath(tex_dir)
 543        files.extend(find_tex_all_files(tex_dir))
 544    return files
 545
 546
 547def get_texpath_dict(reload=False):
 548    if reload or len(texpath_dict) == 0:
 549        texpath_dict.clear()
 550        tex_dirs = get_default_tex_paths()
 551        for tex_dir in tex_dirs:
 552            for path in find_tex_all_files(tex_dir):
 553                path = bpy.path.abspath(path)
 554                file_name = os.path.basename(path).lower()
 555                # 先に見つけたファイルを優先
 556                if file_name not in texpath_dict:
 557                    texpath_dict[file_name] = path
 558    return texpath_dict
 559
 560
 561def reload_png(img, texpath_dict, png_name):
 562    png_path = texpath_dict.get(png_name)
 563    if png_path:
 564        img.filepath = png_path
 565        img.reload()
 566        return True
 567    return False
 568
 569
 570def replace_cm3d2_tex(img, texpath_dict: dict=None, reload_path: bool=True) -> bool:
 571    """replace tex file.
 572    pngファイルを先に走査し、見つからなければtexファイルを探す.
 573    texはpngに展開して読み込みを行う.
 574    reload_path=Trueの場合、png,texファイルが見つからない場合にキャッシュを再構成し、
 575    再度検索を行う.
 576
 577    Parameters:
 578        img (Image): イメージオブジェクト
 579        texpath_dict (dict): テクスチャパスのdict (キャッシュ)
 580        reload_path (bool): 見つからない場合にキャッシュを再読込するか
 581
 582    Returns:
 583        bool: tex load successful
 584    """
 585    if texpath_dict is None:
 586        texpath_dict = get_texpath_dict()
 587
 588    if __replace_cm3d2_tex(img, texpath_dict):
 589        return True
 590    if reload_path:
 591        texpath_dict = get_texpath_dict(True)
 592        return __replace_cm3d2_tex(img, texpath_dict)
 593    return False
 594
 595
 596def __replace_cm3d2_tex(img, texpath_dict: dict) -> bool:
 597    source_name = remove_serial_number(img.name).lower()
 598
 599    source_png_name = source_name + ".png"
 600    if reload_png(img, texpath_dict, source_png_name):
 601        return True
 602
 603    source_tex_name = source_name + ".tex"
 604    tex_path = texpath_dict.get(source_tex_name)
 605    try:
 606        if tex_path is None:
 607            return False
 608        tex_data = load_cm3d2tex(tex_path)
 609        if tex_data is None:
 610            return False
 611        
 612        png_path = tex_path[:-4] + ".png"
 613        with open(png_path, 'wb') as png_file:
 614            png_file.write(tex_data[-1])
 615        img.filepath = png_path
 616        img.reload()
 617        return True
 618    except:
 619        pass
 620    return False
 621
 622
 623# texファイルの読み込み
 624def load_cm3d2tex(path, skip_data=False):
 625
 626    with open(path, 'rb') as file:
 627        header_ext = read_str(file)
 628        if header_ext != 'CM3D2_TEX':
 629            return None
 630        version = struct.unpack('<i', file.read(4))[0]
 631        read_str(file)
 632
 633        # default value
 634        tex_format = 5
 635        uv_rects = None
 636        data = None
 637        if version >= 1010:
 638            if version >= 1011:
 639                num_rect = struct.unpack('<i', file.read(4))[0]
 640                uv_rects = []
 641                for i in range(num_rect):
 642                    # x, y, w, h
 643                    uv_rects.append(struct.unpack('<4f', file.read(4 * 4)))
 644            width = struct.unpack('<i', file.read(4))[0]
 645            height = struct.unpack('<i', file.read(4))[0]
 646            tex_format = struct.unpack('<i', file.read(4))[0]
 647            # if tex_format == 10 or tex_format == 12: return None
 648        if not skip_data:
 649            png_size = struct.unpack('<i', file.read(4))[0]
 650            data = file.read(png_size)
 651        return version, tex_format, uv_rects, data
 652
 653
 654def create_tex(context, mate, node_name, tex_name=None, filepath=None, cm3d2path=None, tex_map_data=None, replace_tex=False, slot_index=-1):
 655    if isinstance(context, bpy.types.Context):
 656        context = context.copy()
 657
 658    if compat.IS_LEGACY:
 659        slot = mate.texture_slots.create(slot_index)
 660        tex = context['blend_data'].textures.new(node_name, 'IMAGE')
 661        slot.texture = tex
 662
 663        if tex_name:
 664            slot.offset[0] = tex_map_data[0]
 665            slot.offset[1] = tex_map_data[1]
 666            slot.scale[0] = tex_map_data[2]
 667            slot.scale[1] = tex_map_data[3]
 668
 669            if os.path.exists(filepath):
 670                img = bpy.data.images.load(filepath)
 671                img.name = tex_name
 672            else:
 673                img = context['blend_data'].images.new(tex_name, 128, 128)
 674                img.filepath = filepath
 675            img['cm3d2_path'] = cm3d2path
 676            img.source = 'FILE'
 677            tex.image = img
 678
 679            if replace_tex:
 680                replaced = replace_cm3d2_tex(tex.image, reload_path=False)
 681                if replaced and node_name == '_MainTex':
 682                    ob = context['active_object']
 683                    me = ob.data
 684                    for face in me.polygons:
 685                        if face.material_index == ob.active_material_index:
 686                            me.uv_textures.active.data[face.index].image = tex.image
 687
 688    else:
 689        # if mate.use_nodes is False:
 690        # 	mate.use_nodes = True
 691        nodes = mate.node_tree.nodes
 692        tex = nodes.get(node_name)
 693        if tex is None:
 694            tex = mate.node_tree.nodes.new(type='ShaderNodeTexImage')
 695            tex.name = tex.label = node_name
 696            tex.show_texture = True
 697
 698        if tex_name:
 699            if tex.image is None:
 700                if os.path.exists(filepath):
 701                    img = bpy.data.images.load(filepath)
 702                    img.name = tex_name
 703                else:
 704                    img = context['blend_data'].images.new(tex_name, 128, 128)
 705                    img.filepath = filepath
 706                img.source = 'FILE'
 707                tex.image = img
 708                img['cm3d2_path'] = cm3d2path
 709            else:
 710                img = tex.image
 711                path = img.get('cm3d2_path')
 712                if path != cm3d2path:
 713                    img['cm3d2_path'] = cm3d2path
 714                    img.filepath = filepath
 715
 716            tex_map = tex.texture_mapping
 717            tex_map.translation[0] = tex_map_data[0]
 718            tex_map.translation[1] = tex_map_data[1]
 719            tex_map.scale[0] = tex_map_data[2]
 720            tex_map.scale[1] = tex_map_data[3]
 721
 722        # tex.color = tex_data['color'][:3]
 723        # tex.outputs['Color'].default_value = tex_data['color'][:]
 724        # tex.outputs['ALpha'].default_value = tex_data['color'][3]
 725
 726            # tex探し
 727            if replace_tex:
 728                replaced = replace_cm3d2_tex(tex.image, reload_path=False)
 729                # TODO 2.8での実施方法を調査. shader editorで十分?
 730
 731    return tex
 732
 733
 734def create_col(context, mate, node_name, color, slot_index=-1):
 735    if isinstance(context, bpy.types.Context):
 736        context = context.copy()
 737
 738    if compat.IS_LEGACY:
 739        if slot_index >= 0:
 740            mate.use_textures[slot_index] = False
 741        node = mate.texture_slots.create(slot_index)
 742        node.color = color[:3]
 743        node.diffuse_color_factor = color[3]
 744        node.use_rgb_to_intensity = True
 745        tex = context['blend_data'].textures.new(node_name, 'BLEND')
 746        node.texture = tex
 747        node.use = False
 748    else:
 749        node = mate.node_tree.nodes.get(node_name)
 750        if node is None:
 751            node = mate.node_tree.nodes.new(type='ShaderNodeRGB')
 752            node.name = node.label = node_name
 753        node.outputs[0].default_value = color
 754
 755    return node
 756
 757
 758def create_float(context, mate, node_name, value, slot_index=-1):
 759    if isinstance(context, bpy.types.Context):
 760        context = context.copy()
 761
 762    if compat.IS_LEGACY:
 763        if slot_index >= 0:
 764            mate.use_textures[slot_index] = False
 765        node = mate.texture_slots.create(slot_index)
 766        node.diffuse_color_factor = value
 767        node.use_rgb_to_intensity = False
 768        tex = context['blend_data'].textures.new(node_name, 'BLEND')
 769        node.texture = tex
 770        node.use = False
 771    else:
 772        node = mate.node_tree.nodes.get(node_name)
 773        if node is None:
 774            node = mate.node_tree.nodes.new(type='ShaderNodeValue')
 775            node.name = node.label = node_name
 776        node.outputs[0].default_value = value
 777
 778    return node
 779
 780
 781def setup_material(mate):
 782    if mate:
 783        if 'CM3D2 Texture Expand' not in mate:
 784            mate['CM3D2 Texture Expand'] = True
 785
 786        if not compat.IS_LEGACY:
 787            mate.use_nodes = True
 788
 789
 790def setup_image_name(img):
 791    """イメージの名前から拡張子を除外する"""
 792    # consider case with serial number. ex) sample.png.001
 793    img.name = re_png.sub(r'\1', img.name)
 794
 795
 796def get_tex_cm3d2path(filepath):
 797    return BASE_PATH_TEX + os.path.basename(filepath)
 798
 799
 800def to_cm3d2path(path):
 801    path = path.replace('\\', '/')
 802    path = re_prefix.sub('', path)
 803    if not re_path_prefix.search(path):
 804        path = get_tex_cm3d2path(path)
 805    return path
 806
 807
 808# col f タイプの設定値を値に合わせて着色
 809def set_texture_color(slot):
 810    if not slot or not slot.texture or slot.use:
 811        return
 812
 813    slot_type = 'col' if slot.use_rgb_to_intensity else 'f'
 814    tex = slot.texture
 815    base_name = remove_serial_number(tex.name)
 816    tex.type = 'BLEND'
 817
 818    if hasattr(tex, 'progression'):
 819        tex.progression = 'DIAGONAL'
 820    tex.use_color_ramp = True
 821    tex.use_preview_alpha = True
 822    elements = tex.color_ramp.elements
 823
 824    element_count = 4
 825    if element_count < len(elements):
 826        for i in range(len(elements) - element_count):
 827            elements.remove(elements[-1])
 828    elif len(elements) < element_count:
 829        for i in range(element_count - len(elements)):
 830            elements.new(1.0)
 831
 832    elements[0].position, elements[1].position, elements[2].position, elements[3].position = 0.2, 0.21, 0.25, 0.26
 833
 834    if slot_type == 'col':
 835        elements[0].color = [0.2, 1, 0.2, 1]
 836        elements[-1].color = slot.color[:] + (slot.diffuse_color_factor, )
 837        if 0.3 < mathutils.Color(slot.color[:3]).v:
 838            elements[1].color, elements[2].color = [0, 0, 0, 1], [0, 0, 0, 1]
 839        else:
 840            elements[1].color, elements[2].color = [1, 1, 1, 1], [1, 1, 1, 1]
 841
 842    elif slot_type == 'f':
 843        elements[0].color = [0.2, 0.2, 1, 1]
 844        multi = 1.0
 845        if base_name == '_OutlineWidth':
 846            multi = 200
 847        elif base_name == '_RimPower':
 848            multi = 1.0 / 30.0
 849        value = slot.diffuse_color_factor * multi
 850        elements[-1].color = [value, value, value, 1]
 851        if 0.3 < value:
 852            elements[1].color, elements[2].color = [0, 0, 0, 1], [0, 0, 0, 1]
 853        else:
 854            elements[1].color, elements[2].color = [1, 1, 1, 1], [1, 1, 1, 1]
 855
 856
 857# 必要なエリアタイプを設定を変更してでも取得
 858def get_request_area(context, request_type, except_types=None):
 859    if except_types is None:
 860        except_types = ['VIEW_3D', 'PROPERTIES', 'INFO', compat.pref_type()]
 861
 862    request_areas = [(a, a.width * a.height) for a in context.screen.areas if a.type == request_type]
 863    candidate_areas = [(a, a.width * a.height) for a in context.screen.areas if a.type not in except_types]
 864
 865    return_areas = request_areas[:] if len(request_areas) else candidate_areas
 866    if not len(return_areas):
 867        return None
 868
 869    return_areas.sort(key=lambda i: i[1])
 870    return_area = return_areas[-1][0]
 871    return_area.type = request_type
 872    return return_area
 873
 874
 875# 複数のデータを完全に削除
 876def remove_data(target_data):
 877    try:
 878        target_data = target_data[:]
 879    except:
 880        target_data = [target_data]
 881
 882    if compat.IS_LEGACY:
 883        for data in target_data:
 884            if data.__class__.__name__ == 'Object':
 885                if data.name in bpy.context.scene.objects:
 886                    bpy.context.scene.objects.unlink(data)
 887    else:
 888        for data in target_data:
 889            if data.__class__.__name__ == 'Object':
 890                if data.name in bpy.context.scene.collection.objects:
 891                    bpy.context.scene.collection.objects.unlink(data)
 892
 893    # https://developer.blender.org/T49837
 894    # によると、xxx.remove(data, do_unlink=True)で十分
 895    #
 896    # for data in target_data:
 897    # 	users = getattr(data, 'users')
 898    # 	if users and 'user_clear' in dir(data):
 899    # 		data.user_clear()
 900
 901    for data in target_data:
 902        for data_str in dir(bpy.data):
 903            if not data_str.endswith('s'):
 904                continue
 905            try:
 906                if data.__class__.__name__ == eval('bpy.data.%s[0].__class__.__name__' % data_str):
 907                    exec('bpy.data.%s.remove(data, do_unlink=True)' % data_str)
 908                    break
 909            except:
 910                pass
 911
 912
 913# オブジェクトのマテリアルを削除/復元するクラス
 914class material_restore:
 915    def __init__(self, ob):
 916        override = bpy.context.copy()
 917        override['object'] = ob
 918        self.object = ob
 919
 920        self.slots = [slot.material if slot.material else None for slot in ob.material_slots]
 921
 922        self.mesh_data = []
 923        for index, slot in enumerate(ob.material_slots):
 924            mesh_datum = []
 925            for face in ob.data.polygons:
 926                if face.material_index == index:
 927                    mesh_datum.append(face.index)
 928            self.mesh_data.append(mesh_datum)
 929
 930        for slot in ob.material_slots[:]:
 931            bpy.ops.object.material_slot_remove(override)
 932
 933    def restore(self):
 934        override = bpy.context.copy()
 935        override['object'] = self.object
 936
 937        for slot in self.object.material_slots[:]:
 938            bpy.ops.object.material_slot_remove(override)
 939
 940        for index, mate in enumerate(self.slots):
 941            bpy.ops.object.material_slot_add(override)
 942            slot = self.object.material_slots[index]
 943            if slot:
 944                slot.material = mate
 945            for face_index in self.mesh_data[index]:
 946                self.object.data.polygons[face_index].material_index = index
 947
 948
 949# 現在のレイヤー内のオブジェクトをレンダリングしなくする/戻す
 950class hide_render_restore:
 951    def __init__(self, render_objects=[]):
 952        try:
 953            render_objects = render_objects[:]
 954        except:
 955            render_objects = [render_objects]
 956
 957        if not len(render_objects):
 958            render_objects = bpy.context.selected_objects[:]
 959
 960        self.render_objects = render_objects[:]
 961        self.render_object_names = [ob.name for ob in render_objects]
 962
 963        self.rendered_objects = []
 964        for ob in render_objects:
 965            if ob.hide_render:
 966                self.rendered_objects.append(ob)
 967                ob.hide_render = False
 968
 969        self.hide_rendered_objects = []
 970        if compat.IS_LEGACY:
 971            for ob in bpy.data.objects:
 972                for layer_index, is_used in enumerate(bpy.context.scene.layers):
 973                    if not is_used:
 974                        continue
 975                    if ob.layers[layer_index] and is_used and ob.name not in self.render_object_names and not ob.hide_render:
 976                        self.hide_rendered_objects.append(ob)
 977                        ob.hide_render = True
 978                        break
 979        else:
 980            clct_children = bpy.context.scene.collection.children
 981            for ob in bpy.data.objects:
 982                if ob.name not in self.render_object_names and not ob.hide_render:
 983                    # ble-2.8ではlayerではなく、collectionからのリンクで判断
 984                    for clct in bpy.context.window.view_layer.layer_collection.children:
 985                        if clct.exclude is False and ob.name in clct_children[clct.name].objects.keys():
 986                            self.hide_rendered_objects.append(ob)
 987                            ob.hide_render = True
 988                            break
 989
 990    def restore(self):
 991        for ob in self.rendered_objects:
 992            ob.hide_render = True
 993        for ob in self.hide_rendered_objects:
 994            ob.hide_render = False
 995
 996
 997# 指定エリアに変数をセット
 998def set_area_space_attr(area, attr_name, value):
 999    if not area:
1000        return
1001    for space in area.spaces:
1002        if space.type == area.type:
1003            space.__setattr__(attr_name, value)
1004            break
1005
1006
1007# スムーズなグラフを返す1
1008def in_out_quad_blend(f):
1009    if f <= 0.5:
1010        return 2.0 * math.sqrt(f)
1011    f -= 0.5
1012    return 2.0 * f * (1.0 - f) + 0.5
1013
1014
1015# スムーズなグラフを返す2
1016def bezier_blend(f):
1017    return math.sqrt(f) * (3.0 - 2.0 * f)
1018
1019
1020# 三角関数でスムーズなグラフを返す
1021def trigonometric_smooth(x):
1022    return math.sin((x - 0.5) * math.pi) * 0.5 + 0.5
1023
1024
1025# エクスポート例外クラス
1026class CM3D2ExportException(Exception):
1027    pass
1028
1029class CM3D2ImportException(Exception):
1030    pass
1031
1032
1033# ノード取得クラス
1034class NodeHandler():
1035    node_name = bpy.props.StringProperty(name='NodeName')
1036
1037    def get_node(self, context):
1038        mate = context.material
1039        if mate and mate.use_nodes:
1040            return mate.node_tree.nodes.get(self.node_name)
1041
1042            # if node is None:
1043            # # 見つからない場合は、シリアル番号付きのノードを探す
1044            # prefix = self.node_name + '.'
1045            # for n in nodes:
1046            # 	if n.name.startwith(prefix):
1047            # 		node = n
1048            # 		break
1049
1050        return None
1051
1052#@compat.BlRegister()
1053class CNV_UL_generic_selector(bpy.types.UIList):
1054    bl_label       = "CNV_UL_generic_selector"
1055    bl_options     = {'DEFAULT_CLOSED'}
1056    bl_region_type = 'WINDOW'
1057    bl_space_type  = 'PROPERTIES'
1058
1059    # Constants (flags)
1060    # Be careful not to shadow FILTER_ITEM!
1061    #bitflag_soft_filter = 1073741824 >> 0
1062    bitflag_soft_filter  = 1073741824 >> 3
1063
1064    bitflag_forced_value = 1073741824 >> 10
1065    bitflag_forced_true  = 1073741824 >> 11
1066    bitflag_forced_false = 1073741824 >> 12
1067
1068    
1069    cached_values = {}
1070    expanded_layout = False
1071
1072    # Custom properties, saved with .blend file.
1073    use_filter_name_reverse = bpy.props.BoolProperty(
1074        name="Reverse Name",
1075        default=False,
1076        options=set(),
1077        description="Reverse name filtering",
1078    )
1079    #use_filter_deform = bpy.props.BoolProperty(
1080    #    name="Only Deform",
1081    #    default=True,
1082    #    options=set(),
1083    #    description="Only show deforming vertex groups",
1084    #)
1085    #use_filter_deform_reverse = bpy.props.BoolProperty(
1086    #    name="Other",
1087    #    default=False,
1088    #    options=set(),
1089    #    description="Only show non-deforming vertex groups",
1090    #)
1091    #use_filter_empty = bpy.props.BoolProperty(
1092    #    name="Filter Empty",
1093    #    default=False,
1094    #    options=set(),
1095    #    description="Whether to filter empty vertex groups",
1096    #)
1097    #use_filter_empty_reverse = bpy.props.BoolProperty(
1098    #    name="Reverse Empty",
1099    #    default=False,
1100    #    options=set(),
1101    #    description="Reverse empty filtering",
1102    #)
1103    
1104    # This allows us to have mutually exclusive options, which are also all disable-able!
1105    def _gen_order_update(name1, name2):
1106        def _u(self, ctxt):
1107            if (getattr(self, name1)):
1108                setattr(self, name2, False)
1109        return _u
1110    use_order_name = bpy.props.BoolProperty(
1111        name="Name", default=False, options=set(),
1112        description="Sort groups by their name (case-insensitive)",
1113        update=_gen_order_update("use_order_name", "use_order_importance"),
1114    )
1115    use_filter_orderby_invert = bpy.props.BoolProperty(
1116        name="Order by Invert",
1117        default=False,
1118        options=set(),
1119        description="Invert the sort by order"
1120    )
1121    #use_order_importance = bpy.props.BoolProperty(
1122    #    name="Importance",
1123    #    default=False,
1124    #    options=set(),
1125    #    description="Sort groups by their average weight in the mesh",
1126    #    update=_gen_order_update("use_order_importance", "use_order_name"),
1127    #)
1128        
1129    # Usual draw item function.
1130    def draw_item(self, context, layout, data, item, icon_value, active_data, active_propname, index, flt_flag):
1131        # Just in case, we do not use it here!
1132        self.use_filter_invert = False
1133
1134        # assert(isinstance(item, bpy.types.VertexGroup)
1135        #vgroup = getattr(data, 'matched_vgroups')[item.index]
1136        if self.layout_type in {'DEFAULT', 'COMPACT'}:
1137            # Here we use one feature of new filtering feature: it can pass data to draw_item, through flt_flag
1138            # parameter, which contains exactly what filter_items set in its filter list for this item!
1139            # In this case, we show empty groups grayed out.
1140            cached_value = self.cached_values.get(item.name, None)
1141            if (cached_value != None) and (cached_value != item.value):
1142                item.preferred = item.value
1143
1144            force_values = flt_flag & self.bitflag_forced_value
1145            print("GET force_values =", force_values)
1146            if force_values:
1147                print("FORCE VALUES")
1148                if flt_flag & self.bitflag_forced_true:
1149                    item.value = True
1150                elif flt_flag & self.bitflag_forced_false:
1151                    item.value = False
1152                else:
1153                    item.value = item.preferred
1154
1155            self.cached_values[item.name] = item.value
1156
1157            if flt_flag & self.bitflag_soft_filter:
1158                row = layout.row()
1159                row.enabled = False
1160                #row.alignment = 'LEFT'
1161                row.prop(item, "value", text=item.name, icon=item.icon)
1162            else:
1163                layout.prop(item, "value", text=item.name, icon=item.icon)
1164            
1165            #layout.prop(item, "value", text=item.name, icon=item.icon)
1166            icon = 'RADIOBUT_ON' if item.preferred else 'RADIOBUT_OFF'
1167            layout.prop(item, "preferred", text="", icon=compat.icon(icon), emboss=False)
1168        
1169        elif self.layout_type in {'GRID'}:
1170            layout.alignment = 'CENTER'
1171            if flt_flag & self.VGROUP_EMPTY:
1172                layout.enabled = False
1173            layout.label(text="", icon_value=icon)
1174
1175    def draw_filter(self, context, layout):
1176        # Nothing much to say here, it's usual UI code...
1177        row = layout.row()
1178        if not self.expanded_layout:
1179            layout.active = True
1180            layout.enabled = True
1181            row.active = True
1182            row.enabled = True
1183            self.expanded_layout = True
1184
1185        subrow = row.row(align=True)
1186        subrow.prop(self, "filter_name", text="")
1187        icon = 'ZOOM_OUT' if self.use_filter_name_reverse else 'ZOOM_IN'
1188        subrow.prop(self, "use_filter_name_reverse", text="", icon=icon)
1189
1190        #subrow = row.row(align=True)
1191        #subrow.prop(self, "use_filter_deform", toggle=True)
1192        #icon = 'ZOOM_OUT' if self.use_filter_deform_reverse else 'ZOOM_IN'
1193        #subrow.prop(self, "use_filter_deform_reverse", text="", icon=icon)
1194
1195        #subrow = row.row(align=True)
1196        #subrow.prop(self, "use_filter_empty", toggle=True)
1197        #icon = 'ZOOM_OUT' if self.use_filter_empty_reverse else 'ZOOM_IN'
1198        #subrow.prop(self, "use_filter_empty_reverse", text="", icon=icon)
1199
1200        row = layout.row(align=True)
1201        row.label(text="Order by:")
1202        row.prop(self, "use_order_name", toggle=True)
1203        #row.prop(self, "use_order_importance", toggle=True)
1204        icon = 'TRIA_UP' if self.use_filter_orderby_invert else 'TRIA_DOWN'
1205        row.prop(self, "use_filter_orderby_invert", text="", icon=icon)
1206
1207    def filter_items(self, context, data, propname):
1208        # This function gets the collection property (as the usual tuple (data, propname)), and must return two lists:
1209        # * The first one is for filtering, it must contain 32bit integers were self.bitflag_filter_item marks the
1210        #   matching item as filtered (i.e. to be shown), and 31 other bits are free for custom needs. Here we use the
1211        #   first one to mark VGROUP_EMPTY.
1212        # * The second one is for reordering, it must return a list containing the new indices of the items (which
1213        #   gives us a mapping org_idx -> new_idx).
1214        # Please note that the default UI_UL_list defines helper functions for common tasks (see its doc for more info).
1215        # If you do not make filtering and/or ordering, return empty list(s) (this will be more efficient than
1216        # returning full lists doing nothing!).
1217        items = getattr(data, propname)
1218        
1219        #if self.armature == None:
1220        #    target_ob, source_ob = common.get_target_and_source_ob(context)
1221        #    armature_ob = target_ob.find_armature() or source_ob.find_armature()
1222        #    self.armature = armature_ob and armature_ob.data or False
1223        #
1224        #if not self.local_bone_names:
1225        #    target_ob, source_ob = common.get_target_and_source_ob(context)
1226        #    bone_data_ob = (target_ob.get("LocalBoneData:0") and target_ob) or (source_ob.get("LocalBoneData:0") and source_ob) or None
1227        #    if bone_data_ob:
1228        #        local_bone_data = model_export.CNV_OT_export_cm3d2_model.local_bone_data_parser(model_export.CNV_OT_export_cm3d2_model.indexed_data_generator(bone_data_ob, prefix="LocalBoneData:"))
1229        #        self.local_bone_names = [ bone['name'] for bone in local_bone_data ]
1230        
1231        if not self.cached_values:
1232            self.cached_values = { item.name: item.value for item in items }
1233        #vgroups = [ getattr(data, 'matched_vgroups')[item.index][0]   for item in items ]
1234        helper_funcs = bpy.types.UI_UL_list
1235
1236        # Default return values.
1237        flt_flags = []
1238        flt_neworder = []
1239
1240        # Pre-compute of vgroups data, CPU-intensive. :/
1241        #vgroups_empty = self.filter_items_empty_vgroups(context, vgroups)
1242
1243        # Filtering by name
1244        if self.filter_name:
1245            flt_flags = helper_funcs.filter_items_by_name(self.filter_name, self.bitflag_filter_item, items, "name",
1246                                                          reverse=self.use_filter_name_reverse)
1247        if not flt_flags:
1248            flt_flags = [self.bitflag_filter_item] * len(items)
1249        
1250        #for idx, vg in enumerate(items):
1251        #    # Filter by deform.
1252        #    if self.use_filter_deform:
1253        #        flt_flags[idx] |= self.VGROUP_DEFORM
1254        #        if self.use_filter_deform:
1255        #            if self.armature and self.armature.get(vg.name):
1256        #                if not self.use_filter_deform_reverse:
1257        #                    flt_flags[idx] &= ~self.VGROUP_DEFORM
1258        #            elif bone_data_ob and (vg.name in self.local_bone_names):
1259        #                if not self.use_filter_deform_reverse:
1260        #                    flt_flags[idx] &= ~self.VGROUP_DEFORM
1261        #            elif self.use_filter_deform_reverse or (not self.armature and not self.local_bone_names):
1262        #                flt_flags[idx] &= ~self.VGROUP_DEFORM
1263        #    else:
1264        #        flt_flags[idx] &= ~self.VGROUP_DEFORM
1265        #
1266        #    # Filter by emptiness.
1267        #    #if vgroups_empty[vg.index][0]:
1268        #    #    flt_flags[idx] |= self.VGROUP_EMPTY
1269        #    #    if self.use_filter_empty and self.use_filter_empty_reverse:
1270        #    #        flt_flags[idx] &= ~self.bitflag_filter_item
1271        #    #elif self.use_filter_empty and not self.use_filter_empty_reverse:
1272        #    #    flt_flags[idx] &= ~self.bitflag_filter_item
1273        
1274        # Reorder by name or average weight.
1275        if self.use_order_name:
1276            flt_neworder = helper_funcs.sort_items_by_name(items, "name")
1277        #elif self.use_order_importance:
1278        #    _sort = [(idx, vgroups_empty[vg.index][1]) for idx, vg in enumerate(vgroups)]
1279        #    flt_neworder = helper_funcs.sort_items_helper(_sort, lambda e: e[1], True)
1280
1281        return flt_flags, flt_neworder
1282
1283
1284
1285@compat.BlRegister()
1286class CNV_SelectorItem(bpy.types.PropertyGroup):
1287    bl_label       = "CNV_SelectorItem"
1288    bl_region_type = 'WINDOW'
1289    bl_space_type  = 'PROPERTIES'
1290
1291    name      = bpy.props.StringProperty (name="Name"    , default="Unknown")
1292    value     = bpy.props.BoolProperty   (name="Value"   , default=True     )
1293    index     = bpy.props.IntProperty    (name="Index"   , default=-1       )
1294    preferred = bpy.props.BoolProperty   (name="Prefered", default=True     )
1295    icon      = bpy.props.StringProperty (name="Icon"    , default='NONE'   )
1296
1297    filter0   = bpy.props.BoolProperty   (name="Filter 0", default=False    )
1298    filter1   = bpy.props.BoolProperty   (name="Filter 1", default=False    )
1299    filter2   = bpy.props.BoolProperty   (name="Filter 2", default=False    )
1300    filter3   = bpy.props.BoolProperty   (name="Filter 3", default=False    )
1301
1302
1303
1304
1305
1306# luvoid : for loop helper returns values with matching keys
1307def values_of_matched_keys(dict1, dict2):
1308    value_list = []
1309    items1 = dict1.items()
1310    items2 = dict2.items()
1311    if len(items1) <= len(items2): 
1312        items1.reverse()
1313        for k1, v1 in items1:
1314            for i in range(len(items2)-1, 0-1, -1):
1315                k2, v2 = items2[i]
1316                if k1 == k2:
1317                    value_list.append((v1,v2))
1318                    del items2[i]
1319    else:
1320        items2.reverse()
1321        for k2, v2 in items2:
1322            for i in range(len(items1)-1, 0-1, -1):
1323                k1, v1 = items1[i]
1324                if k1 == k2:
1325                    value_list.append((v1,v2))
1326                    del items1[i]
1327    
1328    value_list.reverse()
1329    return value_list
1330
1331
1332# luvoid : helper to easily get source and target objects
1333def get_target_and_source_ob(context, copyTarget=False, copySource=False):
1334    target_ob = None
1335    source_ob = None
1336    target_original_ob = None
1337    source_original_ob = None
1338
1339    target_original_ob = context.object
1340    if copyTarget:
1341        target_ob = target_original_ob.copy()
1342        target_ob.data = target_original_ob.data.copy()
1343    else:
1344        target_ob = target_original_ob
1345
1346    for ob in context.selected_objects:
1347        if ob != target_ob:
1348            source_original_ob = ob
1349            break
1350    
1351    if copySource:
1352        source_ob = source_original_ob.copy()
1353        source_ob.data = source_original_ob.data.copy()
1354    else:
1355        source_ob = source_original_ob
1356    
1357    if copyTarget:
1358        if copySource:
1359            return target_ob, source_ob, target_original_ob, source_original_ob
1360        else:
1361            return target_ob, source_ob, target_original_ob
1362    elif copySource:
1363        return  target_ob, source_ob, source_original_ob
1364    else:
1365        return  target_ob, source_ob
1366
1367
1368# luvoid
1369def is_descendant_of(bone, ancestor) -> bool:
1370    """Returns true if a bone is the descendant of the given ancestor"""
1371    while bone.parent:
1372        bone = bone.parent
1373        if bone.name == ancestor.name:
1374            return True
1375    return False
bl_info = {'name': 'CM3D2 Converter', 'author': '@saidenka_cm3d2, @trzrz, @luvoid', 'version': ('luv', 2023, 6, '11a'), 'blender': (2, 80, 0), 'location': 'ファイル > インポート/エクスポート > CM3D2 Model (.model)', 'description': 'カスタムメイド3D2/カスタムオーダーメイド3D2専用ファイルのインポート/エクスポートを行います', 'warning': '', 'wiki_url': 'https://github.com/luvoid/Blender-CM3D2-Converter/blob/bl_28/translations/en_US/README.md', 'tracker_url': 'https://github.com/luvoid/Blender-CM3D2-Converter', 'category': 'Import-Export'}
ADDON_NAME = 'CM3D2 Converter'
BASE_PATH_TEX = 'Assets/texture/texture/'
BRANCH = 'bl_28'
URL_REPOS = 'https://github.com/luvoid/Blender-CM3D2-Converter/'
URL_ATOM = 'https://github.com/luvoid/Blender-CM3D2-Converter/commits/{branch}.atom'
URL_MODULE = 'https://github.com/luvoid/Blender-CM3D2-Converter/archive/{branch}.zip'
KISS_ICON = None
PREFS = None
preview_collections = {'main': <ImagePreviewCollection id=0x7f6a190195d0[1], <super: <class 'ImagePreviewCollection'>, <ImagePreviewCollection object>>>}
texpath_dict = {}
re_png = re.compile('\\.[Pp][Nn][Gg](\\.\\d{3})?$')
re_serial = re.compile('(\\.\\d{3})$')
re_prefix = re.compile('^[\\/\\.]*')
re_path_prefix = re.compile('^assets/', re.IGNORECASE)
re_ext_png = re.compile('\\.png$', re.IGNORECASE)
re_bone1 = re.compile('([_ ])\\*([_ ].*)\\.([rRlL])$')
re_bone2 = re.compile('([_ ])([rRlL])([_ ].*)$')
def preferences():
38def preferences():
39    global PREFS
40    if PREFS is None:
41        PREFS = compat.get_prefs(bpy.context).addons[__package__].preferences
42    return PREFS
def kiss_icon():
45def kiss_icon():
46    global KISS_ICON
47    if KISS_ICON is None:
48        KISS_ICON = preview_collections['main']['KISS'].icon_id
49    return KISS_ICON
def remove_serial_number(name, enable=True):
53def remove_serial_number(name, enable=True):
54    return re_serial.sub('', name) if enable else name
def has_serial_number(name):
58def has_serial_number(name):
59    return re_serial.search(name) is not None
def line_trim(line, enable=True):
63def line_trim(line, enable=True):
64    return line.strip('  \t\r\n') if enable else line
def write_str(file, raw_str):
68def write_str(file, raw_str):
69    b_str = format(len(raw_str.encode('utf-8')), 'b')
70    for i in range(9):
71        if 7 < len(b_str):
72            file.write(struct.pack('<B', int("1" + b_str[-7:], 2)))
73            b_str = b_str[:-7]
74        else:
75            file.write(struct.pack('<B', int(b_str, 2)))
76            break
77    file.write(raw_str.encode('utf-8'))
def pack_str(buffer, raw_str):
79def pack_str(buffer, raw_str):
80    b_str = format(len(raw_str.encode('utf-8')), 'b')
81    for i in range(9):
82        if 7 < len(b_str):
83            buffer = buffer + struct.pack('<B', int("1" + b_str[-7:], 2))
84            b_str = b_str[:-7]
85        else:
86            buffer = buffer + struct.pack('<B', int(b_str, 2))
87            break
88    buffer = buffer + raw_str.encode('utf-8')
89    return buffer
def read_str(file, total_b=''):
93def read_str(file, total_b=""):
94    for i in range(9):
95        b_str = format(struct.unpack('<B', file.read(1))[0], '08b')
96        total_b = b_str[1:] + total_b
97        if b_str[0] == '0':
98            break
99    return file.read(int(total_b, 2)).decode('utf-8')
def encode_bone_name(name, enable=True):
103def encode_bone_name(name, enable=True):
104    return re.sub(r'([_ ])\*([_ ].*)\.([rRlL])$', r'\1\3\2', name) if enable and name.count('*') == 1 else name
def decode_bone_name(name, enable=True):
108def decode_bone_name(name, enable=True):
109    return re.sub(r'([_ ])([rRlL])([_ ].*)$', r'\1*\3.\2', name) if enable else name
def decorate_material_old(mate, enable=True, me=None, mate_index=-1):
113def decorate_material_old(mate, enable=True, me=None, mate_index=-1):
114    if not compat.IS_LEGACY or not enable or 'shader1' not in mate:
115        return
116
117    shader = mate['shader1']
118    if 'CM3D2/Man' == shader:
119        mate.use_shadeless = True
120        mate.diffuse_color = (0, 1, 1)
121    elif 'CM3D2/Mosaic' == shader:
122        mate.use_transparency = True
123        mate.transparency_method = 'RAYTRACE'
124        mate.alpha = 0.25
125        mate.raytrace_transparency.ior = 2
126    elif 'CM3D2_Debug/Debug_CM3D2_Normal2Color' == shader:
127        mate.use_tangent_shading = True
128        mate.diffuse_color = (0.5, 0.5, 1)
129
130    else:
131        if '/Toony_' in shader:
132            mate.diffuse_shader = 'TOON'
133            mate.diffuse_toon_smooth = 0.01
134            mate.diffuse_toon_size = 1.2
135        if 'Trans' in shader:
136            mate.use_transparency = True
137            mate.alpha = 0.0
138            mate.texture_slots[0].use_map_alpha = True
139        if 'Unlit/' in shader:
140            mate.emit = 0.5
141        if '_NoZ' in shader:
142            mate.offset_z = 9999
143
144    is_colored = False
145    is_textured = [False, False, False, False]
146    rimcolor, rimpower, rimshift = mathutils.Color((1, 1, 1)), 0.0, 0.0
147    for slot in mate.texture_slots:
148        if not slot or not slot.texture:
149            continue
150
151        tex = slot.texture
152        tex_name = remove_serial_number(tex.name)
153        slot.use_map_color_diffuse = False
154
155        if tex_name == '_MainTex':
156            slot.use_map_color_diffuse = True
157            img = getattr(tex, 'image')
158            if img and len(img.pixels):
159                if me:
160                    color = mathutils.Color(get_image_average_color_uv(img, me, mate_index)[:3])
161                else:
162                    color = mathutils.Color(get_image_average_color(img)[:3])
163                mate.diffuse_color = color
164                is_colored = True
165
166        elif tex_name == '_RimColor':
167            rimcolor = slot.color[:]
168            if not is_colored:
169                mate.diffuse_color = slot.color[:]
170                mate.diffuse_color.v += 0.5
171
172        elif tex_name == '_Shininess':
173            mate.specular_intensity = slot.diffuse_color_factor
174
175        elif tex_name == '_RimPower':
176            rimpower = slot.diffuse_color_factor
177
178        elif tex_name == '_RimShift':
179            rimshift = slot.diffuse_color_factor
180
181        for index, name in enumerate(['_MainTex', '_ToonRamp', '_ShadowTex', '_ShadowRateToon']):
182            if tex_name == name:
183                img = getattr(tex, 'image')
184                if img and len(tex.image.pixels):
185                    is_textured[index] = tex
186
187        set_texture_color(slot)
188
189    # よりオリジナルに近く描画するノード作成
190    if all(is_textured):
191        mate.use_nodes = True
192        mate.use_shadeless = True
193
194        node_tree = mate.node_tree
195        for node in node_tree.nodes[:]:
196            node_tree.nodes.remove(node)
197
198        mate_node = node_tree.nodes.new('ShaderNodeExtendedMaterial')
199        mate_node.location = (0, 0)
200        mate_node.material = mate
201
202        if "CM3D2 Shade" in bpy.context.blend_data.materials:
203            shade_mate = bpy.context.blend_data.materials["CM3D2 Shade"]
204        else:
205            shade_mate = bpy.context.blend_data.materials.new("CM3D2 Shade")
206        shade_mate.diffuse_color = (1, 1, 1)
207        shade_mate.diffuse_intensity = 1
208        shade_mate.specular_intensity = 1
209        shade_mate_node = node_tree.nodes.new('ShaderNodeExtendedMaterial')
210        shade_mate_node.location = (234.7785, -131.8243)
211        shade_mate_node.material = shade_mate
212
213        toon_node = node_tree.nodes.new('ShaderNodeValToRGB')
214        toon_node.location = (571.3662, -381.0965)
215        toon_img = is_textured[1].image
216        toon_w, toon_h = toon_img.size[0], toon_img.size[1]
217        for i in range(32 - 2):
218            toon_node.color_ramp.elements.new(0.0)
219        for i in range(32):
220            pos = i / (32 - 1)
221            toon_node.color_ramp.elements[i].position = pos
222            x = int((toon_w / (32 - 1)) * i)
223            pixel_index = x * toon_img.channels
224            toon_node.color_ramp.elements[i].color = toon_img.pixels[pixel_index: pixel_index + 4]
225        toon_node.color_ramp.interpolation = 'EASE'
226
227        shadow_rate_node = node_tree.nodes.new('ShaderNodeValToRGB')
228        shadow_rate_node.location = (488.2785, 7.8446)
229        shadow_rate_img = is_textured[3].image
230        shadow_rate_w, shadow_rate_h = shadow_rate_img.size[0], shadow_rate_img.size[1]
231        for i in range(32 - 2):
232            shadow_rate_node.color_ramp.elements.new(0.0)
233        for i in range(32):
234            pos = i / (32 - 1)
235            shadow_rate_node.color_ramp.elements[i].position = pos
236            x = int((shadow_rate_w / (32)) * i)
237            pixel_index = x * shadow_rate_img.channels
238            shadow_rate_node.color_ramp.elements[i].color = shadow_rate_img.pixels[pixel_index: pixel_index + 4]
239        shadow_rate_node.color_ramp.interpolation = 'EASE'
240
241        geometry_node = node_tree.nodes.new('ShaderNodeGeometry')
242        geometry_node.location = (323.4597, -810.8045)
243
244        shadow_texture_node = node_tree.nodes.new('ShaderNodeTexture')
245        shadow_texture_node.location = (626.0117, -666.0227)
246        shadow_texture_node.texture = is_textured[2]
247
248        invert_node = node_tree.nodes.new('ShaderNodeInvert')
249        invert_node.location = (805.6814, -132.9144)
250
251        shadow_mix_node = node_tree.nodes.new('ShaderNodeMixRGB')
252        shadow_mix_node.location = (1031.2714, -201.5598)
253
254        toon_mix_node = node_tree.nodes.new('ShaderNodeMixRGB')
255        toon_mix_node.location = (1257.5538, -308.8037)
256        toon_mix_node.blend_type = 'MULTIPLY'
257        toon_mix_node.inputs[0].default_value = 1.0
258
259        specular_mix_node = node_tree.nodes.new('ShaderNodeMixRGB')
260        specular_mix_node.location = (1473.2079, -382.7421)
261        specular_mix_node.blend_type = 'SCREEN'
262        specular_mix_node.inputs[0].default_value = mate.specular_intensity
263
264        normal_node = node_tree.nodes.new('ShaderNodeNormal')
265        normal_node.location = (912.1372, -590.8748)
266
267        rim_ramp_node = node_tree.nodes.new('ShaderNodeValToRGB')
268        rim_ramp_node.location = (1119.0664, -570.0284)
269        rim_ramp_node.color_ramp.elements[0].color = list(rimcolor[:]) + [1.0]
270        rim_ramp_node.color_ramp.elements[0].position = rimshift
271        rim_ramp_node.color_ramp.elements[1].color = (0, 0, 0, 1)
272        rim_ramp_node.color_ramp.elements[1].position = (rimshift) + ((1.0 - (rimpower * 0.03333)) * 0.5)
273
274        rim_power_node = node_tree.nodes.new('ShaderNodeHueSaturation')
275        rim_power_node.location = (1426.6332, -575.6142)
276        # rim_power_node.inputs[2].default_value = rimpower * 0.1
277
278        rim_mix_node = node_tree.nodes.new('ShaderNodeMixRGB')
279        rim_mix_node.location = (1724.7024, -451.9624)
280        rim_mix_node.blend_type = 'ADD'
281
282        out_node = node_tree.nodes.new('ShaderNodeOutput')
283        out_node.location = (1957.4023, -480.5365)
284
285        node_tree.links.new(shadow_mix_node.inputs[1], mate_node.outputs[0])
286        node_tree.links.new(shadow_rate_node.inputs[0], shade_mate_node.outputs[3])
287        node_tree.links.new(invert_node.inputs[1], shadow_rate_node.outputs[0])
288        node_tree.links.new(shadow_mix_node.inputs[0], invert_node.outputs[0])
289        node_tree.links.new(toon_node.inputs[0], shade_mate_node.outputs[3])
290        node_tree.links.new(shadow_texture_node.inputs[0], geometry_node.outputs[4])
291        node_tree.links.new(shadow_mix_node.inputs[2], shadow_texture_node.outputs[1])
292        node_tree.links.new(toon_node.inputs[0], shade_mate_node.outputs[3])
293        node_tree.links.new(toon_mix_node.inputs[1], shadow_mix_node.outputs[0])
294        node_tree.links.new(toon_mix_node.inputs[2], toon_node.outputs[0])
295        node_tree.links.new(specular_mix_node.inputs[1], toon_mix_node.outputs[0])
296        node_tree.links.new(specular_mix_node.inputs[2], shade_mate_node.outputs[4])
297        node_tree.links.new(normal_node.inputs[0], mate_node.outputs[2])
298        node_tree.links.new(rim_ramp_node.inputs[0], normal_node.outputs[1])
299        node_tree.links.new(rim_power_node.inputs[4], rim_ramp_node.outputs[0])
300        node_tree.links.new(rim_mix_node.inputs[2], rim_power_node.outputs[0])
301        node_tree.links.new(rim_mix_node.inputs[0], shadow_rate_node.outputs[0])
302        node_tree.links.new(rim_mix_node.inputs[1], specular_mix_node.outputs[0])
303        node_tree.links.new(out_node.inputs[0], rim_mix_node.outputs[0])
304        node_tree.links.new(out_node.inputs[1], mate_node.outputs[1])
305
306        for node in node_tree.nodes[:]:
307            compat.set_select(node, False)
308        node_tree.nodes.active = mate_node
309        node_tree.nodes.active.select = True
310
311    else:
312        mate.use_nodes = False
313        mate.use_shadeless = False
def decorate_material(mate, enable=True, me=None, mate_index=-1):
316def decorate_material(mate, enable=True, me=None, mate_index=-1):
317    if not enable or 'shader1' not in mate:
318        return
319    if compat.IS_LEGACY:
320        return decorate_material_old(mate, enable=enable, me=me, mate_index=mate_index)
321
322    # luvoid : set properties of the mate
323    shader = mate['shader1']
324    mate.preview_render_type  = 'FLAT'
325    mate.blend_method         = 'BLEND' if 'Trans' in shader else 'OPAQUE'
326    mate.use_backface_culling = 'Outline' not in shader
327
328    # luvoid : create cm3d2 shader node group and material output node
329    cmnode = None
330    if not compat.IS_LEGACY:
331        mate.use_nodes = True
332        #cm3d2_data.clear_nodes(mate.node_tree.nodes)
333        cmtree = bpy.data.node_groups.get('CM3D2 Shader')
334        if not cmtree:
335            blend_path = os.path.join(os.path.dirname(__file__), "append_data.blend")
336            with bpy.data.libraries.load(blend_path) as (data_from, data_to):
337                data_to.node_groups = ['CM3D2 Shader']
338            cmtree = data_to.node_groups[0]
339        cmnode = mate.node_tree.nodes.new('ShaderNodeGroup')
340        cmnode.node_tree = cmtree
341        matout = mate.node_tree.nodes.new('ShaderNodeOutputMaterial')
342        matout.location = (300,0)
343        mate.node_tree.links.new(matout.inputs.get('Surface'), cmnode.outputs.get('Surface'))
344
345    for key, node in mate.node_tree.nodes.items():
346        if not key.startswith('_'):
347            continue
348        if type(node) == bpy.types.ShaderNodeTexImage:
349            # luvoid : attatch tex node to cmnode sockets
350            socket = cmnode.inputs.get(key+" Color")
351            if socket:
352                mate.node_tree.links.new(socket, node.outputs.get('Color'))
353            socket = cmnode.inputs.get(key+" Alpha")
354            if socket:
355                mate.node_tree.links.new(socket, node.outputs.get('Alpha'))
356        else:
357            # luvoid : attatch color/float node to cmnode socket
358            input_socket = cmnode.inputs.get(key)
359            output_socket = node.outputs.get('Color') or node.outputs.get('Value')
360            if input_socket and output_socket:
361                mate.node_tree.links.new(input_socket, output_socket)
def get_image_average_color(img, sample_count=10):
367def get_image_average_color(img, sample_count=10):
368    if not len(img.pixels):
369        return mathutils.Color([0, 0, 0])
370
371    pixel_count = img.size[0] * img.size[1]
372    channels = img.channels
373
374    max_s = 0.0
375    max_s_color, average_color = mathutils.Color([0, 0, 0]), mathutils.Color([0, 0, 0])
376    seek_interval = pixel_count / sample_count
377    for sample_index in range(sample_count):
378
379        index = int(seek_interval * sample_index) * channels
380        color = mathutils.Color(img.pixels[index: index + 3])
381        average_color += color
382        if max_s < color.s:
383            max_s_color, max_s = color, color.s
384
385    average_color /= sample_count
386    output_color = (average_color + max_s_color) / 2
387    output_color.s *= 1.5
388    return max_s_color
def get_image_average_color_uv(img, me=None, mate_index=-1, sample_count=10):
392def get_image_average_color_uv(img, me=None, mate_index=-1, sample_count=10):
393    if not len(img.pixels): return mathutils.Color([0, 0, 0])
394
395    img_width, img_height, img_channel = img.size[0], img.size[1], img.channels
396
397    bm = bmesh.new()
398    bm.from_mesh(me)
399    uv_lay = bm.loops.layers.uv.active
400    uvs = [l[uv_lay].uv[:] for f in bm.faces if f.material_index == mate_index for l in f.loops]
401    bm.free()
402
403    if len(uvs) <= sample_count:
404        return get_image_average_color(img)
405
406    average_color = mathutils.Color([0, 0, 0])
407    max_s = 0.0
408    max_s_color = mathutils.Color([0, 0, 0])
409    seek_interval = len(uvs) / sample_count
410    for sample_index in range(sample_count):
411
412        uv_index = int(seek_interval * sample_index)
413        x, y = uvs[uv_index]
414
415        x = math.modf(x)[0]
416        if x < 0.0:
417            x += 1.0
418        y = math.modf(y)[0]
419        if y < 0.0:
420            y += 1.0
421
422        x, y = int(x * img_width), int(y * img_height)
423
424        pixel_index = ((y * img_width) + x) * img_channel
425        color = mathutils.Color(img.pixels[pixel_index: pixel_index + 3])
426
427        average_color += color
428        if max_s < color.s:
429            max_s_color, max_s = color, color.s
430
431    average_color /= sample_count
432    output_color = (average_color + max_s_color) / 2
433    output_color.s *= 1.5
434    return output_color
def get_cm3d2_dir():
437def get_cm3d2_dir():
438    try:
439        import winreg
440        with winreg.OpenKey(winreg.HKEY_CURRENT_USER, r'Software\KISS\カスタムメイド3D2') as key:
441            return winreg.QueryValueEx(key, 'InstallPath')[0]
442    except:
443        return None
def get_com3d2_dir():
446def get_com3d2_dir():
447    try:
448        import winreg
449        with winreg.OpenKey(winreg.HKEY_CURRENT_USER, r'Software\KISS\カスタムオーダーメイド3D2') as key:
450            return winreg.QueryValueEx(key, 'InstallPath')[0]
451    except:
452        return None
def default_cm3d2_dir(base_dir, file_name, new_ext):
456def default_cm3d2_dir(base_dir, file_name, new_ext):
457    if not base_dir:
458        prefs = preferences()
459        if prefs.cm3d2_path:
460            base_dir = os.path.join(prefs.cm3d2_path, "GameData", "*." + new_ext)
461        else:
462            base_dir = get_cm3d2_dir()
463            if base_dir is None:
464                base_dir = get_com3d2_dir()
465
466            if base_dir:
467                prefs.cm3d2_path = base_dir
468                base_dir = os.path.join(base_dir, "GameData", "*." + new_ext)
469
470        if base_dir is None:
471            base_dir = "."
472
473    if file_name:
474        base_dir = os.path.join(os.path.split(base_dir)[0], file_name)
475    base_dir = os.path.splitext(base_dir)[0] + "." + new_ext
476    return base_dir
def open_temporary(filepath, mode, is_backup=False):
480def open_temporary(filepath, mode, is_backup=False):
481    backup_ext = preferences().backup_ext
482    if is_backup and backup_ext:
483        backup_filepath = filepath + '.' + backup_ext
484    else:
485        backup_filepath = None
486    return fileutil.TemporaryFileWriter(filepath, mode, backup_filepath=backup_filepath)
def file_backup(filepath, enable=True):
490def file_backup(filepath, enable=True):
491    backup_ext = preferences().backup_ext
492    if enable and backup_ext and os.path.exists(filepath):
493        shutil.copyfile(filepath, filepath + "." + backup_ext)
def find_tex_all_files(dir):
497def find_tex_all_files(dir):
498    for root, dirs, files in os.walk(dir):
499        for f in files:
500            ext = os.path.splitext(f)[1].lower()
501            if ext == ".tex" or ext == ".png":
502                yield os.path.join(root, f)
def get_default_tex_paths():
506def get_default_tex_paths():
507    prefs = preferences()
508    default_paths = [prefs.default_tex_path0, prefs.default_tex_path1, prefs.default_tex_path2, prefs.default_tex_path3]
509    if not any(default_paths):
510        target_dirs = []
511        cm3d2_dir = prefs.cm3d2_path
512        if not cm3d2_dir:
513            cm3d2_dir = get_cm3d2_dir()
514
515        if cm3d2_dir:
516            target_dirs.append(os.path.join(cm3d2_dir, "GameData", "texture"))
517            target_dirs.append(os.path.join(cm3d2_dir, "GameData", "texture2"))
518            target_dirs.append(os.path.join(cm3d2_dir, "Sybaris", "GameData"))
519            target_dirs.append(os.path.join(cm3d2_dir, "Mod"))
520
521        # com3d2_dir = prefs.com3d2_path
522        # if not com3d2_dir:
523        #     com3d2_dir = get_cm3d2_dir()
524        # if com3d2_dir:
525        #     target_dirs.append(os.path.join(com3d2_dir, "GameData", "parts"))
526        #     target_dirs.append(os.path.join(com3d2_dir, "GameData", "parts2"))
527        #     target_dirs.append(os.path.join(com3d2_dir, "MOD"))
528
529        tex_dirs = [path for path in target_dirs if os.path.isdir(path)]
530
531        for index, path in enumerate(tex_dirs):
532            setattr(prefs, 'default_tex_path' + str(index), path)
533    else:
534        tex_dirs = [getattr(prefs, 'default_tex_path' + str(i)) for i in range(4) if getattr(prefs, 'default_tex_path' + str(i))]
535    return tex_dirs
def get_tex_storage_files():
539def get_tex_storage_files():
540    files = []
541    tex_dirs = get_default_tex_paths()
542    for tex_dir in tex_dirs:
543        tex_dir = bpy.path.abspath(tex_dir)
544        files.extend(find_tex_all_files(tex_dir))
545    return files
def get_texpath_dict(reload=False):
548def get_texpath_dict(reload=False):
549    if reload or len(texpath_dict) == 0:
550        texpath_dict.clear()
551        tex_dirs = get_default_tex_paths()
552        for tex_dir in tex_dirs:
553            for path in find_tex_all_files(tex_dir):
554                path = bpy.path.abspath(path)
555                file_name = os.path.basename(path).lower()
556                # 先に見つけたファイルを優先
557                if file_name not in texpath_dict:
558                    texpath_dict[file_name] = path
559    return texpath_dict
def reload_png(img, texpath_dict, png_name):
562def reload_png(img, texpath_dict, png_name):
563    png_path = texpath_dict.get(png_name)
564    if png_path:
565        img.filepath = png_path
566        img.reload()
567        return True
568    return False
def replace_cm3d2_tex(img, texpath_dict: dict = None, reload_path: bool = True) -> bool:
571def replace_cm3d2_tex(img, texpath_dict: dict=None, reload_path: bool=True) -> bool:
572    """replace tex file.
573    pngファイルを先に走査し、見つからなければtexファイルを探す.
574    texはpngに展開して読み込みを行う.
575    reload_path=Trueの場合、png,texファイルが見つからない場合にキャッシュを再構成し、
576    再度検索を行う.
577
578    Parameters:
579        img (Image): イメージオブジェクト
580        texpath_dict (dict): テクスチャパスのdict (キャッシュ)
581        reload_path (bool): 見つからない場合にキャッシュを再読込するか
582
583    Returns:
584        bool: tex load successful
585    """
586    if texpath_dict is None:
587        texpath_dict = get_texpath_dict()
588
589    if __replace_cm3d2_tex(img, texpath_dict):
590        return True
591    if reload_path:
592        texpath_dict = get_texpath_dict(True)
593        return __replace_cm3d2_tex(img, texpath_dict)
594    return False

replace tex file. pngファイルを先に走査し、見つからなければtexファイルを探す. texはpngに展開して読み込みを行う. reload_path=Trueの場合、png,texファイルが見つからない場合にキャッシュを再構成し、 再度検索を行う.

Parameters: img (Image): イメージオブジェクト texpath_dict (dict): テクスチャパスのdict (キャッシュ) reload_path (bool): 見つからない場合にキャッシュを再読込するか

Returns: bool: tex load successful

def load_cm3d2tex(path, skip_data=False):
625def load_cm3d2tex(path, skip_data=False):
626
627    with open(path, 'rb') as file:
628        header_ext = read_str(file)
629        if header_ext != 'CM3D2_TEX':
630            return None
631        version = struct.unpack('<i', file.read(4))[0]
632        read_str(file)
633
634        # default value
635        tex_format = 5
636        uv_rects = None
637        data = None
638        if version >= 1010:
639            if version >= 1011:
640                num_rect = struct.unpack('<i', file.read(4))[0]
641                uv_rects = []
642                for i in range(num_rect):
643                    # x, y, w, h
644                    uv_rects.append(struct.unpack('<4f', file.read(4 * 4)))
645            width = struct.unpack('<i', file.read(4))[0]
646            height = struct.unpack('<i', file.read(4))[0]
647            tex_format = struct.unpack('<i', file.read(4))[0]
648            # if tex_format == 10 or tex_format == 12: return None
649        if not skip_data:
650            png_size = struct.unpack('<i', file.read(4))[0]
651            data = file.read(png_size)
652        return version, tex_format, uv_rects, data
def create_tex( context, mate, node_name, tex_name=None, filepath=None, cm3d2path=None, tex_map_data=None, replace_tex=False, slot_index=-1):
655def create_tex(context, mate, node_name, tex_name=None, filepath=None, cm3d2path=None, tex_map_data=None, replace_tex=False, slot_index=-1):
656    if isinstance(context, bpy.types.Context):
657        context = context.copy()
658
659    if compat.IS_LEGACY:
660        slot = mate.texture_slots.create(slot_index)
661        tex = context['blend_data'].textures.new(node_name, 'IMAGE')
662        slot.texture = tex
663
664        if tex_name:
665            slot.offset[0] = tex_map_data[0]
666            slot.offset[1] = tex_map_data[1]
667            slot.scale[0] = tex_map_data[2]
668            slot.scale[1] = tex_map_data[3]
669
670            if os.path.exists(filepath):
671                img = bpy.data.images.load(filepath)
672                img.name = tex_name
673            else:
674                img = context['blend_data'].images.new(tex_name, 128, 128)
675                img.filepath = filepath
676            img['cm3d2_path'] = cm3d2path
677            img.source = 'FILE'
678            tex.image = img
679
680            if replace_tex:
681                replaced = replace_cm3d2_tex(tex.image, reload_path=False)
682                if replaced and node_name == '_MainTex':
683                    ob = context['active_object']
684                    me = ob.data
685                    for face in me.polygons:
686                        if face.material_index == ob.active_material_index:
687                            me.uv_textures.active.data[face.index].image = tex.image
688
689    else:
690        # if mate.use_nodes is False:
691        # 	mate.use_nodes = True
692        nodes = mate.node_tree.nodes
693        tex = nodes.get(node_name)
694        if tex is None:
695            tex = mate.node_tree.nodes.new(type='ShaderNodeTexImage')
696            tex.name = tex.label = node_name
697            tex.show_texture = True
698
699        if tex_name:
700            if tex.image is None:
701                if os.path.exists(filepath):
702                    img = bpy.data.images.load(filepath)
703                    img.name = tex_name
704                else:
705                    img = context['blend_data'].images.new(tex_name, 128, 128)
706                    img.filepath = filepath
707                img.source = 'FILE'
708                tex.image = img
709                img['cm3d2_path'] = cm3d2path
710            else:
711                img = tex.image
712                path = img.get('cm3d2_path')
713                if path != cm3d2path:
714                    img['cm3d2_path'] = cm3d2path
715                    img.filepath = filepath
716
717            tex_map = tex.texture_mapping
718            tex_map.translation[0] = tex_map_data[0]
719            tex_map.translation[1] = tex_map_data[1]
720            tex_map.scale[0] = tex_map_data[2]
721            tex_map.scale[1] = tex_map_data[3]
722
723        # tex.color = tex_data['color'][:3]
724        # tex.outputs['Color'].default_value = tex_data['color'][:]
725        # tex.outputs['ALpha'].default_value = tex_data['color'][3]
726
727            # tex探し
728            if replace_tex:
729                replaced = replace_cm3d2_tex(tex.image, reload_path=False)
730                # TODO 2.8での実施方法を調査. shader editorで十分?
731
732    return tex
def create_col(context, mate, node_name, color, slot_index=-1):
735def create_col(context, mate, node_name, color, slot_index=-1):
736    if isinstance(context, bpy.types.Context):
737        context = context.copy()
738
739    if compat.IS_LEGACY:
740        if slot_index >= 0:
741            mate.use_textures[slot_index] = False
742        node = mate.texture_slots.create(slot_index)
743        node.color = color[:3]
744        node.diffuse_color_factor = color[3]
745        node.use_rgb_to_intensity = True
746        tex = context['blend_data'].textures.new(node_name, 'BLEND')
747        node.texture = tex
748        node.use = False
749    else:
750        node = mate.node_tree.nodes.get(node_name)
751        if node is None:
752            node = mate.node_tree.nodes.new(type='ShaderNodeRGB')
753            node.name = node.label = node_name
754        node.outputs[0].default_value = color
755
756    return node
def create_float(context, mate, node_name, value, slot_index=-1):
759def create_float(context, mate, node_name, value, slot_index=-1):
760    if isinstance(context, bpy.types.Context):
761        context = context.copy()
762
763    if compat.IS_LEGACY:
764        if slot_index >= 0:
765            mate.use_textures[slot_index] = False
766        node = mate.texture_slots.create(slot_index)
767        node.diffuse_color_factor = value
768        node.use_rgb_to_intensity = False
769        tex = context['blend_data'].textures.new(node_name, 'BLEND')
770        node.texture = tex
771        node.use = False
772    else:
773        node = mate.node_tree.nodes.get(node_name)
774        if node is None:
775            node = mate.node_tree.nodes.new(type='ShaderNodeValue')
776            node.name = node.label = node_name
777        node.outputs[0].default_value = value
778
779    return node
def setup_material(mate):
782def setup_material(mate):
783    if mate:
784        if 'CM3D2 Texture Expand' not in mate:
785            mate['CM3D2 Texture Expand'] = True
786
787        if not compat.IS_LEGACY:
788            mate.use_nodes = True
def setup_image_name(img):
791def setup_image_name(img):
792    """イメージの名前から拡張子を除外する"""
793    # consider case with serial number. ex) sample.png.001
794    img.name = re_png.sub(r'\1', img.name)

イメージの名前から拡張子を除外する

def get_tex_cm3d2path(filepath):
797def get_tex_cm3d2path(filepath):
798    return BASE_PATH_TEX + os.path.basename(filepath)
def to_cm3d2path(path):
801def to_cm3d2path(path):
802    path = path.replace('\\', '/')
803    path = re_prefix.sub('', path)
804    if not re_path_prefix.search(path):
805        path = get_tex_cm3d2path(path)
806    return path
def set_texture_color(slot):
810def set_texture_color(slot):
811    if not slot or not slot.texture or slot.use:
812        return
813
814    slot_type = 'col' if slot.use_rgb_to_intensity else 'f'
815    tex = slot.texture
816    base_name = remove_serial_number(tex.name)
817    tex.type = 'BLEND'
818
819    if hasattr(tex, 'progression'):
820        tex.progression = 'DIAGONAL'
821    tex.use_color_ramp = True
822    tex.use_preview_alpha = True
823    elements = tex.color_ramp.elements
824
825    element_count = 4
826    if element_count < len(elements):
827        for i in range(len(elements) - element_count):
828            elements.remove(elements[-1])
829    elif len(elements) < element_count:
830        for i in range(element_count - len(elements)):
831            elements.new(1.0)
832
833    elements[0].position, elements[1].position, elements[2].position, elements[3].position = 0.2, 0.21, 0.25, 0.26
834
835    if slot_type == 'col':
836        elements[0].color = [0.2, 1, 0.2, 1]
837        elements[-1].color = slot.color[:] + (slot.diffuse_color_factor, )
838        if 0.3 < mathutils.Color(slot.color[:3]).v:
839            elements[1].color, elements[2].color = [0, 0, 0, 1], [0, 0, 0, 1]
840        else:
841            elements[1].color, elements[2].color = [1, 1, 1, 1], [1, 1, 1, 1]
842
843    elif slot_type == 'f':
844        elements[0].color = [0.2, 0.2, 1, 1]
845        multi = 1.0
846        if base_name == '_OutlineWidth':
847            multi = 200
848        elif base_name == '_RimPower':
849            multi = 1.0 / 30.0
850        value = slot.diffuse_color_factor * multi
851        elements[-1].color = [value, value, value, 1]
852        if 0.3 < value:
853            elements[1].color, elements[2].color = [0, 0, 0, 1], [0, 0, 0, 1]
854        else:
855            elements[1].color, elements[2].color = [1, 1, 1, 1], [1, 1, 1, 1]
def get_request_area(context, request_type, except_types=None):
859def get_request_area(context, request_type, except_types=None):
860    if except_types is None:
861        except_types = ['VIEW_3D', 'PROPERTIES', 'INFO', compat.pref_type()]
862
863    request_areas = [(a, a.width * a.height) for a in context.screen.areas if a.type == request_type]
864    candidate_areas = [(a, a.width * a.height) for a in context.screen.areas if a.type not in except_types]
865
866    return_areas = request_areas[:] if len(request_areas) else candidate_areas
867    if not len(return_areas):
868        return None
869
870    return_areas.sort(key=lambda i: i[1])
871    return_area = return_areas[-1][0]
872    return_area.type = request_type
873    return return_area
def remove_data(target_data):
877def remove_data(target_data):
878    try:
879        target_data = target_data[:]
880    except:
881        target_data = [target_data]
882
883    if compat.IS_LEGACY:
884        for data in target_data:
885            if data.__class__.__name__ == 'Object':
886                if data.name in bpy.context.scene.objects:
887                    bpy.context.scene.objects.unlink(data)
888    else:
889        for data in target_data:
890            if data.__class__.__name__ == 'Object':
891                if data.name in bpy.context.scene.collection.objects:
892                    bpy.context.scene.collection.objects.unlink(data)
893
894    # https://developer.blender.org/T49837
895    # によると、xxx.remove(data, do_unlink=True)で十分
896    #
897    # for data in target_data:
898    # 	users = getattr(data, 'users')
899    # 	if users and 'user_clear' in dir(data):
900    # 		data.user_clear()
901
902    for data in target_data:
903        for data_str in dir(bpy.data):
904            if not data_str.endswith('s'):
905                continue
906            try:
907                if data.__class__.__name__ == eval('bpy.data.%s[0].__class__.__name__' % data_str):
908                    exec('bpy.data.%s.remove(data, do_unlink=True)' % data_str)
909                    break
910            except:
911                pass
class material_restore:
915class material_restore:
916    def __init__(self, ob):
917        override = bpy.context.copy()
918        override['object'] = ob
919        self.object = ob
920
921        self.slots = [slot.material if slot.material else None for slot in ob.material_slots]
922
923        self.mesh_data = []
924        for index, slot in enumerate(ob.material_slots):
925            mesh_datum = []
926            for face in ob.data.polygons:
927                if face.material_index == index:
928                    mesh_datum.append(face.index)
929            self.mesh_data.append(mesh_datum)
930
931        for slot in ob.material_slots[:]:
932            bpy.ops.object.material_slot_remove(override)
933
934    def restore(self):
935        override = bpy.context.copy()
936        override['object'] = self.object
937
938        for slot in self.object.material_slots[:]:
939            bpy.ops.object.material_slot_remove(override)
940
941        for index, mate in enumerate(self.slots):
942            bpy.ops.object.material_slot_add(override)
943            slot = self.object.material_slots[index]
944            if slot:
945                slot.material = mate
946            for face_index in self.mesh_data[index]:
947                self.object.data.polygons[face_index].material_index = index
material_restore(ob)
916    def __init__(self, ob):
917        override = bpy.context.copy()
918        override['object'] = ob
919        self.object = ob
920
921        self.slots = [slot.material if slot.material else None for slot in ob.material_slots]
922
923        self.mesh_data = []
924        for index, slot in enumerate(ob.material_slots):
925            mesh_datum = []
926            for face in ob.data.polygons:
927                if face.material_index == index:
928                    mesh_datum.append(face.index)
929            self.mesh_data.append(mesh_datum)
930
931        for slot in ob.material_slots[:]:
932            bpy.ops.object.material_slot_remove(override)
object
slots
mesh_data
def restore(self):
934    def restore(self):
935        override = bpy.context.copy()
936        override['object'] = self.object
937
938        for slot in self.object.material_slots[:]:
939            bpy.ops.object.material_slot_remove(override)
940
941        for index, mate in enumerate(self.slots):
942            bpy.ops.object.material_slot_add(override)
943            slot = self.object.material_slots[index]
944            if slot:
945                slot.material = mate
946            for face_index in self.mesh_data[index]:
947                self.object.data.polygons[face_index].material_index = index
class hide_render_restore:
951class hide_render_restore:
952    def __init__(self, render_objects=[]):
953        try:
954            render_objects = render_objects[:]
955        except:
956            render_objects = [render_objects]
957
958        if not len(render_objects):
959            render_objects = bpy.context.selected_objects[:]
960
961        self.render_objects = render_objects[:]
962        self.render_object_names = [ob.name for ob in render_objects]
963
964        self.rendered_objects = []
965        for ob in render_objects:
966            if ob.hide_render:
967                self.rendered_objects.append(ob)
968                ob.hide_render = False
969
970        self.hide_rendered_objects = []
971        if compat.IS_LEGACY:
972            for ob in bpy.data.objects:
973                for layer_index, is_used in enumerate(bpy.context.scene.layers):
974                    if not is_used:
975                        continue
976                    if ob.layers[layer_index] and is_used and ob.name not in self.render_object_names and not ob.hide_render:
977                        self.hide_rendered_objects.append(ob)
978                        ob.hide_render = True
979                        break
980        else:
981            clct_children = bpy.context.scene.collection.children
982            for ob in bpy.data.objects:
983                if ob.name not in self.render_object_names and not ob.hide_render:
984                    # ble-2.8ではlayerではなく、collectionからのリンクで判断
985                    for clct in bpy.context.window.view_layer.layer_collection.children:
986                        if clct.exclude is False and ob.name in clct_children[clct.name].objects.keys():
987                            self.hide_rendered_objects.append(ob)
988                            ob.hide_render = True
989                            break
990
991    def restore(self):
992        for ob in self.rendered_objects:
993            ob.hide_render = True
994        for ob in self.hide_rendered_objects:
995            ob.hide_render = False
hide_render_restore(render_objects=[])
952    def __init__(self, render_objects=[]):
953        try:
954            render_objects = render_objects[:]
955        except:
956            render_objects = [render_objects]
957
958        if not len(render_objects):
959            render_objects = bpy.context.selected_objects[:]
960
961        self.render_objects = render_objects[:]
962        self.render_object_names = [ob.name for ob in render_objects]
963
964        self.rendered_objects = []
965        for ob in render_objects:
966            if ob.hide_render:
967                self.rendered_objects.append(ob)
968                ob.hide_render = False
969
970        self.hide_rendered_objects = []
971        if compat.IS_LEGACY:
972            for ob in bpy.data.objects:
973                for layer_index, is_used in enumerate(bpy.context.scene.layers):
974                    if not is_used:
975                        continue
976                    if ob.layers[layer_index] and is_used and ob.name not in self.render_object_names and not ob.hide_render:
977                        self.hide_rendered_objects.append(ob)
978                        ob.hide_render = True
979                        break
980        else:
981            clct_children = bpy.context.scene.collection.children
982            for ob in bpy.data.objects:
983                if ob.name not in self.render_object_names and not ob.hide_render:
984                    # ble-2.8ではlayerではなく、collectionからのリンクで判断
985                    for clct in bpy.context.window.view_layer.layer_collection.children:
986                        if clct.exclude is False and ob.name in clct_children[clct.name].objects.keys():
987                            self.hide_rendered_objects.append(ob)
988                            ob.hide_render = True
989                            break
render_objects
render_object_names
rendered_objects
hide_rendered_objects
def restore(self):
991    def restore(self):
992        for ob in self.rendered_objects:
993            ob.hide_render = True
994        for ob in self.hide_rendered_objects:
995            ob.hide_render = False
def set_area_space_attr(area, attr_name, value):
 999def set_area_space_attr(area, attr_name, value):
1000    if not area:
1001        return
1002    for space in area.spaces:
1003        if space.type == area.type:
1004            space.__setattr__(attr_name, value)
1005            break
def in_out_quad_blend(f):
1009def in_out_quad_blend(f):
1010    if f <= 0.5:
1011        return 2.0 * math.sqrt(f)
1012    f -= 0.5
1013    return 2.0 * f * (1.0 - f) + 0.5
def bezier_blend(f):
1017def bezier_blend(f):
1018    return math.sqrt(f) * (3.0 - 2.0 * f)
def trigonometric_smooth(x):
1022def trigonometric_smooth(x):
1023    return math.sin((x - 0.5) * math.pi) * 0.5 + 0.5
class CM3D2ExportException(builtins.Exception):
1027class CM3D2ExportException(Exception):
1028    pass

Common base class for all non-exit exceptions.

Inherited Members
builtins.Exception
Exception
builtins.BaseException
with_traceback
args
class CM3D2ImportException(builtins.Exception):
1030class CM3D2ImportException(Exception):
1031    pass

Common base class for all non-exit exceptions.

Inherited Members
builtins.Exception
Exception
builtins.BaseException
with_traceback
args
class NodeHandler:
1035class NodeHandler():
1036    node_name = bpy.props.StringProperty(name='NodeName')
1037
1038    def get_node(self, context):
1039        mate = context.material
1040        if mate and mate.use_nodes:
1041            return mate.node_tree.nodes.get(self.node_name)
1042
1043            # if node is None:
1044            # # 見つからない場合は、シリアル番号付きのノードを探す
1045            # prefix = self.node_name + '.'
1046            # for n in nodes:
1047            # 	if n.name.startwith(prefix):
1048            # 		node = n
1049            # 		break
1050
1051        return None
node_name = <_PropertyDeferred, <built-in function StringProperty>, {'name': 'NodeName', 'attr': 'node_name'}>
def get_node(self, context):
1038    def get_node(self, context):
1039        mate = context.material
1040        if mate and mate.use_nodes:
1041            return mate.node_tree.nodes.get(self.node_name)
1042
1043            # if node is None:
1044            # # 見つからない場合は、シリアル番号付きのノードを探す
1045            # prefix = self.node_name + '.'
1046            # for n in nodes:
1047            # 	if n.name.startwith(prefix):
1048            # 		node = n
1049            # 		break
1050
1051        return None
class CNV_UL_generic_selector(bpy_types.UIList):
1054class CNV_UL_generic_selector(bpy.types.UIList):
1055    bl_label       = "CNV_UL_generic_selector"
1056    bl_options     = {'DEFAULT_CLOSED'}
1057    bl_region_type = 'WINDOW'
1058    bl_space_type  = 'PROPERTIES'
1059
1060    # Constants (flags)
1061    # Be careful not to shadow FILTER_ITEM!
1062    #bitflag_soft_filter = 1073741824 >> 0
1063    bitflag_soft_filter  = 1073741824 >> 3
1064
1065    bitflag_forced_value = 1073741824 >> 10
1066    bitflag_forced_true  = 1073741824 >> 11
1067    bitflag_forced_false = 1073741824 >> 12
1068
1069    
1070    cached_values = {}
1071    expanded_layout = False
1072
1073    # Custom properties, saved with .blend file.
1074    use_filter_name_reverse = bpy.props.BoolProperty(
1075        name="Reverse Name",
1076        default=False,
1077        options=set(),
1078        description="Reverse name filtering",
1079    )
1080    #use_filter_deform = bpy.props.BoolProperty(
1081    #    name="Only Deform",
1082    #    default=True,
1083    #    options=set(),
1084    #    description="Only show deforming vertex groups",
1085    #)
1086    #use_filter_deform_reverse = bpy.props.BoolProperty(
1087    #    name="Other",
1088    #    default=False,
1089    #    options=set(),
1090    #    description="Only show non-deforming vertex groups",
1091    #)
1092    #use_filter_empty = bpy.props.BoolProperty(
1093    #    name="Filter Empty",
1094    #    default=False,
1095    #    options=set(),
1096    #    description="Whether to filter empty vertex groups",
1097    #)
1098    #use_filter_empty_reverse = bpy.props.BoolProperty(
1099    #    name="Reverse Empty",
1100    #    default=False,
1101    #    options=set(),
1102    #    description="Reverse empty filtering",
1103    #)
1104    
1105    # This allows us to have mutually exclusive options, which are also all disable-able!
1106    def _gen_order_update(name1, name2):
1107        def _u(self, ctxt):
1108            if (getattr(self, name1)):
1109                setattr(self, name2, False)
1110        return _u
1111    use_order_name = bpy.props.BoolProperty(
1112        name="Name", default=False, options=set(),
1113        description="Sort groups by their name (case-insensitive)",
1114        update=_gen_order_update("use_order_name", "use_order_importance"),
1115    )
1116    use_filter_orderby_invert = bpy.props.BoolProperty(
1117        name="Order by Invert",
1118        default=False,
1119        options=set(),
1120        description="Invert the sort by order"
1121    )
1122    #use_order_importance = bpy.props.BoolProperty(
1123    #    name="Importance",
1124    #    default=False,
1125    #    options=set(),
1126    #    description="Sort groups by their average weight in the mesh",
1127    #    update=_gen_order_update("use_order_importance", "use_order_name"),
1128    #)
1129        
1130    # Usual draw item function.
1131    def draw_item(self, context, layout, data, item, icon_value, active_data, active_propname, index, flt_flag):
1132        # Just in case, we do not use it here!
1133        self.use_filter_invert = False
1134
1135        # assert(isinstance(item, bpy.types.VertexGroup)
1136        #vgroup = getattr(data, 'matched_vgroups')[item.index]
1137        if self.layout_type in {'DEFAULT', 'COMPACT'}:
1138            # Here we use one feature of new filtering feature: it can pass data to draw_item, through flt_flag
1139            # parameter, which contains exactly what filter_items set in its filter list for this item!
1140            # In this case, we show empty groups grayed out.
1141            cached_value = self.cached_values.get(item.name, None)
1142            if (cached_value != None) and (cached_value != item.value):
1143                item.preferred = item.value
1144
1145            force_values = flt_flag & self.bitflag_forced_value
1146            print("GET force_values =", force_values)
1147            if force_values:
1148                print("FORCE VALUES")
1149                if flt_flag & self.bitflag_forced_true:
1150                    item.value = True
1151                elif flt_flag & self.bitflag_forced_false:
1152                    item.value = False
1153                else:
1154                    item.value = item.preferred
1155
1156            self.cached_values[item.name] = item.value
1157
1158            if flt_flag & self.bitflag_soft_filter:
1159                row = layout.row()
1160                row.enabled = False
1161                #row.alignment = 'LEFT'
1162                row.prop(item, "value", text=item.name, icon=item.icon)
1163            else:
1164                layout.prop(item, "value", text=item.name, icon=item.icon)
1165            
1166            #layout.prop(item, "value", text=item.name, icon=item.icon)
1167            icon = 'RADIOBUT_ON' if item.preferred else 'RADIOBUT_OFF'
1168            layout.prop(item, "preferred", text="", icon=compat.icon(icon), emboss=False)
1169        
1170        elif self.layout_type in {'GRID'}:
1171            layout.alignment = 'CENTER'
1172            if flt_flag & self.VGROUP_EMPTY:
1173                layout.enabled = False
1174            layout.label(text="", icon_value=icon)
1175
1176    def draw_filter(self, context, layout):
1177        # Nothing much to say here, it's usual UI code...
1178        row = layout.row()
1179        if not self.expanded_layout:
1180            layout.active = True
1181            layout.enabled = True
1182            row.active = True
1183            row.enabled = True
1184            self.expanded_layout = True
1185
1186        subrow = row.row(align=True)
1187        subrow.prop(self, "filter_name", text="")
1188        icon = 'ZOOM_OUT' if self.use_filter_name_reverse else 'ZOOM_IN'
1189        subrow.prop(self, "use_filter_name_reverse", text="", icon=icon)
1190
1191        #subrow = row.row(align=True)
1192        #subrow.prop(self, "use_filter_deform", toggle=True)
1193        #icon = 'ZOOM_OUT' if self.use_filter_deform_reverse else 'ZOOM_IN'
1194        #subrow.prop(self, "use_filter_deform_reverse", text="", icon=icon)
1195
1196        #subrow = row.row(align=True)
1197        #subrow.prop(self, "use_filter_empty", toggle=True)
1198        #icon = 'ZOOM_OUT' if self.use_filter_empty_reverse else 'ZOOM_IN'
1199        #subrow.prop(self, "use_filter_empty_reverse", text="", icon=icon)
1200
1201        row = layout.row(align=True)
1202        row.label(text="Order by:")
1203        row.prop(self, "use_order_name", toggle=True)
1204        #row.prop(self, "use_order_importance", toggle=True)
1205        icon = 'TRIA_UP' if self.use_filter_orderby_invert else 'TRIA_DOWN'
1206        row.prop(self, "use_filter_orderby_invert", text="", icon=icon)
1207
1208    def filter_items(self, context, data, propname):
1209        # This function gets the collection property (as the usual tuple (data, propname)), and must return two lists:
1210        # * The first one is for filtering, it must contain 32bit integers were self.bitflag_filter_item marks the
1211        #   matching item as filtered (i.e. to be shown), and 31 other bits are free for custom needs. Here we use the
1212        #   first one to mark VGROUP_EMPTY.
1213        # * The second one is for reordering, it must return a list containing the new indices of the items (which
1214        #   gives us a mapping org_idx -> new_idx).
1215        # Please note that the default UI_UL_list defines helper functions for common tasks (see its doc for more info).
1216        # If you do not make filtering and/or ordering, return empty list(s) (this will be more efficient than
1217        # returning full lists doing nothing!).
1218        items = getattr(data, propname)
1219        
1220        #if self.armature == None:
1221        #    target_ob, source_ob = common.get_target_and_source_ob(context)
1222        #    armature_ob = target_ob.find_armature() or source_ob.find_armature()
1223        #    self.armature = armature_ob and armature_ob.data or False
1224        #
1225        #if not self.local_bone_names:
1226        #    target_ob, source_ob = common.get_target_and_source_ob(context)
1227        #    bone_data_ob = (target_ob.get("LocalBoneData:0") and target_ob) or (source_ob.get("LocalBoneData:0") and source_ob) or None
1228        #    if bone_data_ob:
1229        #        local_bone_data = model_export.CNV_OT_export_cm3d2_model.local_bone_data_parser(model_export.CNV_OT_export_cm3d2_model.indexed_data_generator(bone_data_ob, prefix="LocalBoneData:"))
1230        #        self.local_bone_names = [ bone['name'] for bone in local_bone_data ]
1231        
1232        if not self.cached_values:
1233            self.cached_values = { item.name: item.value for item in items }
1234        #vgroups = [ getattr(data, 'matched_vgroups')[item.index][0]   for item in items ]
1235        helper_funcs = bpy.types.UI_UL_list
1236
1237        # Default return values.
1238        flt_flags = []
1239        flt_neworder = []
1240
1241        # Pre-compute of vgroups data, CPU-intensive. :/
1242        #vgroups_empty = self.filter_items_empty_vgroups(context, vgroups)
1243
1244        # Filtering by name
1245        if self.filter_name:
1246            flt_flags = helper_funcs.filter_items_by_name(self.filter_name, self.bitflag_filter_item, items, "name",
1247                                                          reverse=self.use_filter_name_reverse)
1248        if not flt_flags:
1249            flt_flags = [self.bitflag_filter_item] * len(items)
1250        
1251        #for idx, vg in enumerate(items):
1252        #    # Filter by deform.
1253        #    if self.use_filter_deform:
1254        #        flt_flags[idx] |= self.VGROUP_DEFORM
1255        #        if self.use_filter_deform:
1256        #            if self.armature and self.armature.get(vg.name):
1257        #                if not self.use_filter_deform_reverse:
1258        #                    flt_flags[idx] &= ~self.VGROUP_DEFORM
1259        #            elif bone_data_ob and (vg.name in self.local_bone_names):
1260        #                if not self.use_filter_deform_reverse:
1261        #                    flt_flags[idx] &= ~self.VGROUP_DEFORM
1262        #            elif self.use_filter_deform_reverse or (not self.armature and not self.local_bone_names):
1263        #                flt_flags[idx] &= ~self.VGROUP_DEFORM
1264        #    else:
1265        #        flt_flags[idx] &= ~self.VGROUP_DEFORM
1266        #
1267        #    # Filter by emptiness.
1268        #    #if vgroups_empty[vg.index][0]:
1269        #    #    flt_flags[idx] |= self.VGROUP_EMPTY
1270        #    #    if self.use_filter_empty and self.use_filter_empty_reverse:
1271        #    #        flt_flags[idx] &= ~self.bitflag_filter_item
1272        #    #elif self.use_filter_empty and not self.use_filter_empty_reverse:
1273        #    #    flt_flags[idx] &= ~self.bitflag_filter_item
1274        
1275        # Reorder by name or average weight.
1276        if self.use_order_name:
1277            flt_neworder = helper_funcs.sort_items_by_name(items, "name")
1278        #elif self.use_order_importance:
1279        #    _sort = [(idx, vgroups_empty[vg.index][1]) for idx, vg in enumerate(vgroups)]
1280        #    flt_neworder = helper_funcs.sort_items_helper(_sort, lambda e: e[1], True)
1281
1282        return flt_flags, flt_neworder
bl_label = 'CNV_UL_generic_selector'
bl_options = {'DEFAULT_CLOSED'}
bl_region_type = 'WINDOW'
bl_space_type = 'PROPERTIES'
bitflag_soft_filter = 134217728
bitflag_forced_value = 1048576
bitflag_forced_true = 524288
bitflag_forced_false = 262144
cached_values = {}
expanded_layout = False
use_filter_name_reverse = <_PropertyDeferred, <built-in function BoolProperty>, {'name': 'Reverse Name', 'default': False, 'options': set(), 'description': 'Reverse name filtering'}>
use_order_name = <_PropertyDeferred, <built-in function BoolProperty>, {'name': 'Name', 'default': False, 'options': set(), 'description': 'Sort groups by their name (case-insensitive)', 'update': <function CNV_UL_generic_selector._gen_order_update.<locals>._u>}>
use_filter_orderby_invert = <_PropertyDeferred, <built-in function BoolProperty>, {'name': 'Order by Invert', 'default': False, 'options': set(), 'description': 'Invert the sort by order'}>
def draw_item( self, context, layout, data, item, icon_value, active_data, active_propname, index, flt_flag):
1131    def draw_item(self, context, layout, data, item, icon_value, active_data, active_propname, index, flt_flag):
1132        # Just in case, we do not use it here!
1133        self.use_filter_invert = False
1134
1135        # assert(isinstance(item, bpy.types.VertexGroup)
1136        #vgroup = getattr(data, 'matched_vgroups')[item.index]
1137        if self.layout_type in {'DEFAULT', 'COMPACT'}:
1138            # Here we use one feature of new filtering feature: it can pass data to draw_item, through flt_flag
1139            # parameter, which contains exactly what filter_items set in its filter list for this item!
1140            # In this case, we show empty groups grayed out.
1141            cached_value = self.cached_values.get(item.name, None)
1142            if (cached_value != None) and (cached_value != item.value):
1143                item.preferred = item.value
1144
1145            force_values = flt_flag & self.bitflag_forced_value
1146            print("GET force_values =", force_values)
1147            if force_values:
1148                print("FORCE VALUES")
1149                if flt_flag & self.bitflag_forced_true:
1150                    item.value = True
1151                elif flt_flag & self.bitflag_forced_false:
1152                    item.value = False
1153                else:
1154                    item.value = item.preferred
1155
1156            self.cached_values[item.name] = item.value
1157
1158            if flt_flag & self.bitflag_soft_filter:
1159                row = layout.row()
1160                row.enabled = False
1161                #row.alignment = 'LEFT'
1162                row.prop(item, "value", text=item.name, icon=item.icon)
1163            else:
1164                layout.prop(item, "value", text=item.name, icon=item.icon)
1165            
1166            #layout.prop(item, "value", text=item.name, icon=item.icon)
1167            icon = 'RADIOBUT_ON' if item.preferred else 'RADIOBUT_OFF'
1168            layout.prop(item, "preferred", text="", icon=compat.icon(icon), emboss=False)
1169        
1170        elif self.layout_type in {'GRID'}:
1171            layout.alignment = 'CENTER'
1172            if flt_flag & self.VGROUP_EMPTY:
1173                layout.enabled = False
1174            layout.label(text="", icon_value=icon)
def draw_filter(self, context, layout):
1176    def draw_filter(self, context, layout):
1177        # Nothing much to say here, it's usual UI code...
1178        row = layout.row()
1179        if not self.expanded_layout:
1180            layout.active = True
1181            layout.enabled = True
1182            row.active = True
1183            row.enabled = True
1184            self.expanded_layout = True
1185
1186        subrow = row.row(align=True)
1187        subrow.prop(self, "filter_name", text="")
1188        icon = 'ZOOM_OUT' if self.use_filter_name_reverse else 'ZOOM_IN'
1189        subrow.prop(self, "use_filter_name_reverse", text="", icon=icon)
1190
1191        #subrow = row.row(align=True)
1192        #subrow.prop(self, "use_filter_deform", toggle=True)
1193        #icon = 'ZOOM_OUT' if self.use_filter_deform_reverse else 'ZOOM_IN'
1194        #subrow.prop(self, "use_filter_deform_reverse", text="", icon=icon)
1195
1196        #subrow = row.row(align=True)
1197        #subrow.prop(self, "use_filter_empty", toggle=True)
1198        #icon = 'ZOOM_OUT' if self.use_filter_empty_reverse else 'ZOOM_IN'
1199        #subrow.prop(self, "use_filter_empty_reverse", text="", icon=icon)
1200
1201        row = layout.row(align=True)
1202        row.label(text="Order by:")
1203        row.prop(self, "use_order_name", toggle=True)
1204        #row.prop(self, "use_order_importance", toggle=True)
1205        icon = 'TRIA_UP' if self.use_filter_orderby_invert else 'TRIA_DOWN'
1206        row.prop(self, "use_filter_orderby_invert", text="", icon=icon)
def filter_items(self, context, data, propname):
1208    def filter_items(self, context, data, propname):
1209        # This function gets the collection property (as the usual tuple (data, propname)), and must return two lists:
1210        # * The first one is for filtering, it must contain 32bit integers were self.bitflag_filter_item marks the
1211        #   matching item as filtered (i.e. to be shown), and 31 other bits are free for custom needs. Here we use the
1212        #   first one to mark VGROUP_EMPTY.
1213        # * The second one is for reordering, it must return a list containing the new indices of the items (which
1214        #   gives us a mapping org_idx -> new_idx).
1215        # Please note that the default UI_UL_list defines helper functions for common tasks (see its doc for more info).
1216        # If you do not make filtering and/or ordering, return empty list(s) (this will be more efficient than
1217        # returning full lists doing nothing!).
1218        items = getattr(data, propname)
1219        
1220        #if self.armature == None:
1221        #    target_ob, source_ob = common.get_target_and_source_ob(context)
1222        #    armature_ob = target_ob.find_armature() or source_ob.find_armature()
1223        #    self.armature = armature_ob and armature_ob.data or False
1224        #
1225        #if not self.local_bone_names:
1226        #    target_ob, source_ob = common.get_target_and_source_ob(context)
1227        #    bone_data_ob = (target_ob.get("LocalBoneData:0") and target_ob) or (source_ob.get("LocalBoneData:0") and source_ob) or None
1228        #    if bone_data_ob:
1229        #        local_bone_data = model_export.CNV_OT_export_cm3d2_model.local_bone_data_parser(model_export.CNV_OT_export_cm3d2_model.indexed_data_generator(bone_data_ob, prefix="LocalBoneData:"))
1230        #        self.local_bone_names = [ bone['name'] for bone in local_bone_data ]
1231        
1232        if not self.cached_values:
1233            self.cached_values = { item.name: item.value for item in items }
1234        #vgroups = [ getattr(data, 'matched_vgroups')[item.index][0]   for item in items ]
1235        helper_funcs = bpy.types.UI_UL_list
1236
1237        # Default return values.
1238        flt_flags = []
1239        flt_neworder = []
1240
1241        # Pre-compute of vgroups data, CPU-intensive. :/
1242        #vgroups_empty = self.filter_items_empty_vgroups(context, vgroups)
1243
1244        # Filtering by name
1245        if self.filter_name:
1246            flt_flags = helper_funcs.filter_items_by_name(self.filter_name, self.bitflag_filter_item, items, "name",
1247                                                          reverse=self.use_filter_name_reverse)
1248        if not flt_flags:
1249            flt_flags = [self.bitflag_filter_item] * len(items)
1250        
1251        #for idx, vg in enumerate(items):
1252        #    # Filter by deform.
1253        #    if self.use_filter_deform:
1254        #        flt_flags[idx] |= self.VGROUP_DEFORM
1255        #        if self.use_filter_deform:
1256        #            if self.armature and self.armature.get(vg.name):
1257        #                if not self.use_filter_deform_reverse:
1258        #                    flt_flags[idx] &= ~self.VGROUP_DEFORM
1259        #            elif bone_data_ob and (vg.name in self.local_bone_names):
1260        #                if not self.use_filter_deform_reverse:
1261        #                    flt_flags[idx] &= ~self.VGROUP_DEFORM
1262        #            elif self.use_filter_deform_reverse or (not self.armature and not self.local_bone_names):
1263        #                flt_flags[idx] &= ~self.VGROUP_DEFORM
1264        #    else:
1265        #        flt_flags[idx] &= ~self.VGROUP_DEFORM
1266        #
1267        #    # Filter by emptiness.
1268        #    #if vgroups_empty[vg.index][0]:
1269        #    #    flt_flags[idx] |= self.VGROUP_EMPTY
1270        #    #    if self.use_filter_empty and self.use_filter_empty_reverse:
1271        #    #        flt_flags[idx] &= ~self.bitflag_filter_item
1272        #    #elif self.use_filter_empty and not self.use_filter_empty_reverse:
1273        #    #    flt_flags[idx] &= ~self.bitflag_filter_item
1274        
1275        # Reorder by name or average weight.
1276        if self.use_order_name:
1277            flt_neworder = helper_funcs.sort_items_by_name(items, "name")
1278        #elif self.use_order_importance:
1279        #    _sort = [(idx, vgroups_empty[vg.index][1]) for idx, vg in enumerate(vgroups)]
1280        #    flt_neworder = helper_funcs.sort_items_helper(_sort, lambda e: e[1], True)
1281
1282        return flt_flags, flt_neworder
Inherited Members
bpy_types.UIList
bl_rna
bpy_types._GenericUI
is_extended
append
prepend
remove
builtins.bpy_struct
keys
values
items
get
pop
as_pointer
keyframe_insert
keyframe_delete
driver_add
driver_remove
is_property_set
property_unset
is_property_hidden
is_property_readonly
is_property_overridable_library
property_overridable_library_set
path_resolve
path_from_id
type_recast
bl_rna_get_subclass_py
bl_rna_get_subclass
id_properties_ensure
id_properties_clear
id_properties_ui
id_data
@compat.BlRegister()
class CNV_SelectorItem(bpy_types.PropertyGroup):
1286@compat.BlRegister()
1287class CNV_SelectorItem(bpy.types.PropertyGroup):
1288    bl_label       = "CNV_SelectorItem"
1289    bl_region_type = 'WINDOW'
1290    bl_space_type  = 'PROPERTIES'
1291
1292    name      = bpy.props.StringProperty (name="Name"    , default="Unknown")
1293    value     = bpy.props.BoolProperty   (name="Value"   , default=True     )
1294    index     = bpy.props.IntProperty    (name="Index"   , default=-1       )
1295    preferred = bpy.props.BoolProperty   (name="Prefered", default=True     )
1296    icon      = bpy.props.StringProperty (name="Icon"    , default='NONE'   )
1297
1298    filter0   = bpy.props.BoolProperty   (name="Filter 0", default=False    )
1299    filter1   = bpy.props.BoolProperty   (name="Filter 1", default=False    )
1300    filter2   = bpy.props.BoolProperty   (name="Filter 2", default=False    )
1301    filter3   = bpy.props.BoolProperty   (name="Filter 3", default=False    )
bl_label = 'CNV_SelectorItem'
bl_region_type = 'WINDOW'
bl_space_type = 'PROPERTIES'
name: <_PropertyDeferred, <built-in function StringProperty>, {'name': 'Name', 'default': 'Unknown', 'attr': 'name'}> = <_PropertyDeferred, <built-in function StringProperty>, {'name': 'Name', 'default': 'Unknown', 'attr': 'name'}>
value: <_PropertyDeferred, <built-in function BoolProperty>, {'name': 'Value', 'default': True, 'attr': 'value'}> = <_PropertyDeferred, <built-in function BoolProperty>, {'name': 'Value', 'default': True, 'attr': 'value'}>
index: <_PropertyDeferred, <built-in function IntProperty>, {'name': 'Index', 'default': -1, 'attr': 'index'}> = <_PropertyDeferred, <built-in function IntProperty>, {'name': 'Index', 'default': -1, 'attr': 'index'}>
preferred: <_PropertyDeferred, <built-in function BoolProperty>, {'name': 'Prefered', 'default': True, 'attr': 'preferred'}> = <_PropertyDeferred, <built-in function BoolProperty>, {'name': 'Prefered', 'default': True, 'attr': 'preferred'}>
icon: <_PropertyDeferred, <built-in function StringProperty>, {'name': 'Icon', 'default': 'NONE', 'attr': 'icon'}> = <_PropertyDeferred, <built-in function StringProperty>, {'name': 'Icon', 'default': 'NONE', 'attr': 'icon'}>
filter0: <_PropertyDeferred, <built-in function BoolProperty>, {'name': 'Filter 0', 'default': False, 'attr': 'filter0'}> = <_PropertyDeferred, <built-in function BoolProperty>, {'name': 'Filter 0', 'default': False, 'attr': 'filter0'}>
filter1: <_PropertyDeferred, <built-in function BoolProperty>, {'name': 'Filter 1', 'default': False, 'attr': 'filter1'}> = <_PropertyDeferred, <built-in function BoolProperty>, {'name': 'Filter 1', 'default': False, 'attr': 'filter1'}>
filter2: <_PropertyDeferred, <built-in function BoolProperty>, {'name': 'Filter 2', 'default': False, 'attr': 'filter2'}> = <_PropertyDeferred, <built-in function BoolProperty>, {'name': 'Filter 2', 'default': False, 'attr': 'filter2'}>
filter3: <_PropertyDeferred, <built-in function BoolProperty>, {'name': 'Filter 3', 'default': False, 'attr': 'filter3'}> = <_PropertyDeferred, <built-in function BoolProperty>, {'name': 'Filter 3', 'default': False, 'attr': 'filter3'}>
bl_rna = <bpy_struct, Struct("CNV_SelectorItem")>
Inherited Members
builtins.bpy_struct
keys
values
items
get
pop
as_pointer
keyframe_insert
keyframe_delete
driver_add
driver_remove
is_property_set
property_unset
is_property_hidden
is_property_readonly
is_property_overridable_library
property_overridable_library_set
path_resolve
path_from_id
type_recast
bl_rna_get_subclass_py
bl_rna_get_subclass
id_properties_ensure
id_properties_clear
id_properties_ui
id_data
def values_of_matched_keys(dict1, dict2):
1308def values_of_matched_keys(dict1, dict2):
1309    value_list = []
1310    items1 = dict1.items()
1311    items2 = dict2.items()
1312    if len(items1) <= len(items2): 
1313        items1.reverse()
1314        for k1, v1 in items1:
1315            for i in range(len(items2)-1, 0-1, -1):
1316                k2, v2 = items2[i]
1317                if k1 == k2:
1318                    value_list.append((v1,v2))
1319                    del items2[i]
1320    else:
1321        items2.reverse()
1322        for k2, v2 in items2:
1323            for i in range(len(items1)-1, 0-1, -1):
1324                k1, v1 = items1[i]
1325                if k1 == k2:
1326                    value_list.append((v1,v2))
1327                    del items1[i]
1328    
1329    value_list.reverse()
1330    return value_list
def get_target_and_source_ob(context, copyTarget=False, copySource=False):
1334def get_target_and_source_ob(context, copyTarget=False, copySource=False):
1335    target_ob = None
1336    source_ob = None
1337    target_original_ob = None
1338    source_original_ob = None
1339
1340    target_original_ob = context.object
1341    if copyTarget:
1342        target_ob = target_original_ob.copy()
1343        target_ob.data = target_original_ob.data.copy()
1344    else:
1345        target_ob = target_original_ob
1346
1347    for ob in context.selected_objects:
1348        if ob != target_ob:
1349            source_original_ob = ob
1350            break
1351    
1352    if copySource:
1353        source_ob = source_original_ob.copy()
1354        source_ob.data = source_original_ob.data.copy()
1355    else:
1356        source_ob = source_original_ob
1357    
1358    if copyTarget:
1359        if copySource:
1360            return target_ob, source_ob, target_original_ob, source_original_ob
1361        else:
1362            return target_ob, source_ob, target_original_ob
1363    elif copySource:
1364        return  target_ob, source_ob, source_original_ob
1365    else:
1366        return  target_ob, source_ob
def is_descendant_of(bone, ancestor) -> bool:
1370def is_descendant_of(bone, ancestor) -> bool:
1371    """Returns true if a bone is the descendant of the given ancestor"""
1372    while bone.parent:
1373        bone = bone.parent
1374        if bone.name == ancestor.name:
1375            return True
1376    return False

Returns true if a bone is the descendant of the given ancestor