From ad2b463d715922e0c25055fed179028cd46c2b22 Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Mon, 28 Jan 2019 19:45:32 -0500 Subject: [PATCH] Implement localization export operator This is like the Package Python operator in that the artist can now export the loc data independent of the rest of the age. The difference is that due to the extremely different loc formats from PotS+UAM to MOUL, we can't easily allow the user to specify where the data will be exported to. So, we only allow this operation in the context of a game. --- korman/exporter/__init__.py | 1 + korman/exporter/locman.py | 89 +++++++++++++++++++++++++++++------ korman/operators/op_export.py | 48 +++++++++++++++++++ korman/ui/ui_world.py | 11 +++++ 4 files changed, 134 insertions(+), 15 deletions(-) diff --git a/korman/exporter/__init__.py b/korman/exporter/__init__.py index e8652a8..60123e0 100644 --- a/korman/exporter/__init__.py +++ b/korman/exporter/__init__.py @@ -18,5 +18,6 @@ from PyHSPlasma import * from .convert import * from .explosions import * +from .locman import * from .python import * from . import utils diff --git a/korman/exporter/locman.py b/korman/exporter/locman.py index 72dd9ed..205b7d1 100644 --- a/korman/exporter/locman.py +++ b/korman/exporter/locman.py @@ -13,6 +13,12 @@ # You should have received a copy of the GNU General Public License # along with Korman. If not, see . +import bpy +from contextlib import contextmanager +from .explosions import NonfatalExportError +from .. import korlib +from . import logger +from pathlib import Path from PyHSPlasma import * import weakref from xml.sax.saxutils import escape as xml_escape @@ -20,8 +26,17 @@ from xml.sax.saxutils import escape as xml_escape _SP_LANGUAGES = {"English", "French", "German", "Italian", "Spanish"} class LocalizationConverter: - def __init__(self, exporter): - self._exporter = weakref.ref(exporter) + def __init__(self, exporter=None, **kwargs): + if exporter is not None: + self._exporter = weakref.ref(exporter) + self._age_name = exporter.age_name + self._report = exporter.report + self._version = exporter.mgr.getVer() + else: + self._exporter = None + self._age_name = kwargs.get("age_name") + self._path = kwargs.get("path") + self._version = kwargs.get("version") self._journals = {} self._strings = {} @@ -37,13 +52,28 @@ class LocalizationConverter: trans_element = trans_set.setdefault(element_name, {}) trans_element[language] = value + @contextmanager + def _generate_file(self, filename, **kwargs): + if self._exporter is not None: + with self._exporter().output.generate_dat_file(filename, **kwargs) as handle: + yield handle + else: + dirname = kwargs.get("dirname", "dat") + filepath = str(Path(self._path) / dirname / filename) + handle = open(filepath, "wb") + try: + yield handle + except: + raise + finally: + handle.close() + def _generate_journal_texts(self): - age_name = self._exporter().age_name - output = self._exporter().output + age_name = self._age_name def write_journal_file(language, file_name, contents): try: - with output.generate_dat_file(dirname="ageresources", filename=file_name) as stream: + with self._generate_file(dirname="ageresources", filename=file_name) as stream: stream.write(contents.encode("windows-1252")) except UnicodeEncodeError: self._report.error("Translation '{}': Contents contains characters that cannot be used in this version of Plasma", @@ -89,10 +119,10 @@ class LocalizationConverter: line = "".join((whitespace, value, "\n")) stream.write(line.encode("utf-16_le")) - age_name = self._exporter().age_name + age_name = self._age_name enc = plEncryptedStream.kEncAes if self._version == pvEoa else None file_name = "{}.loc".format(age_name) - with self._exporter().output.generate_dat_file(file_name, enc=enc) as stream: + with self._generate_file(file_name, enc=enc) as stream: # UTF-16 little endian byte order mark stream.write(b"\xFF\xFE") @@ -127,16 +157,45 @@ class LocalizationConverter: write_line("", indent=1) write_line("") + def run(self): + age_props = bpy.context.scene.world.plasma_age + loc_path = str(Path(self._path) / "dat" / "{}.loc".format(self._age_name)) + log = logger.ExportVerboseLogger if age_props.verbose else logger.ExportProgressLogger + with korlib.ConsoleToggler(age_props.show_console), log(loc_path) as self._report: + self._report.progress_add_step("Harvesting Journals") + self._report.progress_add_step("Generating Localization") + self._report.progress_start("Exporting Localization Data") + + self._run_harvest_journals() + self._run_generate() + + # DONE + self._report.progress_end() + self._report.raise_errors() + + def _run_harvest_journals(self): + objects = bpy.context.scene.objects + self._report.progress_advance() + self._report.progress_range = len(objects) + inc_progress = self._report.progress_increment + + for i in objects: + journal = i.plasma_modifiers.journalbookmod + if journal.enabled: + translations = [j for j in journal.journal_translations if j.text_id is not None] + if not translations: + self._report.error("Journal '{}': No content translations available. The journal will not be exported.", + i.name, indent=2) + for j in translations: + self.add_journal(journal.key_name, j.language, j.text_id, indent=1) + inc_progress() + + def _run_generate(self): + self._report.progress_advance() + self.save() + def save(self): if self._version > pvPots: self._generate_loc_file() else: self._generate_journal_texts() - - @property - def _report(self): - return self._exporter().report - - @property - def _version(self): - return self._exporter().mgr.getVer() diff --git a/korman/operators/op_export.py b/korman/operators/op_export.py index e4df68e..f5fa58e 100644 --- a/korman/operators/op_export.py +++ b/korman/operators/op_export.py @@ -218,6 +218,54 @@ class PlasmaAgeExportOperator(ExportOperator, bpy.types.Operator): setattr(PlasmaAge, name, prop(**age_options)) +class PlasmaLocalizationExportOperator(ExportOperator, bpy.types.Operator): + bl_idname = "export.plasma_loc" + bl_label = "Export Localization" + bl_description = "Export Age Localization Data" + + filepath = StringProperty(subtype="DIR_PATH") + filter_glob = StringProperty(default="*.pak", options={'HIDDEN'}) + + version = EnumProperty(name="Version", + description="Plasma version to export this age for", + items=game_versions, + default="pvPots", + options=set()) + + def execute(self, context): + path = Path(self.filepath) + if not self.filepath: + self.report({"ERROR"}, "No file specified") + return {"CANCELLED"} + else: + if not path.exists: + try: + path.mkdir(parents=True) + except OSError: + self.report({"ERROR"}, "Failed to create export directory") + return {"CANCELLED"} + + # Age names cannot be python keywords + age_name = context.scene.world.plasma_age.age_name + if korlib.is_python_keyword(age_name): + self.report({"ERROR"}, "The Age name conflicts with the Python keyword '{}'".format(age_name)) + return {"CANCELLED"} + + # Bonus Fun: Implement Profile-mode here (later...) + e = exporter.LocalizationConverter(age_name=age_name, path=self.filepath, + version=globals()[self.version]) + try: + e.run() + except exporter.ExportError as error: + self.report({"ERROR"}, str(error)) + return {"CANCELLED"} + except exporter.NonfatalExportError as error: + self.report({"WARNING"}, str(error)) + return {"FINISHED"} + else: + return {"FINISHED"} + + class PlasmaPythonExportOperator(ExportOperator, bpy.types.Operator): bl_idname = "export.plasma_pak" bl_label = "Package Scripts" diff --git a/korman/ui/ui_world.py b/korman/ui/ui_world.py index 6618bfb..d97a073 100644 --- a/korman/ui/ui_world.py +++ b/korman/ui/ui_world.py @@ -55,6 +55,7 @@ class PlasmaGamePanel(AgeButtonsPanel, bpy.types.Panel): row = layout.row(align=True) legal_game = bool(age.age_name.strip()) and active_game is not None + # Export Age row.operator_context = "EXEC_DEFAULT" row.enabled = legal_game op = row.operator("export.plasma_age", icon="EXPORT") @@ -62,6 +63,7 @@ class PlasmaGamePanel(AgeButtonsPanel, bpy.types.Panel): op.dat_only = False op.filepath = str((Path(active_game.path) / "dat" / age.age_name).with_suffix(".age")) op.version = active_game.version + # Package Age row = row.row(align=True) row.enabled = legal_game row.operator_context = "INVOKE_DEFAULT" @@ -70,6 +72,15 @@ class PlasmaGamePanel(AgeButtonsPanel, bpy.types.Panel): op.dat_only = False op.filepath = "{}.zip".format(age.age_name) op.version = active_game.version + # Export Localization + row = row.row(align=True) + row.operator_context = "EXEC_DEFAULT" + row.enabled = legal_game + op = row.operator("export.plasma_loc", icon="FILE_SCRIPT") + if active_game is not None: + op.filepath = active_game.path + op.version = active_game.version + # Package Scripts row = row.row(align=True) row.operator_context = "EXEC_DEFAULT" row.enabled = legal_game and active_game.version != "pvMoul"