PMX Import now works
This commit is contained in:
@@ -0,0 +1,461 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2014 MMD Tools authors
|
||||
# This file was originally part of the MMD Tools add-on for Blender
|
||||
# You can find MMD Tools here: https://github.com/MMD-Blender/blender_mmd_tools
|
||||
# Neoneko has modified this file to work with Avatar Toolkit and may of made changes or improvements.
|
||||
# MMD Tools is licensed under the terms of the GNU General Public License version 3 (GPLv3) same as Avatar Toolkit.
|
||||
|
||||
import csv
|
||||
import logging
|
||||
import time
|
||||
|
||||
import bpy
|
||||
|
||||
from .bpyutils import FnContext
|
||||
|
||||
jp_half_to_full_tuples = (
|
||||
("ヴ", "ヴ"),
|
||||
("ガ", "ガ"),
|
||||
("ギ", "ギ"),
|
||||
("グ", "グ"),
|
||||
("ゲ", "ゲ"),
|
||||
("ゴ", "ゴ"),
|
||||
("ザ", "ザ"),
|
||||
("ジ", "ジ"),
|
||||
("ズ", "ズ"),
|
||||
("ゼ", "ゼ"),
|
||||
("ゾ", "ゾ"),
|
||||
("ダ", "ダ"),
|
||||
("ヂ", "ヂ"),
|
||||
("ヅ", "ヅ"),
|
||||
("デ", "デ"),
|
||||
("ド", "ド"),
|
||||
("バ", "バ"),
|
||||
("パ", "パ"),
|
||||
("ビ", "ビ"),
|
||||
("ピ", "ピ"),
|
||||
("ブ", "ブ"),
|
||||
("プ", "プ"),
|
||||
("ベ", "ベ"),
|
||||
("ペ", "ペ"),
|
||||
("ボ", "ボ"),
|
||||
("ポ", "ポ"),
|
||||
("。", "。"),
|
||||
("「", "「"),
|
||||
("」", "」"),
|
||||
("、", "、"),
|
||||
("・", "・"),
|
||||
("ヲ", "ヲ"),
|
||||
("ァ", "ァ"),
|
||||
("ィ", "ィ"),
|
||||
("ゥ", "ゥ"),
|
||||
("ェ", "ェ"),
|
||||
("ォ", "ォ"),
|
||||
("ャ", "ャ"),
|
||||
("ュ", "ュ"),
|
||||
("ョ", "ョ"),
|
||||
("ッ", "ッ"),
|
||||
("ー", "ー"),
|
||||
("ア", "ア"),
|
||||
("イ", "イ"),
|
||||
("ウ", "ウ"),
|
||||
("エ", "エ"),
|
||||
("オ", "オ"),
|
||||
("カ", "カ"),
|
||||
("キ", "キ"),
|
||||
("ク", "ク"),
|
||||
("ケ", "ケ"),
|
||||
("コ", "コ"),
|
||||
("サ", "サ"),
|
||||
("シ", "シ"),
|
||||
("ス", "ス"),
|
||||
("セ", "セ"),
|
||||
("ソ", "ソ"),
|
||||
("タ", "タ"),
|
||||
("チ", "チ"),
|
||||
("ツ", "ツ"),
|
||||
("テ", "テ"),
|
||||
("ト", "ト"),
|
||||
("ナ", "ナ"),
|
||||
("ニ", "ニ"),
|
||||
("ヌ", "ヌ"),
|
||||
("ネ", "ネ"),
|
||||
("ノ", "ノ"),
|
||||
("ハ", "ハ"),
|
||||
("ヒ", "ヒ"),
|
||||
("フ", "フ"),
|
||||
("ヘ", "ヘ"),
|
||||
("ホ", "ホ"),
|
||||
("マ", "マ"),
|
||||
("ミ", "ミ"),
|
||||
("ム", "ム"),
|
||||
("メ", "メ"),
|
||||
("モ", "モ"),
|
||||
("ヤ", "ヤ"),
|
||||
("ユ", "ユ"),
|
||||
("ヨ", "ヨ"),
|
||||
("ラ", "ラ"),
|
||||
("リ", "リ"),
|
||||
("ル", "ル"),
|
||||
("レ", "レ"),
|
||||
("ロ", "ロ"),
|
||||
("ワ", "ワ"),
|
||||
("ン", "ン"),
|
||||
)
|
||||
|
||||
jp_to_en_tuples = [
|
||||
("全ての親", "ParentNode"),
|
||||
("操作中心", "ControlNode"),
|
||||
("センター", "Center"),
|
||||
("センター", "Center"),
|
||||
("グループ", "Group"),
|
||||
("グルーブ", "Groove"),
|
||||
("キャンセル", "Cancel"),
|
||||
("上半身", "UpperBody"),
|
||||
("下半身", "LowerBody"),
|
||||
("手首", "Wrist"),
|
||||
("足首", "Ankle"),
|
||||
("首", "Neck"),
|
||||
("頭", "Head"),
|
||||
("顔", "Face"),
|
||||
("下顎", "Chin"),
|
||||
("下あご", "Chin"),
|
||||
("あご", "Jaw"),
|
||||
("顎", "Jaw"),
|
||||
("両目", "Eyes"),
|
||||
("目", "Eye"),
|
||||
("眉", "Eyebrow"),
|
||||
("舌", "Tongue"),
|
||||
("涙", "Tears"),
|
||||
("泣き", "Cry"),
|
||||
("歯", "Teeth"),
|
||||
("照れ", "Blush"),
|
||||
("青ざめ", "Pale"),
|
||||
("ガーン", "Gloom"),
|
||||
("汗", "Sweat"),
|
||||
("怒", "Anger"),
|
||||
("感情", "Emotion"),
|
||||
("符", "Marks"),
|
||||
("暗い", "Dark"),
|
||||
("腰", "Waist"),
|
||||
("髪", "Hair"),
|
||||
("三つ編み", "Braid"),
|
||||
("胸", "Breast"),
|
||||
("乳", "Boob"),
|
||||
("おっぱい", "Tits"),
|
||||
("筋", "Muscle"),
|
||||
("腹", "Belly"),
|
||||
("鎖骨", "Clavicle"),
|
||||
("肩", "Shoulder"),
|
||||
("腕", "Arm"),
|
||||
("うで", "Arm"),
|
||||
("ひじ", "Elbow"),
|
||||
("肘", "Elbow"),
|
||||
("手", "Hand"),
|
||||
("親指", "Thumb"),
|
||||
("人指", "IndexFinger"),
|
||||
("人差指", "IndexFinger"),
|
||||
("中指", "MiddleFinger"),
|
||||
("薬指", "RingFinger"),
|
||||
("小指", "LittleFinger"),
|
||||
("足", "Leg"),
|
||||
("ひざ", "Knee"),
|
||||
("つま", "Toe"),
|
||||
("袖", "Sleeve"),
|
||||
("新規", "New"),
|
||||
("ボーン", "Bone"),
|
||||
("捩", "Twist"),
|
||||
("回転", "Rotation"),
|
||||
("軸", "Axis"),
|
||||
("ネクタイ", "Necktie"),
|
||||
("ネクタイ", "Necktie"),
|
||||
("ヘッドセット", "Headset"),
|
||||
("飾り", "Accessory"),
|
||||
("リボン", "Ribbon"),
|
||||
("襟", "Collar"),
|
||||
("紐", "String"),
|
||||
("コード", "Cord"),
|
||||
("イヤリング", "Earring"),
|
||||
("メガネ", "Eyeglasses"),
|
||||
("眼鏡", "Glasses"),
|
||||
("帽子", "Hat"),
|
||||
("スカート", "Skirt"),
|
||||
("スカート", "Skirt"),
|
||||
("パンツ", "Pantsu"),
|
||||
("シャツ", "Shirt"),
|
||||
("フリル", "Frill"),
|
||||
("マフラー", "Muffler"),
|
||||
("マフラー", "Muffler"),
|
||||
("服", "Clothes"),
|
||||
("ブーツ", "Boots"),
|
||||
("ねこみみ", "CatEars"),
|
||||
("ジップ", "Zip"),
|
||||
("ジップ", "Zip"),
|
||||
("ダミー", "Dummy"),
|
||||
("ダミー", "Dummy"),
|
||||
("基", "Category"),
|
||||
("あほ毛", "Antenna"),
|
||||
("アホ毛", "Antenna"),
|
||||
("モミアゲ", "Sideburn"),
|
||||
("もみあげ", "Sideburn"),
|
||||
("ツインテ", "Twintail"),
|
||||
("おさげ", "Pigtail"),
|
||||
("ひらひら", "Flutter"),
|
||||
("調整", "Adjustment"),
|
||||
("補助", "Aux"),
|
||||
("右", "Right"),
|
||||
("左", "Left"),
|
||||
("前", "Front"),
|
||||
("後ろ", "Behind"),
|
||||
("後", "Back"),
|
||||
("横", "Side"),
|
||||
("中", "Middle"),
|
||||
("上", "Upper"),
|
||||
("下", "Lower"),
|
||||
("親", "Parent"),
|
||||
("先", "Tip"),
|
||||
("パーツ", "Part"),
|
||||
("光", "Light"),
|
||||
("戻", "Return"),
|
||||
("羽", "Wing"),
|
||||
("根", "Base"), # ideally 'Root' but to avoid confusion
|
||||
("毛", "Strand"),
|
||||
("尾", "Tail"),
|
||||
("尻", "Butt"),
|
||||
# full-width unicode forms I think: https://en.wikipedia.org/wiki/Halfwidth_and_fullwidth_forms
|
||||
("0", "0"),
|
||||
("1", "1"),
|
||||
("2", "2"),
|
||||
("3", "3"),
|
||||
("4", "4"),
|
||||
("5", "5"),
|
||||
("6", "6"),
|
||||
("7", "7"),
|
||||
("8", "8"),
|
||||
("9", "9"),
|
||||
("a", "a"),
|
||||
("b", "b"),
|
||||
("c", "c"),
|
||||
("d", "d"),
|
||||
("e", "e"),
|
||||
("f", "f"),
|
||||
("g", "g"),
|
||||
("h", "h"),
|
||||
("i", "i"),
|
||||
("j", "j"),
|
||||
("k", "k"),
|
||||
("l", "l"),
|
||||
("m", "m"),
|
||||
("n", "n"),
|
||||
("o", "o"),
|
||||
("p", "p"),
|
||||
("q", "q"),
|
||||
("r", "r"),
|
||||
("s", "s"),
|
||||
("t", "t"),
|
||||
("u", "u"),
|
||||
("v", "v"),
|
||||
("w", "w"),
|
||||
("x", "x"),
|
||||
("y", "y"),
|
||||
("z", "z"),
|
||||
("A", "A"),
|
||||
("B", "B"),
|
||||
("C", "C"),
|
||||
("D", "D"),
|
||||
("E", "E"),
|
||||
("F", "F"),
|
||||
("G", "G"),
|
||||
("H", "H"),
|
||||
("I", "I"),
|
||||
("J", "J"),
|
||||
("K", "K"),
|
||||
("L", "L"),
|
||||
("M", "M"),
|
||||
("N", "N"),
|
||||
("O", "O"),
|
||||
("P", "P"),
|
||||
("Q", "Q"),
|
||||
("R", "R"),
|
||||
("S", "S"),
|
||||
("T", "T"),
|
||||
("U", "U"),
|
||||
("V", "V"),
|
||||
("W", "W"),
|
||||
("X", "X"),
|
||||
("Y", "Y"),
|
||||
("Z", "Z"),
|
||||
("+", "+"),
|
||||
("-", "-"),
|
||||
("_", "_"),
|
||||
("/", "/"),
|
||||
(".", "_"), # probably should be combined with the global 'use underscore' option
|
||||
]
|
||||
|
||||
|
||||
def translateFromJp(name):
|
||||
for tuple in jp_to_en_tuples:
|
||||
if tuple[0] in name:
|
||||
name = name.replace(tuple[0], tuple[1])
|
||||
return name
|
||||
|
||||
|
||||
def getTranslator(csvfile="", keep_order=False):
|
||||
translator = MMDTranslator()
|
||||
if isinstance(csvfile, bpy.types.Text):
|
||||
translator.load_from_stream(csvfile)
|
||||
elif isinstance(csvfile, dict):
|
||||
translator.csv_tuples.extend(csvfile.items())
|
||||
elif csvfile in bpy.data.texts.keys():
|
||||
translator.load_from_stream(bpy.data.texts[csvfile])
|
||||
else:
|
||||
translator.load(csvfile)
|
||||
|
||||
if not keep_order:
|
||||
translator.sort()
|
||||
translator.update()
|
||||
return translator
|
||||
|
||||
|
||||
class MMDTranslator:
|
||||
def __init__(self):
|
||||
self.__csv_tuples = []
|
||||
self.__fails = {}
|
||||
|
||||
@staticmethod
|
||||
def default_csv_filepath():
|
||||
return __file__[:-3] + ".csv"
|
||||
|
||||
@staticmethod
|
||||
def get_csv_text(text_name=None):
|
||||
text_name = text_name or bpy.path.basename(MMDTranslator.default_csv_filepath())
|
||||
csv_text = bpy.data.texts.get(text_name, None)
|
||||
if csv_text is None:
|
||||
csv_text = bpy.data.texts.new(text_name)
|
||||
return csv_text
|
||||
|
||||
@staticmethod
|
||||
def replace_from_tuples(name, tuples):
|
||||
for pair in tuples:
|
||||
if pair[0] in name:
|
||||
name = name.replace(pair[0], pair[1])
|
||||
return name
|
||||
|
||||
@property
|
||||
def csv_tuples(self):
|
||||
return self.__csv_tuples
|
||||
|
||||
@property
|
||||
def fails(self):
|
||||
return self.__fails
|
||||
|
||||
def sort(self):
|
||||
self.__csv_tuples.sort(key=lambda row: (-len(row[0]), row))
|
||||
|
||||
def update(self):
|
||||
from collections import OrderedDict
|
||||
|
||||
count_old = len(self.__csv_tuples)
|
||||
tuples_dict = OrderedDict((row[0], row) for row in self.__csv_tuples if len(row) >= 2 and row[0])
|
||||
self.__csv_tuples.clear()
|
||||
self.__csv_tuples.extend(tuples_dict.values())
|
||||
logging.info(" - removed items:\t%d\t(of %d)", count_old - len(self.__csv_tuples), count_old)
|
||||
|
||||
def half_to_full(self, name):
|
||||
return self.replace_from_tuples(name, jp_half_to_full_tuples)
|
||||
|
||||
def is_translated(self, name):
|
||||
try:
|
||||
name.encode("ascii", errors="strict")
|
||||
except UnicodeEncodeError:
|
||||
return False
|
||||
return True
|
||||
|
||||
def translate(self, name, default=None, from_full_width=True):
|
||||
if from_full_width:
|
||||
name = self.half_to_full(name)
|
||||
name_new = self.replace_from_tuples(name, self.__csv_tuples)
|
||||
if default is not None and not self.is_translated(name_new):
|
||||
self.__fails[name] = name_new
|
||||
return default
|
||||
return name_new
|
||||
|
||||
def save_fails(self, text_name=None):
|
||||
text_name = text_name or (__name__ + ".fails")
|
||||
txt = self.get_csv_text(text_name)
|
||||
fmt = '"%s","%s"'
|
||||
items = sorted(self.__fails.items(), key=lambda row: (-len(row[0]), row))
|
||||
txt.from_string("\n".join(fmt % (k, v) for k, v in items))
|
||||
return txt
|
||||
|
||||
def load_from_stream(self, csvfile=None):
|
||||
csvfile = csvfile or self.get_csv_text()
|
||||
if isinstance(csvfile, bpy.types.Text):
|
||||
csvfile = (l.body + "\n" for l in csvfile.lines)
|
||||
spamreader = csv.reader(csvfile, delimiter=",", skipinitialspace=True)
|
||||
csv_tuples = [tuple(row) for row in spamreader if len(row) >= 2]
|
||||
self.__csv_tuples = csv_tuples
|
||||
logging.info(" - load items:\t%d", len(self.__csv_tuples))
|
||||
|
||||
def save_to_stream(self, csvfile=None):
|
||||
csvfile = csvfile or self.get_csv_text()
|
||||
lineterminator = "\r\n"
|
||||
if isinstance(csvfile, bpy.types.Text):
|
||||
csvfile.clear()
|
||||
lineterminator = "\n"
|
||||
spamwriter = csv.writer(csvfile, delimiter=",", lineterminator=lineterminator, quoting=csv.QUOTE_ALL)
|
||||
spamwriter.writerows(self.__csv_tuples)
|
||||
logging.info(" - save items:\t%d", len(self.__csv_tuples))
|
||||
|
||||
def load(self, filepath=None):
|
||||
filepath = filepath or self.default_csv_filepath()
|
||||
logging.info("Loading csv file:\t%s", filepath)
|
||||
with open(filepath, "rt", encoding="utf-8", newline="") as csvfile:
|
||||
self.load_from_stream(csvfile)
|
||||
|
||||
def save(self, filepath=None):
|
||||
filepath = filepath or self.default_csv_filepath()
|
||||
logging.info("Saving csv file:\t%s", filepath)
|
||||
with open(filepath, "wt", encoding="utf-8", newline="") as csvfile:
|
||||
self.save_to_stream(csvfile)
|
||||
|
||||
|
||||
class DictionaryEnum:
|
||||
__items_ttl = 0.0
|
||||
__items_cache = None
|
||||
|
||||
@staticmethod
|
||||
def get_dictionary_items(prop, context):
|
||||
if DictionaryEnum.__items_ttl > time.time():
|
||||
return DictionaryEnum.__items_cache
|
||||
|
||||
DictionaryEnum.__items_ttl = time.time() + 5
|
||||
DictionaryEnum.__items_cache = items = []
|
||||
if "import" in prop.bl_rna.identifier:
|
||||
items.append(("DISABLED", "Disabled", "", 0))
|
||||
|
||||
items.append(("INTERNAL", "Internal Dictionary", "The dictionary defined in " + __name__, len(items)))
|
||||
|
||||
for txt_name in sorted(x.name for x in bpy.data.texts if x.name.lower().endswith(".csv")):
|
||||
items.append((txt_name, txt_name, "bpy.data.texts['%s']" % txt_name, "TEXT", len(items)))
|
||||
|
||||
import os
|
||||
|
||||
folder = FnContext.get_addon_preferences_attribute(context, "dictionary_folder", "")
|
||||
if os.path.isdir(folder):
|
||||
for filename in sorted(x for x in os.listdir(folder) if x.lower().endswith(".csv")):
|
||||
filepath = os.path.join(folder, filename)
|
||||
if os.path.isfile(filepath):
|
||||
items.append((filepath, filename, filepath, "FILE", len(items)))
|
||||
|
||||
if "dictionary" in prop:
|
||||
prop["dictionary"] = min(prop["dictionary"], len(items) - 1)
|
||||
return items
|
||||
|
||||
@staticmethod
|
||||
def get_translator(dictionary):
|
||||
if dictionary == "DISABLED":
|
||||
return None
|
||||
if dictionary == "INTERNAL":
|
||||
return getTranslator(dict(jp_to_en_tuples))
|
||||
return getTranslator(dictionary)
|
||||
Reference in New Issue
Block a user