Browse Source

Merge pull request #276 from Hoikas/loc_mark_deux

Add "Localized Text" Modifier
pull/282/head
Adam Johnson 3 years ago committed by GitHub
parent
commit
f0dd0c5b2a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 16
      korman/exporter/convert.py
  2. 169
      korman/exporter/locman.py
  3. 7
      korman/exporter/material.py
  4. 8
      korman/nodes/node_python.py
  5. 8
      korman/operators/op_export.py
  6. 33
      korman/properties/modifiers/base.py
  7. 114
      korman/properties/modifiers/gui.py
  8. 147
      korman/properties/modifiers/render.py
  9. 45
      korman/ui/modifiers/render.py
  10. 1
      korman/ui/ui_world.py

16
korman/exporter/convert.py

@ -69,6 +69,7 @@ class Exporter:
self.report.progress_add_step("Collecting Objects")
self.report.progress_add_step("Verify Competence")
self.report.progress_add_step("Touching the Intangible")
self.report.progress_add_step("Unifying Superstrings")
self.report.progress_add_step("Harvesting Actors")
if self._op.lighting_method != "skip":
etlight.LightBaker.add_progress_steps(self.report)
@ -98,6 +99,9 @@ class Exporter:
# In other words, generate any ephemeral Blender objects that need to be exported.
self._pre_export_scene_objects()
# Step 2.3: Run through all the objects and export localization.
self._export_localization()
# Step 2.5: Run through all the objects we collected in Step 2 and see if any relationships
# that the artist made requires something to have a CoordinateInterface
self._harvest_actors()
@ -248,6 +252,18 @@ class Exporter:
return ci
return so.coord.object
def _export_localization(self):
self.report.progress_advance()
self.report.progress_range = len(self._objects)
inc_progress = self.report.progress_increment
self.report.msg("\nExporting localization...")
for bl_obj in self._objects:
for mod in filter(lambda x: hasattr(x, "export_localization"), bl_obj.plasma_modifiers.modifiers):
mod.export_localization(self)
inc_progress()
def _export_scene_objects(self):
self.report.progress_advance()
self.report.progress_range = len(self._objects)

169
korman/exporter/locman.py

