diff --git a/korman/exporter/convert.py b/korman/exporter/convert.py index 644e100..c12627b 100644 --- a/korman/exporter/convert.py +++ b/korman/exporter/convert.py @@ -510,8 +510,12 @@ class Exporter: @handle_temporary.register(bpy.types.NodeTree) def _(temporary, parent): - self.exit_stack.enter_context(TemporaryObject(temporary, bpy.data.node_groups.remove)) - log_msg(f"'{parent.name}' generated NodeTree '{temporary.name}'") + # NodeTrees are reuseable, so make sure we haven't already encountered it. + if not temporary.name in self.want_node_trees: + self.exit_stack.enter_context(TemporaryObject(temporary, bpy.data.node_groups.remove)) + log_msg(f"'{parent.name}' generated NodeTree '{temporary.name}'") + else: + log_msg(f"'{parent.name}' reused NodeTree '{temporary.name}'") if temporary.bl_idname == "PlasmaNodeTree": parent_so = self.mgr.find_create_object(plSceneObject, bl=parent) self.want_node_trees[temporary.name].add((parent, parent_so)) @@ -594,6 +598,10 @@ class Exporter: else: return bpy.context.scene.world.plasma_age.age_name + @property + def age_sdl(self) -> bool: + return bpy.context.scene.world.plasma_age.age_sdl + @property def dat_only(self): return self._op.dat_only diff --git a/korman/operators/op_ui.py b/korman/operators/op_ui.py index ffa3b17..fa90436 100644 --- a/korman/operators/op_ui.py +++ b/korman/operators/op_ui.py @@ -77,11 +77,17 @@ class CollectionRemoveOperator(UIOperator, bpy.types.Operator): index_prop = StringProperty(name="Index Property", description="Name of the active element index property", options=set()) + manual_index = IntProperty(name="Manual Index", + description="Manual integer index to remove", + options=set()) def execute(self, context): props = getattr(context, self.context).path_resolve(self.group_path) collection = getattr(props, self.collection_prop) - index = getattr(props, self.index_prop) + if self.index_prop: + index = getattr(props, self.index_prop) + else: + index = self.manual_index if len(collection) > index: collection.remove(index) setattr(props, self.index_prop, index - 1) diff --git a/korman/plasma_api.py b/korman/plasma_api.py new file mode 100644 index 0000000..0d63a8e --- /dev/null +++ b/korman/plasma_api.py @@ -0,0 +1,134 @@ +# This file is part of Korman. +# +# Korman is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Korman is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Korman. If not, see . + +python_files = { + "xAgeSDLBoolShowHide.py": ( + { "id": 1, "type": "ptAttribString", "name": "sdlName" }, + { "id": 2, "type": "ptAttribBoolean", "name": "showOnTrue" }, + # --- CWE Only Below --- + { "id": 3, "type": "ptAttribBoolean", "name": "defaultValue" }, + { "id": 4, "type": "ptAttribBoolean", "name": "evalOnFirstUpdate "}, + ), + + "xAgeSDLIntShowHide.py": ( + { "id": 1, "type": "ptAttribString", "name": "stringVarName" }, + { "id": 2, "type": "ptAttribString", "name": "stringShowStates" }, + # --- CWE Only Below --- + { "id": 3, "type": "ptAttribInt", "name": "intDefault" }, + { "id": 4, "type": "ptAttribBoolean", "name": "boolFirstUpdate "}, + ), + + # Provided by all variants of Uru and Myst V + "xDialogToggle.py": ( + { "id": 1, "type": "ptAttribActivator", "name": "Activate" }, + { "id": 4, "type": "ptAttribString", "name": "Vignette" }, + ), + + # Provided by CWE or OfflineKI + "xDynTextLoc.py": ( + { "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" }, + { "id": 19, "type": "ptAttribBoolean", "name": "blockRGB" }, + ), + + # Provided by CWE and OfflineKI + "xEntryCam.py": ( + { "id": 1, "type": "ptAttribActivator", "name": "actRegionSensor" }, + { "id": 2, "type": "ptAttribSceneobject", "name": "camera" }, + { "id": 3, "type": "ptAttribBoolean", "name": "undoFirstPerson" }, + ), + + # Provided by CWE + "xJournalBookGUIPopup.py": ( + { "id": 1, "type": "ptAttribActivator", "name": "actClickableBook" }, + { "id": 10, "type": "ptAttribBoolean", "name": "StartOpen" }, + { "id": 11, "type": "ptAttribFloat", "name": "BookWidth" }, + { "id": 12, "type": "ptAttribFloat", "name": "BookHeight" }, + { "id": 13, "type": "ptAttribString", "name": "LocPath" }, + { "id": 14, "type": "ptAttribString", "name": "GUIType" }, + ), + + # Provided by all variants of Uru and Myst V + "xLinkingBookGUIPopup.py": ( + { "id": 1, "type": "ptAttribActivator", "name": "actClickableBook" }, + { "id": 2, "type": "ptAttribBehavior", "name": "SeekBehavior" }, + { "id": 3, "type": "ptAttribResponder", "name": "respLinkResponder" }, + { "id": 4, "type": "ptAttribString", "name": "TargetAge" }, + { "id": 5, "type": "ptAttribActivator", "name": "actBookshelf" }, + { "id": 6, "type": "ptAttribActivator", "name": "shareRegion" }, + { "id": 7, "type": "ptAttribBehavior", "name": "shareBookSeek" }, + { "id": 10, "type": "ptAttribBoolean", "name": "IsDRCStamped" }, + { "id": 11, "type": "ptAttribBoolean", "name": "ForceThirdPerson" }, + ), + + # Supplied by the OfflineKI script: + # https://gitlab.com/diafero/offline-ki/blob/master/offlineki/xSimpleJournal.py + "xSimpleJournal.py": ( + { "id": 1, "type": "ptAttribActivator", "name": "bookClickable" }, + { "id": 2, "type": "ptAttribString", "name": "journalFileName" }, + { "id": 3, "type": "ptAttribBoolean", "name": "isNotebook" }, + { "id": 4, "type": "ptAttribFloat", "name": "BookWidth" }, + { "id": 5, "type": "ptAttribFloat", "name": "BookHeight" }, + ), + + # Supplied by the OfflineKI script: + # https://gitlab.com/diafero/offline-ki/blob/master/offlineki/xSimpleLinkingBook.py + "xSimpleLinkingBook.py": ( + { "id": 1, "type": "ptAttribActivator", "name": "bookClickable" }, + { "id": 2, "type": "ptAttribString", "name": "destinationAge" }, + { "id": 3, "type": "ptAttribString", "name": "spawnPoint" }, + { "id": 4, "type": "ptAttribString", "name": "linkPanel" }, + { "id": 5, "type": "ptAttribString", "name": "bookCover" }, + { "id": 6, "type": "ptAttribString", "name": "stampTexture" }, + { "id": 7, "type": "ptAttribFloat", "name": "stampX" }, + { "id": 8, "type": "ptAttribFloat", "name": "stampY" }, + { "id": 9, "type": "ptAttribFloat", "name": "bookWidth" }, + { "id": 10, "type": "ptAttribFloat", "name": "BookHeight" }, + { "id": 11, "type": "ptAttribBehavior", "name": "msbSeekBeforeUI" }, + { "id": 12, "type": "ptAttribResponder", "name": "respOneShot" }, + ), + + # Provided by CWE or OfflineKI + "xSitCam.py": ( + { "id": 1, "type": "ptAttribActivator", "name": "sitAct" }, + { "id": 2, "type": "ptAttribSceneobject", "name": "sitCam" }, + ), + + # Provided by all variants of Uru and Myst V + "xTelescope.py": ( + { "id": 1, "type": "ptAttribActivator", "name": "Activate" }, + { "id": 2, "type": "ptAttribSceneobject", "name": "Camera" }, + { "id": 3, "type": "ptAttribBehavior", "name": "Behavior" }, + { "id": 4, "type": "ptAttribString", "name": "Vignette" }, + ) +} diff --git a/korman/properties/modifiers/avatar.py b/korman/properties/modifiers/avatar.py index c6397f5..bbd0bc9 100644 --- a/korman/properties/modifiers/avatar.py +++ b/korman/properties/modifiers/avatar.py @@ -78,16 +78,6 @@ class PlasmaLadderModifier(PlasmaModifierProperties): return True -# Use xSitCam.py for when we want a camera to pop up -sitting_pfm = { - "filename": "xSitCam.py", - "attribs": ( - { 'id': 1, 'type': "ptAttribActivator", 'name': "sitAct" }, - { 'id': 2, 'type': "ptAttribSceneobject", 'name': "sitCam" }, - ) -} - - sitting_approach_flags = [("kApproachFront", "Front", "Approach from the font"), ("kApproachLeft", "Left", "Approach from the left"), ("kApproachRight", "Right", "Approach from the right"), @@ -140,7 +130,7 @@ class PlasmaSittingBehavior(idprops.IDPropObjectMixin, PlasmaModifierProperties, sittingmod.name = "SittingBeh" # xSitCam.py PythonFileMod if self.sitting_camera is not None: - sittingpynode = self._create_python_file_node(tree, sitting_pfm["filename"], sitting_pfm["attribs"]) + sittingpynode = self._create_python_standard_file_node(tree, "xSitCam.py") sittingmod.link_output(sittingpynode, "satisfies", "sitAct") # Camera Object diff --git a/korman/properties/modifiers/base.py b/korman/properties/modifiers/base.py index 977c069..5fee595 100644 --- a/korman/properties/modifiers/base.py +++ b/korman/properties/modifiers/base.py @@ -26,6 +26,7 @@ if TYPE_CHECKING: from ...nodes.node_python import * from ... import helpers +from ... import plasma_api class PlasmaModifierProperties(bpy.types.PropertyGroup): @property @@ -180,20 +181,24 @@ class PlasmaModifierProperties(bpy.types.PropertyGroup): class PlasmaModifierLogicWiz: def convert_logic(self, bo, **kwargs): - """Creates, converts, and returns an unmanaged NodeTree for this logic wizard. If the wizard - fails during conversion, the temporary tree is deleted for you. However, on success, you - are responsible for removing the tree from Blender, if applicable.""" + """Attempts to look up an already existing logic tree matching the name provided and returns + it, if found. If not, creates, converts, and returns an unmanaged NodeTree for this wizard. + If the wizard fails during conversion, the temporary tree is deleted for you. However, on + success, you are responsible for removing the tree from Blender, if applicable.""" name = kwargs.pop("name", self.key_name) assert not "tree" in kwargs - tree = bpy.data.node_groups.new(name, "PlasmaNodeTree") - kwargs["tree"] = tree - try: - self.logicwiz(bo, **kwargs) - except: - bpy.data.node_groups.remove(tree) - raise - else: - return tree + + node_groups = bpy.data.node_groups + tree = node_groups.get(name) + if tree is None: + tree = node_groups.new(name, "PlasmaNodeTree") + kwargs["tree"] = tree + try: + self.logicwiz(bo, **kwargs) + except: + bpy.data.node_groups.remove(tree) + raise + return tree def _create_python_file_node(self, tree, filename: str, attributes: Dict[str, Any]) -> bpy.types.Node: pfm_node = tree.nodes.new("PlasmaPythonFileNode") @@ -207,6 +212,10 @@ class PlasmaModifierLogicWiz: pfm_node.update() return pfm_node + def _create_standard_python_file_node(self, tree, filename: str) -> bpy.types.Node: + """Create a Python File Node for a standard Plasma Python API file (e.g. xAgeSDLBoolShowHide.py)""" + return self._create_python_file_node(tree, filename, plasma_api.python_files[filename]) + def _create_python_attribute(self, pfm_node: PlasmaPythonFileNode, attribute_name: str, **kwargs): """Creates and links a Python Attribute Node to the Python File Node given by `pfm_node`. For attribute nodes that require multiple values, the `value` may be set to None and diff --git a/korman/properties/modifiers/gui.py b/korman/properties/modifiers/gui.py index 5b06df2..0016e4d 100644 --- a/korman/properties/modifiers/gui.py +++ b/korman/properties/modifiers/gui.py @@ -35,33 +35,6 @@ if TYPE_CHECKING: from ...exporter import Exporter from .game_gui import PlasmaGameGuiDialogModifier - -journal_pfms = { - pvPots : { - # Supplied by the OfflineKI script: - # https://gitlab.com/diafero/offline-ki/blob/master/offlineki/xSimpleJournal.py - "filename": "xSimpleJournal.py", - "attribs": ( - { 'id': 1, 'type': "ptAttribActivator", "name": "bookClickable" }, - { 'id': 2, 'type': "ptAttribString", "name": "journalFileName" }, - { 'id': 3, 'type': "ptAttribBoolean", "name": "isNotebook" }, - { 'id': 4, 'type': "ptAttribFloat", "name": "BookWidth" }, - { 'id': 5, 'type': "ptAttribFloat", "name": "BookHeight" }, - ) - }, - pvMoul : { - "filename": "xJournalBookGUIPopup.py", - "attribs": ( - { 'id': 1, 'type': "ptAttribActivator", 'name': "actClickableBook" }, - { 'id': 10, 'type': "ptAttribBoolean", 'name': "StartOpen" }, - { 'id': 11, 'type': "ptAttribFloat", 'name': "BookWidth" }, - { 'id': 12, 'type': "ptAttribFloat", 'name': "BookHeight" }, - { 'id': 13, 'type': "ptAttribString", 'name': "LocPath" }, - { 'id': 14, 'type': "ptAttribString", 'name': "GUIType" }, - ) - }, -} - # Do not change the numeric IDs. They allow the list to be rearranged. _languages = [("Dutch", "Nederlands", "Dutch", 0), ("English", "English", "", 1), @@ -246,11 +219,11 @@ class PlasmaJournalBookModifier(PlasmaModifierProperties, PlasmaModifierLogicWiz def logicwiz(self, bo, tree, age_name, version): # Assign journal script based on target version - journal_pfm = journal_pfms[version] - journalnode = self._create_python_file_node(tree, journal_pfm["filename"], journal_pfm["attribs"]) if version <= pvPots: + journalnode = self._create_standard_python_file_node(tree, "xSimpleJournal.py") self._create_pots_nodes(bo, tree.nodes, journalnode, age_name) else: + journalnode = self._create_standard_python_file_node(tree, "xJournalBookGUIPopup.py") self._create_moul_nodes(bo, tree.nodes, journalnode, age_name) def _create_pots_nodes(self, clickable_object, nodes, journalnode, age_name): @@ -324,43 +297,6 @@ class PlasmaJournalBookModifier(PlasmaModifierProperties, PlasmaModifierLogicWiz return self.journal_translations -linking_pfms = { - pvPots : { - # Supplied by the OfflineKI script: - # https://gitlab.com/diafero/offline-ki/blob/master/offlineki/xSimpleLinkingBook.py - "filename": "xSimpleLinkingBook.py", - "attribs": ( - { 'id': 1, 'type': "ptAttribActivator", "name": "bookClickable" }, - { 'id': 2, 'type': "ptAttribString", "name": "destinationAge" }, - { 'id': 3, 'type': "ptAttribString", "name": "spawnPoint" }, - { 'id': 4, 'type': "ptAttribString", "name": "linkPanel" }, - { 'id': 5, 'type': "ptAttribString", "name": "bookCover" }, - { 'id': 6, 'type': "ptAttribString", "name": "stampTexture" }, - { 'id': 7, 'type': "ptAttribFloat", "name": "stampX" }, - { 'id': 8, 'type': "ptAttribFloat", "name": "stampY" }, - { 'id': 9, 'type': "ptAttribFloat", "name": "bookWidth" }, - { 'id': 10, 'type': "ptAttribFloat", "name": "BookHeight" }, - { 'id': 11, 'type': "ptAttribBehavior", "name": "msbSeekBeforeUI" }, - { 'id': 12, 'type': "ptAttribResponder", "name": "respOneShot" }, - ) - }, - pvMoul : { - "filename": "xLinkingBookGUIPopup.py", - "attribs": ( - { 'id': 1, 'type': "ptAttribActivator", 'name': "actClickableBook" }, - { 'id': 2, 'type': "ptAttribBehavior", 'name': "SeekBehavior" }, - { 'id': 3, 'type': "ptAttribResponder", 'name': "respLinkResponder" }, - { 'id': 4, 'type': "ptAttribString", 'name': "TargetAge" }, - { 'id': 5, 'type': "ptAttribActivator", 'name': "actBookshelf" }, - { 'id': 6, 'type': "ptAttribActivator", 'name': "shareRegion" }, - { 'id': 7, 'type': "ptAttribBehavior", 'name': "shareBookSeek" }, - { 'id': 10, 'type': "ptAttribBoolean", 'name': "IsDRCStamped" }, - { 'id': 11, 'type': "ptAttribBoolean", 'name': "ForceThirdPerson" }, - ) - }, -} - - class PlasmaLinkingBookModifier(PlasmaModifierProperties, PlasmaModifierLogicWiz): pl_id = "linkingbookmod" @@ -495,12 +431,11 @@ class PlasmaLinkingBookModifier(PlasmaModifierProperties, PlasmaModifierLogicWiz yield self.seek_point.name def logicwiz(self, bo, tree, age_name, version): - # Assign linking book script based on target version - linking_pfm = linking_pfms[version] - linkingnode = self._create_python_file_node(tree, linking_pfm["filename"], linking_pfm["attribs"]) if version <= pvPots: + linkingnode = self._create_standard_python_file_node(tree, "xSimpleLinkingBook.py") self._create_pots_nodes(bo, tree.nodes, linkingnode, age_name) else: + linkingnode = self._create_standard_python_file_node(tree, "xLinkingBookGUIPopup.py") self._create_moul_nodes(bo, tree.nodes, linkingnode, age_name) def _create_pots_nodes(self, clickable_object, nodes, linkingnode, age_name): @@ -667,14 +602,6 @@ class PlasmaLinkingBookModifier(PlasmaModifierProperties, PlasmaModifierLogicWiz raise ExportError("{}: Linking Book modifier requires a seek point!", self.id_data.name) -dialog_toggle = { - "filename": "xDialogToggle.py", - "attribs": ( - { 'id': 1, 'type': "ptAttribActivator", 'name': "Activate" }, - { 'id': 4, 'type': "ptAttribString", 'name': "Vignette" }, - ) -} - class PlasmaNotePopupModifier(PlasmaModifierProperties, PlasmaModifierLogicWiz): pl_id = "note_popup" @@ -755,11 +682,7 @@ class PlasmaNotePopupModifier(PlasmaModifierProperties, PlasmaModifierLogicWiz): nodes = tree.nodes # xDialogToggle.py PythonFile Node - dialog_node = self._create_python_file_node( - tree, - dialog_toggle["filename"], - dialog_toggle["attribs"] - ) + dialog_node = self._create_standard_python_file_node(tree, "xDialogToggle.py") self._create_python_attribute(dialog_node, "Vignette", value=self.gui_page) # Clickable diff --git a/korman/properties/modifiers/logic.py b/korman/properties/modifiers/logic.py index dfd9e68..86e39b3 100644 --- a/korman/properties/modifiers/logic.py +++ b/korman/properties/modifiers/logic.py @@ -15,10 +15,8 @@ from __future__ import annotations -import bmesh import bpy from bpy.props import * -import mathutils from PyHSPlasma import * from typing import * @@ -28,21 +26,17 @@ if TYPE_CHECKING: from ...nodes.node_messages import * from ...nodes.node_responder import * +from typing import * + +if TYPE_CHECKING: + from ...exporter import Exporter + from ...addon_prefs import game_versions from .base import PlasmaModifierProperties, PlasmaModifierLogicWiz from ... import enum_props from ...exporter import ExportError, utils from ... import idprops -entry_cam_pfm = { - "filename": "xEntryCam.py", - "attribs": ( - { 'id': 1, 'type': "ptAttribActivator", 'name': "actRegionSensor" }, - { 'id': 2, 'type': "ptAttribSceneobject", 'name': "camera" }, - { 'id': 3, 'type': "ptAttribBoolean", 'name': "undoFirstPerson" }, - ) -} - class PlasmaVersionedNodeTree(idprops.IDPropMixin, bpy.types.PropertyGroup): version = EnumProperty(name="Version", description="Plasma versions this node tree exports under", @@ -137,11 +131,7 @@ class PlasmaSpawnPoint(PlasmaModifierProperties, PlasmaModifierLogicWiz): yield self.convert_logic(bo) def logicwiz(self, bo, tree): - pfm_node = self._create_python_file_node( - tree, - entry_cam_pfm["filename"], - entry_cam_pfm["attribs"] - ) + pfm_node = self._create_standard_python_file_node(tree, "xEntryCam.py") volume_sensor: PlasmaVolumeSensorNode = tree.nodes.new("PlasmaVolumeSensorNode") volume_sensor.find_input_socket("enter").allow = True @@ -193,15 +183,83 @@ class PlasmaMaintainersMarker(PlasmaModifierProperties): return True -telescope_pfm = { - "filename": "xTelescope.py", - "attribs": ( - { 'id': 1, 'type': "ptAttribActivator", 'name': "Activate" }, - { 'id': 2, 'type': "ptAttribSceneobject", 'name': "Camera" }, - { 'id': 3, 'type': "ptAttribBehavior", 'name': "Behavior" }, - { 'id': 4, 'type': "ptAttribString", 'name': "Vignette" }, +class PlasmaSDLIntState(bpy.types.PropertyGroup): + value: int = IntProperty( + name="State Value", + description="The object is shown when the SDL variable is set to this value", + min=0, + soft_max=255, + options=set() + ) + + +class PlasmaSDLShowHide(PlasmaModifierProperties, PlasmaModifierLogicWiz): + pl_id = "sdl_showhide" + + bl_category = "Logic" + bl_label = "SDL Show/Hide" + bl_description = "Show/Hide an object based on an SDL Variable" + bl_object_types = {"MESH", "FONT"} + bl_icon = "VISIBLE_IPO_OFF" + + sdl_variable: str = StringProperty( + name="SDL Variable", + description="Name of the SDL variable that controls visibility", + options=set() + ) + variable_type: str = EnumProperty( + name="Type", + description="Data type of the SDL variable", + items=[ + ("bool", "Boolean", "A boolean, used to represent simple on/off for a single state"), + ("int", "Integer", "An integer, used to represent multiple state combinations"), + ], + options=set() + ) + + int_states = CollectionProperty(type=PlasmaSDLIntState) + bool_state: bool = BoolProperty( + name="Show When True", + description="If checked, show this object when the SDL Variable is TRUE. If not, hide it when TRUE.", + default=True, + options=set() ) -} + + def created(self): + # Ensure at least one SDL int state is precreated for ease of use. + # REMEMBER: Blender's "sequences" don't do truthiness correctly... + if len(self.int_states) == 0: + self.int_states.add() + + def sanity_check(self, exporter: Exporter): + if not exporter.age_sdl: + raise ExportError(f"'{self.id_data.name}': Age Global SDL is required for the SDL Show/Hide modifier!") + if not self.sdl_variable.strip(): + raise ExportError(f"'{self.id_data.name}': A valid SDL variable is required for the SDL Show/Hide modifier!") + + def logicwiz(self, bo, tree): + if self.variable_type == "bool": + pfm_node = self._create_standard_python_file_node(tree, "xAgeSDLBoolShowHide.py") + self._create_python_attribute(pfm_node, "sdlName", value=self.sdl_variable) + self._create_python_attribute(pfm_node, "showOnTrue", value=self.bool_state) + elif self.variable_type == "int": + pfm_node = self._create_standard_python_file_node(tree, "xAgeSDLIntShowHide.py") + self._create_python_attribute(pfm_node, "stringVarName", value=self.sdl_variable) + self._create_python_attribute(pfm_node, "stringShowStates", value=",".join(self._states)) + else: + raise RuntimeError() + + @property + def key_name(self): + if self.variable_type == "bool": + return f"cPythBoolShowHide_{self.sdl_variable}_{self.bool_state:d}" + elif self.variable_type == "int": + return f"cPythIntShowHide_{self.sdl_variable}_{'-'.join(self._states)}" + + @property + def _states(self) -> Iterable[str]: + """Returns a sorted, deduplicated iterable of the integer (converted to strings) states we should be visible in.""" + return (str(i) for i in sorted(frozenset((i.value for i in self.int_states)))) class PlasmaTelescope(PlasmaModifierProperties, PlasmaModifierLogicWiz): @@ -244,7 +302,7 @@ class PlasmaTelescope(PlasmaModifierProperties, PlasmaModifierLogicWiz): nodes = tree.nodes # Create Python Node - telescopepynode = self._create_python_file_node(tree, telescope_pfm["filename"], telescope_pfm["attribs"]) + telescopepynode = self._create_standard_python_file_node(tree, "xTelescope.py") # Clickable telescopeclick = nodes.new("PlasmaClickableNode") diff --git a/korman/properties/modifiers/render.py b/korman/properties/modifiers/render.py index ec0645e..3488bfa 100644 --- a/korman/properties/modifiers/render.py +++ b/korman/properties/modifiers/render.py @@ -624,30 +624,6 @@ 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" }, - { 'id': 19, 'type': "ptAttribBoolean", 'name': "blockRGB" }, -) - class PlasmaLocalizedTextModifier(PlasmaModifierProperties, PlasmaModifierLogicWiz, TranslationMixin): pl_id = "dynatext" pl_page_types = {"gui", "room"} @@ -738,7 +714,7 @@ class PlasmaLocalizedTextModifier(PlasmaModifierProperties, PlasmaModifierLogicW 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) + pfm_node = self._create_standard_python_file_node(tree, "xDynTextLoc.py") 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", diff --git a/korman/ui/modifiers/logic.py b/korman/ui/modifiers/logic.py index a99b169..f401ed5 100644 --- a/korman/ui/modifiers/logic.py +++ b/korman/ui/modifiers/logic.py @@ -60,6 +60,43 @@ def maintainersmarker(modifier, layout, context): layout.label(text="Positive Y is North, positive Z is up.") layout.prop(modifier, "calibration") +def sdl_showhide(modifier: PlasmaSDLShowHide, layout, context): + if not context.scene.world.plasma_age.age_sdl: + layout.label("This modifier requires Age Global SDL!", icon="ERROR") + return + + valid_variable = modifier.sdl_variable.strip() + layout.alert = not valid_variable + layout.prop(modifier, "sdl_variable") + if not valid_variable: + layout.label("A valid SDL variable is required!", icon="ERROR") + layout.alert = False + layout.prop(modifier, "variable_type") + layout.separator() + + def setup_collection_operator(op): + op.context = "object" + op.group_path = modifier.path_from_id() + op.collection_prop = "int_states" + op.index_prop = "" + + if modifier.variable_type == "bool": + layout.prop(modifier, "bool_state") + elif modifier.variable_type == "int": + layout.label("Show when SDL variable is:") + sub = layout.column_flow() + for i, state in enumerate(modifier.int_states): + row = sub.row(align=True) + row.prop(state, "value", text="Value") + op = row.operator("ui.plasma_collection_remove", icon="ZOOMOUT", text="") + setup_collection_operator(op) + op.manual_index = i + + op = layout.operator("ui.plasma_collection_add", icon="ZOOMIN", text="Add State Value") + setup_collection_operator(op) + else: + raise RuntimeError() + def telescope(modifier, layout, context): layout.prop(modifier, "clickable_region") layout.prop(modifier, "seek_target_object", icon="EMPTY_DATA")