├── .gitattributes ├── .gitignore ├── README.md ├── LICENSE ├── misc ├── Vroid2vrc_ripsync_recipe.json ├── glb_bin_collector.py ├── VRM_HELPER.py └── glb_factory.py ├── importer ├── binaly_loader.py ├── vrm2pydata_factory.py ├── vrm_load.py └── model_build.py ├── __init__.py ├── V_Types.py └── gl_const.py /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .vscode/.ropeproject/objectdb 3 | .vscode/.ropeproject/config.py 4 | .vscode/tasks.json 5 | *.pyc 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # THIS ADDON IS OUTDATED. NO SUPPORT. 2 | # NO WARRANTEIS in here (written in licence) AND other fork addons. 3 | # VRM_IMPORTER_for_Blender2_79 は今後更新・サポートの予定はありません。 4 | ## 旧来のライセンス(MIT)にある通りですが、改めて、このアドオンの動作については無保証で、 5 | ## つまり、このアドオンの使用等において発生した損害等々において私はすべての責を負いません。 6 | ( https://github.com/iCyP/VRM_IMPORTER_for_Blender2_79 にあるものは悪意のある実装はしていないと思っていますが念のため) 7 | # また当然のことですが、このアドオンの後継等が発生したとして、私はそれらが安全であることを保障することは_不可能_です。 8 | 後継を騙る悪意あるスクリプトしかない状況、を防ぐ対策として、このリポジトリは存在しています。他者に譲ることもありません(アカウントごと奪われることはあるかもしれませんが) 9 | また、このリポジトリは一個人の趣味の成果物が置いてあるだけという事を、ここに付しておきます。 10 | 11 | OUTDATED(更新停止)の理由は、同じようなものを2.79,2.80向けに2つも作るのが面白くないからです。以上。 12 | 13 | では、HAPPY BLENDING! 14 | ## MIT license  15 | ## vrm0.0(uniVRM0.50)未満まである程度対応のはず 16 | ## Draco complessed data is unsupported 17 | ## Blender2.80向けはこっち (for Blender2.8 is below) 18 | https://github.com/iCyP/VRM_IMPORTER_for_Blender2_8 19 | 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 iCyP 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /misc/Vroid2vrc_ripsync_recipe.json: -------------------------------------------------------------------------------- 1 | { 2 | "licence": "author https://creativecommons.org/licenses/by/4.0/", 3 | "shapekeys": { 4 | "vrc.blink_left":{"Face.M_F00_000_Fcl_EYE_Close_L":1.0}, 5 | "vrc.blink_right":{"Face.M_F00_000_Fcl_EYE_Close_R":1.0}, 6 | "vrc.lowerlid_left":{}, 7 | "vrc.lowerlid_right":{}, 8 | "vrc.v_aa":{"Face.M_F00_000_Fcl_MTH_A":1.0}, 9 | "vrc.v_ch":{"Face.M_F00_000_Fcl_MTH_I":0.6}, 10 | "vrc.v_dd":{"Face.M_F00_000_Fcl_MTH_E":0.7}, 11 | "vrc.v_e":{"Face.M_F00_000_Fcl_MTH_E":1.0}, 12 | "vrc.v_ff":{"Face.M_F00_000_Fcl_MTH_A":0.2}, 13 | "vrc.v_ih":{"Face.M_F00_000_Fcl_MTH_A":1.0}, 14 | "vrc.v_kk":{"Face.M_F00_000_Fcl_MTH_A":1.0}, 15 | "vrc.v_nn":{"Face.M_F00_000_Fcl_MTH_E":0.7}, 16 | "vrc.v_oh":{"Face.M_F00_000_Fcl_MTH_O":1.0}, 17 | "vrc.v_ou":{"Face.M_F00_000_Fcl_MTH_U":0.8}, 18 | "vrc.v_pp":{"Face.M_F00_000_Fcl_MTH_Angry":1.0}, 19 | "vrc.v_rr":{"Face.M_F00_000_Fcl_MTH_A":0.2}, 20 | "vrc.v_sil":{}, 21 | "vrc.v_ss":{"Face.M_F00_000_Fcl_MTH_I":0.8}, 22 | "vrc.v_th":{"Face.M_F00_000_Fcl_MTH_A":1.0} 23 | } 24 | } -------------------------------------------------------------------------------- /importer/binaly_loader.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2018 iCyP 3 | Released under the MIT license 4 | https://opensource.org/licenses/mit-license.php 5 | 6 | """ 7 | 8 | import struct 9 | from ..gl_const import GL_CONSTANS 10 | 11 | 12 | class Binaly_Reader: 13 | def __init__(self, data: bytes)->None: 14 | self.data = data 15 | self.pos = 0 16 | 17 | def set_pos(self, pos): 18 | self.pos = pos 19 | 20 | def read_str(self, size): 21 | result = self.data[self.pos: self.pos + size] 22 | self.pos += size 23 | return result.decode("utf-8") 24 | 25 | def read_binaly(self, size): 26 | result = self.data[self.pos: self.pos + size] 27 | self.pos += size 28 | return result 29 | 30 | def read_uint(self): 31 | #unpackは内容の個数に関わらずタプルで返すので[0]が必要 32 | result = struct.unpack('Import", 22 | "description": "VRM(less than spec0.0(uniVRM0.49)) Importer", 23 | "warning": "THIS ADDON IS OUTDATED. NO SURPORT. In particular,there is no warranteis of safe, if you don't download this from https://github.com/iCyP/VRM_IMPORTER_for_Blender2_79, ", 24 | "support": "TESTING", 25 | "wiki_url": "", 26 | "tracker_url": "https://github.com/iCyP/VRM_IMPORTER_for_Blender2_79", 27 | "category": "Import-Export" 28 | } 29 | 30 | 31 | class ImportVRM(bpy.types.Operator,ImportHelper): 32 | bl_idname = "import_scene.vrm" 33 | bl_label = "import VRM" 34 | bl_description = "import VRM" 35 | bl_options = {'REGISTER', 'UNDO'} 36 | 37 | filename_ext = '.vrm' 38 | filter_glob = bpy.props.StringProperty( 39 | default='*.vrm', 40 | options={'HIDDEN'} 41 | ) 42 | 43 | is_put_spring_bone_info = bpy.props.BoolProperty(name = "Put Collider Empty") 44 | 45 | 46 | def execute(self,context): 47 | fdir = self.filepath 48 | model_build.Blend_model(vrm_load.read_vrm(fdir),self.is_put_spring_bone_info) 49 | return {'FINISHED'} 50 | 51 | 52 | def menu_import(self, context): 53 | op = self.layout.operator(ImportVRM.bl_idname, text="VRM (.vrm)") 54 | op.is_put_spring_bone_info = True 55 | 56 | class ExportVRM(bpy.types.Operator,ExportHelper): 57 | bl_idname = "export_scene.vrm" 58 | bl_label = "export VRM" 59 | bl_description = "export VRM" 60 | bl_options = {'REGISTER', 'UNDO'} 61 | 62 | filename_ext = '.vrm' 63 | filter_glob = bpy.props.StringProperty( 64 | default='*.vrm', 65 | options={'HIDDEN'} 66 | ) 67 | 68 | def execute(self,context): 69 | fdir = self.filepath 70 | bin = glb_factory.Glb_obj(bl_info["author"]).convert_bpy2glb() 71 | with open(fdir,"wb") as f: 72 | f.write(bin) 73 | return {'FINISHED'} 74 | 75 | 76 | def menu_export(self, context): 77 | op = self.layout.operator(ExportVRM.bl_idname, text="VRM (.vrm)") 78 | 79 | 80 | class VRM_IMPORTER_UI_controller(bpy.types.Panel): 81 | bl_idname = "icyp_ui_controller" 82 | bl_label = "vrm import helper" 83 | #どこに置くかの定義 84 | bl_space_type = "VIEW_3D" 85 | bl_region_type = "TOOLS" 86 | bl_category = "VRM HELPER" 87 | 88 | @classmethod 89 | def poll(self, context): 90 | return True 91 | 92 | def draw(self, context): 93 | self.layout.label(text="if you select armature in object mode") 94 | self.layout.label(text="armature renamer is shown") 95 | self.layout.label(text="if you in MESH EDIT") 96 | self.layout.label(text="symmetry button is shown") 97 | self.layout.label(text="*symmetry is in default blender") 98 | if context.mode == "OBJECT": 99 | if context.active_object is not None: 100 | self.layout.operator(VRM_HELPER.VRM_VALIDATOR.bl_idname) 101 | if context.active_object.type == 'ARMATURE': 102 | self.layout.label(icon ="ERROR" ,text="EXPERIMENTAL!!!") 103 | self.layout.operator(VRM_HELPER.Bones_rename.bl_idname) 104 | if context.active_object.type =="MESH": 105 | self.layout.label(icon="ERROR",text="EXPERIMENTAL!お試し版。あてにしない") 106 | self.layout.operator(VRM_HELPER.Vroid2VRC_ripsync_from_json_recipe.bl_idname) 107 | if context.mode == "EDIT_MESH": 108 | self.layout.operator(bpy.ops.mesh.symmetry_snap.idname_py()) 109 | 110 | 111 | 112 | 113 | classes = ( 114 | ImportVRM, 115 | ExportVRM, 116 | VRM_HELPER.Bones_rename, 117 | VRM_HELPER.Vroid2VRC_ripsync_from_json_recipe, 118 | VRM_HELPER.VRM_VALIDATOR, 119 | VRM_IMPORTER_UI_controller 120 | ) 121 | 122 | 123 | # アドオン有効化時の処理 124 | def register(): 125 | for cls in classes: 126 | bpy.utils.register_class(cls) 127 | bpy.types.INFO_MT_file_import.append(menu_import) 128 | bpy.types.INFO_MT_file_export.append(menu_export) 129 | 130 | 131 | 132 | 133 | # アドオン無効化時の処理 134 | def unregister(): 135 | bpy.types.INFO_MT_file_export.remove(menu_export) 136 | bpy.types.INFO_MT_file_import.remove(menu_import) 137 | for cls in classes: 138 | bpy.utils.unregister_class(cls) 139 | 140 | if "__main__" == __name__: 141 | register() 142 | -------------------------------------------------------------------------------- /importer/vrm2pydata_factory.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2018 iCyP 3 | Released under the MIT license 4 | https://opensource.org/licenses/mit-license.php 5 | 6 | """ 7 | 8 | from .. import V_Types as VRM_Types 9 | 10 | def bone(node)->VRM_Types.Node: 11 | v_node = VRM_Types.Node() 12 | if "name" in node: 13 | v_node.name = node["name"] 14 | else: 15 | v_node.name = "tmp" 16 | v_node.position = node["translation"] 17 | v_node.rotation = node["rotation"] 18 | v_node.scale = node["scale"] 19 | if "children" in node: 20 | if type(node["children"]) is int: 21 | v_node.children = [node["children"]] 22 | else: 23 | v_node.children = node["children"] 24 | else: 25 | v_node.children = None 26 | if "mesh" in node: 27 | v_node.mesh_id = node["mesh"] 28 | if "skin" in node: 29 | v_node.skin_id = node["skin"] 30 | return v_node 31 | 32 | 33 | 34 | def material(mat,ext_mat,textures)->VRM_Types.Material: 35 | def gltf_mat_factory(): 36 | gltf_mat = VRM_Types.Material_GLTF() 37 | gltf_mat.name = mat["name"] 38 | gltf_mat.shader_name = "gltf" 39 | if "pbrMetallicRoughness" in mat: 40 | pbrmat = mat["pbrMetallicRoughness"] 41 | if "baseColorTexture" in pbrmat: 42 | texture_index = pbrmat["baseColorTexture"]["index"] 43 | gltf_mat.color_texture_index = textures[texture_index]["source"] 44 | gltf_mat.color_texcoord_index= pbrmat["baseColorTexture"]["texCoord"] 45 | if "baseColorFactor" in pbrmat: 46 | gltf_mat.base_color = pbrmat["baseColorFactor"] 47 | if "metallicFactor" in pbrmat: 48 | gltf_mat.metallic_factor = pbrmat["metallicFactor"] 49 | if "roughnessFactor" in pbrmat: 50 | gltf_mat.roughness_factor = pbrmat["roughnessFactor"] 51 | if "metallicRoughnessTexture" in pbrmat: 52 | gltf_mat.metallic_roughness_texture_index = pbrmat["metallicRoughnessTexture"] 53 | gltf_mat.metallic_roughness_texture_texcood = pbrmat["baseColorTexture"]["texCoord"] 54 | if "normalTexture" in mat: 55 | gltf_mat.normal_texture_index = mat["normalTexture"]["index"] 56 | gltf_mat.normal_texture_texcoord_index = mat["normalTexture"]["texCoord"] 57 | if "emissiveTexture" in mat: 58 | gltf_mat.emissive_texture_index = mat["emissiveTexture"]["index"] 59 | gltf_mat.emissive_texture_texcoord_index = mat["emissiveTexture"]["texCoord"] 60 | if "occlusionTexture" in mat: 61 | gltf_mat.occlusion_texture_index = mat["occlusionTexture"]["index"] 62 | gltf_mat.occlusion_texture_texcood_index = mat["occlusionTexture"]["texCoord"] 63 | if "emissiveFactor" in mat: 64 | gltf_mat.emissive_color = mat["emissiveFactor"] 65 | 66 | if "doubleSided" in mat: 67 | gltf_mat.doubleSided = mat["doubleSided"] 68 | if "alphaMode" in mat: 69 | if mat["alphaMode"] == "MASK": 70 | gltf_mat.alpha_mode = "MASK" 71 | if mat["alphaMode"] == "BLEND": 72 | gltf_mat.alpha_mode = "Z_TRANSPARENCY" 73 | if mat["alphaMode"] == "OPAQUE": 74 | gltf_mat.alpha_mode = "OPAQUE" 75 | if "extensions" in mat: 76 | if "KHR_materials_unlit" in mat["extensions"]: 77 | gltf_mat.shadeless = True 78 | 79 | return gltf_mat 80 | 81 | if "VRM_USE_GLTFSHADER" in ext_mat["shader"]: #standard, or VRM unsuported shader(no saved) 82 | v_mat = gltf_mat_factory() 83 | 84 | else:#"MToon or Transparent_Zwrite" 85 | if ext_mat["shader"] == "VRM/MToon": 86 | v_mat = VRM_Types.Material_MToon() 87 | v_mat.name = ext_mat["name"] 88 | v_mat.shader_name = ext_mat["shader"] 89 | #region check unknown props exist 90 | subset = { 91 | "float": ext_mat["floatProperties"].keys() - v_mat.float_props_dic.keys() , 92 | "vector": ext_mat["vectorProperties"].keys() - v_mat.vector_props_dic.keys(), 93 | "texture": ext_mat["textureProperties"].keys() - v_mat.texture_index_dic.keys(), 94 | "keyword": ext_mat["keywordMap"].keys() - v_mat.keyword_dic.keys() 95 | } 96 | for k, _subset in subset.items(): 97 | if _subset: 98 | print("unknown {} propaties {} in {}".format(k, _subset, ext_mat["name"])) 99 | #endregion check unknown props exit 100 | 101 | v_mat.float_props_dic.update(ext_mat["floatProperties"]) 102 | v_mat.vector_props_dic.update(ext_mat["vectorProperties"]) 103 | v_mat.texture_index_dic.update(ext_mat["textureProperties"]) 104 | v_mat.keyword_dic.update(ext_mat["keywordMap"]) 105 | v_mat.tag_dic.update(ext_mat["tagMap"]) 106 | 107 | elif ext_mat["shader"] == "VRM/UnlitTransparentZWrite": 108 | v_mat = VRM_Types.Material_Transparent_Z_write() 109 | v_mat.name = ext_mat["name"] 110 | v_mat.shader_name = ext_mat["shader"] 111 | v_mat.float_props_dic.update(ext_mat["floatProperties"]) 112 | v_mat.vector_props_dic.update(ext_mat["vectorProperties"]) 113 | v_mat.texture_index_dic.update(ext_mat["textureProperties"]) 114 | else: 115 | print("unknow shader:{}. use gltf material".format(ext_mat["shader"])) 116 | v_mat = gltf_mat_factory() 117 | 118 | return v_mat 119 | 120 | 121 | 122 | 123 | -------------------------------------------------------------------------------- /V_Types.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2018 iCyP 3 | Released under the MIT license 4 | https://opensource.org/licenses/mit-license.php 5 | 6 | """ 7 | class VRM_pydata(object): 8 | def __init__( 9 | self, 10 | filepath = None,json = None,decoded_binary = None, 11 | image_propaties = None,meshes =None,materials = None, 12 | nodes_dict = None,origine_nodes_dict = None, 13 | skins_joints_list = None , skins_root_node_list = None 14 | ): 15 | self.filepath = filepath 16 | self.json = json 17 | self.decoded_binary = decoded_binary 18 | 19 | self.image_propaties = image_propaties if image_propaties is not None else [] 20 | self.meshes = meshes if meshes is not None else [] 21 | self.materials = materials if materials is not None else [] 22 | self.nodes_dict = nodes_dict if nodes_dict is not None else {} 23 | self.origine_nodes_dict = origine_nodes_dict if origine_nodes_dict is not None else {} 24 | self.skins_joints_list = skins_joints_list if skins_joints_list is not None else [] 25 | self.skins_root_node_list = skins_root_node_list if skins_root_node_list is not None else [] 26 | 27 | 28 | class Mesh(object): 29 | def __init__(self): 30 | self.name = "" 31 | self.face_indices = [] 32 | self.skin_id = None 33 | self.object_id = None 34 | 35 | 36 | 37 | class Node(object): 38 | def __init__(self): 39 | self.name = "" 40 | self.position = None 41 | self.rotation = None 42 | self.scale = None 43 | self.children = None 44 | self.blend_bone = None 45 | self.mesh_id = None 46 | self.skin_id = None 47 | 48 | 49 | 50 | class Image_props(object): 51 | def __init__(self,name,filepath,fileType): 52 | self.name = name 53 | self.filePath = filepath 54 | self.fileType = fileType 55 | 56 | 57 | 58 | 59 | class Material(object): 60 | def __init__(self): 61 | self.name = "" 62 | self.shader_name = "" 63 | 64 | 65 | 66 | class Material_GLTF(Material): 67 | def __init__(self): 68 | super().__init__() 69 | self.color_texture_index = None 70 | self.color_texcood_index = None 71 | self.base_color = [1,1,1,1] 72 | self.metallic_factor = 1 73 | self.roughness_factor = 1 74 | self.emissiveFactor = None 75 | self.metallic_roughness_texture_index = None 76 | self.metallic_roughness_texture_texcood = None 77 | self.normal_texture_index = None 78 | self.normal_texture_texcoord_index = None 79 | self.emissive_texture_index = None 80 | self.emissive_texture_texcoord_index = None 81 | self.occlusion_texture_index = None 82 | self.occlusion_texture_texcood_index = None 83 | self.double_sided = None 84 | self.alphaMode = "OPAQUE" 85 | self.shadeless = False 86 | 87 | 88 | class Material_Transparent_Z_write(Material): 89 | float_props = [ 90 | "_MainTex", 91 | "_Cutoff", 92 | "_BlendMode", 93 | "_CullMode", 94 | "_VColBlendMode", 95 | "_SrcBlend", 96 | "_DstBlend", 97 | "_ZWrite", 98 | ] 99 | texture_index_list = [ 100 | "_MainTex" 101 | ] 102 | vector_props = [ 103 | "_Color" 104 | ] 105 | 106 | def __init__(self): 107 | super().__init__() 108 | self.float_props_dic = {prop: None for prop in self.float_props} 109 | self.vector_props_dic = {prop: None for prop in self.vector_props} 110 | self.texture_index_dic = {tex:None for tex in self.texture_index_list} 111 | 112 | 113 | 114 | class Material_MToon(Material): 115 | float_props = [ 116 | "_Cutoff", 117 | "_BumpScale", 118 | "_ReceiveShadowRate", 119 | "_ShadeShift", 120 | "_ShadeToony", 121 | "_ShadingGradeRate", 122 | "_LightColorAttenuation", 123 | "_IndirectLightIntensity", 124 | "_OutlineWidth", 125 | "_OutlineScaledMaxDistance", 126 | "_OutlineLightingMix", 127 | "_DebugMode", 128 | "_BlendMode", 129 | "_OutlineWidthMode", 130 | "_OutlineColorMode", 131 | "_CullMode", 132 | "_OutlineCullMode", 133 | "_SrcBlend", 134 | "_DstBlend", 135 | "_ZWrite", 136 | "_IsFirstSetup" 137 | ] 138 | 139 | texture_index_list = [ 140 | "_MainTex",#use in BI 141 | "_ShadeTexture",#ignore in BI 142 | "_BumpMap",#use in BI 143 | "_ReceiveShadowTexture",#ignore in BI 144 | "_ShadingGradeTexture",#ignore in BI 145 | "_EmissionMap",#ignore in BI 146 | "_SphereAdd",#use in BI 147 | "_OutlineWidthTexture"#ignore in BI 148 | ] 149 | vector_props = [ 150 | "_Color", 151 | "_EmissionColor", 152 | "_OutlineColor", 153 | "_ShadeColor" 154 | ] 155 | #texture offset and scaling props by texture 156 | vector_props.extend(texture_index_list) 157 | 158 | keyword_list = [ 159 | "_NORMALMAP", 160 | "_ALPHATEST_ON", 161 | "_ALPHABLEND_ON", 162 | "_ALPHAPREMULTIPLY_ON", 163 | "MTOON_OUTLINE_WIDTH_WORLD", 164 | "MTOON_OUTLINE_WIDTH_SCREEN", 165 | "MTOON_OUTLINE_COLOR_FIXED", 166 | "MTOON_OUTLINE_COLOR_MIXED", 167 | "MTOON_DEBUG_NORMAL", 168 | "MTOON_DEBUG_LITSHADERATE" 169 | ] 170 | tagmap_list = [ 171 | "RenderType" 172 | ] 173 | 174 | def __init__(self): 175 | super().__init__() 176 | self.float_props_dic = {prop:None for prop in self.float_props} 177 | self.vector_props_dic = {prop:None for prop in self.vector_props} 178 | self.texture_index_dic = {prop: None for prop in self.texture_index_list} 179 | self.keyword_dic = {kw:False for kw in self.keyword_list} 180 | self.tag_dic = {tag:None for tag in self.tagmap_list} 181 | 182 | 183 | if "__main__" == __name__: 184 | pass 185 | -------------------------------------------------------------------------------- /gl_const.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2018 iCyP 3 | Released under the MIT license 4 | https://opensource.org/licenses/mit-license.php 5 | 6 | """ 7 | 8 | class GL_CONSTANS: 9 | NONE = 0 10 | NO_ERROR = 0 11 | POINTS = 0 12 | ZERO = 0 13 | LINES = 1 14 | ONE = 1 15 | LINE_LOOP = 2 16 | LINE_STRIP = 3 17 | TRIANGLES = 4 18 | TRIANGLE_STRIP = 5 19 | TRIANGLE_FAN = 6 20 | DEPTH_BUFFER_BIT = 256 21 | NEVER = 512 22 | LESS = 513 23 | EQUAL = 514 24 | LEQUAL = 515 25 | GREATER = 516 26 | NOTEQUAL = 517 27 | GEQUAL = 518 28 | ALWAYS = 519 29 | SRC_COLOR = 768 30 | ONE_MINUS_SRC_COLOR = 769 31 | SRC_ALPHA = 770 32 | ONE_MINUS_SRC_ALPHA = 771 33 | DST_ALPHA = 772 34 | ONE_MINUS_DST_ALPHA = 773 35 | DST_COLOR = 774 36 | ONE_MINUS_DST_COLOR = 775 37 | SRC_ALPHA_SATURATE = 776 38 | STENCIL_BUFFER_BIT = 1024 39 | FRONT = 1028 40 | BACK = 1029 41 | FRONT_AND_BACK = 1032 42 | INVALID_ENUM = 1280 43 | INVALID_VALUE = 1281 44 | INVALID_OPERATION = 1282 45 | OUT_OF_MEMORY = 1285 46 | INVALID_FRAMEBUFFER_OPERATION = 1286 47 | CW = 2304 48 | CCW = 2305 49 | LINE_WIDTH = 2849 50 | CULL_FACE = 2884 51 | CULL_FACE_MODE = 2885 52 | FRONT_FACE = 2886 53 | DEPTH_RANGE = 2928 54 | DEPTH_TEST = 2929 55 | DEPTH_WRITEMASK = 2930 56 | DEPTH_CLEAR_VALUE = 2931 57 | DEPTH_FUNC = 2932 58 | STENCIL_TEST = 2960 59 | STENCIL_CLEAR_VALUE = 2961 60 | STENCIL_FUNC = 2962 61 | STENCIL_VALUE_MASK = 2963 62 | STENCIL_FAIL = 2964 63 | STENCIL_PASS_DEPTH_FAIL = 2965 64 | STENCIL_PASS_DEPTH_PASS = 2966 65 | STENCIL_REF = 2967 66 | STENCIL_WRITEMASK = 2968 67 | VIEWPORT = 2978 68 | DITHER = 3024 69 | BLEND = 3042 70 | SCISSOR_BOX = 3088 71 | SCISSOR_TEST = 3089 72 | COLOR_CLEAR_VALUE = 3106 73 | COLOR_WRITEMASK = 3107 74 | UNPACK_ALIGNMENT = 3317 75 | PACK_ALIGNMENT = 3333 76 | MAX_TEXTURE_SIZE = 3379 77 | MAX_VIEWPORT_DIMS = 3386 78 | SUBPIXEL_BITS = 3408 79 | RED_BITS = 3410 80 | GREEN_BITS = 3411 81 | BLUE_BITS = 3412 82 | ALPHA_BITS = 3413 83 | DEPTH_BITS = 3414 84 | STENCIL_BITS = 3415 85 | TEXTURE_2D = 3553 86 | DONT_CARE = 4352 87 | FASTEST = 4353 88 | NICEST = 4354 89 | BYTE = 5120 90 | UNSIGNED_BYTE = 5121 91 | SHORT = 5122 92 | UNSIGNED_SHORT = 5123 93 | INT = 5124 94 | UNSIGNED_INT = 5125 95 | FLOAT = 5126 96 | INVERT = 5386 97 | TEXTURE = 5890 98 | STENCIL_INDEX = 6401 99 | DEPTH_COMPONENT = 6402 100 | ALPHA = 6406 101 | RGB = 6407 102 | RGBA = 6408 103 | LUMINANCE = 6409 104 | LUMINANCE_ALPHA = 6410 105 | KEEP = 7680 106 | REPLACE = 7681 107 | INCR = 7682 108 | DECR = 7683 109 | VENDOR = 7936 110 | RENDERER = 7937 111 | VERSION = 7938 112 | NEAREST = 9728 113 | LINEAR = 9729 114 | NEAREST_MIPMAP_NEAREST = 9984 115 | LINEAR_MIPMAP_NEAREST = 9985 116 | NEAREST_MIPMAP_LINEAR = 9986 117 | LINEAR_MIPMAP_LINEAR = 9987 118 | TEXTURE_MAG_FILTER = 10240 119 | TEXTURE_MIN_FILTER = 10241 120 | TEXTURE_WRAP_S = 10242 121 | TEXTURE_WRAP_T = 10243 122 | REPEAT = 10497 123 | POLYGON_OFFSET_UNITS = 10752 124 | COLOR_BUFFER_BIT = 16384 125 | CONSTANT_COLOR = 32769 126 | ONE_MINUS_CONSTANT_COLOR = 32770 127 | CONSTANT_ALPHA = 32771 128 | ONE_MINUS_CONSTANT_ALPHA = 32772 129 | BLEND_COLOR = 32773 130 | FUNC_ADD = 32774 131 | BLEND_EQUATION = 32777 132 | BLEND_EQUATION_RGB = 32777 133 | FUNC_SUBTRACT = 32778 134 | FUNC_REVERSE_SUBTRACT = 32779 135 | UNSIGNED_SHORT_4_4_4_4 = 32819 136 | UNSIGNED_SHORT_5_5_5_1 = 32820 137 | POLYGON_OFFSET_FILL = 32823 138 | POLYGON_OFFSET_FACTOR = 32824 139 | RGBA4 = 32854 140 | RGB5_A1 = 32855 141 | TEXTURE_BINDING_2D = 32873 142 | SAMPLE_ALPHA_TO_COVERAGE = 32926 143 | SAMPLE_COVERAGE = 32928 144 | SAMPLE_BUFFERS = 32936 145 | SAMPLES = 32937 146 | SAMPLE_COVERAGE_VALUE = 32938 147 | SAMPLE_COVERAGE_INVERT = 32939 148 | BLEND_DST_RGB = 32968 149 | BLEND_SRC_RGB = 32969 150 | BLEND_DST_ALPHA = 32970 151 | BLEND_SRC_ALPHA = 32971 152 | CLAMP_TO_EDGE = 33071 153 | GENERATE_MIPMAP_HINT = 33170 154 | DEPTH_COMPONENT16 = 33189 155 | DEPTH_STENCIL_ATTACHMENT = 33306 156 | UNSIGNED_SHORT_5_6_5 = 33635 157 | MIRRORED_REPEAT = 33648 158 | ALIASED_POINT_SIZE_RANGE = 33901 159 | ALIASED_LINE_WIDTH_RANGE = 33902 160 | TEXTURE0 = 33984 161 | TEXTURE1 = 33985 162 | TEXTURE2 = 33986 163 | TEXTURE3 = 33987 164 | TEXTURE4 = 33988 165 | TEXTURE5 = 33989 166 | TEXTURE6 = 33990 167 | TEXTURE7 = 33991 168 | TEXTURE8 = 33992 169 | TEXTURE9 = 33993 170 | TEXTURE10 = 33994 171 | TEXTURE11 = 33995 172 | TEXTURE12 = 33996 173 | TEXTURE13 = 33997 174 | TEXTURE14 = 33998 175 | TEXTURE15 = 33999 176 | TEXTURE16 = 34000 177 | TEXTURE17 = 34001 178 | TEXTURE18 = 34002 179 | TEXTURE19 = 34003 180 | TEXTURE20 = 34004 181 | TEXTURE21 = 34005 182 | TEXTURE22 = 34006 183 | TEXTURE23 = 34007 184 | TEXTURE24 = 34008 185 | TEXTURE25 = 34009 186 | TEXTURE26 = 34010 187 | TEXTURE27 = 34011 188 | TEXTURE28 = 34012 189 | TEXTURE29 = 34013 190 | TEXTURE30 = 34014 191 | TEXTURE31 = 34015 192 | ACTIVE_TEXTURE = 34016 193 | MAX_RENDERBUFFER_SIZE = 34024 194 | DEPTH_STENCIL = 34041 195 | INCR_WRAP = 34055 196 | DECR_WRAP = 34056 197 | TEXTURE_CUBE_MAP = 34067 198 | TEXTURE_BINDING_CUBE_MAP = 34068 199 | TEXTURE_CUBE_MAP_POSITIVE_X = 34069 200 | TEXTURE_CUBE_MAP_NEGATIVE_X = 34070 201 | TEXTURE_CUBE_MAP_POSITIVE_Y = 34071 202 | TEXTURE_CUBE_MAP_NEGATIVE_Y = 34072 203 | TEXTURE_CUBE_MAP_POSITIVE_Z = 34073 204 | TEXTURE_CUBE_MAP_NEGATIVE_Z = 34074 205 | MAX_CUBE_MAP_TEXTURE_SIZE = 34076 206 | VERTEX_ATTRIB_ARRAY_ENABLED = 34338 207 | VERTEX_ATTRIB_ARRAY_SIZE = 34339 208 | VERTEX_ATTRIB_ARRAY_STRIDE = 34340 209 | VERTEX_ATTRIB_ARRAY_TYPE = 34341 210 | CURRENT_VERTEX_ATTRIB = 34342 211 | VERTEX_ATTRIB_ARRAY_POINTER = 34373 212 | NUM_COMPRESSED_TEXTURE_FORMATS = 34466 213 | COMPRESSED_TEXTURE_FORMATS = 34467 214 | BUFFER_SIZE = 34660 215 | BUFFER_USAGE = 34661 216 | STENCIL_BACK_FUNC = 34816 217 | STENCIL_BACK_FAIL = 34817 218 | STENCIL_BACK_PASS_DEPTH_FAIL = 34818 219 | STENCIL_BACK_PASS_DEPTH_PASS = 34819 220 | BLEND_EQUATION_ALPHA = 34877 221 | MAX_VERTEX_ATTRIBS = 34921 222 | VERTEX_ATTRIB_ARRAY_NORMALIZED = 34922 223 | MAX_TEXTURE_IMAGE_UNITS = 34930 224 | ARRAY_BUFFER = 34962 225 | ELEMENT_ARRAY_BUFFER = 34963 226 | ARRAY_BUFFER_BINDING = 34964 227 | ELEMENT_ARRAY_BUFFER_BINDING = 34965 228 | VERTEX_ATTRIB_ARRAY_BUFFER_BINDING = 34975 229 | STREAM_DRAW = 35040 230 | STATIC_DRAW = 35044 231 | DYNAMIC_DRAW = 35048 232 | FRAGMENT_SHADER = 35632 233 | VERTEX_SHADER = 35633 234 | MAX_VERTEX_TEXTURE_IMAGE_UNITS = 35660 235 | MAX_COMBINED_TEXTURE_IMAGE_UNITS = 35661 236 | SHADER_TYPE = 35663 237 | FLOAT_VEC2 = 35664 238 | FLOAT_VEC3 = 35665 239 | FLOAT_VEC4 = 35666 240 | INT_VEC2 = 35667 241 | INT_VEC3 = 35668 242 | INT_VEC4 = 35669 243 | BOOL = 35670 244 | BOOL_VEC2 = 35671 245 | BOOL_VEC3 = 35672 246 | BOOL_VEC4 = 35673 247 | FLOAT_MAT2 = 35674 248 | FLOAT_MAT3 = 35675 249 | FLOAT_MAT4 = 35676 250 | SAMPLER_2D = 35678 251 | SAMPLER_CUBE = 35680 252 | DELETE_STATUS = 35712 253 | COMPILE_STATUS = 35713 254 | LINK_STATUS = 35714 255 | VALIDATE_STATUS = 35715 256 | INFO_LOG_LENGTH = 35716 257 | ATTACHED_SHADERS = 35717 258 | ACTIVE_UNIFORMS = 35718 259 | ACTIVE_UNIFORM_MAX_LENGTH = 35719 260 | SHADER_SOURCE_LENGTH = 35720 261 | ACTIVE_ATTRIBUTES = 35721 262 | ACTIVE_ATTRIBUTE_MAX_LENGTH = 35722 263 | SHADING_LANGUAGE_VERSION = 35724 264 | CURRENT_PROGRAM = 35725 265 | STENCIL_BACK_REF = 36003 266 | STENCIL_BACK_VALUE_MASK = 36004 267 | STENCIL_BACK_WRITEMASK = 36005 268 | FRAMEBUFFER_BINDING = 36006 269 | RENDERBUFFER_BINDING = 36007 270 | FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE = 36048 271 | FRAMEBUFFER_ATTACHMENT_OBJECT_NAME = 36049 272 | FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL = 36050 273 | FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE = 36051 274 | FRAMEBUFFER_COMPLETE = 36053 275 | FRAMEBUFFER_INCOMPLETE_ATTACHMENT = 36054 276 | FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT = 36055 277 | FRAMEBUFFER_INCOMPLETE_DIMENSIONS = 36057 278 | FRAMEBUFFER_UNSUPPORTED = 36061 279 | COLOR_ATTACHMENT0 = 36064 280 | DEPTH_ATTACHMENT = 36096 281 | STENCIL_ATTACHMENT = 36128 282 | FRAMEBUFFER = 36160 283 | RENDERBUFFER = 36161 284 | RENDERBUFFER_WIDTH = 36162 285 | RENDERBUFFER_HEIGHT = 36163 286 | RENDERBUFFER_INTERNAL_FORMAT = 36164 287 | STENCIL_INDEX8 = 36168 288 | RENDERBUFFER_RED_SIZE = 36176 289 | RENDERBUFFER_GREEN_SIZE = 36177 290 | RENDERBUFFER_BLUE_SIZE = 36178 291 | RENDERBUFFER_ALPHA_SIZE = 36179 292 | RENDERBUFFER_DEPTH_SIZE = 36180 293 | RENDERBUFFER_STENCIL_SIZE = 36181 294 | RGB565 = 36194 295 | LOW_FLOAT = 36336 296 | MEDIUM_FLOAT = 36337 297 | HIGH_FLOAT = 36338 298 | LOW_INT = 36339 299 | MEDIUM_INT = 36340 300 | HIGH_INT = 36341 301 | SHADER_COMPILER = 36346 302 | MAX_VERTEX_UNIFORM_VECTORS = 36347 303 | MAX_VARYING_VECTORS = 36348 304 | MAX_FRAGMENT_UNIFORM_VECTORS = 36349 305 | UNPACK_FLIP_Y_WEBGL = 37440 306 | UNPACK_PREMULTIPLY_ALPHA_WEBGL = 37441 307 | CONTEXT_LOST_WEBGL = 37442 308 | UNPACK_COLORSPACE_CONVERSION_WEBGL = 37443 309 | BROWSER_DEFAULT_WEBGL = 37444 -------------------------------------------------------------------------------- /misc/VRM_HELPER.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2018 iCyP 3 | Released under the MIT license 4 | https://opensource.org/licenses/mit-license.php 5 | 6 | """ 7 | import bpy,blf 8 | import bmesh 9 | import re 10 | from math import sqrt, pow 11 | from mathutils import Vector 12 | from collections import deque 13 | class Bones_rename(bpy.types.Operator): 14 | bl_idname = "vrm.bones_rename" 15 | bl_label = "convert Vroid_bones" 16 | bl_description = "convert Vroid_bones as blender type" 17 | bl_options = {'REGISTER', 'UNDO'} 18 | 19 | 20 | def execute(self, context): 21 | for x in bpy.context.active_object.data.bones: 22 | for RL in ["L","R"]: 23 | ma = re.match("(.*)_"+RL+"_(.*)",x.name) 24 | if ma: 25 | tmp = "" 26 | for y in ma.groups(): 27 | tmp += y + "_" 28 | tmp += RL 29 | x.name = tmp 30 | return {"FINISHED"} 31 | 32 | 33 | import json 34 | from collections import OrderedDict 35 | import os 36 | 37 | class Vroid2VRC_ripsync_from_json_recipe(bpy.types.Operator): 38 | bl_idname = "vrm.ripsync_vrm" 39 | bl_label = "make ripsync4VRC" 40 | bl_description = "make ripsync from Vroid to VRC by json" 41 | bl_options = {'REGISTER', 'UNDO'} 42 | 43 | def execute(self, context): 44 | recipe_uri =os.path.join(os.path.dirname(__file__) ,"Vroid2vrc_ripsync_recipe.json") 45 | recipe = None 46 | with open(recipe_uri,"rt") as raw_recipe: 47 | recipe = json.loads(raw_recipe.read(),object_pairs_hook=OrderedDict) 48 | for shapekey_name,based_values in recipe["shapekeys"].items(): 49 | for k in bpy.context.active_object.data.shape_keys.key_blocks: 50 | k.value = 0.0 51 | for based_shapekey_name,based_val in based_values.items(): 52 | bpy.context.active_object.data.shape_keys.key_blocks[based_shapekey_name].value = based_val 53 | bpy.ops.object.shape_key_add(from_mix = True) 54 | bpy.context.active_object.data.shape_keys.key_blocks[-1].name = shapekey_name 55 | for k in bpy.context.active_object.data.shape_keys.key_blocks: 56 | k.value = 0.0 57 | return {"FINISHED"} 58 | 59 | 60 | class VRM_VALIDATOR(bpy.types.Operator): 61 | bl_idname = "vrm.model_validate" 62 | bl_label = "check as VRM model" 63 | bl_description = "NO Quad_Poly & N_GON, NO unSkind Mesh etc..." 64 | bl_options = {'REGISTER', 'UNDO'} 65 | 66 | messages_set= [] 67 | def execute(self,context): 68 | messages = VRM_VALIDATOR.messages_set = set() 69 | print("validation start") 70 | armature_count = 0 71 | armature = None 72 | node_name_set = set() 73 | #region selected object seeking 74 | for obj in bpy.context.selected_objects: 75 | if obj.name in node_name_set: 76 | messages.add("VRM exporter need Nodes(mesh,bones) name is unique. {} is doubled.".format(obj.name)) 77 | node_name_set.add(obj.name) 78 | if obj.type != "EMPTY" and (obj.parent is not None and obj.parent.type != "ARMATURE" and obj.type == "MESH"): 79 | if obj.location != Vector([0.0,0.0,0.0]):#mesh and armature origin is on [0,0,0] 80 | messages.add("There are not on origine location object {}".format(obj.name)) 81 | if obj.type == "ARMATURE": 82 | armature = obj 83 | armature_count += 1 84 | if armature_count >= 2:#only one armature 85 | messages.add("VRM exporter needs only one armature not some armatures") 86 | already_root_bone_exist = False 87 | for bone in obj.data.bones: 88 | if bone.name in node_name_set:#nodes name is unique 89 | messages.add("VRM exporter need Nodes(mesh,bones) name is unique. {} is doubled".format(bone.name)) 90 | node_name_set.add(bone.name) 91 | if bone.parent == None: #root bone is only 1 92 | if already_root_bone_exist: 93 | messages.add("root bone is only one {},{} are root bone now".format(bone.name,already_root_bone_exist)) 94 | already_root_bone_exist = bone.name 95 | #TODO: T_POSE, 96 | require_human_bone_dic = {bone_tag : None for bone_tag in [ 97 | "hips","leftUpperLeg","rightUpperLeg","leftLowerLeg","rightLowerLeg","leftFoot","rightFoot", 98 | "spine","chest","neck","head","leftUpperArm","rightUpperArm", 99 | "leftLowerArm","rightLowerArm","leftHand","rightHand" 100 | ]} 101 | for bone in armature.data.bones: 102 | if "humanBone" in bone.keys(): 103 | if bone["humanBone"] in require_human_bone_dic.keys(): 104 | if require_human_bone_dic[bone["humanBone"]]: 105 | messages.add("humanBone is doubled with {},{}".format(bone.name,require_human_bone_dic[bone["humanBone"]].name)) 106 | else: 107 | require_human_bone_dic[bone["humanBone"]] = bone 108 | for k,v in require_human_bone_dic.items(): 109 | if v is None: 110 | messages.add("humanBone: {} is not defined.".format(k)) 111 | defined_human_bone = ["jaw","leftShoulder","rightShoulder", 112 | "leftEye","rightEye","upperChest","leftToes","rightToes", 113 | "leftThumbProximal","leftThumbIntermediate","leftThumbDistal","leftIndexProximal", 114 | "leftIndexIntermediate","leftIndexDistal","leftMiddleProximal","leftMiddleIntermediate", 115 | "leftMiddleDistal","leftRingProximal","leftRingIntermediate","leftRingDistal", 116 | "leftLittleProximal","leftLittleIntermediate","leftLittleDistal", 117 | "rightThumbProximal","rightThumbIntermediate","rightThumbDistal", 118 | "rightIndexProximal","rightIndexIntermediate","rightIndexDistal", 119 | "rightMiddleProximal","rightMiddleIntermediate","rightMiddleDistal", 120 | "rightRingProximal","rightRingIntermediate","rightRingDistal", 121 | "rightLittleProximal","rightLittleIntermediate","rightLittleDistal" 122 | ] 123 | 124 | if obj.type == "MESH": 125 | if len(obj.data.materials) == 0: 126 | messages.add("There is no material in mesh {}".format(obj.name)) 127 | for poly in obj.data.polygons: 128 | if poly.loop_total > 3:#polygons need all triangle 129 | messages.add("There are not Triangle faces in {}".format(obj.name)) 130 | #TODO modifier applyed, vertex weight Bone exist, vertex weight numbers. 131 | #endregion selected object seeking 132 | if armature_count == 0: 133 | messages.add("NO ARMATURE!") 134 | 135 | used_image = [] 136 | used_material_set = set() 137 | for mesh in [obj for obj in bpy.context.selected_objects if obj.type == "MESH"]: 138 | for mat in mesh.data.materials: 139 | used_material_set.add(mat) 140 | for mat in used_material_set: 141 | if mat.texture_slots is not None: 142 | used_image += [tex_slot.texture.image for tex_slot in mat.texture_slots if tex_slot is not None] 143 | #thumbnail 144 | try: 145 | used_image.append(bpy.data.images[armature["texture"]]) 146 | except: 147 | messages.add("thumbnail_image is missing. please load {}".format(armature["texture"])) 148 | for img in used_image: 149 | if img.is_dirty or img.filepath =="": 150 | messages.add("{} is not saved, please save.".format(img.name)) 151 | if img.file_format.lower() not in ["png","jpeg"]: 152 | messages.add("GLTF texture format is PNG AND JPEG only") 153 | 154 | #TODO textblock_validate 155 | 156 | for mes in messages: 157 | print(mes) 158 | print("validation finished") 159 | if len(messages) > 0 : 160 | VRM_VALIDATOR.draw_func_add() 161 | raise Exception 162 | return {"FINISHED"} 163 | 164 | #region 3Dview drawer 165 | draw_func = None 166 | counter = 0 167 | @staticmethod 168 | def draw_func_add(): 169 | if VRM_VALIDATOR.draw_func is not None: 170 | VRM_VALIDATOR.draw_func_remove() 171 | VRM_VALIDATOR.draw_func = bpy.types.SpaceView3D.draw_handler_add( 172 | VRM_VALIDATOR.texts_draw, 173 | (), 'WINDOW', 'POST_PIXEL') 174 | VRM_VALIDATOR.counter = 300 175 | 176 | @staticmethod 177 | def draw_func_remove(): 178 | if VRM_VALIDATOR.draw_func is not None: 179 | bpy.types.SpaceView3D.draw_handler_remove( 180 | VRM_VALIDATOR.draw_func, 'WINDOW') 181 | VRM_VALIDATOR.draw_func = None 182 | 183 | @staticmethod 184 | def texts_draw(): 185 | text_size = 20 186 | dpi = 72 187 | blf.size(0, text_size, dpi) 188 | for i,text in enumerate(list(VRM_VALIDATOR.messages_set)): 189 | blf.position(0, text_size, text_size*(i+1)+100, 0) 190 | blf.draw(0, text) 191 | blf.position(0,text_size,text_size*(2+len(VRM_VALIDATOR.messages_set))+100,0) 192 | blf.draw(0, "message delete count down...:{}".format(VRM_VALIDATOR.counter)) 193 | VRM_VALIDATOR.counter -= 1 194 | if VRM_VALIDATOR.counter <= 0: 195 | VRM_VALIDATOR.draw_func_remove() 196 | #endregion 3Dview drawer 197 | -------------------------------------------------------------------------------- /importer/vrm_load.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2018 iCyP 3 | Released under the MIT license 4 | https://opensource.org/licenses/mit-license.php 5 | 6 | """ 7 | 8 | # codig :utf-8 9 | #for python3.5 - for blender2.79 10 | from .binaly_loader import Binaly_Reader 11 | from ..gl_const import GL_CONSTANS as GLC 12 | from .. import V_Types as VRM_Types 13 | from . import vrm2pydata_factory 14 | import os,re,copy 15 | from math import sqrt,pow 16 | import json 17 | import numpy 18 | from collections import OrderedDict 19 | 20 | 21 | 22 | 23 | def parse_glb(data: bytes): 24 | reader = Binaly_Reader(data) 25 | magic = reader.read_str(4) 26 | if magic != 'glTF': 27 | raise Exception('magic not found: #{}'.format(magic)) 28 | 29 | version = reader.read_as_dataType(GLC.UNSIGNED_INT) 30 | if version != 2: 31 | raise Exception('version:#{} is not 2'.format(version)) 32 | 33 | size = reader.read_as_dataType(GLC.UNSIGNED_INT) 34 | size -= 12 35 | 36 | json_str = None 37 | body = None 38 | while size > 0: 39 | # print(size) 40 | 41 | if json_str is not None and body is not None: 42 | raise Exception('this vrm has chunks, this importer reads one chunk only.') 43 | 44 | chunk_size = reader.read_as_dataType(GLC.UNSIGNED_INT) 45 | size -= 4 46 | 47 | chunk_type = reader.read_str(4) 48 | size -= 4 49 | 50 | chunk_data = reader.read_binaly(chunk_size) 51 | size -= chunk_size 52 | 53 | if chunk_type == 'BIN\x00': 54 | body = chunk_data 55 | elif chunk_type == 'JSON': 56 | json_str = chunk_data.decode('utf-8')#blenderのpythonverが古く自前decode要す 57 | else: 58 | raise Exception('unknown chunk_type: {}'.format(chunk_type)) 59 | 60 | return json.loads(json_str,object_pairs_hook=OrderedDict), body 61 | 62 | #あくまでvrm(の特にバイナリ)をpythonデータ化するだけで、blender型に変形はここではしない 63 | def read_vrm(model_path): 64 | vrm_pydata = VRM_Types.VRM_pydata(filepath=model_path) 65 | #datachunkは普通一つしかない 66 | with open(model_path, 'rb') as f: 67 | vrm_pydata.json, body_binary = parse_glb(f.read()) 68 | 69 | #KHR_DRACO_MESH_COMPRESSION は対応してない場合落とさないといけないらしい。どのみち壊れたデータになるからね。 70 | if "extensionsRequired" in vrm_pydata.json: 71 | if "KHR_DRACO_MESH_COMPRESSION" in vrm_pydata.json["extensionsRequired"]: 72 | raise Exception("This VRM has DRACO COMPRESSION. This importer can't read this VRM. Draco圧縮されたVRMは未対応です") 73 | #改変不可ライセンスを撥ねる 74 | #CC_ND 75 | if "licenseName" in vrm_pydata.json["extensions"]["VRM"]["meta"]: 76 | if re.match("CC(.*)ND(.*)", vrm_pydata.json["extensions"]["VRM"]["meta"]["licenseName"]) is not None: 77 | raise Exception("This VRM is not allowed to Edit. CHECK ITS LICENSE 改変不可Licenseです。") 78 | #Vroidbhub licence 79 | if "otherPermissionUrl" in vrm_pydata.json["extensions"]["VRM"]["meta"]: 80 | from urllib.parse import parse_qsl,urlparse 81 | address = urlparse(vrm_pydata.json["extensions"]["VRM"]["meta"]["otherPermissionUrl"]).hostname 82 | if address is None: 83 | pass 84 | elif "vroid" in address : 85 | if dict(parse_qsl(vrm_pydata.json["extensions"]["VRM"]["meta"]["otherPermissionUrl"])).get("modification") == "disallow": 86 | raise Exception("This VRM is not allowed to Edit. CHECK ITS LICENSE 改変不可Licenseです。") 87 | #オリジナルライセンスに対する注意 88 | if vrm_pydata.json["extensions"]["VRM"]["meta"]["licenseName"] == "Other": 89 | print("Is this VRM allowed to Edit? CHECK IT LICENSE") 90 | 91 | texture_rip(vrm_pydata,body_binary) 92 | 93 | vrm_pydata.decoded_binary = decode_bin(vrm_pydata.json,body_binary) 94 | 95 | mesh_read(vrm_pydata) 96 | material_read(vrm_pydata) 97 | skin_read(vrm_pydata) 98 | node_read(vrm_pydata) 99 | 100 | 101 | return vrm_pydata 102 | 103 | def texture_rip(vrm_pydata,body_binary): 104 | bufferViews = vrm_pydata.json["bufferViews"] 105 | binary_reader = Binaly_Reader(body_binary) 106 | #ここ画像切り出し #blenderはバイト列から画像を読み込む術がないので、画像ファイルを書き出して、それを読み込むしかない。 107 | vrm_dir_path = os.path.dirname(os.path.abspath(vrm_pydata.filepath)) 108 | if not "images" in vrm_pydata.json: 109 | return 110 | for id,image_prop in enumerate(vrm_pydata.json["images"]): 111 | if "extra" in image_prop: 112 | image_name = image_prop["extra"]["name"] 113 | else : 114 | image_name = image_prop["name"] 115 | binary_reader.set_pos(bufferViews[image_prop["bufferView"]]["byteOffset"]) 116 | image_binary = binary_reader.read_binaly(bufferViews[image_prop["bufferView"]]["byteLength"]) 117 | image_type = image_prop["mimeType"].split("/")[-1] 118 | if image_name == "": 119 | image_name = "texture_" + str(id) 120 | print("no name image is named {}".format(image_name)) 121 | elif len(image_name) >=50: 122 | print("too long name image: {} is named {}".format(image_name,"tex_2longname_"+str(id))) 123 | image_name = "tex_2longname_"+str(id) 124 | 125 | def invalid_chars_remover(filename): 126 | unsafe_chars = { 127 | 0: '\x00', 1: '\x01', 2: '\x02', 3: '\x03', 4: '\x04', 5: '\x05', 6: '\x06', 7: '\x07', 8: '\x08', 9: '\t', 10: '\n',\ 128 | 11: '\x0b', 12: '\x0c', 13: '\r', 14: '\x0e', 15: '\x0f', 16: '\x10', 17: '\x11', 18: '\x12', 19: '\x13', 20: '\x14',\ 129 | 21: '\x15', 22: '\x16', 23: '\x17', 24: '\x18', 25: '\x19', 26: '\x1a', 27: '\x1b', 28: '\x1c', 29: '\x1d', 30: '\x1e',\ 130 | 31: '\x1f', 34: '"', 42: '*', 47: '/', 58: ':', 60: '<', 62: '>', 63: '?', 92: '\\', 124: '|' 131 | } #32:space #33:! 132 | remove_table = str.maketrans("","","".join([chr(charnum) for charnum in unsafe_chars.keys()])) 133 | safe_filename = filename.translate(remove_table) 134 | return safe_filename 135 | image_name = invalid_chars_remover(image_name) 136 | image_path = os.path.join(vrm_dir_path, image_name + "." + image_type) 137 | if not os.path.exists(image_path):#すでに同名の画像がある場合は基本上書きしない 138 | with open(image_path, "wb") as imageWriter: 139 | imageWriter.write(image_binary) 140 | elif image_name in [img.name for img in vrm_pydata.image_propaties]:#ただ、それがこのVRMを開いた時の名前の時はちょっと考えて書いてみる。 141 | written_flag = False 142 | for i in range(5): 143 | second_image_name = image_name+"_"+str(i) 144 | image_path = os.path.join(vrm_dir_path, second_image_name + "." + image_type) 145 | if not os.path.exists(image_path): 146 | with open(image_path, "wb") as imageWriter: 147 | imageWriter.write(image_binary) 148 | image_name = second_image_name 149 | written_flag = True 150 | break 151 | if not written_flag: 152 | print("Thare are same name images more than 5 in folder. Failed to write file: {}".format(image_name)) 153 | else: 154 | print(image_name + " Image is already exists. NOT OVER WRITTEN") 155 | image_propaty = VRM_Types.Image_props(image_name,image_path,image_type) 156 | vrm_pydata.image_propaties.append(image_propaty) 157 | 158 | # ”accessorの順に” データを読み込んでリストにしたものを返す 159 | def decode_bin(json_data,binary): 160 | br = Binaly_Reader(binary) 161 | #This list indexed by accesser index 162 | decoded_binary = [] 163 | bufferViews = json_data["bufferViews"] 164 | accessors = json_data["accessors"] 165 | type_num_dict = {"SCALAR":1,"VEC2":2,"VEC3":3,"VEC4":4,"MAT4":16} 166 | for accessor in accessors: 167 | type_num = type_num_dict[accessor["type"]] 168 | br.set_pos(bufferViews[accessor["bufferView"]]["byteOffset"]) 169 | data_list = [] 170 | for num in range(accessor["count"]): 171 | if type_num == 1: 172 | data = br.read_as_dataType(accessor["componentType"]) 173 | else: 174 | data = [] 175 | for l in range(type_num): 176 | data.append(br.read_as_dataType(accessor["componentType"])) 177 | data_list.append(data) 178 | decoded_binary.append(data_list) 179 | 180 | return decoded_binary 181 | 182 | def mesh_read(vrm_pydata): 183 | #メッシュをパースする 184 | for n,mesh in enumerate(vrm_pydata.json["meshes"]): 185 | for j,primitive in enumerate(mesh["primitives"]): 186 | vrm_mesh = VRM_Types.Mesh() 187 | vrm_mesh.object_id = n 188 | if j == 0:#mesh annotationとの兼ね合い 189 | vrm_mesh.name = mesh["name"] 190 | else : 191 | vrm_mesh.name = mesh["name"]+str(j) 192 | 193 | #region 頂点index 194 | if primitive["mode"] != GLC.TRIANGLES: 195 | #TODO その他メッシュタイプ対応 196 | raise Exception("unSupported polygon type(:{}) Exception".format(primitive["mode"])) 197 | vrm_mesh.face_indices = vrm_pydata.decoded_binary[primitive["indices"]] 198 | #3要素ずつに変換しておく(GCL.TRIANGLES前提なので) 199 | #ATTENTION これだけndarray 200 | vrm_mesh.face_indices = numpy.reshape(vrm_mesh.face_indices, (-1, 3)) 201 | #endregion 頂点index 202 | 203 | #ここから頂点属性 204 | vertex_attributes = primitive["attributes"] 205 | #頂点属性は実装によっては存在しない属性(例えばJOINTSやWEIGHTSがなかったりもする)もあるし、UVや頂点カラー0->Nで増やせる(スキニングは1要素(ボーン4本)限定 206 | for attr in vertex_attributes.keys(): 207 | vrm_mesh.__setattr__(attr,vrm_pydata.decoded_binary[vertex_attributes[attr]]) 208 | #region TEXCOORD_FIX [ 古いuniVRM誤り: uv.y = -uv.y ->修復 uv.y = 1 - ( -uv.y ) => uv.y=1+uv.y] 209 | legacy_uv_flag = False #f*** 210 | if vrm_pydata.json.get("aseets"): 211 | if vrm_pydata["assets"].get("generator"): 212 | if vrm_pydata["assets"]["generator"][0:7] == "UniGLTF": 213 | if float("".join(vrm_pydata["assets"]["generator"][-4:])) < 1.16: 214 | legacy_uv_flag = True 215 | uv_count = 0 216 | while True: 217 | texcoordName = "TEXCOORD_{}".format(uv_count) 218 | if hasattr(vrm_mesh, texcoordName): 219 | texcoord = getattr(vrm_mesh,texcoordName) 220 | for uv in texcoord: 221 | if legacy_uv_flag: 222 | uv[1] = 1 + uv[1] 223 | uv_count +=1 224 | else: 225 | break 226 | #blenderとは上下反対のuv,それはblenderに書き込むときに直す 227 | #endregion TEXCOORD_FIX 228 | 229 | #meshに当てられるマテリアルの場所を記録 230 | vrm_mesh.material_index = primitive["material"] 231 | 232 | #変換時のキャッシュ対応のためのデータ 233 | vrm_mesh.POSITION_accessor = primitive["attributes"]["POSITION"] 234 | 235 | #ここからモーフターゲット vrmのtargetは相対位置 normalは無視する 236 | if "targets" in primitive: 237 | morphTarget_point_list_and_accessor_index_dict = dict() 238 | for i,morphTarget in enumerate(primitive["targets"]): 239 | posArray = vrm_pydata.decoded_binary[morphTarget["POSITION"]] 240 | if "extra" in morphTarget:#for old AliciaSolid 241 | #accesserのindexを持つのは変換時のキャッシュ対応のため 242 | morphTarget_point_list_and_accessor_index_dict[primitive["targets"][i]["extra"]["name"]] = [posArray,primitive["targets"][i]["POSITION"]] 243 | else: 244 | #同上 245 | morphTarget_point_list_and_accessor_index_dict[primitive["extras"]["targetNames"][i]] = [posArray,primitive["targets"][i]["POSITION"]] 246 | vrm_mesh.__setattr__("morphTarget_point_list_and_accessor_index_dict",morphTarget_point_list_and_accessor_index_dict) 247 | 248 | vrm_pydata.meshes.append(vrm_mesh) 249 | 250 | 251 | #ここからマテリアル 252 | def material_read(vrm_pydata): 253 | VRM_EXTENSION_material_promaties = None 254 | textures = None 255 | try: 256 | VRM_EXTENSION_material_propaties = vrm_pydata.json["extensions"]["VRM"]["materialProperties"] 257 | except Exception as e: 258 | print(e) 259 | VRM_EXTENSION_material_propaties = [{"shader":"VRM_USE_GLTFSHADER"}]*len(vrm_pydata.json["materials"]) 260 | if "textures" in vrm_pydata.json: 261 | textures = vrm_pydata.json["textures"] 262 | for mat,ext_mat in zip(vrm_pydata.json["materials"],VRM_EXTENSION_material_propaties): 263 | vrm_pydata.materials.append(vrm2pydata_factory.material(mat,ext_mat,textures)) 264 | 265 | 266 | 267 | #skinをパース ->バイナリの中身はskining実装の横着用 268 | #skinのjointsの(nodesの)indexをvertsのjoints_0は指定してる 269 | #inverseBindMatrices: 単にスキニングするときの逆行列。読み込み不要なのでしない(自前計算もできる、めんどいけど) 270 | #ついでに[i][3]ではなく、[3][i]にマイナスx,y,zが入っている。 ここで詰まった。(出力時に) 271 | #joints:JOINTS_0の指定node番号のindex 272 | def skin_read(vrm_pydata): 273 | for skin in vrm_pydata.json["skins"]: 274 | vrm_pydata.skins_joints_list.append(skin["joints"]) 275 | if "skeleton" in skin.keys(): 276 | vrm_pydata.skins_root_node_list.append(skin["skeleton"]) 277 | 278 | 279 | #node(ボーン)をパースする->親からの相対位置で記録されている 280 | def node_read(vrm_pydata): 281 | for i,node in enumerate(vrm_pydata.json["nodes"]): 282 | vrm_pydata.nodes_dict[i] = vrm2pydata_factory.bone(node) 283 | #TODO こっからorigine_bone 284 | if "mesh" in node.keys(): 285 | vrm_pydata.origine_nodes_dict[i] = [vrm_pydata.nodes_dict[i],node["mesh"]] 286 | if "skin" in node.keys(): 287 | vrm_pydata.origine_nodes_dict[i].append(node["skin"]) 288 | else: 289 | print(node["name"] + "is not have skin") 290 | 291 | 292 | 293 | if "__main__" == __name__: 294 | model_path = "./AliciaSolid\\AliciaSolid.vrm" 295 | model_path = "./Vroid\\Vroid.vrm" 296 | read_vrm(model_path) 297 | -------------------------------------------------------------------------------- /misc/glb_factory.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2018 iCyP 3 | Released under the MIT license 4 | https://opensource.org/licenses/mit-license.php 5 | 6 | """ 7 | from .glb_bin_collector import Glb_bin_collection, Image_bin, Glb_bin 8 | from ..gl_const import GL_CONSTANS 9 | from .. import V_Types as VRM_types 10 | from collections import OrderedDict 11 | from math import pow 12 | import json 13 | import struct 14 | from sys import float_info 15 | import bpy,bmesh 16 | class Glb_obj(): 17 | def __init__(self,author): 18 | bpy.ops.vrm.model_validate() 19 | 20 | self.json_dic = OrderedDict() 21 | self.bin = b"" 22 | self.glb_bin_collector = Glb_bin_collection() 23 | self.armature = [obj for obj in bpy.context.selected_objects if obj.type == "ARMATURE"][0] 24 | self.result = None 25 | self.author = author 26 | 27 | def convert_bpy2glb(self): 28 | self.image_to_bin() 29 | self.armature_to_node_and_scenes_dic() #親のないboneは1つだけ as root_bone 30 | self.texture_to_dic() 31 | self.material_to_dic() 32 | self.mesh_to_bin_and_dic() 33 | self.json_dic["scene"] = 0 34 | self.glTF_meta_to_dic() 35 | self.vrm_meta_to_dic() #colliderとかmetaとか.... 36 | self.finalize() 37 | return self.result 38 | @staticmethod 39 | def axis_blender_to_glb(vec3): 40 | return [vec3[i]*t for i,t in zip([0,2,1],[-1,1,1])] 41 | 42 | @staticmethod 43 | def textblock2str(textblock): 44 | return "".join([line.body for line in textblock.lines]) 45 | 46 | def image_to_bin(self): 47 | #collect used image 48 | used_image = set() 49 | used_material_set = set() 50 | for mesh in [obj for obj in bpy.context.selected_objects if obj.type == "MESH"]: 51 | for mat in mesh.data.materials: 52 | used_material_set.add(mat) 53 | for mat in used_material_set: 54 | if mat.texture_slots is not None: 55 | used_image = used_image.union(set([tex_slot.texture.image for tex_slot in mat.texture_slots if tex_slot is not None])) 56 | #thumbnail 57 | used_image.add(bpy.data.images[self.armature["texture"]]) 58 | 59 | for image in used_image: 60 | if image.is_dirty: 61 | print("unsaved image name:{}, please save it".format(image.name)) 62 | raise Exception() 63 | if image.file_format.lower() not in ["png","jpeg"]: 64 | print("GLTF texture format is PNG AND JPEG only") 65 | raise Exception() 66 | with open(image.filepath_from_user(),"rb") as f: 67 | image_bin = f.read() 68 | name = image.name 69 | filetype = "image/"+image.file_format.lower() 70 | Image_bin(image_bin,name,filetype,self.glb_bin_collector) 71 | return 72 | 73 | def armature_to_node_and_scenes_dic(self): 74 | nodes = [] 75 | scene = [] 76 | skins = [] 77 | 78 | bone_id_dic = {b.name : bone_id for bone_id,b in enumerate(self.armature.data.bones)} 79 | def bone_to_node(b_bone): 80 | parent_head_local = b_bone.parent.head_local if b_bone.parent is not None else [0,0,0] 81 | node = OrderedDict({ 82 | "name":b_bone.name, 83 | "translation":self.axis_blender_to_glb([b_bone.head_local[i] - parent_head_local[i] for i in range(3)]), 84 | "rotation":[0,0,0,1], 85 | "scale":[1,1,1], 86 | "children":[bone_id_dic[ch.name] for ch in b_bone.children] 87 | }) 88 | if len(node["children"]) == 0: 89 | del node["children"] 90 | return node 91 | skin = {"joints":[]} 92 | for bone in self.armature.data.bones: 93 | if bone.parent is None: #root bone 94 | root_bone_id = bone_id_dic[bone.name] 95 | skin["joints"].append(root_bone_id) 96 | skin["skeleton"] = root_bone_id 97 | scene.append(root_bone_id) 98 | nodes.append(bone_to_node(bone)) 99 | bone_children = [b for b in bone.children] 100 | while bone_children : 101 | child = bone_children.pop() 102 | nodes.append(bone_to_node(child)) 103 | skin["joints"].append(bone_id_dic[child.name]) 104 | bone_children += [ch for ch in child.children] 105 | nodes = sorted(nodes,key=lambda node: bone_id_dic[node["name"]]) 106 | skins.append(skin) 107 | 108 | 109 | skin_invert_matrix_bin = b"" 110 | f_4x4_packer = struct.Struct("<16f").pack 111 | for node_id in skins[0]["joints"]: 112 | bone_name = nodes[node_id]["name"] 113 | bone_glb_world_pos = self.axis_blender_to_glb(self.armature.data.bones[bone_name].head_local) 114 | inv_matrix = [ 115 | 1,0,0,0, 116 | 0,1,0,0, 117 | 0,0,1,0, 118 | -bone_glb_world_pos[0],-bone_glb_world_pos[1],-bone_glb_world_pos[2],1 119 | ] 120 | skin_invert_matrix_bin += f_4x4_packer(*inv_matrix) 121 | 122 | IM_bin = Glb_bin(skin_invert_matrix_bin,"MAT4",GL_CONSTANS.FLOAT,len(skins[0]["joints"]),None,self.glb_bin_collector) 123 | skins[0]["inverseBindMatrices"] = IM_bin.accessor_id 124 | self.json_dic.update({"scenes":[{"nodes":scene}]}) 125 | self.json_dic.update({"nodes":nodes}) 126 | self.json_dic.update({"skins":skins}) 127 | return 128 | 129 | def texture_to_dic(self): 130 | self.json_dic["samplers"] = [{ 131 | "magFilter": GL_CONSTANS.LINEAR, #TODO: 決め打ちすんな? 132 | "minFilter": GL_CONSTANS.LINEAR, 133 | "wrapS": GL_CONSTANS.REPEAT, 134 | "wrapT": GL_CONSTANS.REPEAT 135 | }] 136 | textures = [] 137 | for id in range(len(self.glb_bin_collector.image_bins)): 138 | texture = { 139 | "sampler":0, 140 | "source": id 141 | } 142 | textures.append(texture) 143 | self.json_dic.update({"textures":textures}) 144 | return 145 | 146 | def material_to_dic(self): 147 | glb_material_list = [] 148 | VRM_material_props_list = [] 149 | 150 | image_id_dic = {image.name:image.image_id for image in self.glb_bin_collector.image_bins} 151 | used_material_set = set() 152 | for mesh in [obj for obj in bpy.context.selected_objects if obj.type == "MESH"]: 153 | for mat in mesh.data.materials: 154 | used_material_set.add(mat) 155 | 156 | for b_mat in used_material_set: 157 | #region pbr_mat 158 | mat_dic = {"name":b_mat.name} 159 | 160 | mat_dic["pbrMetallicRoughness"] = { 161 | #gammma correction 162 | "baseColorFactor":[*[pow(v,1/2.2) for v in b_mat.diffuse_color],1.0], 163 | "metallicFactor": 0, 164 | "roughnessFactor": 0.9 165 | } 166 | for slot_id,texslot in enumerate(b_mat.texture_slots): 167 | if texslot == None: 168 | continue 169 | if texslot.use_map_color_diffuse: 170 | if texslot.texture_coords == "UV": 171 | mat_dic["pbrMetallicRoughness"].update({"baseColorTexture": { 172 | "index": image_id_dic[texslot.texture.image.name], 173 | "texCoord": 0 #TODO: 174 | }}) 175 | 176 | 177 | if not b_mat.use_transparency: 178 | mat_dic["alphaMode"] = "OPAQUE" 179 | elif b_mat.transparency_method == "MASK": 180 | mat_dic["alphaMode"] = "MASK" 181 | mat_dic["alphaCutoff"] = 0.5 182 | else:# Z_TRANSPARENCY or RAYTRACE 183 | mat_dic["alphaMode"] = "BLEND" 184 | glb_material_list.append(mat_dic) 185 | #endregion pbr mat 186 | 187 | #region VRM_mat 188 | 189 | v_mat_dic = OrderedDict() 190 | v_mat_dic["name"] = b_mat.name 191 | v_mat_dic["shader"] = "VRM/MToon" 192 | v_mat_dic["keywordMap"] = keyword_map = {} 193 | v_mat_dic["tagMap"] = tag_map = {} 194 | #TODO: vector props 195 | 196 | def linear_to_sRGB_color(linear_color): 197 | sRGB_color = [0,0,0,1] 198 | for i,c in enumerate(linear_color): 199 | if c < 0.0031308: 200 | sRGB_color[i] = c*12.92 201 | else: 202 | sRGB_color[i] = 1.055 * pow(c, 1 / 2.4) - 0.055 203 | return sRGB_color 204 | 205 | def get_prop(material, prop_name, defo): 206 | return [*material[prop_name]] if prop_name in material.keys() else defo 207 | v_mat_dic["vectorProperties"] = vec_dic = OrderedDict() 208 | vec_dic["_Color"] = linear_to_sRGB_color(b_mat.diffuse_color) 209 | vec_dic["_ShadeColor"] = get_prop(b_mat, "_ShadeColor", [0.3, 0.3, 0.5, 1.0]) 210 | vec_dic["_EmissionColor"] = get_prop(b_mat, "_EmissionColor", [0.0, 0.0, 0.0, 1.0]) 211 | vec_dic["_OutlineColor"] = get_prop(b_mat, "_OutlineColor", [0.0, 0.0, 0.0, 1.0]) 212 | 213 | 214 | #TODO: float props 215 | v_mat_dic["floatProperties"] = float_dic = OrderedDict() 216 | for prop in b_mat.keys(): 217 | if prop in VRM_types.Material_MToon.float_props: 218 | if b_mat[prop] == None: 219 | continue 220 | float_dic[prop] = b_mat[prop] 221 | # _BlendMode : 0:Opacue 1:Cutout 2:Transparent 3:TransparentZwrite, 222 | # _Src,_Dst(ry): CONST 223 | # _ZWrite: 1: true 0:false 224 | def material_prop_setter(blend_mode, \ 225 | src_blend, dst_blend, \ 226 | z_write, alphatest, \ 227 | render_queue, render_type): 228 | float_dic["_BlendMode"] = blend_mode 229 | float_dic["_SrcBlend"] = src_blend 230 | float_dic["_DstBlend"] = dst_blend 231 | float_dic["_ZWrite"] = z_write 232 | keyword_map.update({"_ALPHATEST_ON": alphatest}) 233 | v_mat_dic["renderQueue"] = render_queue 234 | tag_map["RenderType"] = render_type 235 | 236 | if not b_mat.use_transparency: 237 | material_prop_setter(0,1,0,1,False,-1,"Opaque") 238 | elif b_mat.transparency_method == "MASK": 239 | material_prop_setter(1,1,0,1,True,2450,"TransparentCutout") 240 | else: #transparent and Z_TRANPARENCY or Raytrace 241 | material_prop_setter(2,5,10,0,False,3000,"Transparent") 242 | keyword_map.update({"_ALPHABLEND_ON": b_mat.use_transparency}) 243 | keyword_map.update({"_ALPHAPREMULTIPLY_ON":False}) 244 | 245 | float_dic["_CullMode"] = 0 #no cull 246 | float_dic["_OutlineCullMode"] = 1 #front face cull (for invert normal outline) 247 | float_dic["_DebugMode"] = 0 248 | keyword_map.update({"MTOON_DEBUG_NORMAL":False}) 249 | keyword_map.update({"MTOON_DEBUG_LITSHADERATE":False}) 250 | #region texture props 251 | def texuture_prop_add(dic,tex_attr,tex_slot_id)->dict(): 252 | try: 253 | tex_dic = {tex_attr:image_id_dic[b_mat.texture_slots[tex_slot_id].texture.image.name]} 254 | dic.update(tex_dic) 255 | except AttributeError: 256 | print("{} is nothing".format(tex_attr)) 257 | return 258 | v_mat_dic["textureProperties"] = tex_dic = OrderedDict() 259 | use_nomalmap = False 260 | for slot_id,texslot in enumerate(b_mat.texture_slots): 261 | if texslot == None: 262 | continue 263 | if texslot.use_map_color_diffuse: 264 | if texslot.texture_coords == "UV": 265 | texuture_prop_add(tex_dic, "_MainTex", slot_id) 266 | elif texslot.texture_coords == "NORMAL": 267 | texuture_prop_add(tex_dic,"_SphereAdd",slot_id) 268 | elif texslot.use_map_normal: 269 | texuture_prop_add(tex_dic,"_BumpMap",slot_id) 270 | use_nomalmap = True 271 | elif texslot.use_map_emit: 272 | texuture_prop_add(tex_dic, "_EmissionMap", slot_id) 273 | else: 274 | if "role" in texslot.texture.keys(): 275 | texuture_prop_add(tex_dic, texslot.texture["role"], slot_id) 276 | for tex_prop in tex_dic.keys(): 277 | if not tex_prop in vec_dic: 278 | vec_dic[tex_prop] = [0,0,1,1] 279 | 280 | keyword_map.update({"_NORMALMAP": use_nomalmap}) 281 | 282 | VRM_material_props_list.append(v_mat_dic) 283 | #endregion VRM_mat 284 | self.json_dic.update({"materials" : glb_material_list}) 285 | self.json_dic.update({"extensions":{"VRM":{"materialProperties":VRM_material_props_list}}}) 286 | return 287 | 288 | def mesh_to_bin_and_dic(self): 289 | self.json_dic["meshes"] = [] 290 | for id,mesh in enumerate([obj for obj in bpy.context.selected_objects if obj.type == "MESH"]): 291 | is_skin_mesh = True 292 | if len([m for m in mesh.modifiers if m.type == "ARMATURE"]) == 0: 293 | if mesh.parent is not None: 294 | if mesh.parent.type == "ARMATURE": 295 | if mesh.parent_bone != None: 296 | is_skin_mesh = False 297 | node_dic = OrderedDict({ 298 | "name":mesh.name, 299 | "translation":self.axis_blender_to_glb(mesh.location), #原点にいてほしいけどね, vectorのままだとjsonに出来ないからこうする 300 | "rotation":[0,0,0,1], #このへんは規約なので 301 | "scale":[1,1,1], #このへんは規約なので 302 | "mesh":id, 303 | }) 304 | if is_skin_mesh: 305 | node_dic["skin"] = 0 #TODO: 決め打ちってどうよ:一体のモデルなのだから2つもあっては困る(から決め打ち(やめろ(やだ)) 306 | self.json_dic["nodes"].append(node_dic) 307 | 308 | mesh_node_id = len(self.json_dic["nodes"])-1 309 | 310 | if is_skin_mesh: 311 | self.json_dic["scenes"][0]["nodes"].append(mesh_node_id) 312 | else: 313 | parent_node = [node for node in self.json_dic["nodes"] if node["name"] == mesh.parent_bone ][0] 314 | if "children" in parent_node.keys(): 315 | parent_node["children"].append(mesh_node_id) 316 | else: 317 | parent_node["children"] = [mesh_node_id] 318 | relate_pos = [mesh.location[i] - self.armature.data.bones[mesh.parent_bone].head_local[i] for i in range(3)] 319 | self.json_dic["nodes"][mesh_node_id]["translation"] = self.axis_blender_to_glb(relate_pos) 320 | 321 | #region hell 322 | bpy.ops.object.mode_set(mode='OBJECT') 323 | mesh.hide = False 324 | mesh.hide_select = False 325 | bpy.context.scene.objects.active = mesh 326 | bpy.ops.object.mode_set(mode='EDIT') 327 | bm = bmesh.from_edit_mesh(mesh.data) 328 | 329 | #region tempolary_used 330 | mat_id_dic = {mat["name"]:i for i,mat in enumerate(self.json_dic["materials"])} 331 | material_slot_dic = {i:mat.name for i,mat in enumerate(mesh.material_slots)} 332 | node_id_dic = {node["name"]:i for i,node in enumerate(self.json_dic["nodes"])} 333 | def joint_id_from_node_name_solver(node_name): 334 | try: 335 | node_id = node_id_dic[node_name] 336 | joint_id = self.json_dic["skins"][0]["joints"].index(node_id) 337 | except ValueError: 338 | joint_id = -1 #存在しないボーンを指してる場合は-1を返す 339 | print("{} bone may be not exist".format(node_name)) 340 | return joint_id 341 | v_group_name_dic = {i:vg.name for i,vg in enumerate(mesh.vertex_groups)} 342 | fmin,fmax = float_info.min,float_info.max 343 | unique_vertex_id = 0 344 | unique_vertex_id_dic = {} #loop verts id : base vertex id (uv違いを同じ頂点番号で管理されているので) 345 | unique_vertex_dic = {} # {(uv...,vertex_index):unique_vertex_id} (uvと頂点番号が同じ頂点は同じものとして省くようにする) 346 | uvlayers_dic = {i:uvlayer.name for i,uvlayer in enumerate(mesh.data.uv_layers)} 347 | def fetch_morph_vertex_normal_difference(): #TODO 実装 348 | morph_normal_diff_dic = {} 349 | vert_base_normal_dic = OrderedDict() 350 | for kb in mesh.data.shape_keys.key_blocks: 351 | vert_base_normal_dic.update( {kb.name:kb.normals_vertex_get()}) 352 | for k,v in vert_base_normal_dic.items(): 353 | if k == "Basis": 354 | continue 355 | values = [] 356 | for vert_morph_normal,vert_base_normal in zip(zip(*[iter(v)]*3),zip(*[iter(vert_base_normal_dic["Basis"])]*3)): 357 | values.append([vert_morph_normal[i]- vert_base_normal[i] for i in range(3)]) 358 | morph_normal_diff_dic.update({k:values}) 359 | return morph_normal_diff_dic 360 | #endregion tempolary_used 361 | primitive_index_bin_dic = OrderedDict({mat_id_dic[mat.name]:b"" for mat in mesh.material_slots}) 362 | primitive_index_vertex_count = OrderedDict({mat_id_dic[mat.name]:0 for mat in mesh.material_slots}) 363 | if mesh.data.shape_keys is None : 364 | shape_pos_bin_dic = {} 365 | shape_normal_bin_dic = {} 366 | shape_min_max_dic = {} 367 | morph_normal_diff_dic = {} 368 | else: 369 | shape_pos_bin_dic = OrderedDict({shape.name:b"" for shape in mesh.data.shape_keys.key_blocks[1:]})#0番目Basisは省く 370 | shape_normal_bin_dic = OrderedDict({shape.name:b"" for shape in mesh.data.shape_keys.key_blocks[1:]}) 371 | shape_min_max_dic = OrderedDict({shape.name:[[fmax,fmax,fmax],[fmin,fmin,fmin]] for shape in mesh.data.shape_keys.key_blocks[1:]}) 372 | morph_normal_diff_dic = fetch_morph_vertex_normal_difference() #{morphname:{vertexid:[diff_X,diff_y,diff_z]}} 373 | position_bin =b"" 374 | position_min_max = [[fmax,fmax,fmax],[fmin,fmin,fmin]] 375 | normal_bin = b"" 376 | joints_bin = b"" 377 | weights_bin = b"" 378 | texcord_bins = {id:b"" for id in uvlayers_dic.keys()} 379 | f_vec4_packer = struct.Struct(" minmax[1][i] else minmax[1][i] 389 | return 390 | for face in bm.faces: 391 | #このへん絶対超遅い 392 | for loop in face.loops: 393 | uv_list = [] 394 | for uvlayer_name in uvlayers_dic.values(): 395 | uv_layer = bm.loops.layers.uv[uvlayer_name] 396 | uv_list += [loop[uv_layer].uv[0],loop[uv_layer].uv[1]] 397 | cached_vert_id = unique_vertex_dic.get((*uv_list,loop.vert.index)) #keyがなければNoneを返す 398 | if cached_vert_id is not None: 399 | primitive_index_bin_dic[mat_id_dic[material_slot_dic[face.material_index]]] += I_scalar_packer(cached_vert_id) 400 | primitive_index_vertex_count[mat_id_dic[material_slot_dic[face.material_index]]] += 1 401 | continue 402 | else: 403 | unique_vertex_dic[(*uv_list,loop.vert.index)] = unique_vertex_id 404 | for id,uvlayer_name in uvlayers_dic.items(): 405 | uv_layer = bm.loops.layers.uv[uvlayer_name] 406 | uv = loop[uv_layer].uv 407 | texcord_bins[id] += f_pair_packer(uv[0],-uv[1]) #blenderとglbのuvは上下逆 408 | for shape_name in shape_pos_bin_dic.keys(): 409 | shape_layer = bm.verts.layers.shape[shape_name] 410 | morph_pos = self.axis_blender_to_glb( [loop.vert[shape_layer][i] - loop.vert.co[i] for i in range(3)]) 411 | shape_pos_bin_dic[shape_name] += f_vec3_packer(*morph_pos) 412 | shape_normal_bin_dic[shape_name] +=f_vec3_packer(*self.axis_blender_to_glb(morph_normal_diff_dic[shape_name][loop.vert.index])) 413 | min_max(shape_min_max_dic[shape_name],morph_pos) 414 | if is_skin_mesh: 415 | magic = 0 416 | joints = [magic,magic,magic,magic] 417 | weights = [0.0, 0.0, 0.0, 0.0] 418 | if len(mesh.data.vertices[loop.vert.index].groups) >= 5: 419 | print("vertex weights are less than 4 in {}".format(mesh.name)) 420 | raise Exception 421 | for v_group in mesh.data.vertices[loop.vert.index].groups: 422 | joint_id = joint_id_from_node_name_solver(v_group_name_dic[v_group.group]) 423 | if joint_id == -1:#存在しないボーンを指してる場合は-1を返されてるので、その場合は飛ばす 424 | continue 425 | weights.pop(3) 426 | weights.insert(0,v_group.weight) 427 | joints.pop(3) 428 | joints.insert(0,joint_id) 429 | nomalize_fact = sum(weights) 430 | try: 431 | weights = [weights[i]/nomalize_fact for i in range(4)] 432 | except ZeroDivisionError : 433 | print("vertex has no weight in {}".format(mesh.name)) 434 | raise ZeroDivisionError 435 | if sum(weights) < 1: 436 | weights[0] += 1 - sum(weights) 437 | joints_bin += H_vec4_packer(*joints) 438 | weights_bin += f_vec4_packer(*weights) 439 | 440 | vert_location = self.axis_blender_to_glb(loop.vert.co) 441 | position_bin += f_vec3_packer(*vert_location) 442 | min_max(position_min_max,vert_location) 443 | normal_bin += f_vec3_packer(*self.axis_blender_to_glb(loop.vert.normal)) 444 | unique_vertex_id_dic[unique_vertex_id]=loop.vert.index 445 | primitive_index_bin_dic[mat_id_dic[material_slot_dic[face.material_index]]] += I_scalar_packer(unique_vertex_id) 446 | primitive_index_vertex_count[mat_id_dic[material_slot_dic[face.material_index]]] += 1 447 | unique_vertex_id += 1 448 | 449 | #DONE :index position, uv, normal, position morph,JOINT WEIGHT 450 | #TODO: morph_normal, v_color...? 451 | primitive_glbs_dic = OrderedDict({ 452 | mat_id:Glb_bin(index_bin,"SCALAR",GL_CONSTANS.UNSIGNED_INT,primitive_index_vertex_count[mat_id],None,self.glb_bin_collector) 453 | for mat_id,index_bin in primitive_index_bin_dic.items() if index_bin !=b"" 454 | }) 455 | pos_glb = Glb_bin(position_bin,"VEC3",GL_CONSTANS.FLOAT,unique_vertex_id,position_min_max,self.glb_bin_collector) 456 | nor_glb = Glb_bin(normal_bin,"VEC3",GL_CONSTANS.FLOAT,unique_vertex_id,None,self.glb_bin_collector) 457 | uv_glbs = [ 458 | Glb_bin(texcood_bin,"VEC2",GL_CONSTANS.FLOAT,unique_vertex_id,None,self.glb_bin_collector) 459 | for texcood_bin in texcord_bins.values()] 460 | if is_skin_mesh: 461 | joints_glb = Glb_bin(joints_bin,"VEC4",GL_CONSTANS.UNSIGNED_SHORT,unique_vertex_id,None,self.glb_bin_collector) 462 | weights_glb = Glb_bin(weights_bin,"VEC4",GL_CONSTANS.FLOAT,unique_vertex_id,None,self.glb_bin_collector) 463 | if len(shape_pos_bin_dic.keys()) != 0: 464 | morph_pos_glbs = [Glb_bin(morph_pos_bin,"VEC3",GL_CONSTANS.FLOAT,unique_vertex_id,morph_minmax,self.glb_bin_collector) 465 | for morph_pos_bin,morph_minmax in zip(shape_pos_bin_dic.values(),shape_min_max_dic.values()) 466 | ] 467 | morph_normal_glbs = [Glb_bin(morph_normal_bin,"VEC3",GL_CONSTANS.FLOAT,unique_vertex_id,None,self.glb_bin_collector) 468 | for morph_normal_bin in shape_normal_bin_dic.values() 469 | ] 470 | primitive_list = [] 471 | for primitive_id,index_glb in primitive_glbs_dic.items(): 472 | primitive = OrderedDict({"mode":4}) 473 | primitive["material"] = primitive_id 474 | primitive["indices"] = index_glb.accessor_id 475 | primitive["attributes"] = { 476 | "POSITION":pos_glb.accessor_id, 477 | "NORMAL":nor_glb.accessor_id, 478 | } 479 | if is_skin_mesh: 480 | primitive["attributes"].update({ 481 | "JOINTS_0":joints_glb.accessor_id, 482 | "WEIGHTS_0":weights_glb.accessor_id 483 | }) 484 | primitive["attributes"].update({"TEXCOORD_{}".format(i):uv_glb.accessor_id for i,uv_glb in enumerate(uv_glbs)}) 485 | if len(shape_pos_bin_dic.keys()) != 0: 486 | primitive["targets"]=[{"POSITION":morph_pos_glb.accessor_id,"NORMAL":morph_normal_glb.accessor_id} for morph_pos_glb,morph_normal_glb in zip(morph_pos_glbs,morph_normal_glbs)] 487 | primitive["extras"] = {"targetNames":[shape_name for shape_name in shape_pos_bin_dic.keys()]} 488 | primitive_list.append(primitive) 489 | self.json_dic["meshes"].append(OrderedDict({"name":mesh.name,"primitives":primitive_list})) 490 | #endregion hell 491 | bpy.ops.object.mode_set(mode='OBJECT') 492 | 493 | return 494 | 495 | 496 | def glTF_meta_to_dic(self): 497 | glTF_meta_dic = { 498 | "extensionsUsed":["VRM"], 499 | "asset":{ 500 | "generator":self.author+"_blender_vrm_exporter_experimental_0.0", 501 | "version":"2.0" 502 | } 503 | } 504 | 505 | self.json_dic.update(glTF_meta_dic) 506 | return 507 | 508 | def vrm_meta_to_dic(self): 509 | #materialProperties は material_to_dic()で処理する 510 | #region vrm_extension 511 | vrm_extension_dic = OrderedDict() 512 | vrm_extension_dic["exporterVersion"] = self.author+"_blender_vrm_exporter_experimental_0.0" 513 | #region meta 514 | vrm_extension_dic["meta"] = vrm_meta_dic = {} 515 | vrm_metas = [ 516 | "version", 517 | "author", 518 | "contactInformation", 519 | "reference", 520 | "title", 521 | "allowedUserName", 522 | "violentUssageName", 523 | "sexualUssageName", 524 | "commercialUssageName", 525 | "otherPermissionUrl", 526 | "licenseName", 527 | "otherLicenseUrl" 528 | ] 529 | for key in vrm_metas: 530 | vrm_meta_dic[key] = self.armature[key] if key in self.armature.keys() else "" 531 | if "texture" in self.armature.keys(): 532 | thumbnail_index_list =[i for i,img in enumerate(self.glb_bin_collector.image_bins) if img.name == self.armature["texture"]] 533 | if len(thumbnail_index_list) > 0 : 534 | vrm_meta_dic["texture"] = thumbnail_index_list[0] 535 | #endregion meta 536 | #region humanoid 537 | vrm_extension_dic["humanoid"] = vrm_humanoid_dic = {"humanBones":[]} 538 | node_name_id_dic = {node["name"]:i for i, node in enumerate(self.json_dic["nodes"])} 539 | for bone in self.armature.data.bones: 540 | if "humanBone" in bone.keys(): 541 | vrm_humanoid_dic["humanBones"].append({ 542 | "bone": bone["humanBone"], 543 | "node":node_name_id_dic[bone.name], 544 | "useDefaultValues": True 545 | }) 546 | vrm_humanoid_dic.update(json.loads(self.textblock2str(bpy.data.texts[self.armature["humanoid_params"]]))) 547 | #endregion humanoid 548 | #region firstPerson 549 | vrm_extension_dic["firstPerson"] = vrm_FP_dic = {} 550 | vrm_FP_dic.update(json.loads(self.textblock2str(bpy.data.texts[self.armature["firstPerson_params"]]))) 551 | if vrm_FP_dic["firstPersonBone"] != -1: 552 | vrm_FP_dic["firstPersonBone"] = node_name_id_dic[vrm_FP_dic["firstPersonBone"]] 553 | if "meshAnnotations" in vrm_FP_dic.keys(): 554 | for meshAnnotation in vrm_FP_dic["meshAnnotations"]: 555 | meshAnnotation["mesh"] = [i for i,mesh in enumerate(self.json_dic["meshes"]) if mesh["name"]==meshAnnotation["mesh"]][0] 556 | 557 | #endregion firstPerson 558 | #region blendShapeMaster 559 | vrm_extension_dic["blendShapeMaster"] = vrm_BSM_dic = {} 560 | BSM_list = json.loads(self.textblock2str(bpy.data.texts[self.armature["blendshape_group"]])) 561 | #meshを名前からid 562 | #weightを0-1から0-100に 563 | #shape_indexを名前からindexに 564 | def clamp(min,val,max): 565 | if max >= val: 566 | if val >= min:return val 567 | else: 568 | print("blendshapeGroup weight is between 0 - 1, value is {}".format(val)) 569 | return min 570 | else: 571 | print("blendshapeGroup weight is between 0 - 1, value is {}".format(val)) 572 | return max 573 | for bsm in BSM_list: 574 | for bind in bsm["binds"]: 575 | bind["mesh"] = [i for i,mesh in enumerate(self.json_dic["meshes"]) if mesh["name"]==bind["mesh"]][0] 576 | bind["index"] = self.json_dic["meshes"][bind["mesh"]]["primitives"][0]["extras"]["targetNames"].index(bind["index"]) 577 | bind["weight"] = clamp(0, bind["weight"]*100, 100) 578 | vrm_BSM_dic["blendShapeGroups"] = BSM_list 579 | #endregion blendShapeMaster 580 | 581 | #region secondaryAnimation 582 | vrm_extension_dic["secondaryAnimation"] = {"boneGroups":[],"colliderGroups":[]} 583 | 584 | #region colliderGroups 585 | #armatureの子emptyを変換する 586 | collider_group_list = [] 587 | empty_dic = {node_name_id_dic[ch.parent_bone]:[] for ch in self.armature.children if ch.type == "EMPTY"} 588 | for childEmpty in [ch for ch in self.armature.children if ch.type == "EMPTY"]: 589 | empty_dic[node_name_id_dic[childEmpty.parent_bone]].append(childEmpty) 590 | for node_id,empty_objs in empty_dic.items(): 591 | collider_group = {"node":node_id,"colliders":[]} 592 | colliders = collider_group["colliders"] 593 | for empty in empty_objs: 594 | collider = {"radius":empty.empty_draw_size} 595 | empty_offset_pos = [empty.matrix_world.to_translation()[i] \ 596 | - self.armature.location[i] \ 597 | - self.armature.data.bones[empty.parent_bone].head_local[i] \ 598 | for i in range(3)] 599 | collider["offset"] = OrderedDict({axis: o_s for axis, o_s in zip(("x", "y", "z"), self.axis_blender_to_glb(empty_offset_pos))}) 600 | collider["offset"]["z"] = collider["offset"]["z"]*-1 #TODO: たぶんuniVRMのシリアライズがコライダーだけunity系になってる 601 | colliders.append(collider) 602 | collider_group_list.append(collider_group) 603 | 604 | vrm_extension_dic["secondaryAnimation"]["colliderGroups"] = collider_group_list 605 | #endrigon colliderGroups 606 | 607 | #region boneGroup 608 | #ボーン名からnode_idに 609 | #collider_groupも名前からcolliderGroupのindexに直す 610 | collider_node_id_list = [c_g["node"] for c_g in collider_group_list] 611 | BG_list = json.loads(self.textblock2str(bpy.data.texts[self.armature["spring_bone"]])) 612 | for bone_group in BG_list: 613 | bone_group["bones"] = [node_name_id_dic[name] for name in bone_group["bones"] ] 614 | bone_group["colliderGroups"] = [collider_node_id_list.index(node_name_id_dic[name]) for name in bone_group["colliderGroups"] ] 615 | vrm_extension_dic["secondaryAnimation"]["boneGroups"]= BG_list 616 | #endregion boneGroup 617 | #endregion secondaryAnimation 618 | self.json_dic["extensions"]["VRM"].update(vrm_extension_dic) 619 | #endregion vrm_extension 620 | 621 | #region secondary 622 | self.json_dic["nodes"].append({ 623 | "name":"secondary", 624 | "translation":[0.0,0.0,0.0], 625 | "rotation":[0.0,0.0,0.0,1.0], 626 | "scale":[1.0,1.0,1.0] 627 | }) 628 | self.json_dic["scenes"][0]["nodes"].append(len(self.json_dic["nodes"])-1) 629 | return 630 | 631 | 632 | def finalize(self): 633 | bin_json, self.bin = self.glb_bin_collector.pack_all() 634 | self.json_dic.update(bin_json) 635 | magic = b'glTF' + struct.pack('>objの順でやらないと不具合 549 | bpy.ops.object.select_all(action="DESELECT") 550 | bpy.context.scene.objects.active = self.armature 551 | self.armature.select = True 552 | self.armature.rotation_mode = "XYZ" 553 | self.armature.rotation_euler[0] = numpy.deg2rad(90) 554 | self.armature.rotation_euler[2] = numpy.deg2rad(-180) 555 | self.armature.location = [self.armature.location[axis]*inv for axis,inv in zip([0,2,1],[-1,1,1])] 556 | bpy.ops.object.transform_apply(location = True,rotation=True) 557 | bpy.ops.object.mode_set(mode='OBJECT') 558 | bpy.ops.object.select_all(action="DESELECT") 559 | for obj in self.mesh_joined_objects: 560 | bpy.context.scene.objects.active = obj 561 | obj.select = True 562 | if obj.parent_type == 'BONE':#ボーンにくっ付いて動くのは無視:なんか吹っ飛ぶ髪の毛がいる? 563 | bpy.ops.object.transform_apply(rotation=True) 564 | print("bone parent object {}".format(obj.name)) 565 | obj.select = False 566 | continue 567 | obj.rotation_mode = "XYZ" 568 | obj.rotation_euler[0] = numpy.deg2rad(90) 569 | obj.rotation_euler[2] = numpy.deg2rad(-180) 570 | obj.location = [obj.location[axis]*inv for axis,inv in zip([0,2,1],[-1,1,1])] 571 | bpy.ops.object.transform_apply(location= True,rotation=True) 572 | obj.select = False 573 | return 574 | 575 | def put_spring_bone_info(self): 576 | 577 | if not "secondaryAnimation" in self.vrm_pydata.json["extensions"]["VRM"]: 578 | print("no secondary animation object") 579 | return 580 | secondaryAnimation_json = self.vrm_pydata.json["extensions"]["VRM"]["secondaryAnimation"] 581 | spring_rootbone_groups_json = secondaryAnimation_json["boneGroups"] 582 | collider_groups_json = secondaryAnimation_json["colliderGroups"] 583 | nodes_json = self.vrm_pydata.json["nodes"] 584 | for bone_group in spring_rootbone_groups_json: 585 | for bone_id in bone_group["bones"]: 586 | bone = self.armature.data.bones[nodes_json[bone_id]["name"]] 587 | for key, val in bone_group.items(): 588 | if key == "bones": 589 | continue 590 | bone[key] = val 591 | 592 | for collider_group in collider_groups_json: 593 | collider_base_node = nodes_json[collider_group["node"]] 594 | node_name = collider_base_node["name"] 595 | for i, collider in enumerate(collider_group["colliders"]): 596 | collider_name = "{}_collider_{}".format(node_name,i) 597 | obj = bpy.data.objects.new(name = collider_name,object_data = None) 598 | obj.parent = self.armature 599 | obj.parent_type = "BONE" 600 | obj.parent_bone = node_name 601 | offset = [collider["offset"]["x"],collider["offset"]["y"],collider["offset"]["z"]] #values直接はindexアクセス出来ないのでしゃあなし 602 | offset = [offset[axis]*inv for axis,inv in zip([0,2,1],[-1,-1,1])] #TODO: Y軸反転はuniVRMのシリアライズに合わせてる 603 | 604 | obj.matrix_world = self.armature.matrix_world * Matrix.Translation(offset) * self.armature.data.bones[node_name].matrix_local 605 | obj.empty_draw_size = collider["radius"] 606 | obj.empty_draw_type = "SPHERE" 607 | bpy.context.scene.objects.link(obj) 608 | 609 | return --------------------------------------------------------------------------------