@ -14,17 +14,26 @@
# along with Korman. If not, see <http://www.gnu.org/licenses/>.
import bpy
from PyHSPlasma import *
from collections import defaultdict
from contextlib import contextmanager
import itertools
from pathlib import Path
import re
from xml.sax.saxutils import escape as xml_escape
import weakref
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
_SP_LANGUAGES = {"English", "French", "German", "Italian", "Spanish"}
# Detects if there are any Plasma esHTML tags in the translated data. If so, we store
# as CDATA instead of XML encoding the entry.
_ESHTML_REGEX = re.compile("<.+>")
class LocalizationConverter:
def __init__(self, exporter=None, **kwargs):
if exporter is not None:
@ -37,20 +46,16 @@ class LocalizationConverter:
self._age_name = kwargs.get("age_name")
self._path = kwargs.get("path")
self._version = kwargs.get("version")
self._journals = {}
self._strings = {}
self._strings = defaultdict(lambda: defaultdict(dict))
def add_journal(self, name, language, text_id, indent=0):
if text_id.is_modified:
self._report.warn("Journal '{}' translation for '{}' is modified on the disk but not reloaded in Blender.",
name, language, indent=indent)
journal = self._journals.setdefault(name, {})
journal[language] = text_id.as_string()
def add_string(self, set_name, element_name, language, value):
trans_set = self._strings.setdefault(set_name, {})
trans_element = trans_set.setdefault(element_name, {})
trans_element[language] = value
def add_string(self, set_name, element_name, language, value, indent=0):
self._report.msg("Accepted '{}' translation for '{}'.", element_name, language, indent=indent)
if isinstance(value, bpy.types.Text):
if value.is_modified:
self._report.warn("'{}' translation for '{}' is modified on the disk but not reloaded in Blender.",
element_name, language, indent=indent)
value = value.as_string()
self._strings[set_name][element_name][language] = value
@contextmanager
def _generate_file(self, filename, **kwargs):
@ -68,24 +73,25 @@ class LocalizationConverter:
finally:
handle.close()
def _generate_journal_texts(self):
def _generate_text_files(self):
age_name = self._age_name
def write_journal_file(language, file_name, contents):
try:
with self._generate_file(dirname="ageresources", filename=file_name) as stream:
def write_text_file(language, file_name, contents):
with self._generate_file(dirname="ageresources", filename=file_name) as stream:
try:
stream.write(contents.encode("windows-1252"))
except UnicodeEncodeError:
self._report.warn("Translation '{}': Contents contains characters that cannot be used in this version of Plasma. They will appear as a '?' in game.",
language, indent=2)
# Yes, there are illegal characters... As a stopgap, we will export the file with
# replacement characters ("?") just so it'll work dammit.
stream.write(contents.encode("windows-1252", "replace"))
return True
for journal_name, translations in self._journals.items():
self._report.msg("Copying Journal '{}'", journal_name, indent=1)
except UnicodeEncodeError:
self._report.warn("Translation '{}': Contents contains characters that cannot be used in this version of Plasma. They will appear as a '?' in game.",
language, indent=2)
# Yes, there are illegal characters... As a stopgap, we will export the file with
# replacement characters ("?") just so it'll work dammit.
stream.write(contents.encode("windows-1252", "replace"))
return True
locs = itertools.chain(self._strings["Journals"].items(), self._strings["DynaTexts"].items())
for journal_name, translations in locs:
self._report.msg("Copying localization '{}'", journal_name, indent=1)
for language_name, value in translations.items():
if language_name not in _SP_LANGUAGES:
self._report.warn("Translation '{}' will not be used because it is not supported in this version of Plasma.",
@ -93,7 +99,7 @@ class LocalizationConverter:
continue
suffix = "_{}".format(language_name.lower()) if language_name != "English" else ""
file_name = "{}--{}{}.txt".format(age_name, journal_name, suffix)
write_journal_file(language_name, file_name, value)
write_text_file(language_name, file_name, value)
# Ensure that default (read: "English") journal is available
if "English" not in translations:
@ -102,55 +108,69 @@ class LocalizationConverter:
if language_name is not None:
file_name = "{}--{}.txt".format(age_name, journal_name)
# If you manage to screw up this badly... Well, I am very sorry.
if write_journal_file(language_name, file_name, value):
if write_text_file(language_name, file_name, value):
self._report.warn("No 'English' translation available, so '{}' will be used as the default",
language_name, indent=2)
else:
self._report.port("No 'English' nor any other suitable default translation available", indent=2)
def _generate_loc_file(self):
# Only generate this junk if needed
if not self._strings and not self._journals:
def _generate_loc_files(self):
if not self._strings:
return
method = bpy.context.scene.world.plasma_age.localization_method
if method == "single_file":
self._generate_loc_file("{}.loc".format(self._age_name), self._strings)
elif method in {"database", "database_back_compat"}:
# Where the strings are set -> element -> language: str, we want language -> set -> element: str
# This is so we can mimic pfLocalizationEditor's <agename>English.loc pathing.
database = defaultdict(lambda: defaultdict(dict))
for set_name, elements in self._strings.items():
for element_name, translations in elements.items():
for language_name, value in translations.items():
database[language_name][set_name][element_name] = value
for language_name, sets in database.items():
self._generate_loc_file("{}{}.loc".format(self._age_name, language_name), sets, language_name)
# Generate an empty localization file to defeat any old ones from Korman 0.11 (and lower)
if method == "database_back_compat":
self._generate_loc_file("{}.loc".format(self._age_name), {})
else:
raise RuntimeError("Unexpected localization method {}".format(method))
def _generate_loc_file(self, filename, sets, language_name=None):
def write_line(value, *args, **kwargs):
# tabs suck, then you die...
whitespace = " " * kwargs.pop("indent", 0)
if args or kwargs:
value = value.format(*args, **kwargs)
line = "".join((whitespace, value, "\n"))
stream.write(line.encode("utf-16_le"))
stream.write(line.encode("utf-8"))
age_name = self._age_name
enc = plEncryptedStream.kEncAes if self._version == pvEoa else None
file_name = "{}.loc".format(age_name)
with self._generate_file(file_name, enc=enc) as stream:
# UTF-16 little endian byte order mark
stream.write(b"\xFF\xFE")
def iter_element(element):
if language_name is None:
yield from element.items()
else:
yield language_name, element
write_line("<?xml version=\"1.0\" encoding=\"utf-16\"?>")
enc = plEncryptedStream.kEncAes if self._version == pvEoa else None
with self._generate_file(filename, enc=enc) as stream:
write_line("<?xml version=\"1.0\" encoding=\"utf-8\"?>")
write_line("<localizations>")
write_line("<age name=\"{}\">", age_name, indent=1)
write_line("<age name=\"{}\">", self._age_name, indent=1)
# Arbitrary strings defined by something like a GUI or a node tree
for set_name, elements in self._strings.items():
for set_name, elements in sets.items():
write_line("<set name=\"{}\">", set_name, indent=2)
for element_name, translations in elements.items():
for element_name, value in elements.items():
write_line("<element name=\"{}\">", element_name, indent=3)
for language_name, value in translations.items():
write_line("<translation language=\"{language}\">{translation}</translation>",
language=language_name, translation=xml_escape(value), indent=4)
write_line("</element>", indent=3)
write_line("</set>", indent=2)
# Journals
if self._journals:
write_line("<set name=\"Journals\">", indent=2)
for journal_name, translations in self._journals.items():
write_line("<element name=\"{}\">", journal_name, indent=3)
for language_name, value in translations.items():
for translation_language, translation_value in iter_element(value):
if _ESHTML_REGEX.search(translation_value):
encoded_value = "<![CDATA[{}]]>".format(translation_value)
else:
encoded_value = xml_escape(translation_value)
write_line("<translation language=\"{language}\">{translation}</translation>",
language=language_name, translation=xml_escape(value), indent=4)
language=translation_language, translation=encoded_value, indent=4)
write_line("</element>", indent=3)
write_line("</set>", indent=2)
@ -164,7 +184,7 @@ class LocalizationConverter:
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("Harvesting Translations")
self._report.progress_add_step("Generating Localization")
self._report.progress_start("Exporting Localization Data")
@ -176,20 +196,23 @@ class LocalizationConverter:
self._report.raise_errors()
def _run_harvest_journals(self):
from ..properties.modifiers import TranslationMixin
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)
for mod_type in filter(None, (getattr(j, "pl_id", None) for j in TranslationMixin.__subclasses__())):
modifier = getattr(i.plasma_modifiers, mod_type)
if modifier.enabled:
translations = [j for j in modifier.translations if j.text_id is not None]
if not translations:
self._report.error("'{}': No content translations available. The localization will not be exported.",
i.name, indent=2)
for j in translations:
self.add_string(modifier.localization_set, modifier.key_name, j.language, j.text_id, indent=1)
inc_progress()
def _run_generate(self):
@ -198,6 +221,6 @@ class LocalizationConverter:
def save(self):
if self._version > pvPots:
self._generate_loc_file()
self._generate_loc_files()
else:
self._generate_journal_texts()
self._generate_text_files()

