diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d737f96 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ + +.vscode/.ropeproject/objectdb +.vscode/.ropeproject/config.py +.vscode/tasks.json +*.pyc diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f0f13b1 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 iCyP + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..8e0b386 --- /dev/null +++ b/README.md @@ -0,0 +1,34 @@ +# MIT license  +# Draco圧縮 非 対 応 (Draco complessed data is unsupported) +# Blender2.80向けはこっち (for Blender2.8 is below) +https://github.com/iCyP/VRM_IMPORTER_for_Blender2_8 +# 注意 +## インポートボタン押したら固まったんだけど??? + - インポートには数十秒程度かかります。しばらく待ってみてください。 +## 雰囲気でインポートしたので、間違いがあります。 +## 変更禁止(CC_ND)VRMは弾く" 仕様 "です悪しからず。 +## attention! for comers from other addon's notice. + - This addon is independent project from other addons. +## インストール方法 + - そこの右上の緑のボタンを押してZIP Download + ー blender->User preference->addon -> addon installで先に落としたZipを選ぶ + - おわり +## つかいかた + - menubar->File->import->VRM + - select relational object(armature,meshes) menubar->File->export->VRM + - export require setting text file in blender text editor, metas in custum propaty in armature object and humanbone custom propaties and, so on. +## 既知の不具合  + - マテリアルがおかしい->blender2.8が出たら本気だす(直すとは言ってない) + + +## 機能説明 +### 出来ること + - VRMをそれっぽくBlenderにimportできる + - VRM内すべてのテクスチャを(インポートするうえで必要に迫られて)VRMと同じフォルダに書き出す。 + - 同じフォルダ以外も要望があれば、個人的開発ポリシーに反しない範囲で実装可能 + - ※すでに*同名のテクスチャがある場合、上書きしない*ようになっているので、注意してください。(不慮の上書き防止の仕様です) +### 出来ないこと + - VRMのマテリアルをカンペキにBlenderにimportする + - その他私の想定外なモデルのimport + +- memo:実はExport、出来るとか出来ないとか、でもフルスクラッチからはかーーなり難しいんじゃないかな。 diff --git a/V_Types.py b/V_Types.py new file mode 100644 index 0000000..c236591 --- /dev/null +++ b/V_Types.py @@ -0,0 +1,184 @@ +""" +Copyright (c) 2018 iCyP +Released under the MIT license +https://opensource.org/licenses/mit-license.php + +""" +class VRM_pydata(object): + def __init__( + self, + filepath = None,json = None,decoded_binary = None, + image_propaties = None,meshes =None,materials = None, + nodes_dict = None,origine_nodes_dict = None, + skins_joints_list = None , skins_root_node_list = None + ): + self.filepath = filepath + self.json = json + self.decoded_binary = decoded_binary + + self.image_propaties = image_propaties if image_propaties is not None else [] + self.meshes = meshes if meshes is not None else [] + self.materials = materials if materials is not None else [] + self.nodes_dict = nodes_dict if nodes_dict is not None else {} + self.origine_nodes_dict = origine_nodes_dict if origine_nodes_dict is not None else {} + self.skins_joints_list = skins_joints_list if skins_joints_list is not None else [] + self.skins_root_node_list = skins_root_node_list if skins_root_node_list is not None else [] + + +class Mesh(object): + def __init__(self): + self.name = "" + self.face_indices = [] + self.skin_id = None + self.object_id = None + + + +class Node(object): + def __init__(self): + self.name = "" + self.position = None + self.rotation = None + self.scale = None + self.children = None + self.blend_bone = None + self.mesh_id = None + self.skin_id = None + + + +class Image_props(object): + def __init__(self,name,filepath,fileType): + self.name = name + self.filePath = filepath + self.fileType = fileType + + + + +class Material(object): + def __init__(self): + self.name = "" + self.shader_name = "" + + + +class Material_GLTF(Material): + def __init__(self): + super().__init__() + self.color_texture_index = None + self.color_texcood_index = None + self.base_color = None + self.metallic_factor = None + self.roughnessFactor = None + self.emissiveFactor = None + self.metallic_roughness_texture_index = None + self.metallic_roughness_texture_texcood = None + self.normal_texture_index = None + self.normal_texture_texcoord_index = None + self.emissive_texture_index = None + self.emissive_texture_texcoord_index = None + self.occlusion_texture_index = None + self.occlusion_texture_texcood_index = None + self.double_sided = None + self.alphaMode = "OPAQUE" + self.shadeless = False + + +class Material_Transparent_Z_write(Material): + float_props = [ + "_MainTex", + "_Cutoff", + "_BlendMode", + "_CullMode", + "_VColBlendMode", + "_SrcBlend", + "_DstBlend", + "_ZWrite", + ] + texture_index_list = [ + "_MainTex" + ] + vector_props = [ + "_Color" + ] + + def __init__(self): + super().__init__() + self.float_props_dic = {prop: None for prop in self.float_props} + self.vector_props_dic = {prop: None for prop in self.vector_props} + self.texture_index_dic = {tex:None for tex in self.texture_index_list} + + + +class Material_MToon(Material): + float_props = [ + "_Cutoff", + "_BumpScale", + "_ReceiveShadowRate", + "_ShadeShift", + "_ShadeToony", + "_ShadingGradeRate", + "_LightColorAttenuation", + "_IndirectLightIntensity", + "_OutlineWidth", + "_OutlineScaledMaxDistance", + "_OutlineLightingMix", + "_DebugMode", + "_BlendMode", + "_OutlineWidthMode", + "_OutlineColorMode", + "_CullMode", + "_OutlineCullMode", + "_SrcBlend", + "_DstBlend", + "_ZWrite", + "_IsFirstSetup" + ] + + texture_index_list = [ + "_MainTex",#use in BI + "_ShadeTexture",#ignore in BI + "_BumpMap",#use in BI + "_ReceiveShadowTexture",#ignore in BI + "_ShadingGradeTexture",#ignore in BI + "_EmissionMap",#ignore in BI + "_SphereAdd",#use in BI + "_OutlineWidthTexture"#ignore in BI + ] + vector_props = [ + "_Color", + "_EmissionColor", + "_OutlineColor", + "_ShadeColor" + ] + #texture offset and scaling props by texture + vector_props.extend(texture_index_list) + + keyword_list = [ + "_NORMALMAP", + "_ALPHATEST_ON", + "_ALPHABLEND_ON", + "_ALPHAPREMULTIPLY_ON", + "MTOON_OUTLINE_WIDTH_WORLD", + "MTOON_OUTLINE_WIDTH_SCREEN", + "MTOON_OUTLINE_COLOR_FIXED", + "MTOON_OUTLINE_COLOR_MIXED", + "MTOON_DEBUG_NORMAL", + "MTOON_DEBUG_LITSHADERATE" + ] + tagmap_list = [ + "RenderType" + ] + + def __init__(self): + super().__init__() + self.float_props_dic = {prop:None for prop in self.float_props} + self.vector_props_dic = {prop:None for prop in self.vector_props} + self.texture_index_dic = {prop: None for prop in self.texture_index_list} + self.keyword_dic = {kw:False for kw in self.keyword_list} + self.tag_dic = {tag:None for tag in self.tagmap_list} + + +if "__main__" == __name__: + pass diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..06f7f88 --- /dev/null +++ b/__init__.py @@ -0,0 +1,141 @@ +""" +Copyright (c) 2018 iCyP +Released under the MIT license +https://opensource.org/licenses/mit-license.php + +""" + +import bpy +from bpy_extras.io_utils import ImportHelper,ExportHelper +from .importer import vrm_load,model_build +from .misc import VRM_HELPER +from .misc import glb_factory +import os + + +bl_info = { + "name":"VRM_IMPORTER", + "author": "iCyP", + "version": (0, 5), + "blender": (2, 79, 0), + "location": "File->Import", + "description": "VRM Importer", + "warning": "", + "support": "TESTING", + "wiki_url": "", + "tracker_url": "", + "category": "Import-Export" +} + + +class ImportVRM(bpy.types.Operator,ImportHelper): + bl_idname = "import_scene.vrm" + bl_label = "import VRM" + bl_description = "import VRM" + bl_options = {'REGISTER', 'UNDO'} + + filename_ext = '.vrm' + filter_glob = bpy.props.StringProperty( + default='*.vrm', + options={'HIDDEN'} + ) + + is_put_spring_bone_info = bpy.props.BoolProperty(name = "Put Collider Empty") + + + def execute(self,context): + fdir = self.filepath + model_build.Blend_model(vrm_load.read_vrm(fdir),self.is_put_spring_bone_info) + return {'FINISHED'} + + +def menu_import(self, context): + op = self.layout.operator(ImportVRM.bl_idname, text="VRM (.vrm)") + op.is_put_spring_bone_info = True + +class ExportVRM(bpy.types.Operator,ExportHelper): + bl_idname = "export_scene.vrm" + bl_label = "export VRM" + bl_description = "export VRM" + bl_options = {'REGISTER', 'UNDO'} + + filename_ext = '.vrm' + filter_glob = bpy.props.StringProperty( + default='*.vrm', + options={'HIDDEN'} + ) + + def execute(self,context): + fdir = self.filepath + bin = glb_factory.Glb_obj().convert_bpy2glb() + with open(fdir,"wb") as f: + f.write(bin) + return {'FINISHED'} + + +def menu_export(self, context): + op = self.layout.operator(ExportVRM.bl_idname, text="VRM (.vrm)") + + +class VRM_IMPORTER_UI_controller(bpy.types.Panel): + bl_idname = "icyp_ui_controller" + bl_label = "vrm import helper" + #どこに置くかの定義 + bl_space_type = "VIEW_3D" + bl_region_type = "TOOLS" + bl_category = "VRM HELPER" + + @classmethod + def poll(self, context): + return True + + def draw(self, context): + self.layout.label(text="if you select armature in object mode") + self.layout.label(text="armature renamer is shown") + self.layout.label(text="if you in MESH EDIT") + self.layout.label(text="symmetry button is shown") + self.layout.label(text="*symmetry is in default blender") + if context.mode == "OBJECT": + if context.active_object is not None: + self.layout.operator(VRM_HELPER.VRM_VALIDATOR.bl_idname) + if context.active_object.type == 'ARMATURE': + self.layout.label(icon ="ERROR" ,text="EXPERIMENTAL!!!") + self.layout.operator(VRM_HELPER.Bones_rename.bl_idname) + if context.active_object.type =="MESH": + self.layout.label(icon="ERROR",text="EXPERIMENTAL!お試し版。あてにしない") + self.layout.operator(VRM_HELPER.Vroid2VRC_ripsync_from_json_recipe.bl_idname) + if context.mode == "EDIT_MESH": + self.layout.operator(bpy.ops.mesh.symmetry_snap.idname_py()) + + + + +classes = ( + ImportVRM, + ExportVRM, + VRM_HELPER.Bones_rename, + VRM_HELPER.Vroid2VRC_ripsync_from_json_recipe, + VRM_HELPER.VRM_VALIDATOR, + VRM_IMPORTER_UI_controller +) + + +# アドオン有効化時の処理 +def register(): + for cls in classes: + bpy.utils.register_class(cls) + bpy.types.INFO_MT_file_import.append(menu_import) + bpy.types.INFO_MT_file_export.append(menu_export) + + + + +# アドオン無効化時の処理 +def unregister(): + bpy.types.INFO_MT_file_export.remove(menu_export) + bpy.types.INFO_MT_file_import.remove(menu_import) + for cls in classes: + bpy.utils.unregister_class(cls) + +if "__main__" == __name__: + register() diff --git a/gl_const.py b/gl_const.py new file mode 100644 index 0000000..be99f71 --- /dev/null +++ b/gl_const.py @@ -0,0 +1,309 @@ +""" +Copyright (c) 2018 iCyP +Released under the MIT license +https://opensource.org/licenses/mit-license.php + +""" + +class GL_CONSTANS: + NONE = 0 + NO_ERROR = 0 + POINTS = 0 + ZERO = 0 + LINES = 1 + ONE = 1 + LINE_LOOP = 2 + LINE_STRIP = 3 + TRIANGLES = 4 + TRIANGLE_STRIP = 5 + TRIANGLE_FAN = 6 + DEPTH_BUFFER_BIT = 256 + NEVER = 512 + LESS = 513 + EQUAL = 514 + LEQUAL = 515 + GREATER = 516 + NOTEQUAL = 517 + GEQUAL = 518 + ALWAYS = 519 + SRC_COLOR = 768 + ONE_MINUS_SRC_COLOR = 769 + SRC_ALPHA = 770 + ONE_MINUS_SRC_ALPHA = 771 + DST_ALPHA = 772 + ONE_MINUS_DST_ALPHA = 773 + DST_COLOR = 774 + ONE_MINUS_DST_COLOR = 775 + SRC_ALPHA_SATURATE = 776 + STENCIL_BUFFER_BIT = 1024 + FRONT = 1028 + BACK = 1029 + FRONT_AND_BACK = 1032 + INVALID_ENUM = 1280 + INVALID_VALUE = 1281 + INVALID_OPERATION = 1282 + OUT_OF_MEMORY = 1285 + INVALID_FRAMEBUFFER_OPERATION = 1286 + CW = 2304 + CCW = 2305 + LINE_WIDTH = 2849 + CULL_FACE = 2884 + CULL_FACE_MODE = 2885 + FRONT_FACE = 2886 + DEPTH_RANGE = 2928 + DEPTH_TEST = 2929 + DEPTH_WRITEMASK = 2930 + DEPTH_CLEAR_VALUE = 2931 + DEPTH_FUNC = 2932 + STENCIL_TEST = 2960 + STENCIL_CLEAR_VALUE = 2961 + STENCIL_FUNC = 2962 + STENCIL_VALUE_MASK = 2963 + STENCIL_FAIL = 2964 + STENCIL_PASS_DEPTH_FAIL = 2965 + STENCIL_PASS_DEPTH_PASS = 2966 + STENCIL_REF = 2967 + STENCIL_WRITEMASK = 2968 + VIEWPORT = 2978 + DITHER = 3024 + BLEND = 3042 + SCISSOR_BOX = 3088 + SCISSOR_TEST = 3089 + COLOR_CLEAR_VALUE = 3106 + COLOR_WRITEMASK = 3107 + UNPACK_ALIGNMENT = 3317 + PACK_ALIGNMENT = 3333 + MAX_TEXTURE_SIZE = 3379 + MAX_VIEWPORT_DIMS = 3386 + SUBPIXEL_BITS = 3408 + RED_BITS = 3410 + GREEN_BITS = 3411 + BLUE_BITS = 3412 + ALPHA_BITS = 3413 + DEPTH_BITS = 3414 + STENCIL_BITS = 3415 + TEXTURE_2D = 3553 + DONT_CARE = 4352 + FASTEST = 4353 + NICEST = 4354 + BYTE = 5120 + UNSIGNED_BYTE = 5121 + SHORT = 5122 + UNSIGNED_SHORT = 5123 + INT = 5124 + UNSIGNED_INT = 5125 + FLOAT = 5126 + INVERT = 5386 + TEXTURE = 5890 + STENCIL_INDEX = 6401 + DEPTH_COMPONENT = 6402 + ALPHA = 6406 + RGB = 6407 + RGBA = 6408 + LUMINANCE = 6409 + LUMINANCE_ALPHA = 6410 + KEEP = 7680 + REPLACE = 7681 + INCR = 7682 + DECR = 7683 + VENDOR = 7936 + RENDERER = 7937 + VERSION = 7938 + NEAREST = 9728 + LINEAR = 9729 + NEAREST_MIPMAP_NEAREST = 9984 + LINEAR_MIPMAP_NEAREST = 9985 + NEAREST_MIPMAP_LINEAR = 9986 + LINEAR_MIPMAP_LINEAR = 9987 + TEXTURE_MAG_FILTER = 10240 + TEXTURE_MIN_FILTER = 10241 + TEXTURE_WRAP_S = 10242 + TEXTURE_WRAP_T = 10243 + REPEAT = 10497 + POLYGON_OFFSET_UNITS = 10752 + COLOR_BUFFER_BIT = 16384 + CONSTANT_COLOR = 32769 + ONE_MINUS_CONSTANT_COLOR = 32770 + CONSTANT_ALPHA = 32771 + ONE_MINUS_CONSTANT_ALPHA = 32772 + BLEND_COLOR = 32773 + FUNC_ADD = 32774 + BLEND_EQUATION = 32777 + BLEND_EQUATION_RGB = 32777 + FUNC_SUBTRACT = 32778 + FUNC_REVERSE_SUBTRACT = 32779 + UNSIGNED_SHORT_4_4_4_4 = 32819 + UNSIGNED_SHORT_5_5_5_1 = 32820 + POLYGON_OFFSET_FILL = 32823 + POLYGON_OFFSET_FACTOR = 32824 + RGBA4 = 32854 + RGB5_A1 = 32855 + TEXTURE_BINDING_2D = 32873 + SAMPLE_ALPHA_TO_COVERAGE = 32926 + SAMPLE_COVERAGE = 32928 + SAMPLE_BUFFERS = 32936 + SAMPLES = 32937 + SAMPLE_COVERAGE_VALUE = 32938 + SAMPLE_COVERAGE_INVERT = 32939 + BLEND_DST_RGB = 32968 + BLEND_SRC_RGB = 32969 + BLEND_DST_ALPHA = 32970 + BLEND_SRC_ALPHA = 32971 + CLAMP_TO_EDGE = 33071 + GENERATE_MIPMAP_HINT = 33170 + DEPTH_COMPONENT16 = 33189 + DEPTH_STENCIL_ATTACHMENT = 33306 + UNSIGNED_SHORT_5_6_5 = 33635 + MIRRORED_REPEAT = 33648 + ALIASED_POINT_SIZE_RANGE = 33901 + ALIASED_LINE_WIDTH_RANGE = 33902 + TEXTURE0 = 33984 + TEXTURE1 = 33985 + TEXTURE2 = 33986 + TEXTURE3 = 33987 + TEXTURE4 = 33988 + TEXTURE5 = 33989 + TEXTURE6 = 33990 + TEXTURE7 = 33991 + TEXTURE8 = 33992 + TEXTURE9 = 33993 + TEXTURE10 = 33994 + TEXTURE11 = 33995 + TEXTURE12 = 33996 + TEXTURE13 = 33997 + TEXTURE14 = 33998 + TEXTURE15 = 33999 + TEXTURE16 = 34000 + TEXTURE17 = 34001 + TEXTURE18 = 34002 + TEXTURE19 = 34003 + TEXTURE20 = 34004 + TEXTURE21 = 34005 + TEXTURE22 = 34006 + TEXTURE23 = 34007 + TEXTURE24 = 34008 + TEXTURE25 = 34009 + TEXTURE26 = 34010 + TEXTURE27 = 34011 + TEXTURE28 = 34012 + TEXTURE29 = 34013 + TEXTURE30 = 34014 + TEXTURE31 = 34015 + ACTIVE_TEXTURE = 34016 + MAX_RENDERBUFFER_SIZE = 34024 + DEPTH_STENCIL = 34041 + INCR_WRAP = 34055 + DECR_WRAP = 34056 + TEXTURE_CUBE_MAP = 34067 + TEXTURE_BINDING_CUBE_MAP = 34068 + TEXTURE_CUBE_MAP_POSITIVE_X = 34069 + TEXTURE_CUBE_MAP_NEGATIVE_X = 34070 + TEXTURE_CUBE_MAP_POSITIVE_Y = 34071 + TEXTURE_CUBE_MAP_NEGATIVE_Y = 34072 + TEXTURE_CUBE_MAP_POSITIVE_Z = 34073 + TEXTURE_CUBE_MAP_NEGATIVE_Z = 34074 + MAX_CUBE_MAP_TEXTURE_SIZE = 34076 + VERTEX_ATTRIB_ARRAY_ENABLED = 34338 + VERTEX_ATTRIB_ARRAY_SIZE = 34339 + VERTEX_ATTRIB_ARRAY_STRIDE = 34340 + VERTEX_ATTRIB_ARRAY_TYPE = 34341 + CURRENT_VERTEX_ATTRIB = 34342 + VERTEX_ATTRIB_ARRAY_POINTER = 34373 + NUM_COMPRESSED_TEXTURE_FORMATS = 34466 + COMPRESSED_TEXTURE_FORMATS = 34467 + BUFFER_SIZE = 34660 + BUFFER_USAGE = 34661 + STENCIL_BACK_FUNC = 34816 + STENCIL_BACK_FAIL = 34817 + STENCIL_BACK_PASS_DEPTH_FAIL = 34818 + STENCIL_BACK_PASS_DEPTH_PASS = 34819 + BLEND_EQUATION_ALPHA = 34877 + MAX_VERTEX_ATTRIBS = 34921 + VERTEX_ATTRIB_ARRAY_NORMALIZED = 34922 + MAX_TEXTURE_IMAGE_UNITS = 34930 + ARRAY_BUFFER = 34962 + ELEMENT_ARRAY_BUFFER = 34963 + ARRAY_BUFFER_BINDING = 34964 + ELEMENT_ARRAY_BUFFER_BINDING = 34965 + VERTEX_ATTRIB_ARRAY_BUFFER_BINDING = 34975 + STREAM_DRAW = 35040 + STATIC_DRAW = 35044 + DYNAMIC_DRAW = 35048 + FRAGMENT_SHADER = 35632 + VERTEX_SHADER = 35633 + MAX_VERTEX_TEXTURE_IMAGE_UNITS = 35660 + MAX_COMBINED_TEXTURE_IMAGE_UNITS = 35661 + SHADER_TYPE = 35663 + FLOAT_VEC2 = 35664 + FLOAT_VEC3 = 35665 + FLOAT_VEC4 = 35666 + INT_VEC2 = 35667 + INT_VEC3 = 35668 + INT_VEC4 = 35669 + BOOL = 35670 + BOOL_VEC2 = 35671 + BOOL_VEC3 = 35672 + BOOL_VEC4 = 35673 + FLOAT_MAT2 = 35674 + FLOAT_MAT3 = 35675 + FLOAT_MAT4 = 35676 + SAMPLER_2D = 35678 + SAMPLER_CUBE = 35680 + DELETE_STATUS = 35712 + COMPILE_STATUS = 35713 + LINK_STATUS = 35714 + VALIDATE_STATUS = 35715 + INFO_LOG_LENGTH = 35716 + ATTACHED_SHADERS = 35717 + ACTIVE_UNIFORMS = 35718 + ACTIVE_UNIFORM_MAX_LENGTH = 35719 + SHADER_SOURCE_LENGTH = 35720 + ACTIVE_ATTRIBUTES = 35721 + ACTIVE_ATTRIBUTE_MAX_LENGTH = 35722 + SHADING_LANGUAGE_VERSION = 35724 + CURRENT_PROGRAM = 35725 + STENCIL_BACK_REF = 36003 + STENCIL_BACK_VALUE_MASK = 36004 + STENCIL_BACK_WRITEMASK = 36005 + FRAMEBUFFER_BINDING = 36006 + RENDERBUFFER_BINDING = 36007 + FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE = 36048 + FRAMEBUFFER_ATTACHMENT_OBJECT_NAME = 36049 + FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL = 36050 + FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE = 36051 + FRAMEBUFFER_COMPLETE = 36053 + FRAMEBUFFER_INCOMPLETE_ATTACHMENT = 36054 + FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT = 36055 + FRAMEBUFFER_INCOMPLETE_DIMENSIONS = 36057 + FRAMEBUFFER_UNSUPPORTED = 36061 + COLOR_ATTACHMENT0 = 36064 + DEPTH_ATTACHMENT = 36096 + STENCIL_ATTACHMENT = 36128 + FRAMEBUFFER = 36160 + RENDERBUFFER = 36161 + RENDERBUFFER_WIDTH = 36162 + RENDERBUFFER_HEIGHT = 36163 + RENDERBUFFER_INTERNAL_FORMAT = 36164 + STENCIL_INDEX8 = 36168 + RENDERBUFFER_RED_SIZE = 36176 + RENDERBUFFER_GREEN_SIZE = 36177 + RENDERBUFFER_BLUE_SIZE = 36178 + RENDERBUFFER_ALPHA_SIZE = 36179 + RENDERBUFFER_DEPTH_SIZE = 36180 + RENDERBUFFER_STENCIL_SIZE = 36181 + RGB565 = 36194 + LOW_FLOAT = 36336 + MEDIUM_FLOAT = 36337 + HIGH_FLOAT = 36338 + LOW_INT = 36339 + MEDIUM_INT = 36340 + HIGH_INT = 36341 + SHADER_COMPILER = 36346 + MAX_VERTEX_UNIFORM_VECTORS = 36347 + MAX_VARYING_VECTORS = 36348 + MAX_FRAGMENT_UNIFORM_VECTORS = 36349 + UNPACK_FLIP_Y_WEBGL = 37440 + UNPACK_PREMULTIPLY_ALPHA_WEBGL = 37441 + CONTEXT_LOST_WEBGL = 37442 + UNPACK_COLORSPACE_CONVERSION_WEBGL = 37443 + BROWSER_DEFAULT_WEBGL = 37444 \ No newline at end of file diff --git a/importer/binaly_loader.py b/importer/binaly_loader.py new file mode 100644 index 0000000..0dd4976 --- /dev/null +++ b/importer/binaly_loader.py @@ -0,0 +1,74 @@ +""" +Copyright (c) 2018 iCyP +Released under the MIT license +https://opensource.org/licenses/mit-license.php + +""" + +import struct +from ..gl_const import GL_CONSTANS + + +class Binaly_Reader: + def __init__(self, data: bytes)->None: + self.data = data + self.pos = 0 + + def set_pos(self, pos): + self.pos = pos + + def read_str(self, size): + result = self.data[self.pos: self.pos + size] + self.pos += size + return result.decode("utf-8") + + def read_binaly(self, size): + result = self.data[self.pos: self.pos + size] + self.pos += size + return result + + def read_uint(self): + #unpackは内容の個数に関わらずタプルで返すので[0]が必要 + result = struct.unpack('>objの順でやらないと不具合 + bpy.ops.object.select_all(action="DESELECT") + bpy.context.scene.objects.active = self.armature + self.armature.select = True + self.armature.rotation_mode = "XYZ" + self.armature.rotation_euler[0] = numpy.deg2rad(90) + self.armature.rotation_euler[2] = numpy.deg2rad(-180) + self.armature.location = [self.armature.location[axis]*inv for axis,inv in zip([0,2,1],[-1,1,1])] + bpy.ops.object.transform_apply(location = True,rotation=True) + bpy.ops.object.mode_set(mode='OBJECT') + bpy.ops.object.select_all(action="DESELECT") + for obj in self.mesh_joined_objects: + bpy.context.scene.objects.active = obj + obj.select = True + if obj.parent_type == 'BONE':#ボーンにくっ付いて動くのは無視:なんか吹っ飛ぶ髪の毛がいる? + bpy.ops.object.transform_apply(rotation=True) + print("bone parent object {}".format(obj.name)) + obj.select = False + continue + obj.rotation_mode = "XYZ" + obj.rotation_euler[0] = numpy.deg2rad(90) + obj.rotation_euler[2] = numpy.deg2rad(-180) + obj.location = [obj.location[axis]*inv for axis,inv in zip([0,2,1],[-1,1,1])] + bpy.ops.object.transform_apply(location= True,rotation=True) + obj.select = False + return + + def put_spring_bone_info(self): + + if not "secondaryAnimation" in self.vrm_pydata.json["extensions"]["VRM"]: + print("no secondary animation object") + return + secondaryAnimation_json = self.vrm_pydata.json["extensions"]["VRM"]["secondaryAnimation"] + spring_rootbone_groups_json = secondaryAnimation_json["boneGroups"] + collider_groups_json = secondaryAnimation_json["colliderGroups"] + nodes_json = self.vrm_pydata.json["nodes"] + for bone_group in spring_rootbone_groups_json: + for bone_id in bone_group["bones"]: + bone = self.armature.data.bones[nodes_json[bone_id]["name"]] + for key, val in bone_group.items(): + if key == "bones": + continue + bone[key] = val + + for collider_group in collider_groups_json: + collider_base_node = nodes_json[collider_group["node"]] + node_name = collider_base_node["name"] + for i, collider in enumerate(collider_group["colliders"]): + collider_name = "{}_collider_{}".format(node_name,i) + obj = bpy.data.objects.new(name = collider_name,object_data = None) + obj.parent = self.armature + obj.parent_type = "BONE" + obj.parent_bone = node_name + offset = [collider["offset"]["x"],collider["offset"]["y"],collider["offset"]["z"]] #values直接はindexアクセス出来ないのでしゃあなし + offset = [offset[axis]*inv for axis,inv in zip([0,2,1],[-1,-1,1])] #TODO: Y軸反転はuniVRMのシリアライズに合わせてる + + obj.matrix_world = self.armature.matrix_world * Matrix.Translation(offset) * self.armature.data.bones[node_name].matrix_local + obj.empty_draw_size = collider["radius"] + obj.empty_draw_type = "SPHERE" + bpy.context.scene.objects.link(obj) + + return \ No newline at end of file diff --git a/importer/vrm2pydata_factory.py b/importer/vrm2pydata_factory.py new file mode 100644 index 0000000..ea5cee1 --- /dev/null +++ b/importer/vrm2pydata_factory.py @@ -0,0 +1,122 @@ +""" +Copyright (c) 2018 iCyP +Released under the MIT license +https://opensource.org/licenses/mit-license.php + +""" + +from .. import V_Types as VRM_Types + +def bone(node)->VRM_Types.Node: + v_node = VRM_Types.Node() + if "name" in node: + v_node.name = node["name"] + else: + v_node.name = "tmp" + v_node.position = node["translation"] + v_node.rotation = node["rotation"] + v_node.scale = node["scale"] + if "children" in node: + if type(node["children"]) is int: + v_node.children = [node["children"]] + else: + v_node.children = node["children"] + else: + v_node.children = None + if "mesh" in node: + v_node.mesh_id = node["mesh"] + if "skin" in node: + v_node.skin_id = node["skin"] + return v_node + + + +def material(mat,ext_mat,textures)->VRM_Types.Material: + def gltf_mat_factory(): + gltf_mat = VRM_Types.Material_GLTF() + gltf_mat.name = mat["name"] + gltf_mat.shader_name = "gltf" + if "pbrMetallicRoughness" in mat: + pbrmat = mat["pbrMetallicRoughness"] + if "baseColorTexture" in pbrmat: + texture_index = pbrmat["baseColorTexture"]["index"] + gltf_mat.color_texture_index = textures[texture_index]["source"] + gltf_mat.color_texcoord_index= pbrmat["baseColorTexture"]["texCoord"] + if "baseColorFactor" in pbrmat: + gltf_mat.base_color = pbrmat["baseColorFactor"] + if "metallicFactor" in pbrmat: + gltf_mat.metallicFactor = pbrmat["metallicFactor"] + if "roughnessFactor" in pbrmat: + gltf_mat.roughnessFactor = pbrmat["roughnessFactor"] + if "metallicRoughnessTexture" in pbrmat: + gltf_mat.metallicRoughnessTexture_index = pbrmat["metallicRoughnessTexture"] + gltf_mat.metallicRoughnessTexture_texcoord = pbrmat["baseColorTexture"]["texCoord"] + if "normalTexture" in mat: + gltf_mat.normal_texture_index = mat["normalTexture"]["index"] + gltf_mat.normal_texture_texcoord_index = mat["normalTexture"]["texCoord"] + if "emissiveTexture" in mat: + gltf_mat.emissive_texture_index = mat["emissiveTexture"]["index"] + gltf_mat.emissive_texture_texcoord_index = mat["emissiveTexture"]["texCoord"] + if "occlusionTexture" in mat: + gltf_mat.occlusion_texture_index = mat["occlusionTexture"]["index"] + gltf_mat.occlusion_texture_texcood_index = mat["occlusionTexture"]["texCoord"] + if "emissiveFactor" in mat: + gltf_mat.emissive_color = mat["emissiveFactor"] + + if "doubleSided" in mat: + gltf_mat.doubleSided = mat["doubleSided"] + if "alphaMode" in mat: + if mat["alphaMode"] == "MASK": + gltf_mat.alpha_mode = "MASK" + if mat["alphaMode"] == "BLEND": + gltf_mat.alpha_mode = "Z_TRANSPARENCY" + if mat["alphaMode"] == "OPAQUE": + gltf_mat.alpha_mode = "OPAQUE" + if "extensions" in mat: + if "KHR_materials_unlit" in mat["extensions"]: + gltf_mat.shadeless = True + + return gltf_mat + + if "VRM_USE_GLTFSHADER" in ext_mat["shader"]: #standard, or VRM unsuported shader(no saved) + v_mat = gltf_mat_factory() + + else:#"MToon or Transparent_Zwrite" + if ext_mat["shader"] == "VRM/MToon": + v_mat = VRM_Types.Material_MToon() + v_mat.name = ext_mat["name"] + v_mat.shader_name = ext_mat["shader"] + #region check unknown props exist + subset = { + "float": ext_mat["floatProperties"].keys() - v_mat.float_props_dic.keys() , + "vector": ext_mat["vectorProperties"].keys() - v_mat.vector_props_dic.keys(), + "texture": ext_mat["textureProperties"].keys() - v_mat.texture_index_dic.keys(), + "keyword": ext_mat["keywordMap"].keys() - v_mat.keyword_dic.keys() + } + for k, _subset in subset.items(): + if _subset: + print("unknown {} propaties {} in {}".format(k, _subset, ext_mat["name"])) + #endregion check unknown props exit + + v_mat.float_props_dic.update(ext_mat["floatProperties"]) + v_mat.vector_props_dic.update(ext_mat["vectorProperties"]) + v_mat.texture_index_dic.update(ext_mat["textureProperties"]) + v_mat.keyword_dic.update(ext_mat["keywordMap"]) + v_mat.tag_dic.update(ext_mat["tagMap"]) + + elif ext_mat["shader"] == "VRM/UnlitTransparentZWrite": + v_mat = VRM_Types.Material_Transparent_Z_write() + v_mat.name = ext_mat["name"] + v_mat.shader_name = ext_mat["shader"] + v_mat.float_props_dic.update(ext_mat["floatProperties"]) + v_mat.vector_props_dic.update(ext_mat["vectorProperties"]) + v_mat.texture_index_dic.update(ext_mat["textureProperties"]) + else: + print("unknow shader:{}. use gltf material".format(ext_mat["shader"])) + v_mat = gltf_mat_factory() + + return v_mat + + + + diff --git a/importer/vrm_load.py b/importer/vrm_load.py new file mode 100644 index 0000000..3789176 --- /dev/null +++ b/importer/vrm_load.py @@ -0,0 +1,262 @@ +""" +Copyright (c) 2018 iCyP +Released under the MIT license +https://opensource.org/licenses/mit-license.php + +""" + +# codig :utf-8 +#for python3.5 - for blender2.79 +from .binaly_loader import Binaly_Reader +from ..gl_const import GL_CONSTANS as GLC +from .. import V_Types as VRM_Types +from . import vrm2pydata_factory +import os,re,copy +from math import sqrt,pow +import json +import numpy +from collections import OrderedDict + + + + +def parse_glb(data: bytes): + reader = Binaly_Reader(data) + magic = reader.read_str(4) + if magic != 'glTF': + raise Exception('magic not found: #{}'.format(magic)) + + version = reader.read_as_dataType(GLC.UNSIGNED_INT) + if version != 2: + raise Exception('version:#{} is not 2'.format(version)) + + size = reader.read_as_dataType(GLC.UNSIGNED_INT) + size -= 12 + + json_str = None + body = None + while size > 0: + # print(size) + + if json_str is not None and body is not None: + raise Exception('this vrm has chunks, this importer reads one chunk only.') + + chunk_size = reader.read_as_dataType(GLC.UNSIGNED_INT) + size -= 4 + + chunk_type = reader.read_str(4) + size -= 4 + + chunk_data = reader.read_binaly(chunk_size) + size -= chunk_size + + if chunk_type == 'BIN\x00': + body = chunk_data + elif chunk_type == 'JSON': + json_str = chunk_data.decode('utf-8')#blenderのpythonverが古く自前decode要す + else: + raise Exception('unknown chunk_type: {}'.format(chunk_type)) + + return json.loads(json_str,object_pairs_hook=OrderedDict), body + +#あくまでvrm(の特にバイナリ)をpythonデータ化するだけで、blender型に変形はここではしない +def read_vrm(model_path): + vrm_pydata = VRM_Types.VRM_pydata(filepath=model_path) + #datachunkは普通一つしかない + with open(model_path, 'rb') as f: + vrm_pydata.json, body_binary = parse_glb(f.read()) + + #KHR_DRACO_MESH_COMPRESSION は対応してない場合落とさないといけないらしい。どのみち壊れたデータになるからね。 + if "extensionsRequired" in vrm_pydata.json: + if "KHR_DRACO_MESH_COMPRESSION" in vrm_pydata.json["extensionsRequired"]: + raise Exception("This VRM has DRACO COMPRESSION. This importer can't read this VRM. Draco圧縮されたVRMは未対応です") + #改変不可ライセンスを撥ねる + #CC_ND + if "licenseName" in vrm_pydata.json["extensions"]["VRM"]["meta"]: + if re.match("CC(.*)ND(.*)", vrm_pydata.json["extensions"]["VRM"]["meta"]["licenseName"]) is not None: + raise Exception("This VRM is not allowed to Edit. CHECK ITS LICENSE 改変不可Licenseです。") + #Vroidbhub licence + if "otherPermissionUrl" in vrm_pydata.json["extensions"]["VRM"]["meta"]: + from urllib.parse import parse_qsl,urlparse + address = urlparse(vrm_pydata.json["extensions"]["VRM"]["meta"]["otherPermissionUrl"]).hostname + if address is None: + pass + elif "vroid" in address : + if dict(parse_qsl(vrm_pydata.json["extensions"]["VRM"]["meta"]["otherPermissionUrl"])).get("modification") == "disallow": + raise Exception("This VRM is not allowed to Edit. CHECK ITS LICENSE 改変不可Licenseです。") + #オリジナルライセンスに対する注意 + if vrm_pydata.json["extensions"]["VRM"]["meta"]["licenseName"] == "Other": + print("Is this VRM allowed to Edit? CHECK IT LICENSE") + + texture_rip(vrm_pydata,body_binary) + + vrm_pydata.decoded_binary = decode_bin(vrm_pydata.json,body_binary) + + mesh_read(vrm_pydata) + material_read(vrm_pydata) + skin_read(vrm_pydata) + node_read(vrm_pydata) + + + return vrm_pydata + +def texture_rip(vrm_pydata,body_binary): + bufferViews = vrm_pydata.json["bufferViews"] + binary_reader = Binaly_Reader(body_binary) + #ここ画像切り出し #blenderはバイト列から画像を読み込む術がないので、画像ファイルを書き出して、それを読み込むしかない。 + vrm_dir_path = os.path.dirname(os.path.abspath(vrm_pydata.filepath)) + if not "images" in vrm_pydata.json: + return + for id,image_prop in enumerate(vrm_pydata.json["images"]): + if "extra" in image_prop: + image_name = image_prop["extra"]["name"] + else : + image_name = image_prop["name"] + binary_reader.set_pos(bufferViews[image_prop["bufferView"]]["byteOffset"]) + image_binary = binary_reader.read_binaly(bufferViews[image_prop["bufferView"]]["byteLength"]) + image_type = image_prop["mimeType"].split("/")[-1] + if image_name == "": + image_name = "texture_" + str(id) + print("no name image is named {}".format(image_name)) + image_path = os.path.join(vrm_dir_path, image_name + "." + image_type) + if not os.path.exists(image_path):#すでに同名の画像がある場合は上書きしない + with open(image_path, "wb") as imageWriter: + imageWriter.write(image_binary) + else: + print(image_name + " Image is already exists. NOT OVER WRITTEN") + image_propaty = VRM_Types.Image_props(image_name,image_path,image_type) + vrm_pydata.image_propaties.append(image_propaty) + +# ”accessorの順に” データを読み込んでリストにしたものを返す +def decode_bin(json_data,binary): + br = Binaly_Reader(binary) + #This list indexed by accesser index + decoded_binary = [] + bufferViews = json_data["bufferViews"] + accessors = json_data["accessors"] + type_num_dict = {"SCALAR":1,"VEC2":2,"VEC3":3,"VEC4":4,"MAT4":16} + for accessor in accessors: + type_num = type_num_dict[accessor["type"]] + br.set_pos(bufferViews[accessor["bufferView"]]["byteOffset"]) + data_list = [] + for num in range(accessor["count"]): + if type_num == 1: + data = br.read_as_dataType(accessor["componentType"]) + else: + data = [] + for l in range(type_num): + data.append(br.read_as_dataType(accessor["componentType"])) + data_list.append(data) + decoded_binary.append(data_list) + + return decoded_binary + +def mesh_read(vrm_pydata): + #メッシュをパースする + for n,mesh in enumerate(vrm_pydata.json["meshes"]): + for j,primitive in enumerate(mesh["primitives"]): + vrm_mesh = VRM_Types.Mesh() + vrm_mesh.object_id = n + if j == 0:#mesh annotationとの兼ね合い + vrm_mesh.name = mesh["name"] + else : + vrm_mesh.name = mesh["name"]+str(j) + + #region 頂点index + if primitive["mode"] != GLC.TRIANGLES: + #TODO その他メッシュタイプ対応 + raise Exception("unSupported polygon type(:{}) Exception".format(primitive["mode"])) + vrm_mesh.face_indices = vrm_pydata.decoded_binary[primitive["indices"]] + #3要素ずつに変換しておく(GCL.TRIANGLES前提なので) + #ATTENTION これだけndarray + vrm_mesh.face_indices = numpy.reshape(vrm_mesh.face_indices, (-1, 3)) + #endregion 頂点index + + #ここから頂点属性 + vertex_attributes = primitive["attributes"] + #頂点属性は実装によっては存在しない属性(例えばJOINTSやWEIGHTSがなかったりもする)もあるし、UVや頂点カラー0->Nで増やせる(スキニングは1要素(ボーン4本)限定 + for attr in vertex_attributes.keys(): + vrm_mesh.__setattr__(attr,vrm_pydata.decoded_binary[vertex_attributes[attr]]) + #region TEXCOORD_FIX [ 古いuniVRM誤り: uv.y = -uv.y ->修復 uv.y = 1 - ( -uv.y ) => uv.y=1+uv.y] + #uvは0-1にある前提で、マイナスであれば変換ミスとみなす + uv_count = 0 + while True: + texcoordName = "TEXCOORD_{}".format(uv_count) + if hasattr(vrm_mesh, texcoordName): + texcoord = getattr(vrm_mesh,texcoordName) + for uv in texcoord: + if uv[1] < 0: + uv[1] = 1 + uv[1] + uv_count +=1 + else: + break + #blenderとは上下反対のuv,それはblenderに書き込むときに直す + #endregion TEXCOORD_FIX + + #meshに当てられるマテリアルの場所を記録 + vrm_mesh.material_index = primitive["material"] + + #変換時のキャッシュ対応のためのデータ + vrm_mesh.POSITION_accessor = primitive["attributes"]["POSITION"] + + #ここからモーフターゲット vrmのtargetは相対位置 normalは無視する + if "targets" in primitive: + morphTarget_point_list_and_accessor_index_dict = dict() + for i,morphTarget in enumerate(primitive["targets"]): + posArray = vrm_pydata.decoded_binary[morphTarget["POSITION"]] + if "extra" in morphTarget:#for old AliciaSolid + #accesserのindexを持つのは変換時のキャッシュ対応のため + morphTarget_point_list_and_accessor_index_dict[primitive["targets"][i]["extra"]["name"]] = [posArray,primitive["targets"][i]["POSITION"]] + else: + #同上 + morphTarget_point_list_and_accessor_index_dict[primitive["extras"]["targetNames"][i]] = [posArray,primitive["targets"][i]["POSITION"]] + vrm_mesh.__setattr__("morphTarget_point_list_and_accessor_index_dict",morphTarget_point_list_and_accessor_index_dict) + + vrm_pydata.meshes.append(vrm_mesh) + + + #ここからマテリアル +def material_read(vrm_pydata): + VRM_EXTENSION_material_promaties = None + textures = None + try: + VRM_EXTENSION_material_propaties = vrm_pydata.json["extensions"]["VRM"]["materialProperties"] + except Exception as e: + print(e) + if "textures" in vrm_pydata.json: + textures = vrm_pydata.json["textures"] + for mat,ext_mat in zip(vrm_pydata.json["materials"],VRM_EXTENSION_material_propaties): + vrm_pydata.materials.append(vrm2pydata_factory.material(mat,ext_mat,textures)) + + + + #skinをパース ->バイナリの中身はskining実装の横着用 + #skinのjointsの(nodesの)indexをvertsのjoints_0は指定してる + #inverseBindMatrices: 単にスキニングするときの逆行列。読み込み不要なのでしない(自前計算もできる、めんどいけど) + #ついでに[i][3]ではなく、[3][i]にマイナスx,y,zが入っている。 ここで詰まった。(出力時に) + #joints:JOINTS_0の指定node番号のindex +def skin_read(vrm_pydata): + for skin in vrm_pydata.json["skins"]: + vrm_pydata.skins_joints_list.append(skin["joints"]) + if "skeleton" in skin.keys(): + vrm_pydata.skins_root_node_list.append(skin["skeleton"]) + + + #node(ボーン)をパースする->親からの相対位置で記録されている +def node_read(vrm_pydata): + for i,node in enumerate(vrm_pydata.json["nodes"]): + vrm_pydata.nodes_dict[i] = vrm2pydata_factory.bone(node) + #TODO こっからorigine_bone + if "mesh" in node.keys(): + vrm_pydata.origine_nodes_dict[i] = [vrm_pydata.nodes_dict[i],node["mesh"]] + if "skin" in node.keys(): + vrm_pydata.origine_nodes_dict[i].append(node["skin"]) + else: + print(node["name"] + "is not have skin") + + + +if "__main__" == __name__: + model_path = "./AliciaSolid\\AliciaSolid.vrm" + model_path = "./Vroid\\Vroid.vrm" + read_vrm(model_path) diff --git a/misc/VRM_HELPER.py b/misc/VRM_HELPER.py new file mode 100644 index 0000000..c0c3435 --- /dev/null +++ b/misc/VRM_HELPER.py @@ -0,0 +1,148 @@ +""" +Copyright (c) 2018 iCyP +Released under the MIT license +https://opensource.org/licenses/mit-license.php + +""" +import bpy +import bmesh +import re +from math import sqrt, pow +from mathutils import Vector +from collections import deque +class Bones_rename(bpy.types.Operator): + bl_idname = "vrm.bones_rename" + bl_label = "convert Vroid_bones" + bl_description = "convert Vroid_bones as blender type" + bl_options = {'REGISTER', 'UNDO'} + + + def execute(self, context): + for x in bpy.context.active_object.data.bones: + for RL in ["L","R"]: + ma = re.match("(.*)_"+RL+"_(.*)",x.name) + if ma: + tmp = "" + for y in ma.groups(): + tmp += y + "_" + tmp += RL + x.name = tmp + return {"FINISHED"} + + +import json +from collections import OrderedDict +import os + +class Vroid2VRC_ripsync_from_json_recipe(bpy.types.Operator): + bl_idname = "vrm.ripsync_vrm" + bl_label = "make ripsync4VRC" + bl_description = "make ripsync from Vroid to VRC by json" + bl_options = {'REGISTER', 'UNDO'} + + def execute(self, context): + recipe_uri =os.path.join(os.path.dirname(__file__) ,"Vroid2vrc_ripsync_recipe.json") + recipe = None + with open(recipe_uri,"rt") as raw_recipe: + recipe = json.loads(raw_recipe.read(),object_pairs_hook=OrderedDict) + for shapekey_name,based_values in recipe["shapekeys"].items(): + for k in bpy.context.active_object.data.shape_keys.key_blocks: + k.value = 0.0 + for based_shapekey_name,based_val in based_values.items(): + bpy.context.active_object.data.shape_keys.key_blocks[based_shapekey_name].value = based_val + bpy.ops.object.shape_key_add(from_mix = True) + bpy.context.active_object.data.shape_keys.key_blocks[-1].name = shapekey_name + for k in bpy.context.active_object.data.shape_keys.key_blocks: + k.value = 0.0 + return {"FINISHED"} + + +class VRM_VALIDATOR(bpy.types.Operator): + bl_idname = "vrm.model_validate" + bl_label = "check as VRM model" + bl_description = "NO Quad_Poly & N_GON, NO unSkind Mesh etc..." + bl_options = {'REGISTER', 'UNDO'} + + + def execute(self,context): + print("validation start") + armature_count = 0 + armature = None + node_name_set = set() + for obj in bpy.context.selected_objects: + if obj.name in node_name_set: + print("VRM exporter need Nodes(mesh,bones) name is unique. {} is doubled.".format(obj.name)) + node_name_set.add(obj.name) + if obj.type != "EMPTY" and (obj.parent is not None and obj.parent.type != "ARMATURE" and obj.type == "MESH"): + if obj.location != Vector([0.0,0.0,0.0]):#mesh and armature origin is on [0,0,0] + print("There are not on origine location object {}".format(obj.name)) + if obj.type == "ARMATURE": + armature = obj + armature_count += 1 + if armature_count > 2:#only one armature + print("VRM exporter needs only one armature not some armatures") + already_root_bone_exist = False + for bone in obj.data.bones: + if bone.name in node_name_set:#nodes name is unique + print("VRM exporter need Nodes(mesh,bones) name is unique. {} is doubled".format(bone.name)) + node_name_set.add(bone.name) + if bone.parent == None: #root bone is only 1 + if already_root_bone_exist: + print("root bone is only one {},{} are root bone now".format(bone.name,already_root_bone_exist)) + already_root_bone_exist = bone.name + #TODO: T_POSE, + require_human_bone_dic = {bone_tag : None for bone_tag in [ + "hips","leftUpperLeg","rightUpperLeg","leftLowerLeg","rightLowerLeg","leftFoot","rightFoot", + "spine","chest","neck","head","leftUpperArm","rightUpperArm", + "leftLowerArm","rightLowerArm","leftHand","rightHand" + ]} + for bone in armature.data.bones: + if "humanBone" in bone.keys(): + if bone["humanBone"] in require_human_bone_dic.keys(): + if require_human_bone_dic[bone["humanBone"]]: + print("humanBone is doubled with {},{}".format(bone.name,require_human_bone_dic[bone["humanBone"]].name)) + else: + require_human_bone_dic[bone["humanBone"]] = bone + for k,v in require_human_bone_dic.items(): + if v is None: + print("humanBone: {} is not defined.".format(k)) + defined_human_bone = ["jaw","leftShoulder","rightShoulder", + "leftEye","rightEye","upperChest","leftToes","rightToes", + "leftThumbProximal","leftThumbIntermediate","leftThumbDistal","leftIndexProximal", + "leftIndexIntermediate","leftIndexDistal","leftMiddleProximal","leftMiddleIntermediate", + "leftMiddleDistal","leftRingProximal","leftRingIntermediate","leftRingDistal", + "leftLittleProximal","leftLittleIntermediate","leftLittleDistal", + "rightThumbProximal","rightThumbIntermediate","rightThumbDistal", + "rightIndexProximal","rightIndexIntermediate","rightIndexDistal", + "rightMiddleProximal","rightMiddleIntermediate","rightMiddleDistal", + "rightRingProximal","rightRingIntermediate","rightRingDistal", + "rightLittleProximal","rightLittleIntermediate","rightLittleDistal" + ] + + if obj.type == "MESH": + if len(obj.data.materials) == 0: + print("There is no material in mesh {}".format(obj.name)) + for poly in obj.data.polygons: + if poly.loop_total > 3:#polygons need all triangle + print("There are not Triangle faces in {}".format(obj.name)) + #TODO modifier applyed, vertex weight Bone exist, vertex weight numbers. + used_image = [] + used_material_set = set() + for mesh in [obj for obj in bpy.context.selected_objects if obj.type == "MESH"]: + for mat in mesh.data.materials: + used_material_set.add(mat) + for mat in used_material_set: + if mat.texture_slots is not None: + used_image += [tex_slot.texture.image for tex_slot in mat.texture_slots if tex_slot is not None] + #thumbnail + try: + used_image.append(bpy.data.images[armature["texture"]]) + except: + pass + for img in used_image: + if img.is_dirty or img.filepath =="": + print("{} is not saved, please save.".format(img.name)) + + #TODO textblock_validate + print("validation finished") + return {"FINISHED"} \ No newline at end of file diff --git a/misc/Vroid2vrc_ripsync_recipe.json b/misc/Vroid2vrc_ripsync_recipe.json new file mode 100644 index 0000000..3a8ca13 --- /dev/null +++ b/misc/Vroid2vrc_ripsync_recipe.json @@ -0,0 +1,24 @@ +{ + "licence": "auther: https://creativecommons.org/licenses/by/4.0/", + "shapekeys": { + "vrc.blink_left":{"Face.M_F00_000_Fcl_EYE_Close_L":1.0}, + "vrc.blink_right":{"Face.M_F00_000_Fcl_EYE_Close_R":1.0}, + "vrc.lowerlid_left":{}, + "vrc.lowerlid_right":{}, + "vrc.v_aa":{"Face.M_F00_000_Fcl_MTH_A":1.0}, + "vrc.v_ch":{"Face.M_F00_000_Fcl_MTH_I":0.6}, + "vrc.v_dd":{"Face.M_F00_000_Fcl_MTH_E":0.7}, + "vrc.v_e":{"Face.M_F00_000_Fcl_MTH_E":1.0}, + "vrc.v_ff":{"Face.M_F00_000_Fcl_MTH_A":0.2}, + "vrc.v_ih":{"Face.M_F00_000_Fcl_MTH_A":1.0}, + "vrc.v_kk":{"Face.M_F00_000_Fcl_MTH_A":1.0}, + "vrc.v_nn":{"Face.M_F00_000_Fcl_MTH_E":0.7}, + "vrc.v_oh":{"Face.M_F00_000_Fcl_MTH_O":1.0}, + "vrc.v_ou":{"Face.M_F00_000_Fcl_MTH_U":0.8}, + "vrc.v_pp":{"Face.M_F00_000_Fcl_MTH_Angry":1.0}, + "vrc.v_rr":{"Face.M_F00_000_Fcl_MTH_A":0.2}, + "vrc.v_sil":{}, + "vrc.v_ss":{"Face.M_F00_000_Fcl_MTH_I":0.8}, + "vrc.v_th":{"Face.M_F00_000_Fcl_MTH_A":1.0} + } +} \ No newline at end of file diff --git a/misc/glb_bin_collector.py b/misc/glb_bin_collector.py new file mode 100644 index 0000000..6a064ac --- /dev/null +++ b/misc/glb_bin_collector.py @@ -0,0 +1,101 @@ +""" +Copyright (c) 2018 iCyP +Released under the MIT license +https://opensource.org/licenses/mit-license.php + +""" +from ..gl_const import GL_CONSTANS +import json +from collections import OrderedDict +class Glb_bin_collection: + def __init__(self): + self.vertex_attribute_bins = [] #Glb_bin list + self.image_bins = [] + self.bin = b"" + def pack_all(self): + bin_dic = OrderedDict() + byteOffset = 0 + bin_dic["bufferViews"] = [] + bin_dic["images"] = [] + for img in self.image_bins: + self.bin +=img.bin + bin_dic["images"].append(OrderedDict({ + "name": img.name, + "bufferView": img.buffer_view_id, + "mimeType": img.image_type + })) + bin_dic["bufferViews"].append(OrderedDict({ + "buffer": 0, + "byteOffset": byteOffset, + "byteLength": img.bin_length + })) + byteOffset += img.bin_length + bin_dic["accessors"] = [] + for vab in self.vertex_attribute_bins: + self.bin += vab.bin + vab_dic = OrderedDict({ + "bufferView": vab.buffer_view_id, + "byteOffset": 0, + "type": vab.array_type, + "componentType": vab.component_type, + "count": vab.array_count, + "normalized": False + }) + if vab.min_max: + vab_dic["min"] = vab.min_max[0] + vab_dic["max"] = vab.min_max[1] + bin_dic["accessors"].append(vab_dic) + bin_dic["bufferViews"].append(OrderedDict({ + "buffer": 0, + "byteOffset": byteOffset, + "byteLength": vab.bin_length + })) + byteOffset += vab.bin_length + bin_dic["buffers"] = [{"byteLength":byteOffset}] + + buffer_view_and_accessors_orderd_dic = bin_dic + return buffer_view_and_accessors_orderd_dic,self.bin + + def get_new_buffer_view_id(self): + return len(self.vertex_attribute_bins) + len(self.image_bins) + + def get_new_image_id(self): + return len(self.image_bins) + + def get_new_glb_bin_id(self): + return len(self.vertex_attribute_bins) + +class Base_bin(): + def __init__(self,bin,glb_bin_collection): + self.bin = bin + self.bin_length = len(bin) + self.buffer_view_id = glb_bin_collection.get_new_buffer_view_id() + +class Image_bin(Base_bin): + def __init__(self, + image_bin="", name="", image_type="image/png", + glb_bin_collection = None): + super().__init__(image_bin,glb_bin_collection) + self.name = name + self.image_type = image_type + self.image_id = glb_bin_collection.get_new_image_id() + glb_bin_collection.image_bins.append(self) + +class Glb_bin(Base_bin): + def __init__(self, + bin="", + array_type="SCALAR", component_type=GL_CONSTANS.FLOAT, + array_count=0, + min_max_tuple = None, + glb_bin_collection = None): + super().__init__(bin,glb_bin_collection) + self.array_type = array_type #String:scalar,VEC3 etc... + self.component_type = component_type #GL_CONSTANS:FLOAT, uint etc... + self.array_count = array_count #array num + self.min_max = min_max_tuple # position attribute must need min_max + self.accessor_id = glb_bin_collection.get_new_glb_bin_id() + glb_bin_collection.vertex_attribute_bins.append(self) + + + + \ No newline at end of file diff --git a/misc/glb_factory.py b/misc/glb_factory.py new file mode 100644 index 0000000..3fb0e3e --- /dev/null +++ b/misc/glb_factory.py @@ -0,0 +1,630 @@ +""" +Copyright (c) 2018 iCyP +Released under the MIT license +https://opensource.org/licenses/mit-license.php + +""" +from .glb_bin_collector import Glb_bin_collection, Image_bin, Glb_bin +from ..gl_const import GL_CONSTANS +from .. import V_Types as VRM_types +from collections import OrderedDict +from math import pow +import json +import struct +from sys import float_info +import bpy,bmesh +class Glb_obj(): + def __init__(self): + self.json_dic = OrderedDict() + self.bin = b"" + self.glb_bin_collector = Glb_bin_collection() + self.armature = [obj for obj in bpy.context.selected_objects if obj.type == "ARMATURE"][0] + self.result = None + def convert_bpy2glb(self): + self.image_to_bin() + self.armature_to_node_and_scenes_dic() #親のないboneは1つだけ as root_bone + self.texture_to_dic() + self.material_to_dic() + self.mesh_to_bin_and_dic() + self.json_dic["scene"] = 0 + self.glTF_meta_to_dic() + self.vrm_meta_to_dic() #colliderとかmetaとか.... + self.finalize() + return self.result + @staticmethod + def axis_blender_to_glb(vec3): + return [vec3[i]*t for i,t in zip([0,2,1],[-1,1,1])] + + @staticmethod + def textblock2str(textblock): + return "".join([line.body for line in textblock.lines]) + + def image_to_bin(self): + #collect used image + used_image = set() + used_material_set = set() + for mesh in [obj for obj in bpy.context.selected_objects if obj.type == "MESH"]: + for mat in mesh.data.materials: + used_material_set.add(mat) + for mat in used_material_set: + if mat.texture_slots is not None: + used_image = used_image.union(set([tex_slot.texture.image for tex_slot in mat.texture_slots if tex_slot is not None])) + #thumbnail + used_image.add(bpy.data.images[self.armature["texture"]]) + + for image in used_image: + if image.is_dirty: + print("unsaved image name:{}, please save it".format(image.name)) + raise Exception() + with open(image.filepath_from_user(),"rb") as f: + image_bin = f.read() + name = image.name + filetype = "image/"+image.file_format.lower() + Image_bin(image_bin,name,filetype,self.glb_bin_collector) + return + + def armature_to_node_and_scenes_dic(self): + nodes = [] + scene = [] + skins = [] + + bone_id_dic = {b.name : bone_id for bone_id,b in enumerate(self.armature.data.bones)} + def bone_to_node(b_bone): + parent_head_local = b_bone.parent.head_local if b_bone.parent is not None else [0,0,0] + node = OrderedDict({ + "name":b_bone.name, + "translation":self.axis_blender_to_glb([b_bone.head_local[i] - parent_head_local[i] for i in range(3)]), + "rotation":[0,0,0,1], + "scale":[1,1,1], + "children":[bone_id_dic[ch.name] for ch in b_bone.children] + }) + if len(node["children"]) == 0: + del node["children"] + return node + skin = {"joints":[]} + for bone in self.armature.data.bones: + if bone.parent is None: #root bone + root_bone_id = bone_id_dic[bone.name] + skin["joints"].append(root_bone_id) + skin["skeleton"] = root_bone_id + scene.append(root_bone_id) + nodes.append(bone_to_node(bone)) + bone_children = [b for b in bone.children] + while bone_children : + child = bone_children.pop() + nodes.append(bone_to_node(child)) + skin["joints"].append(bone_id_dic[child.name]) + bone_children += [ch for ch in child.children] + nodes = sorted(nodes,key=lambda node: bone_id_dic[node["name"]]) + skins.append(skin) + + + skin_invert_matrix_bin = b"" + f_4x4_packer = struct.Struct("<16f").pack + for node_id in skins[0]["joints"]: + bone_name = nodes[node_id]["name"] + bone_glb_world_pos = self.axis_blender_to_glb(self.armature.data.bones[bone_name].head_local) + inv_matrix = [ + 1,0,0,0, + 0,1,0,0, + 0,0,1,0, + -bone_glb_world_pos[0],-bone_glb_world_pos[1],-bone_glb_world_pos[2],1 + ] + skin_invert_matrix_bin += f_4x4_packer(*inv_matrix) + + IM_bin = Glb_bin(skin_invert_matrix_bin,"MAT4",GL_CONSTANS.FLOAT,len(skins[0]["joints"]),None,self.glb_bin_collector) + skins[0]["inverseBindMatrices"] = IM_bin.accessor_id + self.json_dic.update({"scenes":[{"nodes":scene}]}) + self.json_dic.update({"nodes":nodes}) + self.json_dic.update({"skins":skins}) + return + + def texture_to_dic(self): + self.json_dic["samplers"] = [{ + "magFilter": GL_CONSTANS.LINEAR, #TODO: 決め打ちすんな? + "minFilter": GL_CONSTANS.LINEAR, + "wrapS": GL_CONSTANS.REPEAT, + "wrapT": GL_CONSTANS.REPEAT + }] + textures = [] + for id in range(len(self.glb_bin_collector.image_bins)): + texture = { + "sampler":0, + "source": id + } + textures.append(texture) + self.json_dic.update({"textures":textures}) + return + + def material_to_dic(self): + glb_material_list = [] + VRM_material_props_list = [] + + image_id_dic = {image.name:image.image_id for image in self.glb_bin_collector.image_bins} + used_material_set = set() + for mesh in [obj for obj in bpy.context.selected_objects if obj.type == "MESH"]: + for mat in mesh.data.materials: + used_material_set.add(mat) + + for b_mat in used_material_set: + #region pbr_mat + mat_dic = {"name":b_mat.name} + + mat_dic["pbrMetallicRoughness"] = { + #gammma correction + "baseColorFactor":[*[pow(v,1/2.2) for v in b_mat.diffuse_color],1.0], + "metallicFactor": 0, + "roughnessFactor": 0.9 + } + if b_mat.texture_slots[0] is not None : + mat_dic.update({"baseColorTexture": { + "index": image_id_dic[b_mat.texture_slots[0].texture.image.name], + "texCoord": 0 #TODO: + }}) + + if not b_mat.use_transparency: + mat_dic["alphaMode"] = "OPAQUE" + elif b_mat.transparency_method == "MASK": + mat_dic["alphaMode"] = "MASK" + else:# Z_TRANSPARENCY or RAYTRACE + mat_dic["alphaMode"] = "BLEND" + glb_material_list.append(mat_dic) + #endregion pbr mat + + #region VRM_mat + + v_mat_dic = OrderedDict() + v_mat_dic["name"] = b_mat.name + v_mat_dic["shader"] = "VRM/MToon" + v_mat_dic["keywordMap"] = keyword_map = {} + v_mat_dic["tagMap"] = tag_map = {} + #TODO: vector props + + def linear_to_sRGB_color(linear_color): + sRGB_color = [0,0,0,1] + for i,c in enumerate(linear_color): + if c < 0.0031308: + sRGB_color[i] = c*12.92 + else: + sRGB_color[i] = 1.055 * pow(c, 1 / 2.4) - 0.055 + return sRGB_color + + def get_prop(material, prop_name, defo): + return [*material[prop_name]] if prop_name in material.keys() else defo + v_mat_dic["vectorProperties"] = vec_dic = OrderedDict() + vec_dic["_Color"] = linear_to_sRGB_color(b_mat.diffuse_color) + vec_dic["_ShadeColor"] = get_prop(b_mat, "_ShadeColor", [0.3, 0.3, 0.5, 1.0]) + vec_dic["_EmissionColor"] = get_prop(b_mat, "_EmissionColor", [0.0, 0.0, 0.0, 1.0]) + vec_dic["_OutlineColor"] = get_prop(b_mat, "_OutlineColor", [0.0, 0.0, 0.0, 1.0]) + + + #TODO: float props + v_mat_dic["floatProperties"] = float_dic = OrderedDict() + for prop in b_mat.keys(): + if prop in VRM_types.Material_MToon.float_props: + if b_mat[prop] == None: + continue + float_dic[prop] = b_mat[prop] + # _BlendMode : 0:Opacue 1:Cutout 2:Transparent 3:TransparentZwrite, + # _Src,_Dst(ry): CONST + # _ZWrite: 1: true 0:false + def material_prop_setter(blend_mode, \ + src_blend, dst_blend, \ + z_write, alphatest, \ + render_queue, render_type): + float_dic["_BlendMode"] = blend_mode + float_dic["_SrcBlend"] = src_blend + float_dic["_DstBlend"] = dst_blend + float_dic["_ZWrite"] = z_write + keyword_map.update({"_ALPHATEST_ON": alphatest}) + v_mat_dic["renderQueue"] = render_queue + tag_map["RenderType"] = render_type + + if not b_mat.use_transparency: + material_prop_setter(0,1,0,1,False,-1,"Opaque") + elif b_mat.transparency_method == "MASK": + material_prop_setter(1,1,0,1,True,2450,"TransparentCutout") + else: #transparent and Z_TRANPARENCY or Raytrace + material_prop_setter(3,5,10,1,True,3000,"Transparent") + keyword_map.update({"_ALPHABLEND_ON": b_mat.use_transparency}) + keyword_map.update({"_ALPHAPREMULTIPLY_ON":False}) + + float_dic["_CullMode"] = 0 #no cull + float_dic["_OutlineCullMode"] = 1 #front face cull (for invert normal outline) + float_dic["_DebugMode"] = 0 + keyword_map.update({"MTOON_DEBUG_NORMAL":False}) + keyword_map.update({"MTOON_DEBUG_LITSHADERATE":False}) + #region texture props + def texuture_prop_add(dic,tex_attr,tex_slot_id)->dict(): + try: + tex_dic = {tex_attr:image_id_dic[b_mat.texture_slots[tex_slot_id].texture.image.name]} + dic.update(tex_dic) + except AttributeError: + print("{} is nothing".format(tex_attr)) + return + v_mat_dic["textureProperties"] = tex_dic = OrderedDict() + use_nomalmap = False + for slot_id,texslot in enumerate(b_mat.texture_slots): + if texslot == None: + continue + if texslot.use_map_color_diffuse: + if texslot.texture_coords == "UV": + texuture_prop_add(tex_dic, "_MainTex", slot_id) + elif texslot.texture_coords == "NORMAL": + texuture_prop_add(tex_dic,"_SphereAdd",slot_id) + elif texslot.use_map_normal: + texuture_prop_add(tex_dic,"_BumpMap",slot_id) + use_nomalmap = True + elif texslot.use_map_emit: + texuture_prop_add(tex_dic, "_EmissionMap", slot_id) + else: + if "role" in texslot.texture.keys(): + texuture_prop_add(tex_dic, texslot.texture["role"], slot_id) + for tex_prop in tex_dic.keys(): + if not tex_prop in vec_dic: + vec_dic[tex_prop] = [0,0,1,1] + + keyword_map.update({"_NORMALMAP": use_nomalmap}) + + VRM_material_props_list.append(v_mat_dic) + #endregion VRM_mat + self.json_dic.update({"materials" : glb_material_list}) + self.json_dic.update({"extensions":{"VRM":{"materialProperties":VRM_material_props_list}}}) + return + + def mesh_to_bin_and_dic(self): + self.json_dic["meshes"] = [] + for id,mesh in enumerate([obj for obj in bpy.context.selected_objects if obj.type == "MESH"]): + is_skin_mesh = True + if len([m for m in mesh.modifiers if m.type == "ARMATURE"]) == 0: + if mesh.parent is not None: + if mesh.parent.type == "ARMATURE": + if mesh.parent_bone != None: + is_skin_mesh = False + node_dic = OrderedDict({ + "name":mesh.name, + "translation":self.axis_blender_to_glb(mesh.location), #原点にいてほしいけどね, vectorのままだとjsonに出来ないからこうする + "rotation":[0,0,0,1], #このへんは規約なので + "scale":[1,1,1], #このへんは規約なので + "mesh":id, + }) + if is_skin_mesh: + node_dic["skin"] = 0 #TODO: 決め打ちってどうよ:一体のモデルなのだから2つもあっては困る(から決め打ち(やめろ(やだ)) + self.json_dic["nodes"].append(node_dic) + + mesh_node_id = len(self.json_dic["nodes"])-1 + + if is_skin_mesh: + self.json_dic["scenes"][0]["nodes"].append(mesh_node_id) + else: + parent_node = [node for node in self.json_dic["nodes"] if node["name"] == mesh.parent_bone ][0] + if "children" in parent_node.keys(): + parent_node["children"].append(mesh_node_id) + else: + parent_node["children"] = [mesh_node_id] + relate_pos = [mesh.location[i] - self.armature.data.bones[mesh.parent_bone].head_local[i] for i in range(3)] + self.json_dic["nodes"][mesh_node_id]["translation"] = self.axis_blender_to_glb(relate_pos) + + #region hell + bpy.ops.object.mode_set(mode='OBJECT') + mesh.hide = False + mesh.hide_select = False + bpy.context.scene.objects.active = mesh + bpy.ops.object.mode_set(mode='EDIT') + bm = bmesh.from_edit_mesh(mesh.data) + + #region tempolary_used + mat_id_dic = {mat["name"]:i for i,mat in enumerate(self.json_dic["materials"])} + material_slot_dic = {i:mat.name for i,mat in enumerate(mesh.material_slots)} + node_id_dic = {node["name"]:i for i,node in enumerate(self.json_dic["nodes"])} + def joint_id_from_node_name_solver(node_name): + try: + node_id = node_id_dic[node_name] + joint_id = self.json_dic["skins"][0]["joints"].index(node_id) + except ValueError: + joint_id = -1 #存在しないボーンを指してる場合は-1を返す + print("{} bone may be not exist".format(node_name)) + return joint_id + v_group_name_dic = {i:vg.name for i,vg in enumerate(mesh.vertex_groups)} + fmin,fmax = float_info.min,float_info.max + unique_vertex_id = 0 + unique_vertex_id_dic = {} #loop verts id : base vertex id (uv違いを同じ頂点番号で管理されているので) + unique_vertex_dic = {} # {(uv...,vertex_index):unique_vertex_id} (uvと頂点番号が同じ頂点は同じものとして省くようにする) + uvlayers_dic = {i:uvlayer.name for i,uvlayer in enumerate(mesh.data.uv_layers)} + def fetch_morph_vertex_normal_difference(): #TODO 実装 + morph_normal_diff_dic = {} + vert_base_normal_dic = OrderedDict() + for kb in mesh.data.shape_keys.key_blocks: + vert_base_normal_dic.update( {kb.name:kb.normals_vertex_get()}) + for k,v in vert_base_normal_dic.items(): + if k == "Basis": + continue + values = [] + for vert_morph_normal,vert_base_normal in zip(zip(*[iter(v)]*3),zip(*[iter(vert_base_normal_dic["Basis"])]*3)): + values.append([vert_morph_normal[i]- vert_base_normal[i] for i in range(3)]) + morph_normal_diff_dic.update({k:values}) + return morph_normal_diff_dic + #endregion tempolary_used + primitive_index_bin_dic = OrderedDict({mat_id_dic[mat.name]:b"" for mat in mesh.material_slots}) + primitive_index_vertex_count = OrderedDict({mat_id_dic[mat.name]:0 for mat in mesh.material_slots}) + if mesh.data.shape_keys is None : + shape_pos_bin_dic = {} + shape_normal_bin_dic = {} + shape_min_max_dic = {} + morph_normal_diff_dic = {} + else: + shape_pos_bin_dic = OrderedDict({shape.name:b"" for shape in mesh.data.shape_keys.key_blocks[1:]})#0番目Basisは省く + shape_normal_bin_dic = OrderedDict({shape.name:b"" for shape in mesh.data.shape_keys.key_blocks[1:]}) + shape_min_max_dic = OrderedDict({shape.name:[[fmax,fmax,fmax],[fmin,fmin,fmin]] for shape in mesh.data.shape_keys.key_blocks[1:]}) + morph_normal_diff_dic = fetch_morph_vertex_normal_difference() #{morphname:{vertexid:[diff_X,diff_y,diff_z]}} + position_bin =b"" + position_min_max = [[fmax,fmax,fmax],[fmin,fmin,fmin]] + normal_bin = b"" + joints_bin = b"" + weights_bin = b"" + texcord_bins = {id:b"" for id in uvlayers_dic.keys()} + f_vec4_packer = struct.Struct(" minmax[1][i] else minmax[1][i] + return + for face in bm.faces: + #このへん絶対超遅い + for loop in face.loops: + uv_list = [] + for uvlayer_name in uvlayers_dic.values(): + uv_layer = bm.loops.layers.uv[uvlayer_name] + uv_list += [loop[uv_layer].uv[0],loop[uv_layer].uv[1]] + cached_vert_id = unique_vertex_dic.get((*uv_list,loop.vert.index)) #keyがなければNoneを返す + if cached_vert_id is not None: + primitive_index_bin_dic[mat_id_dic[material_slot_dic[face.material_index]]] += I_scalar_packer(cached_vert_id) + primitive_index_vertex_count[mat_id_dic[material_slot_dic[face.material_index]]] += 1 + continue + else: + unique_vertex_dic[(*uv_list,loop.vert.index)] = unique_vertex_id + for id,uvlayer_name in uvlayers_dic.items(): + uv_layer = bm.loops.layers.uv[uvlayer_name] + uv = loop[uv_layer].uv + texcord_bins[id] += f_pair_packer(uv[0],-uv[1]) #blenderとglbのuvは上下逆 + for shape_name in shape_pos_bin_dic.keys(): + shape_layer = bm.verts.layers.shape[shape_name] + morph_pos = self.axis_blender_to_glb( [loop.vert[shape_layer][i] - loop.vert.co[i] for i in range(3)]) + shape_pos_bin_dic[shape_name] += f_vec3_packer(*morph_pos) + shape_normal_bin_dic[shape_name] +=f_vec3_packer(*self.axis_blender_to_glb(morph_normal_diff_dic[shape_name][loop.vert.index])) + min_max(shape_min_max_dic[shape_name],morph_pos) + if is_skin_mesh: + magic = 0 + joints = [magic,magic,magic,magic] + weights = [0.0, 0.0, 0.0, 0.0] + if len(mesh.data.vertices[loop.vert.index].groups) >= 5: + print("vertex weights are less than 4 in {}".format(mesh.name)) + raise Exception + for v_group in mesh.data.vertices[loop.vert.index].groups: + joint_id = joint_id_from_node_name_solver(v_group_name_dic[v_group.group]) + if joint_id == -1:#存在しないボーンを指してる場合は-1を返されてるので、その場合は飛ばす + continue + weights.pop(3) + weights.insert(0,v_group.weight) + joints.pop(3) + joints.insert(0,joint_id) + nomalize_fact = sum(weights) + try: + weights = [weights[i]/nomalize_fact for i in range(4)] + except ZeroDivisionError : + print("vertex has no weight in {}".format(mesh.name)) + raise ZeroDivisionError + if sum(weights) < 1: + weights[0] += 1 - sum(weights) + joints_bin += H_vec4_packer(*joints) + weights_bin += f_vec4_packer(*weights) + + vert_location = self.axis_blender_to_glb(loop.vert.co) + position_bin += f_vec3_packer(*vert_location) + min_max(position_min_max,vert_location) + normal_bin += f_vec3_packer(*self.axis_blender_to_glb(loop.vert.normal)) + unique_vertex_id_dic[unique_vertex_id]=loop.vert.index + primitive_index_bin_dic[mat_id_dic[material_slot_dic[face.material_index]]] += I_scalar_packer(unique_vertex_id) + primitive_index_vertex_count[mat_id_dic[material_slot_dic[face.material_index]]] += 1 + unique_vertex_id += 1 + + #DONE :index position, uv, normal, position morph,JOINT WEIGHT + #TODO: morph_normal, v_color...? + primitive_glbs_dic = OrderedDict({ + mat_id:Glb_bin(index_bin,"SCALAR",GL_CONSTANS.UNSIGNED_INT,primitive_index_vertex_count[mat_id],None,self.glb_bin_collector) + for mat_id,index_bin in primitive_index_bin_dic.items() if index_bin !=b"" + }) + pos_glb = Glb_bin(position_bin,"VEC3",GL_CONSTANS.FLOAT,unique_vertex_id,position_min_max,self.glb_bin_collector) + nor_glb = Glb_bin(normal_bin,"VEC3",GL_CONSTANS.FLOAT,unique_vertex_id,None,self.glb_bin_collector) + uv_glbs = [ + Glb_bin(texcood_bin,"VEC2",GL_CONSTANS.FLOAT,unique_vertex_id,None,self.glb_bin_collector) + for texcood_bin in texcord_bins.values()] + if is_skin_mesh: + joints_glb = Glb_bin(joints_bin,"VEC4",GL_CONSTANS.UNSIGNED_SHORT,unique_vertex_id,None,self.glb_bin_collector) + weights_glb = Glb_bin(weights_bin,"VEC4",GL_CONSTANS.FLOAT,unique_vertex_id,None,self.glb_bin_collector) + if len(shape_pos_bin_dic.keys()) != 0: + morph_pos_glbs = [Glb_bin(morph_pos_bin,"VEC3",GL_CONSTANS.FLOAT,unique_vertex_id,morph_minmax,self.glb_bin_collector) + for morph_pos_bin,morph_minmax in zip(shape_pos_bin_dic.values(),shape_min_max_dic.values()) + ] + morph_normal_glbs = [Glb_bin(morph_normal_bin,"VEC3",GL_CONSTANS.FLOAT,unique_vertex_id,None,self.glb_bin_collector) + for morph_normal_bin in shape_normal_bin_dic.values() + ] + primitive_list = [] + for primitive_id,index_glb in primitive_glbs_dic.items(): + primitive = OrderedDict({"mode":4}) + primitive["material"] = primitive_id + primitive["indices"] = index_glb.accessor_id + primitive["attributes"] = { + "POSITION":pos_glb.accessor_id, + "NORMAL":nor_glb.accessor_id, + } + if is_skin_mesh: + primitive["attributes"].update({ + "JOINTS_0":joints_glb.accessor_id, + "WEIGHTS_0":weights_glb.accessor_id + }) + primitive["attributes"].update({"TEXCOORD_{}".format(i):uv_glb.accessor_id for i,uv_glb in enumerate(uv_glbs)}) + if len(shape_pos_bin_dic.keys()) != 0: + 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)] + primitive["extras"] = {"targetNames":[shape_name for shape_name in shape_pos_bin_dic.keys()]} + primitive_list.append(primitive) + self.json_dic["meshes"].append(OrderedDict({"name":mesh.name,"primitives":primitive_list})) + #endregion hell + bpy.ops.object.mode_set(mode='OBJECT') + + return + + def glTF_meta_to_dic(self): + glTF_meta_dic = { + "extensionsUsed":["VRM"], + "asset":{ + "generator":"icyp_blender_vrm_exporter_experimental_0.0", + "version":"2.0" + } + } + + self.json_dic.update(glTF_meta_dic) + return + + def vrm_meta_to_dic(self): + #materialProperties は material_to_dic()で処理する + #region vrm_extension + vrm_extension_dic = OrderedDict() + + #region meta + vrm_extension_dic["meta"] = vrm_meta_dic = {} + vrm_metas = [ + "version", + "author", + "contactInformation", + "reference", + "title", + "allowedUserName", + "violentUssageName", + "sexualUssageName", + "commercialUssageName", + "otherPermissionUrl", + "licenseName", + "otherLicenseUrl" + ] + for key in vrm_metas: + vrm_meta_dic[key] = self.armature[key] if key in self.armature.keys() else "" + if "texture" in self.armature.keys(): + thumbnail_index_list =[i for i,img in enumerate(self.glb_bin_collector.image_bins) if img.name == self.armature["texture"]] + if len(thumbnail_index_list) > 0 : + vrm_meta_dic["texture"] = thumbnail_index_list[0] + #endregion meta + #region humanoid + vrm_extension_dic["humanoid"] = vrm_humanoid_dic = {"humanBones":[]} + node_name_id_dic = {node["name"]:i for i, node in enumerate(self.json_dic["nodes"])} + for bone in self.armature.data.bones: + if "humanBone" in bone.keys(): + vrm_humanoid_dic["humanBones"].append({ + "bone": bone["humanBone"], + "node":node_name_id_dic[bone.name], + "useDefaultValues": True + }) + vrm_humanoid_dic.update(json.loads(self.textblock2str(bpy.data.texts[self.armature["humanoid_params"]]))) + #endregion humanoid + #region firstPerson + vrm_extension_dic["firstPerson"] = vrm_FP_dic = {} + vrm_FP_dic.update(json.loads(self.textblock2str(bpy.data.texts[self.armature["firstPerson_params"]]))) + if vrm_FP_dic["firstPersonBone"] != -1: + vrm_FP_dic["firstPersonBone"] = node_name_id_dic[vrm_FP_dic["firstPersonBone"]] + if "meshAnnotations" in vrm_FP_dic.keys(): + for meshAnnotation in vrm_FP_dic["meshAnnotations"]: + meshAnnotation["mesh"] = [i for i,mesh in enumerate(self.json_dic["meshes"]) if mesh["name"]==meshAnnotation["mesh"]][0] + + #endregion firstPerson + #region blendShapeMaster + vrm_extension_dic["blendShapeMaster"] = vrm_BSM_dic = {} + BSM_list = json.loads(self.textblock2str(bpy.data.texts[self.armature["blendshape_group"]])) + #meshを名前からid + #weightを0-1から0-100に + #shape_indexを名前からindexに + def clamp(min,val,max): + if max >= val: + if val >= min:return val + else: + print("blendshapeGroup weight is between 0 - 1, value is {}".format(val)) + return min + else: + print("blendshapeGroup weight is between 0 - 1, value is {}".format(val)) + return max + for bsm in BSM_list: + for bind in bsm["binds"]: + bind["mesh"] = [i for i,mesh in enumerate(self.json_dic["meshes"]) if mesh["name"]==bind["mesh"]][0] + bind["index"] = self.json_dic["meshes"][bind["mesh"]]["primitives"][0]["extras"]["targetNames"].index(bind["index"]) + bind["weight"] = clamp(0, bind["weight"]*100, 100) + vrm_BSM_dic["blendShapeGroups"] = BSM_list + #endregion blendShapeMaster + + #region secondaryAnimation + vrm_extension_dic["secondaryAnimation"] = {"boneGroups":[],"colliderGroups":[]} + + #region colliderGroups + #armatureの子emptyを変換する + collider_group_list = [] + empty_dic = {node_name_id_dic[ch.parent_bone]:[] for ch in self.armature.children if ch.type == "EMPTY"} + for childEmpty in [ch for ch in self.armature.children if ch.type == "EMPTY"]: + empty_dic[node_name_id_dic[childEmpty.parent_bone]].append(childEmpty) + for node_id,empty_objs in empty_dic.items(): + collider_group = {"node":node_id,"colliders":[]} + colliders = collider_group["colliders"] + for empty in empty_objs: + collider = {"radius":empty.empty_draw_size} + empty_offset_pos = [empty.matrix_world.to_translation()[i] \ + - self.armature.location[i] \ + - self.armature.data.bones[empty.parent_bone].head_local[i] \ + for i in range(3)] + collider["offset"] = OrderedDict({axis: o_s for axis, o_s in zip(("x", "y", "z"), self.axis_blender_to_glb(empty_offset_pos))}) + collider["offset"]["z"] = collider["offset"]["z"]*-1 #TODO: たぶんuniVRMのシリアライズがコライダーだけunity系になってる + colliders.append(collider) + collider_group_list.append(collider_group) + + vrm_extension_dic["secondaryAnimation"]["colliderGroups"] = collider_group_list + #endrigon colliderGroups + + #region boneGroup + #ボーン名からnode_idに + #collider_groupも名前からcolliderGroupのindexに直す + collider_node_id_list = [c_g["node"] for c_g in collider_group_list] + BG_list = json.loads(self.textblock2str(bpy.data.texts[self.armature["spring_bone"]])) + for bone_group in BG_list: + bone_group["bones"] = [node_name_id_dic[name] for name in bone_group["bones"] ] + bone_group["colliderGroups"] = [collider_node_id_list.index(node_name_id_dic[name]) for name in bone_group["colliderGroups"] ] + vrm_extension_dic["secondaryAnimation"]["boneGroups"]= BG_list + #endregion boneGroup + #endregion secondaryAnimation + self.json_dic["extensions"]["VRM"].update(vrm_extension_dic) + #endregion vrm_extension + + #region secondary + self.json_dic["nodes"].append({ + "name":"secondary", + "translation":[0.0,0.0,0.0], + "rotation":[0.0,0.0,0.0,1.0], + "scale":[1.0,1.0,1.0] + }) + self.json_dic["scenes"][0]["nodes"].append(len(self.json_dic["nodes"])-1) + return + + + def finalize(self): + bin_json, self.bin = self.glb_bin_collector.pack_all() + self.json_dic.update(bin_json) + magic = b'glTF' + struct.pack('