Browse Source

Add localized text modifier.

pull/276/head
Adam Johnson 3 years ago
parent
commit
a31b8ac964
Signed by: Hoikas
GPG Key ID: 0B6515D6FF6F271E
  1. 7
      korman/exporter/material.py
  2. 86
      korman/properties/modifiers/gui.py
  3. 148
      korman/properties/modifiers/render.py
  4. 45
      korman/ui/modifiers/render.py

7
korman/exporter/material.py

@ -799,7 +799,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

86
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,50 @@ 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 _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 translations(self):
raise RuntimeError("TranslationMixin subclass needs a translation getter!")
class PlasmaJournalBookModifier(PlasmaModifierProperties, PlasmaModifierLogicWiz, TranslationMixin):
pl_id = "journalbookmod"
bl_category = "GUI"
@ -156,36 +194,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 +201,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):
@ -307,6 +316,11 @@ class PlasmaJournalBookModifier(PlasmaModifierProperties, PlasmaModifierLogicWiz
# 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 : {

148
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):
@ -604,6 +605,151 @@ 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 export(self, exporter, bo, so):
# TODO: This should probably be pulled out into its own export pass for locs
for i in filter(None, self.translations):
exporter.locman.add_string("DynaTexts", self.key_name, i.language, i.text_id, indent=2)
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 "{}.DynaTexts.{}".format(age_name, 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)
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")

Loading…
Cancel
Save