7
korman/exporter/material.py

@ -805,7 +805,12 @@ class MaterialConverter:
# when the exporter tells us to finalize all our shit
if texture.image is None:
dtm = self._mgr.find_create_object(plDynamicTextMap, name="{}_DynText".format(layer.key.name), bl=bo)
dtm.hasAlpha = texture.use_alpha
if texture.use_alpha:
dtm.hasAlpha = True
if not state.blendFlags & hsGMatState.kBlendMask:
state.blendFlags |= hsGMatState.kBlendAlpha
else:
dtm.hasAlpha = False
dtm.visWidth = int(layer_props.dynatext_resolution)
dtm.visHeight = int(layer_props.dynatext_resolution)
layer.texture = dtm.key

8
korman/nodes/node_python.py

@ -138,6 +138,14 @@ class PlasmaAttributeArguments(bpy.types.PropertyGroup):
class PlasmaAttribute(bpy.types.PropertyGroup):
# This is thy lookup helper
type_LUT = {
bool: "ptAttribBoolean",
float: "ptAttribFloat",
int: "ptAttribInt",
str: "ptAttribString",
}
attribute_id = IntProperty()
attribute_type = StringProperty()
attribute_name = StringProperty()

8
korman/operators/op_export.py

@ -96,6 +96,14 @@ class PlasmaAgeExportOperator(ExportOperator, bpy.types.Operator):
"default": "as_requested",
"options": set()}),
"localization_method": (EnumProperty, {"name": "Localization",
"description": "Specifies how localization data should be exported",
"items": [("database", "Localization Database", "A per-language database compatible with pfLocalizationEditor"),
("database_back_compat", "Localization Database (Compat Mode)", "A per-language database compatible with pfLocalizationEditor and Korman <=0.11"),
("single_file", "Single File", "A single file database, as in Korman <=0.11")],
"default": "database",
"options": set()}),
"export_active": (BoolProperty, {"name": "INTERNAL: Export currently running",
"default": False,
"options": {"SKIP_SAVE"}}),

