Browse Source

Ensure note popups don't clobber each other.

Fail fast if someone sets up multiple note popups with conflicting
settings. This will allow us to obey the principle of least surprise.
pull/406/head
Adam Johnson 3 months ago
parent
commit
20ccfa87f9
Signed by: Hoikas
GPG Key ID: 0B6515D6FF6F271E
  1. 6
      korman/exporter/convert.py
  2. 8
      korman/exporter/gui.py
  3. 2
      korman/properties/modifiers/anim.py
  4. 2
      korman/properties/modifiers/avatar.py
  5. 2
      korman/properties/modifiers/game_gui.py
  6. 8
      korman/properties/modifiers/gui.py
  7. 2
      korman/properties/modifiers/logic.py
  8. 4
      korman/properties/modifiers/render.py
  9. 2
      korman/properties/modifiers/sound.py

6
korman/exporter/convert.py

@ -229,7 +229,7 @@ class Exporter:
for mod in bl_obj.plasma_modifiers.modifiers: for mod in bl_obj.plasma_modifiers.modifiers:
fn = getattr(mod, "sanity_check", None) fn = getattr(mod, "sanity_check", None)
if fn is not None: if fn is not None:
fn() fn(self)
inc_progress() inc_progress()
self.report.msg("... Age is grinning and holding a spatula. Must be OK, then.") self.report.msg("... Age is grinning and holding a spatula. Must be OK, then.")
@ -502,7 +502,9 @@ class Exporter:
# Wow, recursively generated objects. Aren't you special? # Wow, recursively generated objects. Aren't you special?
with indent(): with indent():
for mod in temporary.plasma_modifiers.modifiers: for mod in temporary.plasma_modifiers.modifiers:
mod.sanity_check() fn = getattr(mod, "sanity_check", None)
if fn is not None:
fn(self)
do_pre_export(temporary) do_pre_export(temporary)
return temporary return temporary

8
korman/exporter/gui.py

@ -49,10 +49,12 @@ class GuiConverter:
if TYPE_CHECKING: if TYPE_CHECKING:
_parent: weakref.ref[Exporter] = ... _parent: weakref.ref[Exporter] = ...
_pages: Dict[str, Any] = ...
_mods_exported: Set[str] = ... _mods_exported: Set[str] = ...
def __init__(self, parent: Optional[Exporter] = None): def __init__(self, parent: Optional[Exporter] = None):
self._parent = weakref.ref(parent) if parent is not None else None self._parent = weakref.ref(parent) if parent is not None else None
self._pages = {}
self._mods_exported = set() self._mods_exported = set()
# Go ahead and prepare the GUI transparent material for future use. # Go ahead and prepare the GUI transparent material for future use.
@ -206,6 +208,12 @@ class GuiConverter:
w2c[2, i] *= -1.0 w2c[2, i] *= -1.0
return PostEffectModMatrices(c2w, w2c) return PostEffectModMatrices(c2w, w2c)
def check_pre_export(self, name: str, **kwargs):
previous = self._pages.setdefault(name, kwargs)
if previous != kwargs:
diff = set(previous.items()) - set(kwargs.items())
raise ExportError(f"GUI Page '{name}' has target modifiers with conflicting settings:\n{diff}")
def create_note_gui(self, gui_page: str, gui_camera: bpy.types.Object): def create_note_gui(self, gui_page: str, gui_camera: bpy.types.Object):
if not gui_page in self._mods_exported: if not gui_page in self._mods_exported:
guidialog_object = utils.create_empty_object(f"{gui_page}_NoteDialog") guidialog_object = utils.create_empty_object(f"{gui_page}_NoteDialog")

2
korman/properties/modifiers/anim.py

@ -42,7 +42,7 @@ class ActionModifier:
return None return None
raise ExportError("'{}': Object has an animation modifier but is not animated".format(bo.name)) raise ExportError("'{}': Object has an animation modifier but is not animated".format(bo.name))
def sanity_check(self) -> None: def sanity_check(self, exporter) -> None:
if not self.id_data.plasma_object.has_animation_data: if not self.id_data.plasma_object.has_animation_data:
raise ExportError("'{}': Has an animation modifier but no animation data.", self.id_data.name) raise ExportError("'{}': Has an animation modifier but no animation data.", self.id_data.name)

2
korman/properties/modifiers/avatar.py

