# -*- 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)