diff --git a/.gitignore b/.gitignore index c4914928..9125d9c5 100644 --- a/.gitignore +++ b/.gitignore @@ -13,5 +13,6 @@ resources/settings.json resources/dictionary_google.json resources/google-response.txt resources/ignore_version.txt +resources/no_auto_ver_check.txt .coverage mmd_tools_local2 \ No newline at end of file diff --git a/README.md b/README.md index c8404ab9..19915e42 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Cats Blender Plugin (0.14.0) +# Cats Blender Plugin (0.15.0) 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 @@ -11,9 +11,7 @@ There are a lot of perks like having your name inside the plugin! [![](https://i.imgur.com/BFIald5.png)](https://www.patreon.com/catsblenderplugin) -Master branch: ![](https://api.travis-ci.org/michaeldegroot/cats-blender-plugin.svg?branch=master) - -Development branch: ![](https://api.travis-ci.org/michaeldegroot/cats-blender-plugin.svg?branch=development) +#### Download here: [Cats Blender Plugin](https://github.com/michaeldegroot/cats-blender-plugin/archive/master.zip) ## Features - Optimizing model with one click! @@ -31,14 +29,15 @@ Development branch: ![](https://api.travis-ci.org/michaeldegroot/cats-blender-pl *More to come!* -## Website -Check our website to report errors, suggestions or make comments! -https://catsblenderplugin.com +## Discord +Join our Discord to report errors, suggestions and make comments! -## Requirement +**Discord: https://discord.gg/f8yZGnv** - - Blender 2.79 or 2.80 (run as administrator is recommended) +## Requirements + - Blender **2.79** or **2.80** (run as administrator is recommended) - mmd_tools is **no longer required**! Cats comes pre-installed with it! + - If you have custom Python installed which Blender might use, you need to have Numpy installed ## Installation - Download the plugin: [Cats Blender Plugin](https://github.com/michaeldegroot/cats-blender-plugin/archive/master.zip) @@ -102,7 +101,7 @@ This tries to completely fix your model with one click. ## Model Options -![](https://i.imgur.com/ZiyyAsn.png) +![](https://i.imgur.com/ZPj2VUJ.png) ##### Translation - Translate certain entities from any japanese to english. @@ -343,6 +342,30 @@ It checks for a new version automatically once every day. ## Changelog +#### 0.15.0 +- **Importer**: + - FBX no longer imports animations and poses by default +- **Fix Model**: + - Now always applies transforms of the model + - Added "Keep Upper Chest" option + - Warning: Currently having an Upper Chest breaks Eye Tracking, so don't use this if you want Eye Tracking + - Removed "Fix Full Body Tracking" option + - It is no longer needed for VrChat + - The button to add/remove the fix is still available in Model Options + - Improved Hips placement as recommended by VRChat + - Legs are now getting bend forward very slightly if they are completely straight + - Fixed a bug which could sometimes delete bones unintentionally +- **Model**: + - Fixed pose mode error in 2.80 +- **Model Options**: + - Added new "Delete Zero Weight Vertex Groups" button + - Improved layout of the "Full Body Tracking Fix" buttons + - Fixed visual "Merge Weights" bug in Blender 2.80 +- **Optimization**: + - Improved Material Combiner detection algorithm +- **General**: + - Updated mmd_tools + #### 0.14.0 - **Cats is now fully compatible with Blender 2.80!** - **Fix Model**: @@ -361,7 +384,7 @@ It checks for a new version automatically once every day. - "Combine Same Materials" and "Convert Textures to PNG" are now compatible with Blender 2.80 - Added loading cursor to "Convert Textures to PNG" - Added support for Shotariyas Material Combiner in Blender 2.80 - - Minimum required Material Combiner version is now v2.1.1.1 + - Minimum required Material Combiner version is now v2.1.1.2 - It now fully supports VRM models, has a greatly improved combining logic and an updated UI - It also got compression removed, so you will always get full quality images now - **Updater**: @@ -388,21 +411,6 @@ It checks for a new version automatically once every day. - 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 - - 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 - Read the full changelog [here](https://github.com/michaeldegroot/cats-blender-plugin/releases). diff --git a/__init__.py b/__init__.py index 0fbe6d4a..54c454be 100644 --- a/__init__.py +++ b/__init__.py @@ -30,7 +30,7 @@ 'author': 'GiveMeAllYourCats & Hotox', 'location': 'View 3D > Tool Shelf > CATS', 'description': 'A tool designed to shorten steps needed to import and optimize models into VRChat', - 'version': (0, 14, 0), # 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, 15, 0), # 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', @@ -59,12 +59,6 @@ else: is_reloading = True -# Don't allow Blender versions older than 2.75 -if bpy.app.version < (2, 75): - sys.tracebacklimit = 0 - raise ImportError('\n\nThis Blender version is not supported by Cats. ' - '\nPlease use Blender 2.79 or later.') - # Load or reload all cats modules if not is_reloading: # This order is important @@ -211,6 +205,24 @@ def remove_corrupted_files(): '\n') +def check_unsupported_blender_versions(): + # Don't allow Blender versions older than 2.75 + if bpy.app.version < (2, 75): + unregister() + sys.tracebacklimit = 0 + raise ImportError('\n\nBlender versions older than 2.75 are not supported by Cats. ' + '\nPlease use Blender 2.79 or later.' + '\n') + + # Versions 2.80.0 to 2.80.74 are beta versions, stable is 2.80.75 + if (2, 80, 0) <= bpy.app.version < (2, 80, 75): + unregister() + sys.tracebacklimit = 0 + raise ImportError('\n\nYou are still on the beta version of Blender 2.80!' + '\nPlease update to the release version of Blender 2.80.' + '\n') + + def set_cats_version_string(): version = bl_info.get('version') version_temp = [] @@ -241,6 +253,9 @@ def set_cats_version_string(): def register(): print("\n### Loading CATS...") + # Check for unsupported Blender versions + check_unsupported_blender_versions() + # Check for faulty CATS installations remove_corrupted_files() diff --git a/extentions.py b/extentions.py index ac5435e5..7bf5c11b 100644 --- a/extentions.py +++ b/extentions.py @@ -16,11 +16,10 @@ def register(): update=Common.update_material_list ) - Scene.full_body = BoolProperty( - name='Apply Full Body Tracking Fix', - description="Applies a general fix for Full Body Tracking.\n\n" - 'Can potentially reduce the knee bending of every avatar in VRChat.\n' - 'You can safely ignore the "Spine length zero" warning in Unity', + Scene.keep_upper_chest = BoolProperty( + name='Keep Upper Chest', + description="VrChat now partially supports the Upper Chest bone, so deleting it is no longer necessary." + "\n\nWARNING: Currently this breaks Eye Tracking, so don't check this if you want Eye Tracking", default=False ) diff --git a/extern_tools/mmd_tools_local/__init__.py b/extern_tools/mmd_tools_local/__init__.py index da164143..809b934c 100644 --- a/extern_tools/mmd_tools_local/__init__.py +++ b/extern_tools/mmd_tools_local/__init__.py @@ -102,6 +102,11 @@ def menu_func_export(self, context): def menu_func_armature(self, context): self.layout.operator(operators.model.CreateMMDModelRoot.bl_idname, text='Create MMD Model', icon='OUTLINER_OB_ARMATURE') +def header_view3d_pose_draw(self, context): + obj = context.active_object + if obj and obj.type == 'ARMATURE' and obj.mode == 'POSE': + self.layout.operator('mmd_tools.flip_pose', text='', icon='ARROW_LEFTRIGHT') + @persistent def load_handler(dummy): from mmd_tools_local.core.sdef import FnSDEF @@ -114,6 +119,7 @@ def register(): print(__name__, 'registed %d classes'%len(__bl_classes)) properties.register() bpy.app.handlers.load_post.append(load_handler) + bpy.types.VIEW3D_HT_header.append(header_view3d_pose_draw) if bpy.app.version < (2, 80, 0): bpy.types.INFO_MT_file_import.append(menu_func_import) bpy.types.INFO_MT_file_export.append(menu_func_export) @@ -132,6 +138,7 @@ def unregister(): bpy.types.TOPBAR_MT_file_import.remove(menu_func_import) bpy.types.TOPBAR_MT_file_export.remove(menu_func_export) bpy.types.VIEW3D_MT_armature_add.remove(menu_func_armature) + bpy.types.VIEW3D_HT_header.remove(header_view3d_pose_draw) bpy.app.handlers.load_post.remove(load_handler) properties.unregister() for cls in reversed(__bl_classes): diff --git a/extern_tools/mmd_tools_local/core/material.py b/extern_tools/mmd_tools_local/core/material.py index 0225fbae..5c4e210d 100644 --- a/extern_tools/mmd_tools_local/core/material.py +++ b/extern_tools/mmd_tools_local/core/material.py @@ -545,8 +545,6 @@ def update_self_shadow_map(self): cast_shadows = mmd_mat.enabled_self_shadow_map if mmd_mat.alpha > 1e-3 else False 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): mat = self.material diff --git a/extern_tools/mmd_tools_local/core/model.py b/extern_tools/mmd_tools_local/core/model.py index 3f68b937..9520fea1 100644 --- a/extern_tools/mmd_tools_local/core/model.py +++ b/extern_tools/mmd_tools_local/core/model.py @@ -411,11 +411,11 @@ def rigidGroupObject(self): break if self.__rigid_grp is None: rigids = bpy.data.objects.new(name='rigidbodies', object_data=None) + SceneOp(bpy.context).link_object(rigids) rigids.mmd_type = 'RIGID_GRP_OBJ' rigids.parent = self.__root rigids.hide = rigids.hide_select = True rigids.lock_rotation = rigids.lock_location = rigids.lock_scale = [True, True, True] - SceneOp(bpy.context).link_object(rigids) self.__rigid_grp = rigids return self.__rigid_grp @@ -426,11 +426,11 @@ def jointGroupObject(self): break if self.__joint_grp is None: joints = bpy.data.objects.new(name='joints', object_data=None) + SceneOp(bpy.context).link_object(joints) joints.mmd_type = 'JOINT_GRP_OBJ' joints.parent = self.__root joints.hide = joints.hide_select = True joints.lock_rotation = joints.lock_location = joints.lock_scale = [True, True, True] - SceneOp(bpy.context).link_object(joints) self.__joint_grp = joints return self.__joint_grp @@ -441,11 +441,11 @@ def temporaryGroupObject(self): break if self.__temporary_grp is None: temporarys = bpy.data.objects.new(name='temporary', object_data=None) + SceneOp(bpy.context).link_object(temporarys) temporarys.mmd_type = 'TEMPORARY_GRP_OBJ' temporarys.parent = self.__root temporarys.hide = temporarys.hide_select = True temporarys.lock_rotation = temporarys.lock_location = temporarys.lock_scale = [True, True, True] - SceneOp(bpy.context).link_object(temporarys) self.__temporary_grp = temporarys return self.__temporary_grp @@ -552,6 +552,9 @@ def build(self): rigid_body.setRigidBodyWorldEnabled(rigidbody_world_enabled) def clean(self): + #FIXME rigid body cache is out of sync on Blender 2.8 + # [Build] at frame 1 -> [Play] -> [Stop] at frame N -> [Clean] at frame N -> [Play] -> crash + # [Build] at frame 1 -> [Play] -> [Stop] at frame N -> [Clean] at frame N -> go to frame 1 ->[Play] -> ok rigidbody_world_enabled = rigid_body.setRigidBodyWorldEnabled(False) logging.info('****************************************') logging.info(' Clean rig') diff --git a/extern_tools/mmd_tools_local/core/pmd/__init__.py b/extern_tools/mmd_tools_local/core/pmd/__init__.py index f0ee030a..00cf3f25 100644 --- a/extern_tools/mmd_tools_local/core/pmd/__init__.py +++ b/extern_tools/mmd_tools_local/core/pmd/__init__.py @@ -74,10 +74,7 @@ def readFloat(self): return v def readVector(self, size): - fmt = '<' - for i in range(size): - fmt += 'f' - return list(struct.unpack(fmt, self.__fin.read(4*size))) + return struct.unpack('<'+'f'*size, self.__fin.read(4*size)) def readByte(self): v, = struct.unpack('= (2, 80, 0) and (not bulk_update and use_skip): + use_skip = False # Add the driver to the shapekey f = obj.data.shape_keys.driver_add('key_blocks["'+cls.SHAPEKEY_NAME+'"].value', -1) if hasattr(f.driver, 'show_debug_info'): @@ -233,6 +237,13 @@ def bind(cls, obj, bulk_update=None, use_skip=True, use_scale=False): ov.type = 'SINGLE_PROP' ov.targets[0].id = obj ov.targets[0].data_path = 'name' + mod = obj.modifiers.get('mmd_bone_order_override') + if mod and mod.type == 'ARMATURE': + ov = f.driver.variables.new() + ov.name = 'arm' + ov.type = 'SINGLE_PROP' + ov.targets[0].id = mod.object + ov.targets[0].data_path = 'name' if hasattr(f.driver, 'use_self'): # Blender 2.78+ f.driver.use_self = True param = (bulk_update, use_skip, use_scale) diff --git a/extern_tools/mmd_tools_local/operators/view.py b/extern_tools/mmd_tools_local/operators/view.py index e264a70b..22025e11 100644 --- a/extern_tools/mmd_tools_local/operators/view.py +++ b/extern_tools/mmd_tools_local/operators/view.py @@ -62,7 +62,7 @@ def execute(self, context): space.show_backface_culling = True return {'FINISHED'} else: - def execute(self, context): #TODO + def execute(self, context): #TODO context.scene.render.engine = 'BLENDER_EEVEE' shading_mode = getattr(self, '_shading_mode', None) diff --git a/extern_tools/mmd_tools_local/panels/tool.py b/extern_tools/mmd_tools_local/panels/tool.py index 1fb6f119..c2015c59 100644 --- a/extern_tools/mmd_tools_local/panels/tool.py +++ b/extern_tools/mmd_tools_local/panels/tool.py @@ -21,13 +21,6 @@ ICON_ADD, ICON_REMOVE = 'ZOOMIN', 'ZOOMOUT' -def draw_filter_wrap(func): - if 1 or bpy.app.version < (2, 80, 0): - return func - def draw_filter_new(self_, context, layout, reverse=False): - func(self_, context, layout) - return draw_filter_new - if bpy.app.version < (2, 80, 0): def _layout_split(layout, factor, align): return layout.split(percentage=factor, align=align) @@ -202,7 +195,6 @@ def filter_items(self, context, data, propname): return flt_flags, flt_neworder - @draw_filter_wrap def draw_filter(self, context, layout): row = layout.row() row.prop(self, 'morph_filter', expand=True) @@ -628,7 +620,6 @@ def draw_item(self, context, layout, data, item, icon, active_data, active_propn layout.alignment = 'CENTER' layout.label(text='', icon=self.icon) - @draw_filter_wrap def draw_filter(self, context, layout): row = layout.row(align=True) row.prop(self, 'model_filter', expand=True) diff --git a/extern_tools/mmd_tools_local/panels/util_tools.py b/extern_tools/mmd_tools_local/panels/util_tools.py index 3a6976ec..949b5413 100644 --- a/extern_tools/mmd_tools_local/panels/util_tools.py +++ b/extern_tools/mmd_tools_local/panels/util_tools.py @@ -6,7 +6,6 @@ from mmd_tools_local.bpyutils import SceneOp from mmd_tools_local.core.model import Model from mmd_tools_local.panels.tool import TRIA_UP_BAR, TRIA_DOWN_BAR -from mmd_tools_local.panels.tool import draw_filter_wrap from mmd_tools_local.panels.tool import _PanelBase @register_wrap @@ -26,7 +25,6 @@ def draw_item(self, context, layout, data, item, icon, active_data, active_propn layout.alignment = 'CENTER' layout.label(text="", icon_value=icon) - @draw_filter_wrap def draw_filter(self, context, layout): layout.label(text="Use the arrows to sort", icon='INFO') @@ -66,7 +64,6 @@ def draw_item(self, context, layout, data, item, icon, active_data, active_propn layout.alignment = 'CENTER' layout.label(text="", icon_value=icon) - @draw_filter_wrap def draw_filter(self, context, layout): layout.label(text="Use the arrows to sort", icon='INFO') diff --git a/extern_tools/mmd_tools_local/panels/view_header.py b/extern_tools/mmd_tools_local/panels/view_header.py index 60ab572f..c9fc9c6e 100644 --- a/extern_tools/mmd_tools_local/panels/view_header.py +++ b/extern_tools/mmd_tools_local/panels/view_header.py @@ -4,7 +4,7 @@ from mmd_tools_local import register_wrap -@register_wrap +#@register_wrap # use draw function instead class MMDViewHeader(Header): bl_idname = 'MMD_TOOLS_HT_view_header' bl_space_type = 'VIEW_3D' diff --git a/extern_tools/mmd_tools_local/properties/__init__.py b/extern_tools/mmd_tools_local/properties/__init__.py index 90a7ad4e..b37521ff 100644 --- a/extern_tools/mmd_tools_local/properties/__init__.py +++ b/extern_tools/mmd_tools_local/properties/__init__.py @@ -74,8 +74,8 @@ def __patch(properties): # temporary patching, should be removed in the future set=lambda prop, value: prop.select_set(value), ) prop_obj['hide'] = bpy.props.BoolProperty( - get=lambda prop: prop.hide_viewport, - set=lambda prop, value: setattr(prop, 'hide_viewport', value), + get=lambda prop: prop.hide_get(), + set=lambda prop, value: prop.hide_set(value) or setattr(prop, 'hide_viewport', False), ) prop_obj['show_x_ray'] = bpy.props.BoolProperty( get=lambda prop: prop.show_in_front, diff --git a/extern_tools/mmd_tools_local/properties/bone.py b/extern_tools/mmd_tools_local/properties/bone.py index 8cf7a9ca..2740f8d7 100644 --- a/extern_tools/mmd_tools_local/properties/bone.py +++ b/extern_tools/mmd_tools_local/properties/bone.py @@ -181,6 +181,7 @@ def _mmd_ik_toggle_get(prop): return prop.get('mmd_ik_toggle', True) def _mmd_ik_toggle_set(prop, v): + #FIXME animation is not working well on Blender 2.8. Using driver is another way but it's troublesome. if v != prop.get('mmd_ik_toggle', None): prop['mmd_ik_toggle'] = v #print('_mmd_ik_toggle_set', v, prop.name) diff --git a/extern_tools/mmd_tools_local/properties/camera.py b/extern_tools/mmd_tools_local/properties/camera.py index e1482903..369e6f3c 100644 --- a/extern_tools/mmd_tools_local/properties/camera.py +++ b/extern_tools/mmd_tools_local/properties/camera.py @@ -16,16 +16,19 @@ def __get_camera(empty): else: def __get_camera(empty): cam = mmd_camera.MMDCamera(empty).camera() - return cam.evaluated_get(__find_depsgraph(empty)) if empty.is_evaluated else cam + if empty.is_evaluated: + depsgraph = __find_depsgraph(empty) + return cam.evaluated_get(depsgraph) if depsgraph else cam + return cam def __find_depsgraph(obj): depsgraph = bpy.context.view_layer.depsgraph if obj == depsgraph.objects.get(obj.name): return depsgraph - for view_layer in bpy.context.scene.view_layers: + for view_layer in (l for s in bpy.data.scenes for l in s.view_layers): if obj == view_layer.depsgraph.objects.get(obj.name): return view_layer.depsgraph - raise Exception('Bug???') + return None def _getMMDCameraAngle(prop): diff --git a/extern_tools/mmd_tools_local/properties/root.py b/extern_tools/mmd_tools_local/properties/root.py index c0272559..0e9cabd7 100644 --- a/extern_tools/mmd_tools_local/properties/root.py +++ b/extern_tools/mmd_tools_local/properties/root.py @@ -52,14 +52,15 @@ def _toggleVisibilityOfMeshes(self, context): rig = mmd_model.Model(root) hide = not self.show_meshes for i in rig.meshes(): - i.hide = hide - if hide and context.active_object is None: + i.hide = i.hide_render = hide + if hide and not getattr(context, 'active_object', True): SceneOp(context).active_object = root def _show_meshes_get(prop): return prop.get('show_meshes', True) def _show_meshes_set(prop, v): + #FIXME animation is not working well on Blender 2.8. Using driver is another way but it's troublesome. if v != prop.get('show_meshes', None): prop['show_meshes'] = v _toggleVisibilityOfMeshes(prop, bpy.context) diff --git a/resources/icons/supporters/Anon27.png b/resources/icons/supporters/Anon27.png new file mode 100644 index 00000000..11315f63 Binary files /dev/null and b/resources/icons/supporters/Anon27.png differ diff --git a/resources/icons/supporters/FustyLugs.png b/resources/icons/supporters/FustyLugs.png new file mode 100644 index 00000000..058a23ef Binary files /dev/null and b/resources/icons/supporters/FustyLugs.png differ diff --git a/resources/icons/supporters/Googie.png b/resources/icons/supporters/Googie.png index dec47f56..46c6bab2 100644 Binary files a/resources/icons/supporters/Googie.png and b/resources/icons/supporters/Googie.png differ diff --git a/resources/icons/supporters/Krisu.Miushy.png b/resources/icons/supporters/Krisu.Miushy.png index 971366d0..e44d70e3 100644 Binary files a/resources/icons/supporters/Krisu.Miushy.png and b/resources/icons/supporters/Krisu.Miushy.png differ diff --git a/resources/icons/supporters/SiriusSelf.png b/resources/icons/supporters/SiriusSelf.png new file mode 100644 index 00000000..04d8f784 Binary files /dev/null and b/resources/icons/supporters/SiriusSelf.png differ diff --git a/resources/supporters.json b/resources/supporters.json index b27f54dc..098fcd2a 100644 --- a/resources/supporters.json +++ b/resources/supporters.json @@ -130,7 +130,8 @@ "disabled": true },{ "displayname": "Naranar", - "startdate": "2018-04-05" + "startdate": "2018-04-05", + "disabled": true },{ "displayname": "liquid (retso)", "startdate": "2018-04-05", @@ -275,7 +276,7 @@ "displayname": "Krisu.Miushy", "startdate": "2018-06-21", "description": "I'll support your creations, let's keep the hard work everyone!\n", - "website": "https://www.youtube.com/watch?v=VHAxxGV1hNI", + "website": "https://www.youtube.com/watch?v=8H5e3L_nXdY", "tier": 1 },{ "displayname": "Atirion", @@ -296,7 +297,8 @@ },{ "displayname": "baffler", "startdate": "2018-08-07", - "info": "Was added 2 months late. ended may, remove july" + "info": "Was added 2 months late. ended may, remove july", + "disabled": true },{ "displayname": "Aliquem", "startdate": "2018-08-07", @@ -326,7 +328,8 @@ "disabled": true },{ "displayname": "Rincewind", - "startdate": "2018-10-28" + "startdate": "2018-10-28", + "disabled": true },{ "displayname": "Vanilla Neko", "startdate": "2018-11-08", @@ -354,11 +357,12 @@ "tier": 1 },{ "displayname": "Tierson", - "startdate": "2019-01-01" + "startdate": "2019-01-01", + "disabled": true },{ "displayname": "azupwn", "startdate": "2019-01-25", - "info": "Cancelled May, Rejoined Jan 19, duplicate is above" + "disabled": true },{ "displayname": "liggi", "startdate": "2019-01-25" @@ -400,7 +404,8 @@ "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 + "tier": 1, + "disabled": true },{ "displayname": "Hammey", "startdate": "2019-05-25" @@ -420,6 +425,21 @@ "description": "Thanks to this amazing program I was able to make some amazing VRchat character, make alot of friends and have so much fun in doing so!", "website": "https://www.twitch.tv/codysuniverse", "tier": 1 + },{ + "displayname": "SiriusSelf", + "startdate": "2019-08-11", + "description": "Cats is a major part of my workflow and I'll gladly raise a glass to the team behind this impressive piece of work any day", + "website": "https://twitter.com/SiriusSelf", + "tier": 1 + },{ + "displayname": "Ziggy_Stardust_", + "startdate": "2019-08-11" + },{ + "displayname": "Anon27", + "startdate": "2019-08-17" + },{ + "displayname": "FustyLugs", + "startdate": "2019-08-22" } ] } diff --git a/tools/armature.py b/tools/armature.py index b202bed4..4b0b201d 100644 --- a/tools/armature.py +++ b/tools/armature.py @@ -98,7 +98,6 @@ def execute(self, context): wm = bpy.context.window_manager armature = Common.set_default_stage() - full_body_tracking = context.scene.full_body # Check if bone matrix == world matrix, important for xps models x_cord, y_cord, z_cord, fbx = Common.get_bone_orientations(armature) @@ -325,49 +324,38 @@ def execute(self, context): if mesh.name.endswith(('.baked', '.baked0')): mesh.parent = armature # TODO - # Check if DAZ model - is_daz = False + # Check if weird FBX model print('CHECK TRANSFORMS:', armature.scale[0], armature.scale[1], armature.scale[2]) if round(armature.scale[0], 2) == 0.01 \ and round(armature.scale[1], 2) == 0.01 \ and round(armature.scale[2], 2) == 0.01: - is_daz = True + + # Delete keyframes + Common.set_active(armature) + armature.animation_data_clear() + for mesh in Common.get_meshes_objects(): + mesh.animation_data_clear() # Fixes bones disappearing, prevents bones from having their tail and head at the exact same position - Common.fix_zero_length_bones(armature, full_body_tracking, x_cord, y_cord, z_cord) + Common.fix_zero_length_bones(armature, x_cord, y_cord, z_cord) # Combines same materials if context.scene.combine_mats: bpy.ops.cats_material.combine_mats() + # Apply transforms of this model + Common.apply_transforms() + # Puts all meshes into a list and joins them if selected if context.scene.join_meshes: meshes = [Common.join_meshes()] else: meshes = Common.get_meshes_objects() - # Common.select(armature) - # - # # Correct pivot position - # try: - # # bpy.ops.view3d.snap_cursor_to_center() - # bpy.context.scene.cursor_location = (0.0, 0.0, 0.0) - # bpy.ops.object.origin_set(type='ORIGIN_CURSOR') - # except RuntimeError: - # pass - for mesh in meshes: Common.unselect_all() Common.set_active(mesh) - # # Correct pivot position - # try: - # # bpy.ops.view3d.snap_cursor_to_center() - # bpy.context.scene.cursor_location = (0.0, 0.0, 0.0) - # bpy.ops.object.origin_set(type='ORIGIN_CURSOR') - # except RuntimeError: - # pass - # Unlock all mesh transforms for i in range(0, 3): mesh.lock_location[i] = False @@ -440,17 +428,6 @@ def execute(self, context): uv.data[vert].uv.y = 0 fixed_uv_coords += 1 - # If DAZ model, reset all transforms and delete key frames. This has to be done after join meshes - print('CHECK TRANSFORMS:', armature.scale[0], armature.scale[1], armature.scale[2]) - if is_daz: - Common.reset_transforms() - - # Delete keyframes - Common.set_active(armature) - armature.animation_data_clear() - for mesh in Common.get_meshes_objects(): - mesh.animation_data_clear() - # Translate bones and unhide them all to_translate = [] for bone in armature.data.bones: @@ -670,6 +647,7 @@ def execute(self, context): # Rename the bone if bone[0] not in armature.data.edit_bones: + # print(bone_final.name, '>', bone[0]) bone_final.name = bone[0] # Check if it is a mixamo model @@ -775,6 +753,12 @@ def execute(self, context): armature.data.edit_bones.get(spines[0]).name = 'Spine' armature.data.edit_bones.get(spines[1]).name = 'Chest' + elif spine_count == 3: # Everything correct, just rename them + print('NORMAL') + armature.data.edit_bones.get(spines[0]).name = 'Spine' + armature.data.edit_bones.get(spines[1]).name = 'Chest' + armature.data.edit_bones.get(spines[2]).name = 'Upper Chest' + elif spine_count == 4 and source_engine: # SOURCE ENGINE SPECIFIC print('SOURCE ENGINE') spine = armature.data.edit_bones.get(spines[0]) @@ -842,125 +826,39 @@ def execute(self, context): right_leg = armature.data.edit_bones.get('Right leg') # Fixing the hips - if not full_body_tracking: - - # Hips should have x value of 0 in both head and tail - middle_x = (right_leg.head[x_cord] + left_leg.head[x_cord]) / 2 - hips.head[x_cord] = middle_x - hips.tail[x_cord] = middle_x - - # Make sure the hips bone (tail and head tip) is aligned with the legs Y - hips.head[y_cord] = right_leg.head[y_cord] - hips.tail[y_cord] = right_leg.head[y_cord] - - hips.head[z_cord] = right_leg.head[z_cord] - hips.tail[z_cord] = spine.head[z_cord] - - if hips.tail[z_cord] < hips.head[z_cord]: - hips.tail[z_cord] = hips.tail[z_cord] + 0.1 - - # if hips.tail[z_cord] < hips.head[z_cord]: - # hips_height = hips.head[z_cord] - # hips.head = hips.tail - # hips.tail[z_cord] = hips_height - # - # - # - # hips_height = hips.head[z_cord] - # hips.head = hips.tail - # hips.tail[z_cord] = hips_height - - # # Hips should have x value of 0 in both head and tail - # hips.head[x_cord] = 0 - # hips.tail[x_cord] = 0 - - # # Make sure the hips bone (tail and head tip) is aligned with the legs Y - # hips.head[y_cord] = right_leg.head[y_cord] - # hips.tail[y_cord] = right_leg.head[y_cord] - - # Flip the hips bone and make sure the hips bone is not below the legs bone - # hip_bone_length = abs(hips.tail[z_cord] - hips.head[z_cord]) - # hips.head[z_cord] = right_leg.head[z_cord] - # hips.tail[z_cord] = hips.head[z_cord] + hip_bone_length - - # hips.head[z_cord] = right_leg.head[z_cord] - # hips.tail[z_cord] = spine.head[z_cord] - - # if hips.tail[z_cord] < hips.head[z_cord]: - # hips.tail[z_cord] = hips.tail[z_cord] + 0.1 - - # elif spine and chest and neck and head: - # bones = [hips, spine, chest, neck, head] - # for bone in bones: - # bone_length = abs(bone.tail[z_cord] - bone.head[z_cord]) - # bone.tail[x_cord] = bone.head[x_cord] - # bone.tail[y_cord] = bone.head[y_cord] - # bone.tail[z_cord] = bone.head[z_cord] + bone_length - else: - # FBT Fix - # Flip hips - hips.head = spine.head - hips.tail = spine.head - hips.tail[z_cord] = left_leg.head[z_cord] - - if hips.tail[z_cord] > hips.head[z_cord]: - hips.tail[z_cord] -= 0.1 - - left_leg_new = armature.data.edit_bones.get('Left leg 2') - right_leg_new = armature.data.edit_bones.get('Right leg 2') - left_leg_new_alt = armature.data.edit_bones.get('Left_Leg_2') - right_leg_new_alt = armature.data.edit_bones.get('Right_Leg_2') - - # Create new leg bones and put them at the old location - if not left_leg_new: - print("DEBUG 1") - if left_leg_new_alt: - left_leg_new = left_leg_new_alt - left_leg_new.name = 'Left leg 2' - print("DEBUG 1.1") - else: - left_leg_new = armature.data.edit_bones.new('Left leg 2') - print("DEBUG 1.2") - if not right_leg_new: - print("DEBUG 2") - if right_leg_new_alt: - right_leg_new = right_leg_new_alt - right_leg_new.name = 'Right leg 2' - print("DEBUG 2.1") - else: - right_leg_new = armature.data.edit_bones.new('Right leg 2') - print("DEBUG 2.2") - - left_leg_new.head = left_leg.head - left_leg_new.tail = left_leg.tail - - right_leg_new.head = right_leg.head - right_leg_new.tail = right_leg.tail - - # Set new location for old leg bones - left_leg.tail = left_leg.head - left_leg.tail[z_cord] = left_leg.head[z_cord] + 0.1 - - right_leg.tail = right_leg.head - right_leg.tail[z_cord] = right_leg.head[z_cord] + 0.1 - - # # Fixing legs - # right_knee = armature.data.edit_bones.get('Right knee') - # left_knee = armature.data.edit_bones.get('Left knee') - # if right_knee and left_knee: - # # Make sure the upper legs tail are the same x/y values as the lower leg tail x/y - # right_leg.tail[x_cord] = right_leg.head[x_cord] - # left_leg.tail[x_cord] = left_knee.head[x_cord] - # right_leg.head[y_cord] = right_knee.head[y_cord] - # left_leg.head[y_cord] = left_knee.head[y_cord] - # - # # Make sure the leg bones are setup straight. (head should be same X as tail) - # left_leg.head[x_cord] = left_leg.tail[x_cord] - # right_leg.head[x_cord] = right_leg.tail[x_cord] - # - # # Make sure the left legs (head tip) have the same Y values as right leg (head tip) - # left_leg.head[y_cord] = right_leg.head[y_cord] + # Put Hips in the center of the leg bones + hips.head[x_cord] = (right_leg.head[x_cord] + left_leg.head[x_cord]) / 2 + + # Put Hips at 90% between spine and legs + hips.head[z_cord] = left_leg.head[z_cord] + (spine.head[z_cord] - left_leg.head[z_cord]) * 0.9 + + # If Hips are below or at the leg bones, put them above + if hips.head[z_cord] <= right_leg.head[z_cord]: + hips.head[z_cord] = right_leg.head[z_cord] + 0.1 + + # Make Hips point straight up + hips.tail[x_cord] = hips.head[x_cord] + hips.tail[y_cord] = hips.head[y_cord] + hips.tail[z_cord] = spine.head[z_cord] + + if hips.tail[z_cord] < hips.head[z_cord]: + hips.tail[z_cord] = hips.tail[z_cord] + 0.1 + + # Make legs bend very slightly forward + right_knee = armature.data.edit_bones.get('Right knee') + left_knee = armature.data.edit_bones.get('Left knee') + print(round(left_leg.head[x_cord], 4), round(left_leg.head[y_cord], 4)) + print(round(left_knee.head[x_cord], 4), round(left_knee.head[y_cord], 4)) + if left_knee: + if round(left_leg.head[x_cord], 4) == round(left_knee.head[x_cord], 4) \ + and round(left_leg.head[y_cord], 4) == round(left_knee.head[y_cord], 4): + print('FIXING LEG') + left_knee.head[y_cord] -= 0.001 + if right_knee: + if round(right_leg.head[x_cord], 4) == round(right_knee.head[x_cord], 4) \ + and round(right_leg.head[y_cord], 4) == round(right_knee.head[y_cord], 45): + right_knee.head[y_cord] -= 0.001 # Function: Reweight all eye children into the eyes def add_eye_children(eye_bone, parent_name): @@ -995,7 +893,10 @@ def add_eye_children(eye_bone, parent_name): mesh.rotation_euler = (math.radians(180), 0, 0) # Fixes bones disappearing, prevents bones from having their tail and head at the exact same position - Common.fix_zero_length_bones(armature, full_body_tracking, x_cord, y_cord, z_cord) + Common.fix_zero_length_bones(armature, x_cord, y_cord, z_cord) + + # Merged bones that should be deleted + bones_to_delete = [] # Mixing the weights for mesh in meshes: @@ -1048,6 +949,9 @@ def add_eye_children(eye_bone, parent_name): continue if bone_child.name not in mesh.vertex_groups: + # Add bone to delete list + if bone_child.name not in bones_to_delete: + bones_to_delete.append(bone_child.name) continue if bone_parent.name not in mesh.vertex_groups: @@ -1062,7 +966,11 @@ def add_eye_children(eye_bone, parent_name): # Mix the weights Common.mix_weights(mesh, bone_child.name, bone_parent.name) - # Mix weights + # Add bone to delete list + if bone_child.name not in bones_to_delete: + bones_to_delete.append(bone_child.name) + + # Merge weights for bone_new, bones_old in temp_reweight_bones.items(): if '\Left' in bone_new or '\L' in bone_new: bones = [[bone_new.replace('\Left', 'Left').replace('\left', 'left').replace('\L', 'L').replace('\l', 'l'), ''], @@ -1089,6 +997,9 @@ def add_eye_children(eye_bone, parent_name): # Cancel if vertex group was not found if not vg: + # Add bone to delete list + if bone[1] not in bones_to_delete: + bones_to_delete.append(bone[1]) continue if bone[0] == vg.name: @@ -1116,6 +1027,10 @@ def add_eye_children(eye_bone, parent_name): # print(vg.name + " to " + bone[0]) Common.mix_weights(mesh, vg.name, bone[0]) + # Add bone to delete list + if vg.name not in bones_to_delete: + bones_to_delete.append(vg.name) + # Old mixing weights. Still important for key, value in temp_list_reweight_bones.items(): current_step += 1 @@ -1135,7 +1050,14 @@ def add_eye_children(eye_bone, parent_name): break # Cancel if vertex groups was not found - if not vg_from or not vg_to: + if not vg_from: + # Add bone to delete list + if key not in bones_to_delete: + bones_to_delete.append(key) + continue + + # Cancel if vertex groups was not found + if not vg_to: continue bone_tmp = armature.data.bones.get(vg_from.name) @@ -1152,18 +1074,37 @@ def add_eye_children(eye_bone, parent_name): # print(vg_from.name, 'into', vg_to.name) Common.mix_weights(mesh, vg_from.name, vg_to.name) + # Add bone to delete list + if vg_from.name not in bones_to_delete: + bones_to_delete.append(vg_from.name) + # Put back armature modifier mod = mesh.modifiers.new("Armature", 'ARMATURE') mod.object = armature + # Delete Upper Chest, if selected + if not context.scene.keep_upper_chest: + if 'Upper Chest' in mesh.vertex_groups and 'Chest' in mesh.vertex_groups: + Common.mix_weights(mesh, 'Upper Chest', 'Chest') + + # Add bone to delete list + if 'Upper Chest' not in bones_to_delete: + bones_to_delete.append('Upper Chest') + Common.unselect_all() Common.set_active(armature) Common.switch('EDIT') + # Delete all the leftover bones from the merging process + for bone_name in bones_to_delete: + if bone_name in armature.data.edit_bones: + armature.data.edit_bones.remove(armature.data.edit_bones.get(bone_name)) + # Reparent all bones to be correct for unity mapping and vrc itself for key, value in temp_list_reparent_bones.items(): - # current_step += 1 - # wm.progress_update(current_step) + if value == 'Upper Chest' and 'Upper Chest' not in armature.data.edit_bones: + value = 'Chest' + if key in armature.data.edit_bones and value in armature.data.edit_bones: armature.data.edit_bones.get(key).parent = armature.data.edit_bones.get(value) @@ -1291,51 +1232,3 @@ def set_material_shading(): space.shading.studio_light = 'forest.exr' space.shading.studiolight_rotate_z = 0.0 space.shading.studiolight_background_alpha = 0.0 - - -@register_wrap -class ModelSettings(bpy.types.Operator): - bl_idname = "cats_armature.settings" - bl_label = "Fix Model Settings" - - def execute(self, context): - return {'FINISHED'} - - def invoke(self, context, event): - dpi_value = Common.get_user_preferences().system.dpi - return context.window_manager.invoke_props_dialog(self, width=dpi_value * 3.25, height=-550) - - def check(self, context): - # Important for changing options - return True - - def draw(self, context): - layout = self.layout - col = layout.column(align=True) - - row = col.row(align=True) - row.prop(context.scene, 'full_body') - row = col.row(align=True) - row.active = context.scene.remove_zero_weight - row.prop(context.scene, 'keep_end_bones') - 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') - - if context.scene.full_body: - col.separator() - row = col.row(align=True) - row.scale_y = 0.7 - row.label(text='INFO:', icon='INFO') - row = col.row(align=True) - row.scale_y = 0.7 - row.label(text='You can safely ignore the', icon_value=Supporter.preview_collections["custom_icons"]["empty"].icon_id) - row = col.row(align=True) - row.scale_y = 0.7 - row.label(text='"Spine length zero" warning in Unity.', icon_value=Supporter.preview_collections["custom_icons"]["empty"].icon_id) - col.separator() diff --git a/tools/armature_bones.py b/tools/armature_bones.py index 561dede8..643af2cd 100644 --- a/tools/armature_bones.py +++ b/tools/armature_bones.py @@ -34,10 +34,11 @@ bone_list_parenting = { 'Spine': 'Hips', 'Chest': 'Spine', - 'Neck': 'Chest', + 'Upper Chest': 'Chest', + 'Neck': 'Upper Chest', 'Head': 'Neck', - 'Left shoulder': 'Chest', - 'Right shoulder': 'Chest', + 'Left shoulder': 'Upper Chest', + 'Right shoulder': 'Upper Chest', 'Left arm': 'Left shoulder', 'Right arm': 'Right shoulder', 'Left elbow': 'Left arm', @@ -105,7 +106,7 @@ # 'J_L_Eye_UpperCorner_001': 'Head', } dont_delete_these_bones = [ - 'Hips', 'Spine', 'Chest', 'Neck', 'Head', + 'Hips', 'Spine', 'Chest', 'Upper Chest', 'Neck', 'Head', 'Left leg', 'Left knee', 'Left ankle', 'Left toe', 'Right leg', 'Right knee', 'Right ankle', 'Right toe', 'Left shoulder', 'Left arm', 'Left elbow', 'Left wrist', @@ -128,7 +129,7 @@ 'Breast_L', 'Breast_R', ] dont_delete_these_main_bones = [ - 'Hips', 'Spine', 'Chest', 'Neck', 'Head', + 'Hips', 'Spine', 'Chest', 'Upper Chest', 'Neck', 'Head', 'Left leg', 'Left knee', 'Left ankle', 'Left toe', 'Right leg', 'Right knee', 'Right ankle', 'Right toe', 'Left shoulder', 'Left arm', 'Left elbow', 'Left wrist', @@ -158,6 +159,18 @@ 'Arm_\Left_Shoulder_Adj_2', 'Arm_\Left_Shoulder_Adj_3', 'Arm_\Left_Shoulder_Adj_4', + 'Arm_\Left_Shoulder_2_Adj_1', + 'Arm_\Left_Shoulder_2_Adj_2', + 'Arm_\Left_Shoulder_2_Adj_3', + 'Arm_\Left_Shoulder_2_Adj_4', + 'Arm_\Left_Elbow_Adj_1', + 'Arm_\Left_Elbow_Adj_2', + 'Arm_\Left_Elbow_Adj_3', + 'Arm_\Left_Elbow_Adj_4', + 'Leg_\Left_Knee_Adj_1', + 'Leg_\Left_Knee_Adj_2', + 'Leg_\Left_Knee_Adj_3', + 'Leg_\Left_Knee_Adj_4', 'ArmTwist_\L', 'H(\L)_0', 'H(\L)_1', @@ -347,6 +360,8 @@ 'J_Hip', 'Pelvis_L', 'Pelvis_R', + 'Root_Pelvis_1', + 'Root_X', ] bone_rename['Spine'] = [ # This is a list of all the spine and chest bones. They will be correctly fixed 'Spine', # First entry! @@ -473,6 +488,7 @@ 'J_Bip_C_Spine', 'J_Bip_C_Chest', + 'J_Bip_C_UpperChest', 'Pelwas', 'Pelwas2', @@ -504,11 +520,18 @@ 'AbdomenUpper', 'ChestLower', + 'Spine_01_X', + 'Spine_02_X', + 'Mune', 'SpineTop', - 'Chest' # Last entry! + 'UpperBodyx2', + + 'Chest', + + 'Upper_Chest' # Last entry! ] bone_rename['Neck'] = [ 'Mixamorig:Neck', @@ -531,6 +554,7 @@ 'NeckA_01', 'J_Neck1', 'NeckLower', + 'Neck_X', ] bone_rename['Head'] = [ 'Mixamorig:Head', @@ -548,6 +572,7 @@ 'Head_01', 'Head_001', 'J_Head', + 'Head_X', ] bone_rename['\Left shoulder'] = [ '\Left_Shoulder', @@ -634,6 +659,7 @@ 'Upperarm01_\L', '\L_Shldr', '\LShldrBend', + 'Arm_Stretch_\L', ] bone_rename['Left arm'] = [ '+_Leisure_Elder_Supplement', @@ -682,6 +708,7 @@ 'Lowerarm01_\L', '\LElbow', '\LForearmBend', + 'Forearm_Stretch_\L', ] bone_rename['\Left wrist'] = [ '\Left_Wrist', @@ -771,6 +798,7 @@ 'Groin_\L', 'Upperleg01_\L', '\LThighBend', + 'Thigh_Stretch_\L', ] bone_rename['\Left knee'] = [ '\Left_Knee', @@ -815,6 +843,7 @@ 'Leg_2_\L', 'Bip_Leg_\L', 'Lowerleg01_\L', + 'Leg_Stretch_\L', ] bone_rename['\Left ankle'] = [ '\Left_Ankle', @@ -889,6 +918,7 @@ 'J_\L_Toe', 'Bip_Toe_\L', 'Toe_Boot_\L', + 'Toes_01_\L', ] bone_rename['Eye_\L'] = [ '\Left_Eye', @@ -917,6 +947,8 @@ bone_rename['Breast_\L'] = [ 'J_Sec_\L_Bust1', '\LPectoral', + 'Spine_\Left_Breast_2', + 'Breast_Def_\L', ] ################################ @@ -949,6 +981,13 @@ 'Bip_SpineBase', 'Bip_HipFront_L', 'Bip_HipFront_R', + 'Root_Pelvis_2', + 'Root_Waist', + 'Root_Waist_L', + 'Root_Waist_R', + 'Hiphalf_L', + 'Hiphalf_R', + 'PelvisDown_Sld', ] bone_reweight['Spine'] = [ 'UpperBodyx', @@ -958,14 +997,16 @@ 'Bip_Spine0a', ] bone_reweight['Chest'] = [ - 'UpperBodyx2', - 'Spine_Upper_Adj', + # 'UpperBodyx2', + # 'J_Bip_C_UpperChest', 'Bip_Spine1a', - 'J_Bip_C_UpperChest', - 'J_Adj_C_UpperChest', 'Bip_CollarHelper_L', 'Bip_CollarHelper_R', ] +bone_reweight['Upper Chest'] = [ + 'J_Adj_C_UpperChest', + 'Spine_Upper_Adj', +] bone_reweight['Neck'] = [ 'Neck1', 'Neck2', @@ -1004,6 +1045,7 @@ 'Shoulder\LT_Roll_01', 'Bip_Armpit_\L', 'Bip_UpperArmBase_\L', + 'ShoulderHalf_\L', ] bone_reweight['\Left arm'] = [ 'Arm01_\L', @@ -1107,6 +1149,8 @@ '\LArmJiggle3', '\LShldrTwist', 'J_Sec_\L_UpperArm', + 'UpArmLow_\L', + 'UpArmUp_\L', ] bone_reweight['Left arm'] = [ # This has apparently no side in the name 'エプロンArm', @@ -1163,8 +1207,6 @@ '+Elbow_\L', 'Elbowa_\L', 'Arm_\Left_Wrist_Adj', - 'Arm_\Left_Elbow_Adj_2', - 'Arm_\Left_Elbow_Adj_1', 'Arm_\Left_Forearm', '\Left_Forearm_Twist', '\LHandEX', @@ -1243,6 +1285,8 @@ '\LCuffsMain', '\LForearmTwist', 'J_Sec_\L_LowerArm', + 'ForearmLow_\L', + 'ForearmUp_\L', ] bone_reweight['\Left wrist'] = [ # 'Sleeve3_\L', @@ -1295,6 +1339,7 @@ '\LCarpal2', '\LCarpal3', '\LCarpal4', + 'Arm_\Left_Fist', ] bone_reweight['Left wrist'] = [ 'Left_Hand_002', @@ -1406,6 +1451,9 @@ 'Upperleg02_\L', '\LThighTwist', 'J_Sec_\L_UpperLeg', + '\Left_Thigh_Twist', + 'ThighLow_\L', + 'ThighUp_\L', ] bone_reweight['\Left knee'] = [ 'KneeD_\L', @@ -1439,8 +1487,6 @@ 'DEF_Shin_Twist_50_\L', 'DEF_Shin_Twist_75_\L', 'Leg_\Left_Ankle_Adj', - 'Leg_\Left_Knee_Adj_1', - 'Leg_\Left_Knee_Adj_2', '\L_Knee_Ast', '\L_HorseLink', 'KneeD2_\L', @@ -1475,6 +1521,9 @@ 'Lowerleg02_\L', '\LKneeJiggle', 'J_Sec_\L_LowerLeg', + '\Left_Ankle_Twist', + 'CalfLow_\L', + 'CalfUp_\L', ] bone_reweight['\Left ankle'] = [ 'AnkleD_\L', @@ -1500,7 +1549,8 @@ 'BK_\L_Ankle_03', 'BK_\L_Ankle_04', 'Foot_\LT_01_IK', - '\LMetatarsals' + '\LMetatarsals', + 'Leg_\Left_Ankle_Heel', ] bone_reweight['\Left toe'] = [ '\Left_Toes', @@ -1637,6 +1687,7 @@ 'ThumbA\LT_01', 'J_Bip_\L_Thumb1', 'Bip_FThumb01_\L', + 'Arm_\Left_Finger_1a', ] bone_rename_fingers['Thumb1_\L'] = [ # 'Arm_\Left_Finger_1b', @@ -1667,6 +1718,7 @@ 'ThumbB\LT_01', 'J_Bip_\L_Thumb2', 'Bip_FThumb02_\L', + 'Arm_\Left_Finger_1b', ] bone_rename_fingers['Thumb2_\L'] = [ # 'Arm_\Left_Finger_1c', @@ -1696,6 +1748,7 @@ 'ThumbC\LT_01', 'J_Bip_\L_Thumb3', 'Bip_FThumb03_\L', + 'Arm_\Left_Finger_1c', ] bone_rename_fingers['IndexFinger1_\L'] = [ 'Fore1_\L', @@ -1728,6 +1781,7 @@ 'IndexA\LT_01', 'J_Bip_\L_Index1', 'Bip_FIndex00_\L', + 'Arm_\Left_Finger_2a', ] bone_rename_fingers['IndexFinger2_\L'] = [ 'Fore2_\L', @@ -1760,6 +1814,7 @@ 'IndexB\LT_01', 'J_Bip_\L_Index2', 'Bip_FIndex01_\L', + 'Arm_\Left_Finger_2b', ] bone_rename_fingers['IndexFinger3_\L'] = [ 'Fore3_\L', @@ -1793,6 +1848,7 @@ 'IndexC\LT_01', 'J_Bip_\L_Index3', 'Bip_FIndex02_\L', + 'Arm_\Left_Finger_2c', ] bone_rename_fingers['MiddleFinger1_\L'] = [ 'Middle1_\L', @@ -1826,6 +1882,7 @@ 'FingerA\LT_01', 'J_Bip_\L_Middle1', 'Bip_FMiddle00_\L', + 'Arm_\Left_Finger_3a', ] bone_rename_fingers['MiddleFinger2_\L'] = [ 'Middle2_\L', @@ -1858,6 +1915,7 @@ '\L_Middlefinger_B', 'J_Bip_\L_Middle2', 'Bip_FMiddle01_\L', + 'Arm_\Left_Finger_3b', ] bone_rename_fingers['MiddleFinger3_\L'] = [ 'Middle3_\L', @@ -1890,6 +1948,7 @@ 'FingerC\LT_01', 'J_Bip_\L_Middle3', 'Bip_FMiddle02_\L', + 'Arm_\Left_Finger_3c', ] bone_rename_fingers['RingFinger1_\L'] = [ 'Third1_\L', @@ -1923,6 +1982,7 @@ 'RingA\LT_01', 'J_Bip_\L_Ring1', 'Bip_FRing00_\L', + 'Arm_\Left_Finger_4a', ] bone_rename_fingers['RingFinger2_\L'] = [ 'Third2_\L', @@ -1956,6 +2016,7 @@ 'RingB\LT_01', 'J_Bip_\L_Ring2', 'Bip_FRing01_\L', + 'Arm_\Left_Finger_4b', ] bone_rename_fingers['RingFinger3_\L'] = [ 'Third3_\L', @@ -1989,6 +2050,7 @@ 'RingC\LT_01', 'J_Bip_\L_Ring3', 'Bip_FRing02_\L', + 'Arm_\Left_Finger_4c', ] bone_rename_fingers['LittleFinger1_\L'] = [ 'Little1_\L', @@ -2023,6 +2085,7 @@ 'PinkyA\LT_01', 'J_Bip_\L_Little1', 'Bip_FPinky00_\L', + 'Arm_\Left_Finger_5a', ] bone_rename_fingers['LittleFinger2_\L'] = [ 'Little2_\L', @@ -2057,6 +2120,7 @@ 'PinkyB\LT_01', 'J_Bip_\L_Little2', 'Bip_FPinky01_\L', + 'Arm_\Left_Finger_5b', ] bone_rename_fingers['LittleFinger3_\L'] = [ 'Little3_\L', @@ -2091,4 +2155,5 @@ 'PinkyC\LT_01', 'J_Bip_\L_Little3', 'Bip_FPinky02_\L', + 'Arm_\Left_Finger_5c', ] diff --git a/tools/armature_custom.py b/tools/armature_custom.py index ea0d35af..69f2defb 100644 --- a/tools/armature_custom.py +++ b/tools/armature_custom.py @@ -207,9 +207,9 @@ def merge_armatures(base_armature_name, merge_armature_name, mesh_only, mesh_nam # Fixes bones disappearing, prevents bones from having their tail and head at the exact same position x_cord, y_cord, z_cord, fbx = Common.get_bone_orientations(base_armature) - Common.fix_zero_length_bones(base_armature, False, x_cord, y_cord, z_cord) + Common.fix_zero_length_bones(base_armature, x_cord, y_cord, z_cord) x_cord, y_cord, z_cord, fbx = Common.get_bone_orientations(merge_armature) - Common.fix_zero_length_bones(merge_armature, False, x_cord, y_cord, z_cord) + Common.fix_zero_length_bones(merge_armature, x_cord, y_cord, z_cord) # Join meshes in both armatures mesh_base = Common.join_meshes(armature_name=base_armature_name, apply_transformations=False) diff --git a/tools/armature_manual.py b/tools/armature_manual.py index 290a5418..97f5c6c3 100644 --- a/tools/armature_manual.py +++ b/tools/armature_manual.py @@ -121,11 +121,14 @@ def execute(self, context): saved_data = Common.SavedData() armature = Common.get_armature() Common.set_active(armature) - Common.hide(armature, False) + + # Make all objects visible + bpy.ops.object.hide_view_clear() for pb in armature.data.bones: pb.hide = False pb.select = True + bpy.ops.pose.rot_clear() bpy.ops.pose.scale_clear() bpy.ops.pose.transforms_clear() @@ -725,8 +728,8 @@ def execute(self, context): @register_wrap -class RemoveZeroWeight(bpy.types.Operator): - bl_idname = 'cats_manual.remove_zero_weight' +class RemoveZeroWeightBones(bpy.types.Operator): + bl_idname = 'cats_manual.remove_zero_weight_bones' bl_label = 'Remove Zero Weight Bones' bl_description = "Cleans up the bones hierarchy, deleting all bones that don't directly affect any vertices\n" \ "Don't use this if you plan to use 'Fix Model'" @@ -750,6 +753,46 @@ def execute(self, context): return {'FINISHED'} +@register_wrap +class RemoveZeroWeightGroups(bpy.types.Operator): + bl_idname = 'cats_manual.remove_zero_weight_groups' + bl_label = 'Remove Zero Weight Vertex Groups' + bl_description = "Cleans up the vertex groups of all meshes, deleting all groups that don't directly affect any vertices" + bl_options = {'REGISTER', 'UNDO'} + + @classmethod + def poll(cls, context): + return Common.get_meshes_objects(mode=2, check=False) + + def execute(self, context): + saved_data = Common.SavedData() + + Common.set_default_stage() + count = Common.remove_unused_vertex_groups() + + saved_data.load() + self.report({'INFO'}, 'Removed ' + str(count) + ' zero weight vertex groups.') + return {'FINISHED'} + + # Maybe only remove groups from selected meshes instead of from all of them + # THis still needs some work + # + # @classmethod + # def poll2(cls, context): + # return Common.get_meshes_objects(mode=3, check=False) + # + # def execute2(self, context): + # saved_data = Common.SavedData() + # remove_count = 0 + # + # for mesh in Common.get_meshes_objects(mode=3): + # remove_count += Common.remove_unused_vertex_groups_of_mesh(mesh) + # + # saved_data.load() + # self.report({'INFO'}, 'Removed ' + str(remove_count) + ' zero weight vertex groups.') + # return {'FINISHED'} + + @register_wrap class RemoveConstraints(bpy.types.Operator): bl_idname = 'cats_manual.remove_constraints' @@ -1063,13 +1106,10 @@ def execute(self, context): class FixFBTButton(bpy.types.Operator): bl_idname = 'cats_manual.fix_fbt' bl_label = 'Fix Full Body Tracking' - bl_description = "Applies a general fix for Full Body Tracking." \ + bl_description = "WARNING: This fix is no longer needed for VRChat, you should not use it!" \ "\n" \ - '\nCan potentially reduce the knee bending of this avatar in VRChat.' \ - '\nIgnore the "Spine length zero" warning in Unity.' \ - '\n' \ - '\nRequires bones:' \ - '\n - Hips, Spine, Left leg, Right leg' + "\nApplies a general fix for Full Body Tracking." \ + '\nIgnore the "Spine length zero" warning in Unity' bl_options = {'REGISTER', 'UNDO', 'INTERNAL'} @classmethod @@ -1101,6 +1141,11 @@ def execute(self, context): saved_data.load() return {'CANCELLED'} + if left_leg_new or right_leg_new or left_leg_new_alt or right_leg_new_alt: + self.report({'ERROR'}, 'Full Body Tracking Fix already applied!') + saved_data.load() + return {'CANCELLED'} + # FBT Fix # Disconnect bones for child in hips.children: @@ -1120,23 +1165,17 @@ def execute(self, context): # Create new leg bones and put them at the old location if not left_leg_new: - print("DEBUG 1") if left_leg_new_alt: left_leg_new = left_leg_new_alt left_leg_new.name = 'Left leg 2' - print("DEBUG 1.1") else: left_leg_new = armature.data.edit_bones.new('Left leg 2') - print("DEBUG 1.2") if not right_leg_new: - print("DEBUG 2") if right_leg_new_alt: right_leg_new = right_leg_new_alt right_leg_new.name = 'Right leg 2' - print("DEBUG 2.1") else: right_leg_new = armature.data.edit_bones.new('Right leg 2') - print("DEBUG 2.2") left_leg_new.head = left_leg.head left_leg_new.tail = left_leg.tail @@ -1166,8 +1205,6 @@ def execute(self, context): Common.switch('OBJECT') - context.scene.full_body = True - saved_data.load() self.report({'INFO'}, 'Successfully applied the Full Body Tracking fix.') @@ -1177,8 +1214,8 @@ def execute(self, context): @register_wrap class RemoveFBTButton(bpy.types.Operator): bl_idname = 'cats_manual.remove_fbt' - bl_label = 'Remove Full Body Tracking' - bl_description = "Removes the fix for Full Body Tracking." \ + bl_label = 'Remove Full Body Tracking Fix' + bl_description = "Removes the fix for Full Body Tracking, since it is no longer advised to use it." \ '\n' \ '\nRequires bones:' \ '\n - Hips, Spine, Left leg, Right leg, Left leg 2, Right leg 2' @@ -1220,16 +1257,24 @@ def execute(self, context): # Remove FBT Fix # Corrects hips if hips.head[z_cord] > hips.tail[z_cord]: - middle_x = (right_leg.head[x_cord] + left_leg.head[x_cord]) / 2 - hips.head[x_cord] = middle_x - hips.tail[x_cord] = middle_x + # Put Hips in the center of the leg bones + hips.head[x_cord] = (right_leg.head[x_cord] + left_leg.head[x_cord]) / 2 - hips.head[y_cord] = right_leg.head[y_cord] - hips.tail[y_cord] = right_leg.head[y_cord] + # Put Hips at 90% between spine and legs + hips.head[z_cord] = left_leg.head[z_cord] + (spine.head[z_cord] - left_leg.head[z_cord]) * 0.9 - hips.head[z_cord] = right_leg.head[z_cord] + # If Hips are below or at the leg bones, put them above + if hips.head[z_cord] <= right_leg.head[z_cord]: + hips.head[z_cord] = right_leg.head[z_cord] + 0.1 + + # Make Hips point straight up + hips.tail[x_cord] = hips.head[x_cord] + hips.tail[y_cord] = hips.head[y_cord] hips.tail[z_cord] = spine.head[z_cord] + if hips.tail[z_cord] < hips.head[z_cord]: + hips.tail[z_cord] = hips.tail[z_cord] + 0.1 + # Put the original legs at their old location left_leg.head = left_leg_new.head left_leg.tail = left_leg_new.tail @@ -1250,8 +1295,6 @@ def execute(self, context): Common.switch('OBJECT') - context.scene.full_body = False - saved_data.load() self.report({'INFO'}, 'Successfully removed the Full Body Tracking fix.') diff --git a/tools/atlas.py b/tools/atlas.py index 37c877a1..8e18344b 100644 --- a/tools/atlas.py +++ b/tools/atlas.py @@ -45,10 +45,31 @@ class EnableSMC(bpy.types.Operator): bl_options = {'REGISTER', 'UNDO', 'INTERNAL'} def execute(self, context): + # disable all wrong versions for mod in addon_utils.modules(): + if mod.bl_info['name'] == "Shotariya-don": + if addon_utils.check(mod.__name__)[0]: + try: + bpy.ops.wm.addon_disable(module=mod.__name__) + except: + pass + continue if mod.bl_info['name'] == "Shotariya's Material Combiner": - bpy.ops.wm.addon_enable(module=mod.__name__) - break + if mod.bl_info['version'] < (2, 1, 1, 2) and addon_utils.check(mod.__name__)[0]: + try: + bpy.ops.wm.addon_disable(module=mod.__name__) + except: + pass + continue + + # then enable correct version + for mod in addon_utils.modules(): + if mod.bl_info['name'] == "Shotariya's Material Combiner": + if mod.bl_info['version'] < (2, 1, 1, 2): + continue + if not addon_utils.check(mod.__name__)[0]: + bpy.ops.wm.addon_enable(module=mod.__name__) + break self.report({'INFO'}, 'Enabled Material Combiner!') return {'FINISHED'} diff --git a/tools/common.py b/tools/common.py index 99478808..302263af 100644 --- a/tools/common.py +++ b/tools/common.py @@ -23,7 +23,6 @@ # Code author: GiveMeAllYourCats # Repo: https://github.com/michaeldegroot/cats-blender-plugin # Edits by: GiveMeAllYourCats, Hotox - import re import os import bpy @@ -277,6 +276,8 @@ def set_default_stage(): armature = get_armature() if armature: set_active(armature) + if version_2_79_or_older(): + armature.layers[0] = True return armature @@ -307,23 +308,44 @@ def get_bone_angle(p1, p2): def remove_unused_vertex_groups(ignore_main_bones=False): + remove_count = 0 unselect_all() - for ob in get_objects(): - if ob.type == 'MESH': - ob.update_from_editmode() + for mesh in get_meshes_objects(mode=2): + mesh.update_from_editmode() - vgroup_used = {i: False for i, k in enumerate(ob.vertex_groups)} + vgroup_used = {i: False for i, k in enumerate(mesh.vertex_groups)} - for v in ob.data.vertices: - for g in v.groups: - if g.weight > 0.0: - vgroup_used[g.group] = True + for v in mesh.data.vertices: + for g in v.groups: + if g.weight > 0.0: + vgroup_used[g.group] = True - for i, used in sorted(vgroup_used.items(), reverse=True): - if not used: - if ignore_main_bones and ob.vertex_groups[i].name in Bones.dont_delete_these_main_bones: - continue - ob.vertex_groups.remove(ob.vertex_groups[i]) + for i, used in sorted(vgroup_used.items(), reverse=True): + if not used: + if ignore_main_bones and mesh.vertex_groups[i].name in Bones.dont_delete_these_main_bones: + continue + mesh.vertex_groups.remove(mesh.vertex_groups[i]) + remove_count += 1 + return remove_count + + +def remove_unused_vertex_groups_of_mesh(mesh): + remove_count = 0 + unselect_all() + mesh.update_from_editmode() + + vgroup_used = {i: False for i, k in enumerate(mesh.vertex_groups)} + + for v in mesh.data.vertices: + for g in v.groups: + if g.weight > 0.0: + vgroup_used[g.group] = True + + for i, used in sorted(vgroup_used.items(), reverse=True): + if not used: + mesh.vertex_groups.remove(mesh.vertex_groups[i]) + remove_count += 1 + return remove_count def find_center_vector_of_vertex_group(mesh, vertex_group): @@ -1427,11 +1449,16 @@ def correct_bone_positions(armature_name=None): armature_name = bpy.context.scene.armature armature = get_armature(armature_name=armature_name) + upper_chest = armature.data.edit_bones.get('Upper Chest') chest = armature.data.edit_bones.get('Chest') neck = armature.data.edit_bones.get('Neck') head = armature.data.edit_bones.get('Head') if chest and neck: - chest.tail = neck.head + if upper_chest and bpy.context.scene.keep_upper_chest: + chest.tail = upper_chest.head + upper_chest.tail = neck.head + else: + chest.tail = neck.head if neck and head: neck.tail = head.head @@ -1629,6 +1656,7 @@ def mix_weights(mesh, vg_from, vg_to, delete_old_vg=True): bpy.ops.object.modifier_apply(modifier=mod.name) if delete_old_vg: mesh.vertex_groups.remove(mesh.vertex_groups.get(vg_from)) + mesh.active_shape_key_index = 0 # This line fixes a visual bug in 2.80 which causes random weights to be stuck after being merged def get_user_preferences(): @@ -1662,7 +1690,7 @@ def ui_refresh(): time.sleep(0.5) -def fix_zero_length_bones(armature, full_body_tracking, x_cord, y_cord, z_cord): +def fix_zero_length_bones(armature, x_cord, y_cord, z_cord): pre_mode = armature.mode set_active(armature) switch('EDIT') @@ -1671,11 +1699,7 @@ def fix_zero_length_bones(armature, full_body_tracking, x_cord, y_cord, z_cord): if round(bone.head[x_cord], 4) == round(bone.tail[x_cord], 4) \ and round(bone.head[y_cord], 4) == round(bone.tail[y_cord], 4) \ and round(bone.head[z_cord], 4) == round(bone.tail[z_cord], 4): - if bone.name == 'Hips' and full_body_tracking: - bone.tail[z_cord] -= 0.1 - else: - bone.tail[z_cord] += 0.1 - print('YES') + bone.tail[z_cord] += 0.1 switch(pre_mode) @@ -1684,7 +1708,13 @@ 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 + p1 = bone.head + p2 = bone.children[0].head + dist = ((p2[0] - p1[0]) ** 2 + (p2[1] - p1[1]) ** 2 + (p2[2] - p1[2]) ** 2) ** (1/2) + + # Only connect them if the other bone is a certain distance away, otherwise blender will delete them + if dist > 0.005: + bone.tail = bone.children[0].head def update_material_list(self=None, context=None): diff --git a/tools/importer.py b/tools/importer.py index 5459d409..075f2371 100644 --- a/tools/importer.py +++ b/tools/importer.py @@ -146,7 +146,9 @@ def execute(self, context): try: bpy.ops.import_scene.fbx('EXEC_DEFAULT', filepath=file_path, - automatic_bone_orientation=False) # Is true better? There are issues with True + automatic_bone_orientation=False, # Is true better? There are issues with True + use_prepost_rot=False, + use_anim=False) except (TypeError, ValueError): bpy.ops.import_scene.fbx('INVOKE_DEFAULT') except RuntimeError as e: @@ -265,7 +267,10 @@ def execute(self, context): return {'FINISHED'} try: - bpy.ops.mmd_tools.import_model('INVOKE_DEFAULT', scale=0.08, types={'MESH', 'ARMATURE', 'MORPHS'}, log_level='WARNING') + bpy.ops.mmd_tools.import_model('INVOKE_DEFAULT', + scale=0.08, + types={'MESH', 'ARMATURE', 'MORPHS'}, + log_level='WARNING') except AttributeError: bpy.ops.cats_importer.enable_mmd('INVOKE_DEFAULT') except (TypeError, ValueError): @@ -336,7 +341,10 @@ def execute(self, context): context.scene.layers[0] = True try: - bpy.ops.import_scene.fbx('INVOKE_DEFAULT', automatic_bone_orientation=False) + bpy.ops.import_scene.fbx('INVOKE_DEFAULT', + automatic_bone_orientation=False, + use_prepost_rot=False, + use_anim=False) except (TypeError, ValueError): bpy.ops.import_scene.fbx('INVOKE_DEFAULT') diff --git a/tools/settings.py b/tools/settings.py index 943c9896..765d0105 100644 --- a/tools/settings.py +++ b/tools/settings.py @@ -130,7 +130,7 @@ def load_settings(): to_reset_settings = [] # Check for missing entries, reset if necessary - for setting in ['last_supporter_update', 'last_cats_update_check', 'ignore_cats_version']: + for setting in ['last_supporter_update']: if setting not in settings_data and setting not in to_reset_settings: to_reset_settings.append(setting) print('RESET SETTING', setting) @@ -143,7 +143,7 @@ def load_settings(): # Check if timestamps are correct utc_now = datetime.strptime(datetime.now(timezone.utc).strftime(globs.time_format), globs.time_format) - for setting in ['last_supporter_update', 'last_cats_update_check']: + for setting in ['last_supporter_update']: if setting not in to_reset_settings and settings_data.get(setting): try: timestamp = datetime.strptime(settings_data.get(setting), globs.time_format) @@ -184,8 +184,6 @@ def reset_settings(full_reset=False, to_reset_settings=None): if full_reset: settings_data = OrderedDict() settings_data['last_supporter_update'] = None - settings_data['last_cats_update_check'] = None - settings_data['ignore_cats_version'] = None for setting, value in settings_default.items(): settings_data[setting] = value[0] diff --git a/tools/supporter.py b/tools/supporter.py index 43320a0b..c69d54b6 100644 --- a/tools/supporter.py +++ b/tools/supporter.py @@ -49,6 +49,7 @@ preview_collections = {} supporter_data = None reloading = False +auto_updated = False button_list = [] last_update = None @@ -81,12 +82,7 @@ def poll(cls, context): return not reloading def execute(self, context): - global reloading - reloading = True - - thread = Thread(target=download_file, args=[]) - thread.start() - + check_for_update_background(force_update=True) return {'FINISHED'} @@ -244,12 +240,6 @@ def readJson(): def load_supporters(): - # Check for update - global reloading - reloading = True - thread = Thread(target=check_for_update, args=[]) - thread.start() - # Read existing supporter list readJson() @@ -364,8 +354,27 @@ def unload_icons(): print('DONE!') -def check_for_update(): - if update_needed(): +def check_for_update_background(force_update=False): + global reloading, auto_updated + if reloading: + return + + if not force_update: + if auto_updated: + return + auto_updated = True + + reloading = True + + thread = Thread(target=check_for_update, args=[force_update]) + thread.start() + + +def check_for_update(force_update): + if force_update: + print('Force updating supporter list..') + download_file() + elif update_needed(): print('Updating supporter list..') download_file() else: diff --git a/ui/armature.py b/ui/armature.py index 59e34304..2bdfca29 100644 --- a/ui/armature.py +++ b/ui/armature.py @@ -11,6 +11,7 @@ from ..tools import armature_manual as Armature_manual from ..tools.register import register_wrap + @register_wrap class ArmaturePanel(ToolPanel, bpy.types.Panel): bl_idname = 'VIEW3D_PT_armature_v3' @@ -20,7 +21,7 @@ def draw(self, context): layout = self.layout box = layout.box() - updater.check_for_update_background(onstart=True) + updater.check_for_update_background(check_on_startup=True) updater.draw_update_notification_panel(box) col = box.column(align=True) @@ -156,20 +157,9 @@ def draw(self, context): row = split.row(align=True) row.alignment = 'RIGHT' row.scale_y = 1.5 - row.operator(Armature.ModelSettings.bl_idname, text="", icon='MODIFIER') - - if context.scene.full_body: - col.separator() - row = col.row(align=True) - row.scale_y = 0.9 - row.label(text='You can safely ignore the', icon='INFO') - row = col.row(align=True) - row.scale_y = 0.5 - row.label(text='"Spine length zero" warning in Unity.', icon='BLANK1') - col.separator() - else: - col.separator() - col.separator() + row.operator(ModelSettings.bl_idname, text="", icon='MODIFIER') + col.separator() + col.separator() armature_obj = Common.get_armature() if not armature_obj or armature_obj.mode != 'POSE': @@ -187,3 +177,50 @@ def draw(self, context): row = col.row(align=True) row.scale_y = 0.9 row.operator(Armature_manual.PoseToRest.bl_idname, icon='POSE_HLT') + + +@register_wrap +class ModelSettings(bpy.types.Operator): + bl_idname = "cats_armature.settings" + bl_label = "Fix Model Settings" + + def execute(self, context): + return {'FINISHED'} + + def invoke(self, context, event): + dpi_value = Common.get_user_preferences().system.dpi + return context.window_manager.invoke_props_dialog(self, width=dpi_value * 3.25, height=-550) + + def check(self, context): + # Important for changing options + return True + + def draw(self, context): + layout = self.layout + col = layout.column(align=True) + + row = col.row(align=True) + row.active = context.scene.remove_zero_weight + row.prop(context.scene, 'keep_end_bones') + row = col.row(align=True) + row.prop(context.scene, 'keep_upper_chest') + 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') + + col.separator() + row = col.row(align=True) + row.scale_y = 0.7 + row.label(text='The Full Body Tracking Fix', icon='INFO') + row = col.row(align=True) + row.scale_y = 0.7 + row.label(text='is no longer needed for VrChat.', icon_value=Supporter.preview_collections["custom_icons"]["empty"].icon_id) + row = col.row(align=True) + row.scale_y = 0.7 + row.label(text='It\'s still available in Model Options.', icon_value=Supporter.preview_collections["custom_icons"]["empty"].icon_id) + col.separator() diff --git a/ui/manual.py b/ui/manual.py index f1b5eb68..54f5e349 100644 --- a/ui/manual.py +++ b/ui/manual.py @@ -3,6 +3,7 @@ from .. import globs from .main import ToolPanel from .main import layout_split +from ..tools import supporter from ..tools import translate as Translate from ..tools import armature_manual as Armature_manual from ..tools.register import register_wrap @@ -20,12 +21,6 @@ def draw(self, context): button_height = 1 col = box.column(align=True) - # if not context.scene.show_manual_options: - # row = col.row(align=False) - # row.prop(context.scene, 'show_manual_options', emboss=True, expand=False, icon='TRIA_RIGHT') - # else: - # row = col.row(align=True) - # row.prop(context.scene, 'show_manual_options', emboss=True, expand=False, icon='TRIA_DOWN') row = layout_split(col, factor=0.32, align=True) row.scale_y = button_height @@ -46,10 +41,6 @@ def draw(self, context): row.operator(Armature_manual.MergeWeights.bl_idname, text='To Parents') row.operator(Armature_manual.MergeWeightsToActive.bl_idname, text='To Active') - # row = col.row(align=True) - # row.scale_y = button_height - # row.operator('Armature_manual.merge_weights', icon='BONE_DATA') - # Translate col.separator() row = col.row(align=True) @@ -74,7 +65,6 @@ def draw(self, context): row.operator(Translate.TranslateMaterialsButton.bl_idname, text='Materials', icon='MATERIAL') col.separator() - # col.separator() row = col.row(align=True) row.scale_y = 0.85 @@ -84,12 +74,19 @@ def draw(self, context): row.prop(context.scene, 'show_more_options', icon=globs.ICON_REMOVE, emboss=True, expand=False, toggle=False, event=False) col.separator() - row = layout_split(col, factor=0.23, align=True) + row = layout_split(col, factor=0.24, align=True) row.scale_y = button_height row.label(text="Delete:", icon='X') - row.operator(Armature_manual.RemoveZeroWeight.bl_idname, text='Zero Weight Bones') - row.operator(Armature_manual.RemoveConstraints.bl_idname, text='Constraints') + row2 = layout_split(row, factor=0.61, align=True) + row2.operator(Armature_manual.RemoveZeroWeightBones.bl_idname, text='Zero Weight Bones') + row2.operator(Armature_manual.RemoveConstraints.bl_idname, text='Constraints') + + row = layout_split(col, factor=0.24, align=True) + row.scale_y = button_height + row.label(text="") + row.operator(Armature_manual.RemoveZeroWeightGroups.bl_idname, text='Zero Weight Vertex Groups') + col.separator() row = col.row(align=True) row.scale_y = button_height row.operator(Armature_manual.DuplicateBonesButton.bl_idname, icon='GROUP_BONE') @@ -114,28 +111,13 @@ def draw(self, context): row.scale_y = button_height row.operator(Armature_manual.RemoveDoubles.bl_idname, icon='X') - # row = col.row(align=True) - # row.scale_y = 1 - # subcol = layout_split(row, factor=0, align=True) - # subcol.scale_y = button_height - # subcol.operator(Armature_manual.RemoveDoubles.bl_idname, icon='STICKY_UVS_VERT') - # subcol = layout_split(row, factor=0, align=True) - # subcol.scale_y = button_height - # subcol.operator(Armature_manual.RemoveDoublesNormal.bl_idname, text="", icon='X') - col.separator() - # row = col.row(align=True) - # row.scale_y = button_height - # row.label(text="Other:", icon='COLLAPSEMENU') - - row = col.row(align=True) - row.scale_y = 1 - subcol = layout_split(row, factor=0, align=True) - subcol.scale_y = button_height - subcol.operator(Armature_manual.FixFBTButton.bl_idname, icon='MODIFIER') - subcol = layout_split(row, factor=0, align=True) - subcol.scale_y = button_height - subcol.operator(Armature_manual.RemoveFBTButton.bl_idname, text="", icon='X') + row = layout_split(col, factor=0.6, align=True) + row.scale_y = button_height + row.label(text="Full Body Tracking Fix:", icon='ARMATURE_DATA') + row2 = layout_split(row, factor=0.35, align=True) + row2.operator(Armature_manual.FixFBTButton.bl_idname, text='Add') + row2.operator(Armature_manual.RemoveFBTButton.bl_idname, text="Remove") row = col.row(align=True) row.scale_y = button_height diff --git a/ui/optimization.py b/ui/optimization.py index 0084c250..e533f2ef 100644 --- a/ui/optimization.py +++ b/ui/optimization.py @@ -32,12 +32,13 @@ def check_for_smc(): found_very_old_smc = True continue if mod.bl_info['name'] == "Shotariya's Material Combiner": - # print(mod.bl_info['version']) + # print(mod.__name__, mod.bl_info['version']) + # print(addon_utils.check(mod.__name__)) if mod.bl_info['version'] < (2, 1, 1, 2): old_smc_version = True # print('TOO OLD!') continue - if not hasattr(bpy.context.scene, 'smc_ob_data'): + if not addon_utils.check(mod.__name__)[0]: smc_is_disabled = True # print('DISABLED!') continue @@ -45,7 +46,9 @@ def check_for_smc(): # print('FOUND!') old_smc_version = False smc_is_disabled = False + found_very_old_smc = False draw_smc_ui = getattr(import_module(mod.__name__ + '.operators.ui.include'), 'draw_ui') + break # @register_wrap @@ -149,29 +152,26 @@ def draw(self, context): # check_for_smc() # return - # Found very old v1.0 mat comb - if found_very_old_smc and not draw_smc_ui: + # If supported version is outdated + if smc_is_disabled: col.separator() box = col.box() col = box.column(align=True) row = col.row(align=True) row.scale_y = 0.75 - row.label(text="Your Material Combiner is outdated!") - row = col.row(align=True) - row.scale_y = 0.75 - row.label(text="Please update to the latest version.") + row.label(text="Material Combiner is not enabled!") row = col.row(align=True) row.scale_y = 0.75 - row.label(text="Download and install it manually:") + row.label(text="Enable it in your user preferences:") col.separator() row = col.row(align=True) - row.operator(Atlas.ShotariyaButton.bl_idname, icon=globs.ICON_URL) + row.operator(Atlas.EnableSMC.bl_idname, icon='CHECKBOX_HLT') check_for_smc() return - # Draw v2.0 mat comb ui + # If old version is installed if old_smc_version: col.separator() box = col.box() @@ -194,24 +194,29 @@ def draw(self, context): check_for_smc() return - if smc_is_disabled: + # Found very old v1.0 mat comb + if found_very_old_smc: col.separator() box = col.box() col = box.column(align=True) row = col.row(align=True) row.scale_y = 0.75 - row.label(text="Material Combiner is not enabled!") + row.label(text="Your Material Combiner is outdated!") row = col.row(align=True) row.scale_y = 0.75 - row.label(text="Enable it in your user preferences:") + row.label(text="Please update to the latest version.") + row = col.row(align=True) + row.scale_y = 0.75 + row.label(text="Download and install it manually:") col.separator() row = col.row(align=True) - row.operator(Atlas.EnableSMC.bl_idname, icon='CHECKBOX_HLT') + row.operator(Atlas.ShotariyaButton.bl_idname, icon=globs.ICON_URL) check_for_smc() return + # If no matcomb is found if not draw_smc_ui: col.separator() box = col.box() diff --git a/ui/supporter.py b/ui/supporter.py index 21c7b5cb..3e975c8e 100644 --- a/ui/supporter.py +++ b/ui/supporter.py @@ -3,6 +3,7 @@ from .main import ToolPanel from ..tools import supporter as Supporter from ..tools.register import register_wrap +from ..tools.supporter import check_for_update_background @register_wrap @@ -16,6 +17,7 @@ def draw(self, context): col = box.column(align=True) # supporter_data = Supporter.supporter_data + check_for_update_background() row = col.row(align=True) row.label(text='Do you like this plugin and want to support us?') diff --git a/updater.py b/updater.py index 035c0a05..cfb6f707 100644 --- a/updater.py +++ b/updater.py @@ -7,11 +7,11 @@ import pathlib import zipfile import addon_utils -from collections import OrderedDict from threading import Thread - +from collections import OrderedDict from bpy.app.handlers import persistent +no_ver_check = False fake_update = False is_checking_for_update = False @@ -35,6 +35,7 @@ downloads_dir = os.path.join(main_dir, "downloads") resources_dir = os.path.join(main_dir, "resources") ignore_ver_file = os.path.join(resources_dir, "ignore_version.txt") +no_auto_ver_check_file = os.path.join(resources_dir, "no_auto_ver_check.txt") # Get package name, important for panel in user preferences package_name = '' @@ -351,18 +352,23 @@ def draw(self, context): row.prop(context.scene, 'cats_update_action', expand=True) -def check_for_update_background(onstart=False): +def check_for_update_background(check_on_startup=False): global is_checking_for_update, checked_on_startup - if onstart and checked_on_startup: + if check_on_startup and checked_on_startup: # print('ALREADY CHECKED ON STARTUP') return if is_checking_for_update: # print('ALREADY CHECKING') return - is_checking_for_update = True checked_on_startup = True + if check_on_startup and os.path.isfile(no_auto_ver_check_file): + print('AUTO CHECK DISABLED VIA FILE') + return + + is_checking_for_update = True + thread = Thread(target=check_for_update, args=[]) thread.start()