From 1b3afbe8d4f504b968787b61c24ef7c69fe35495 Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Wed, 7 Jun 2017 19:23:01 -0400 Subject: [PATCH] Convert sound modifiers to newfangled ID Props Unfortunately, sound indices do not match up directly with sound ID blocks, therefore, those remain string properties. --- korman/nodes/node_messages.py | 36 ++++++---- korman/operators/op_sound.py | 5 +- korman/properties/modifiers/sound.py | 104 +++++++++++++++------------ korman/ui/modifiers/sound.py | 14 ++-- 4 files changed, 86 insertions(+), 73 deletions(-) diff --git a/korman/nodes/node_messages.py b/korman/nodes/node_messages.py index c455742..1308c1d 100644 --- a/korman/nodes/node_messages.py +++ b/korman/nodes/node_messages.py @@ -21,6 +21,7 @@ from PyHSPlasma import * from .node_core import * from ..properties.modifiers.region import footstep_surfaces, footstep_surface_ids from ..exporter import ExportError +from .. import idprops class PlasmaMessageSocketBase(PlasmaNodeSocketBase): bl_color = (0.004, 0.282, 0.349, 1.0) @@ -484,14 +485,19 @@ class PlasmaSceneObjectMsgRcvrNode(PlasmaNodeBase, bpy.types.Node): return ref_so_key -class PlasmaSoundMsgNode(PlasmaMessageNode, bpy.types.Node): +class PlasmaSoundMsgNode(idprops.IDPropObjectMixin, PlasmaMessageNode, bpy.types.Node): bl_category = "MSG" bl_idname = "PlasmaSoundMsgNode" bl_label = "Sound" bl_width_default = 190 - object_name = StringProperty(name="Object", - description="Sound emitter object") + def _poll_sound_emitters(self, value): + return value.plasma_modifiers.soundemit.enabled + + emitter_object = PointerProperty(name="Object", + description="Sound emitter object", + type=bpy.types.Object, + poll=_poll_sound_emitters) sound_name = StringProperty(name="Sound", description="Sound datablock") @@ -540,20 +546,19 @@ class PlasmaSoundMsgNode(PlasmaMessageNode, bpy.types.Node): msg.setCmd(plSoundMsg.kAddCallbacks) def convert_message(self, exporter, so): - sound_bo = bpy.data.objects.get(self.object_name, None) - if sound_bo is None: - self.raise_error("'{}' is not a valid object".format(self.object_name)) - soundemit = sound_bo.plasma_modifiers.soundemit + if self.emitter_object is None: + self.raise_error("Sound emitter must be set") + soundemit = self.emitter_object.plasma_modifiers.soundemit if not soundemit.enabled: - self.raise_error("'{}' is not a valid Sound Emitter".format(self.object_name)) + self.raise_error("'{}' is not a valid Sound Emitter".format(self.emitter_object.name)) # Always test the specified audible for validity if self.sound_name and soundemit.sounds.get(self.sound_name, None) is None: - self.raise_error("Invalid Sound '{}' requested from Sound Emitter '{}'".format(self.sound_name, self.object_name)) + self.raise_error("Invalid Sound '{}' requested from Sound Emitter '{}'".format(self.sound_name, self.emitter_object.name)) # Remember that 3D stereo sounds are exported as two emitters... # But, if we only have one sound attached, who cares, we can just address the message to all - audible_key = exporter.mgr.find_create_key(plAudioInterface, bl=sound_bo) + audible_key = exporter.mgr.find_create_key(plAudioInterface, bl=self.emitter_object) indices = (-1,) if not self.sound_name or len(soundemit.sounds) == 1 else soundemit.get_sound_indices(self.sound_name) for idx in indices: msg = plSoundMsg() @@ -586,10 +591,9 @@ class PlasmaSoundMsgNode(PlasmaMessageNode, bpy.types.Node): yield msg def draw_buttons(self, context, layout): - layout.prop_search(self, "object_name", bpy.data, "objects") - bo = bpy.data.objects.get(self.object_name, None) - if bo is not None: - soundemit = bo.plasma_modifiers.soundemit + layout.prop(self, "emitter_object") + if self.emitter_object is not None: + soundemit = self.emitter_object.plasma_modifiers.soundemit if soundemit.enabled: layout.prop_search(self, "sound_name", soundemit, "sounds", icon="SOUND") else: @@ -608,6 +612,10 @@ class PlasmaSoundMsgNode(PlasmaMessageNode, bpy.types.Node): def has_callbacks(self): return True + @classmethod + def _idprop_mapping(cls): + return {"emitter_object": "object_name"} + class PlasmaTimerCallbackMsgNode(PlasmaMessageNode, bpy.types.Node): bl_category = "MSG" diff --git a/korman/operators/op_sound.py b/korman/operators/op_sound.py index 44b6adc..220222b 100644 --- a/korman/operators/op_sound.py +++ b/korman/operators/op_sound.py @@ -36,19 +36,16 @@ class PlasmaSoundOpenOperator(SoundOperator, bpy.types.Operator): def execute(self, context): # Check to see if the sound exists... Because the sneakily introduced bpy.data.sounds.load # check_existing doesn't tell us if it already exists... dammit... - # We don't want to take ownership forcefully if we don't have to. for i in bpy.data.sounds: if self.filepath == i.filepath: sound = i break else: sound = bpy.data.sounds.load(self.filepath) - sound.plasma_owned = True - sound.use_fake_user = True # Now do the stanky leg^H^H^H^H^H^H^H^H^H^H deed and put the sound on the mod dest = eval(self.data_path) - setattr(dest, self.sound_property, sound.name) + setattr(dest, self.sound_property, sound) return {"FINISHED"} def invoke(self, context, event): diff --git a/korman/properties/modifiers/sound.py b/korman/properties/modifiers/sound.py index cce45b3..ccbaa42 100644 --- a/korman/properties/modifiers/sound.py +++ b/korman/properties/modifiers/sound.py @@ -23,6 +23,7 @@ from PyHSPlasma import * from ... import korlib from .base import PlasmaModifierProperties from ...exporter import ExportError +from ... import idprops class PlasmaSfxFade(bpy.types.PropertyGroup): fade_type = EnumProperty(name="Type", @@ -38,9 +39,17 @@ class PlasmaSfxFade(bpy.types.PropertyGroup): options=set(), subtype="TIME", unit="TIME") -class PlasmaSound(bpy.types.PropertyGroup): - def _sound_picked(self, context): - if not self.sound_data: +class PlasmaSound(idprops.IDPropMixin, bpy.types.PropertyGroup): + def _get_name_proxy(self): + if self.sound is not None: + return self.sound.name + return "" + + def _set_name_proxy(self, value): + self.sound = bpy.data.sounds.get(value, None) + + # This is the actual pointer update callback + if not self.sound: self.name = "[Empty]" return @@ -54,24 +63,33 @@ class PlasmaSound(bpy.types.PropertyGroup): else: self.is_valid = True self.is_stereo = header.numChannels == 2 - self._update_name(context) + self._update_name() - def _update_name(self, context): + def _update_name(self, context=None): if self.is_stereo and self.channel != {"L", "R"}: - self.name = "{}:{}".format(self.sound_data, "L" if "L" in self.channel else "R") + self.name = "{}:{}".format(self._sound_name, "L" if "L" in self.channel else "R") else: - self.name = self.sound_data + self.name = self._sound_name enabled = BoolProperty(name="Enabled", default=True, options=set()) - sound_data = StringProperty(name="Sound", description="Sound Datablock", - options=set(), update=_sound_picked) + sound = PointerProperty(name="Sound", + description="Sound Datablock", + type=bpy.types.Sound) + + # This is needed because pointer properties do not seem to allow update CBs... Bug? + sound_data_proxy = StringProperty(name="Sound", + description="Name of sound datablock", + get=_get_name_proxy, + set=_set_name_proxy, + options=set()) is_stereo = BoolProperty(default=True, options={"HIDDEN"}) is_valid = BoolProperty(default=False, options={"HIDDEN"}) - soft_region = StringProperty(name="Soft Volume", + sfx_region = PointerProperty(name="Soft Volume", description="Soft region this sound can be heard in", - options=set()) + type=bpy.types.Object, + poll=idprops.poll_softvolume_objects) sfx_type = EnumProperty(name="Category", description="Describes the purpose of this sound", @@ -170,9 +188,9 @@ class PlasmaSound(bpy.types.PropertyGroup): def _convert_sound(self, exporter, so, pClass, wavHeader, dataSize, channel=None): if channel is None: - name = "Sfx-{}_{}".format(so.key.name, self.sound_data) + name = "Sfx-{}_{}".format(so.key.name, self._sound_name) else: - name = "Sfx-{}_{}:{}".format(so.key.name, self.sound_data, channel) + name = "Sfx-{}_{}:{}".format(so.key.name, self._sound_name, channel) exporter.report.msg("[{}] {}", pClass.__name__[2:], name, indent=1) sound = exporter.mgr.find_create_object(pClass, so=so, name=name) @@ -181,13 +199,10 @@ class PlasmaSound(bpy.types.PropertyGroup): sv_mod, sv_key = self.id_data.plasma_modifiers.softvolume, None if sv_mod.enabled: sv_key = sv_mod.get_key(exporter, so) - elif self.soft_region: - sv_bo = bpy.data.objects.get(self.soft_region, None) - if sv_bo is None: - raise ExportError("'{}': Invalid object '{}' for SoundEmit '{}' soft volume".format(self.id_data.name, self.soft_region, self.sound_data)) - sv_mod = sv_bo.plasma_modifiers.softvolume + elif self.sfx_region: + sv_mod = self.sfx_region.plasma_modifiers.softvolume if not sv_mod.enabled: - raise ExportError("'{}': SoundEmit '{}', '{}' is not a SoftVolume".format(self.id_data.name, self.sound_data, self.soft_region)) + raise ExportError("'{}': SoundEmit '{}', '{}' is not a SoftVolume".format(self.id_data.name, self._sound_name, self.sfx_region.name)) sv_key = sv_mod.get_key(exporter) if sv_key is not None: sv_key.object.listenState |= plSoftVolume.kListenCheck | plSoftVolume.kListenDirty | plSoftVolume.kListenRegistered @@ -305,21 +320,36 @@ class PlasmaSound(bpy.types.PropertyGroup): key = sound.key return key + @classmethod + def _idprop_mapping(cls): + return {"sound": "sound_data", + "sfx_region": "soft_region"} + + def _idprop_sources(self): + return {"sound_data": bpy.data.sounds, + "soft_region": bpy.data.objects} + @property def is_3d_stereo(self): return self.sfx_type == "kSoundFX" and self.channel == {"L", "R"} and self.is_stereo def _raise_error(self, msg): - raise ExportError("SoundEmitter '{}': Sound '{}' {}".format(self.id_data.name, self.sound_data, msg)) + if self.sound: + raise ExportError("SoundEmitter '{}': Sound '{}' {}".format(self.id_data.name, self.sound.name, msg)) + else: + raise ExportError("SoundEmitter '{}': {}".format(self.id_data.name, msg)) @property def _sound(self): - try: - sound = bpy.data.sounds.get(self.sound_data) - except: - self._raise_error("is not loaded") - else: - return sound + if not self.sound: + self._raise_error("has an invalid sound specified") + return self.sound + + @property + def _sound_name(self): + if self.sound: + return self.sound.name + return "" class PlasmaSoundEmitter(PlasmaModifierProperties): @@ -341,7 +371,7 @@ class PlasmaSoundEmitter(PlasmaModifierProperties): # Pass this off to each individual sound for conversion for i in self.sounds: - if i.sound_data and i.enabled: + if i.enabled: i.convert_sound(exporter, so, winaud) def get_sound_indices(self, name=None, sound=None): @@ -374,26 +404,6 @@ class PlasmaSoundEmitter(PlasmaModifierProperties): else: raise ValueError(name) - @classmethod - def register(cls): - bpy.types.Sound.plasma_owned = BoolProperty(default=False, options={"HIDDEN"}) - @property def requires_actor(self): return True - - -@persistent -def _toss_orphaned_sounds(scene): - used_sounds = set() - for i in bpy.data.objects: - soundemit = i.plasma_modifiers.soundemit - used_sounds.update((j.sound_data for j in soundemit.sounds)) - dead_sounds = [i for i in bpy.data.sounds if i.plasma_owned and i.name not in used_sounds] - for i in dead_sounds: - i.use_fake_user = False - i.user_clear() - bpy.data.sounds.remove(i) - -# collects orphaned Plasma owned sound datablocks -bpy.app.handlers.save_pre.append(_toss_orphaned_sounds) diff --git a/korman/ui/modifiers/sound.py b/korman/ui/modifiers/sound.py index becabc9..1997ed6 100644 --- a/korman/ui/modifiers/sound.py +++ b/korman/ui/modifiers/sound.py @@ -22,10 +22,8 @@ def _draw_fade_ui(modifier, layout, label): class SoundListUI(bpy.types.UIList): def draw_item(self, context, layout, data, item, icon, active_data, active_property, index=0, flt_flag=0): - if item.sound_data: - sound = bpy.data.sounds.get(item.sound_data) - icon = "SOUND" if sound is not None else "ERROR" - layout.prop(item, "name", emboss=False, icon=icon, text="") + if item.sound: + layout.prop(item, "name", emboss=False, icon="SOUND", text="") layout.prop(item, "enabled", text="") else: layout.label("[Empty]") @@ -51,13 +49,13 @@ def soundemit(modifier, layout, context): else: # Sound datablock picker row = layout.row(align=True) - row.prop_search(sound, "sound_data", bpy.data, "sounds", text="") + row.prop_search(sound, "sound_data_proxy", bpy.data, "sounds", text="") open_op = row.operator("sound.plasma_open", icon="FILESEL", text="") open_op.data_path = repr(sound) open_op.sound_property = "sound_data" # Pack/Unpack - data = bpy.data.sounds.get(sound.sound_data) + data = sound.sound if data is not None: if data.packed_file is None: row.operator("sound.plasma_pack", icon="UGLYPACKAGE", text="") @@ -65,7 +63,7 @@ def soundemit(modifier, layout, context): row.operator_menu_enum("sound.plasma_unpack", "method", icon="PACKAGE", text="") # If an invalid sound data block is spec'd, let them know about it. - if sound.sound_data and not sound.is_valid: + if data and not sound.is_valid: layout.label(text="Invalid sound specified", icon="ERROR") # Core Props @@ -102,4 +100,4 @@ def soundemit(modifier, layout, context): if not sv.enabled: col.separator() col.label("Soft Region:") - col.prop_search(sound, "soft_region", bpy.data, "objects", text="") + col.prop(sound, "sfx_region", text="")