Merge branch 'main' into viseme-dev

This commit is contained in:
Yusarina
2024-07-25 00:57:08 +01:00
committed by GitHub
18 changed files with 712 additions and 200 deletions
+1
View File
@@ -1,2 +1,3 @@
*.pyc *.pyc
.vscode/settings.json
+11
View File
@@ -0,0 +1,11 @@
{
"python.analysis.extraPaths": [
"D:\\SteamLibrary\\steamapps\\common\\Blender\\4.3\\scripts\\addons",
"C:\\Users\\Onan\\AppData\\Roaming\\Blender Foundation\\Blender\\4.3\\extensions\\user_default\\",//C:/Users/Onan/AppData/Roaming/Blender Foundation/Blender/4.0/scripts/addons
"D:\\blender stuff\\blendercodestuff\\4.3"
],
"python.analysis.diagnosticSeverityOverrides": {
"reportInvalidTypeForm": "none"
},
"python.REPL.enableREPLSmartSend": false,
}
+60 -2
View File
@@ -1,8 +1,12 @@
import bpy import bpy
import numpy as np import numpy as np
from .dictionaries import bone_names from .dictionaries import bone_names
import threading
import time
import webbrowser
import typing
from typing import List, Optional from typing import List, Optional, Tuple
from bpy.types import Object, ShapeKey, Mesh, Context from bpy.types import Object, ShapeKey, Mesh, Context
from functools import lru_cache from functools import lru_cache
@@ -13,7 +17,6 @@ def clean_material_names(mesh: Mesh) -> None:
mesh.active_material_index = j mesh.active_material_index = j
mesh.active_material.name = mat.name[:-len(mat.name.rstrip('0')) - 1] mesh.active_material.name = mat.name[:-len(mat.name.rstrip('0')) - 1]
# This will fix faulty uv coordinates, cats did this a other way which can have unintended consequences, # This will fix faulty uv coordinates, cats did this a other way which can have unintended consequences,
# this is the best way i could of think of doing this for the time being, however may need improvements. # this is the best way i could of think of doing this for the time being, however may need improvements.
@@ -57,6 +60,61 @@ def get_armature(context: Context, armature_name: Optional[str] = None) -> Optio
if obj.type == "ARMATURE": if obj.type == "ARMATURE":
return obj return obj
return next((obj for obj in context.view_layer.objects if obj.type == 'ARMATURE'), None) return next((obj for obj in context.view_layer.objects if obj.type == 'ARMATURE'), None)
def get_armatures(self, context: Context) -> List[Tuple[str, str, str]]:
return [(obj.name, obj.name, "") for obj in bpy.data.objects if obj.type == 'ARMATURE']
def get_selected_armature(context: Context) -> Optional[Object]:
if context.scene.selected_armature:
armature = bpy.data.objects.get(context.scene.selected_armature)
if is_valid_armature(armature):
return armature
return None
def set_selected_armature(context: Context, armature: Optional[Object]) -> None:
context.scene.selected_armature = armature.name if armature else ""
def is_valid_armature(armature: Object) -> bool:
if not armature or armature.type != 'ARMATURE':
return False
if not armature.data or not armature.data.bones:
return False
return True
def select_current_armature(context: Context) -> bool:
armature = get_selected_armature(context)
if armature:
bpy.ops.object.select_all(action='DESELECT')
armature.select_set(True)
context.view_layer.objects.active = armature
return True
return False
def get_all_meshes(context: Context) -> List[Object]:
armature = get_selected_armature(context)
if armature and is_valid_armature(armature):
return [obj for obj in bpy.data.objects if obj.type == 'MESH' and obj.parent == armature]
return []
def open_web_after_delay_multi_threaded(delay: typing.Optional[float] = 1.0, url: typing.Union[str, typing.Any] = ""):
thread = threading.Thread(target=open_web_after_delay,args=[delay,url],name="open_browser_thread")
thread.start()
def open_web_after_delay(delay, url):
print("opening browser in "+str(delay)+" seconds.")
time.sleep(delay)
webbrowser.open_new_tab(url)
def duplicatebone(b: bpy.types.EditBone) -> bpy.types.EditBone:
arm = bpy.context.object.data
cb = arm.edit_bones.new(b.name)
cb.head = b.head
cb.tail = b.tail
cb.matrix = b.matrix
cb.parent = b.parent
return cb
def has_shapekeys(mesh_obj: Object) -> bool: def has_shapekeys(mesh_obj: Object) -> bool:
return mesh_obj.data.shape_keys is not None return mesh_obj.data.shape_keys is not None
+52 -52
View File
@@ -5,85 +5,85 @@
# Note from @989onan: Please make sure to make your names are lowercase in this array. I banged my head metaphorically till I figured that out... # Note from @989onan: Please make sure to make your names are lowercase in this array. I banged my head metaphorically till I figured that out...
# Taken from Tuxedo/Cats # Taken from Tuxedo/Cats
bone_names = { bone_names = {
"right_shoulder": ["rightshoulder", "shoulderr", "rshoulder"], "right_shoulder": ["rightshoulder", "shoulderr", "rshoulder", "valvebipedbip01rclavicle"],
"right_arm": ["rightarm", "armr", "rarm", "upperarmr", "rupperarm", "rightupperarm", "upperarmright", "uparmr", "ruparm"], "right_arm": ["rightarm", "armr", "rarm", "upperarmr", "rupperarm", "rightupperarm", "uparmr", "ruparm", "valvebipedbip01rupperarm"],
"right_elbow": ["rightelbow", "elbowr", "relbow", "lowerarmr", "rightlowerarm", "lowerarmr","rlowerarm", "lowerarmright", "lowarmr", "rlowarm", "forearmr","rforearm"], "right_elbow": ["rightelbow", "elbowr", "relbow", "lowerarmr", "rightlowerarm", "lowerarmr","rlowerarm", "lowarmr", "rlowarm", "forearmr","rforearm", "valvebipedbip01rforearm"],
"right_wrist": ["rightwrist", "wristr", "rwrist", "handr", "righthand", "rhand"], "right_wrist": ["rightwrist", "wristr", "rwrist", "handr", "righthand", "rhand", "valvebipedbip01rhand"],
#hand l fingers #hand l fingers
"pinkie_0_r": ["littlefinger0r","pinkie0r","rpinkie0","pinkiemetacarpalr"], "pinkie_0_r": ["littlefinger0r","pinkie0r","rpinkie0","pinkiemetacarpalr"],
"pinkie_1_r": ["littlefinger1r","pinkie1r","rpinkie1","pinkieproximalr"], "pinkie_1_r": ["littlefinger1r","pinkie1r","rpinkie1","pinkieproximalr", "valvebipedbip01rfinger4"],
"pinkie_2_r": ["littlefinger2r","pinkie2r","rpinkie2","pinkieintermediater"], "pinkie_2_r": ["littlefinger2r","pinkie2r","rpinkie2","pinkieintermediater", "valvebipedbip01rfinger41"],
"pinkie_3_r": ["littlefinger3r","pinkie3r","rpinkie3","pinkiedistalr"], "pinkie_3_r": ["littlefinger3r","pinkie3r","rpinkie3","pinkiedistalr", "valvebipedbip01rfinger42"],
"ring_0_r": ["ringfinger0r","ring0r","rring0","ringmetacarpalr"], "ring_0_r": ["ringfinger0r","ring0r","rring0","ringmetacarpalr"],
"ring_1_r": ["ringfinger1r","ring1r","rring1","ringproximalr"], "ring_1_r": ["ringfinger1r","ring1r","rring1","ringproximalr", "valvebipedbip01rfinger3"],
"ring_2_r": ["ringfinger2r","ring2r","rring2","ringintermediater"], "ring_2_r": ["ringfinger2r","ring2r","rring2","ringintermediater", "valvebipedbip01rfinger31"],
"ring_3_r": ["ringfinger3r","ring3r","rring3","ringdistalr"], "ring_3_r": ["ringfinger3r","ring3r","rring3","ringdistalr", "valvebipedbip01rfinger32"],
"middle_0_r": ["middlefinger0r","middle0r","rmiddle0","middlemetacarpalr"], "middle_0_r": ["middlefinger0r","middle0r","rmiddle0","middlemetacarpalr"],
"middle_1_r": ["middlefinger1r","middle1r","rmiddle1","middleproximalr"], "middle_1_r": ["middlefinger1r","middle1r","rmiddle1","middleproximalr", "valvebipedbip01rfinger2"],
"middle_2_r": ["middlefinger2r","middle2r","rmiddle2","middleintermediater"], "middle_2_r": ["middlefinger2r","middle2r","rmiddle2","middleintermediater", "valvebipedbip01rfinger21"],
"middle_3_r": ["middlefinger3r","middle3r","rmiddle3","middledistalr"], "middle_3_r": ["middlefinger3r","middle3r","rmiddle3","middledistalr", "valvebipedbip01rfinger22"],
"index_0_r": ["indexfinger0r","index0r","rindex0","indexmetacarpalr"], "index_0_r": ["indexfinger0r","index0r","rindex0","indexmetacarpalr"],
"index_1_r": ["indexfinger1r","index1r","rindex1","indexproximalr"], "index_1_r": ["indexfinger1r","index1r","rindex1","indexproximalr", "valvebipedbip01rfinger1"],
"index_2_r": ["indexfinger2r","index2r","rindex2","indexintermediater"], "index_2_r": ["indexfinger2r","index2r","rindex2","indexintermediater", "valvebipedbip01rfinger11"],
"index_3_r": ["indexfinger3r","index3r","rindex3","indexdistalr"], "index_3_r": ["indexfinger3r","index3r","rindex3","indexdistalr", "valvebipedbip01rfinger12"],
"thumb_0_r": ["thumb0r","rthumb0","thumbmetacarpalr"], "thumb_0_r": ["thumb0r","rthumb0","thumbmetacarpalr"],
"thumb_1_r": ['thumb1r',"rthumb1","thumbproximalr"], "thumb_1_r": ['thumb1r',"rthumb1","thumbproximalr", "valvebipedbip01rfinger0"],
"thumb_2_r": ['thumb2r',"rthumb2","thumbintermediater"], "thumb_2_r": ['thumb2r',"rthumb2","thumbintermediater", "valvebipedbip01rfinger01"],
"thumb_3_r": ['thumb3r',"rthumb3","thumbdistalr"], "thumb_3_r": ['thumb3r',"rthumb3","thumbdistalr", "valvebipedbip01rfinger02"],
"right_leg": ["rightleg", "legr", "rleg", "upperlegr", "rupperleg", "thighr", "rightupperleg", "upperlegright", "uplegr", "rupleg"], "right_leg": ["rightleg", "legr", "rleg", "upperlegr", "rupperleg", "thighr", "rightupperleg", "uplegr", "rupleg", "valvebipedbip01rthigh"],
"right_knee": ["rightknee", "kneer", "rknee", "lowerlegr", "calfr", "rlowerleg", "rcalf", "rightlowerleg", "lowerlegright", "lowlegr", "rlowleg"], "right_knee": ["rightknee", "kneer", "rknee", "lowerlegr", "calfr", "rlowerleg", "rcalf", "rightlowerleg", "lowlegr", "rlowleg", "valvebipedbip01rcalf"],
"right_ankle": ["rightankle", "ankler", "rankle", "footright", "footr", "rfoot", "rightfoot", "rightfeet", "feetright", "rfeet", "feetr"], "right_ankle": ["rightankle", "ankler", "rankle", "rightfoot", "footr", "rfoot", "rightfoot", "rightfeet", "feetright", "rfeet", "feetr", "valvebipedbip01rfoot"],
"right_toe": ["righttoe", "toeright", "toer", "rtoe", "toesr", "rtoes"], "right_toe": ["righttoe", "toeright", "toer", "rtoe", "toesr", "rtoes", "valvebipedbip01rtoe0"],
"left_shoulder": ["leftshoulder", "shoulderl", "lshoulder"], "left_shoulder": ["leftshoulder", "shoulderl", "lshoulder", "valvebipedbip01lclavicle"],
"left_arm": ["leftarm", "arml", "rarm", "upperarml", "lupperarm", "leftupperarm", "upperarmleft", "uparml", "luparm"], "left_arm": ["leftarm", "arml", "rarm", "upperarml", "lupperarm", "leftupperarm", "uparml", "luparm", "valvebipedbip01lupperarm"],
"left_elbow": ["leftelbow", "elbowl", "lelbow", "lowerarml", "leftlowerarm", "lowerarmleft", "lowerarml", "llowerarm", "lowarml", "llowarm", "forearml","lforearm"], "left_elbow": ["leftelbow", "elbowl", "lelbow", "lowerarml", "leftlowerarm", "lowerarml", "llowerarm", "lowarml", "llowarm", "forearml","lforearm", "valvebipedbip01lforearm"],
"left_wrist": ["leftwrist", "wristl", "lwrist", "handl", "lefthand", "lhand"], "left_wrist": ["leftwrist", "wristl", "lwrist", "handl", "lefthand", "lhand", "valvebipedbip01lhand"],
#hand l fingers #hand l fingers
"pinkie_0_l": ["pinkiefinger0l","pinkie0l","lpinkie0","pinkiemetacarpall"], "pinkie_0_l": ["pinkiefinger0l","pinkie0l","lpinkie0","pinkiemetacarpall"],
"pinkie_1_l": ["littlefinger1l","pinkie1l","lpinkie1","pinkieproximall"], "pinkie_1_l": ["littlefinger1l","pinkie1l","lpinkie1","pinkieproximall", "valvebipedbip01lfinger4"],
"pinkie_2_l": ["littlefinger2l","pinkie2l","lpinkie2","pinkieintermediatel"], "pinkie_2_l": ["littlefinger2l","pinkie2l","lpinkie2","pinkieintermediatel", "valvebipedbip01lfinger41"],
"pinkie_3_l": ["littlefinger3l","pinkie3l","lpinkie3","pinkiedistall"], "pinkie_3_l": ["littlefinger3l","pinkie3l","lpinkie3","pinkiedistall", "valvebipedbip01lfinger42"],
"ring_0_l": ["ringfinger0l","ring0l","lring0","ringmetacarpall"], "ring_0_l": ["ringfinger0l","ring0l","lring0","ringmetacarpall"],
"ring_1_l": ["ringfinger1l","ring1l","lring1","ringproximall"], "ring_1_l": ["ringfinger1l","ring1l","lring1","ringproximall", "valvebipedbip01lfinger3"],
"ring_2_l": ["ringfinger2l","ring2l","lring2","ringintermediatel"], "ring_2_l": ["ringfinger2l","ring2l","lring2","ringintermediatel", "valvebipedbip01lfinger31"],
"ring_3_l": ["ringfinger3l","ring3l","lring3","ringdistall"], "ring_3_l": ["ringfinger3l","ring3l","lring3","ringdistall", "valvebipedbip01lfinger32"],
"middle_0_l": ["middlefinger0l","middle_0l","lmiddle0","middlemetacarpall"], "middle_0_l": ["middlefinger0l","middle_0l","lmiddle0","middlemetacarpall"],
"middle_1_l": ["middlefinger1l","middle_1l","lmiddle1","middleproximall"], "middle_1_l": ["middlefinger1l","middle_1l","lmiddle1","middleproximall", "valvebipedbip01lfinger2"],
"middle_2_l": ["middlefinger2l","middle_2l","lmiddle2","middleintermediatel"], "middle_2_l": ["middlefinger2l","middle_2l","lmiddle2","middleintermediatel", "valvebipedbip01lfinger21"],
"middle_3_l": ["middlefinger3l","middle_3l","lmiddle3","middledistall"], "middle_3_l": ["middlefinger3l","middle_3l","lmiddle3","middledistall", "valvebipedbip01lfinger22"],
"index_0_l": ["indexfinger0l","index0l","lindex0","indexmetacarpall"], "index_0_l": ["indexfinger0l","index0l","lindex0","indexmetacarpall"],
"index_1_l": ["indexfinger1l","index1l","lindex1","indexproximall"], "index_1_l": ["indexfinger1l","index1l","lindex1","indexproximall", "valvebipedbip01lfinger1"],
"index_2_l": ["indexfinger2l","index2l","lindex2","indexintermediatel"], "index_2_l": ["indexfinger2l","index2l","lindex2","indexintermediatel", "valvebipedbip01lfinger11"],
"index_3_l": ["indexfinger3l","index3l","lindex3","indexdistall"], "index_3_l": ["indexfinger3l","index3l","lindex3","indexdistall", "valvebipedbip01lfinger12"],
"thumb_0_l": ["thumb0l","lthumb0","thumbmetacarpall"], "thumb_0_l": ["thumb0l","lthumb0","thumbmetacarpall"],
"thumb_1_l": ['thumb1l',"lthumb1","thumbproximall"], "thumb_1_l": ['thumb1l',"lthumb1","thumbproximall", "valvebipedbip01lfinger0"],
"thumb_2_l": ['thumb2l',"lthumb2","thumbintermediatel"], "thumb_2_l": ['thumb2l',"lthumb2","thumbintermediatel", "valvebipedbip01lfinger01"],
"thumb_3_l": ['thumb3l',"lthumb3","thumbdistall"], "thumb_3_l": ['thumb3l',"lthumb3","thumbdistall", "valvebipedbip01lfinger02"],
"left_leg": ["leftleg", "legl", "lleg", "upperlegl", "lupperleg", "thighl", "leftupperleg", "upperlegleft", "uplegl", "lupleg"], "left_leg": ["leftleg", "legl", "lleg", "upperlegl", "lupperleg", "thighl", "leftupperleg", "uplegl", "lupleg", "valvebipedbip01lthigh"],
"left_knee": ["leftknee", "kneel", "lknee", "lowerlegl", "llowerleg", "calfl", "lcalf", "leftlowerleg", "lowerlegleft", 'lowlegl', 'llowleg'], "left_knee": ["leftknee", "kneel", "lknee", "lowerlegl", "llowerleg", "calfl", "lcalf", "leftlowerleg", 'lowlegl', 'llowleg', "valvebipedbip01lcalf"],
"left_ankle": ["leftankle", "anklel", "rankle", "footleft", "footl", "lfoot", "leftfoot", "leftfeet", "feetleft", "lfeet", "feetl"], "left_ankle": ["leftankle", "anklel", "rankle", "leftfoot", "footl", "lfoot", "leftfoot", "leftfeet", "feetleft", "lfeet", "feetl", "valvebipedbip01lfoot"],
"left_toe": ["lefttoe", "toeleft", "toel", "ltoe", "toesl", "ltoes"], "left_toe": ["lefttoe", "toeleft", "toel", "ltoe", "toesl", "ltoes", "valvebipedbip01ltoe0"],
"hips": ["pelvis", "hips", "hip"], "hips": ["pelvis", "hips", "valvebipedbip01pelvis"],
"spine": ["torso", "spine"], "spine": ["torso", "spine", "valvebipedbip01spine"],
"chest": ["chest"], "chest": ["chest", "valvebipedbip01spine1"],
"upper_chest": ["upperchest", "chestupper"], "upper_chest": ["upperchest", "valvebipedbip01spine4"],
"neck": ["neck"], "neck": ["neck", "valvebipedbip01neck1"],
"head": ["head", "cabeza"], "head": ["head", "valvebipedbip01head1"],
"left_eye": ["eyeleft", "lefteye", "eyel", "leye"], "left_eye": ["eyeleft", "lefteye", "eyel", "leye"],
"right_eye": ["eyeright", "righteye", "eyer", "reye"], "right_eye": ["eyeright", "righteye", "eyer", "reye"],
} }
+48 -12
View File
@@ -1,17 +1,53 @@
import bpy import bpy
# Importers which don't need much code should be added here, however if a importer needs alot of code # Importers which don't need much code should be added here, however if a importer needs alot of code
# Like the PMX and PMD importers, they should be added to their own files. # Like the PMX and PMD importers, they should be added to their own files and referenced in the import_types str->lambda dictionary.
#See below comments on how the system works. - @989onan
# FBX Importer settings borrowed form Cat's Blender Plugin import importlib.util
def import_fbx(filepath): import os
try: import typing
bpy.ops.import_scene.fbx( from .import_pmx import import_pmx
filepath=filepath, from .import_pmd import import_pmd
automatic_bone_orientation=False,
use_prepost_rot=False, if importlib.util.find_spec("io_scene_valvesource") is not None:
use_anim=False #from .....scripts.addons.io_scene_valvesource.import_smd import SmdImporter #<- use this to check if your IDE is working properly. idfk
) from io_scene_valvesource.import_smd import SmdImporter #ignore IDE bitching this is fine, trust me, also above comment should be okay to an IDE usually if set up right. ^_^ - @989onan
except (TypeError, ValueError) as e:
print(f"Error importing FBX: {str(e)}") def import_multi_files(method = None, directory: typing.Optional[str] = None, files: list[dict[str,str]] = None, filepath: typing.Optional[str] = ""):
if not files:
method(directory, filepath)
else:
for file in files:
fullpath = os.path.join(directory,os.path.basename(file["name"]))
print("run method!")
method(directory, fullpath)
#each import should map to a type. even in the case that multiple methods should import together, or have the same import method. Make sure the lambdas match so they get grouped together
#In the case of a file importer that takes only one file argument and each one needs individual import, use above method. (example of it in use is ".dae" format)
import_types: dict[str, typing.Callable[[str, list[dict[str,str]], str], None]] = {
"fbx": (lambda directory, files, filepath : bpy.ops.import_scene.fbx(files=files, directory=directory, filepath=filepath,automatic_bone_orientation=False,use_prepost_rot=False,use_anim=False)),
"smd": (lambda directory, files, filepath : eval("bpy."+SmdImporter.bl_idname+".(files=files, directory=directory, filepath=filepath)")),
"dmx": (lambda directory, files, filepath: eval("bpy."+SmdImporter.bl_idname+".(files=files, directory=directory, filepath=filepath)")),
"gltf": (lambda directory, files, filepath : bpy.ops.import_scene.gltf(files=files, filepath=filepath)),
"glb": (lambda directory, files, filepath : bpy.ops.import_scene.gltf(files=files, filepath=filepath)),
"qc": (lambda directory, files, filepath : eval("bpy."+SmdImporter.bl_idname+".(files=files, directory=directory, filepath=filepath)")),
"obj": (lambda directory, files, filepath : bpy.ops.wm.obj_import(files=files, directory=directory, filepath=filepath)),
"dae": (lambda directory, files, filepath : import_multi_files(directory=directory, files=files, filepath=filepath, method = (lambda directory, filepath: bpy.ops.wm.collada_import(filepath=filepath, auto_connect = True, find_chains = True, fix_orientation = True)))),
"3ds": (lambda directory, files, filepath : bpy.ops.import_scene.max3ds(files=files, directory=directory, filepath=filepath)),
"stl": (lambda directory, files, filepath : bpy.ops.import_mesh.stl(files=files, directory=directory, filepath=filepath)),
"mtl": (lambda directory, files, filepath : bpy.ops.wm.obj_import(files=files, directory=directory, filepath=filepath)),
"x3d": (lambda directory, files, filepath : bpy.ops.import_scene.x3d(files=files, directory=directory, filepath=filepath)),
"wrl": (lambda directory, files, filepath : bpy.ops.import_scene.x3d(files=files, directory=directory, filepath=filepath)),
"vmd": (lambda directory, files, filepath : import_multi_files(directory=directory, files=files, filepath=filepath, method = (lambda directory, filepath: bpy.ops.tuxedo.import_mmd_animation(directory=directory, filepath=filepath)))),
"pmx": (lambda directory, files, filepath : import_pmx(filepath)),
"pmd": (lambda directory, files, filepath : import_pmd(filepath)),
}
def concat_imports_filter(imports):
names = ""
for importer in imports.keys():
names = names+"*."+importer+";"
return names
imports = concat_imports_filter(import_types)
+3
View File
@@ -0,0 +1,3 @@
{
"language": 0
}
+12
View File
@@ -1,6 +1,8 @@
import bpy import bpy
from ..functions.translations import t, get_languages_list, update_language from ..functions.translations import t, get_languages_list, update_language
from ..core.addon_preferences import get_preference from ..core.addon_preferences import get_preference
from .common import get_armatures
def register() -> None: def register() -> None:
default_language = get_preference("language", 0) default_language = get_preference("language", 0)
@@ -35,6 +37,12 @@ def register() -> None:
max=2.0 max=2.0
) )
bpy.types.Scene.selected_armature = bpy.props.EnumProperty(
items=get_armatures,
name="Selected Armature",
description="The currently selected armature for Avatar Toolkit operations"
)
def unregister() -> None: def unregister() -> None:
if hasattr(bpy.types.Scene, "avatar_toolkit_language"): if hasattr(bpy.types.Scene, "avatar_toolkit_language"):
del bpy.types.Scene.avatar_toolkit_language del bpy.types.Scene.avatar_toolkit_language
@@ -53,3 +61,7 @@ def unregister() -> None:
if hasattr(bpy.types.Scene, "shape_intensity"): if hasattr(bpy.types.Scene, "shape_intensity"):
del bpy.types.Scene.shape_intensity del bpy.types.Scene.shape_intensity
if hasattr(bpy.types.Scene, "selected_armature"):
del bpy.types.Scene.selected_armature
+1 -1
View File
@@ -49,7 +49,7 @@ def toposort(deps_dict):
unsorted.append(value) unsorted.append(value)
deps_dict = {value : deps_dict[value] - sorted_values for value in unsorted} deps_dict = {value : deps_dict[value] - sorted_values for value in unsorted}
sort_order(sorted_list) #to sort by 'bl_order' so we can choose how things may appear in the ui #sort_order(sorted_list) #to sort by 'bl_order' so we can choose how things may appear in the ui
return sorted_list return sorted_list
+27 -23
View File
@@ -1,8 +1,8 @@
import bpy import bpy
import re import re
from typing import List, Tuple, Optional from typing import List, Tuple, Optional, Set
from bpy.types import Material, Operator, Context, Object from bpy.types import Material, Operator, Context, Object
from ..core.common import clean_material_names from ..core.common import clean_material_names, get_selected_armature, is_valid_armature, get_all_meshes
from ..core.register import register_wrap from ..core.register import register_wrap
from ..functions.translations import t from ..functions.translations import t
@@ -65,49 +65,54 @@ class CombineMaterials(Operator):
@classmethod @classmethod
def poll(cls, context: Context) -> bool: def poll(cls, context: Context) -> bool:
return context.active_object is not None armature = get_selected_armature(context)
return armature is not None and is_valid_armature(armature)
def execute(self, context: Context) -> set: def execute(self, context: Context) -> Set[str]:
bpy.ops.object.mode_set(mode='OBJECT') armature = get_selected_armature(context)
armature: Optional[Object] = next((obj for obj in bpy.data.objects if obj.type == 'ARMATURE'), None)
if not armature: if not armature:
self.report({'WARNING'}, "No armature selected")
return {'CANCELLED'} return {'CANCELLED'}
meshes: List[Object] = [obj for obj in bpy.data.objects if obj.type == 'MESH' and 'Armature' in obj.modifiers and obj.modifiers['Armature'].object == armature] context.view_layer.objects.active = armature
if not meshes:
return {'CANCELLED'}
bpy.ops.object.mode_set(mode='OBJECT') bpy.ops.object.mode_set(mode='OBJECT')
meshes = get_all_meshes(context)
if not meshes:
self.report({'WARNING'}, "No meshes found for the selected armature")
return {'CANCELLED'}
self.consolidate_materials(meshes) self.consolidate_materials(meshes)
self.remove_unused_materials() self.remove_unused_materials()
self.cleanmatslots() self.cleanmatslots()
self.clean_material_names() self.clean_material_names()
bpy.ops.object.mode_set(mode='OBJECT')
bpy.context.view_layer.objects.active = armature
return {'FINISHED'} return {'FINISHED'}
def consolidate_materials(self, objects: List[Object]) -> None: def consolidate_materials(self, meshes: List[Object]) -> None:
mat_mapping: dict = {} mat_mapping: Dict[str, Material] = {}
num_combined: int = 0 num_combined: int = 0
for ob in objects: for mesh in meshes:
for slot in ob.material_slots: for slot in mesh.material_slots:
mat: Optional[Material] = slot.material mat: Optional[Material] = slot.material
if mat: if mat:
base_name: str = get_base_name(mat.name) base_name: str = get_base_name(mat.name)
if base_name in mat_mapping: if base_name in mat_mapping:
base_mat: Material = mat_mapping[base_name] base_mat: Material = mat_mapping[base_name]
if materials_match(base_mat, mat): try:
consolidate_textures(base_mat, mat) if materials_match(base_mat, mat):
num_combined += 1 consolidate_textures(base_mat, mat)
slot.material = base_mat num_combined += 1
slot.material = base_mat
except AttributeError:
# Skip this material if there's an attribute mismatch
continue
else: else:
mat_mapping[base_name] = mat mat_mapping[base_name] = mat
report_consolidated(self, num_combined) report_consolidated(self, num_combined)
def remove_unused_materials(self) -> None: def remove_unused_materials(self) -> None:
for mat in bpy.data.materials: for mat in bpy.data.materials:
if not any(obj for obj in bpy.data.objects if obj.material_slots and mat.name in obj.material_slots): if not any(obj for obj in bpy.data.objects if obj.material_slots and mat.name in obj.material_slots):
@@ -125,4 +130,3 @@ class CombineMaterials(Operator):
for obj in bpy.data.objects: for obj in bpy.data.objects:
if obj.type == 'MESH': if obj.type == 'MESH':
clean_material_names(obj) clean_material_names(obj)
+117
View File
@@ -0,0 +1,117 @@
import bpy
from ..core import common
from ..core import register_wrap
from .translations import t
import re
@register_wrap
class CreateDigitigradeLegs(bpy.types.Operator):
bl_idname = "avatar_toolkit.createdigitigradelegs"
bl_label = t('Tools.create_digitigrade_legs.label')
bl_description = t('Tools.create_digitigrade_legs.desc')
@classmethod
def poll(cls, context):
if(context.active_object is None):
return False
if(context.selected_editable_bones is not None):
if(len(context.selected_editable_bones) == 2):
return True
return False
def execute(self, context):
for digi0 in context.selected_editable_bones:
digi1: bpy.types.EditBone = None
digi2: bpy.types.EditBone = None
digi3: bpy.types.EditBone = None
try:
digi1 = digi0.children[0]
digi2 = digi1.children[0]
digi3 = digi2.children[0]
except:
print("bone format incorrect! Please select a chain of 4 continious bones!") #TODO: Show this to user. this is an error.
return {'CANCELLED'}
digi4 = None
try:
digi4 = digi3.children[0]
except:
print("no toe bone. Continuing.")
digi0.select = True
digi1.select = True
digi2.select = True
digi3.select = True
if(digi4):
digi4.select = True
bpy.ops.armature.roll_clear()
bpy.ops.armature.select_all(action='DESELECT')
#creating transform for upper leg
digi0.select = True
bpy.ops.transform.create_orientation(name="Toolkit_digi0", overwrite=True)
bpy.ops.armature.select_all(action='DESELECT')
#duplicate digi0 and assign it to thigh
thigh = common.duplicatebone(digi0)
bpy.ops.armature.select_all(action='DESELECT')
#make digi2 parrallel to digi1
digi2.align_orientation(digi0)
#extrude thigh
thigh.select_tail = True
bpy.ops.armature.extrude_move(ARMATURE_OT_extrude={"forked":False},TRANSFORM_OT_translate=None)
#set new bone to calf varible
bpy.ops.armature.select_more()
calf = context.selected_bones[0]
bpy.ops.armature.select_all(action='DESELECT')
#set calf end to digi2 end
calf.tail = digi2.tail
#make copy of calf, flip it, and then align bone so that it's head is moved to match in align phase
flipedcalf = common.duplicatebone(calf)
bpy.ops.armature.select_all(action='DESELECT')
flipedcalf.select = True
bpy.ops.armature.switch_direction()
bpy.ops.armature.select_all(action='DESELECT')
flippeddigi1 = common.duplicatebone(digi1)
bpy.ops.armature.select_all(action='DESELECT')
flippeddigi1.select = True
bpy.ops.armature.switch_direction()
bpy.ops.armature.select_all(action='DESELECT')
#align flipped calf to flipped middle leg to move the head
flipedcalf.align_orientation(flippeddigi1)
flipedcalf.length = flippeddigi1.length
#assign calf tail to flipped calf head so it moves calf's tail to be out at the perfect parallelagram
calf.head = flipedcalf.tail
#delete helper bones
bpy.ops.armature.select_all(action='DESELECT')
flippeddigi1.select = True
bpy.ops.armature.delete()
bpy.ops.armature.select_all(action='DESELECT')
flipedcalf.select = True
bpy.ops.armature.delete()
bpy.ops.armature.select_all(action='DESELECT')
#reparent the foot to the new calf so it will be part of the new foot IK chain
digi3.parent = calf
#Tada! It's done! now to rename the old 3 segments that make up the old part to noik so resonite doesn't try to select them
digi0.name = re.compile(re.escape("<noik>"), re.IGNORECASE).sub("",digi0.name)+"<noik>"
digi1.name = re.compile(re.escape("<noik>"), re.IGNORECASE).sub("",digi1.name)+"<noik>"
digi2.name = re.compile(re.escape("<noik>"), re.IGNORECASE).sub("",digi2.name)+"<noik>"
#finally fully done!
return {'FINISHED'}
+186
View File
@@ -0,0 +1,186 @@
import bpy
from bpy.types import Operator
from bpy_extras.io_utils import ImportHelper
from ..core.register import register_wrap
from ..core.importer import imports, import_types
from ..functions.translations import t
import pathlib
import os
from ..core import common
@register_wrap
class ImportAnyModel(Operator, ImportHelper):
bl_idname = 'avatar_toolkit.import_any_model'
bl_label = t('Tools.import_any_model.label')
bl_description = t('Tools.import_any_model.desc')
bl_options = {'REGISTER', 'UNDO'}
files: bpy.props.CollectionProperty(type=bpy.types.OperatorFileListElement, options={'HIDDEN', 'SKIP_SAVE'})
filter_glob: bpy.props.StringProperty(default = imports, options={'HIDDEN','SKIP_SAVE'})
directory: bpy.props.StringProperty(maxlen=1024, subtype='FILE_PATH', options={'HIDDEN', 'SKIP_SAVE'})
#since I wrote this myself, a bit more efficent than cats. mostly - @989onan
def execute(self, context: bpy.types.Context):
file_grouping_dict: dict[str, list[dict[str,str]]] = dict()#group our files so our importers can import them together. in the case of OBJ+MTL and others that need grouped files, this is extremely important.
#check if we are importing multiple files
is_multi = False
try:
for file in self.files:
pass
is_multi = True
except Exception as e:
is_multi = False
print(e)
#put the files together into lists of same importers
if(is_multi):
for file in self.files:
fullpath = os.path.join(self.directory,os.path.basename(file.name))
name = pathlib.Path(fullpath).suffix.replace(".","")
#this makes sure our imports that should be grouped stay together.
#basically the method checks for if the first value has a lambda with the same bytecode as another lambda, then it will use that value's key (ex:"obj"<->"mtl" or "fbx"), keeping same importers together
try:
name2 = next(key for key,value in import_types.items() if value.__code__.co_code == import_types[name].__code__.co_code)
print(name +" is the same importer as "+name2+", grouping.")
name = name2
except Exception as e:
print("error when trying to find a value of the same value in the kinds of importers. May just be an import type that's a singlet:")
print(e)
if name not in file_grouping_dict: file_grouping_dict[name] = []
file_grouping_dict[name].append({"name": os.path.basename(file.name)}) #emulate passing a list of files.
else:
fullpath: str = os.path.join(os.path.dirname(self.filepath),os.path.basename(self.filepath))
name = pathlib.Path(fullpath).suffix.replace(".","")
if name not in file_grouping_dict: file_grouping_dict[name] = []
file_grouping_dict[name].append({"name": fullpath}) #emulate passing a list of files.
#import the files together to make sure things like obj import together. This is important
for file_group_name,files in file_grouping_dict.items():
try:
if(self.directory):
print(files)
import_types[file_group_name](self.directory,files,self.filepath)
else:
import_types[file_group_name]("",files,self.filepath) #give an empty directory, works just fine for 90%
except AttributeError as e:
print("Warning, you may not have the required importer!")
common.open_web_after_delay_multi_threaded(delay=12, url=t('Importing.importer_search_term').format(extension = file_group_name))
self.report({'ERROR'},t('Importing.need_importer').format(extension = file_group_name))
print("importer error was:")
print(e)
return {'FINISHED'}
#This needs to be done with our own MMD importer:
"""
#stolen from cats. Oh wait I made this code riiiiiiight - @989onan
@register_wrap
class ImportMMDAnimation(bpy.types.Operator, ImportHelper):
bl_idname = 'avatar_toolkit.import_mmd_animation'
bl_label = t('Importer.mmd_anim_importer.label')
bl_description = t('Importer.mmd_anim_importer.desc')
bl_options = {'INTERNAL', 'UNDO'}
filter_glob: bpy.props.StringProperty(
default="*.vmd",
options={'HIDDEN'}
)
files: bpy.props.CollectionProperty(type=bpy.types.OperatorFileListElement, options={'HIDDEN', 'SKIP_SAVE'})
directory: bpy.props.StringProperty(maxlen=1024, subtype='FILE_PATH', options={'HIDDEN', 'SKIP_SAVE'})
filepath: bpy.props.StringProperty()
@classmethod
def poll(cls, context):
if common.get_armature(context) is None:
return False
return True
def execute(self, context):
# Make sure that the first layer is visible
if hasattr(context.scene, 'layers'):
context.scene.layers[0] = True
filename, extension = os.path.splitext(self.filepath)
if(extension == ".vmd"):
#A dictionary to change the current model to MMD importer compatable temporarily
bonedict = {
"chest":"UpperBody",
"neck":"Neck",
"head":"Head",
"hips":"Center",
"spine":"LowerBody",
"right_wrist":"Wrist_R",
"right_elbow":"Elbow_R",
"right_arm":"Arm_R",
"right_shoulder":"Shoulder_R",
"right_leg":"Leg_R",
"right_knee":"Knee_R",
"right_ankle":"Ankle_R",
"right_toe":"Toe_R",
"left_wrist":"Wrist_L",
"left_elbow":"Elbow_L",
"left_arm":"Arm_L",
"left_shoulder":"Shoulder_L",
"left_leg":"Leg_L",
"left_knee":"Knee_L",
"left_ankle":"Ankle_L",
"left_toe":"Toe_L"
}
armature = common.get_armature(context)
common.unselect_all()
common.Set_Mode(context, 'OBJECT')
common.unselect_all()
common.set_active(armature)
orig_names = dict()
reverse_bone_lookup = dict()
for (preferred_name, name_list) in bone_names.items():
for name in name_list:
reverse_bone_lookup[name] = preferred_name
for bone in armature.data.bones:
if common.simplify_bonename(bone.name) in reverse_bone_lookup and reverse_bone_lookup[common.simplify_bonename(bone.name)] in bonedict:
orig_names[bonedict[reverse_bone_lookup[common.simplify_bonename(bone.name)]]] = bone.name
bone.name = bonedict[reverse_bone_lookup[common.simplify_bonename(bone.name)]]
try:
bpy.ops.mmd_tools.import_vmd(filepath=self.filepath,bone_mapper='RENAMED_BONES',use_underscore=True, dictionary='INTERNAL')
except AttributeError as e:
print("importer error was:")
print(e)
print(t('Importing.importer_search_term'))
common.open_web_after_delay_multi_threaded(delay=12, url=t('Importing.importer_search_term').format(extension = "MMD"))
self.report({'ERROR'},t('Importing.need_importer').format(extension = "MMD"))
return {'CANCELLED'}
#iterate through bones and put them back, therefore blender API will change the animation to be correct.
#this is because renaming bones fixes the animation targets in the data model.
for bone in armature.data.bones:
if common.simplify_bonename(bone.name) in orig_names:
bone.name = orig_names[common.simplify_bonename(bone.name)]
common.unselect_all()
common.Set_Mode(context, 'OBJECT')
common.unselect_all()
common.set_active(armature)
return {'FINISHED'} """
+15 -11
View File
@@ -1,8 +1,8 @@
import bpy import bpy
from typing import List, Optional from typing import List, Optional, Set
from bpy.types import Operator, Context, Object from bpy.types import Operator, Context, Object
from ..core.register import register_wrap from ..core.register import register_wrap
from ..core.common import fix_uv_coordinates from ..core.common import fix_uv_coordinates, get_selected_armature, is_valid_armature, select_current_armature, get_all_meshes
from ..functions.translations import t from ..functions.translations import t
@register_wrap @register_wrap
@@ -14,21 +14,23 @@ class JoinAllMeshes(Operator):
@classmethod @classmethod
def poll(cls, context: Context) -> bool: def poll(cls, context: Context) -> bool:
return context.mode == 'OBJECT' armature = get_selected_armature(context)
return armature is not None and is_valid_armature(armature)
def execute(self, context: Context) -> set: def execute(self, context: Context) -> Set[str]:
self.join_all_meshes(context) self.join_all_meshes(context)
return {'FINISHED'} return {'FINISHED'}
def join_all_meshes(self, context: Context) -> None: def join_all_meshes(self, context: Context) -> None:
if not bpy.data.objects: if not select_current_armature(context):
self.report({'INFO'}, "No objects in the scene") self.report({'WARNING'}, "No armature selected")
return return
armature = get_selected_armature(context)
bpy.ops.object.mode_set(mode='OBJECT') bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.select_all(action='DESELECT') bpy.ops.object.select_all(action='DESELECT')
meshes: List[Object] = [obj for obj in bpy.data.objects if obj.type == 'MESH'] meshes: List[Object] = get_all_meshes(context)
for mesh in meshes: for mesh in meshes:
mesh.select_set(True) mesh.select_set(True)
@@ -43,6 +45,8 @@ class JoinAllMeshes(Operator):
else: else:
self.report({'WARNING'}, "No mesh objects selected") self.report({'WARNING'}, "No mesh objects selected")
context.view_layer.objects.active = armature
@register_wrap @register_wrap
class JoinSelectedMeshes(Operator): class JoinSelectedMeshes(Operator):
bl_idname = "avatar_toolkit.join_selected_meshes" bl_idname = "avatar_toolkit.join_selected_meshes"
@@ -52,17 +56,17 @@ class JoinSelectedMeshes(Operator):
@classmethod @classmethod
def poll(cls, context: Context) -> bool: def poll(cls, context: Context) -> bool:
return context.mode == 'OBJECT' return context.mode == 'OBJECT' and len([obj for obj in context.selected_objects if obj.type == 'MESH']) > 1
def execute(self, context: Context) -> set: def execute(self, context: Context) -> Set[str]:
self.join_selected_meshes(context) self.join_selected_meshes(context)
return {'FINISHED'} return {'FINISHED'}
def join_selected_meshes(self, context: Context) -> None: def join_selected_meshes(self, context: Context) -> None:
selected_objects: List[Object] = [obj for obj in bpy.context.selected_objects if obj.type == 'MESH'] selected_objects: List[Object] = [obj for obj in bpy.context.selected_objects if obj.type == 'MESH']
if not selected_objects: if len(selected_objects) < 2:
self.report({'WARNING'}, "No mesh objects selected") self.report({'WARNING'}, "Please select at least two mesh objects")
return return
bpy.ops.object.mode_set(mode='OBJECT') bpy.ops.object.mode_set(mode='OBJECT')
+122
View File
@@ -0,0 +1,122 @@
from ast import Dict
from itertools import count
import bpy
import re
from typing import List, Tuple, Optional, TypedDict
from bpy.types import Material, Operator, Context, Object
from ..core.register import register_wrap
from ..core.common import get_selected_armature, is_valid_armature, select_current_armature, get_all_meshes
class meshEntry(TypedDict):
mesh: bpy.types.Object
shapekeys: list[str]
@register_wrap
class RemoveDoublesSafely(Operator):
bl_idname = "avatar_toolkit.remove_doubles_safely"
bl_label = "Remove Doubles Safely"
bl_description = "Remove Doubles on all meshes, making sure to not fuse things like mouths together."
bl_options = {'REGISTER', 'UNDO'}
objects_to_do: list[meshEntry] = []
merge_distance: bpy.props.FloatProperty(default=0.0001)
@classmethod
def poll(cls, context: Context) -> bool:
armature = get_selected_armature(context)
return armature is not None and is_valid_armature(armature)
def execute(self, context: Context) -> set:
if not select_current_armature(context):
self.report({'WARNING'}, "No armature selected")
return {'CANCELLED'}
armature = get_selected_armature(context)
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.select_all(action='DESELECT')
objects: List[Object] = get_all_meshes(context)
for mesh in objects:
if mesh.data.name not in [stored_object["mesh"].data.name for stored_object in self.objects_to_do]:
mesh_shapekeys = {"mesh":mesh,"shapekeys":[]}
mesh_data: bpy.types.Mesh = mesh.data
shape: bpy.types.ShapeKey = None
if mesh_data.shape_keys:
for shape in mesh_data.shape_keys.key_blocks:
mesh_shapekeys["shapekeys"].append(shape.name)
self.objects_to_do.append(mesh_shapekeys)
return {'FINISHED'}
def invoke(self, context: Context, event: bpy.types.Event) -> set:
self.execute(context)
context.window_manager.modal_handler_add(self)
return {'RUNNING_MODAL'}
def modify_mesh(self, context: Context, mesh: meshEntry):
mesh["mesh"].select_set(True)
context.view_layer.objects.active = mesh["mesh"]
context.view_layer.objects.active = mesh["mesh"]
mesh_data: bpy.types.Mesh = mesh["mesh"].data
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.object.mode_set(mode='OBJECT')
for index, point in enumerate(mesh["mesh"].active_shape_key.points):
if point.co.xyz != mesh_data.shape_keys.key_blocks[0].points[index].co.xyz:
mesh_data.vertices[index].select = True
print("shapekey has a moved vertex at index \""+str(index)+"\", excluding from double merging!")
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.object.mode_set(mode='OBJECT')
mesh["mesh"].select_set(False)
def modal(self, context: Context, event: bpy.types.Event) -> set:
if len(self.objects_to_do) > 0:
mesh = self.objects_to_do[0]
mesh_data: bpy.types.Mesh = mesh["mesh"].data
if len(mesh['shapekeys']) > 0:
shapekeyname: str = mesh['shapekeys'].pop(0)
target_shapekey: int = mesh_data.shape_keys.key_blocks.find(shapekeyname)
mesh["mesh"].active_shape_key_index = target_shapekey
print("doing shapekey \""+shapekeyname+"\" on mesh \""+mesh['mesh'].name+"\".")
self.modify_mesh(context, mesh)
elif not (mesh_data.shape_keys):
print("doing mesh with no shapekeys named \""+mesh['mesh'].name+"\".")
mesh["mesh"].select_set(True)
context.view_layer.objects.active = mesh["mesh"]
bpy.ops.object.mode_set(mode='EDIT')
mesh_data.vertices.foreach_set("select",[False]*len(mesh_data.vertices))
bpy.ops.mesh.select_all(action="INVERT")
bpy.ops.mesh.remove_doubles(threshold=self.merge_distance,use_unselected=False)
bpy.ops.object.mode_set(mode='OBJECT')
mesh["mesh"].select_set(False)
self.objects_to_do.pop(0)
else:
mesh["mesh"].select_set(True)
context.view_layer.objects.active = mesh["mesh"]
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_all(action="INVERT")
bpy.ops.mesh.remove_doubles(threshold=self.merge_distance,use_unselected=False)
bpy.ops.object.mode_set(mode='OBJECT')
mesh["mesh"].select_set(False)
self.objects_to_do.pop(0)
if len(self.objects_to_do) > 0:
mesh = self.objects_to_do[0]
mesh["mesh"].select_set(True)
context.view_layer.objects.active = mesh["mesh"]
bpy.ops.object.mode_set(mode='EDIT')
mesh_data.vertices.foreach_set("select",[False]*len(mesh_data.vertices))
bpy.ops.object.mode_set(mode='OBJECT')
mesh["mesh"].select_set(False)
else:
return {'FINISHED'}
return {'RUNNING_MODAL'}
+8 -9
View File
@@ -4,7 +4,7 @@ from typing import List, Optional
import re import re
from bpy.types import Operator, Context, Object from bpy.types import Operator, Context, Object
from ..core.dictionaries import bone_names from ..core.dictionaries import bone_names
from ..core.common import get_armature, simplify_bonename from ..core.common import get_selected_armature, simplify_bonename, is_valid_armature
from ..functions.translations import t from ..functions.translations import t
@register_wrap @register_wrap
@@ -16,12 +16,14 @@ class ConvertToResonite(Operator):
@classmethod @classmethod
def poll(cls, context: Context) -> bool: def poll(cls, context: Context) -> bool:
if not get_armature(context): armature = get_selected_armature(context)
return False return armature is not None and is_valid_armature(armature)
return True
def execute(self, context: Context) -> set: def execute(self, context: Context) -> set:
armature = get_armature(context) armature = get_selected_armature(context)
if not armature:
self.report({'WARNING'}, "No armature selected")
return {'CANCELLED'}
translate_bone_fails = 0 translate_bone_fails = 0
untranslated_bones = set() untranslated_bones = set()
@@ -89,8 +91,6 @@ class ConvertToResonite(Operator):
'thumb_3_r': "thumb3.R" 'thumb_3_r': "thumb3.R"
} }
context.view_layer.objects.active = armature context.view_layer.objects.active = armature
bpy.ops.object.mode_set(mode='EDIT') bpy.ops.object.mode_set(mode='EDIT')
@@ -111,5 +111,4 @@ class ConvertToResonite(Operator):
else: else:
self.report({'INFO'}, "Successfully translated all bones to humanoid names") self.report({'INFO'}, "Successfully translated all bones to humanoid names")
return {'FINISHED'}
return {'FINISHED'}
+10 -1
View File
@@ -49,7 +49,16 @@
"Settings.translation_restart_popup.label": "Translation Update", "Settings.translation_restart_popup.label": "Translation Update",
"Settings.translation_restart_popup.description": "Information about translation updates", "Settings.translation_restart_popup.description": "Information about translation updates",
"Settings.translation_restart_popup.message1": "Some translations may not apply", "Settings.translation_restart_popup.message1": "Some translations may not apply",
"Settings.translation_restart_popup.message2": "until you restart Blender." "Settings.translation_restart_popup.message2": "until you restart Blender.",
"Importing.need_importer":"You do not have the required importer for the {extension} type! Opening web browser for importer search term...",
"Importer.mmd_anim_importer.label":"MMD Animation",
"Importer.mmd_anim_importer.desc":"Import a MMD Animation (.vmd)",
"Importing.importer_search_term":"https://search.brave.com/search?q=blender+{extension}+importer+addon&source=web",
"Importer.export_resonite.label":"Export to Resonite",
"Importer.export_resonite.desc":"Export to Resonite as a GLTF. Make sure your model is to scale in blender, and import as meters in Resonite.",
"Importer.export_vrchat.label":"Export to VRChat",
"Importer.export_vrchat.desc":"Export to VRChat, may also work for ChilloutVR. Is similar to Cats export."
} }
} }
+18 -15
View File
@@ -2,6 +2,7 @@ import bpy
from ..core.register import register_wrap from ..core.register import register_wrap
from .panel import AvatarToolkitPanel from .panel import AvatarToolkitPanel
from ..functions.translations import t from ..functions.translations import t
from ..core.common import get_selected_armature
@register_wrap @register_wrap
class AvatarToolkitOptimizationPanel(bpy.types.Panel): class AvatarToolkitOptimizationPanel(bpy.types.Panel):
@@ -14,19 +15,21 @@ class AvatarToolkitOptimizationPanel(bpy.types.Panel):
def draw(self, context): def draw(self, context):
layout = self.layout layout = self.layout
layout.label(text=t("Optimization.options.label")) armature = get_selected_armature(context)
row = layout.row() if armature:
row.scale_y = 1.2 layout.label(text=t("Optimization.options.label"))
row.operator("avatar_toolkit.combine_materials", text=t("Optimization.combine_materials.label"))
row = layout.row()
row.scale_y = 1.2
layout.separator(factor=0.5) row.operator("avatar_toolkit.combine_materials", text=t("Optimization.combine_materials.label"))
row = layout.row(align=True) layout.separator(factor=0.5)
row.scale_y = 1.2
row.operator("avatar_toolkit.join_all_meshes", text=t("Optimization.join_all_meshes.label")) row = layout.row(align=True)
row.operator("avatar_toolkit.join_selected_meshes", text=t("Optimization.join_selected_meshes.label")) row.scale_y = 1.2
row.operator("avatar_toolkit.join_all_meshes", text=t("Optimization.join_all_meshes.label"))
# Add optimization options here row.operator("avatar_toolkit.join_selected_meshes", text=t("Optimization.join_selected_meshes.label"))
row.operator("avatar_toolkit.remove_doubles_safely", text="Remove Doubles Safely")
else:
layout.label(text="Please select an armature in Quick Access")
+5 -69
View File
@@ -6,7 +6,8 @@ from ..functions.translations import t
from ..core.import_pmx import import_pmx from ..core.import_pmx import import_pmx
from ..core.import_pmd import import_pmd from ..core.import_pmd import import_pmd
from ..core.importer import import_fbx from ..functions.import_anything import ImportAnyModel
from ..core.common import get_selected_armature, set_selected_armature
@register_wrap @register_wrap
class AvatarToolkitQuickAccessPanel(bpy.types.Panel): class AvatarToolkitQuickAccessPanel(bpy.types.Panel):
@@ -21,6 +22,8 @@ class AvatarToolkitQuickAccessPanel(bpy.types.Panel):
layout = self.layout layout = self.layout
layout.label(text=t("Quick_Access.options")) layout.label(text=t("Quick_Access.options"))
layout.prop(context.scene, "selected_armature", text="Select Armature")
row = layout.row() row = layout.row()
row.label(text=t("Quick_Access.import_export.label"), icon='IMPORT') row.label(text=t("Quick_Access.import_export.label"), icon='IMPORT')
@@ -28,29 +31,9 @@ class AvatarToolkitQuickAccessPanel(bpy.types.Panel):
row = layout.row(align=True) row = layout.row(align=True)
row.scale_y = 1.5 row.scale_y = 1.5
row.operator("avatar_toolkit.import_menu", text=t("Quick_Access.import")) row.operator(ImportAnyModel.bl_idname, text=t("Quick_Access.import"))
row.operator("avatar_toolkit.export_menu", text=t("Quick_Access.export")) row.operator("avatar_toolkit.export_menu", text=t("Quick_Access.export"))
@register_wrap
class AVATAR_TOOLKIT_OT_import_menu(bpy.types.Operator):
bl_idname = "avatar_toolkit.import_menu"
bl_label = t("Quick_Access.import_menu.label")
bl_description = t("Quick_Access.import_menu.desc")
def execute(self, context: Context):
return {'FINISHED'}
def invoke(self, context: Context, event):
wm = context.window_manager
return wm.invoke_popup(self, width=200)
def draw(self, context: Context):
layout = self.layout
layout.label(text="Select Import Method")
layout.operator("avatar_toolkit.import_pmx", text=t("Quick_Access.import_pmx"))
layout.operator("avatar_toolkit.import_pmd", text=t("Quick_Access.import_pmd"))
layout.operator("avatar_toolkit.import_fbx", text="Import FBX")
@register_wrap @register_wrap
class AVATAR_TOOLKIT_OT_export_menu(bpy.types.Operator): class AVATAR_TOOLKIT_OT_export_menu(bpy.types.Operator):
bl_idname = "avatar_toolkit.export_menu" bl_idname = "avatar_toolkit.export_menu"
@@ -74,51 +57,6 @@ class AVATAR_TOOLKIT_OT_export_menu(bpy.types.Operator):
layout.operator("avatar_toolkit.export_resonite", text=t("Quick_Access.select_export_resonite.label")) layout.operator("avatar_toolkit.export_resonite", text=t("Quick_Access.select_export_resonite.label"))
layout.operator("avatar_toolkit.export_fbx", text="Export FBX") layout.operator("avatar_toolkit.export_fbx", text="Export FBX")
@register_wrap
class AVATAR_TOOLKIT_OT_import_pmx(bpy.types.Operator):
bl_idname = "avatar_toolkit.import_pmx"
bl_label = t("Quick_Access.import_pmx")
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def execute(self, context: Context):
import_pmx(self.filepath)
return {'FINISHED'}
def invoke(self, context: Context, event):
context.window_manager.fileselect_add(self)
return {'RUNNING_MODAL'}
@register_wrap
class AVATAR_TOOLKIT_OT_import_pmd(bpy.types.Operator):
bl_idname = "avatar_toolkit.import_pmd"
bl_label = t("Quick_Access.import_pmd")
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def execute(self, context: Context):
import_pmd(self.filepath)
return {'FINISHED'}
def invoke(self, context: Context, event):
context.window_manager.fileselect_add(self)
return {'RUNNING_MODAL'}
@register_wrap
class AVATAR_TOOLKIT_OT_import_fbx(bpy.types.Operator):
bl_idname = "avatar_toolkit.import_fbx"
bl_label = "Import FBX"
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def execute(self, context):
import_fbx(self.filepath)
return {'FINISHED'}
def invoke(self, context, event):
context.window_manager.fileselect_add(self)
return {'RUNNING_MODAL'}
@register_wrap @register_wrap
class AVATAR_TOOLKIT_OT_export_fbx(bpy.types.Operator): class AVATAR_TOOLKIT_OT_export_fbx(bpy.types.Operator):
bl_idname = 'avatar_toolkit.export_fbx' bl_idname = 'avatar_toolkit.export_fbx'
@@ -129,5 +67,3 @@ class AVATAR_TOOLKIT_OT_export_fbx(bpy.types.Operator):
def execute(self, context): def execute(self, context):
bpy.ops.export_scene.fbx('INVOKE_DEFAULT') bpy.ops.export_scene.fbx('INVOKE_DEFAULT')
return {'FINISHED'} return {'FINISHED'}
+16 -5
View File
@@ -2,7 +2,9 @@ import bpy
from ..core.register import register_wrap from ..core.register import register_wrap
from .panel import AvatarToolkitPanel from .panel import AvatarToolkitPanel
from bpy.types import Context from bpy.types import Context
from ..functions.digitigrade_legs import CreateDigitigradeLegs
from ..functions.translations import t from ..functions.translations import t
from ..core.common import get_selected_armature
@register_wrap @register_wrap
class AvatarToolkitToolsPanel(bpy.types.Panel): class AvatarToolkitToolsPanel(bpy.types.Panel):
@@ -15,9 +17,18 @@ class AvatarToolkitToolsPanel(bpy.types.Panel):
def draw(self, context: Context): def draw(self, context: Context):
layout = self.layout layout = self.layout
layout.label(text=t("Tools.tools_title.label")) armature = get_selected_armature(context)
layout.separator(factor=0.5)
if armature:
layout.label(text=t("Tools.tools_title.label"))
layout.separator(factor=0.5)
row = layout.row(align=True) row = layout.row(align=True)
row.scale_y = 1.5 row.scale_y = 1.5
row.operator("avatar_toolkit.convert_to_resonite", text=t("Tools.convert_to_resonite.label")) row.operator("avatar_toolkit.convert_to_resonite", text=t("Tools.convert_to_resonite.label"))
row = layout.row(align=True)
row.operator("avatar_toolkit.remove_doubles_safely", text="Remove Doubles Safely")
row = layout.row(align=True)
row.operator(CreateDigitigradeLegs.bl_idname, text="Create Digitigrade Legs")
else:
layout.label(text="Please select an armature in Quick Access")