33
korman/properties/modifiers/base.py

@ -17,7 +17,7 @@ import bpy
from bpy.props import *
import abc
from typing import Any, Dict, Generator
from typing import Any, Dict, Generator, Optional
class PlasmaModifierProperties(bpy.types.PropertyGroup):
@property
@ -57,6 +57,15 @@ class PlasmaModifierProperties(bpy.types.PropertyGroup):
"""
pass
# Commented out to prevent conflicts with TranslationMixin overload.
"""
def export_localization(self, exporter):
'''This is an auxiliary export phase that should only convert localization data. PRP objects
are in an undefined state and therefore should not be used.
'''
pass
"""
@property
def face_sort(self):
"""Indicates that the geometry's faces should be sorted by the engine"""
@ -143,6 +152,28 @@ class PlasmaModifierLogicWiz:
pfm_node.update()
return pfm_node
def _create_python_attribute(self, pfm_node, attribute_name: str, attribute_type: Optional[str] = None, **kwargs):
"""Creates and links a Python Attribute Node to the Python File Node given by `pfm_node`.
This will automatically handle simple attribute types such as numbers and strings, however,
for object linkage, you should specify the optional `attribute_type` to ensure the proper
attribute type is found. For attribute nodes that require multiple values, the `value` may
be set to None and handled in your code."""
from ...nodes.node_python import PlasmaAttribute, PlasmaAttribNodeBase
if attribute_type is None:
assert len(kwargs) == 1 and "value" in kwargs, \
"In order to deduce the attribute_type, exactly one attribute value must be passed as a kw named `value`"
attribute_type = PlasmaAttribute.type_LUT.get(kwargs["value"].__class__)
node_cls = next((i for i in PlasmaAttribNodeBase.__subclasses__() if attribute_type in i.pl_attrib), None)
assert node_cls is not None, "'{}': Unable to find attribute node type for '{}' ('{}')".format(
self.id_data.name, attribute_name, attribute_type
)
node = pfm_node.id_data.nodes.new(node_cls.bl_idname)
node.link_output(pfm_node, "pfm", attribute_name)
for i, j in kwargs.items():
setattr(node, i, j)
return node
@abc.abstractmethod
def logicwiz(self, bo, tree):
pass

114
korman/properties/modifiers/gui.py

