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"