@ -189,7 +189,7 @@ class PlasmaSittingBehavior(idprops.IDPropObjectMixin, PlasmaModifierProperties,
# This should be an empty, really... # This should be an empty, really...
return True return True
def sanity_check(self): def sanity_check(self, exporter):
# The user absolutely MUST specify a clickable or this won't export worth crap. # The user absolutely MUST specify a clickable or this won't export worth crap.
if self.clickable_object is None: if self.clickable_object is None:
raise ExportError("'{}': Sitting Behavior's clickable object is invalid".format(self.key_name)) raise ExportError("'{}': Sitting Behavior's clickable object is invalid".format(self.key_name))

2
korman/properties/modifiers/game_gui.py

@ -70,7 +70,7 @@ class _GameGuiMixin:
def requires_dyntext(self) -> bool: def requires_dyntext(self) -> bool:
return False return False
def sanity_check(self): def sanity_check(self, exporter):
age: PlasmaAge = bpy.context.scene.world.plasma_age age: PlasmaAge = bpy.context.scene.world.plasma_age
# Game GUI modifiers must be attached to objects in a GUI page, ONLY # Game GUI modifiers must be attached to objects in a GUI page, ONLY

8
korman/properties/modifiers/gui.py

@ -660,7 +660,7 @@ class PlasmaLinkingBookModifier(PlasmaModifierProperties, PlasmaModifierLogicWiz
share.link_input(share_anim_stage, "stage", "stage_refs") share.link_input(share_anim_stage, "stage", "stage_refs")
share.link_output(linkingnode, "hosts", "shareBookSeek") share.link_output(linkingnode, "hosts", "shareBookSeek")
def sanity_check(self): def sanity_check(self, exporter):
if self.clickable is None: if self.clickable is None:
raise ExportError("{}: Linking Book modifier requires a clickable!", self.id_data.name) raise ExportError("{}: Linking Book modifier requires a clickable!", self.id_data.name)
if self.seek_point is None: if self.seek_point is None:
@ -724,11 +724,15 @@ class PlasmaNotePopupModifier(PlasmaModifierProperties, PlasmaModifierLogicWiz):
if self.id_data.type == "MESH": if self.id_data.type == "MESH":
return self.id_data return self.id_data
def sanity_check(self): def sanity_check(self, exporter: Exporter):
page_type = helpers.get_page_type(self.id_data.plasma_object.page) page_type = helpers.get_page_type(self.id_data.plasma_object.page)
if page_type != "room": if page_type != "room":
raise ExportError(f"Note Popup modifiers should be in a 'room' page, not a '{page_type}' page!") raise ExportError(f"Note Popup modifiers should be in a 'room' page, not a '{page_type}' page!")
# It's OK if multiple note popups point to the same GUI page,
# they just need to have the same camera.
exporter.gui.check_pre_export(self.gui_page, pl_id="note_popup", camera=self.gui_camera)
def pre_export(self, exporter: Exporter, bo: bpy.types.Object): def pre_export(self, exporter: Exporter, bo: bpy.types.Object):
# The GUI converter will debounce duplicate GUI dialogs. # The GUI converter will debounce duplicate GUI dialogs.
yield from exporter.gui.create_note_gui(self.gui_page, self.gui_camera) yield from exporter.gui.create_note_gui(self.gui_page, self.gui_camera)

2
korman/properties/modifiers/logic.py

@ -154,7 +154,7 @@ class PlasmaTelescope(PlasmaModifierProperties, PlasmaModifierLogicWiz):
type=bpy.types.Object, type=bpy.types.Object,
poll=idprops.poll_camera_objects) poll=idprops.poll_camera_objects)
def sanity_check(self): def sanity_check(self, exporter):
if self.camera_object is None: if self.camera_object is None:
raise ExportError(f"'{self.id_data.name}': Telescopes must specify a camera!") raise ExportError(f"'{self.id_data.name}': Telescopes must specify a camera!")

4
korman/properties/modifiers/render.py

@ -120,7 +120,7 @@ class PlasmaBlendMod(PlasmaModifierProperties):
for i in (j.blend_onto for j in self.dependencies if j.blend_onto is not None and j.enabled): for i in (j.blend_onto for j in self.dependencies if j.blend_onto is not None and j.enabled):
yield i yield i
def sanity_check(self): def sanity_check(self, exporter):
if self.has_circular_dependency: if self.has_circular_dependency:
raise ExportError("'{}': Circular Render Dependency detected!".format(self.id_data.name)) raise ExportError("'{}': Circular Render Dependency detected!".format(self.id_data.name))
@ -770,7 +770,7 @@ class PlasmaLocalizedTextModifier(PlasmaModifierProperties, PlasmaModifierLogicW
def localization_set(self): def localization_set(self):
return "DynaTexts" return "DynaTexts"
def sanity_check(self): def sanity_check(self, exporter):
if self.texture is None: if self.texture is None:
raise ExportError("'{}': Localized Text modifier requires a texture", self.id_data.name) raise ExportError("'{}': Localized Text modifier requires a texture", self.id_data.name)

2
korman/properties/modifiers/sound.py

@ -533,7 +533,7 @@ class PlasmaSoundEmitter(PlasmaModifierProperties):
stereize_left = PointerProperty(type=bpy.types.Object, options={"HIDDEN", "SKIP_SAVE"}) stereize_left = PointerProperty(type=bpy.types.Object, options={"HIDDEN", "SKIP_SAVE"})
stereize_right = PointerProperty(type=bpy.types.Object, options={"HIDDEN", "SKIP_SAVE"}) stereize_right = PointerProperty(type=bpy.types.Object, options={"HIDDEN", "SKIP_SAVE"})
def sanity_check(self): def sanity_check(self, exporter):
modifiers = self.id_data.plasma_modifiers modifiers = self.id_data.plasma_modifiers
# Sound emitters can potentially export sounds to more than one emitter SceneObject. Currently, # Sound emitters can potentially export sounds to more than one emitter SceneObject. Currently,

Loading…
Cancel
Save