@ -14,11 +14,13 @@
# along with Korman. If not, see <http://www.gnu.org/licenses/>.
import bpy
import math
import bmesh
from bpy.props import *
import mathutils
import math
from pathlib import Path
from bpy.props import *
from PyHSPlasma import *
from ...addon_prefs import game_versions
@ -113,14 +115,63 @@ class PlasmaJournalTranslation(bpy.types.PropertyGroup):
items=languages,
default=_DEFAULT_LANGUAGE_NAME,
options=set())
text_id = PointerProperty(name="Journal Contents",
description="Text data block containing the journal's contents for this language",
text_id = PointerProperty(name="Contents",
description="Text data block containing the text for this language",
type=bpy.types.Text,
poll=_poll_nonpytext,
options=set())
class PlasmaJournalBookModifier(PlasmaModifierProperties, PlasmaModifierLogicWiz):
class TranslationMixin:
def export_localization(self, exporter):
translations = [i for i in self.translations if i.text_id is not None]
if not translations:
exporter.report.error("'{}': '{}' No content translations available. The localization will not be exported.",
self.id_data.name, self.bl_label, indent=1)
return
for i in translations:
exporter.locman.add_string(self.localization_set, self.key_name, i.language, i.text_id, indent=1)
def _get_translation(self):
# Ensure there is always a default (read: English) translation available.
default_idx, default = next(((idx, translation) for idx, translation in enumerate(self.translations)
if translation.language == _DEFAULT_LANGUAGE_NAME), (None, None))
if default is None:
default_idx = len(self.translations)
default = self.translations.add()
default.language = _DEFAULT_LANGUAGE_NAME
if self.active_translation_index < len(self.translations):
language = self.translations[self.active_translation_index].language
else:
self.active_translation_index = default_idx
language = default.language
# Due to the fact that we are using IDs to keep the data from becoming insane on new
# additions, we must return the integer id...
return next((idx for key, _, _, idx in languages if key == language))
def _set_translation(self, value):
# We were given an int here, must change to a string
language_name = next((key for key, _, _, i in languages if i == value))
idx = next((idx for idx, translation in enumerate(self.translations)
if translation.language == language_name), None)
if idx is None:
self.active_translation_index = len(self.translations)
translation = self.translations.add()
translation.language = language_name
else:
self.active_translation_index = idx
@property
def localization_set(self):
raise RuntimeError("TranslationMixin subclass needs a localization set getter!")
@property
def translations(self):
raise RuntimeError("TranslationMixin subclass needs a translation getter!")
class PlasmaJournalBookModifier(PlasmaModifierProperties, PlasmaModifierLogicWiz, TranslationMixin):
pl_id = "journalbookmod"
bl_category = "GUI"
@ -156,36 +207,6 @@ class PlasmaJournalBookModifier(PlasmaModifierProperties, PlasmaModifierLogicWiz
type=bpy.types.Object,
poll=idprops.poll_mesh_objects)
def _get_translation(self):
# Ensure there is always a default (read: English) translation available.
default_idx, default = next(((idx, translation) for idx, translation in enumerate(self.journal_translations)
if translation.language == _DEFAULT_LANGUAGE_NAME), (None, None))
if default is None:
default_idx = len(self.journal_translations)
default = self.journal_translations.add()
default.language = _DEFAULT_LANGUAGE_NAME
if self.active_translation_index < len(self.journal_translations):
language = self.journal_translations[self.active_translation_index].language
else:
self.active_translation_index = default_idx
language = default.language
# Due to the fact that we are using IDs to keep the data from becoming insane on new
# additions, we must return the integer id...
return next((idx for key, _, _, idx in languages if key == language))
def _set_translation(self, value):
# We were given an int here, must change to a string
language_name = next((key for key, _, _, i in languages if i == value))
idx = next((idx for idx, translation in enumerate(self.journal_translations)
if translation.language == language_name), None)
if idx is None:
self.active_translation_index = len(self.journal_translations)
translation = self.journal_translations.add()
translation.language = language_name
else:
self.active_translation_index = idx
journal_translations = CollectionProperty(name="Journal Translations",
type=PlasmaJournalTranslation,
options=set())
@ -193,7 +214,8 @@ class PlasmaJournalBookModifier(PlasmaModifierProperties, PlasmaModifierLogicWiz
active_translation = EnumProperty(name="Language",
description="Language of this translation",
items=languages,
get=_get_translation, set=_set_translation,
get=TranslationMixin._get_translation,
set=TranslationMixin._set_translation,
options=set())
def pre_export(self, exporter, bo):
@ -205,15 +227,6 @@ class PlasmaJournalBookModifier(PlasmaModifierProperties, PlasmaModifierLogicWiz
bo.name, version, indent=2)
return
# Export the Journal translation contents
translations = [i for i in self.journal_translations if i.text_id is not None]
if not translations:
exporter.report.error("Journal '{}': No content translations available. The journal will not be exported.",
bo.name, indent=2)
return
for i in translations:
exporter.locman.add_journal(self.key_name, i.language, i.text_id, indent=2)
if self.clickable_region is None:
with utils.bmesh_object("{}_Journal_ClkRgn".format(self.key_name)) as (rgn_obj, bm):
bmesh.ops.create_cube(bm, size=(6.0))
@ -296,17 +309,26 @@ class PlasmaJournalBookModifier(PlasmaModifierProperties, PlasmaModifierLogicWiz
locpath = nodes.new("PlasmaAttribStringNode")
locpath.link_output(journalnode, "pfm", "LocPath")
locpath.value = "{}.Journals.{}".format(age_name, self.key_name)
locpath.value = "{}.{}.{}".format(age_name, self.localization_set, self.key_name)
guitype = nodes.new("PlasmaAttribStringNode")
guitype.link_output(journalnode, "pfm", "GUIType")
guitype.value = self.book_type
@property
def localization_set(self):
return "Journals"
@property
def requires_actor(self):
# We are too late in the export to be harvested automatically, so let's be explicit
return True
@property
def translations(self):
# Backwards compatibility thunk.
return self.journal_translations
linking_pfms = {
pvPots : {

147
korman/properties/modifiers/render.py

@ -19,10 +19,11 @@ from bpy.props import *
import functools
from PyHSPlasma import *
from .base import PlasmaModifierProperties, PlasmaModifierUpgradable
from .base import PlasmaModifierProperties, PlasmaModifierLogicWiz, PlasmaModifierUpgradable
from ...exporter.etlight import _NUM_RENDER_LAYERS
from ...exporter import utils
from ...exporter.explosions import ExportError
from .gui import languages, PlasmaJournalTranslation, TranslationMixin
from ... import idprops
class PlasmaBlendOntoObject(bpy.types.PropertyGroup):
@ -609,6 +610,150 @@ class PlasmaLightingMod(PlasmaModifierProperties):
return False
_LOCALIZED_TEXT_PFM = (
{ 'id': 1, 'type': "ptAttribDynamicMap", 'name': "dynTextMap", },
{ 'id': 2, 'type': "ptAttribString", 'name': "locPath" },
{ 'id': 3, 'type': "ptAttribString", 'name': "fontFace" },
{ 'id': 4, 'type': "ptAttribInt", 'name': "fontSize" },
{ 'id': 5, 'type': "ptAttribFloat", 'name': "fontColorR" },
{ 'id': 6, 'type': "ptAttribFloat", 'name': "fontColorG" },
{ 'id': 7, 'type': "ptAttribFloat", 'name': "fontColorB" },
{ 'id': 8, 'type': "ptAttribFloat", 'name': "fontColorA" },
{ 'id': 9, 'type': "ptAttribInt", 'name': "marginTop" },
{ 'id': 10, 'type': "ptAttribInt", 'name': "marginLeft" },
{ 'id': 11, 'type': "ptAttribInt", 'name': "marginBottom" },
{ 'id': 12, 'type': "ptAttribInt", 'name': "marginRight" },
{ 'id': 13, 'type': "ptAttribInt", 'name': "lineSpacing" },
# Yes, it's really a ptAttribDropDownList, but those are only for use in
# artist generated node trees.
{ 'id': 14, 'type': "ptAttribString", 'name': "justify" },
{ 'id': 15, 'type': "ptAttribFloat", 'name': "clearColorR" },
{ 'id': 16, 'type': "ptAttribFloat", 'name': "clearColorG" },
{ 'id': 17, 'type': "ptAttribFloat", 'name': "clearColorB" },
{ 'id': 18, 'type': "ptAttribFloat", 'name': "clearColorA" },
)
class PlasmaLocalizedTextModifier(PlasmaModifierProperties, PlasmaModifierLogicWiz, TranslationMixin):
pl_id = "dynatext"
bl_category = "Render"
bl_label = "Localized Text"
bl_description = ""
bl_icon = "TEXT"
translations = CollectionProperty(name="Translations",
type=PlasmaJournalTranslation,
options=set())
active_translation_index = IntProperty(options={"HIDDEN"})
active_translation = EnumProperty(name="Language",
description="Language of this translation",
items=languages,
get=TranslationMixin._get_translation,
set=TranslationMixin._set_translation,
options=set())
def _poll_dyna_text(self, value: bpy.types.Texture) -> bool:
if value.type != "IMAGE":
return False
if value.image is not None:
return False
tex_materials = frozenset(value.users_material)
obj_materials = frozenset(filter(None, (i.material for i in self.id_data.material_slots)))
return bool(tex_materials & obj_materials)
texture = PointerProperty(name="Texture",
description="The texture to write the localized text on",
type=bpy.types.Texture,
poll=_poll_dyna_text)
font_face = StringProperty(name="Font Face",
default="Arial",
options=set())
font_size = IntProperty(name="Font Size",
default=12,
min=0, soft_max=72,
options=set())
font_color = FloatVectorProperty(name="Font Color",
default=(0.0, 0.0, 0.0, 1.0),
min=0.0, max=1.0,
subtype="COLOR", size=4,
options=set())
# Using individual properties for better UI documentation
margin_top = IntProperty(name="Margin Top",
min=-4096, soft_min=0, max=4096,
options=set())
margin_left = IntProperty(name="Margin Left",
min=-4096, soft_min=0, max=4096,
options=set())
margin_bottom = IntProperty(name="Margin Bottom",
min=-4096, soft_min=0, max=4096,
options=set())
margin_right = IntProperty(name="Margin Right",
min=-4096, soft_min=0, max=4096,
options=set())
justify = EnumProperty(name="Justification",
items=[("left", "Left", ""),
("center", "Center", ""),
("right", "Right", "")],
default="left",
options=set())
line_spacing = IntProperty(name="Line Spacing",
default=0,
soft_min=0, soft_max=10,
options=set())
def pre_export(self, exporter, bo):
yield self.convert_logic(bo, age_name=exporter.age_name, version=exporter.mgr.getVer())
def logicwiz(self, bo, tree, *, age_name, version):
# Rough justice. If the dynamic text map texture doesn't request alpha, then we'll want
# to explicitly clear it to the material's diffuse color. This will allow artists to trivially
# add text surfaces directly to objects, opposed to where Cyan tends to use a separate
# transparent object over the background object.
if not self.texture.use_alpha:
material_filter = lambda slot: slot and slot.material and self.texture in (i.texture for i in slot.material.texture_slots if i)
for slot in filter(material_filter, bo.material_slots):
self._create_nodes(bo, tree, age_name=age_name, version=version,
material=slot.material, clear_color=slot.material.diffuse_color)
else:
self._create_nodes(bo, tree, age_name=age_name, version=version)
def _create_nodes(self, bo, tree, *, age_name, version, material=None, clear_color=None):
pfm_node = self._create_python_file_node(tree, "xDynTextLoc.py", _LOCALIZED_TEXT_PFM)
loc_path = self.key_name if version <= pvPots else "{}.{}.{}".format(age_name, self.localization_set, self.key_name)
self._create_python_attribute(pfm_node, "dynTextMap", "ptAttribDynamicMap",
target_object=bo, material=material, texture=self.texture)
self._create_python_attribute(pfm_node, "locPath", value=loc_path)
self._create_python_attribute(pfm_node, "fontFace", value=self.font_face)
self._create_python_attribute(pfm_node, "fontSize", value=self.font_size)
self._create_python_attribute(pfm_node, "fontColorR", value=self.font_color[0])
self._create_python_attribute(pfm_node, "fontColorG", value=self.font_color[1])
self._create_python_attribute(pfm_node, "fontColorB", value=self.font_color[2])
self._create_python_attribute(pfm_node, "fontColorA", value=self.font_color[3])
self._create_python_attribute(pfm_node, "marginTop", value=self.margin_top)
self._create_python_attribute(pfm_node, "marginLeft", value=self.margin_left)
self._create_python_attribute(pfm_node, "marginBottom", value=self.margin_bottom)
self._create_python_attribute(pfm_node, "marginRight", value=self.margin_right)
self._create_python_attribute(pfm_node, "justify", value=self.justify)
if clear_color is not None:
self._create_python_attribute(pfm_node, "clearColorR", value=clear_color[0])
self._create_python_attribute(pfm_node, "clearColorG", value=clear_color[1])
self._create_python_attribute(pfm_node, "clearColorB", value=clear_color[2])
self._create_python_attribute(pfm_node, "clearColorA", value=1.0)
@property
def localization_set(self):
return "DynaTexts"
def sanity_check(self):
if self.texture is None:
raise ExportError("'{}': Localized Text modifier requires a texture", self.id_data.name)
class PlasmaShadowCasterMod(PlasmaModifierProperties):
pl_id = "rtshadow"

45
korman/ui/modifiers/render.py

@ -97,6 +97,51 @@ def decal_receive(modifier, layout, context):
layout.alert = decal_mgr is None
layout.prop_search(mgr_ref, "name", scene, "decal_managers", icon="BRUSH_DATA")
def dynatext(modifier, layout, context):
col = layout.column()
col.alert = modifier.texture is None
col.prop(modifier, "texture")
if modifier.texture is None:
col.label("You must specify a blank image texture to draw on.", icon="ERROR")
split = layout.split()
col = split.column()
col.label("Content Translations:")
col.prop(modifier, "active_translation", text="")
# This should never fail...
try:
translation = modifier.translations[modifier.active_translation_index]
except Exception as e:
col.label(text="Error (see console)", icon="ERROR")
print(e)
else:
col.prop(translation, "text_id", text="")
col = split.column()
col.label("Font:")
sub = col.row()
sub.alert = not modifier.font_face.strip()
sub.prop(modifier, "font_face", text="", icon="OUTLINER_DATA_FONT")
col.prop(modifier, "font_size", text="Size")
layout.separator()
split = layout.split()
col = split.column(align=True)
if modifier.texture is not None:
col.alert = modifier.margin_top + modifier.margin_bottom >= int(modifier.texture.plasma_layer.dynatext_resolution)
col.prop(modifier, "margin_top")
col.prop(modifier, "margin_bottom")
col = split.column(align=True)
if modifier.texture is not None:
col.alert = modifier.margin_left + modifier.margin_right >= int(modifier.texture.plasma_layer.dynatext_resolution)
col.prop(modifier, "margin_left")
col.prop(modifier, "margin_right")
layout.separator()
flow = layout.column_flow(columns=2)
flow.prop_menu_enum(modifier, "justify")
flow.prop(modifier, "line_spacing")
def fademod(modifier, layout, context):
layout.prop(modifier, "fader_type")

1
korman/ui/ui_world.py

@ -276,6 +276,7 @@ class PlasmaAgePanel(AgeButtonsPanel, bpy.types.Panel):
layout.separator()
layout.prop(age, "envmap_method")
layout.prop(age, "lighting_method")
layout.prop(age, "localization_method")
layout.prop(age, "python_method")
layout.prop(age, "texcache_method")

Loading…
Cancel
Save