diff --git a/README.md b/README.md index f1a7ffd2..88a81884 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Cats Blender Plugin (0.13.1) +# Cats Blender Plugin (0.13.2) 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 @@ -332,6 +332,21 @@ It checks for a new version automatically once every day. ## Changelog +#### 0.13.2 +- **Importer**: + - Now selects the imported armature in Cats + - Added bone orientation fix after import if all bones point in the same direction +- **Fix Model**: + - Changed clipping planes to 0.01 and 150 + - This prevents rendering inaccuracies (thanks Rokk!) + - Fix Model also no longer resets the visibility of objects + - Added option to not connect the bones to their respective child +- **Eye Tracking**: + - Added random vertex movement back in + - Instead of the exporter, Fix Model deleted empty shapekeys now (whoops) +- **General** + - Disabled backface culling in mmd_tools again + #### 0.13.1 - **Fix Model**: - Added option to not join the meshes diff --git a/__init__.py b/__init__.py index 6812aa27..76901172 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, 1), # 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, 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! 'blender': (2, 80, 0), 'wiki_url': 'https://github.com/michaeldegroot/cats-blender-plugin', 'tracker_url': 'https://github.com/michaeldegroot/cats-blender-plugin/issues', diff --git a/extentions.py b/extentions.py index 51f7d1d7..6bb427e2 100644 --- a/extentions.py +++ b/extentions.py @@ -62,6 +62,13 @@ def register(): default=True ) + Scene.connect_bones = BoolProperty( + name='Connect Bones', + description="This connects all bones to their child bone if they have exactly one child bone.\n" + "This will not change how the bones function in any way, it just improves the aesthetic of the armature", + default=True + ) + Scene.use_google_only = BoolProperty( name='Use Old Translations (not recommended)', description="Ignores the internal dictionary and only uses the Google Translator for shape key translations." diff --git a/extern_tools/mmd_tools_local/operators/view.py b/extern_tools/mmd_tools_local/operators/view.py index 22025e11..6c362a80 100644 --- a/extern_tools/mmd_tools_local/operators/view.py +++ b/extern_tools/mmd_tools_local/operators/view.py @@ -59,7 +59,7 @@ def execute(self, context): shade, context.scene.game_settings.material_mode = ('TEXTURED', 'GLSL') if shading_mode else ('SOLID', 'MULTITEXTURE') for space in self._get_view3d_spaces(context): space.viewport_shade = shade - space.show_backface_culling = True + space.show_backface_culling = False return {'FINISHED'} else: def execute(self, context): #TODO @@ -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 = True + shading.show_backface_culling = False return {'FINISHED'} diff --git a/resources/icons/supporters/Nyako_Sai.png b/resources/icons/supporters/Nyako_Sai.png new file mode 100644 index 00000000..97ff0234 Binary files /dev/null and b/resources/icons/supporters/Nyako_Sai.png differ diff --git a/resources/supporters.json b/resources/supporters.json index 4649a86b..d572917b 100644 --- a/resources/supporters.json +++ b/resources/supporters.json @@ -375,6 +375,9 @@ "description": "creating a comfy life for people who enjoy social VR", "website": "www.subcomvr.com", "tier": 1 + },{ + "displayname": "Nyako_Sai", + "startdate": "2019-05-02" } ] } diff --git a/tools/armature.py b/tools/armature.py index 92ce828c..f21a7136 100644 --- a/tools/armature.py +++ b/tools/armature.py @@ -83,6 +83,8 @@ def execute(self, context): # # return {'FINISHED'} + saved_data = Common.SavedData() + is_vrm = False if len(Common.get_meshes_objects()) == 0: for mesh in Common.get_meshes_objects(mode=2): @@ -229,7 +231,8 @@ def execute(self, context): armature = Common.set_default_stage() if bpy.context.space_data: - bpy.context.space_data.clip_start = 0.001 + bpy.context.space_data.clip_start = 0.01 + bpy.context.space_data.clip_end = 150 if version_2_79_or_older(): # Set better bone view @@ -1181,9 +1184,8 @@ def add_eye_children(eye_bone, parent_name): Common.delete_zero_weight() # Connect all bones with their children if they have exactly one - for bone in armature.data.edit_bones: - if len(bone.children) == 1 and bone.name not in ['LeftEye', 'RightEye', 'Head', 'Hips']: - bone.tail = bone.children[0].head + if context.scene.connect_bones: + Common.fix_bone_orientations(armature) # # This is code for testing # print('LOOKING FOR BONES!') @@ -1222,14 +1224,18 @@ def add_eye_children(eye_bone, parent_name): if not hierarchy_check_hips['result']: self.report({'ERROR'}, hierarchy_check_hips['message']) + saved_data.load() return {'FINISHED'} if fixed_uv_coords: + saved_data.load() Common.show_error(6.2, ['The model was successfully fixed, but there were ' + str(fixed_uv_coords) + ' faulty UV coordinates.', 'This could result in broken textures and you might have to fix them manually.', 'This issue is often caused by edits in PMX editor.']) return {'FINISHED'} + saved_data.load() + self.report({'INFO'}, 'Model successfully fixed.') return {'FINISHED'} @@ -1312,6 +1318,8 @@ def draw(self, context): row = col.row(align=True) row.prop(context.scene, 'join_meshes') row = col.row(align=True) + row.prop(context.scene, 'connect_bones') + row = col.row(align=True) row.prop(context.scene, 'combine_mats') row = col.row(align=True) row.prop(context.scene, 'remove_zero_weight') diff --git a/tools/common.py b/tools/common.py index 812a758b..f345de02 100644 --- a/tools/common.py +++ b/tools/common.py @@ -30,7 +30,6 @@ from math import degrees from mathutils import Vector -from threading import Thread from datetime import datetime from html.parser import HTMLParser from html.entities import name2codepoint @@ -1651,6 +1650,13 @@ def fix_zero_length_bones(armature, full_body_tracking, x_cord, y_cord, z_cord): switch(pre_mode) +def fix_bone_orientations(armature): + # Connect all bones with their children if they have exactly one + for bone in armature.data.edit_bones: + if len(bone.children) == 1 and bone.name not in ['LeftEye', 'RightEye', 'Head', 'Hips']: + bone.tail = bone.children[0].head + + def update_material_list(self=None, context=None): try: if hasattr(bpy.context.scene, 'smc_ob_data') and bpy.context.scene.smc_ob_data: diff --git a/tools/eyetracking.py b/tools/eyetracking.py index f92272ca..d132cb84 100644 --- a/tools/eyetracking.py +++ b/tools/eyetracking.py @@ -227,13 +227,13 @@ def execute(self, context): # Check for correct bone hierarchy is_correct = Armature.check_hierarchy(True, [['Hips', 'Spine', 'Chest', 'Neck', 'Head']]) - # if context.scene.disable_eye_movement: - # # print('Repair with mouth.') - # repair_shapekeys_mouth(mesh_name) - # # repair_shapekeys_mouth(mesh_name, context.scene.wink_left) # TODO - # else: - # # print('Repair normal "' + new_right_eye.name + '".') - # repair_shapekeys(mesh_name, new_right_eye.name) + if context.scene.disable_eye_movement: + # print('Repair with mouth.') + repair_shapekeys_mouth(mesh_name) + # repair_shapekeys_mouth(mesh_name, context.scene.wink_left) # TODO + else: + # print('Repair normal "' + new_right_eye.name + '".') + repair_shapekeys(mesh_name, new_right_eye.name) # deleted = [] # # deleted = checkshapekeys() @@ -364,111 +364,111 @@ def fix_eye_position(context, old_eye, new_eye, head, right_side): new_eye.tail[z_cord] = new_eye.head[z_cord] + 0.1 -# # Repair vrc shape keys -# def repair_shapekeys(mesh_name, vertex_group): -# # This is done to fix a very weird bug where the mouth stays open sometimes -# Common.set_default_stage() -# mesh = Common.get_objects()[mesh_name] -# Common.unselect_all() -# Common.set_active(mesh) -# Common.switch('EDIT') -# Common.switch('OBJECT') -# -# bm = bmesh.new() -# bm.from_mesh(mesh.data) -# bm.verts.ensure_lookup_table() -# -# # Get a vertex from the eye vertex group # TODO https://i.imgur.com/tWi8lk6.png after many times resetting the eyes -# print('DEBUG: Group: ' + vertex_group) -# group = mesh.vertex_groups.get(vertex_group) -# if group is None: -# print('DEBUG: Group: ' + vertex_group + ' not found!') -# repair_shapekeys_mouth(mesh_name) -# return -# print('DEBUG: Group: ' + vertex_group + ' found!') -# -# vcoords = None -# gi = group.index -# for v in mesh.data.vertices: -# for g in v.groups: -# if g.group == gi: -# vcoords = v.co.xyz -# -# if not vcoords: -# return -# -# print('DEBUG: Repairing shapes!') -# # Move that vertex by a tiny amount -# moved = False -# i = 0 -# for key in bm.verts.layers.shape.keys(): -# if not key.startswith('vrc.'): -# continue -# print('DEBUG: Repairing shape: ' + key) -# value = bm.verts.layers.shape.get(key) -# for index, vert in enumerate(bm.verts): -# if vert.co.xyz == vcoords: -# if index < i: -# continue -# shapekey = vert -# shapekey_coords = Common.matmul(mesh.matrix_world, shapekey[value]) -# shapekey_coords[0] -= 0.00007 * randBoolNumber() -# shapekey_coords[1] -= 0.00007 * randBoolNumber() -# shapekey_coords[2] -= 0.00007 * randBoolNumber() -# shapekey[value] = Common.matmul(mesh.matrix_world.inverted(), shapekey_coords) -# print('DEBUG: Repaired shape: ' + key) -# i += 1 -# moved = True -# break -# -# bm.to_mesh(mesh.data) -# -# if not moved: -# print('Error: Shapekey repairing failed for some reason! Using random shapekey method now.') -# repair_shapekeys_mouth(mesh_name) -# -# -# def randBoolNumber(): -# if random() < 0.5: -# return -1 -# return 1 -# -# -# # Repair vrc shape keys with random vertex -# def repair_shapekeys_mouth(mesh_name): # TODO Add vertex repairing! -# # This is done to fix a very weird bug where the mouth stays open sometimes -# Common.set_default_stage() -# mesh = Common.get_objects()[mesh_name] -# Common.unselect_all() -# Common.set_active(mesh) -# Common.switch('EDIT') -# Common.switch('OBJECT') -# -# bm = bmesh.new() -# bm.from_mesh(mesh.data) -# bm.verts.ensure_lookup_table() -# -# # Move that vertex by a tiny amount -# moved = False -# for key in bm.verts.layers.shape.keys(): -# if not key.startswith('vrc'): -# continue -# value = bm.verts.layers.shape.get(key) -# for vert in bm.verts: -# shapekey = vert -# shapekey_coords = Common.matmul(mesh.matrix_world, shapekey[value]) -# shapekey_coords[0] -= 0.00007 -# shapekey_coords[1] -= 0.00007 -# shapekey_coords[2] -= 0.00007 -# shapekey[value] = Common.matmul(mesh.matrix_world.inverted(), shapekey_coords) -# print('TEST') -# moved = True -# break -# -# bm.to_mesh(mesh.data) -# -# if not moved: -# print('Error: Random shapekey repairing failed for some reason! Canceling!') +# Repair vrc shape keys +def repair_shapekeys(mesh_name, vertex_group): + # This is done to fix a very weird bug where the mouth stays open sometimes + Common.set_default_stage() + mesh = Common.get_objects()[mesh_name] + Common.unselect_all() + Common.set_active(mesh) + Common.switch('EDIT') + Common.switch('OBJECT') + + bm = bmesh.new() + bm.from_mesh(mesh.data) + bm.verts.ensure_lookup_table() + + # Get a vertex from the eye vertex group # TODO https://i.imgur.com/tWi8lk6.png after many times resetting the eyes + print('DEBUG: Group: ' + vertex_group) + group = mesh.vertex_groups.get(vertex_group) + if group is None: + print('DEBUG: Group: ' + vertex_group + ' not found!') + repair_shapekeys_mouth(mesh_name) + return + print('DEBUG: Group: ' + vertex_group + ' found!') + + vcoords = None + gi = group.index + for v in mesh.data.vertices: + for g in v.groups: + if g.group == gi: + vcoords = v.co.xyz + + if not vcoords: + return + + print('DEBUG: Repairing shapes!') + # Move that vertex by a tiny amount + moved = False + i = 0 + for key in bm.verts.layers.shape.keys(): + if not key.startswith('vrc.'): + continue + print('DEBUG: Repairing shape: ' + key) + value = bm.verts.layers.shape.get(key) + for index, vert in enumerate(bm.verts): + if vert.co.xyz == vcoords: + if index < i: + continue + shapekey = vert + shapekey_coords = Common.matmul(mesh.matrix_world, shapekey[value]) + shapekey_coords[0] -= 0.00007 * randBoolNumber() + shapekey_coords[1] -= 0.00007 * randBoolNumber() + shapekey_coords[2] -= 0.00007 * randBoolNumber() + shapekey[value] = Common.matmul(mesh.matrix_world.inverted(), shapekey_coords) + print('DEBUG: Repaired shape: ' + key) + i += 1 + moved = True + break + + bm.to_mesh(mesh.data) + + if not moved: + print('Error: Shapekey repairing failed for some reason! Using random shapekey method now.') + repair_shapekeys_mouth(mesh_name) + + +def randBoolNumber(): + if random() < 0.5: + return -1 + return 1 + + +# Repair vrc shape keys with random vertex +def repair_shapekeys_mouth(mesh_name): # TODO Add vertex repairing! + # This is done to fix a very weird bug where the mouth stays open sometimes + Common.set_default_stage() + mesh = Common.get_objects()[mesh_name] + Common.unselect_all() + Common.set_active(mesh) + Common.switch('EDIT') + Common.switch('OBJECT') + + bm = bmesh.new() + bm.from_mesh(mesh.data) + bm.verts.ensure_lookup_table() + + # Move that vertex by a tiny amount + moved = False + for key in bm.verts.layers.shape.keys(): + if not key.startswith('vrc'): + continue + value = bm.verts.layers.shape.get(key) + for vert in bm.verts: + shapekey = vert + shapekey_coords = Common.matmul(mesh.matrix_world, shapekey[value]) + shapekey_coords[0] -= 0.00007 + shapekey_coords[1] -= 0.00007 + shapekey_coords[2] -= 0.00007 + shapekey[value] = Common.matmul(mesh.matrix_world.inverted(), shapekey_coords) + print('TEST') + moved = True + break + + bm.to_mesh(mesh.data) + + if not moved: + print('Error: Random shapekey repairing failed for some reason! Canceling!') eye_left = None diff --git a/tools/importer.py b/tools/importer.py index 01048999..09ece416 100644 --- a/tools/importer.py +++ b/tools/importer.py @@ -96,6 +96,9 @@ def execute(self, context): if version_2_79_or_older(): context.scene.layers[0] = True + # Save all current objects to check which armatures got added by the importer + pre_import_objects = [obj for obj in bpy.data.objects if obj.type == 'ARMATURE'] + # Import the file using their corresponding importer for f in self.files: file_name = f['name'] @@ -169,8 +172,34 @@ def execute(self, context): except AttributeError: bpy.ops.cats_importer.install_vrm('INVOKE_DEFAULT') + # Create list of armatures that got added during import + 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.set_active(armature) + Common.switch('EDIT') + for bone in armature.data.edit_bones: + equal_axis_count = 0 + if bone.head[0] == bone.tail[0]: + equal_axis_count += 1 + if bone.head[1] == bone.tail[1]: + equal_axis_count += 1 + if bone.head[2] == bone.tail[2]: + equal_axis_count += 1 + + # If the bone points to more than one direction, don't fix the armatures bones + if equal_axis_count < 2: + return + + Common.fix_bone_orientations(armature) + @register_wrap class ModelsPopup(bpy.types.Operator):