diff --git a/README.md b/README.md index 88a81884..03292eee 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Cats Blender Plugin (0.13.2) +# Cats Blender Plugin (0.13.3) A tool designed to shorten steps needed to import and optimize models into VRChat. Compatible models are: MMD, XNALara, Mixamo, Source Engine, Unreal Engine, DAZ/Poser, Blender Rigify, Sims 2, Motion Builder, 3DS Max and potentially more @@ -46,6 +46,7 @@ https://catsblenderplugin.com ![](https://i.imgur.com/eZV1zrs.gif) - Check your 3d view and there should be a new menu item called **CATS** ....w00t + - Since Blender 2.80 the CATS tab is on the right in the menu that opens when pressing 'N' ![](https://i.imgur.com/ItJLtNJ.png) @@ -332,6 +333,18 @@ It checks for a new version automatically once every day. ## Changelog +#### 0.13.3 +- **Importer**: + - Fixed imported armatures being in edit mode +- **Custom Model Creation**: + - Merge Armatures now properly merges bones when the vertex group of one of the merging bones is missing + - Attach Mesh no longer removes zero weight bones and constraints +- **Model Options**: + - Fixed error when switching to object mode during pose mode +- **General** + - Updated mmd_tools + - The Blender 2.80 API is stable now, so Cats should no longer break in 2.80 + #### 0.13.2 - **Importer**: - Now selects the imported armature in Cats @@ -394,35 +407,6 @@ It checks for a new version automatically once every day. - Updated mmd_tools - Fixed multiple errors -#### 0.12.2 -- **Optimization**: - - Added new "Convert Textures to PNG" button - - This converts all texture files into PNG files - - This helps with transparency and compatibility issues - - The converted image files will be saved next to the old ones -- **Model**: - - Made SSBU models compatible -- **Exporter**: - - No longer warns of meshes that could be split by Unity - - Instead warns when having more than 70k tris - - Now warns at 10 materials instead of 4, will be changed back when more nuanced warnings are in place -- **Decimation**: - - Changed default decimation value to 70,000 tris -- **General**: - - Fixed corrupted meshes breaking nearly all Cats features - - Added automatic fixing of faulty Cats installations - - Restart Cats if it doesn't show up after updating - - Rewrote the entire updater - - Added some Blender 2.8 compatibility fixes - - Updated mmd_tools - -#### 0.12.1 -- **General**: - - Fixed an error removing the whole Cats UI - - Fixed an error on Fix Model and other various buttons - - Stopping Pose Mode in Blender 2.8 now selects the Box Selection tool instead of the 3D Cursor - - Updated mmd_tools (improved 2.8 materials) - Read the full changelog [here](https://github.com/michaeldegroot/cats-blender-plugin/releases). diff --git a/__init__.py b/__init__.py index 76901172..bfcee70c 100644 --- a/__init__.py +++ b/__init__.py @@ -30,7 +30,7 @@ 'author': 'GiveMeAllYourCats', 'location': 'View 3D > Tool Shelf > CATS', 'description': 'A tool designed to shorten steps needed to import and optimize models into VRChat', - 'version': (0, 13, 2), # Has to be (x, x, x) not [x, x, x]!! # Only change this version and the dev branch var right before publishing the new update! + 'version': (0, 13, 3), # Has to be (x, x, x) not [x, x, x]!! # Only change this version and the dev branch var right before publishing the new update! 'blender': (2, 80, 0), 'wiki_url': 'https://github.com/michaeldegroot/cats-blender-plugin', 'tracker_url': 'https://github.com/michaeldegroot/cats-blender-plugin/issues', @@ -83,9 +83,10 @@ # How to update mmd_tools: -# Paste mmd_tools folder into project # Delete mmd_tools_local folder +# Paste mmd_tools folder into project # Refactor folder name "mmd_tools" to "mmd_tools_local" +# Move mmd_tools_local folder into extern_tools folder # Search for "show_backface_culling" and set it to False in view.py # Done diff --git a/extern_tools/mmd_tools_local/core/camera.py b/extern_tools/mmd_tools_local/core/camera.py index d322cca3..050a3967 100644 --- a/extern_tools/mmd_tools_local/core/camera.py +++ b/extern_tools/mmd_tools_local/core/camera.py @@ -56,6 +56,21 @@ def removeDrivers(cameraObj): cameraObj.data.driver_remove('ortho_scale') cameraObj.driver_remove('rotation_euler') + @staticmethod + def __focus_object_get(cameraObj): + camera = cameraObj.data + data = getattr(camera, 'dof', camera) + return data.focus_object if hasattr(data, 'focus_object') else data.dof_object + + @staticmethod + def __focus_object_set(cameraObj, focus): + camera = cameraObj.data + data = getattr(camera, 'dof', camera) + if hasattr(data, 'focus_object'): + data.focus_object = focus + else: + data.dof_object = focus + @staticmethod def convertToMMDCamera(cameraObj, scale=1.0): if MMDCamera.isMMDCamera(cameraObj): @@ -65,7 +80,6 @@ def convertToMMDCamera(cameraObj, scale=1.0): SceneOp(bpy.context).link_object(empty) cameraObj.parent = empty - cameraObj.data.dof_object = empty cameraObj.data.sensor_fit = 'VERTICAL' cameraObj.data.lens_unit = 'MILLIMETERS' # MILLIMETERS, FOV cameraObj.data.ortho_scale = 25*scale @@ -77,6 +91,7 @@ def convertToMMDCamera(cameraObj, scale=1.0): cameraObj.lock_location = (True, False, True) cameraObj.lock_rotation = (True, True, True) cameraObj.lock_scale = (True, True, True) + MMDCamera.__focus_object_set(cameraObj, empty) MMDCamera.addDrivers(cameraObj) empty.location = (0, 0, 10*scale) @@ -105,7 +120,7 @@ def newMMDCameraAnimation(cameraObj, cameraTarget=None, scale=1.0, min_distance= _target_override_func = None if cameraTarget is None: - _target_override_func = lambda camObj: camObj.data.dof_object or camObj + _target_override_func = lambda camObj: MMDCamera.__focus_object_get(camObj) or camObj action_name = mmd_cam_root.name parent_action = bpy.data.actions.new(name=action_name) diff --git a/extern_tools/mmd_tools_local/core/material.py b/extern_tools/mmd_tools_local/core/material.py index f3bf2a17..439f3ed9 100644 --- a/extern_tools/mmd_tools_local/core/material.py +++ b/extern_tools/mmd_tools_local/core/material.py @@ -109,7 +109,11 @@ def _load_image(self, filepath): img = bpy.data.images.new(os.path.basename(filepath), 1, 1) img.source = 'FILE' img.filepath = filepath - img.use_alpha = (img.depth == 32 and img.file_format != 'BMP') + use_alpha = (img.depth == 32 and img.file_format != 'BMP') + if hasattr(img, 'use_alpha'): + img.use_alpha = use_alpha + elif not use_alpha: + img.alpha_mode = 'NONE' return img def __load_texture(self, filepath): @@ -401,22 +405,27 @@ def use_sphere_texture(self, use_sphere, obj=None): def create_sphere_texture(self, filepath, obj=None): texture = self.__create_texture_node('mmd_sphere_tex', filepath, (-2, -2)) - sphere_texture_type = int(self.material.mmd_material.sphere_texture_type) - texture.color_space = 'NONE' if sphere_texture_type == 2 else 'COLOR' self.update_sphere_texture_type(obj) return _DummyTextureSlot(texture.image) def update_sphere_texture_type(self, obj=None): sphere_texture_type = int(self.material.mmd_material.sphere_texture_type) + is_sph_add = (sphere_texture_type == 2) + if sphere_texture_type not in (1, 2, 3): self.__update_shader_input('Sphere Tex Fac', 0) else: self.__update_shader_input('Sphere Tex Fac', 1) - self.__update_shader_input('Sphere Mul/Add', sphere_texture_type == 2) - self.__update_shader_input('Sphere Tex', (0, 0, 0, 1) if sphere_texture_type == 2 else (1, 1, 1, 1)) + self.__update_shader_input('Sphere Mul/Add', is_sph_add) + self.__update_shader_input('Sphere Tex', (0, 0, 0, 1) if is_sph_add else (1, 1, 1, 1)) texture = self.__get_texture_node('mmd_sphere_tex') if texture: + if hasattr(texture, 'color_space'): + texture.color_space = 'NONE' if is_sph_add else 'COLOR' + elif hasattr(texture.image, 'colorspace_settings'): + texture.image.colorspace_settings.name = 'Linear' if is_sph_add else 'sRGB' + mat = self.material nodes, links = mat.node_tree.nodes, mat.node_tree.links if sphere_texture_type == 3: @@ -526,13 +535,17 @@ def update_is_double_sided(self): mmd_mat = mat.mmd_material if hasattr(mat, 'game_settings'): mat.game_settings.use_backface_culling = not mmd_mat.is_double_sided + elif hasattr(mat, 'use_backface_culling'): + mat.use_backface_culling = not mmd_mat.is_double_sided self.__update_shader_input('Double Sided', mmd_mat.is_double_sided) def update_self_shadow_map(self): mat = self.material mmd_mat = mat.mmd_material cast_shadows = mmd_mat.enabled_self_shadow_map if mmd_mat.alpha > 1e-3 else False - if hasattr(mat, 'transparent_shadow_method'): + if hasattr(mat, 'shadow_method'): + mat.shadow_method = 'HASHED' if cast_shadows else 'NONE' + if hasattr(mat, 'transparent_shadow_method'): #XXX mat.transparent_shadow_method = 'HASHED' if cast_shadows else 'NONE' def update_self_shadow(self): diff --git a/extern_tools/mmd_tools_local/core/pmx/exporter.py b/extern_tools/mmd_tools_local/core/pmx/exporter.py index 3488c77d..77b73bf6 100644 --- a/extern_tools/mmd_tools_local/core/pmx/exporter.py +++ b/extern_tools/mmd_tools_local/core/pmx/exporter.py @@ -33,7 +33,7 @@ def __init__(self, co, groups, offsets, edge_scale, vertex_order, uv_offsets): self.index = None self.uv = None self.normal = None - self.sdef_data = None # (C, R0, R1) + self.sdef_data = [] # (C, R0, R1) self.add_uvs = [None]*4 # UV1~UV4 class _Face: @@ -43,16 +43,12 @@ def __init__(self, vertices): self.vertices = vertices class _Mesh: - def __init__(self, mesh_data, material_faces, shape_key_names, vertex_group_names, materials): - self.mesh_data = mesh_data + def __init__(self, material_faces, shape_key_names, vertex_group_names, material_names): self.material_faces = material_faces # dict of {material_index => [face1, face2, ....]} self.shape_key_names = shape_key_names self.vertex_group_names = vertex_group_names - self.materials = materials + self.material_names = material_names - def __del__(self): - logging.debug('remove mesh data: %s', str(self.mesh_data)) - bpy.data.meshes.remove(self.mesh_data) class _DefaultMaterial: def __init__(self): @@ -116,7 +112,7 @@ def __exportMeshes(self, meshes, bone_map): mat_map = OrderedDict() for mesh in meshes: for index, mat_faces in sorted(mesh.material_faces.items(), key=lambda x: x[0]): - name = mesh.materials[index].name + name = mesh.material_names[index] if name not in mat_map: mat_map[name] = [] mat_map[name].append((mat_faces, mesh.vertex_group_names)) @@ -993,22 +989,31 @@ def __doLoadMeshData(self, meshObj, bone_map): if bpy.app.version < (2, 80, 0): _to_mesh = lambda obj: obj.to_mesh(bpy.context.scene, apply_modifiers=True, settings='PREVIEW', calc_tessface=False, calc_undeformed=False) + _to_mesh_clear = lambda obj, mesh: bpy.data.meshes.remove(mesh) + elif hasattr(bpy.context, 'depsgraph'): #XXX + def _to_mesh(obj): + bpy.context.view_layer.update() + return obj.to_mesh(bpy.context.depsgraph, apply_modifiers=True, calc_undeformed=False) + _to_mesh_clear = lambda obj, mesh: bpy.data.meshes.remove(mesh) else: - _to_mesh = lambda obj: obj.to_mesh(bpy.context.depsgraph, apply_modifiers=True, calc_undeformed=False) + def _to_mesh(obj): + bpy.context.view_layer.update() + return obj.evaluated_get(bpy.context.evaluated_depsgraph_get()).to_mesh() + _to_mesh_clear = lambda obj, mesh: obj.to_mesh_clear() base_mesh = _to_mesh(meshObj) loop_normals = self.__triangulate(base_mesh, self.__get_normals(base_mesh, normal_matrix)) base_mesh.transform(pmx_matrix) - def _get_weight(vertex_group, vertex, default_weight): + def _get_weight(vertex_group_index, vertex, default_weight): for i in vertex.groups: - if i.group == vertex_group.index: - return vertex_group.weight(vertex.index) + if i.group == vertex_group_index: + return i.weight return default_weight get_edge_scale = None if vg_edge_scale: - get_edge_scale = lambda x: _get_weight(vg_edge_scale, x, 1) + get_edge_scale = lambda x: _get_weight(vg_edge_scale.index, x, 1) else: get_edge_scale = lambda x: 1 @@ -1017,7 +1022,7 @@ def _get_weight(vertex_group, vertex, default_weight): mesh_id = self.__vertex_order_map.setdefault('mesh_id', 0) self.__vertex_order_map['mesh_id'] += 1 if vg_vertex_order and self.__vertex_order_map['method'] == 'CUSTOM': - get_vertex_order = lambda x: (mesh_id, _get_weight(vg_vertex_order, x, 2), x.index) + get_vertex_order = lambda x: (mesh_id, _get_weight(vg_vertex_order.index, x, 2), x.index) else: get_vertex_order = lambda x: (mesh_id, x.index) else: @@ -1036,7 +1041,7 @@ def get_uv_offsets(v): base_vertices = {} for v in base_mesh.vertices: base_vertices[v.index] = [_Vertex( - v.co, + v.co.copy(), [(x.group, x.weight) for x in v.groups if x.weight > 0 and x.group in vertex_group_names], {}, get_edge_scale(v), @@ -1044,6 +1049,64 @@ def get_uv_offsets(v): get_uv_offsets(v), )] + # load face data + class _DummyUV: + uv1 = uv2 = uv3 = mathutils.Vector((0, 1)) + def __init__(self, uvs): + self.uv1, self.uv2, self.uv3 = (v.uv.copy() for v in uvs) + + _UVWrapper = lambda x: (_DummyUV(x[i:i+3]) for i in range(0, len(x), 3)) + + material_faces = {} + uv_data = base_mesh.uv_layers.active + if uv_data: + uv_data = _UVWrapper(uv_data.data) + else: + uv_data = iter(lambda: _DummyUV, None) + face_seq = [] + for face, uv in zip(base_mesh.polygons, uv_data): + if len(face.vertices) != 3: + raise Exception + idx = face.index * 3 + n1, n2, n3 = loop_normals[idx:idx+3] + v1 = self.__convertFaceUVToVertexUV(face.vertices[0], uv.uv1, n1, base_vertices) + v2 = self.__convertFaceUVToVertexUV(face.vertices[1], uv.uv2, n2, base_vertices) + v3 = self.__convertFaceUVToVertexUV(face.vertices[2], uv.uv3, n3, base_vertices) + + t = _Face([v1, v2, v3]) + face_seq.append(t) + if face.material_index not in material_faces: + material_faces[face.material_index] = [] + material_faces[face.material_index].append(t) + + _mat_name = lambda x: x.name if x else self.__getDefaultMaterial().name + material_names = tuple(_mat_name(i) for i in base_mesh.materials) + material_names += tuple(_mat_name(None) for i in range(1+max(material_faces.keys())-len(material_names))) + + # export add UV + bl_add_uvs = [i for i in base_mesh.uv_layers[1:] if not i.name.startswith('_')] + self.__add_uv_count = max(self.__add_uv_count, len(bl_add_uvs)) + for uv_n, uv_tex in enumerate(bl_add_uvs): + if uv_n > 3: + logging.warning(' * extra addUV%d+ are not supported', uv_n+1) + break + uv_data = _UVWrapper(uv_tex.data) + zw_data = base_mesh.uv_layers.get('_'+uv_tex.name, None) + logging.info(' # exporting addUV%d: %s [zw: %s]', uv_n+1, uv_tex.name, zw_data) + if zw_data: + zw_data = _UVWrapper(zw_data.data) + else: + zw_data = iter(lambda: _DummyUV, None) + rip_vertices_map = {} + for f, face, uv, zw in zip(face_seq, base_mesh.polygons, uv_data, zw_data): + vertices = [base_vertices[x] for x in face.vertices] + rip_vertices = [rip_vertices_map.setdefault(x, [x]) for x in f.vertices] + f.vertices[0] = self.__convertAddUV(f.vertices[0], uv.uv1, zw.uv1, uv_n, vertices[0], rip_vertices[0]) + f.vertices[1] = self.__convertAddUV(f.vertices[1], uv.uv2, zw.uv2, uv_n, vertices[1], rip_vertices[1]) + f.vertices[2] = self.__convertAddUV(f.vertices[2], uv.uv3, zw.uv3, uv_n, vertices[2], rip_vertices[2]) + + _to_mesh_clear(meshObj, base_mesh) + # calculate offsets shape_key_list = [] if meshObj.data.shape_keys: @@ -1067,7 +1130,7 @@ def get_uv_offsets(v): mesh = _to_mesh(meshObj) mesh.transform(pmx_matrix) kb.mute = kb_mute - if len(mesh.vertices) != len(base_mesh.vertices): + if len(mesh.vertices) != len(base_vertices): logging.warning(' * Error! vertex count mismatch!') continue if shape_key_name in {'mmd_sdef_c', 'mmd_sdef_r0', 'mmd_sdef_r1'}: @@ -1080,7 +1143,7 @@ def get_uv_offsets(v): c_co = v.co if (c_co - base_co).length < 0.001: continue - base.sdef_data = [tuple(c_co), base_co, base_co] + base.sdef_data[:] = tuple(c_co), base_co, base_co sdef_counts += 1 logging.info(' - Restored %d SDEF vertices', sdef_counts) elif sdef_counts > 0: @@ -1098,78 +1161,17 @@ def get_uv_offsets(v): if offset.length < 0.001: continue base.offsets[shape_key_name] = offset - bpy.data.meshes.remove(mesh) - - # load face data - class _DummyUV: - uv1 = uv2 = uv3 = mathutils.Vector((0, 1)) - def __init__(self, uvs): - self.uv1, self.uv2, self.uv3 = (v.uv for v in uvs) - - _UVWrapper = lambda x: (_DummyUV(x[i:i+3]) for i in range(0, len(x), 3)) - - materials = {} - uv_data = base_mesh.uv_layers.active - if uv_data: - uv_data = _UVWrapper(uv_data.data) - else: - uv_data = iter(lambda: _DummyUV, None) - face_seq = [] - for face, uv in zip(base_mesh.polygons, uv_data): - if len(face.vertices) != 3: - raise Exception - idx = face.index * 3 - n1, n2, n3 = loop_normals[idx:idx+3] - v1 = self.__convertFaceUVToVertexUV(face.vertices[0], uv.uv1, n1, base_vertices) - v2 = self.__convertFaceUVToVertexUV(face.vertices[1], uv.uv2, n2, base_vertices) - v3 = self.__convertFaceUVToVertexUV(face.vertices[2], uv.uv3, n3, base_vertices) - - t = _Face([v1, v2, v3]) - face_seq.append(t) - if face.material_index not in materials: - materials[face.material_index] = [] - materials[face.material_index].append(t) - - # assign default material - if len(base_mesh.materials) < len(materials): - base_mesh.materials.append(self.__getDefaultMaterial()) - else: - for i, m in enumerate(base_mesh.materials): - if m is None: - base_mesh.materials[i] = self.__getDefaultMaterial() - - # export add UV - bl_add_uvs = [i for i in base_mesh.uv_layers[1:] if not i.name.startswith('_')] - self.__add_uv_count = max(self.__add_uv_count, len(bl_add_uvs)) - for uv_n, uv_tex in enumerate(bl_add_uvs): - if uv_n > 3: - logging.warning(' * extra addUV%d+ are not supported', uv_n+1) - break - uv_data = _UVWrapper(uv_tex.data) - zw_data = base_mesh.uv_layers.get('_'+uv_tex.name, None) - logging.info(' # exporting addUV%d: %s [zw: %s]', uv_n+1, uv_tex.name, zw_data) - if zw_data: - zw_data = _UVWrapper(zw_data.data) - else: - zw_data = iter(lambda: _DummyUV, None) - rip_vertices_map = {} - for f, face, uv, zw in zip(face_seq, base_mesh.polygons, uv_data, zw_data): - vertices = [base_vertices[x] for x in face.vertices] - rip_vertices = [rip_vertices_map.setdefault(x, [x]) for x in f.vertices] - f.vertices[0] = self.__convertAddUV(f.vertices[0], uv.uv1, zw.uv1, uv_n, vertices[0], rip_vertices[0]) - f.vertices[1] = self.__convertAddUV(f.vertices[1], uv.uv2, zw.uv2, uv_n, vertices[1], rip_vertices[1]) - f.vertices[2] = self.__convertAddUV(f.vertices[2], uv.uv3, zw.uv3, uv_n, vertices[2], rip_vertices[2]) + _to_mesh_clear(meshObj, mesh) if not pmx_matrix.is_negative: # pmx.load/pmx.save reverse face vertices by default for f in face_seq: f.vertices.reverse() return _Mesh( - base_mesh, - materials, + material_faces, shape_key_names, vertex_group_names, - base_mesh.materials) + material_names) def __loadMeshData(self, meshObj, bone_map): show_only_shape_key = meshObj.show_only_shape_key diff --git a/extern_tools/mmd_tools_local/operators/view.py b/extern_tools/mmd_tools_local/operators/view.py index 6c362a80..378b5dd1 100644 --- a/extern_tools/mmd_tools_local/operators/view.py +++ b/extern_tools/mmd_tools_local/operators/view.py @@ -72,7 +72,7 @@ def execute(self, context): #TODO shading.light = 'FLAT' if shading_mode == 'SHADELESS' else 'STUDIO' shading.color_type = 'TEXTURE' if shading_mode else 'MATERIAL' shading.show_object_outline = False - shading.show_backface_culling = False + shading.show_backface_culling = True return {'FINISHED'} diff --git a/extern_tools/mmd_tools_local/properties/camera.py b/extern_tools/mmd_tools_local/properties/camera.py index 106a3bcd..bec39639 100644 --- a/extern_tools/mmd_tools_local/properties/camera.py +++ b/extern_tools/mmd_tools_local/properties/camera.py @@ -14,7 +14,10 @@ def __update_depsgraph(cam, data_prop_name): pass else: def __update_depsgraph(cam, data_prop_name): - cam_dep = bpy.context.depsgraph.objects.get(cam.name, None) + if hasattr(bpy.context, 'depsgraph'): #XXX + cam_dep = bpy.context.depsgraph.objects.get(cam.name, None) + else: + cam_dep = cam.evaluated_get(bpy.context.evaluated_depsgraph_get()) if cam_dep: setattr(cam_dep.data, data_prop_name, getattr(cam.data, data_prop_name)) diff --git a/resources/icons/supporters/Coolcakes.png b/resources/icons/supporters/Coolcakes.png new file mode 100644 index 00000000..51220c8b Binary files /dev/null and b/resources/icons/supporters/Coolcakes.png differ diff --git a/resources/icons/supporters/Hammey.png b/resources/icons/supporters/Hammey.png new file mode 100644 index 00000000..b5d79cd3 Binary files /dev/null and b/resources/icons/supporters/Hammey.png differ diff --git a/resources/icons/supporters/Nacho Replay.png b/resources/icons/supporters/Nacho Replay.png new file mode 100644 index 00000000..990f3b2c Binary files /dev/null and b/resources/icons/supporters/Nacho Replay.png differ diff --git a/resources/supporters.json b/resources/supporters.json index d572917b..16de2a49 100644 --- a/resources/supporters.json +++ b/resources/supporters.json @@ -368,7 +368,8 @@ "startdate": "2019-03-25" },{ "displayname": "honnmaguro", - "startdate": "2019-04-05" + "startdate": "2019-04-05", + "tier": 1 },{ "displayname": "Subcom", "startdate": "2019-04-23", @@ -378,6 +379,18 @@ },{ "displayname": "Nyako_Sai", "startdate": "2019-05-02" + },{ + "displayname": "Coolcakes", + "startdate": "2019-05-06" + },{ + "displayname": "Nacho Replay", + "startdate": "2019-05-21", + "description": "I put 200 hrs in VrChat in about 2 weeks; I have no regrets", + "website": "https://github.com/NachoReplay", + "tier": 1 + },{ + "displayname": "Hammey", + "startdate": "2019-05-25" } ] } diff --git a/tools/armature.py b/tools/armature.py index f21a7136..453d2f62 100644 --- a/tools/armature.py +++ b/tools/armature.py @@ -432,7 +432,7 @@ def execute(self, context): # #shader.transmission_roughness = 0 # Make materials exportable in Blender 2.8 - Common.add_principle_shader() + Common.add_principled_shader() for area in context.screen.areas: # iterate through areas in current screen if area.type == 'VIEW_3D': diff --git a/tools/armature_bones.py b/tools/armature_bones.py index 3ff8183c..dbea1059 100644 --- a/tools/armature_bones.py +++ b/tools/armature_bones.py @@ -267,6 +267,13 @@ # Fix Detroit model (['Bip_UpperArmBase_L', 'Bip_ArmpitRingBase_L'], 'Bip_UpperArm_R', 'Bip_UpperArm_L'), (['Bip_UpperArmBase_L', 'Bip_ArmpitRingBase_L'], 'Bip_UpperArm_R_001', 'Bip_UpperArm_R'), + + # Fix fox inverted spines model + (['Neck03', 'Bum_L', 'Upperleg01_L', 'Upperleg02_L'], 'Spine01', 'Spine5'), + (['Neck03', 'Bum_L', 'Upperleg01_L', 'Upperleg02_L'], 'Spine02', 'Spine4'), + (['Neck03', 'Bum_L', 'Upperleg01_L', 'Upperleg02_L'], 'Spine03', 'Spine3'), + (['Neck03', 'Bum_L', 'Upperleg01_L', 'Upperleg02_L'], 'Spine04', 'Spine2'), + (['Neck03', 'Bum_L', 'Upperleg01_L', 'Upperleg02_L'], 'Spine05', 'Spine1'), ] bone_finger_list = [ 'Thumb0_', @@ -333,6 +340,8 @@ 'HipMaster_01', 'J_Bip_C_Hips', 'J_Hip', + 'Pelvis_L', + 'Pelvis_R', ] bone_rename['Spine'] = [ # This is a list of all the spine and chest bones. They will be correctly fixed 'Spine', # First entry! @@ -440,6 +449,10 @@ 'J_Spine2', 'J_Spine3', + 'Spine_Jnt_01', + 'Spine_Jnt_02', + 'Spine_Jnt_03', + 'Chest1', 'Chest2', 'Chest3', @@ -563,6 +576,7 @@ 'J_\L_Shoulder', 'Clavicle\L', 'Bip_Clavicle_\L', + '\L_Clavic', ] bone_rename['\Left arm'] = [ '\Left_Arm', @@ -605,6 +619,8 @@ 'J_\L_Elbow', 'Arm_1_\L', 'Bip_UpperArm_\L', + 'Upperarm01_\L', + '\L_Shldr', ] bone_rename['Left arm'] = [ '+_Leisure_Elder_Supplement', @@ -650,6 +666,7 @@ 'J_\L_ForeArm', 'Arm_2_\L', 'Bip_Forearm_\L', + 'Lowerarm01_\L', ] bone_rename['\Left wrist'] = [ '\Left_Wrist', @@ -669,7 +686,6 @@ '\LHandN', '\LeftHand', '\Left_Hand', - 'Finger3_1_\L', '\L_Hand', '\L_Hand_1', '\LFingerBaseN', @@ -680,6 +696,7 @@ 'Hand\LT_01', 'J_Bip_\L_Hand', 'J_\L_Wrist', + '\L_Wrist', ] bone_rename['Left wrist'] = [ 'Left_Hand_003', @@ -735,6 +752,8 @@ 'Leg\L', 'Leg_1_\L', 'Bip_Thigh_\L', + 'Groin_\L', + 'Upperleg01_\L', ] bone_rename['\Left knee'] = [ '\Left_Knee', @@ -778,6 +797,7 @@ 'Knee\L', 'Leg_2_\L', 'Bip_Leg_\L', + 'Lowerleg01_\L', ] bone_rename['\Left ankle'] = [ '\Left_Ankle', @@ -850,6 +870,7 @@ 'J_Bip_\L_ToeBase', 'J_\L_Toe', 'Bip_Toe_\L', + 'Toe_Boot_\L', ] bone_rename['Eye_\L'] = [ '\Left_Eye', @@ -932,6 +953,8 @@ 'NeckW', 'NeckW2', 'Neck01', + 'Neck02', + 'Neck03', 'J_Neck2', ] bone_reweight['Head'] = [ @@ -1057,6 +1080,7 @@ 'Bip_ForearmHelper_\L', 'Bip_ElbowHelper_\L', 'Hlp_UpperArm_\L', + 'Upperarm02_\L', ] bone_reweight['Left arm'] = [ # This has apparently no side in the name 'エプロンArm', @@ -1186,6 +1210,7 @@ 'Bip_WristTwistS_\L', 'Hlp_Wrist_\L', 'Hlp_LowerArm_\L', + 'Lowerarm02_\L', ] bone_reweight['\Left wrist'] = [ # 'Sleeve3_\L', @@ -1230,6 +1255,10 @@ 'Arm_\Left_Finger_5_Base', '\Left_Finger_Index_Metacarpal', '\Left_Finger_Ring_Metacarpal', + 'Metacarpal1_\L', + 'Metacarpal2_\L', + 'Metacarpal3_\L', + 'Metacarpal4_\L', ] bone_reweight['Left wrist'] = [ 'Left_Hand_002', @@ -1338,6 +1367,7 @@ 'Bip_KneeIn_\L', 'Bip_KneeOut_\L', 'Hlp_Hip_\L', + 'Upperleg02_\L', ] bone_reweight['\Left knee'] = [ 'KneeD_\L', @@ -1404,6 +1434,7 @@ 'Hlp_Foot_\L', 'KneeUpper_\L', 'KneeLower_\L', + 'Lowerleg02_\L', ] bone_reweight['\Left ankle'] = [ 'AnkleD_\L', diff --git a/tools/armature_custom.py b/tools/armature_custom.py index 19221d74..6a7db400 100644 --- a/tools/armature_custom.py +++ b/tools/armature_custom.py @@ -298,12 +298,13 @@ def merge_armatures(base_armature_name, merge_armature_name, mesh_only, mesh_nam bpy.context.scene.armature = base_armature_name armature = Common.get_armature(armature_name=base_armature_name) + # Clean up shape keys + Common.clean_shapekeys(mesh_base) + Common.clean_shapekeys(mesh_merge) + # Join the meshes mesh_merged = Common.join_meshes(armature_name=base_armature_name, apply_transformations=False) - # Clean up shape keys - Common.clean_shapekeys(mesh_merged) - # Go into edit mode Common.unselect_all() Common.set_active(armature) @@ -330,7 +331,7 @@ def merge_armatures(base_armature_name, merge_armature_name, mesh_only, mesh_nam for bone in armature.data.edit_bones: if bone.name.endswith('.merge'): new_bone = armature.data.edit_bones.get(bone.name.replace('.merge', '')) - if new_bone \ + if new_bone and new_bone.name not in bones_to_merge \ and round(bone.head[0], 4) == round(new_bone.head[0], 4)\ and round(bone.head[1], 4) == round(new_bone.head[1], 4)\ and round(bone.head[2], 4) == round(new_bone.head[2], 4): @@ -339,10 +340,11 @@ def merge_armatures(base_armature_name, merge_armature_name, mesh_only, mesh_nam # Remove all unused bones, constraints and vertex groups Common.set_default_stage() - Common.delete_bone_constraints(armature_name=base_armature_name) - Common.remove_unused_vertex_groups(ignore_main_bones=True) - Common.delete_zero_weight(armature_name=base_armature_name, ignore=root_name) - Common.set_default_stage() + if not mesh_only: + Common.delete_bone_constraints(armature_name=base_armature_name) + Common.remove_unused_vertex_groups(ignore_main_bones=True) + Common.delete_zero_weight(armature_name=base_armature_name, ignore=root_name) + Common.set_default_stage() # Merge bones into existing bones Common.set_active(mesh_merged) @@ -355,9 +357,13 @@ def merge_armatures(base_armature_name, merge_armature_name, mesh_only, mesh_nam vg_base = mesh_merged.vertex_groups.get(bone_base) vg_merge = mesh_merged.vertex_groups.get(bone_merge) - if vg_base and vg_merge: - Common.mix_weights(mesh_merged, bone_merge, bone_base) - to_delete.append(bone_merge) + if not vg_base: + mesh_merged.vertex_groups.new(name=bone_base) + if not vg_merge: + mesh_merged.vertex_groups.new(name=bone_merge) + + Common.mix_weights(mesh_merged, bone_merge, bone_base) + to_delete.append(bone_merge) Common.set_active(armature) Common.switch('EDIT') @@ -405,9 +411,10 @@ def merge_armatures(base_armature_name, merge_armature_name, mesh_only, mesh_nam # Remove all unused bones, constraints and vertex groups Common.set_default_stage() - Common.remove_unused_vertex_groups() - Common.delete_zero_weight(armature_name=base_armature_name, ignore=root_name) - Common.set_default_stage() + if not mesh_only: + Common.remove_unused_vertex_groups() + Common.delete_zero_weight(armature_name=base_armature_name, ignore=root_name) + Common.set_default_stage() # Fix armature name Common.fix_armature_names(armature_name=base_armature_name) diff --git a/tools/armature_manual.py b/tools/armature_manual.py index b52d7764..03ed0b2e 100644 --- a/tools/armature_manual.py +++ b/tools/armature_manual.py @@ -250,6 +250,7 @@ def execute(self, context): saved_data = Common.SavedData() armature = Common.get_armature() + Common.set_active(armature) scales = {} # Find out how much each bone is scaled diff --git a/tools/common.py b/tools/common.py index f345de02..9181d14e 100644 --- a/tools/common.py +++ b/tools/common.py @@ -382,7 +382,7 @@ def get_meshes(self, context): def get_top_meshes(self, context): choices = [] - for mesh in get_meshes_objects(mode=1): + for mesh in get_meshes_objects(mode=1, check=False): choices.append((mesh.name, mesh.name, mesh.name)) bpy.types.Object.Enum = sorted(choices, key=lambda x: tuple(x[0].lower())) @@ -392,7 +392,7 @@ def get_top_meshes(self, context): def get_all_meshes(self, context): choices = [] - for mesh in get_meshes_objects(mode=2): + for mesh in get_meshes_objects(mode=2, check=False): choices.append((mesh.name, mesh.name, mesh.name)) bpy.types.Object.Enum = sorted(choices, key=lambda x: tuple(x[0].lower())) @@ -696,7 +696,7 @@ def get_meshes_objects(armature_name=None, mode=0, check=True): to_remove = [] for mesh in meshes: selected = is_selected(mesh) - print(mesh.name, mesh.users) + # print(mesh.name, mesh.users) set_active(mesh) if not get_active(): @@ -989,6 +989,7 @@ def prepare_separation(mesh): def clean_shapekeys(mesh): + # Remove empty shapekeys if has_shapekeys(mesh): for kb in mesh.data.shape_keys.key_blocks: if can_remove_shapekey(kb): @@ -1738,7 +1739,10 @@ def unify_materials(): return {'FINISHED'} -def add_principle_shader(): +def add_principled_shader(): + principled_shader_pos = (501, -500) + output_shader_pos = (801, -500) + for mesh in get_meshes_objects(): for mat_slot in mesh.material_slots: if mat_slot.material and mat_slot.material.node_tree: @@ -1746,23 +1750,28 @@ def add_principle_shader(): node_image = None node_image_count = 0 + # Check if and where the new nodes should be added for node in nodes: - if node.type == 'BSDF_PRINCIPLED' and node.location == (501, -500): + # Cancel if the cats nodes are already found + if node.type == 'BSDF_PRINCIPLED' and node.location == principled_shader_pos: node_image = None break - if node.type == 'OUTPUT_MATERIAL' and node.location == (801, -500): + if node.type == 'OUTPUT_MATERIAL' and node.location == output_shader_pos: node_image = None break + # Skip if this node is not an image node if node.type != 'TEX_IMAGE': continue node_image_count += 1 + # If an mmd_texture is found, link it to the principled shader later if node.name == 'mmd_base_tex' or node.label == 'MainTexture': node_image = node node_image_count = 0 break + # This is an image node, so link it to the principled shader later node_image = node if not node_image or node_image_count > 1: @@ -1770,7 +1779,7 @@ def add_principle_shader(): # Create Principled BSDF node node_prinipled = nodes.new(type='ShaderNodeBsdfPrincipled') - node_prinipled.location = 501, -500 + node_prinipled.location = principled_shader_pos node_prinipled.label = 'Cats Emission' node_prinipled.inputs['Specular'].default_value = 0 node_prinipled.inputs['Roughness'].default_value = 0 @@ -1780,7 +1789,7 @@ def add_principle_shader(): # Create Output node for correct image exports node_output = nodes.new(type='ShaderNodeOutputMaterial') - node_output.location = 801, -500 + node_output.location = output_shader_pos node_output.label = 'Cats Export' # Link nodes together diff --git a/tools/credits.py b/tools/credits.py index 7fba4bcd..cb416e45 100644 --- a/tools/credits.py +++ b/tools/credits.py @@ -27,7 +27,6 @@ import webbrowser from .register import register_wrap - @register_wrap class ForumButton(bpy.types.Operator): bl_idname = 'cats_credits.forum' diff --git a/tools/importer.py b/tools/importer.py index 09ece416..319aa372 100644 --- a/tools/importer.py +++ b/tools/importer.py @@ -172,19 +172,23 @@ def execute(self, context): except AttributeError: bpy.ops.cats_importer.install_vrm('INVOKE_DEFAULT') - # Create list of armatures that got added during import + # Create list of armatures that got added during import, select them in cats and fix their bone orientations if necessary arm_added_during_import = [obj for obj in bpy.data.objects if obj.type == 'ARMATURE' and obj not in pre_import_objects] for armature in arm_added_during_import: print('Added: ', armature.name) - # Select the new armature in cats bpy.context.scene.armature = armature.name self.fix_bone_orientations(armature) return {'FINISHED'} def fix_bone_orientations(self, armature): + Common.unselect_all() Common.set_active(armature) Common.switch('EDIT') + + fix_bones = True + + # Check if all the bones are pointing in the same direction for bone in armature.data.edit_bones: equal_axis_count = 0 if bone.head[0] == bone.tail[0]: @@ -196,9 +200,11 @@ def fix_bone_orientations(self, armature): # If the bone points to more than one direction, don't fix the armatures bones if equal_axis_count < 2: - return + fix_bones = False - Common.fix_bone_orientations(armature) + if fix_bones: + Common.fix_bone_orientations(armature) + Common.switch('OBJECT') @register_wrap diff --git a/tools/material.py b/tools/material.py index 5e916c6d..03ff5e8a 100644 --- a/tools/material.py +++ b/tools/material.py @@ -48,6 +48,8 @@ def poll(cls, context): return len(Common.get_meshes_objects(check=False)) > 0 def execute(self, context): + # Common.unify_materials() + # return {'FINISHED'} if not Common.version_2_79_or_older(): self.report({'ERROR'}, 'This function is not yet compatible with Blender 2.8!') return {'CANCELLED'}