From 74278b21baa242b29a3b58ccc7243c452fbe069d Mon Sep 17 00:00:00 2001
From: dhhruv <180320107529.ce.dhruv@gmail.com>
Date: Fri, 15 Oct 2021 21:09:00 +0530
Subject: [PATCH] Files Reformatted and Restructured for Better Visibility and
Understanding.
---
korman/__init__.py | 12 +-
korman/addon_prefs.py | 116 ++-
korman/exporter/animation.py | 679 +++++++++++++----
korman/exporter/camera.py | 58 +-
korman/exporter/convert.py | 88 ++-
korman/exporter/decal.py | 55 +-
korman/exporter/etlight.py | 156 +++-
korman/exporter/explosions.py | 28 +-
korman/exporter/image.py | 27 +-
korman/exporter/locman.py | 123 ++-
korman/exporter/logger.py | 73 +-
korman/exporter/manager.py | 55 +-
korman/exporter/material.py | 713 +++++++++++++-----
korman/exporter/mesh.py | 205 +++--
korman/exporter/outfile.py | 136 +++-
korman/exporter/physics.py | 116 ++-
korman/exporter/python.py | 44 +-
korman/exporter/rtlight.py | 52 +-
korman/exporter/utils.py | 21 +-
korman/helpers.py | 8 +-
korman/idprops.py | 42 +-
korman/korlib/__init__.py | 60 +-
korman/korlib/console.py | 51 +-
korman/korlib/python.py | 54 +-
korman/korlib/texture.py | 82 +-
korman/nodes/__init__.py | 15 +-
korman/nodes/node_avatar.py | 577 +++++++++-----
korman/nodes/node_conditions.py | 470 ++++++++----
korman/nodes/node_core.py | 152 ++--
korman/nodes/node_deprecated.py | 65 +-
korman/nodes/node_logic.py | 153 ++--
korman/nodes/node_messages.py | 880 ++++++++++++++--------
korman/nodes/node_python.py | 381 +++++++---
korman/nodes/node_responder.py | 267 ++++---
korman/nodes/node_softvolume.py | 165 +++--
korman/operators/__init__.py | 2 +
korman/operators/op_export.py | 389 +++++++---
korman/operators/op_image.py | 83 ++-
korman/operators/op_lightmap.py | 55 +-
korman/operators/op_mesh.py | 357 ++++++---
korman/operators/op_modifier.py | 74 +-
korman/operators/op_nodes.py | 134 ++--
korman/operators/op_sound.py | 20 +-
korman/operators/op_toolbox.py | 63 +-
korman/operators/op_ui.py | 89 ++-
korman/operators/op_world.py | 6 +-
korman/ordered_set.py | 26 +-
korman/plasma_attributes.py | 22 +-
korman/plasma_launcher.py | 68 +-
korman/properties/modifiers/__init__.py | 20 +-
korman/properties/modifiers/anim.py | 155 ++--
korman/properties/modifiers/avatar.py | 140 ++--
korman/properties/modifiers/base.py | 90 ++-
korman/properties/modifiers/gui.py | 630 ++++++++++------
korman/properties/modifiers/logic.py | 68 +-
korman/properties/modifiers/physics.py | 130 +++-
korman/properties/modifiers/region.py | 236 ++++--
korman/properties/modifiers/render.py | 948 +++++++++++++++---------
korman/properties/modifiers/sound.py | 467 ++++++++----
korman/properties/modifiers/water.py | 498 ++++++++-----
korman/properties/prop_anim.py | 62 +-
korman/properties/prop_camera.py | 529 ++++++++-----
korman/properties/prop_image.py | 23 +-
korman/properties/prop_lamp.py | 105 ++-
korman/properties/prop_object.py | 76 +-
korman/properties/prop_scene.py | 182 +++--
korman/properties/prop_sound.py | 11 +-
korman/properties/prop_text.py | 7 +-
korman/properties/prop_texture.py | 179 +++--
korman/properties/prop_world.py | 222 +++---
korman/render.py | 8 +
korman/ui/__init__.py | 1 +
korman/ui/modifiers/anim.py | 69 +-
korman/ui/modifiers/avatar.py | 2 +
korman/ui/modifiers/gui.py | 35 +-
korman/ui/modifiers/logic.py | 27 +-
korman/ui/modifiers/physics.py | 2 +
korman/ui/modifiers/region.py | 18 +-
korman/ui/modifiers/render.py | 187 ++++-
korman/ui/modifiers/sound.py | 36 +-
korman/ui/modifiers/water.py | 32 +-
korman/ui/ui_anim.py | 38 +-
korman/ui/ui_camera.py | 94 ++-
korman/ui/ui_image.py | 1 +
korman/ui/ui_lamp.py | 10 +-
korman/ui/ui_list.py | 49 +-
korman/ui/ui_menus.py | 19 +-
korman/ui/ui_modifiers.py | 43 +-
korman/ui/ui_object.py | 1 +
korman/ui/ui_render_layer.py | 29 +-
korman/ui/ui_scene.py | 75 +-
korman/ui/ui_text.py | 1 +
korman/ui/ui_texture.py | 30 +-
korman/ui/ui_toolbox.py | 118 ++-
korman/ui/ui_world.py | 105 ++-
95 files changed, 9250 insertions(+), 4125 deletions(-)
diff --git a/korman/__init__.py b/korman/__init__.py
index 824cfd8..b3d49f7 100644
--- a/korman/__init__.py
+++ b/korman/__init__.py
@@ -21,13 +21,13 @@ from . import nodes
from . import operators
bl_info = {
- "name": "Korman",
- "author": "Guild of Writers",
- "blender": (2, 79, 0),
- "location": "File > Import-Export",
+ "name": "Korman",
+ "author": "Guild of Writers",
+ "blender": (2, 79, 0),
+ "location": "File > Import-Export",
"description": "Exporter for Cyan Worlds' Plasma Engine",
- "warning": "beta",
- "category": "System",
+ "warning": "beta",
+ "category": "System",
}
diff --git a/korman/addon_prefs.py b/korman/addon_prefs.py
index 5bcc5a5..be136eb 100644
--- a/korman/addon_prefs.py
+++ b/korman/addon_prefs.py
@@ -17,31 +17,47 @@ import bpy
from bpy.props import *
from . import korlib
-game_versions = [("pvPrime", "Ages Beyond Myst (63.11)", "Targets the original Uru (Live) game"),
- ("pvPots", "Path of the Shell (63.12)", "Targets the most recent offline expansion pack"),
- ("pvMoul", "Myst Online: Uru Live (70)", "Targets the most recent online game")]
+game_versions = [
+ ("pvPrime", "Ages Beyond Myst (63.11)", "Targets the original Uru (Live) game"),
+ (
+ "pvPots",
+ "Path of the Shell (63.12)",
+ "Targets the most recent offline expansion pack",
+ ),
+ ("pvMoul", "Myst Online: Uru Live (70)", "Targets the most recent online game"),
+]
+
class PlasmaGame(bpy.types.PropertyGroup):
- name = StringProperty(name="Name",
- description="Name of the Plasma Game",
- options=set())
- path = StringProperty(name="Path",
- description="Path to this Plasma Game",
- options=set())
- version = EnumProperty(name="Version",
- description="Plasma version of this game",
- items=game_versions,
- options=set())
-
- player = StringProperty(name="Player",
- description="Name of the player to use when launching the game",
- options=set())
- ki = IntProperty(name="KI",
- description="KI Number of the player to use when launching the game",
- options=set(), min=0)
- serverini = StringProperty(name="Server INI",
- description="Name of the server configuation to use when launching the game",
- options=set())
+ name = StringProperty(
+ name="Name", description="Name of the Plasma Game", options=set()
+ )
+ path = StringProperty(
+ name="Path", description="Path to this Plasma Game", options=set()
+ )
+ version = EnumProperty(
+ name="Version",
+ description="Plasma version of this game",
+ items=game_versions,
+ options=set(),
+ )
+
+ player = StringProperty(
+ name="Player",
+ description="Name of the player to use when launching the game",
+ options=set(),
+ )
+ ki = IntProperty(
+ name="KI",
+ description="KI Number of the player to use when launching the game",
+ options=set(),
+ min=0,
+ )
+ serverini = StringProperty(
+ name="Server INI",
+ description="Name of the server configuation to use when launching the game",
+ options=set(),
+ )
@property
def can_launch(self):
@@ -60,28 +76,36 @@ class KormanAddonPreferences(bpy.types.AddonPreferences):
def _check_py22_exe(self, context):
if self._ensure_abspath((2, 2)):
self._check_python((2, 2))
+
def _check_py23_exe(self, context):
if self._ensure_abspath((2, 3)):
self._check_python((2, 3))
+
def _check_py27_exe(self, context):
if self._ensure_abspath((2, 7)):
self._check_python((2, 7))
- python22_executable = StringProperty(name="Python 2.2",
- description="Path to the Python 2.2 executable",
- options=set(),
- subtype="FILE_PATH",
- update=_check_py22_exe)
- python23_executable = StringProperty(name="Python 2.3",
- description="Path to the Python 2.3 executable",
- options=set(),
- subtype="FILE_PATH",
- update=_check_py23_exe)
- python27_executable = StringProperty(name="Python 2.7",
- description="Path to the Python 2.7 executable",
- options=set(),
- subtype="FILE_PATH",
- update=_check_py27_exe)
+ python22_executable = StringProperty(
+ name="Python 2.2",
+ description="Path to the Python 2.2 executable",
+ options=set(),
+ subtype="FILE_PATH",
+ update=_check_py22_exe,
+ )
+ python23_executable = StringProperty(
+ name="Python 2.3",
+ description="Path to the Python 2.3 executable",
+ options=set(),
+ subtype="FILE_PATH",
+ update=_check_py23_exe,
+ )
+ python27_executable = StringProperty(
+ name="Python 2.7",
+ description="Path to the Python 2.7 executable",
+ options=set(),
+ subtype="FILE_PATH",
+ update=_check_py27_exe,
+ )
def _validate_py_exes(self):
if not self.is_property_set("python22_valid"):
@@ -96,7 +120,9 @@ class KormanAddonPreferences(bpy.types.AddonPreferences):
python22_valid = BoolProperty(options={"HIDDEN", "SKIP_SAVE"})
python23_valid = BoolProperty(options={"HIDDEN", "SKIP_SAVE"})
python27_valid = BoolProperty(options={"HIDDEN", "SKIP_SAVE"})
- python_validated = BoolProperty(get=_validate_py_exes, options={"HIDDEN", "SKIP_SAVE"})
+ python_validated = BoolProperty(
+ get=_validate_py_exes, options={"HIDDEN", "SKIP_SAVE"}
+ )
def _check_python(self, py_version):
py_exe = getattr(self, "python{}{}_executable".format(*py_version))
@@ -121,8 +147,15 @@ class KormanAddonPreferences(bpy.types.AddonPreferences):
main_col.label("Plasma Games:")
row = main_col.row()
- row.template_list("PlasmaGameListRW", "games", self, "games", self,
- "active_game_index", rows=3)
+ row.template_list(
+ "PlasmaGameListRW",
+ "games",
+ self,
+ "games",
+ self,
+ "active_game_index",
+ rows=3,
+ )
col = row.column(align=True)
col.operator("world.plasma_game_add", icon="ZOOMIN", text="")
col.operator("world.plasma_game_remove", icon="ZOOMOUT", text="")
@@ -171,4 +204,5 @@ class KormanAddonPreferences(bpy.types.AddonPreferences):
# Register the old-timey per-world Plasma Games for use in the conversion
# operator. What fun. I guess....
from .properties.prop_world import PlasmaGames
+
PlasmaGames.games = CollectionProperty(type=PlasmaGame)
diff --git a/korman/exporter/animation.py b/korman/exporter/animation.py
index 60b3051..a06b907 100644
--- a/korman/exporter/animation.py
+++ b/korman/exporter/animation.py
@@ -27,6 +27,7 @@ from PyHSPlasma import *
from . import utils
+
class AnimationConverter:
def __init__(self, exporter):
self._exporter = weakref.ref(exporter)
@@ -35,8 +36,15 @@ class AnimationConverter:
def convert_frame_time(self, frame_num: int) -> float:
return frame_num / self._bl_fps
- def convert_object_animations(self, bo: bpy.types.Object, so: plSceneObject, anim_name: str, *,
- start: Optional[int] = None, end: Optional[int] = None) -> Iterable[plAGApplicator]:
+ def convert_object_animations(
+ self,
+ bo: bpy.types.Object,
+ so: plSceneObject,
+ anim_name: str,
+ *,
+ start: Optional[int] = None,
+ end: Optional[int] = None
+ ) -> Iterable[plAGApplicator]:
if not bo.plasma_object.has_animation_data:
return []
@@ -56,30 +64,74 @@ class AnimationConverter:
# things that aren't the typical position, rotation, scale animations.
applicators = []
if isinstance(bo.data, bpy.types.Camera):
- applicators.append(self._convert_camera_animation(bo, so, obj_fcurves, data_fcurves, anim_name, start, end))
+ applicators.append(
+ self._convert_camera_animation(
+ bo, so, obj_fcurves, data_fcurves, anim_name, start, end
+ )
+ )
else:
- applicators.append(self._convert_transform_animation(bo, obj_fcurves, bo.matrix_local, bo.matrix_parent_inverse, start=start, end=end))
+ applicators.append(
+ self._convert_transform_animation(
+ bo,
+ obj_fcurves,
+ bo.matrix_local,
+ bo.matrix_parent_inverse,
+ start=start,
+ end=end,
+ )
+ )
if bo.plasma_modifiers.soundemit.enabled:
- applicators.extend(self._convert_sound_volume_animation(bo.name, obj_fcurves, bo.plasma_modifiers.soundemit, start, end))
+ applicators.extend(
+ self._convert_sound_volume_animation(
+ bo.name, obj_fcurves, bo.plasma_modifiers.soundemit, start, end
+ )
+ )
if isinstance(bo.data, bpy.types.Lamp):
lamp = bo.data
- applicators.extend(self._convert_lamp_color_animation(bo.name, data_fcurves, lamp, start, end))
+ applicators.extend(
+ self._convert_lamp_color_animation(
+ bo.name, data_fcurves, lamp, start, end
+ )
+ )
if isinstance(lamp, bpy.types.SpotLamp):
- applicators.extend(self._convert_spot_lamp_animation(bo.name, data_fcurves, lamp, start, end))
+ applicators.extend(
+ self._convert_spot_lamp_animation(
+ bo.name, data_fcurves, lamp, start, end
+ )
+ )
if isinstance(lamp, bpy.types.PointLamp):
- applicators.extend(self._convert_omni_lamp_animation(bo.name, data_fcurves, lamp, start, end))
+ applicators.extend(
+ self._convert_omni_lamp_animation(
+ bo.name, data_fcurves, lamp, start, end
+ )
+ )
return [i for i in applicators if i is not None]
- def _convert_camera_animation(self, bo, so, obj_fcurves, data_fcurves, anim_name: str,
- start: Optional[int], end: Optional[int]):
+ def _convert_camera_animation(
+ self,
+ bo,
+ so,
+ obj_fcurves,
+ data_fcurves,
+ anim_name: str,
+ start: Optional[int],
+ end: Optional[int],
+ ):
has_fov_anim = False
if data_fcurves:
# The hard part about this crap is that FOV animations are not stored in ATC Animations
# instead, FOV animation keyframes are held inside of the camera modifier. Cyan's solution
# in PlasmaMAX appears to be for any xform keyframe, add two messages to the camera modifier
# representing the FOV at that point. Makes more sense to me to use each FOV keyframe instead
- fov_fcurve = next((i for i in data_fcurves if i.data_path == "plasma_camera.settings.fov"), None)
+ fov_fcurve = next(
+ (
+ i
+ for i in data_fcurves
+ if i.data_path == "plasma_camera.settings.fov"
+ ),
+ None,
+ )
if fov_fcurve:
# NOTE: this is another critically important key ordering in the SceneObject modifier
# list. CameraModifier calls into AGMasterMod code that assumes the AGModifier
@@ -92,7 +144,6 @@ class AnimationConverter:
degrees = math.degrees
fov_fcurve.update()
-
# Well, now that we have multiple animations, we are using our fancier FCurve processing.
# Unfortunately, the code still looks like sin. What can you do?
keyframes, _ = self._process_fcurve(fov_fcurve, start=start, end=end)
@@ -104,7 +155,9 @@ class AnimationConverter:
# So remember, these are messages. When we hit a keyframe, we're dispatching a message
# representing the NEXT desired FOV.
this_keyframe = keyframes[i]
- next_keyframe = keyframes[0] if i+1 == num_keyframes else keyframes[i+1]
+ next_keyframe = (
+ keyframes[0] if i + 1 == num_keyframes else keyframes[i + 1]
+ )
# This message is held on the camera modifier and sent to the animation... It calls
# back when the animation reaches the keyframe time, causing the FOV message to be sent.
@@ -128,12 +181,17 @@ class AnimationConverter:
# export-time and while playing the game, the camera modifier just steals its
# parameters and passes them to the brain. Can't make this stuff up.
# Be sure to only export each instruction once.
- if not any((msg.config.accel == next_keyframe.frame_time for msg in camera.fovInstructions)):
+ if not any(
+ (
+ msg.config.accel == next_keyframe.frame_time
+ for msg in camera.fovInstructions
+ )
+ ):
cam_msg = plCameraMsg()
cam_msg.addReceiver(cam_key)
cam_msg.setCmd(plCameraMsg.kAddFOVKeyFrame, True)
cam_config = cam_msg.config
- cam_config.accel = next_keyframe.frame_time # Yassss...
+ cam_config.accel = next_keyframe.frame_time # Yassss...
cam_config.fovW = degrees(next_keyframe.values[0])
cam_config.fovH = degrees(next_keyframe.values[0] * aspect)
camera.addFOVInstruction(cam_msg)
@@ -143,8 +201,15 @@ class AnimationConverter:
# If we exported any FOV animation at all, then we need to ensure there is an applicator
# returned from here... At bare minimum, we'll need the applicator with an empty
# CompoundController. This should be sufficient to keep CWE from crashing...
- applicator = self._convert_transform_animation(bo, obj_fcurves, bo.matrix_local, bo.matrix_parent_inverse,
- allow_empty=has_fov_anim, start=start, end=end)
+ applicator = self._convert_transform_animation(
+ bo,
+ obj_fcurves,
+ bo.matrix_local,
+ bo.matrix_parent_inverse,
+ allow_empty=has_fov_anim,
+ start=start,
+ end=end,
+ )
camera = self._mgr.find_create_object(plCameraModifier, so=so)
camera.animated = applicator is not None
return applicator
@@ -153,15 +218,25 @@ class AnimationConverter:
if not fcurves:
return None
- energy_curve = next((i for i in fcurves if i.data_path == "energy" and i.keyframe_points), None)
- color_curves = [i for i in fcurves if i.data_path == "color" and i.keyframe_points]
+ energy_curve = next(
+ (i for i in fcurves if i.data_path == "energy" and i.keyframe_points), None
+ )
+ color_curves = [
+ i for i in fcurves if i.data_path == "color" and i.keyframe_points
+ ]
if energy_curve is None and color_curves is None:
return None
elif lamp.use_only_shadow:
- self._exporter().report.warn("Cannot animate Lamp color because this lamp only casts shadows", indent=3)
+ self._exporter().report.warn(
+ "Cannot animate Lamp color because this lamp only casts shadows",
+ indent=3,
+ )
return None
elif not lamp.use_specular and not lamp.use_diffuse:
- self._exporter().report.warn("Cannot animate Lamp color because neither Diffuse nor Specular are enabled", indent=3)
+ self._exporter().report.warn(
+ "Cannot animate Lamp color because neither Diffuse nor Specular are enabled",
+ indent=3,
+ )
return None
# OK Specular is easy. We just toss out the color as a point3.
@@ -170,12 +245,20 @@ class AnimationConverter:
return map(lambda x: x * -1.0, color)
else:
return color
- color_keyframes, color_bez = self._process_keyframes(color_curves, 3, lamp.color,
- convert=convert_specular_animation,
- start=start, end=end)
+
+ color_keyframes, color_bez = self._process_keyframes(
+ color_curves,
+ 3,
+ lamp.color,
+ convert=convert_specular_animation,
+ start=start,
+ end=end,
+ )
if color_keyframes and lamp.use_specular:
channel = plPointControllerChannel()
- channel.controller = self._make_point3_controller(color_keyframes, color_bez)
+ channel.controller = self._make_point3_controller(
+ color_keyframes, color_bez
+ )
applicator = plLightSpecularApplicator()
applicator.channelName = name
applicator.channel = channel
@@ -188,12 +271,21 @@ class AnimationConverter:
else:
proc = lambda x: x * energy[0]
return map(proc, color)
+
diffuse_channels = dict(color=3, energy=1)
diffuse_defaults = dict(color=lamp.color, energy=lamp.energy)
- diffuse_fcurves = color_curves + [energy_curve,]
- diffuse_keyframes = self._process_fcurves(diffuse_fcurves, diffuse_channels, 3,
- convert_diffuse_animation, diffuse_defaults,
- start=start, end=end)
+ diffuse_fcurves = color_curves + [
+ energy_curve,
+ ]
+ diffuse_keyframes = self._process_fcurves(
+ diffuse_fcurves,
+ diffuse_channels,
+ 3,
+ convert_diffuse_animation,
+ diffuse_defaults,
+ start=start,
+ end=end,
+ )
if not diffuse_keyframes:
return None
@@ -227,9 +319,12 @@ class AnimationConverter:
# All types allow animating cutoff
if distance_fcurve is not None:
channel = plScalarControllerChannel()
- channel.controller = self.make_scalar_leaf_controller(distance_fcurve,
- lambda x: x if lamp.use_sphere else x * 2,
- start=start, end=end)
+ channel.controller = self.make_scalar_leaf_controller(
+ distance_fcurve,
+ lambda x: x if lamp.use_sphere else x * 2,
+ start=start,
+ end=end,
+ )
applicator = plOmniCutoffApplicator()
applicator.channelName = name
applicator.channel = channel
@@ -238,10 +333,19 @@ class AnimationConverter:
falloff = lamp.falloff_type
if falloff == "CONSTANT":
if energy_fcurve is not None:
- report.warn("Constant attenuation cannot be animated in Plasma", ident=3)
+ report.warn(
+ "Constant attenuation cannot be animated in Plasma", ident=3
+ )
elif falloff == "INVERSE_LINEAR":
- keyframes = self._process_fcurves(omni_fcurves, omni_channels, 1, convert_omni_atten,
- omni_defaults, start=start, end=end)
+ keyframes = self._process_fcurves(
+ omni_fcurves,
+ omni_channels,
+ 1,
+ convert_omni_atten,
+ omni_defaults,
+ start=start,
+ end=end,
+ )
if keyframes:
channel = plScalarControllerChannel()
channel.controller = self._make_scalar_leaf_controller(keyframes, False)
@@ -251,30 +355,51 @@ class AnimationConverter:
yield applicator
elif falloff == "INVERSE_SQUARE":
if self._mgr.getVer() >= pvMoul:
- report.port("Lamp {} Falloff animations are only supported in Myst Online: Uru Live", falloff, indent=3)
- keyframes = self._process_fcurves(omni_fcurves, omni_channels, 1, convert_omni_atten,
- omni_defaults, start=start, end=end)
+ report.port(
+ "Lamp {} Falloff animations are only supported in Myst Online: Uru Live",
+ falloff,
+ indent=3,
+ )
+ keyframes = self._process_fcurves(
+ omni_fcurves,
+ omni_channels,
+ 1,
+ convert_omni_atten,
+ omni_defaults,
+ start=start,
+ end=end,
+ )
if keyframes:
channel = plScalarControllerChannel()
- channel.controller = self._make_scalar_leaf_controller(keyframes, False)
+ channel.controller = self._make_scalar_leaf_controller(
+ keyframes, False
+ )
applicator = plOmniSqApplicator()
applicator.channelName = name
applicator.channel = channel
yield applicator
else:
- report.warn("Lamp {} Falloff animations are not supported for this version of Plasma", falloff, indent=3)
+ report.warn(
+ "Lamp {} Falloff animations are not supported for this version of Plasma",
+ falloff,
+ indent=3,
+ )
else:
- report.warn("Lamp Falloff '{}' animations are not supported", falloff, ident=3)
+ report.warn(
+ "Lamp Falloff '{}' animations are not supported", falloff, ident=3
+ )
def _convert_sound_volume_animation(self, name, fcurves, soundemit, start, end):
if not fcurves:
return None
- convert_volume = lambda x: math.log10(max(.01, x / 100.0)) * 20.0
+ convert_volume = lambda x: math.log10(max(0.01, x / 100.0)) * 20.0
for sound in soundemit.sounds:
path = "{}.volume".format(sound.path_from_id())
- fcurve = next((i for i in fcurves if i.data_path == path and i.keyframe_points), None)
+ fcurve = next(
+ (i for i in fcurves if i.data_path == path and i.keyframe_points), None
+ )
if fcurve is None:
continue
@@ -287,15 +412,20 @@ class AnimationConverter:
# so yes, we must convert the same animation data again and again.
# To make matters worse, the way that these keyframes are stored can cause
# the animation to evaluate to a no-op. Be ready for that.
- controller = self.make_scalar_leaf_controller(fcurve, convert=convert_volume, start=start, end=end)
+ controller = self.make_scalar_leaf_controller(
+ fcurve, convert=convert_volume, start=start, end=end
+ )
if controller is not None:
channel = plScalarControllerChannel()
channel.controller = controller
applicator.channel = channel
yield applicator
else:
- self._exporter().report.warn("[{}]: Volume animation evaluated to zero keyframes!",
- sound.sound.name, indent=2)
+ self._exporter().report.warn(
+ "[{}]: Volume animation evaluated to zero keyframes!",
+ sound.sound.name,
+ indent=2,
+ )
break
def _convert_spot_lamp_animation(self, name, fcurves, lamp, start, end):
@@ -310,8 +440,9 @@ class AnimationConverter:
# Spot Outer is just the size keyframes...
if size_fcurve is not None:
channel = plScalarControllerChannel()
- channel.controller = self.make_scalar_leaf_controller(size_fcurve, lambda x: math.degrees(x),
- start=start, end=end)
+ channel.controller = self.make_scalar_leaf_controller(
+ size_fcurve, lambda x: math.degrees(x), start=start, end=end
+ )
applicator = plSpotOuterApplicator()
applicator.channelName = name
applicator.channel = channel
@@ -327,8 +458,15 @@ class AnimationConverter:
inner_fcurves = [blend_fcurve, size_fcurve]
inner_channels = dict(spot_blend=1, spot_size=1)
inner_defaults = dict(spot_blend=lamp.spot_blend, spot_size=lamp.spot_size)
- keyframes = self._process_fcurves(inner_fcurves, inner_channels, 1, convert_spot_inner,
- inner_defaults, start=start, end=end)
+ keyframes = self._process_fcurves(
+ inner_fcurves,
+ inner_channels,
+ 1,
+ convert_spot_inner,
+ inner_defaults,
+ start=start,
+ end=end,
+ )
if keyframes:
channel = plScalarControllerChannel()
@@ -338,17 +476,38 @@ class AnimationConverter:
applicator.channel = channel
yield applicator
- def _convert_transform_animation(self, bo, fcurves, default_xform, adjust_xform, *, allow_empty: Optional[bool] = False,
- start: Optional[int] = None, end: Optional[int] = None) -> Optional[plMatrixChannelApplicator]:
+ def _convert_transform_animation(
+ self,
+ bo,
+ fcurves,
+ default_xform,
+ adjust_xform,
+ *,
+ allow_empty: Optional[bool] = False,
+ start: Optional[int] = None,
+ end: Optional[int] = None
+ ) -> Optional[plMatrixChannelApplicator]:
if adjust_xform != mathutils.Matrix.Identity(4):
- self._exporter().report.warn(("{}: Transform animation is not local and may export incorrectly. " +
- "Please use Alt-P -> Clear Parent Inverse before animating objects to avoid issues.").format(bo.name), indent=1)
+ self._exporter().report.warn(
+ (
+ "{}: Transform animation is not local and may export incorrectly. "
+ + "Please use Alt-P -> Clear Parent Inverse before animating objects to avoid issues."
+ ).format(bo.name),
+ indent=1,
+ )
else:
# Adjustment matrix is identity, just pass None instead...
adjust_xform = None
- tm = self.convert_transform_controller(fcurves, bo.rotation_mode, default_xform, adjust_xform, allow_empty=allow_empty,
- start=start, end=end)
+ tm = self.convert_transform_controller(
+ fcurves,
+ bo.rotation_mode,
+ default_xform,
+ adjust_xform,
+ allow_empty=allow_empty,
+ start=start,
+ end=end,
+ )
if tm is None and not allow_empty:
return None
@@ -362,10 +521,17 @@ class AnimationConverter:
return applicator
- def convert_transform_controller(self, fcurves, rotation_mode: str, default_xform, adjust_xform, *,
- allow_empty: Optional[bool] = False,
- start: Optional[int] = None,
- end: Optional[int] = None) -> Union[None, plCompoundController]:
+ def convert_transform_controller(
+ self,
+ fcurves,
+ rotation_mode: str,
+ default_xform,
+ adjust_xform,
+ *,
+ allow_empty: Optional[bool] = False,
+ start: Optional[int] = None,
+ end: Optional[int] = None
+ ) -> Union[None, plCompoundController]:
if not fcurves and not allow_empty:
return None
@@ -384,14 +550,16 @@ class AnimationConverter:
def convert_rot_keyframe(rot):
# Rotation: may cause issues if scale is present.
- if isinstance(rot, mathutils.Quaternion): # quaternion from an axis-angle
+ if isinstance(
+ rot, mathutils.Quaternion
+ ): # quaternion from an axis-angle
return adjust_rotation * rot
elif isinstance(rot, mathutils.Euler):
return (adjust_rotation * rot.to_quaternion()).to_euler(rot.order)
- else: # tuple
- if len(rot) == 4: # quat in a tuple
+ else: # tuple
+ if len(rot) == 4: # quat in a tuple
return (adjust_rotation * mathutils.Quaternion(rot))[:]
- else: # XYZ euler in a tuple
+ else: # XYZ euler in a tuple
rot = mathutils.Euler(rot, "XYZ").to_quaternion()
return (adjust_rotation * rot).to_euler("XYZ")[:]
@@ -408,12 +576,30 @@ class AnimationConverter:
convert_rot = None
convert_scale = None
- pos = self.make_pos_controller(fcurves, "location", default_xform.to_translation(),
- convert=convert_pos, start=start, end=end)
- rot = self.make_rot_controller(fcurves, rotation_mode, default_xform,
- convert=convert_rot, start=start, end=end)
- scale = self.make_scale_controller(fcurves, "scale", default_xform.to_scale(),
- convert=convert_scale, start=start, end=end)
+ pos = self.make_pos_controller(
+ fcurves,
+ "location",
+ default_xform.to_translation(),
+ convert=convert_pos,
+ start=start,
+ end=end,
+ )
+ rot = self.make_rot_controller(
+ fcurves,
+ rotation_mode,
+ default_xform,
+ convert=convert_rot,
+ start=start,
+ end=end,
+ )
+ scale = self.make_scale_controller(
+ fcurves,
+ "scale",
+ default_xform.to_scale(),
+ convert=convert_scale,
+ start=start,
+ end=end,
+ )
if pos is None and rot is None and scale is None:
if not allow_empty:
return None
@@ -429,7 +615,9 @@ class AnimationConverter:
master = self._mgr.find_create_key(plAGMasterMod, so=so, bl=bo)
return mod, master
- def get_anigraph_objects(self, bo=None, so=None) -> Tuple[plAGModifier, plAGMasterMod]:
+ def get_anigraph_objects(
+ self, bo=None, so=None
+ ) -> Tuple[plAGModifier, plAGMasterMod]:
mod = self._mgr.find_create_object(plAGModifier, so=so, bl=bo)
master = self._mgr.find_create_object(plAGMasterMod, so=so, bl=bo)
return mod, master
@@ -440,13 +628,20 @@ class AnimationConverter:
# (but obviously this is not wrong...)
group_mod = bo.plasma_modifiers.animation_group
if group_mod.enabled:
- return self._mgr.find_create_key(plMsgForwarder, bl=bo, so=so, name=group_mod.key_name)
+ return self._mgr.find_create_key(
+ plMsgForwarder, bl=bo, so=so, name=group_mod.key_name
+ )
else:
return self.get_anigraph_keys(bo, so)[1]
- def get_frame_time_range(self, *anims: Iterable[Union[plAGApplicator, plController]],
- so: Optional[plSceneObject] = None, name: Optional[str] = None) -> Tuple[int, int]:
+ def get_frame_time_range(
+ self,
+ *anims: Iterable[Union[plAGApplicator, plController]],
+ so: Optional[plSceneObject] = None,
+ name: Optional[str] = None
+ ) -> Tuple[int, int]:
"""Determines the range of frame numbers in an exported animation."""
+
def iter_frame_times():
nonlocal name
for anim in anims:
@@ -456,7 +651,9 @@ class AnimationConverter:
# Maybe a camera FOV thing, or something.
continue
- def iter_leaves(ctrl: Optional[plController]) -> Iterator[plLeafController]:
+ def iter_leaves(
+ ctrl: Optional[plController],
+ ) -> Iterator[plLeafController]:
if ctrl is None:
return
elif isinstance(ctrl, plCompoundController):
@@ -468,7 +665,9 @@ class AnimationConverter:
else:
raise ValueError(ctrl)
- yield from (key.frameTime for leaf in iter_leaves(anim) for key in leaf.keys[0])
+ yield from (
+ key.frameTime for leaf in iter_leaves(anim) for key in leaf.keys[0]
+ )
# Special case: camera animations are over on the plCameraModifier. Grr.
if so is not None:
@@ -476,47 +675,81 @@ class AnimationConverter:
if camera is not None:
if not name:
name = "(Entire Animation)"
- yield from (msg.time for msg, _ in camera.messageQueue if isinstance(msg, plAnimCmdMsg) and msg.animName == name)
+ yield from (
+ msg.time
+ for msg, _ in camera.messageQueue
+ if isinstance(msg, plAnimCmdMsg) and msg.animName == name
+ )
try:
return min(iter_frame_times()), max(iter_frame_times())
except ValueError:
return 0.0, 0.0
- def make_matrix44_controller(self, fcurves, pos_path: str, scale_path: str, pos_default, scale_default,
- *, start: Optional[int] = None, end: Optional[int] = None) -> Optional[plLeafController]:
+ def make_matrix44_controller(
+ self,
+ fcurves,
+ pos_path: str,
+ scale_path: str,
+ pos_default,
+ scale_default,
+ *,
+ start: Optional[int] = None,
+ end: Optional[int] = None
+ ) -> Optional[plLeafController]:
def convert_matrix_keyframe(**kwargs) -> hsMatrix44:
pos = kwargs[pos_path]
scale = kwargs[scale_path]
- translation = hsVector3(pos[0] - (scale[0] - 1.0) / 2.0,
- -pos[1] - (scale[1] - 1.0) / 2.0,
- pos[2] - (scale[2] - 1.0) / 2.0)
+ translation = hsVector3(
+ pos[0] - (scale[0] - 1.0) / 2.0,
+ -pos[1] - (scale[1] - 1.0) / 2.0,
+ pos[2] - (scale[2] - 1.0) / 2.0,
+ )
matrix = hsMatrix44()
matrix.setTranslate(translation)
matrix.setScale(hsVector3(*scale))
return matrix
- fcurves = [i for i in fcurves if i.data_path == pos_path or i.data_path == scale_path]
+ fcurves = [
+ i for i in fcurves if i.data_path == pos_path or i.data_path == scale_path
+ ]
if not fcurves:
return None
- channels = { pos_path: 3, scale_path: 3 }
- default_values = { pos_path: pos_default, scale_path: scale_default }
- keyframes = self._process_fcurves(fcurves, channels, 1, convert_matrix_keyframe,
- default_values, start=start, end=end)
+ channels = {pos_path: 3, scale_path: 3}
+ default_values = {pos_path: pos_default, scale_path: scale_default}
+ keyframes = self._process_fcurves(
+ fcurves,
+ channels,
+ 1,
+ convert_matrix_keyframe,
+ default_values,
+ start=start,
+ end=end,
+ )
if not keyframes:
return None
# Now we make the controller
return self._make_matrix44_controller(keyframes)
- def make_pos_controller(self, fcurves, data_path: str, default_xform,
- convert: Optional[Callable] = None, *, start: Optional[int] = None,
- end: Optional[int] = None) -> Optional[plLeafController]:
- pos_curves = [i for i in fcurves if i.data_path == data_path and i.keyframe_points]
- keyframes, bez_chans = self._process_keyframes(pos_curves, 3, default_xform, convert,
- start=start, end=end)
+ def make_pos_controller(
+ self,
+ fcurves,
+ data_path: str,
+ default_xform,
+ convert: Optional[Callable] = None,
+ *,
+ start: Optional[int] = None,
+ end: Optional[int] = None
+ ) -> Optional[plLeafController]:
+ pos_curves = [
+ i for i in fcurves if i.data_path == data_path and i.keyframe_points
+ ]
+ keyframes, bez_chans = self._process_keyframes(
+ pos_curves, 3, default_xform, convert, start=start, end=end
+ )
if not keyframes:
return None
@@ -525,34 +758,58 @@ class AnimationConverter:
ctrl = self._make_point3_controller(keyframes, bez_chans)
return ctrl
- def make_rot_controller(self, fcurves, rotation_mode: str, default_xform,
- convert: Optional[Callable] = None, *, start: Optional[int] = None,
- end: Optional[int] = None) -> Union[None, plCompoundController, plLeafController]:
+ def make_rot_controller(
+ self,
+ fcurves,
+ rotation_mode: str,
+ default_xform,
+ convert: Optional[Callable] = None,
+ *,
+ start: Optional[int] = None,
+ end: Optional[int] = None
+ ) -> Union[None, plCompoundController, plLeafController]:
if rotation_mode in {"AXIS_ANGLE", "QUATERNION"}:
- rot_curves = [i for i in fcurves if i.data_path == "rotation_{}".format(rotation_mode.lower()) and i.keyframe_points]
+ rot_curves = [
+ i
+ for i in fcurves
+ if i.data_path == "rotation_{}".format(rotation_mode.lower())
+ and i.keyframe_points
+ ]
if not rot_curves:
return None
default_xform = default_xform.to_quaternion()
if rotation_mode == "AXIS_ANGLE":
default_xform = default_xform.to_axis_angle()
- default_xform = (default_xform[1], default_xform[0].x, default_xform[0].y, default_xform[0].z)
+ default_xform = (
+ default_xform[1],
+ default_xform[0].x,
+ default_xform[0].y,
+ default_xform[0].z,
+ )
if convert is not None:
convert_original = convert
- convert = lambda x: convert_original(mathutils.Quaternion(x[1:4], x[0]))[:]
+ convert = lambda x: convert_original(
+ mathutils.Quaternion(x[1:4], x[0])
+ )[:]
else:
convert = lambda x: mathutils.Quaternion(x[1:4], x[0])[:]
# Just dropping bezier stuff on the floor because Plasma does not support it, and
# I think that opting into quaternion keyframes is a good enough indication that
# you're OK with that.
- keyframes, bez_chans = self._process_keyframes(rot_curves, 4, default_xform, convert,
- start=start, end=end)
+ keyframes, bez_chans = self._process_keyframes(
+ rot_curves, 4, default_xform, convert, start=start, end=end
+ )
if keyframes:
return self._make_quat_controller(keyframes)
else:
- rot_curves = [i for i in fcurves if i.data_path == "rotation_euler" and i.keyframe_points]
+ rot_curves = [
+ i
+ for i in fcurves
+ if i.data_path == "rotation_euler" and i.keyframe_points
+ ]
if not rot_curves:
return None
@@ -567,9 +824,17 @@ class AnimationConverter:
result = convert(result)
return result[:]
- euler_convert = convert_euler_keyframe if rotation_mode != "XYZ" else convert
- keyframes, bez_chans = self._process_keyframes(rot_curves, 3, default_xform.to_euler(rotation_mode),
- euler_convert, start=start, end=end)
+ euler_convert = (
+ convert_euler_keyframe if rotation_mode != "XYZ" else convert
+ )
+ keyframes, bez_chans = self._process_keyframes(
+ rot_curves,
+ 3,
+ default_xform.to_euler(rotation_mode),
+ euler_convert,
+ start=start,
+ end=end,
+ )
if keyframes:
# Once again, quaternion keyframes do not support bezier interpolation. Ideally,
# we would just drop support for rotation beziers entirely to simplify all this
@@ -579,12 +844,22 @@ class AnimationConverter:
else:
return self._make_quat_controller(keyframes)
- def make_scale_controller(self, fcurves, data_path: str, default_xform,
- convert: Optional[Callable] = None, *, start: Optional[int] = None,
- end: Optional[int] = None) -> Optional[plLeafController]:
- scale_curves = [i for i in fcurves if i.data_path == data_path and i.keyframe_points]
- keyframes, bez_chans = self._process_keyframes(scale_curves, 3, default_xform, convert,
- start=start, end=end)
+ def make_scale_controller(
+ self,
+ fcurves,
+ data_path: str,
+ default_xform,
+ convert: Optional[Callable] = None,
+ *,
+ start: Optional[int] = None,
+ end: Optional[int] = None
+ ) -> Optional[plLeafController]:
+ scale_curves = [
+ i for i in fcurves if i.data_path == data_path and i.keyframe_points
+ ]
+ keyframes, bez_chans = self._process_keyframes(
+ scale_curves, 3, default_xform, convert, start=start, end=end
+ )
if not keyframes:
return None
@@ -592,10 +867,14 @@ class AnimationConverter:
ctrl = self._make_scale_value_controller(keyframes, bez_chans)
return ctrl
- def make_scalar_leaf_controller(self, fcurve: bpy.types.FCurve,
- convert: Optional[Callable] = None, *,
- start: Optional[int] = None,
- end: Optional[int] = None) -> Optional[plLeafController]:
+ def make_scalar_leaf_controller(
+ self,
+ fcurve: bpy.types.FCurve,
+ convert: Optional[Callable] = None,
+ *,
+ start: Optional[int] = None,
+ end: Optional[int] = None
+ ) -> Optional[plLeafController]:
keyframes, bezier = self._process_fcurve(fcurve, convert, start=start, end=end)
if not keyframes:
return None
@@ -620,7 +899,9 @@ class AnimationConverter:
def _make_point3_controller(self, keyframes, bezier) -> plLeafController:
ctrl = plLeafController()
- keyframe_type = hsKeyFrame.kBezPoint3KeyFrame if bezier else hsKeyFrame.kPoint3KeyFrame
+ keyframe_type = (
+ hsKeyFrame.kBezPoint3KeyFrame if bezier else hsKeyFrame.kPoint3KeyFrame
+ )
exported_frames = []
for keyframe in keyframes:
@@ -659,12 +940,18 @@ class AnimationConverter:
value.normalize()
exported.value = utils.quaternion(value)
else:
- raise ValueError("Unexpected number of channels in quaternion keyframe {}".format(num_channels))
+ raise ValueError(
+ "Unexpected number of channels in quaternion keyframe {}".format(
+ num_channels
+ )
+ )
exported_frames.append(exported)
ctrl.keys = (exported_frames, keyframe_type)
return ctrl
- def _make_scalar_compound_controller(self, keyframes, bez_chans) -> plCompoundController:
+ def _make_scalar_compound_controller(
+ self, keyframes, bez_chans
+ ) -> plCompoundController:
ctrl = plCompoundController()
subctrls = ("X", "Y", "Z")
for i in subctrls:
@@ -673,7 +960,11 @@ class AnimationConverter:
for keyframe in keyframes:
for i, subctrl in enumerate(subctrls):
- keyframe_type = hsKeyFrame.kBezScalarKeyFrame if i in bez_chans else hsKeyFrame.kScalarKeyFrame
+ keyframe_type = (
+ hsKeyFrame.kBezScalarKeyFrame
+ if i in bez_chans
+ else hsKeyFrame.kScalarKeyFrame
+ )
exported = hsScalarKey()
exported.frame = keyframe.frame_num
exported.frameTime = keyframe.frame_time
@@ -689,7 +980,9 @@ class AnimationConverter:
def _make_scalar_leaf_controller(self, keyframes, bezier) -> plLeafController:
ctrl = plLeafController()
- keyframe_type = hsKeyFrame.kBezScalarKeyFrame if bezier else hsKeyFrame.kScalarKeyFrame
+ keyframe_type = (
+ hsKeyFrame.kBezScalarKeyFrame if bezier else hsKeyFrame.kScalarKeyFrame
+ )
exported_frames = []
for keyframe in keyframes:
@@ -705,7 +998,9 @@ class AnimationConverter:
return ctrl
def _make_scale_value_controller(self, keyframes, bez_chans) -> plLeafController:
- keyframe_type = hsKeyFrame.kBezScaleKeyFrame if bez_chans else hsKeyFrame.kScaleKeyFrame
+ keyframe_type = (
+ hsKeyFrame.kBezScaleKeyFrame if bez_chans else hsKeyFrame.kScaleKeyFrame
+ )
exported_frames = []
# Hmm... This smells... But it was basically doing this before the rewrite.
@@ -727,7 +1022,7 @@ class AnimationConverter:
def _sort_and_dedupe_keyframes(self, keyframes: Dict) -> Sequence:
"""Takes in the final, unsorted keyframe sequence and sorts it. If all keyframes are
- equivalent, eg due to a convert function, then they are discarded."""
+ equivalent, eg due to a convert function, then they are discarded."""
num_keyframes = len(keyframes)
keyframes_sorted = [keyframes[i] for i in sorted(keyframes)]
@@ -736,17 +1031,32 @@ class AnimationConverter:
def filter_boundaries(i):
if i == 0 or i == num_keyframes - 1:
return False
- left, me, right = keyframes_sorted[i - 1], keyframes_sorted[i], keyframes_sorted[i + 1]
+ left, me, right = (
+ keyframes_sorted[i - 1],
+ keyframes_sorted[i],
+ keyframes_sorted[i + 1],
+ )
return left.values == me.values == right.values
- filtered_indices = list(itertools.filterfalse(filter_boundaries, range(num_keyframes)))
+ filtered_indices = list(
+ itertools.filterfalse(filter_boundaries, range(num_keyframes))
+ )
if len(filtered_indices) == 2:
- if keyframes_sorted[filtered_indices[0]].values == keyframes_sorted[filtered_indices[1]].values:
+ if (
+ keyframes_sorted[filtered_indices[0]].values
+ == keyframes_sorted[filtered_indices[1]].values
+ ):
return []
return [keyframes_sorted[i] for i in filtered_indices]
- def _process_fcurve(self, fcurve: bpy.types.FCurve, convert: Optional[Callable] = None, *,
- start: Optional[int] = None, end: Optional[int] = None) -> Tuple[Sequence, AbstractSet]:
+ def _process_fcurve(
+ self,
+ fcurve: bpy.types.FCurve,
+ convert: Optional[Callable] = None,
+ *,
+ start: Optional[int] = None,
+ end: Optional[int] = None
+ ) -> Tuple[Sequence, AbstractSet]:
"""Like _process_keyframes, but for one fcurve"""
# Adapt from incoming single item sequence to a single argument.
@@ -755,32 +1065,52 @@ class AnimationConverter:
else:
single_convert = None
# Can't proxy to _process_fcurves because it only supports linear interoplation.
- return self._process_keyframes([fcurve], 1, [0.0], single_convert, start=start, end=end)
+ return self._process_keyframes(
+ [fcurve], 1, [0.0], single_convert, start=start, end=end
+ )
- def _santize_converted_values(self, num_channels: int, raw_values: Union[Dict, Sequence], convert: Callable):
+ def _santize_converted_values(
+ self, num_channels: int, raw_values: Union[Dict, Sequence], convert: Callable
+ ):
assert convert is not None
if isinstance(raw_values, Dict):
values = convert(**raw_values)
elif isinstance(raw_values, Sequence):
values = convert(raw_values)
else:
- raise AssertionError("Unexpected type for raw_values: {}".format(raw_values.__class__))
+ raise AssertionError(
+ "Unexpected type for raw_values: {}".format(raw_values.__class__)
+ )
if not isinstance(values, Sequence) and isinstance(values, Iterable):
values = tuple(values)
if not isinstance(values, Sequence):
- assert num_channels == 1, "Converter returned 1 value but expected {}".format(num_channels)
+ assert (
+ num_channels == 1
+ ), "Converter returned 1 value but expected {}".format(num_channels)
values = (values,)
else:
- assert len(values) == num_channels, "Converter returned {} values but expected {}".format(len(values), num_channels)
+ assert (
+ len(values) == num_channels
+ ), "Converter returned {} values but expected {}".format(
+ len(values), num_channels
+ )
return values
- def _process_fcurves(self, fcurves: Sequence, channels: Dict[str, int], result_channels: int,
- convert: Callable, defaults: Dict[str, Union[float, Sequence]], *,
- start: Optional[int] = None, end: Optional[int] = None) -> Sequence:
+ def _process_fcurves(
+ self,
+ fcurves: Sequence,
+ channels: Dict[str, int],
+ result_channels: int,
+ convert: Callable,
+ defaults: Dict[str, Union[float, Sequence]],
+ *,
+ start: Optional[int] = None,
+ end: Optional[int] = None
+ ) -> Sequence:
"""This consumes a sequence of Blender FCurves that map to a single Plasma controller.
- Like `_process_keyframes()`, except the converter function is mandatory, and each
- Blender `data_path` must have a fixed number of channels.
+ Like `_process_keyframes()`, except the converter function is mandatory, and each
+ Blender `data_path` must have a fixed number of channels.
"""
# TODO: This fxn should probably issue a warning if any keyframes use bezier interpolation.
@@ -806,9 +1136,17 @@ class AnimationConverter:
fcurve_keyframes = defaultdict(lambda: defaultdict(dict))
for fcurve in (i for i in fcurves if i is not None):
for fkey in filter(framenum_filter, fcurve.keyframe_points):
- fcurve_keyframes[fkey.co[0]][fcurve.data_path][fcurve.array_index] = fkey
-
- def iter_channel_values(frame_num : int, fcurves : Dict, fkeys : Dict, num_channels : int, defaults : Union[float, Sequence]):
+ fcurve_keyframes[fkey.co[0]][fcurve.data_path][
+ fcurve.array_index
+ ] = fkey
+
+ def iter_channel_values(
+ frame_num: int,
+ fcurves: Dict,
+ fkeys: Dict,
+ num_channels: int,
+ defaults: Union[float, Sequence],
+ ):
for i in range(num_channels):
fkey = fkeys.get(i, None)
if fkey is None:
@@ -820,7 +1158,9 @@ class AnimationConverter:
try:
yield defaults[i]
except:
- assert num_channels == 1, "Got a non-subscriptable default for a multi-channel keyframe."
+ assert (
+ num_channels == 1
+ ), "Got a non-subscriptable default for a multi-channel keyframe."
yield defaults
else:
yield fcurve.evaluate(frame_num)
@@ -834,9 +1174,21 @@ class AnimationConverter:
keyframe.frame_num = int(frame_num * (30.0 / fps))
keyframe.frame_num_blender = frame_num
keyframe.frame_time = frame_num / fps
- keyframe.values_raw = { data_path: tuple(iter_channel_values(frame_num, grouped_fcurves[data_path], fkeys, num_channels, defaults[data_path]))
- for data_path, num_channels in channels.items() }
- keyframe.values = self._santize_converted_values(result_channels, keyframe.values_raw, convert)
+ keyframe.values_raw = {
+ data_path: tuple(
+ iter_channel_values(
+ frame_num,
+ grouped_fcurves[data_path],
+ fkeys,
+ num_channels,
+ defaults[data_path],
+ )
+ )
+ for data_path, num_channels in channels.items()
+ }
+ keyframe.values = self._santize_converted_values(
+ result_channels, keyframe.values_raw, convert
+ )
# Very gnawty
keyframe.in_tans = [0.0] * result_channels
@@ -845,9 +1197,16 @@ class AnimationConverter:
return self._sort_and_dedupe_keyframes(keyframes)
- def _process_keyframes(self, fcurves, num_channels: int, default_values: Sequence,
- convert: Optional[Callable] = None, *, start: Optional[int] = None,
- end: Optional[int] = None) -> Tuple[Sequence, AbstractSet]:
+ def _process_keyframes(
+ self,
+ fcurves,
+ num_channels: int,
+ default_values: Sequence,
+ convert: Optional[Callable] = None,
+ *,
+ start: Optional[int] = None,
+ end: Optional[int] = None
+ ) -> Tuple[Sequence, AbstractSet]:
"""Groups all FCurves for the same frame together"""
keyframe_data = type("KeyFrameData", (), {})
fps, pi = self._bl_fps, math.pi
@@ -863,7 +1222,9 @@ class AnimationConverter:
else:
framenum_filter = lambda x: True
- indexed_fcurves = { fcurve.array_index: fcurve for fcurve in fcurves if fcurve is not None }
+ indexed_fcurves = {
+ fcurve.array_index: fcurve for fcurve in fcurves if fcurve is not None
+ }
for i, fcurve in indexed_fcurves.items():
fcurve.update()
for fkey in filter(framenum_filter, fcurve.keyframe_points):
@@ -896,12 +1257,26 @@ class AnimationConverter:
if convert is None:
keyframe.values = keyframe.values_raw
else:
- keyframe.values = self._santize_converted_values(num_channels, keyframe.values_raw, convert)
+ keyframe.values = self._santize_converted_values(
+ num_channels, keyframe.values_raw, convert
+ )
- for i, fkey in ((i, fkey) for i, fkey in fkeys.items() if fkey.interpolation == "BEZIER"):
+ for i, fkey in (
+ (i, fkey) for i, fkey in fkeys.items() if fkey.interpolation == "BEZIER"
+ ):
value = keyframe.values_raw[i]
- keyframe.in_tans[i] = -(value - fkey.handle_left[1]) / (frame_num - fkey.handle_left[0]) / fps / (2 * pi)
- keyframe.out_tans[i] = (value - fkey.handle_right[1]) / (frame_num - fkey.handle_right[0]) / fps / (2 * pi)
+ keyframe.in_tans[i] = (
+ -(value - fkey.handle_left[1])
+ / (frame_num - fkey.handle_left[0])
+ / fps
+ / (2 * pi)
+ )
+ keyframe.out_tans[i] = (
+ (value - fkey.handle_right[1])
+ / (frame_num - fkey.handle_right[0])
+ / fps
+ / (2 * pi)
+ )
bez_chans.add(i)
keyframes[frame_num] = keyframe
diff --git a/korman/exporter/camera.py b/korman/exporter/camera.py
index bed100c..63a1399 100644
--- a/korman/exporter/camera.py
+++ b/korman/exporter/camera.py
@@ -22,6 +22,7 @@ from .explosions import *
from .. import helpers
from . import utils
+
class CameraConverter:
def __init__(self, exporter):
self._exporter = weakref.ref(exporter)
@@ -31,7 +32,9 @@ class CameraConverter:
brain.poaOffset = hsVector3(*camera_props.poa_offset)
if camera_props.poa_type == "object":
- brain.subject = self._mgr.find_create_key(plSceneObject, bl=camera_props.poa_object)
+ brain.subject = self._mgr.find_create_key(
+ plSceneObject, bl=camera_props.poa_object
+ )
brain.xPanLimit = camera_props.x_pan_angle / 2.0
brain.zPanLimit = camera_props.y_pan_angle / 2.0
@@ -72,7 +75,9 @@ class CameraConverter:
brain.setFlags(plCameraBrain1.kIgnoreSubworldMovement, True)
def export_camera(self, so, bo, camera_type, camera_props, camera_trans=[]):
- brain = getattr(self, "_export_{}_camera".format(camera_type))(so, bo, camera_props)
+ brain = getattr(self, "_export_{}_camera".format(camera_type))(
+ so, bo, camera_props
+ )
mod = self._export_camera_modifier(so, bo, camera_props, camera_trans)
mod.brain = brain.key
@@ -97,7 +102,9 @@ class CameraConverter:
continue
cam_trans = plCameraModifier.CamTrans()
if manual_trans.camera:
- cam_trans.transTo = self._mgr.find_create_key(plCameraModifier, bl=manual_trans.camera)
+ cam_trans.transTo = self._mgr.find_create_key(
+ plCameraModifier, bl=manual_trans.camera
+ )
cam_trans.ignore = manual_trans.mode == "ignore"
trans_info = manual_trans.transition
@@ -121,9 +128,15 @@ class CameraConverter:
if props.poa_type == "avatar":
brain.circleFlags |= plCameraBrain1_Circle.kCircleLocalAvatar
elif props.poa_type == "object":
- brain.poaObject = self._mgr.find_create_key(plSceneObject, bl=props.poa_object)
+ brain.poaObject = self._mgr.find_create_key(
+ plSceneObject, bl=props.poa_object
+ )
else:
- self._report.warn("Circle Camera '{}' has no Point of Attention. Is this intended?", bo.name, indent=3)
+ self._report.warn(
+ "Circle Camera '{}' has no Point of Attention. Is this intended?",
+ bo.name,
+ indent=3,
+ )
if props.circle_pos == "farthest":
brain.circleFlags |= plCameraBrain1_Circle.kFarthest
@@ -134,7 +147,9 @@ class CameraConverter:
if props.circle_center is None:
brain.center = hsVector3(*bo.matrix_world.translation)
else:
- brain.centerObject = self._mgr.find_create_key(plSceneObject, bl=props.circle_center)
+ brain.centerObject = self._mgr.find_create_key(
+ plSceneObject, bl=props.circle_center
+ )
# This flag has no effect in CWE, but I'm using it for correctness' sake
brain.circleFlags |= plCameraBrain1_Circle.kHasCenterObject
@@ -172,7 +187,11 @@ class CameraConverter:
def _export_fixed_camera(self, so, bo, props):
anim_mod = bo.plasma_modifiers.animation
- if props.anim_enabled and not anim_mod.enabled and bo.plasma_object.has_animation_data:
+ if (
+ props.anim_enabled
+ and not anim_mod.enabled
+ and bo.plasma_object.has_animation_data
+ ):
anim_mod.convert_object_animations(self._exporter(), bo, so)
brain = self._mgr.find_create_object(plCameraBrain1_Fixed, so=so)
self._convert_brain(so, bo, props, brain)
@@ -203,11 +222,16 @@ class CameraConverter:
# The rail is defined by a position controller in Plasma. Cyan uses a separate
# path object, but it makes more sense to me to just animate the camera with
# the details of the path...
- pos_fcurves = tuple(i for i in helpers.fetch_fcurves(bo, False) if i.data_path == "location")
- pos_ctrl = self._exporter().animation.convert_transform_controller(pos_fcurves, bo.rotation_mode,
- bo.matrix_local, bo.matrix_parent_inverse)
+ pos_fcurves = tuple(
+ i for i in helpers.fetch_fcurves(bo, False) if i.data_path == "location"
+ )
+ pos_ctrl = self._exporter().animation.convert_transform_controller(
+ pos_fcurves, bo.rotation_mode, bo.matrix_local, bo.matrix_parent_inverse
+ )
if pos_ctrl is None:
- raise ExportError("'{}': Rail Camera lacks appropriate rail keyframes".format(bo.name))
+ raise ExportError(
+ "'{}': Rail Camera lacks appropriate rail keyframes".format(bo.name)
+ )
path = plAnimPath()
path.controller = pos_ctrl
path.affineParts = utils.affine_parts(bo.matrix_local)
@@ -217,8 +241,16 @@ class CameraConverter:
if abs(f1 - f2) > 0.001:
break
# to avoid single/duplicate keyframe client crash (per Hoikas)
- if any((len(i.keys) == 1 for i in (pos_ctrl.X, pos_ctrl.Y, pos_ctrl.Z) if i is not None)):
- raise ExportError("'{}': Rail Camera must have more than one keyframe", bo.name)
+ if any(
+ (
+ len(i.keys) == 1
+ for i in (pos_ctrl.X, pos_ctrl.Y, pos_ctrl.Z)
+ if i is not None
+ )
+ ):
+ raise ExportError(
+ "'{}': Rail Camera must have more than one keyframe", bo.name
+ )
else:
# The animation is a loop
path.flags |= plAnimPath.kWrap
diff --git a/korman/exporter/convert.py b/korman/exporter/convert.py
index 9761eb3..6facd71 100644
--- a/korman/exporter/convert.py
+++ b/korman/exporter/convert.py
@@ -40,17 +40,24 @@ from . import physics
from . import rtlight
from . import utils
+
class Exporter:
def __init__(self, op):
- self._op = op # Blender export operator
+ self._op = op # Blender export operator
self._objects = []
self.actors = set()
self.want_node_trees = defaultdict(set)
self.exported_nodes = {}
def run(self):
- log = logger.ExportVerboseLogger if self._op.verbose else logger.ExportProgressLogger
- with ConsoleToggler(self._op.show_console), log(self._op.filepath) as self.report, ExitStack() as self.exit_stack:
+ log = (
+ logger.ExportVerboseLogger
+ if self._op.verbose
+ else logger.ExportProgressLogger
+ )
+ with ConsoleToggler(self._op.show_console), log(
+ self._op.filepath
+ ) as self.report, ExitStack() as self.exit_stack:
# Step 0: Init export resmgr and stuff
self.mgr = manager.ExportManager(self)
self.mesh = mesh.MeshConverter(self)
@@ -153,7 +160,13 @@ class Exporter:
# Grab a naive listing of enabled pages
age = scene.world.plasma_age
- pages_enabled = frozenset((page.name for page in age.pages if page.enabled and self._op.version in page.version))
+ pages_enabled = frozenset(
+ (
+ page.name
+ for page in age.pages
+ if page.enabled and self._op.version in page.version
+ )
+ )
all_pages = frozenset((page.name for page in age.pages))
# Because we can have an unnamed or a named default page, we need to see if that is enabled...
@@ -228,13 +241,18 @@ class Exporter:
parent = bo.parent
if parent is not None:
if parent.plasma_object.enabled:
- self.report.msg("Attaching to parent SceneObject '{}'", parent.name, indent=1)
+ self.report.msg(
+ "Attaching to parent SceneObject '{}'", parent.name, indent=1
+ )
parent_ci = self._export_coordinate_interface(None, parent)
parent_ci.addChild(so.key)
else:
- self.report.warn("You have parented Plasma Object '{}' to '{}', which has not been marked for export. \
+ self.report.warn(
+ "You have parented Plasma Object '{}' to '{}', which has not been marked for export. \
The object may not appear in the correct location or animate properly.".format(
- bo.name, parent.name))
+ bo.name, parent.name
+ )
+ )
def _export_coordinate_interface(self, so, bl):
"""Ensures that the SceneObject has a CoordinateInterface"""
@@ -260,7 +278,10 @@ class Exporter:
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):
+ for mod in filter(
+ lambda x: hasattr(x, "export_localization"),
+ bl_obj.plasma_modifiers.modifiers,
+ ):
mod.export_localization(self)
inc_progress()
@@ -279,10 +300,17 @@ class Exporter:
try:
export_fn = getattr(self, export_fn)
except AttributeError:
- self.report.warn("""'{}' is a Plasma Object of Blender type '{}'
- ... And I have NO IDEA what to do with that! Tossing.""".format(bl_obj.name, bl_obj.type))
+ self.report.warn(
+ """'{}' is a Plasma Object of Blender type '{}'
+ ... And I have NO IDEA what to do with that! Tossing.""".format(
+ bl_obj.name, bl_obj.type
+ )
+ )
continue
- log_msg("Blender Object '{}' of type '{}'".format(bl_obj.name, bl_obj.type), indent=1)
+ log_msg(
+ "Blender Object '{}' of type '{}'".format(bl_obj.name, bl_obj.type),
+ indent=1,
+ )
# Create a sceneobject if one does not exist.
# Before we call the export_fn, we need to determine if this object is an actor of any
@@ -300,7 +328,9 @@ class Exporter:
def _export_camera_blobj(self, so, bo):
# Hey, guess what? Blender's camera data is utter crap!
camera = bo.data.plasma_camera
- self.camera.export_camera(so, bo, camera.camera_type, camera.settings, camera.transitions)
+ self.camera.export_camera(
+ so, bo, camera.camera_type, camera.settings, camera.transitions
+ )
def _export_empty_blobj(self, so, bo):
pass
@@ -319,7 +349,9 @@ class Exporter:
if bo.data.materials:
self.mesh.export_object(meshObj, so)
else:
- self.report.msg("No material(s) on the ObData, so no drawables", indent=1)
+ self.report.msg(
+ "No material(s) on the ObData, so no drawables", indent=1
+ )
def _export_referenced_node_trees(self):
self.report.progress_advance()
@@ -398,7 +430,12 @@ class Exporter:
for mod in bl_obj.plasma_modifiers.modifiers:
proc = getattr(mod, "post_export", None)
if proc is not None:
- self.report.msg("Post processing '{}' modifier '{}'", bl_obj.name, mod.bl_label, indent=1)
+ self.report.msg(
+ "Post processing '{}' modifier '{}'",
+ bl_obj.name,
+ mod.bl_label,
+ indent=1,
+ )
proc(self, bl_obj, sceneobject)
inc_progress()
@@ -413,13 +450,24 @@ class Exporter:
@functools.singledispatch
def handle_temporary(temporary, parent):
- raise RuntimeError("Temporary object of type '{}' generated by '{}' was unhandled".format(temporary.__class__, parent.name))
+ raise RuntimeError(
+ "Temporary object of type '{}' generated by '{}' was unhandled".format(
+ temporary.__class__, parent.name
+ )
+ )
@handle_temporary.register(bpy.types.Object)
def _(temporary, parent):
- self.exit_stack.enter_context(TemporaryObject(temporary, bpy.data.objects.remove))
- self.report.msg("'{}': generated Object '{}' (Plasma Object: {})", parent.name,
- temporary.name, temporary.plasma_object.enabled, indent=1)
+ self.exit_stack.enter_context(
+ TemporaryObject(temporary, bpy.data.objects.remove)
+ )
+ self.report.msg(
+ "'{}': generated Object '{}' (Plasma Object: {})",
+ parent.name,
+ temporary.name,
+ temporary.plasma_object.enabled,
+ indent=1,
+ )
if temporary.plasma_object.enabled:
new_objects.append(temporary)
@@ -435,7 +483,9 @@ class Exporter:
@handle_temporary.register(bpy.types.NodeTree)
def _(temporary, parent):
- self.exit_stack.enter_context(TemporaryObject(temporary, bpy.data.node_groups.remove))
+ self.exit_stack.enter_context(
+ TemporaryObject(temporary, bpy.data.node_groups.remove)
+ )
self.report.msg("'{}' generated NodeTree '{}'", parent.name, temporary.name)
if temporary.bl_idname == "PlasmaNodeTree":
parent_so = self.mgr.find_create_object(plSceneObject, bl=parent)
diff --git a/korman/exporter/decal.py b/korman/exporter/decal.py
index d6120b2..9dc707b 100644
--- a/korman/exporter/decal.py
+++ b/korman/exporter/decal.py
@@ -21,18 +21,24 @@ import weakref
from ..exporter.explosions import ExportError
+
def _get_puddle_class(exporter, name, vs):
if vs:
# sigh... thou shalt not...
- exporter.report.warn("'{}': Cannot use 'Water Ripple (Shallow) on a waveset--forcing to 'Water Ripple (Deep)", name)
+ exporter.report.warn(
+ "'{}': Cannot use 'Water Ripple (Shallow) on a waveset--forcing to 'Water Ripple (Deep)",
+ name,
+ )
return plDynaRippleVSMgr
return plDynaPuddleMgr
+
def _get_footprint_class(exporter, name, vs):
if vs:
raise ExportError("'{}': Footprints cannot be attached to wavesets", name)
return plDynaFootMgr
+
class DecalConverter:
_decal_lookup = {
"footprint_dry": _get_footprint_class,
@@ -53,7 +59,9 @@ class DecalConverter:
# We don't care about: DynaDecalMgrs in another page.
decal_mgrs, so_key = self._decal_managers.get(decal_name), so.key
if decal_mgrs is None:
- raise ExportError("'{}': Invalid decal manager '{}'", so_key.name, decal_name)
+ raise ExportError(
+ "'{}': Invalid decal manager '{}'", so_key.name, decal_name
+ )
# If we are waveset water, then we can only have one target...
waveset_id = plFactory.ClassIndex("plWaveSet7")
@@ -61,12 +69,17 @@ class DecalConverter:
so_loc = so_key.location
for key, decal_mgr in ((i, i.object) for i in decal_mgrs):
- if key.location == so_loc and getattr(decal_mgr, "waveSet", None) == waveset:
+ if (
+ key.location == so_loc
+ and getattr(decal_mgr, "waveSet", None) == waveset
+ ):
decal_mgr.addTarget(so_key)
# HACKAGE: Add the wet/dirty notifes now that we know about all the decal managers.
notify_names = self._notifies[decal_name]
- notify_keys = itertools.chain.from_iterable((self._decal_managers[i] for i in notify_names))
+ notify_keys = itertools.chain.from_iterable(
+ (self._decal_managers[i] for i in notify_names)
+ )
for notify_key in notify_keys:
for i in (i.object for i in decal_mgrs):
i.addNotify(notify_key)
@@ -76,7 +89,9 @@ class DecalConverter:
def export_active_print_shape(self, print_shape, decal_name):
decal_mgrs = self._decal_managers.get(decal_name)
if decal_mgrs is None:
- raise ExportError("'{}': Invalid decal manager '{}'", print_shape.key.name, decal_name)
+ raise ExportError(
+ "'{}': Invalid decal manager '{}'", print_shape.key.name, decal_name
+ )
for i in decal_mgrs:
print_shape.addDecalMgr(i)
@@ -84,7 +99,9 @@ class DecalConverter:
mat_mgr = self._exporter().mesh.material
mat_keys = mat_mgr.get_materials(bo)
if not mat_keys:
- raise ExportError("'{}': Cannot print decal onto object with no materials", bo.name)
+ raise ExportError(
+ "'{}': Cannot print decal onto object with no materials", bo.name
+ )
zFlags = hsGMatState.kZIncLayer | hsGMatState.kZNoZWrite
for material in (i.object for i in mat_keys):
@@ -97,7 +114,14 @@ class DecalConverter:
layer.state.ZFlags |= zFlags
def generate_dynamic_decal(self, bo, decal_name):
- decal = next((i for i in bpy.context.scene.plasma_scene.decal_managers if i.name == decal_name), None)
+ decal = next(
+ (
+ i
+ for i in bpy.context.scene.plasma_scene.decal_managers
+ if i.name == decal_name
+ ),
+ None,
+ )
if decal is None:
raise ExportError("'{}': Invalid decal manager '{}'", bo.name, decal_name)
@@ -112,7 +136,9 @@ class DecalConverter:
name = "{}_{}".format(decal_name, bo.name) if is_waveset else decal_name
decal_mgr = exporter.mgr.find_object(pClass, bl=bo, name=name)
if decal_mgr is None:
- self._report.msg("Exporing decal manager '{}' to '{}'", decal_name, name, indent=2)
+ self._report.msg(
+ "Exporing decal manager '{}' to '{}'", decal_name, name, indent=2
+ )
decal_mgr = exporter.mgr.add_object(pClass, bl=bo, name=name)
self._decal_managers[decal_name].append(decal_mgr.key)
@@ -126,7 +152,9 @@ class DecalConverter:
image = decal.image
if image is None:
- raise ExportError("'{}': decal manager '{}' has no image set", bo.name, decal_name)
+ raise ExportError(
+ "'{}': decal manager '{}' has no image set", bo.name, decal_name
+ )
blend = getattr(hsGMatState, decal.blend)
mats = exporter.mesh.material.export_print_materials(bo, image, name, blend)
@@ -159,8 +187,13 @@ class DecalConverter:
decal_mgr.waitOnEnable = decal_type == "footprint_wet"
if decal_type in {"puddle", "ripple"}:
decal_mgr.wetLength = decal.wet_time
- self._notifies[decal_name].update((i.name for i in decal.wet_managers
- if i.enabled and i.name != decal_name))
+ self._notifies[decal_name].update(
+ (
+ i.name
+ for i in decal.wet_managers
+ if i.enabled and i.name != decal_name
+ )
+ )
# UV Animations are hardcoded in PlasmaMAX. Any reason why we should expose this?
# I can't think of any presently... Note testing the final instance instead of the
diff --git a/korman/exporter/etlight.py b/korman/exporter/etlight.py
index 8b8a67d..d377137 100644
--- a/korman/exporter/etlight.py
+++ b/korman/exporter/etlight.py
@@ -25,6 +25,7 @@ from ..helpers import *
_NUM_RENDER_LAYERS = 20
+
class LightBaker:
"""ExportTime Lighting"""
@@ -132,7 +133,9 @@ class LightBaker:
# Lightmap passes are expensive, so we will warn about any passes that seem
# particularly wasteful.
try:
- largest_pass = max((len(value) for key, value in bake.items() if key[0] != "vcol"))
+ largest_pass = max(
+ (len(value) for key, value in bake.items() if key[0] != "vcol")
+ )
except ValueError:
largest_pass = 0
@@ -146,19 +149,25 @@ class LightBaker:
self._report.msg("Preparing to bake...", indent=1)
for key, value in bake.items():
if key[0] == "lightmap":
- for i in range(len(value)-1, -1, -1):
+ for i in range(len(value) - 1, -1, -1):
obj = value[i]
if not self._prep_for_lightmap(obj, toggle):
- self._report.msg("Lightmap '{}' will not be baked -- no applicable lights",
- obj.name, indent=2)
+ self._report.msg(
+ "Lightmap '{}' will not be baked -- no applicable lights",
+ obj.name,
+ indent=2,
+ )
value.pop(i)
elif key[0] == "vcol":
- for i in range(len(value)-1, -1, -1):
+ for i in range(len(value) - 1, -1, -1):
obj = value[i]
if not self._prep_for_vcols(obj, toggle):
if self._has_valid_material(obj):
- self._report.msg("VCols '{}' will not be baked -- no applicable lights",
- obj.name, indent=2)
+ self._report.msg(
+ "VCols '{}' will not be baked -- no applicable lights",
+ obj.name,
+ indent=2,
+ )
value.pop(i)
else:
raise RuntimeError(key[0])
@@ -172,14 +181,28 @@ class LightBaker:
if value:
if key[0] == "lightmap":
num_objs = len(value)
- self._report.msg("{} Lightmap(s) [H:{:X}]", num_objs, hash(key[1:]), indent=1)
+ self._report.msg(
+ "{} Lightmap(s) [H:{:X}]", num_objs, hash(key[1:]), indent=1
+ )
if largest_pass > 1 and num_objs < round(largest_pass * 0.02):
- pass_names = set((i.plasma_modifiers.lightmap.bake_pass_name for i in value))
+ pass_names = set(
+ (i.plasma_modifiers.lightmap.bake_pass_name for i in value)
+ )
pass_msg = ", ".join(pass_names)
- self._report.warn("Small lightmap bake pass! Bake Pass(es): {}".format(pass_msg), indent=2)
+ self._report.warn(
+ "Small lightmap bake pass! Bake Pass(es): {}".format(
+ pass_msg
+ ),
+ indent=2,
+ )
self._bake_lightmaps(value, key[1:])
elif key[0] == "vcol":
- self._report.msg("{} Vertex Color(s) [H:{:X}]", len(value), hash(key[1:]), indent=1)
+ self._report.msg(
+ "{} Vertex Color(s) [H:{:X}]",
+ len(value),
+ hash(key[1:]),
+ indent=1,
+ )
self._bake_vcols(value, key[1:])
self._fix_vertex_colors(value)
else:
@@ -232,8 +255,10 @@ class LightBaker:
if len(edge.link_faces) != 2:
# Either a border edge, or an abomination.
continue
- if mesh.use_auto_smooth and (not edge.smooth
- or edge.calc_face_angle() > mesh.auto_smooth_angle):
+ if mesh.use_auto_smooth and (
+ not edge.smooth
+ or edge.calc_face_angle() > mesh.auto_smooth_angle
+ ):
# Normals are split for edges marked as sharp by the user, and edges
# whose angle is above the theshold. Auto smooth must be on in both cases.
continue
@@ -241,10 +266,16 @@ class LightBaker:
# Alright, this edge is connected to our loop AND our face.
# Now for the Fun Stuff(c)... First, actually get ahold of the other
# face (the one we're connected to via this edge).
- other_face = next(f for f in edge.link_faces if f != face)
+ other_face = next(
+ f for f in edge.link_faces if f != face
+ )
# Now get ahold of the loop sharing our vertex on the OTHER SIDE
# of that damnable edge...
- other_loop = next(loop for loop in other_face.loops if loop.vert == vert)
+ other_loop = next(
+ loop
+ for loop in other_face.loops
+ if loop.vert == vert
+ )
other_color = other_loop[light_vcol]
# Phew ! Good, now just pick whichever color has the highest average value
if sum(max_color) / 3 < sum(other_color) / 3:
@@ -256,7 +287,7 @@ class LightBaker:
def _generate_lightgroup(self, bo, user_lg=None):
"""Makes a new light group for the baking process that excludes all Plasma RT lamps"""
- shouldibake = (user_lg is not None and bool(user_lg.objects))
+ shouldibake = user_lg is not None and bool(user_lg.objects)
mesh = bo.data
for material in mesh.materials:
@@ -274,7 +305,9 @@ class LightBaker:
source = [i for i in bpy.context.scene.objects if i.type == "LAMP"]
else:
source = lg.objects
- dest = bpy.data.groups.new("_LIGHTMAPGEN_{}_{}".format(bo.name, mat_name))
+ dest = bpy.data.groups.new(
+ "_LIGHTMAPGEN_{}_{}".format(bo.name, mat_name)
+ )
# Rules:
# 1) No animated lights, period.
@@ -321,9 +354,17 @@ class LightBaker:
if mod.image is not None:
uv_texture_names = frozenset((i.name for i in obj.data.uv_textures))
if self.lightmap_uvtex_name in uv_texture_names:
- self._report.msg("'{}': Skipping due to valid lightmap override", obj.name, indent=1)
+ self._report.msg(
+ "'{}': Skipping due to valid lightmap override",
+ obj.name,
+ indent=1,
+ )
else:
- self._report.warn("'{}': Have lightmap, but regenerating UVs", obj.name, indent=1)
+ self._report.warn(
+ "'{}': Have lightmap, but regenerating UVs",
+ obj.name,
+ indent=1,
+ )
self._prep_for_lightmap_uvs(obj, mod.image, toggle)
return False
return True
@@ -332,15 +373,27 @@ class LightBaker:
def vcol_bake_required(obj) -> bool:
if obj.plasma_modifiers.lightmap.bake_lightmap:
return False
- vcol_layer_names = frozenset((vcol_layer.name.lower() for vcol_layer in obj.data.vertex_colors))
+ vcol_layer_names = frozenset(
+ (vcol_layer.name.lower() for vcol_layer in obj.data.vertex_colors)
+ )
manual_layer_names = _VERTEX_COLOR_LAYERS & vcol_layer_names
if manual_layer_names:
- self._report.msg("'{}': Skipping due to valid manual vertex color layer(s): '{}'", obj.name, manual_layer_names.pop(), indent=1)
+ self._report.msg(
+ "'{}': Skipping due to valid manual vertex color layer(s): '{}'",
+ obj.name,
+ manual_layer_names.pop(),
+ indent=1,
+ )
return False
if self.force:
return True
if self.vcol_layer_name.lower() in vcol_layer_names:
- self._report.msg("'{}': Skipping due to valid matching vertex color layer(s): '{}'", obj.name, self.vcol_layer_name, indent=1)
+ self._report.msg(
+ "'{}': Skipping due to valid matching vertex color layer(s): '{}'",
+ obj.name,
+ self.vcol_layer_name,
+ indent=1,
+ )
return False
return True
@@ -351,7 +404,11 @@ class LightBaker:
if lightmap_mod.bake_pass_name:
bake_pass = bake_passes.get(lightmap_mod.bake_pass_name, None)
if bake_pass is None:
- raise ExportError("Bake Lighting '{}': Could not find pass '{}'".format(i.name, lightmap_mod.bake_pass_name))
+ raise ExportError(
+ "Bake Lighting '{}': Could not find pass '{}'".format(
+ i.name, lightmap_mod.bake_pass_name
+ )
+ )
lm_layers = tuple(bake_pass.render_layers)
else:
lm_layers = default_layers
@@ -359,12 +416,23 @@ class LightBaker:
# In order for Blender to be able to bake this properly, at least one of the
# layers this object is on must be selected. We will sanity check this now.
obj_layers = tuple(i.layers)
- lm_active_layers = set((i for i, value in enumerate(lm_layers) if value))
- obj_active_layers = set((i for i, value in enumerate(obj_layers) if value))
+ lm_active_layers = set(
+ (i for i, value in enumerate(lm_layers) if value)
+ )
+ obj_active_layers = set(
+ (i for i, value in enumerate(obj_layers) if value)
+ )
if not lm_active_layers & obj_active_layers:
- raise ExportError("Bake Lighting '{}': At least one layer the object is on must be selected".format(i.name))
-
- if lightmap_bake_required(i) is False and vcol_bake_required(i) is False:
+ raise ExportError(
+ "Bake Lighting '{}': At least one layer the object is on must be selected".format(
+ i.name
+ )
+ )
+
+ if (
+ lightmap_bake_required(i) is False
+ and vcol_bake_required(i) is False
+ ):
continue
method = "lightmap" if lightmap_mod.bake_lightmap else "vcol"
@@ -407,7 +475,11 @@ class LightBaker:
# Due to our batching, however, materials that are transparent cannot be lightmapped.
for material in (i for i in mesh.materials if i is not None):
if material.use_transparency:
- raise ExportError("'{}': Cannot lightmap material '{}' because it is transparnt".format(bo.name, material.name))
+ raise ExportError(
+ "'{}': Cannot lightmap material '{}' because it is transparnt".format(
+ bo.name, material.name
+ )
+ )
for slot in (j for j in material.texture_slots if j is not None):
toggle.track(slot, "use", False)
@@ -485,8 +557,12 @@ class LightBaker:
# from sharing UVs. Sigh.
if self._mesh.is_collapsed(bo):
# Danger: uv_base.name -> UnicodeDecodeError (wtf? another blender bug?)
- self._report.warn("'{}': packing islands in UV Texture '{}' due to modifier collapse",
- bo.name, modifier.uv_map, indent=2)
+ self._report.warn(
+ "'{}': packing islands in UV Texture '{}' due to modifier collapse",
+ bo.name,
+ modifier.uv_map,
+ indent=2,
+ )
with self._set_mode("EDIT"):
bpy.ops.mesh.select_all(action="SELECT")
bpy.ops.uv.select_all(action="SELECT")
@@ -534,13 +610,19 @@ class LightBaker:
# future exports as an optimization. We won't reach this point if there is already an
# autocolor layer (gulp).
if not self.force and needs_vcol_layer:
- self._mesh.context_stack.enter_context(TemporaryObject(vcol_layer.name, lambda layer_name: vcols.remove(vcols[layer_name])))
+ self._mesh.context_stack.enter_context(
+ TemporaryObject(
+ vcol_layer.name, lambda layer_name: vcols.remove(vcols[layer_name])
+ )
+ )
# Indicate we should bake
return True
def _remove_stale_uvtexes(self, bake):
- lightmap_iter = itertools.chain.from_iterable((value for key, value in bake.items() if key[0] == "lightmap"))
+ lightmap_iter = itertools.chain.from_iterable(
+ (value for key, value in bake.items() if key[0] == "lightmap")
+ )
for bo in lightmap_iter:
uv_textures = bo.data.uv_textures
uvtex = uv_textures.get(self.lightmap_uvtex_name, None)
@@ -571,7 +653,9 @@ class LightBaker:
else:
i.select = False
- if isinstance(i.data, bpy.types.Mesh) and not self._has_valid_material(i):
+ if isinstance(i.data, bpy.types.Mesh) and not self._has_valid_material(
+ i
+ ):
toggle.track(i, "hide_render", True)
else:
for i in bpy.data.objects:
@@ -581,7 +665,9 @@ class LightBaker:
for mat in (j for j in i.data.materials if j is not None):
toggle.track(mat, "use_vertex_color_paint", False)
toggle.track(i, "hide_render", False)
- elif isinstance(i.data, bpy.types.Mesh) and not self._has_valid_material(i):
+ elif isinstance(
+ i.data, bpy.types.Mesh
+ ) and not self._has_valid_material(i):
toggle.track(i, "hide_render", True)
i.select = value
diff --git a/korman/exporter/explosions.py b/korman/exporter/explosions.py
index f21ca6c..3d08197 100644
--- a/korman/exporter/explosions.py
+++ b/korman/exporter/explosions.py
@@ -13,6 +13,7 @@
# You should have received a copy of the GNU General Public License
# along with Korman. If not, see .
+
class NonfatalExportError(Exception):
def __init__(self, *args, **kwargs):
assert args
@@ -34,12 +35,16 @@ class ExportError(Exception):
class BlendNotSupported(ExportError):
def __init__(self, progression, axis):
- super(ExportError, self).__init__("Alpha Blend not supported: {}, {}", progression, axis)
+ super(ExportError, self).__init__(
+ "Alpha Blend not supported: {}, {}", progression, axis
+ )
class BlenderOptionNotSupportedError(ExportError):
def __init__(self, opt):
- super(ExportError, self).__init__("Unsupported Blender Option: '{}'".format(opt))
+ super(ExportError, self).__init__(
+ "Unsupported Blender Option: '{}'".format(opt)
+ )
class ExportAssertionError(ExportError):
@@ -60,14 +65,15 @@ class PlasmaLaunchError(ExportError):
class TooManyUVChannelsError(ExportError):
def __init__(self, obj, mat, numUVTexs, maxUVTexCount=8):
msg = "There are too many UV Textures on the material '{}' associated with object '{}'. You can have at most {} (there are {})".format(
- mat.name, obj.name, maxUVTexCount, numUVTexs)
+ mat.name, obj.name, maxUVTexCount, numUVTexs
+ )
super(ExportError, self).__init__(msg)
class TooManyVerticesError(ExportError):
def __init__(self, mesh, matname, vertcount):
msg = "There are too many vertices ({}) on the mesh data '{}' associated with material '{}'".format(
- vertcount, mesh, matname
+ vertcount, mesh, matname
)
super(ExportError, self).__init__(msg)
@@ -76,11 +82,15 @@ class UndefinedPageError(ExportError):
mistakes = {}
def __init__(self):
- super(ExportError, self).__init__("You have objects in pages that do not exist!")
+ super(ExportError, self).__init__(
+ "You have objects in pages that do not exist!"
+ )
def add(self, page, obj):
if page not in self.mistakes:
- self.mistakes[page] = [obj,]
+ self.mistakes[page] = [
+ obj,
+ ]
else:
self.mistakes[page].append(obj)
@@ -93,4 +103,8 @@ class UndefinedPageError(ExportError):
class UnsupportedTextureError(ExportError):
def __init__(self, texture, material):
- super(ExportError, self).__init__("Cannot export texture '{}' on material '{}' -- unsupported type '{}'".format(texture.name, material.name, texture.type))
+ super(ExportError, self).__init__(
+ "Cannot export texture '{}' on material '{}' -- unsupported type '{}'".format(
+ texture.name, material.name, texture.type
+ )
+ )
diff --git a/korman/exporter/image.py b/korman/exporter/image.py
index cf5ac5c..4ebf8c1 100644
--- a/korman/exporter/image.py
+++ b/korman/exporter/image.py
@@ -26,6 +26,7 @@ _ENTRY_MAGICK = b"KTE\x00"
_IMAGE_MAGICK = b"KTT\x00"
_MIP_MAGICK = b"KTM\x00"
+
@enum.unique
class _HeaderBits(enum.IntEnum):
last_export = 0
@@ -79,7 +80,10 @@ class ImageCache:
image, tag = texture.image, texture.tag
image_name = str(texture)
key = (image_name, tag, compression)
- ex_method, im_method = self._exporter().texcache_method, image.plasma_image.texcache_method
+ ex_method, im_method = (
+ self._exporter().texcache_method,
+ image.plasma_image.texcache_method,
+ )
method = set((ex_method, im_method))
if texture.ephemeral or "skip" in method:
self._images.pop(key, None)
@@ -122,7 +126,10 @@ class ImageCache:
# If the texture is ephemeral (eg a lightmap) or has been marked "rebuild" or "skip"
# in the UI, we don't want anything from the cache. In the first two cases, we never
# want to cache that crap. In the latter case, we just want to signal a recache is needed.
- ex_method, im_method = self._exporter().texcache_method, texture.image.plasma_image.texcache_method
+ ex_method, im_method = (
+ self._exporter().texcache_method,
+ texture.image.plasma_image.texcache_method,
+ )
method = set((ex_method, im_method))
if method != {"use"} or texture.ephemeral:
return None
@@ -150,7 +157,10 @@ class ImageCache:
finally:
if exists:
cached_image.modify_time = path.stat().st_mtime
- if cached_image.export_time and cached_image.export_time < cached_image.modify_time:
+ if (
+ cached_image.export_time
+ and cached_image.export_time < cached_image.modify_time
+ ):
return None
else:
cached_image.modify_time = 0
@@ -158,9 +168,15 @@ class ImageCache:
# ensure the data has been loaded from the cache
if cached_image.image_data is None:
try:
- cached_image.image_data = tuple(self._read_image_data(cached_image, self._read_stream))
+ cached_image.image_data = tuple(
+ self._read_image_data(cached_image, self._read_stream)
+ )
except AssertionError:
- self._report.warn("Cached copy of '{}' is corrupt and will be discarded", cached_image.name, indent=2)
+ self._report.warn(
+ "Cached copy of '{}' is corrupt and will be discarded",
+ cached_image.name,
+ indent=2,
+ )
self._images.pop(key)
return None
return cached_image
@@ -229,7 +245,6 @@ class ImageCache:
stream.seek(pos)
yield tuple(_read_image_mips())
-
def _read_index(self, index_pos, stream):
stream.seek(index_pos)
assert stream.read(4) == _INDEX_MAGICK
diff --git a/korman/exporter/locman.py b/korman/exporter/locman.py
index 13418b7..330c29f 100644
--- a/korman/exporter/locman.py
+++ b/korman/exporter/locman.py
@@ -34,6 +34,7 @@ _SP_LANGUAGES = {"English", "French", "German", "Italian", "Spanish"}
# 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:
@@ -49,18 +50,26 @@ class LocalizationConverter:
self._strings = defaultdict(lambda: defaultdict(dict))
def add_string(self, set_name, element_name, language, value, indent=0):
- self._report.msg("Accepted '{}' translation for '{}'.", element_name, language, indent=indent)
+ 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)
+ 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):
if self._exporter is not None:
- with self._exporter().output.generate_dat_file(filename, **kwargs) as handle:
+ with self._exporter().output.generate_dat_file(
+ filename, **kwargs
+ ) as handle:
yield handle
else:
dirname = kwargs.get("dirname", "dat")
@@ -77,42 +86,68 @@ class LocalizationConverter:
age_name = self._age_name
def write_text_file(language, file_name, contents):
- with self._generate_file(dirname="ageresources", filename=file_name) as stream:
+ 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)
+ 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())
+ 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.",
- language_name, indent=2)
+ self._report.warn(
+ "Translation '{}' will not be used because it is not supported in this version of Plasma.",
+ language_name,
+ indent=2,
+ )
continue
- suffix = "_{}".format(language_name.lower()) if language_name != "English" else ""
+ suffix = (
+ "_{}".format(language_name.lower())
+ if language_name != "English"
+ else ""
+ )
file_name = "{}--{}{}.txt".format(age_name, journal_name, suffix)
write_text_file(language_name, file_name, value)
# Ensure that default (read: "English") journal is available
if "English" not in translations:
- language_name, value = next(((language_name, value) for language_name, value in translations.items()
- if language_name in _SP_LANGUAGES), (None, None))
+ language_name, value = next(
+ (
+ (language_name, value)
+ for language_name, value in translations.items()
+ if language_name in _SP_LANGUAGES
+ ),
+ (None, None),
+ )
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_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)
+ 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)
+ self._report.port(
+ "No 'English' nor any other suitable default translation available",
+ indent=2,
+ )
def _generate_loc_files(self):
if not self._strings:
@@ -131,7 +166,11 @@ class LocalizationConverter:
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)
+ 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":
@@ -156,21 +195,25 @@ class LocalizationConverter:
enc = plEncryptedStream.kEncAes if self._version == pvEoa else None
with self._generate_file(filename, enc=enc) as stream:
- write_line("")
+ write_line('')
write_line("")
- write_line("", self._age_name, indent=1)
+ write_line('', self._age_name, indent=1)
for set_name, elements in sets.items():
- write_line("", set_name, indent=2)
+ write_line('', set_name, indent=2)
for element_name, value in elements.items():
- write_line("", element_name, indent=3)
+ write_line('', element_name, indent=3)
for translation_language, translation_value in iter_element(value):
if _ESHTML_REGEX.search(translation_value):
encoded_value = "".format(translation_value)
else:
encoded_value = xml_escape(translation_value)
- write_line("{translation}",
- language=translation_language, translation=encoded_value, indent=4)
+ write_line(
+ '{translation}',
+ language=translation_language,
+ translation=encoded_value,
+ indent=4,
+ )
write_line("", indent=3)
write_line("", indent=2)
@@ -182,8 +225,14 @@ class LocalizationConverter:
def run(self):
age_props = bpy.context.scene.world.plasma_age
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:
+ 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 Translations")
self._report.progress_add_step("Generating Localization")
self._report.progress_start("Exporting Localization Data")
@@ -204,15 +253,29 @@ class LocalizationConverter:
inc_progress = self._report.progress_increment
for i in objects:
- for mod_type in filter(None, (getattr(j, "pl_id", None) for j in TranslationMixin.__subclasses__())):
+ 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]
+ 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)
+ 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)
+ self.add_string(
+ modifier.localization_set,
+ modifier.key_name,
+ j.language,
+ j.text_id,
+ indent=1,
+ )
inc_progress()
def _run_generate(self):
diff --git a/korman/exporter/logger.py b/korman/exporter/logger.py
index 7a91316..faae94c 100644
--- a/korman/exporter/logger.py
+++ b/korman/exporter/logger.py
@@ -23,6 +23,7 @@ _HEADING_SIZE = 60
_MAX_ELIPSES = 3
_MAX_TIME_UNTIL_ELIPSES = 2.0
+
class _ExportLogger:
def __init__(self, print_logs, age_path=None):
self._errors = []
@@ -37,7 +38,9 @@ class _ExportLogger:
if self._age_path is not None:
# Make the log file name from the age file path -- this ensures we're not trying to write
# the log file to the same directory Blender.exe is in, which might be a permission error
- my_path = self._age_path.with_name("{}_export".format(self._age_path.stem)).with_suffix(".log")
+ my_path = self._age_path.with_name(
+ "{}_export".format(self._age_path.stem)
+ ).with_suffix(".log")
self._file = open(str(my_path), "w")
return self
@@ -85,7 +88,6 @@ class _ExportLogger:
cache = args[0] if len(args) == 1 else args[0].format(*args[1:])
self._porting.append(cache)
-
def progress_add_step(self, name):
pass
@@ -113,8 +115,12 @@ class _ExportLogger:
if num_errors == 1:
raise NonfatalExportError(self._errors[0])
elif num_errors:
- raise NonfatalExportError("""{} errors were encountered during export. Check the export log for more details:
- {}""", num_errors, self._file.name)
+ raise NonfatalExportError(
+ """{} errors were encountered during export. Check the export log for more details:
+ {}""",
+ num_errors,
+ self._file.name,
+ )
def save(self):
# TODO
@@ -160,7 +166,9 @@ class ExportProgressLogger(_ExportLogger):
if value is not None:
export_time = time.perf_counter() - self._time_start_overall
with self._print_condition:
- self._progress_print_step(done=(self._step_progress == self._step_max), error=True)
+ self._progress_print_step(
+ done=(self._step_progress == self._step_max), error=True
+ )
self._progress_print_line("\nABORTED AFTER {:.2f}s".format(export_time))
self._progress_print_heading("ERROR")
self._progress_print_line(str(value))
@@ -191,13 +199,17 @@ class ExportProgressLogger(_ExportLogger):
def progress_end(self):
self._progress_print_step(done=True)
- assert self._step_id+1 == len(self._progress_steps)
+ assert self._step_id + 1 == len(self._progress_steps)
export_time = time.perf_counter() - self._time_start_overall
with self._print_condition:
if self._age_path is not None:
self.msg("\nExported '{}' in {:.2f}s", self._age_path.name, export_time)
- self._progress_print_line("\nEXPORTED '{}' IN {:.2f}s".format(self._age_path.name, export_time))
+ self._progress_print_line(
+ "\nEXPORTED '{}' IN {:.2f}s".format(
+ self._age_path.name, export_time
+ )
+ )
else:
self._progress_print_line("\nCOMPLETED IN {:.2f}s".format(export_time))
self._progress_print_heading()
@@ -230,7 +242,9 @@ class ExportProgressLogger(_ExportLogger):
num_chars = len(text)
border = "-" * int((_HEADING_SIZE - (num_chars + 2)) / 2)
pad = " " if num_chars % 2 == 1 else ""
- line = "{border} {pad}{text} {border}".format(border=border, pad=pad, text=text)
+ line = "{border} {pad}{text} {border}".format(
+ border=border, pad=pad, text=text
+ )
self._progress_print_line(line)
else:
self._progress_print_line("-" * _HEADING_SIZE)
@@ -238,7 +252,9 @@ class ExportProgressLogger(_ExportLogger):
def _progress_print_step(self, done=False, error=False):
with self._print_condition:
if done:
- stage = "DONE IN {:.2f}s".format(time.perf_counter() - self._time_start_step)
+ stage = "DONE IN {:.2f}s".format(
+ time.perf_counter() - self._time_start_step
+ )
print_func = self._progress_print_line
self._progress_print_volatile("")
else:
@@ -246,28 +262,39 @@ class ExportProgressLogger(_ExportLogger):
stage = "{} of {}".format(self._step_progress, self._step_max)
else:
stage = ""
- print_func = self._progress_print_line if error else self._progress_print_volatile
+ print_func = (
+ self._progress_print_line
+ if error
+ else self._progress_print_volatile
+ )
# ALLLLL ABOARD!!!!! HAHAHAHA
step_name = self._progress_steps[self._step_id]
- whitespace = ' ' * (self._step_spacing - len(step_name))
+ whitespace = " " * (self._step_spacing - len(step_name))
num_steps = len(self._progress_steps)
step_id = self._step_id + 1
stage_max_whitespace = len(str(num_steps)) * 2
stage_space_used = len(str(step_id)) + len(str(num_steps))
- stage_whitespace = ' ' * (stage_max_whitespace - stage_space_used + 1)
+ stage_whitespace = " " * (stage_max_whitespace - stage_space_used + 1)
# f-strings would be nice here...
line = "{step_name}{step_whitespace}(step {step_id}/{num_steps}):{stage_whitespace}{stage}".format(
- step_name=step_name, step_whitespace=whitespace, step_id=step_id, num_steps=num_steps,
- stage_whitespace=stage_whitespace, stage=stage)
+ step_name=step_name,
+ step_whitespace=whitespace,
+ step_id=step_id,
+ num_steps=num_steps,
+ stage_whitespace=stage_whitespace,
+ stage=stage,
+ )
print_func(line)
def _progress_get_max(self):
return self._step_max
+
def _progress_set_max(self, value):
assert self._step_id != -1
self._step_max = value
self._progress_print_step()
+
progress_range = property(_progress_get_max, _progress_set_max)
def progress_start(self, action):
@@ -289,13 +316,13 @@ class ExportProgressLogger(_ExportLogger):
while self._progress_alive:
with self._print_condition:
signalled = self._print_condition.wait(timeout=1.0)
- print(end='\r')
+ print(end="\r")
# First, we need to print out any queued whole lines.
# NOTE: no need to lock anything here as Blender uses CPython (GIL)
with self._cursor:
if self._queued_lines:
- print(*self._queued_lines, sep='\n')
+ print(*self._queued_lines, sep="\n")
self._queued_lines.clear()
# Now, we need to print out the current volatile line, if any.
@@ -307,20 +334,28 @@ class ExportProgressLogger(_ExportLogger):
# If the proc is long running, let us display some elipses so as to not alarm the user
if self._time_start_step != 0:
- if (time.perf_counter() - self._time_start_step) > _MAX_TIME_UNTIL_ELIPSES:
- num_dots = 0 if signalled or num_dots == _MAX_ELIPSES else num_dots + 1
+ if (
+ time.perf_counter() - self._time_start_step
+ ) > _MAX_TIME_UNTIL_ELIPSES:
+ num_dots = (
+ 0
+ if signalled or num_dots == _MAX_ELIPSES
+ else num_dots + 1
+ )
else:
num_dots = 0
- print('.' * num_dots, end=" " * (_MAX_ELIPSES - num_dots))
+ print("." * num_dots, end=" " * (_MAX_ELIPSES - num_dots))
self._cursor.update()
def _progress_get_current(self):
return self._step_progress
+
def _progress_set_current(self, value):
assert self._step_id != -1
self._step_progress = value
if self._step_max != 0:
self._progress_print_step()
+
progress_value = property(_progress_get_current, _progress_set_current)
diff --git a/korman/exporter/manager.py b/korman/exporter/manager.py
index e5cc8e2..3316418 100644
--- a/korman/exporter/manager.py
+++ b/korman/exporter/manager.py
@@ -75,7 +75,7 @@ class ExportManager:
def add_object(self, pl, name=None, bl=None, loc=None, so=None):
"""Automates adding a converted Blender object to our Plasma Resource Manager"""
- assert (bl or loc or so)
+ assert bl or loc or so
if loc is not None:
location = loc
elif so is not None:
@@ -98,7 +98,7 @@ class ExportManager:
self.AddObject(location, pl)
node = self._nodes[location]
- if node: # All objects must be in the scene node
+ if node: # All objects must be in the scene node
if isinstance(pl, plSceneObject):
node.addSceneObject(pl.key)
pl.sceneNode = node.key
@@ -145,7 +145,9 @@ class ExportManager:
if want_pysdl:
self._pack_agesdl_hook(age)
sdl = self.add_object(plSceneObject, name="AgeSDLHook", loc=builtin)
- pfm = self.add_object(plPythonFileMod, name="VeryVerySpecialPythonFileMod", so=sdl)
+ pfm = self.add_object(
+ plPythonFileMod, name="VeryVerySpecialPythonFileMod", so=sdl
+ )
pfm.filename = replace_python2_identifier(age)
# Textures.prp
@@ -191,7 +193,7 @@ class ExportManager:
else:
return plEncryptedStream.kEncXtea
- def find_interfaces(self, pClass, so : plSceneObject) -> Iterable[plObjInterface]:
+ def find_interfaces(self, pClass, so: plSceneObject) -> Iterable[plObjInterface]:
assert issubclass(pClass, plObjInterface)
for i in (i.object for i in so.interfaces):
@@ -231,8 +233,12 @@ class ExportManager:
# potentially cause URU to crash. I'm uncertain though, so we'll just warn
# for now.
if issubclass(pClass, plSingleModifier):
- self._exporter().report.warn("Adding SingleModifier '{}' (type: '{}'') to another SceneObject '{}'",
- key.name, pClass.__name__[2:], so.key.name)
+ self._exporter().report.warn(
+ "Adding SingleModifier '{}' (type: '{}'') to another SceneObject '{}'",
+ key.name,
+ pClass.__name__[2:],
+ so.key.name,
+ )
so.addModifier(key)
return key
@@ -281,7 +287,10 @@ class ExportManager:
generator = (i for i in bpy.data.texts if i.name.lower() == namei)
result, collision = next(generator, None), next(generator, None)
if collision is not None:
- raise explosions.ExportError("There are multiple copies of case insensitive text block '{}'.", name)
+ raise explosions.ExportError(
+ "There are multiple copies of case insensitive text block '{}'.",
+ name,
+ )
return result
# AgeSDL Hook Python
@@ -321,24 +330,44 @@ class ExportManager:
with output.generate_dat_file(f, enc=self._encryption) as stream:
fni = bpy.context.scene.world.plasma_fni
- stream.writeLine("Graphics.Renderer.SetClearColor {:.2f} {:.2f} {:.2f}".format(*fni.clear_color))
+ stream.writeLine(
+ "Graphics.Renderer.SetClearColor {:.2f} {:.2f} {:.2f}".format(
+ *fni.clear_color
+ )
+ )
stream.writeLine("Graphics.Renderer.SetYon {:.1f}".format(fni.yon))
if fni.fog_method == "none":
stream.writeLine("Graphics.Renderer.Fog.SetDefLinear 0 0 0")
else:
- stream.writeLine("Graphics.Renderer.Fog.SetDefColor {:.2f} {:.2f} {:.2f}".format(*fni.fog_color))
+ stream.writeLine(
+ "Graphics.Renderer.Fog.SetDefColor {:.2f} {:.2f} {:.2f}".format(
+ *fni.fog_color
+ )
+ )
if fni.fog_method == "linear":
- stream.writeLine("Graphics.Renderer.Fog.SetDefLinear {:.2f} {:.2f} {:.2f}".format(fni.fog_start, fni.fog_end, fni.fog_density))
+ stream.writeLine(
+ "Graphics.Renderer.Fog.SetDefLinear {:.2f} {:.2f} {:.2f}".format(
+ fni.fog_start, fni.fog_end, fni.fog_density
+ )
+ )
elif fni.fog_method == "exp":
- stream.writeLine("Graphics.Renderer.Fog.SetDefExp {:.2f} {:.2f}".format(fni.fog_end, fni.fog_density))
+ stream.writeLine(
+ "Graphics.Renderer.Fog.SetDefExp {:.2f} {:.2f}".format(
+ fni.fog_end, fni.fog_density
+ )
+ )
elif fni.fog_method == "exp2":
- stream.writeLine("Graphics.Renderer.Fog.SetDefExp2 {:.2f} {:.2f}".format(fni.fog_end, fni.fog_density))
+ stream.writeLine(
+ "Graphics.Renderer.Fog.SetDefExp2 {:.2f} {:.2f}".format(
+ fni.fog_end, fni.fog_density
+ )
+ )
def _write_pages(self):
age_name = self._age_info.name
output = self._exporter().output
for loc in self._pages.values():
- page = self.mgr.FindPage(loc) # not cached because it's C++ owned
+ page = self.mgr.FindPage(loc) # not cached because it's C++ owned
chapter = "_District_" if self.mgr.getVer() <= pvMoul else "_"
f = "{}{}{}.prp".format(age_name, chapter, page.page)
diff --git a/korman/exporter/material.py b/korman/exporter/material.py
index 5d42323..2078d65 100644
--- a/korman/exporter/material.py
+++ b/korman/exporter/material.py
@@ -35,8 +35,15 @@ _MAX_STENCILS = 6
# Blender cube map mega image to libHSPlasma plCubicEnvironmap faces mapping...
# See https://blender.stackexchange.com/questions/46891/how-to-render-an-environment-to-a-cube-map-in-cycles
-BLENDER_CUBE_MAP = ("leftFace", "backFace", "rightFace",
- "bottomFace", "topFace", "frontFace")
+BLENDER_CUBE_MAP = (
+ "leftFace",
+ "backFace",
+ "rightFace",
+ "bottomFace",
+ "topFace",
+ "frontFace",
+)
+
class _Texture:
_DETAIL_BLEND = {
@@ -78,8 +85,9 @@ class _Texture:
self.alpha_type = TextureAlpha.full
else:
self.alpha_type = kwargs.get("alpha_type", TextureAlpha.opaque)
- self.allowed_formats = kwargs.get("allowed_formats",
- {"DDS"} if self.mipmap else {"PNG", "JPG"})
+ self.allowed_formats = kwargs.get(
+ "allowed_formats", {"DDS"} if self.mipmap else {"PNG", "JPG"}
+ )
self.is_cube_map = kwargs.get("is_cube_map", False)
# Basic format sanity
@@ -120,10 +128,14 @@ class _Texture:
name = "ALPHAGEN_{}".format(self.name)
if self.is_detail_map:
- name = "DETAILGEN_{}-{}-{}-{}-{}_{}".format(self._DETAIL_BLEND[self.detail_blend],
- self.detail_fade_start, self.detail_fade_stop,
- self.detail_opacity_start, self.detail_opacity_stop,
- self.name)
+ name = "DETAILGEN_{}-{}-{}-{}-{}_{}".format(
+ self._DETAIL_BLEND[self.detail_blend],
+ self.detail_fade_start,
+ self.detail_fade_stop,
+ self.detail_opacity_start,
+ self.detail_opacity_stop,
+ self.name,
+ )
return name
def _update(self, other):
@@ -150,10 +162,19 @@ class MaterialConverter:
"NONE": self._export_texture_type_none,
}
self._animation_exporters = {
- "ambientCtl": functools.partial(self._export_layer_diffuse_animation, converter=self.get_material_ambient),
+ "ambientCtl": functools.partial(
+ self._export_layer_diffuse_animation,
+ converter=self.get_material_ambient,
+ ),
"opacityCtl": self._export_layer_opacity_animation,
- "preshadeCtl": functools.partial(self._export_layer_diffuse_animation, converter=self.get_material_preshade),
- "runtimeCtl": functools.partial(self._export_layer_diffuse_animation, converter=self.get_material_runtime),
+ "preshadeCtl": functools.partial(
+ self._export_layer_diffuse_animation,
+ converter=self.get_material_preshade,
+ ),
+ "runtimeCtl": functools.partial(
+ self._export_layer_diffuse_animation,
+ converter=self.get_material_runtime,
+ ),
"transformCtl": self._export_layer_transform_animation,
}
@@ -180,7 +201,9 @@ class MaterialConverter:
elif method == "dcm2dem":
return True
elif method == "perengine":
- return (ver >= pvMoul and envmap.mapping == "PLANE") or envmap.mapping == "CUBE"
+ return (
+ ver >= pvMoul and envmap.mapping == "PLANE"
+ ) or envmap.mapping == "CUBE"
else:
raise NotImplementedError(method)
else:
@@ -193,8 +216,17 @@ class MaterialConverter:
# being a waveset, doublesided, etc.
single_user = self._requires_single_user(bo, bm)
if single_user:
- mat_name = "{}_AutoSingle".format(bm.name) if bo.name == bm.name else "{}_{}".format(bo.name, bm.name)
- self._report.msg("Exporting Material '{}' as single user '{}'", bm.name, mat_name, indent=1)
+ mat_name = (
+ "{}_AutoSingle".format(bm.name)
+ if bo.name == bm.name
+ else "{}_{}".format(bo.name, bm.name)
+ )
+ self._report.msg(
+ "Exporting Material '{}' as single user '{}'",
+ bm.name,
+ mat_name,
+ indent=1,
+ )
hgmat = None
else:
# Ensure that RT-lit objects don't infect the static-lit objects.
@@ -205,7 +237,9 @@ class MaterialConverter:
mat_prefix = "RTLit_"
else:
mat_prefix = ""
- mat_prefix2 = "NonVtxP_" if self._exporter().mesh.is_nonpreshaded(bo, bm) else ""
+ mat_prefix2 = (
+ "NonVtxP_" if self._exporter().mesh.is_nonpreshaded(bo, bm) else ""
+ )
mat_name = "".join((mat_prefix, mat_prefix2, bm.name))
self._report.msg("Exporting Material '{}'", mat_name, indent=1)
hsgmat = self._mgr.find_key(hsGMaterial, name=mat_name, bl=bo)
@@ -213,7 +247,11 @@ class MaterialConverter:
return hsgmat
hsgmat = self._mgr.add_object(hsGMaterial, name=mat_name, bl=bo)
- slots = [(idx, slot) for idx, slot in enumerate(bm.texture_slots) if self._can_export_texslot(slot)]
+ slots = [
+ (idx, slot)
+ for idx, slot in enumerate(bm.texture_slots)
+ if self._can_export_texslot(slot)
+ ]
# There is a major difference in how Blender and Plasma handle stencils.
# In Blender, the stencil is on top and applies to every layer below is. In Plasma, the stencil
@@ -223,7 +261,11 @@ class MaterialConverter:
# main texture and 1 piggyback.
num_stencils = sum((1 for i in slots if i[1].use_stencil))
if num_stencils > _MAX_STENCILS:
- raise ExportError("Material '{}' uses too many stencils. The maximum is {}".format(bm.name, _MAX_STENCILS))
+ raise ExportError(
+ "Material '{}' uses too many stencils. The maximum is {}".format(
+ bm.name, _MAX_STENCILS
+ )
+ )
stencils = []
restart_pass_next = False
@@ -232,17 +274,21 @@ class MaterialConverter:
# Prepend any BumpMapping magic layers
if slot.use_map_normal:
if bo in self._bump_mats:
- raise ExportError("Material '{}' has more than one bumpmap layer".format(bm.name))
+ raise ExportError(
+ "Material '{}' has more than one bumpmap layer".format(bm.name)
+ )
du, dw, dv = self.export_bumpmap_slot(bo, bm, hsgmat, slot, idx)
- hsgmat.addLayer(du.key) # Du
- hsgmat.addLayer(dw.key) # Dw
- hsgmat.addLayer(dv.key) # Dv
+ hsgmat.addLayer(du.key) # Du
+ hsgmat.addLayer(dw.key) # Dw
+ hsgmat.addLayer(dv.key) # Dv
if slot.use_stencil:
stencils.append((idx, slot))
else:
tex_name = "{}_{}".format(mat_name, slot.name)
- tex_layer = self.export_texture_slot(bo, bm, hsgmat, slot, idx, name=tex_name)
+ tex_layer = self.export_texture_slot(
+ bo, bm, hsgmat, slot, idx, name=tex_name
+ )
if restart_pass_next:
tex_layer.state.miscFlags |= hsGMatState.kMiscRestartPassHere
restart_pass_next = False
@@ -256,20 +302,28 @@ class MaterialConverter:
tex_state = tex_layer.state
if not tex_state.blendFlags & hsGMatState.kBlendMask:
tex_state.blendFlags |= hsGMatState.kBlendAlpha
- tex_state.miscFlags |= hsGMatState.kMiscRestartPassHere | hsGMatState.kMiscBindNext
+ tex_state.miscFlags |= (
+ hsGMatState.kMiscRestartPassHere | hsGMatState.kMiscBindNext
+ )
curr_stencils = len(stencils)
for i in range(curr_stencils):
stencil_idx, stencil = stencils[i]
- stencil_name = "STENCILGEN_{}@{}_{}".format(stencil.name, bm.name, slot.name)
- stencil_layer = self.export_texture_slot(bo, bm, hsgmat, stencil, stencil_idx, name=stencil_name)
- if i+1 < curr_stencils:
+ stencil_name = "STENCILGEN_{}@{}_{}".format(
+ stencil.name, bm.name, slot.name
+ )
+ stencil_layer = self.export_texture_slot(
+ bo, bm, hsgmat, stencil, stencil_idx, name=stencil_name
+ )
+ if i + 1 < curr_stencils:
stencil_layer.state.miscFlags |= hsGMatState.kMiscBindNext
hsgmat.addLayer(stencil_layer.key)
# Plasma makes several assumptions that every hsGMaterial has at least one layer. If this
# material had no Textures, we will need to initialize a default layer
if not hsgmat.layers:
- layer = self._mgr.find_create_object(plLayer, name="{}_AutoLayer".format(mat_name), bl=bo)
+ layer = self._mgr.find_create_object(
+ plLayer, name="{}_AutoLayer".format(mat_name), bl=bo
+ )
self._obj2layer[bo][bm][None].append(layer.key)
self._propagate_material_settings(bo, bm, layer)
layer = self._export_layer_animations(bo, bm, None, 0, layer)
@@ -292,8 +346,14 @@ class MaterialConverter:
layer.ambient = hsColorRGBA(0.0, 0.0, 0.0, 1.0)
layer.preshade = hsColorRGBA(0.0, 0.0, 0.0, 1.0)
layer.runtime = hsColorRGBA(1.0, 1.0, 1.0, 1.0)
- self.export_prepared_image(name=image_name, image=image, alpha_type=image_alpha,
- owner=layer, allowed_formats={"DDS"}, indent=4)
+ self.export_prepared_image(
+ name=image_name,
+ image=image,
+ alpha_type=image_alpha,
+ owner=layer,
+ allowed_formats={"DDS"},
+ indent=4,
+ )
material = self._mgr.add_object(hsGMaterial, bl=bo, name=name)
material.addLayer(layer.key)
return material, layer
@@ -302,14 +362,19 @@ class MaterialConverter:
image_alpha = self._test_image_alpha(image)
if image_alpha == TextureAlpha.opaque and want_preshade:
- self._report.warn("Using an opaque texture with alpha blending -- this may look bad")
+ self._report.warn(
+ "Using an opaque texture with alpha blending -- this may look bad"
+ )
# Non-alpha blendmodes absolutely cannot have an alpha channel. Period. Nada.
# You can't even filter it out with blend flags. We'll try to mitigate the damage by
# exporting a DXT1 version. As of right now, opaque vs on_off does nothing, so we still
# get some turd-alpha data.
if image_alpha == TextureAlpha.full and not want_preshade:
- self._report.warn("Using an alpha texture with a non-alpha blend mode -- this may look bad", indent=3)
+ self._report.warn(
+ "Using an alpha texture with a non-alpha blend mode -- this may look bad",
+ indent=3,
+ )
image_alpha = TextureAlpha.opaque
image_name = "DECALPRINT_{}".format(image.name)
else:
@@ -336,12 +401,19 @@ class MaterialConverter:
self._report.msg("Exporting Print Material '{}'", prename, indent=3)
pre_material, pre_layer = make_print_material(prename)
pre_material.compFlags |= hsGMaterial.kCompNeedsBlendChannel
- pre_layer.state.miscFlags |= hsGMatState.kMiscBindNext | hsGMatState.kMiscRestartPassHere
+ pre_layer.state.miscFlags |= (
+ hsGMatState.kMiscBindNext | hsGMatState.kMiscRestartPassHere
+ )
pre_layer.preshade = hsColorRGBA(1.0, 1.0, 1.0, 1.0)
- blend_layer = self._mgr.add_object(plLayer, bl=bo, name="{}_AlphaBlend".format(rtname))
- blend_layer.state.blendFlags = hsGMatState.kBlendAlpha | hsGMatState.kBlendNoTexColor | \
- hsGMatState.kBlendAlphaMult
+ blend_layer = self._mgr.add_object(
+ plLayer, bl=bo, name="{}_AlphaBlend".format(rtname)
+ )
+ blend_layer.state.blendFlags = (
+ hsGMatState.kBlendAlpha
+ | hsGMatState.kBlendNoTexColor
+ | hsGMatState.kBlendAlphaMult
+ )
blend_layer.state.clampFlags = hsGMatState.kClampTexture
blend_layer.state.ZFlags = hsGMatState.kZNoZWrite
blend_layer.ambient = hsColorRGBA(1.0, 1.0, 1.0, 1.0)
@@ -374,9 +446,15 @@ class MaterialConverter:
self._report.msg("Exporting Plasma Bumpmap Layers for '{}'", name, indent=2)
# Okay, now we need to make 3 layers for the Du, Dw, and Dv
- du_layer = self._mgr.find_create_object(plLayer, name="{}_DU_BumpLut".format(name), bl=bo)
- dw_layer = self._mgr.find_create_object(plLayer, name="{}_DW_BumpLut".format(name), bl=bo)
- dv_layer = self._mgr.find_create_object(plLayer, name="{}_DV_BumpLut".format(name), bl=bo)
+ du_layer = self._mgr.find_create_object(
+ plLayer, name="{}_DU_BumpLut".format(name), bl=bo
+ )
+ dw_layer = self._mgr.find_create_object(
+ plLayer, name="{}_DW_BumpLut".format(name), bl=bo
+ )
+ dv_layer = self._mgr.find_create_object(
+ plLayer, name="{}_DV_BumpLut".format(name), bl=bo
+ )
for layer in (du_layer, dw_layer, dv_layer):
layer.ambient = hsColorRGBA(1.0, 1.0, 1.0, 1.0)
@@ -393,7 +471,9 @@ class MaterialConverter:
if not slot.use_map_specular:
du_layer.state.blendFlags = hsGMatState.kBlendMADD
- du_layer.state.miscFlags |= hsGMatState.kMiscBumpDu | hsGMatState.kMiscRestartPassHere
+ du_layer.state.miscFlags |= (
+ hsGMatState.kMiscBumpDu | hsGMatState.kMiscRestartPassHere
+ )
dw_layer.state.miscFlags |= hsGMatState.kMiscBumpDw
dv_layer.state.miscFlags |= hsGMatState.kMiscBumpDv
@@ -406,7 +486,9 @@ class MaterialConverter:
LUT_key = self._mgr.find_key(plMipmap, loc=page, name="BumpLutTexture")
if LUT_key is None:
- bumpLUT = plMipmap("BumpLutTexture", 16, 16, 1, plBitmap.kUncompressed, plBitmap.kRGB8888)
+ bumpLUT = plMipmap(
+ "BumpLutTexture", 16, 16, 1, plBitmap.kUncompressed, plBitmap.kRGB8888
+ )
create_bump_LUT(bumpLUT)
self._mgr.AddObject(page, bumpLUT)
LUT_key = bumpLUT.key
@@ -417,7 +499,9 @@ class MaterialConverter:
return (du_layer, dw_layer, dv_layer)
- def export_texture_slot(self, bo, bm, hsgmat, slot, idx, name=None, blend_flags=True):
+ def export_texture_slot(
+ self, bo, bm, hsgmat, slot, idx, name=None, blend_flags=True
+ ):
if name is None:
name = "{}_{}".format(bm.name if bm is not None else bo.name, slot.name)
self._report.msg("Exporting Plasma Layer '{}'", name, indent=2)
@@ -433,25 +517,39 @@ class MaterialConverter:
self._report.msg("Using UV Map #{} '{}'", i, name, indent=3)
break
else:
- self._report.msg("No UVMap specified... Blindly using the first one, maybe it exists :|", indent=3)
+ self._report.msg(
+ "No UVMap specified... Blindly using the first one, maybe it exists :|",
+ indent=3,
+ )
# Transform
xform = hsMatrix44()
- translation = hsVector3(slot.offset.x - (slot.scale.x - 1.0) / 2.0,
- -slot.offset.y - (slot.scale.y - 1.0) / 2.0,
- slot.offset.z - (slot.scale.z - 1.0) / 2.0)
+ translation = hsVector3(
+ slot.offset.x - (slot.scale.x - 1.0) / 2.0,
+ -slot.offset.y - (slot.scale.y - 1.0) / 2.0,
+ slot.offset.z - (slot.scale.z - 1.0) / 2.0,
+ )
xform.setTranslate(translation)
xform.setScale(hsVector3(*slot.scale))
layer.transform = xform
- wantStencil, canStencil = slot.use_stencil, slot.use_stencil and bm is not None and not slot.use_map_normal
+ wantStencil, canStencil = (
+ slot.use_stencil,
+ slot.use_stencil and bm is not None and not slot.use_map_normal,
+ )
if wantStencil and not canStencil:
- self._exporter().report.warn("{} wants to stencil, but this is not a real Material".format(slot.name))
+ self._exporter().report.warn(
+ "{} wants to stencil, but this is not a real Material".format(slot.name)
+ )
state = layer.state
if canStencil:
hsgmat.compFlags |= hsGMaterial.kCompNeedsBlendChannel
- state.blendFlags |= hsGMatState.kBlendAlpha | hsGMatState.kBlendAlphaMult | hsGMatState.kBlendNoTexColor
+ state.blendFlags |= (
+ hsGMatState.kBlendAlpha
+ | hsGMatState.kBlendAlphaMult
+ | hsGMatState.kBlendNoTexColor
+ )
state.ZFlags |= hsGMatState.kZNoZWrite
layer.ambient = hsColorRGBA(1.0, 1.0, 1.0, 1.0)
elif blend_flags:
@@ -518,7 +616,9 @@ class MaterialConverter:
converter = self._exporter().animation
texture = tex_slot.texture if tex_slot is not None else None
- def attach_layer(pClass: type, anim_name: str, controllers: Dict[str, plController]):
+ def attach_layer(
+ pClass: type, anim_name: str, controllers: Dict[str, plController]
+ ):
nonlocal top_layer
name = "{}_{}".format(base_layer.key.name, anim_name)
layer_animation = self._mgr.find_create_object(pClass, bl=bo, name=name)
@@ -543,15 +643,18 @@ class MaterialConverter:
start, end = anim.start, anim.end
else:
start, end = None, None
- controllers = self._export_layer_controllers(bo, bm, tex_slot, idx, base_layer,
- start=start, end=end)
+ controllers = self._export_layer_controllers(
+ bo, bm, tex_slot, idx, base_layer, start=start, end=end
+ )
if not controllers:
continue
pClass = plLayerSDLAnimation if anim.sdl_var else plLayerAnimation
attach_layer(pClass, anim.animation_name, controllers)
atc = top_layer.timeConvert
- atc.begin, atc.end = converter.get_frame_time_range(*controllers.values())
+ atc.begin, atc.end = converter.get_frame_time_range(
+ *controllers.values()
+ )
atc.loopBegin, atc.loopEnd = atc.begin, atc.end
if not anim.auto_start:
atc.flags |= plAnimTimeConvert.kStopped
@@ -561,21 +664,32 @@ class MaterialConverter:
top_layer.varName = anim.sdl_var
else:
# Crappy automatic entire layer animation. Loop it by default.
- controllers = self._export_layer_controllers(bo, bm, tex_slot, idx, base_layer)
+ controllers = self._export_layer_controllers(
+ bo, bm, tex_slot, idx, base_layer
+ )
if controllers:
attach_layer(plLayerAnimation, "(Entire Animation)", controllers)
atc = top_layer.timeConvert
atc.flags |= plAnimTimeConvert.kLoop
- atc.begin, atc.end = converter.get_frame_time_range(*controllers.values())
+ atc.begin, atc.end = converter.get_frame_time_range(
+ *controllers.values()
+ )
atc.loopBegin = atc.begin
atc.loopEnd = atc.end
return top_layer
-
- def _export_layer_controllers(self, bo: bpy.types.Object, bm: bpy.types.Material, tex_slot,
- idx: int, base_layer, *, start: Optional[int] = None,
- end: Optional[int] = None) -> Dict[str, plController]:
+ def _export_layer_controllers(
+ self,
+ bo: bpy.types.Object,
+ bm: bpy.types.Material,
+ tex_slot,
+ idx: int,
+ base_layer,
+ *,
+ start: Optional[int] = None,
+ end: Optional[int] = None
+ ) -> Dict[str, plController]:
"""Convert animations on this material/texture combo in the requested range to Plasma controllers"""
def harvest_fcurves(bl_id, collection, data_path=None):
@@ -589,7 +703,13 @@ class MaterialConverter:
if data_path is None:
collection.extend(action.fcurves)
else:
- collection.extend((i for i in action.fcurves if i.data_path.startswith(data_path)))
+ collection.extend(
+ (
+ i
+ for i in action.fcurves
+ if i.data_path.startswith(data_path)
+ )
+ )
return action
return None
@@ -609,12 +729,16 @@ class MaterialConverter:
# animation controllers.
controllers = {}
for attr, converter in self._animation_exporters.items():
- ctrl = converter(bo, bm, tex_slot, base_layer, fcurves, start=start, end=end)
+ ctrl = converter(
+ bo, bm, tex_slot, base_layer, fcurves, start=start, end=end
+ )
if ctrl is not None:
controllers[attr] = ctrl
return controllers
- def _export_layer_diffuse_animation(self, bo, bm, tex_slot, base_layer, fcurves, *, start, end, converter):
+ def _export_layer_diffuse_animation(
+ self, bo, bm, tex_slot, base_layer, fcurves, *, start, end, converter
+ ):
assert converter is not None
# If there's no material, then this is simply impossible.
@@ -626,12 +750,19 @@ class MaterialConverter:
result = converter(bo, bm, mathutils.Color(color_sequence))
return result.red, result.green, result.blue
- ctrl = self._exporter().animation.make_pos_controller(fcurves, "diffuse_color",
- bm.diffuse_color, translate_color,
- start=start, end=end)
+ ctrl = self._exporter().animation.make_pos_controller(
+ fcurves,
+ "diffuse_color",
+ bm.diffuse_color,
+ translate_color,
+ start=start,
+ end=end,
+ )
return ctrl
- def _export_layer_opacity_animation(self, bo, bm, tex_slot, base_layer, fcurves, *, start, end):
+ def _export_layer_opacity_animation(
+ self, bo, bm, tex_slot, base_layer, fcurves, *, start, end
+ ):
# Dumb function to intercept the opacity values and properly flag the base layer
def process_opacity(value):
self._handle_layer_opacity(base_layer, value)
@@ -639,20 +770,30 @@ class MaterialConverter:
for i in fcurves:
if i.data_path == "plasma_layer.opacity":
- ctrl = self._exporter().animation.make_scalar_leaf_controller(i, process_opacity, start=start, end=end)
+ ctrl = self._exporter().animation.make_scalar_leaf_controller(
+ i, process_opacity, start=start, end=end
+ )
return ctrl
return None
- def _export_layer_transform_animation(self, bo, bm, tex_slot, base_layer, fcurves, *, start, end):
+ def _export_layer_transform_animation(
+ self, bo, bm, tex_slot, base_layer, fcurves, *, start, end
+ ):
if tex_slot is not None:
path = tex_slot.path_from_id()
pos_path = "{}.offset".format(path)
scale_path = "{}.scale".format(path)
# Plasma uses the controller to generate a matrix44... so we have to produce a leaf controller
- ctrl = self._exporter().animation.make_matrix44_controller(fcurves, pos_path, scale_path,
- tex_slot.offset, tex_slot.scale,
- start=start, end=end)
+ ctrl = self._exporter().animation.make_matrix44_controller(
+ fcurves,
+ pos_path,
+ scale_path,
+ tex_slot.offset,
+ tex_slot.scale,
+ start=start,
+ end=end,
+ )
return ctrl
return None
@@ -683,9 +824,13 @@ class MaterialConverter:
# Sanity check: the image here should be 3x2 faces, so we should not have any
# dam remainder...
if width % 3 != 0:
- raise ExportError("CubeMap '{}' width must be a multiple of 3".format(texture.image.name))
+ raise ExportError(
+ "CubeMap '{}' width must be a multiple of 3".format(texture.image.name)
+ )
if height % 2 != 0:
- raise ExportError("CubeMap '{}' height must be a multiple of 2".format(texture.image.name))
+ raise ExportError(
+ "CubeMap '{}' height must be a multiple of 2".format(texture.image.name)
+ )
# According to PlasmaMAX, we don't give a rip about UVs...
layer.UVWSrc = plLayerInterface.kUVWReflect
@@ -696,10 +841,16 @@ class MaterialConverter:
# to a big "finalize" save step to prevent races. The texture cache would
# prevent that as well, so we could theoretically slice-and-dice the single
# image here... but... meh. Offloading taim.
- self.export_prepared_image(texture=texture, owner=layer, indent=3,
- alpha_type=TextureAlpha.opaque, mipmap=True,
- allowed_formats={"DDS"}, is_cube_map=True, tag="cubemap")
-
+ self.export_prepared_image(
+ texture=texture,
+ owner=layer,
+ indent=3,
+ alpha_type=TextureAlpha.opaque,
+ mipmap=True,
+ allowed_formats={"DDS"},
+ is_cube_map=True,
+ tag="cubemap",
+ )
def export_dynamic_env(self, bo, layer, texture, pl_class):
bl_env = texture.environment_map
@@ -713,7 +864,9 @@ class MaterialConverter:
oRes = bl_env.resolution
eRes = helpers.ensure_power_of_two(oRes)
if oRes != eRes:
- self._report.msg("Overriding EnvMap size to ({}x{}) -- POT", eRes, eRes, indent=3)
+ self._report.msg(
+ "Overriding EnvMap size to ({}x{}) -- POT", eRes, eRes, indent=3
+ )
# And now for the general ho'hum-ness
pl_env = self._mgr.find_create_object(pl_class, bl=bo, name=name)
@@ -733,9 +886,13 @@ class MaterialConverter:
for region in texture.plasma_layer.vis_regions:
rgn = region.control_region
if rgn is None:
- raise ExportError("'{}': Has an invalid Visibility Control".format(texture.name))
+ raise ExportError(
+ "'{}': Has an invalid Visibility Control".format(texture.name)
+ )
if not rgn.plasma_modifiers.visregion.enabled:
- raise ExportError("'{}': '{}' is not a VisControl".format(texture.name, rgn.name))
+ raise ExportError(
+ "'{}': '{}' is not a VisControl".format(texture.name, rgn.name)
+ )
visregions.append(self._mgr.find_create_key(plVisRegion, bl=rgn))
pl_env.visRegions = visregions
@@ -756,7 +913,9 @@ class MaterialConverter:
# This is really just so we don't raise any eyebrows if anyone is looking at the files.
# If you're disabling DCMs, then you're obviuously trolling!
# Cyan generates a single color image, but we'll just set the layer colors and go away.
- fake_layer = self._mgr.find_create_object(plLayer, bl=bo, name="{}_DisabledDynEnvMap".format(texture.name))
+ fake_layer = self._mgr.find_create_object(
+ plLayer, bl=bo, name="{}_DisabledDynEnvMap".format(texture.name)
+ )
fake_layer.ambient = layer.ambient
fake_layer.preshade = layer.preshade
fake_layer.runtime = layer.runtime
@@ -765,15 +924,25 @@ class MaterialConverter:
if pl_env.camera is None:
layer.UVWSrc = plLayerInterface.kUVWPosition
- layer.state.miscFlags |= (hsGMatState.kMiscCam2Screen | hsGMatState.kMiscPerspProjection)
+ layer.state.miscFlags |= (
+ hsGMatState.kMiscCam2Screen | hsGMatState.kMiscPerspProjection
+ )
else:
faces = pl_env.faces + (pl_env,)
# If the user specifies a camera object, this might be worthy of a notice.
if viewpt.type == "CAMERA":
- warn = self._report.port if bl_env.mapping == "PLANE" else self._report.warn
- warn("Environment Map '{}' is exporting as a cube map. The viewpoint '{}' is a camera, but only its position will be used.",
- bl_env.id_data.name, viewpt.name, indent=5)
+ warn = (
+ self._report.port
+ if bl_env.mapping == "PLANE"
+ else self._report.warn
+ )
+ warn(
+ "Environment Map '{}' is exporting as a cube map. The viewpoint '{}' is a camera, but only its position will be used.",
+ bl_env.id_data.name,
+ viewpt.name,
+ indent=5,
+ )
# DEMs can do just a position vector. We actually prefer this because the WaveSet exporter
# will probably want to steal it for diabolical purposes... In MOUL, root objects are
@@ -810,9 +979,15 @@ class MaterialConverter:
# Does the image have any alpha at all?
if texture.image is not None:
alpha_type = self._test_image_alpha(texture.image)
- has_alpha = texture.use_calculate_alpha or slot.use_stencil or alpha_type != TextureAlpha.opaque
+ has_alpha = (
+ texture.use_calculate_alpha
+ or slot.use_stencil
+ or alpha_type != TextureAlpha.opaque
+ )
if (texture.image.use_alpha and texture.use_alpha) and not has_alpha:
- warning = "'{}' wants to use alpha, but '{}' is opaque".format(texture.name, texture.image.name)
+ warning = "'{}' wants to use alpha, but '{}' is opaque".format(
+ texture.name, texture.image.name
+ )
self._exporter().report.warn(warning, indent=3)
else:
alpha_type, has_alpha = TextureAlpha.opaque, False
@@ -845,7 +1020,9 @@ class MaterialConverter:
# Otherwise, we toss this layer and some info into our pending texture dict and process it
# 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 = self._mgr.find_create_object(
+ plDynamicTextMap, name="{}_DynText".format(layer.key.name), bl=bo
+ )
if texture.use_alpha:
dtm.hasAlpha = True
if not state.blendFlags & hsGMatState.kBlendMask:
@@ -864,20 +1041,28 @@ class MaterialConverter:
detail_blend = TEX_DETAIL_MULTIPLY
# Herp, derp... Detail blends are all based on alpha
- if layer_props.is_detail_map and not state.blendFlags & hsGMatState.kBlendMask:
+ if (
+ layer_props.is_detail_map
+ and not state.blendFlags & hsGMatState.kBlendMask
+ ):
state.blendFlags |= hsGMatState.kBlendDetail
allowed_formats = {"DDS"} if mipmap else {"PNG", "BMP"}
- self.export_prepared_image(texture=texture, owner=layer,
- alpha_type=alpha_type, force_calc_alpha=slot.use_stencil,
- is_detail_map=layer_props.is_detail_map,
- detail_blend=detail_blend,
- detail_fade_start=layer_props.detail_fade_start,
- detail_fade_stop=layer_props.detail_fade_stop,
- detail_opacity_start=layer_props.detail_opacity_start,
- detail_opacity_stop=layer_props.detail_opacity_stop,
- mipmap=mipmap, allowed_formats=allowed_formats,
- indent=3)
+ self.export_prepared_image(
+ texture=texture,
+ owner=layer,
+ alpha_type=alpha_type,
+ force_calc_alpha=slot.use_stencil,
+ is_detail_map=layer_props.is_detail_map,
+ detail_blend=detail_blend,
+ detail_fade_start=layer_props.detail_fade_start,
+ detail_fade_stop=layer_props.detail_fade_stop,
+ detail_opacity_start=layer_props.detail_opacity_start,
+ detail_opacity_stop=layer_props.detail_opacity_stop,
+ mipmap=mipmap,
+ allowed_formats=allowed_formats,
+ indent=3,
+ )
def _export_texture_type_none(self, bo, layer, slot, idx):
# We'll allow this, just for sanity's sake...
@@ -885,7 +1070,11 @@ class MaterialConverter:
def _export_texture_type_blend(self, bo, layer, slot, idx):
state = layer.state
- state.blendFlags |= hsGMatState.kBlendAlpha | hsGMatState.kBlendAlphaMult | hsGMatState.kBlendNoTexColor
+ state.blendFlags |= (
+ hsGMatState.kBlendAlpha
+ | hsGMatState.kBlendAlphaMult
+ | hsGMatState.kBlendNoTexColor
+ )
state.clampFlags |= hsGMatState.kClampTexture
state.ZFlags |= hsGMatState.kZNoZWrite
@@ -895,12 +1084,12 @@ class MaterialConverter:
def export_alpha_blend(self, progression, axis, owner, indent=2):
"""This exports an alpha blend texture as exposed by bpy.types.BlendTexture.
- The following arguments are expected:
- - progression: (required)
- - axis: (required)
- - owner: (required) the Plasma object using this image
- - indent: (optional) indentation level for log messages
- default: 2
+ The following arguments are expected:
+ - progression: (required)
+ - axis: (required)
+ - owner: (required) the Plasma object using this image
+ - indent: (optional) indentation level for log messages
+ default: 2
"""
# Certain blend types don't use an axis...
@@ -913,6 +1102,7 @@ class MaterialConverter:
image = bpy.data.images.get(filename)
if image is None:
+
def _calc_diagonal(x, y, width, height):
distance = math.sqrt(pow(x, 2) + pow(y, 2))
total = math.sqrt(pow(width, 2) + pow(height, 2))
@@ -960,16 +1150,26 @@ class MaterialConverter:
("QUADRATIC", "VERTICAL"): (4, 64),
}
funcs = {
- ("DIAGONAL", ""): _calc_diagonal,
- ("EASING", "HORIZONTAL"): lambda x, y, width, height: 0.5 - math.cos(x / width * math.pi) * 0.5,
- ("EASING", "VERTICAL"): lambda x, y, width, height: 0.5 - math.cos(y / height * math.pi) * 0.5,
+ ("DIAGONAL", ""): _calc_diagonal,
+ ("EASING", "HORIZONTAL"): lambda x, y, width, height: 0.5
+ - math.cos(x / width * math.pi) * 0.5,
+ ("EASING", "VERTICAL"): lambda x, y, width, height: 0.5
+ - math.cos(y / height * math.pi) * 0.5,
("LINEAR", "HORIZONTAL"): lambda x, y, width, height: x / width,
("LINEAR", "VERTICAL"): lambda x, y, width, height: y / height,
- ("QUADRATIC", "HORIZONTAL"): lambda x, y, width, height: pow(x / width, 2),
- ("QUADRATIC", "VERTICAL"): lambda x, y, width, height: pow(y / height, 2),
+ ("QUADRATIC", "HORIZONTAL"): lambda x, y, width, height: pow(
+ x / width, 2
+ ),
+ ("QUADRATIC", "VERTICAL"): lambda x, y, width, height: pow(
+ y / height, 2
+ ),
("QUADRATIC_SPHERE", ""): _calc_quad_sphere,
- ("RADIAL", "HORIZONTAL"): functools.partial(_calc_radial, horizontal=True),
- ("RADIAL", "VERTICAL"): functools.partial(_calc_radial, horizontal=False),
+ ("RADIAL", "HORIZONTAL"): functools.partial(
+ _calc_radial, horizontal=True
+ ),
+ ("RADIAL", "VERTICAL"): functools.partial(
+ _calc_radial, horizontal=False
+ ),
("SPHERICAL", ""): _calc_lin_sphere,
}
@@ -987,31 +1187,39 @@ class MaterialConverter:
for y in range(height):
offset = (y * width * 4) + (x * 4)
value = func(x, y, width, height)
- pixels[offset:offset+4] = (value,) * 4
- image = bpy.data.images.new(filename, width=width, height=height, alpha=True)
+ pixels[offset : offset + 4] = (value,) * 4
+ image = bpy.data.images.new(
+ filename, width=width, height=height, alpha=True
+ )
image.pixels = pixels
- self.export_prepared_image(image=image, owner=owner, allowed_formats={"BMP"},
- alpha_type=TextureAlpha.full, indent=indent, ephemeral=True)
+ self.export_prepared_image(
+ image=image,
+ owner=owner,
+ allowed_formats={"BMP"},
+ alpha_type=TextureAlpha.full,
+ indent=indent,
+ ephemeral=True,
+ )
def export_prepared_image(self, **kwargs):
"""This exports an externally prepared image and an optional owning layer.
- The following arguments are typical:
- - texture: (co-required) the image texture datablock to export
- - image: (co-required) the image datablock to export
- - owner: (required) the Plasma object using this image
- - mipmap: (optional) should the image be mipmapped?
- - allowed_formats: (optional) set of string *hints* for desired image export type
- valid options: BMP, DDS, JPG, PNG
- - extension: (optional) file extension to use for the image object
- to use the image datablock extension, set this to None
- - indent: (optional) indentation level for log messages
- default: 2
- - ephemeral: (optional) never cache this image
- - tag: (optional) an optional identifier hint that allows multiple images with the
- same name to coexist in the cache
- - is_cube_map: (optional) indicates the provided image contains six cube faces
- that must be split into six separate images for Plasma
+ The following arguments are typical:
+ - texture: (co-required) the image texture datablock to export
+ - image: (co-required) the image datablock to export
+ - owner: (required) the Plasma object using this image
+ - mipmap: (optional) should the image be mipmapped?
+ - allowed_formats: (optional) set of string *hints* for desired image export type
+ valid options: BMP, DDS, JPG, PNG
+ - extension: (optional) file extension to use for the image object
+ to use the image datablock extension, set this to None
+ - indent: (optional) indentation level for log messages
+ default: 2
+ - ephemeral: (optional) never cache this image
+ - tag: (optional) an optional identifier hint that allows multiple images with the
+ same name to coexist in the cache
+ - is_cube_map: (optional) indicates the provided image contains six cube faces
+ that must be split into six separate images for Plasma
"""
owner = kwargs.pop("owner", None)
indent = kwargs.pop("indent", 2)
@@ -1019,8 +1227,12 @@ class MaterialConverter:
image = key.image
if key not in self._pending:
- self._report.msg("Stashing '{}' for conversion as '{}'", image.name, key, indent=indent)
- self._pending[key] = [owner.key,]
+ self._report.msg(
+ "Stashing '{}' for conversion as '{}'", image.name, key, indent=indent
+ )
+ self._pending[key] = [
+ owner.key,
+ ]
else:
self._report.msg("Found another user of '{}'", key, indent=indent)
self._pending[key].append(owner.key)
@@ -1059,14 +1271,30 @@ class MaterialConverter:
compression = plBitmap.kUncompressed
else:
raise RuntimeError(allowed_formats)
- dxt = plBitmap.kDXT5 if key.alpha_type == TextureAlpha.full else plBitmap.kDXT1
+ dxt = (
+ plBitmap.kDXT5
+ if key.alpha_type == TextureAlpha.full
+ else plBitmap.kDXT1
+ )
# Mayhaps we have a cached version of this that has already been exported
cached_image = texcache.get_from_texture(key, compression)
if cached_image is None:
- numLevels, width, height, data = self._finalize_cache(texcache, key, image, name, compression, dxt)
- self._finalize_bitmap(key, owners, name, numLevels, width, height, compression, dxt, data)
+ numLevels, width, height, data = self._finalize_cache(
+ texcache, key, image, name, compression, dxt
+ )
+ self._finalize_bitmap(
+ key,
+ owners,
+ name,
+ numLevels,
+ width,
+ height,
+ compression,
+ dxt,
+ data,
+ )
else:
width, height = cached_image.export_size
data = cached_image.image_data
@@ -1075,15 +1303,41 @@ class MaterialConverter:
# If the cached image data is junk, PyHSPlasma will raise a RuntimeError,
# so we'll attempt a recache...
try:
- self._finalize_bitmap(key, owners, name, numLevels, width, height, compression, dxt, data)
+ self._finalize_bitmap(
+ key,
+ owners,
+ name,
+ numLevels,
+ width,
+ height,
+ compression,
+ dxt,
+ data,
+ )
except RuntimeError:
- self._report.warn("Cached image is corrupted! Recaching image...", indent=1)
- numLevels, width, height, data = self._finalize_cache(texcache, key, image, name, compression, dxt)
- self._finalize_bitmap(key, owners, name, numLevels, width, height, compression, dxt, data)
+ self._report.warn(
+ "Cached image is corrupted! Recaching image...", indent=1
+ )
+ numLevels, width, height, data = self._finalize_cache(
+ texcache, key, image, name, compression, dxt
+ )
+ self._finalize_bitmap(
+ key,
+ owners,
+ name,
+ numLevels,
+ width,
+ height,
+ compression,
+ dxt,
+ data,
+ )
inc_progress()
- def _finalize_bitmap(self, key, owners, name, numLevels, width, height, compression, dxt, data):
+ def _finalize_bitmap(
+ self, key, owners, name, numLevels, width, height, compression, dxt, data
+ ):
mgr = self._mgr
# Now we poke our new bitmap into the pending layers. Note that we have to do some funny
@@ -1093,15 +1347,24 @@ class MaterialConverter:
self._report.msg("Adding to...", indent=1)
for owner_key in owners:
owner = owner_key.object
- self._report.msg("[{} '{}']", owner.ClassName()[2:], owner_key.name, indent=2)
- page = mgr.get_textures_page(owner_key) # Layer's page or Textures.prp
+ self._report.msg(
+ "[{} '{}']", owner.ClassName()[2:], owner_key.name, indent=2
+ )
+ page = mgr.get_textures_page(owner_key) # Layer's page or Textures.prp
# If we haven't created this texture in the page (either layer's page or Textures.prp),
# then we need to do that and stuff the level data. This is a little tedious, but we
# need to be careful to manage our resources correctly
if page not in pages:
- mipmap = plMipmap(name=name, width=width, height=height, numLevels=numLevels,
- compType=compression, format=plBitmap.kRGB8888, dxtLevel=dxt)
+ mipmap = plMipmap(
+ name=name,
+ width=width,
+ height=height,
+ numLevels=numLevels,
+ compType=compression,
+ format=plBitmap.kRGB8888,
+ dxtLevel=dxt,
+ )
if key.is_cube_map:
assert len(data) == 6
texture = plCubicEnvironmap(name)
@@ -1132,9 +1395,13 @@ class MaterialConverter:
def _finalize_cache(self, texcache, key, image, name, compression, dxt):
if key.is_cube_map:
- numLevels, width, height, data = self._finalize_cube_map(key, image, name, compression, dxt)
+ numLevels, width, height, data = self._finalize_cube_map(
+ key, image, name, compression, dxt
+ )
else:
- numLevels, width, height, data = self._finalize_single_image(key, image, name, compression, dxt)
+ numLevels, width, height, data = self._finalize_single_image(
+ key, image, name, compression, dxt
+ )
texcache.add_texture(key, numLevels, (width, height), compression, data)
return numLevels, width, height, data
@@ -1155,8 +1422,14 @@ class MaterialConverter:
# in the case of POT faces. So, we will scale the image AGAIN, if Blender did
# something funky.
if oWidth != cWidth or oHeight != cHeight:
- self._report.warn("Image was resized by Blender to ({}x{})--resizing the resize to ({}x{})",
- cWidth, cHeight, oWidth, oHeight, indent=1)
+ self._report.warn(
+ "Image was resized by Blender to ({}x{})--resizing the resize to ({}x{})",
+ cWidth,
+ cHeight,
+ oWidth,
+ oHeight,
+ indent=1,
+ )
data = scale_image(data, cWidth, cHeight, oWidth, oHeight)
# Face dimensions
@@ -1192,18 +1465,31 @@ class MaterialConverter:
name = face_name[:-4].upper()
if compression == plBitmap.kDirectXCompression:
numLevels = glimage.num_levels
- self._report.msg("Generating mip levels for cube face '{}'", name, indent=1)
+ self._report.msg(
+ "Generating mip levels for cube face '{}'", name, indent=1
+ )
# If we're compressing this mofo, we'll need a temporary mipmap to do that here...
- mipmap = plMipmap(name=name, width=eWidth, height=eHeight, numLevels=numLevels,
- compType=compression, format=plBitmap.kRGB8888, dxtLevel=dxt)
+ mipmap = plMipmap(
+ name=name,
+ width=eWidth,
+ height=eHeight,
+ numLevels=numLevels,
+ compType=compression,
+ format=plBitmap.kRGB8888,
+ dxtLevel=dxt,
+ )
else:
numLevels = 1
- self._report.msg("Compressing single level for cube face '{}'", name, indent=1)
+ self._report.msg(
+ "Compressing single level for cube face '{}'", name, indent=1
+ )
face_images[i] = [None] * numLevels
for j in range(numLevels):
- level_data = glimage.get_level_data(j, key.calc_alpha, report=self._report)
+ level_data = glimage.get_level_data(
+ j, key.calc_alpha, report=self._report
+ )
if compression == plBitmap.kDirectXCompression:
mipmap.CompressImage(j, level_data)
level_data = mipmap.getLevel(j)
@@ -1227,8 +1513,15 @@ class MaterialConverter:
# If this is a DXT-compressed mipmap, we need to use a temporary mipmap
# to do the compression. We'll then steal the data from it.
- mipmap = plMipmap(name=name, width=eWidth, height=eHeight, numLevels=numLevels,
- compType=compression, format=plBitmap.kRGB8888, dxtLevel=dxt)
+ mipmap = plMipmap(
+ name=name,
+ width=eWidth,
+ height=eHeight,
+ numLevels=numLevels,
+ compType=compression,
+ format=plBitmap.kRGB8888,
+ dxtLevel=dxt,
+ )
else:
numLevels = 1
self._report.msg("Compressing single level", indent=1)
@@ -1237,30 +1530,47 @@ class MaterialConverter:
# this mipmap for per-page textures :(
data = [None] * numLevels
for i in range(numLevels):
- level_data = glimage.get_level_data(i, key.calc_alpha, report=self._report)
+ level_data = glimage.get_level_data(
+ i, key.calc_alpha, report=self._report
+ )
if compression == plBitmap.kDirectXCompression:
mipmap.CompressImage(i, level_data)
level_data = mipmap.getLevel(i)
data[i] = level_data
- return numLevels, eWidth, eHeight, [data,]
-
- def get_materials(self, bo: bpy.types.Object, bm: Optional[bpy.types.Material] = None) -> Iterator[plKey]:
+ return (
+ numLevels,
+ eWidth,
+ eHeight,
+ [
+ data,
+ ],
+ )
+
+ def get_materials(
+ self, bo: bpy.types.Object, bm: Optional[bpy.types.Material] = None
+ ) -> Iterator[plKey]:
material_dict = self._obj2mat.get(bo, {})
if bm is None:
return material_dict.values()
else:
return material_dict.get(bm, [])
- def get_layers(self, bo: Optional[bpy.types.Object] = None,
- bm: Optional[bpy.types.Material] = None,
- tex: Optional[bpy.types.Texture] = None) -> Iterator[plKey]:
+ def get_layers(
+ self,
+ bo: Optional[bpy.types.Object] = None,
+ bm: Optional[bpy.types.Material] = None,
+ tex: Optional[bpy.types.Texture] = None,
+ ) -> Iterator[plKey]:
# All three? Simple.
if bo is not None and bm is not None and tex is not None:
yield from filter(None, self._obj2layer[bo][bm][tex])
return
if bo is None and bm is None and tex is None:
- self._exporter().report.warn("Asking for all the layers we've ever exported, eh? You like living dangerously.", indent=2)
+ self._exporter().report.warn(
+ "Asking for all the layers we've ever exported, eh? You like living dangerously.",
+ indent=2,
+ )
# What we want to do is filter _obj2layers:
# bo if set, or all objects
@@ -1272,7 +1582,12 @@ class MaterialConverter:
elif tex is not None:
material_seq = tex.users_material
else:
- _iter = filter(lambda x: x and x.material, itertools.chain.from_iterable((i.material_slots for i in object_iter())))
+ _iter = filter(
+ lambda x: x and x.material,
+ itertools.chain.from_iterable(
+ (i.material_slots for i in object_iter())
+ ),
+ )
# Performance turd: this could, in the worst case, block on creating a list of every material
# attached to every single Plasma Object in the current scene. This whole algorithm sucks,
# though, so whatever.
@@ -1281,7 +1596,11 @@ class MaterialConverter:
for filtered_obj in object_iter():
for filtered_mat in material_seq:
all_texes = self._obj2layer[filtered_obj][filtered_mat]
- filtered_texes = all_texes[tex] if tex is not None else itertools.chain.from_iterable(all_texes.values())
+ filtered_texes = (
+ all_texes[tex]
+ if tex is not None
+ else itertools.chain.from_iterable(all_texes.values())
+ )
yield from filter(None, filtered_texes)
def get_base_layer(self, hsgmat):
@@ -1295,20 +1614,23 @@ class MaterialConverter:
def get_bump_layer(self, bo):
return self._bump_mats.get(bo, None)
- def get_material_ambient(self, bo, bm, color: Union[None, mathutils.Color] = None) -> hsColorRGBA:
+ def get_material_ambient(
+ self, bo, bm, color: Union[None, mathutils.Color] = None
+ ) -> hsColorRGBA:
# Although Plasma calls this the ambient color, it is actually always used as the emissive color.
emit_scale = bm.emit * 0.5
if emit_scale > 0.0:
if color is None:
color = bm.diffuse_color
- return hsColorRGBA(color.r * emit_scale,
- color.g * emit_scale,
- color.b * emit_scale,
- 1.0)
+ return hsColorRGBA(
+ color.r * emit_scale, color.g * emit_scale, color.b * emit_scale, 1.0
+ )
else:
return utils.color(bpy.context.scene.world.ambient_color)
- def get_material_preshade(self, bo, bm, color: Union[None, mathutils.Color] = None) -> hsColorRGBA:
+ def get_material_preshade(
+ self, bo, bm, color: Union[None, mathutils.Color] = None
+ ) -> hsColorRGBA:
# This color is always used for shading. In all lighting equations, it represents the world
# ambient color. Anyway, if we have a manual (read: animated color), just dump that out.
if color is not None:
@@ -1320,7 +1642,10 @@ class MaterialConverter:
# we'll want black if it's ONLY runtime lighting (and white for lightmaps). Otherwise,
# just use the material color for now.
if self._exporter().mesh.is_nonpreshaded(bo, bm):
- if bo.plasma_modifiers.lightmap.bake_lightmap and not bo.plasma_modifiers.lighting.rt_lights:
+ if (
+ bo.plasma_modifiers.lightmap.bake_lightmap
+ and not bo.plasma_modifiers.lighting.rt_lights
+ ):
return hsColorRGBA.kWhite
elif not bo.plasma_modifiers.lighting.preshade:
return hsColorRGBA.kBlack
@@ -1328,7 +1653,9 @@ class MaterialConverter:
# Gulp
return utils.color(bm.diffuse_color)
- def get_material_runtime(self, bo, bm, color: Union[None, mathutils.Color] = None) -> hsColorRGBA:
+ def get_material_runtime(
+ self, bo, bm, color: Union[None, mathutils.Color] = None
+ ) -> hsColorRGBA:
# The layer runstime color has no effect if the lighting equation is kLiteVtxNonPreshaded,
# so return black to prevent animations from being exported.
if self._exporter().mesh.is_nonpreshaded(bo, bm):
@@ -1339,12 +1666,17 @@ class MaterialConverter:
color = bm.diffuse_color
return utils.color(color)
- def get_texture_animation_key(self, bo, bm, texture, anim_name: str) -> Iterator[plKey]:
+ def get_texture_animation_key(
+ self, bo, bm, texture, anim_name: str
+ ) -> Iterator[plKey]:
"""Finds the appropriate key for sending messages to an animated Texture"""
if not anim_name:
anim_name = "(Entire Animation)"
- for top_layer in filter(lambda x: isinstance(x.object, plLayerAnimationBase), self.get_layers(bo, bm, texture)):
+ for top_layer in filter(
+ lambda x: isinstance(x.object, plLayerAnimationBase),
+ self.get_layers(bo, bm, texture),
+ ):
base_layer = top_layer.object.bottomOfStack
needle = top_layer
while needle is not None:
@@ -1371,13 +1703,15 @@ class MaterialConverter:
is_waveset = bo.plasma_modifiers.water_basic.enabled
if bo.data.show_double_sided:
if is_waveset:
- self._report.warn("FORCING single sided--this is a waveset (are you insane?)")
+ self._report.warn(
+ "FORCING single sided--this is a waveset (are you insane?)"
+ )
else:
state.miscFlags |= hsGMatState.kMiscTwoSided
# Shade Flags
if not bm.use_mist:
- state.shadeFlags |= hsGMatState.kShadeNoFog # Dead in CWE
+ state.shadeFlags |= hsGMatState.kShadeNoFog # Dead in CWE
state.shadeFlags |= hsGMatState.kShadeReallyNoFog
if bm.use_shadeless:
@@ -1387,7 +1721,10 @@ class MaterialConverter:
# Lightmapped emissive layers seem to cause cascading render issues. Skip flagging it
# and just hope that the ambient color bump is good enough.
if bo.plasma_modifiers.lightmap.bake_lightmap:
- self._report.warn("A lightmapped and emissive material??? You like living dangerously...", indent=3)
+ self._report.warn(
+ "A lightmapped and emissive material??? You like living dangerously...",
+ indent=3,
+ )
else:
state.shadeFlags |= hsGMatState.kShadeEmissive
@@ -1402,9 +1739,14 @@ class MaterialConverter:
def requires_material_shading(self, bm: bpy.types.Material) -> bool:
"""Determines if this material requires the lighting equation we all know and love
- (kLiteMaterial) in order to display opacity and color animations."""
+ (kLiteMaterial) in order to display opacity and color animations."""
if bm.animation_data is not None and bm.animation_data.action is not None:
- if any((i.data_path == "diffuse_color" for i in bm.animation_data.action.fcurves)):
+ if any(
+ (
+ i.data_path == "diffuse_color"
+ for i in bm.animation_data.action.fcurves
+ )
+ ):
return True
for slot in filter(lambda x: x and x.use and x.texture, bm.texture_slots):
@@ -1416,7 +1758,12 @@ class MaterialConverter:
return True
if tex.animation_data is not None and tex.animation_data.action is not None:
- if any((i.data_path == "plasma_layer.opacity" for i in tex.animation_data.action.fcurves)):
+ if any(
+ (
+ i.data_path == "plasma_layer.opacity"
+ for i in tex.animation_data.action.fcurves
+ )
+ ):
return True
return False
diff --git a/korman/exporter/mesh.py b/korman/exporter/mesh.py
index 2c24c40..349ea65 100644
--- a/korman/exporter/mesh.py
+++ b/korman/exporter/mesh.py
@@ -32,6 +32,7 @@ _WARN_VERTS_PER_SPAN = 0x8000
_VERTEX_COLOR_LAYERS = {"col", "color", "colour"}
+
class _GeoSpan:
def __init__(self, bo, bm, geospan, pass_index=None):
self.geospan = geospan
@@ -42,7 +43,9 @@ class _GeoSpan:
"""Determines the color all vertex colors should be multipled by in this span."""
if self.geospan.props & plGeometrySpan.kDiffuseFoldedIn:
color = bm.diffuse_color
- base_layer = self.geospan.material.object.layers[0].object.bottomOfStack.object
+ base_layer = self.geospan.material.object.layers[
+ 0
+ ].object.bottomOfStack.object
return (color.r, color.b, color.g, base_layer.opacity)
if not bo.plasma_modifiers.lighting.preshade:
return (0.0, 0.0, 0.0, 0.0)
@@ -57,7 +60,7 @@ class _RenderLevel:
MAJOR_LATE = 8
_MAJOR_SHIFT = 28
- _MINOR_MASK = ((1 << _MAJOR_SHIFT) - 1)
+ _MINOR_MASK = (1 << _MAJOR_SHIFT) - 1
def __init__(self, bo, pass_index, blend_span=False):
if blend_span:
@@ -75,20 +78,24 @@ class _RenderLevel:
def _get_major(self):
return self.level >> self._MAJOR_SHIFT
+
def _set_major(self, value):
self.level = self._calc_level(value, self.minor)
+
major = property(_get_major, _set_major)
def _get_minor(self):
return self.level & self._MINOR_MASK
+
def _set_minor(self, value):
self.level = self._calc_level(self.major, value)
+
minor = property(_get_minor, _set_minor)
- def _calc_level(self, major : int, minor : int=0) -> int:
+ def _calc_level(self, major: int, minor: int = 0) -> int:
return ((major << self._MAJOR_SHIFT) & 0xFFFFFFFF) | minor
- def _determine_level(self, bo : bpy.types.Object, blend_span : bool) -> int:
+ def _determine_level(self, bo: bpy.types.Object, blend_span: bool) -> int:
mods = bo.plasma_modifiers
if mods.test_property("draw_framebuf"):
return self._calc_level(self.MAJOR_FRAMEBUF)
@@ -175,7 +182,11 @@ class _MeshManager:
ident = i.identifier
if ident == "rna_type":
continue
- props[ident] = getattr(bstruct, ident) if getattr(i, "array_length", 0) == 0 else tuple(getattr(bstruct, ident))
+ props[ident] = (
+ getattr(bstruct, ident)
+ if getattr(i, "array_length", 0) == 0
+ else tuple(getattr(bstruct, ident))
+ )
return props
def __enter__(self):
@@ -197,7 +208,7 @@ class _MeshManager:
if isinstance(i.data, mesh_type) and i.is_modified(scene, "RENDER"):
# Remember, storing actual pointers to the Blender objects can cause bad things to
# happen because Blender's memory management SUCKS!
- self._overrides[i.name] = { "mesh": i.data.name, "modifiers": [] }
+ self._overrides[i.name] = {"mesh": i.data.name, "modifiers": []}
i.data = i.to_mesh(scene, True, "RENDER", calc_tessface=False)
# If the modifiers are left on the object, the lightmap bake can break under some
@@ -223,11 +234,16 @@ class _MeshManager:
data_meshes.remove(trash_mesh)
# If modifiers were removed, reapply them now unless they're read-only.
- readonly_attributes = {("DECIMATE", "face_count"),}
+ readonly_attributes = {
+ ("DECIMATE", "face_count"),
+ }
for cached_mod in override["modifiers"]:
mod = bo.modifiers.new(cached_mod["name"], cached_mod["type"])
for key, value in cached_mod.items():
- if key in {"name", "type"} or (cached_mod["type"], key) in readonly_attributes:
+ if (
+ key in {"name", "type"}
+ or (cached_mod["type"], key) in readonly_attributes
+ ):
continue
setattr(mod, key, value)
self._entered = False
@@ -275,8 +291,13 @@ class MeshConverter(_MeshManager):
alpha_layer = self._find_vtx_alpha_layer(mesh.vertex_colors)
if alpha_layer is None:
return False
- alpha_loops = (alpha_layer[i.loop_start:i.loop_start+i.loop_total] for i in polygons)
- opaque = (sum(i.color) == len(i.color) for i in itertools.chain.from_iterable(alpha_loops))
+ alpha_loops = (
+ alpha_layer[i.loop_start : i.loop_start + i.loop_total] for i in polygons
+ )
+ opaque = (
+ sum(i.color) == len(i.color)
+ for i in itertools.chain.from_iterable(alpha_loops)
+ )
has_alpha = not all(opaque)
return has_alpha
@@ -341,7 +362,9 @@ class MeshConverter(_MeshManager):
geospan.props |= plGeometrySpan.kPropNoShadow
# Harvest lights
- permaLights, permaProjs = self._exporter().light.find_material_light_keys(bo, bm)
+ permaLights, permaProjs = self._exporter().light.find_material_light_keys(
+ bo, bm
+ )
for i in permaLights:
geospan.addPermaLight(i)
for i in permaProjs:
@@ -375,12 +398,16 @@ class MeshConverter(_MeshManager):
# Recall that materials is a mapping of exported materials to blender material indices.
# Therefore, geodata maps blender material indices to working geometry data.
# Maybe the logic is a bit inverted, but it keeps the inner loop simple.
- geodata = { idx: _GeoData(len(mesh.vertices)) for idx, _ in materials }
+ geodata = {idx: _GeoData(len(mesh.vertices)) for idx, _ in materials}
bumpmap = self.material.get_bump_layer(bo)
# Locate relevant vertex color layers now...
lm = bo.plasma_modifiers.lightmap
- color = None if lm.bake_lightmap else self._find_vtx_color_layer(mesh.tessface_vertex_colors)
+ color = (
+ None
+ if lm.bake_lightmap
+ else self._find_vtx_color_layer(mesh.tessface_vertex_colors)
+ )
alpha = self._find_vtx_alpha_layer(mesh.tessface_vertex_colors)
# Convert Blender faces into things we can stuff into libHSPlasma
@@ -400,7 +427,12 @@ class MeshConverter(_MeshManager):
# Unpack colors
if color is None:
- tessface_colors = ((1.0, 1.0, 1.0), (1.0, 1.0, 1.0), (1.0, 1.0, 1.0), (1.0, 1.0, 1.0))
+ tessface_colors = (
+ (1.0, 1.0, 1.0),
+ (1.0, 1.0, 1.0),
+ (1.0, 1.0, 1.0),
+ (1.0, 1.0, 1.0),
+ )
else:
src = color[i]
tessface_colors = (src.color1, src.color2, src.color3, src.color4)
@@ -411,31 +443,63 @@ class MeshConverter(_MeshManager):
else:
src = alpha[i]
# average color becomes the alpha value
- tessface_alphas = ((sum(src.color1) / 3), (sum(src.color2) / 3),
- (sum(src.color3) / 3), (sum(src.color4) / 3))
+ tessface_alphas = (
+ (sum(src.color1) / 3),
+ (sum(src.color2) / 3),
+ (sum(src.color3) / 3),
+ (sum(src.color4) / 3),
+ )
if bumpmap is not None:
gradPass = []
gradUVWs = []
if len(tessface.vertices) != 3:
- gradPass.append([tessface.vertices[0], tessface.vertices[1], tessface.vertices[2]])
- gradPass.append([tessface.vertices[0], tessface.vertices[2], tessface.vertices[3]])
- gradUVWs.append((tuple((uvw[0] for uvw in tessface_uvws)),
- tuple((uvw[1] for uvw in tessface_uvws)),
- tuple((uvw[2] for uvw in tessface_uvws))))
- gradUVWs.append((tuple((uvw[0] for uvw in tessface_uvws)),
- tuple((uvw[2] for uvw in tessface_uvws)),
- tuple((uvw[3] for uvw in tessface_uvws))))
+ gradPass.append(
+ [
+ tessface.vertices[0],
+ tessface.vertices[1],
+ tessface.vertices[2],
+ ]
+ )
+ gradPass.append(
+ [
+ tessface.vertices[0],
+ tessface.vertices[2],
+ tessface.vertices[3],
+ ]
+ )
+ gradUVWs.append(
+ (
+ tuple((uvw[0] for uvw in tessface_uvws)),
+ tuple((uvw[1] for uvw in tessface_uvws)),
+ tuple((uvw[2] for uvw in tessface_uvws)),
+ )
+ )
+ gradUVWs.append(
+ (
+ tuple((uvw[0] for uvw in tessface_uvws)),
+ tuple((uvw[2] for uvw in tessface_uvws)),
+ tuple((uvw[3] for uvw in tessface_uvws)),
+ )
+ )
else:
gradPass.append(tessface.vertices)
- gradUVWs.append((tuple((uvw[0] for uvw in tessface_uvws)),
- tuple((uvw[1] for uvw in tessface_uvws)),
- tuple((uvw[2] for uvw in tessface_uvws))))
+ gradUVWs.append(
+ (
+ tuple((uvw[0] for uvw in tessface_uvws)),
+ tuple((uvw[1] for uvw in tessface_uvws)),
+ tuple((uvw[2] for uvw in tessface_uvws)),
+ )
+ )
for p, vids in enumerate(gradPass):
- dPosDu += self._get_bump_gradient(bumpmap[1], gradUVWs[p], mesh, vids, bumpmap[0], 0)
- dPosDv += self._get_bump_gradient(bumpmap[1], gradUVWs[p], mesh, vids, bumpmap[0], 1)
+ dPosDu += self._get_bump_gradient(
+ bumpmap[1], gradUVWs[p], mesh, vids, bumpmap[0], 0
+ )
+ dPosDv += self._get_bump_gradient(
+ bumpmap[1], gradUVWs[p], mesh, vids, bumpmap[0], 1
+ )
dPosDv = -dPosDv
# Convert to per-material indices
@@ -444,14 +508,18 @@ class MeshConverter(_MeshManager):
# Calculate vertex colors.
if mat2span_LUT:
- mult_color = geospans[mat2span_LUT[tessface.material_index]].mult_color
+ mult_color = geospans[
+ mat2span_LUT[tessface.material_index]
+ ].mult_color
else:
mult_color = (1.0, 1.0, 1.0, 1.0)
tessface_color, tessface_alpha = tessface_colors[j], tessface_alphas[j]
- vertex_color = (int(tessface_color[0] * mult_color[0] * 255),
- int(tessface_color[1] * mult_color[1] * 255),
- int(tessface_color[2] * mult_color[2] * 255),
- int(tessface_alpha * mult_color[0] * 255))
+ vertex_color = (
+ int(tessface_color[0] * mult_color[0] * 255),
+ int(tessface_color[1] * mult_color[1] * 255),
+ int(tessface_color[2] * mult_color[2] * 255),
+ int(tessface_alpha * mult_color[0] * 255),
+ )
# Now, we'll index into the vertex dict using the per-face elements :(
# We're using tuples because lists are not hashable. The many mathutils and PyHSPlasma
@@ -467,7 +535,9 @@ class MeshConverter(_MeshManager):
normal = source.normal if use_smooth else tessface.normal
# MOUL/DX9 craps its pants if any element of the normal is exactly 0.0
- normal = map(lambda x: max(x, 0.01) if x >= 0.0 else min(x, -0.01), normal)
+ normal = map(
+ lambda x: max(x, 0.01) if x >= 0.0 else min(x, -0.01), normal
+ )
normal = hsVector3(*normal)
normal.normalize()
geoVertex.normal = normal
@@ -495,7 +565,7 @@ class MeshConverter(_MeshManager):
# PyHSPlasma now returns tuples to indicate this.
geoUVs = list(geoVertex.uvs)
geoUVs[num_user_uvs] += dPosDu
- geoUVs[num_user_uvs+1] += dPosDv
+ geoUVs[num_user_uvs + 1] += dPosDv
geoVertex.uvs = geoUVs
face_verts.append(data.blender2gs[vertex][coluv])
@@ -520,7 +590,9 @@ class MeshConverter(_MeshManager):
# TODO: consider busting up the mesh into multiple geospans?
# or hack plDrawableSpans::composeGeometry to do it for us?
if numVerts > _WARN_VERTS_PER_SPAN:
- raise explosions.TooManyVerticesError(bo.data.name, geospan.material.name, numVerts)
+ raise explosions.TooManyVerticesError(
+ bo.data.name, geospan.material.name, numVerts
+ )
# If we're bump mapping, we need to normalize our magic UVW channels
if bumpmap is not None:
@@ -534,7 +606,6 @@ class MeshConverter(_MeshManager):
geospan.indices = data.triangles
geospan.vertices = data.vertices
-
def _get_bump_gradient(self, xform, uvws, mesh, vIds, uvIdx, iUV):
v0 = hsVector3(*mesh.vertices[vIds[0]].co)
v1 = hsVector3(*mesh.vertices[vIds[1]].co)
@@ -576,11 +647,19 @@ class MeshConverter(_MeshManager):
def _enumerate_materials(self, bo, mesh):
material_source = mesh.materials
- valid_materials = set((tf.material_index for tf in mesh.tessfaces if material_source[tf.material_index] is not None))
+ valid_materials = set(
+ (
+ tf.material_index
+ for tf in mesh.tessfaces
+ if material_source[tf.material_index] is not None
+ )
+ )
# Sequence of tuples (material_index, material)
- return sorted(((i, material_source[i]) for i in valid_materials), key=lambda x: x[0])
+ return sorted(
+ ((i, material_source[i]) for i in valid_materials), key=lambda x: x[0]
+ )
- def export_object(self, bo, so : plSceneObject):
+ def export_object(self, bo, so: plSceneObject):
# If this object has modifiers, then it's a unique mesh, and we don't need to try caching it
# Otherwise, let's *try* to share meshes as best we can...
if bo.modifiers:
@@ -628,8 +707,12 @@ class MeshConverter(_MeshManager):
_diindices = {}
for i in geospans:
dspan = self._find_create_dspan(bo, i.geospan, i.pass_index)
- self._report.msg("Exported hsGMaterial '{}' geometry into '{}'",
- i.geospan.material.name, dspan.key.name, indent=1)
+ self._report.msg(
+ "Exported hsGMaterial '{}' geometry into '{}'",
+ i.geospan.material.name,
+ dspan.key.name,
+ indent=1,
+ )
idx = dspan.addSourceSpan(i.geospan)
diidx = _diindices.setdefault(dspan, [])
diidx.append(idx)
@@ -648,7 +731,9 @@ class MeshConverter(_MeshManager):
waveset_mod = bo.plasma_modifiers.water_basic
if waveset_mod.enabled:
if len(materials) > 1:
- msg = "'{}' is a WaveSet -- only one material is supported".format(bo.name)
+ msg = "'{}' is a WaveSet -- only one material is supported".format(
+ bo.name
+ )
self._exporter().report.warn(msg, indent=1)
blmat = materials[0][1]
self._check_vtx_nonpreshaded(bo, mesh, 0, blmat)
@@ -656,8 +741,12 @@ class MeshConverter(_MeshManager):
geospan = self._create_geospan(bo, mesh, None, blmat, matKey)
# FIXME: Can some of this be generalized?
- geospan.props |= (plGeometrySpan.kWaterHeight | plGeometrySpan.kLiteVtxNonPreshaded |
- plGeometrySpan.kPropReverseSort | plGeometrySpan.kPropNoShadow)
+ geospan.props |= (
+ plGeometrySpan.kWaterHeight
+ | plGeometrySpan.kLiteVtxNonPreshaded
+ | plGeometrySpan.kPropReverseSort
+ | plGeometrySpan.kPropNoShadow
+ )
geospan.waterHeight = bo.matrix_world.translation[2]
return [_GeoSpan(bo, blmat, geospan)], None
else:
@@ -666,9 +755,12 @@ class MeshConverter(_MeshManager):
for i, (blmat_idx, blmat) in enumerate(materials):
self._check_vtx_nonpreshaded(bo, mesh, blmat_idx, blmat)
matKey = self.material.export_material(bo, blmat)
- geospans[i] = _GeoSpan(bo, blmat,
- self._create_geospan(bo, mesh, blmat_idx, blmat, matKey),
- blmat.pass_index)
+ geospans[i] = _GeoSpan(
+ bo,
+ blmat,
+ self._create_geospan(bo, mesh, blmat_idx, blmat, matKey),
+ blmat.pass_index,
+ )
mat2span_LUT[blmat_idx] = i
return geospans, mat2span_LUT
@@ -689,7 +781,9 @@ class MeshConverter(_MeshManager):
# AgeName_[District_]_Page_RenderLevel_Crit[Blend]Spans
# Just because it's nice to be consistent
node = self._mgr.get_scene_node(location=location)
- name = "{}_{:08X}_{:X}{}".format(node.name, crit.render_level.level, crit.criteria, crit.span_type)
+ name = "{}_{:08X}_{:X}{}".format(
+ node.name, crit.render_level.level, crit.criteria, crit.span_type
+ )
dspan = self._mgr.add_object(pl=plDrawableSpans, name=name, loc=location)
criteria = crit.criteria
@@ -699,7 +793,7 @@ class MeshConverter(_MeshManager):
if criteria & plDrawable.kCritSortSpans:
dspan.props |= plDrawable.kPropSortSpans
dspan.renderLevel = crit.render_level.level
- dspan.sceneNode = node # AddViaNotify
+ dspan.sceneNode = node # AddViaNotify
self._dspans[location][crit] = dspan
return dspan
@@ -707,13 +801,18 @@ class MeshConverter(_MeshManager):
return self._dspans[location][crit]
def _find_vtx_alpha_layer(self, color_collection):
- alpha_layer = next((i for i in color_collection if i.name.lower() == "alpha"), None)
+ alpha_layer = next(
+ (i for i in color_collection if i.name.lower() == "alpha"), None
+ )
if alpha_layer is not None:
return alpha_layer.data
return None
def _find_vtx_color_layer(self, color_collection):
- manual_layer = next((i for i in color_collection if i.name.lower() in _VERTEX_COLOR_LAYERS), None)
+ manual_layer = next(
+ (i for i in color_collection if i.name.lower() in _VERTEX_COLOR_LAYERS),
+ None,
+ )
if manual_layer is not None:
return manual_layer.data
baked_layer = color_collection.get("autocolor")
diff --git a/korman/exporter/outfile.py b/korman/exporter/outfile.py
index d3ee7e6..7e012b8 100644
--- a/korman/exporter/outfile.py
+++ b/korman/exporter/outfile.py
@@ -31,6 +31,7 @@ import zipfile
_CHUNK_SIZE = 0xA00000
_encoding = locale.getpreferredencoding(False)
+
def _hashfile(filename, hasher, block=0xFFFF):
with open(str(filename), "rb") as handle:
h = hasher()
@@ -40,6 +41,7 @@ def _hashfile(filename, hasher, block=0xFFFF):
data = handle.read(block)
return h.digest()
+
@enum.unique
class _FileType(enum.Enum):
generated_dat = 0
@@ -58,6 +60,7 @@ _GATHER_BUILD = {
_FileType.video: "avi",
}
+
class _OutputFile:
def __init__(self, **kwargs):
self.file_type = kwargs.get("file_type")
@@ -71,7 +74,9 @@ class _OutputFile:
if self.file_type in (_FileType.generated_dat, _FileType.generated_ancillary):
self.file_data = kwargs.get("file_data", None)
self.file_path = kwargs.get("file_path", None)
- self.mod_time = Path(self.file_path).stat().st_mtime if self.file_path else None
+ self.mod_time = (
+ Path(self.file_path).stat().st_mtime if self.file_path else None
+ )
# need either a data buffer OR a file path
assert bool(self.file_data) ^ bool(self.file_path)
@@ -121,7 +126,9 @@ class _OutputFile:
with plEncryptedStream().open(backing_stream, fmCreate, enc) as enc_stream:
if self.file_path:
if plEncryptedStream.IsFileEncrypted(self.file_path):
- with plEncryptedStream().open(self.file_path, fmRead, plEncryptedStream.kEncAuto) as dec_stream:
+ with plEncryptedStream().open(
+ self.file_path, fmRead, plEncryptedStream.kEncAuto
+ ) as dec_stream:
self._enc_spin_wash(enc_stream, dec_stream)
else:
with hsFileStream().open(self.file_path, fmRead) as dec_stream:
@@ -188,45 +195,63 @@ class OutputFiles:
self._time = time.time()
def add_ancillary(self, filename, dirname="", text_id=None, str_data=None):
- of = _OutputFile(file_type=_FileType.generated_ancillary,
- dirname=dirname, filename=filename,
- id_data=text_id, file_data=str_data)
+ of = _OutputFile(
+ file_type=_FileType.generated_ancillary,
+ dirname=dirname,
+ filename=filename,
+ id_data=text_id,
+ file_data=str_data,
+ )
self._files.add(of)
def add_python_code(self, filename, text_id=None, str_data=None):
assert filename not in self._py_files
- of = _OutputFile(file_type=_FileType.python_code,
- dirname="Python", filename=filename,
- id_data=text_id, file_data=str_data,
- skip_hash=True,
- internal=(self._version != pvMoul),
- needs_glue=False)
+ of = _OutputFile(
+ file_type=_FileType.python_code,
+ dirname="Python",
+ filename=filename,
+ id_data=text_id,
+ file_data=str_data,
+ skip_hash=True,
+ internal=(self._version != pvMoul),
+ needs_glue=False,
+ )
self._files.add(of)
self._py_files.add(filename)
def add_python_mod(self, filename, text_id=None, str_data=None):
assert filename not in self._py_files
- of = _OutputFile(file_type=_FileType.python_code,
- dirname="Python", filename=filename,
- id_data=text_id, file_data=str_data,
- skip_hash=True,
- internal=(self._version != pvMoul),
- needs_glue=True)
+ of = _OutputFile(
+ file_type=_FileType.python_code,
+ dirname="Python",
+ filename=filename,
+ id_data=text_id,
+ file_data=str_data,
+ skip_hash=True,
+ internal=(self._version != pvMoul),
+ needs_glue=True,
+ )
self._files.add(of)
self._py_files.add(filename)
def add_sdl(self, filename, text_id=None, str_data=None):
- of = _OutputFile(file_type=_FileType.sdl,
- dirname="SDL", filename=filename,
- id_data=text_id, file_data=str_data,
- enc=self.super_secure_encryption)
+ of = _OutputFile(
+ file_type=_FileType.sdl,
+ dirname="SDL",
+ filename=filename,
+ id_data=text_id,
+ file_data=str_data,
+ enc=self.super_secure_encryption,
+ )
self._files.add(of)
-
def add_sfx(self, sound_id):
- of = _OutputFile(file_type=_FileType.sfx,
- dirname="sfx", filename=sound_id.name,
- id_data=sound_id)
+ of = _OutputFile(
+ file_type=_FileType.sfx,
+ dirname="sfx",
+ filename=sound_id.name,
+ id_data=sound_id,
+ )
self._files.add(of)
@contextmanager
@@ -243,7 +268,7 @@ class OutputFiles:
else:
file_path = self._export_path.joinpath(dirname, filename)
file_path.parent.mkdir(parents=True, exist_ok=True)
- file_path = str(file_path) # FIXME when we bump to Python 3.6+
+ file_path = str(file_path) # FIXME when we bump to Python 3.6+
stream = hsFileStream(self._version)
stream.open(file_path, fmCreate)
backing_stream = stream
@@ -274,8 +299,9 @@ class OutputFiles:
# instead of doing lots of buffer copying to encrypt as a post step.
if not bogus:
kwargs = {
- "file_type": _FileType.generated_dat if dirname == "dat" else
- _FileType.generated_ancillary,
+ "file_type": _FileType.generated_dat
+ if dirname == "dat"
+ else _FileType.generated_ancillary,
"dirname": dirname,
"filename": filename,
"skip_hash": kwargs.get("skip_hash", False),
@@ -318,15 +344,24 @@ class OutputFiles:
py_code = "{}\n\n{}\n".format(i.file_data, plasma_python_glue)
else:
py_code = i.file_data
- result, pyc = korlib.compyle(i.filename, py_code, py_version, report, indent=1)
+ result, pyc = korlib.compyle(
+ i.filename, py_code, py_version, report, indent=1
+ )
if result:
pyc_objects.append((i.filename, pyc))
except korlib.PythonNotAvailableError as error:
- report.warn("Python {} is not available. Your Age scripts were not packaged.", error, indent=1)
+ report.warn(
+ "Python {} is not available. Your Age scripts were not packaged.",
+ error,
+ indent=1,
+ )
else:
if pyc_objects:
- with self.generate_dat_file("{}.pak".format(self._exporter().age_name),
- dirname="Python", enc=self.super_secure_encryption) as stream:
+ with self.generate_dat_file(
+ "{}.pak".format(self._exporter().age_name),
+ dirname="Python",
+ enc=self.super_secure_encryption,
+ ) as stream:
korlib.package_python(stream, pyc_objects)
def save(self):
@@ -376,7 +411,10 @@ class OutputFiles:
def _write_deps(self):
times = (self._time, self._time)
- func = lambda x: not x.internal and x.file_type not in (_FileType.generated_ancillary, _FileType.generated_dat)
+ func = lambda x: not x.internal and x.file_type not in (
+ _FileType.generated_ancillary,
+ _FileType.generated_dat,
+ )
report = self._exporter().report
for i in self._generate_files(func):
@@ -391,8 +429,11 @@ class OutputFiles:
if i.file_path != dst_path:
shutil.copy2(i.file_path, dst_path)
else:
- report.warn("No data found for dependency file '{}'. It will not be copied into the export directory.",
- str(i.dirname / i.filename), indent=1)
+ report.warn(
+ "No data found for dependency file '{}'. It will not be copied into the export directory.",
+ str(i.dirname / i.filename),
+ indent=1,
+ )
def _write_gather_build(self):
report = self._exporter().report
@@ -400,17 +441,26 @@ class OutputFiles:
for i in self._generate_files():
key = _GATHER_BUILD.get(i.file_type)
if key is None:
- report.warn("Output file '{}' of type '{}' is not supported by MOULa's GatherBuild format.",
- i.file_type, i.filename)
+ report.warn(
+ "Output file '{}' of type '{}' is not supported by MOULa's GatherBuild format.",
+ i.file_type,
+ i.filename,
+ )
else:
path_str = str(PureWindowsPath(i.dirname, i.filename))
files.setdefault(key, []).append(path_str)
- self.add_ancillary("contents.json", str_data=json.dumps(files, ensure_ascii=False, indent=2))
+ self.add_ancillary(
+ "contents.json", str_data=json.dumps(files, ensure_ascii=False, indent=2)
+ )
def _write_sumfile(self):
version = self._version
dat_only = self._exporter().dat_only
- enc = plEncryptedStream.kEncAes if version >= pvEoa else plEncryptedStream.kEncXtea
+ enc = (
+ plEncryptedStream.kEncAes
+ if version >= pvEoa
+ else plEncryptedStream.kEncXtea
+ )
filename = "{}.sum".format(self._exporter().age_name)
if dat_only:
func = lambda x: (not x.skip_hash and not x.internal) and x.dirname == "dat"
@@ -445,7 +495,7 @@ class OutputFiles:
func = lambda x: not x.internal
report = self._exporter().report
- with zipfile.ZipFile(str(self._export_file), 'w', zipfile.ZIP_DEFLATED) as zf:
+ with zipfile.ZipFile(str(self._export_file), "w", zipfile.ZIP_DEFLATED) as zf:
for i in self._generate_files(func):
arcpath = i.filename if dat_only else str(Path(i.dirname, i.filename))
if i.file_data:
@@ -458,7 +508,11 @@ class OutputFiles:
elif i.file_path:
zf.write(i.file_path, arcpath)
else:
- report.warn("No data found for dependency file '{}'. It will not be archived.", arcpath, indent=1)
+ report.warn(
+ "No data found for dependency file '{}'. It will not be archived.",
+ arcpath,
+ indent=1,
+ )
@property
def _version(self):
diff --git a/korman/exporter/physics.py b/korman/exporter/physics.py
index 0a6a0e9..3112204 100644
--- a/korman/exporter/physics.py
+++ b/korman/exporter/physics.py
@@ -24,11 +24,13 @@ from .explosions import ExportError, ExportAssertionError
from ..helpers import bmesh_from_object, TemporaryObject
from . import utils
+
def _set_phys_prop(prop, sim, phys, value=True):
"""Sets properties on plGenericPhysical and plSimulationInterface (seeing as how they are duped)"""
sim.setProperty(prop, value)
phys.setProperty(prop, value)
+
class PhysicsConverter:
def __init__(self, exporter):
self._exporter = weakref.ref(exporter)
@@ -56,8 +58,16 @@ class PhysicsConverter:
if len(v) == 3:
indices += v
elif len(v) == 4:
- indices += (v[0], v[1], v[2],)
- indices += (v[0], v[2], v[3],)
+ indices += (
+ v[0],
+ v[1],
+ v[2],
+ )
+ indices += (
+ v[0],
+ v[2],
+ v[3],
+ )
return indices
def _convert_mesh_data(self, bo, physical, local_space, mat, indices=True):
@@ -75,7 +85,10 @@ class PhysicsConverter:
vertices = [hsVector3(*i.co) for i in mesh.vertices]
else:
# Dagnabbit...
- vertices = [hsVector3(i.co.x * scale.x, i.co.y * scale.y, i.co.z * scale.z) for i in mesh.vertices]
+ vertices = [
+ hsVector3(i.co.x * scale.x, i.co.y * scale.y, i.co.z * scale.z)
+ for i in mesh.vertices
+ ]
else:
# apply the transform to the physical itself
utils.transform_mesh(mesh, mat)
@@ -114,7 +127,9 @@ class PhysicsConverter:
vertices = [hsVector3(*i.co) for i in mesh.vertices]
else:
# Flatten out all points to the given Z-coordinate
- vertices = [hsVector3(i.co.x, i.co.y, z_coord) for i in mesh.vertices]
+ vertices = [
+ hsVector3(i.co.x, i.co.y, z_coord) for i in mesh.vertices
+ ]
physical.verts = vertices
physical.indices = self._convert_indices(mesh)
physical.boundsType = plSimDefs.kProxyBounds
@@ -126,23 +141,28 @@ class PhysicsConverter:
simIface = so.sim.object
physical = simIface.physical.object
- member_group = getattr(plSimDefs, kwargs.get("member_group", "kGroupLOSOnly"))
- if physical.memberGroup != member_group and member_group != plSimDefs.kGroupLOSOnly:
+ member_group = getattr(
+ plSimDefs, kwargs.get("member_group", "kGroupLOSOnly")
+ )
+ if (
+ physical.memberGroup != member_group
+ and member_group != plSimDefs.kGroupLOSOnly
+ ):
self._report.warn("{}: Physical memberGroup overwritten!", bo.name)
physical.memberGroup = member_group
self._apply_props(simIface, physical, kwargs)
def generate_physical(self, bo, so, **kwargs):
"""Generates a physical object for the given object pair.
- The following optional arguments are allowed:
- - bounds: (defaults to collision modifier setting)
- - member_group: str attribute of plSimDefs, defaults to kGroupStatic
- NOTE that kGroupLOSOnly generation will only succeed if no one else
- has generated this physical in another group
- - properties: sequence of str bit names from plSimulationInterface
- - losdbs: sequence of str bit names from plSimDefs
- - report_groups: sequence of str bit names from plSimDefs
- - collide_groups: sequence of str bit names from plSimDefs
+ The following optional arguments are allowed:
+ - bounds: (defaults to collision modifier setting)
+ - member_group: str attribute of plSimDefs, defaults to kGroupStatic
+ NOTE that kGroupLOSOnly generation will only succeed if no one else
+ has generated this physical in another group
+ - properties: sequence of str bit names from plSimulationInterface
+ - losdbs: sequence of str bit names from plSimDefs
+ - report_groups: sequence of str bit names from plSimDefs
+ - collide_groups: sequence of str bit names from plSimDefs
"""
if so.sim is None:
simIface = self._mgr.add_object(pl=plSimulationInterface, bl=bo)
@@ -167,12 +187,17 @@ class PhysicsConverter:
if mod.dynamic:
if ver <= pvPots:
- physical.collideGroup = (1 << plSimDefs.kGroupDynamic) | \
- (1 << plSimDefs.kGroupStatic)
+ physical.collideGroup = (1 << plSimDefs.kGroupDynamic) | (
+ 1 << plSimDefs.kGroupStatic
+ )
physical.memberGroup = plSimDefs.kGroupDynamic
physical.mass = mod.mass
- _set_phys_prop(plSimulationInterface.kStartInactive, simIface, physical,
- value=mod.start_asleep)
+ _set_phys_prop(
+ plSimulationInterface.kStartInactive,
+ simIface,
+ physical,
+ value=mod.start_asleep,
+ )
elif not mod.avatar_blocker:
physical.memberGroup = plSimDefs.kGroupLOSOnly
else:
@@ -181,7 +206,9 @@ class PhysicsConverter:
# Line of Sight DB
if mod.camera_blocker:
physical.LOSDBs |= plSimDefs.kLOSDBCameraBlockers
- _set_phys_prop(plSimulationInterface.kCameraAvoidObject, simIface, physical)
+ _set_phys_prop(
+ plSimulationInterface.kCameraAvoidObject, simIface, physical
+ )
if mod.terrain:
physical.LOSDBs |= plSimDefs.kLOSDBAvatarWalkable
@@ -189,7 +216,11 @@ class PhysicsConverter:
# This could result in a few orphaned PhysicalSndGroups, but I think that's preferable
# to having a bunch of empty objects...?
if mod.surface != "kNone":
- sndgroup = self._mgr.find_create_object(plPhysicalSndGroup, so=so, name="SURFACEGEN_{}".format(mod.surface))
+ sndgroup = self._mgr.find_create_object(
+ plPhysicalSndGroup,
+ so=so,
+ name="SURFACEGEN_{}".format(mod.surface),
+ )
sndgroup.group = getattr(plPhysicalSndGroup, mod.surface)
physical.soundGroup = sndgroup.key
else:
@@ -202,7 +233,9 @@ class PhysicsConverter:
# would miss cases where we have animated detectors (subworlds!!!)
def _iter_object_tree(bo, stop_at_subworld):
while bo is not None:
- if stop_at_subworld and self.is_dedicated_subworld(bo, sanity_check=False):
+ if stop_at_subworld and self.is_dedicated_subworld(
+ bo, sanity_check=False
+ ):
return
yield bo
bo = bo.parent
@@ -229,7 +262,9 @@ class PhysicsConverter:
# Any physical that is parented by not kickable (dynamic) is passive -
# meaning we don't need to report back any changes from physics. Same for
# plFilterCoordInterface, which filters out some axes.
- if (bo.parent is not None and not mod.dynamic) or bo.plasma_object.ci_type == plFilterCoordInterface:
+ if (
+ bo.parent is not None and not mod.dynamic
+ ) or bo.plasma_object.ci_type == plFilterCoordInterface:
_set_phys_prop(plSimulationInterface.kPassive, simIface, physical)
# If the mass is zero, then we will fail to animate. Fix that.
@@ -246,7 +281,11 @@ class PhysicsConverter:
elif ver == pvMoul:
if self._exporter().has_coordiface(bo):
local_space = True
- mat = subworld.matrix_world.inverted() * bo.matrix_world if subworld else bo.matrix_world
+ mat = (
+ subworld.matrix_world.inverted() * bo.matrix_world
+ if subworld
+ else bo.matrix_world
+ )
else:
local_space, mat = False, bo.matrix_world
else:
@@ -256,9 +295,16 @@ class PhysicsConverter:
simIface = so.sim.object
physical = simIface.physical.object
- member_group = getattr(plSimDefs, kwargs.get("member_group", "kGroupLOSOnly"))
- if physical.memberGroup != member_group and member_group != plSimDefs.kGroupLOSOnly:
- self._report.warn("{}: Physical memberGroup overwritten!", bo.name, indent=2)
+ member_group = getattr(
+ plSimDefs, kwargs.get("member_group", "kGroupLOSOnly")
+ )
+ if (
+ physical.memberGroup != member_group
+ and member_group != plSimDefs.kGroupLOSOnly
+ ):
+ self._report.warn(
+ "{}: Physical memberGroup overwritten!", bo.name, indent=2
+ )
physical.memberGroup = member_group
self._apply_props(simIface, physical, kwargs)
@@ -267,7 +313,9 @@ class PhysicsConverter:
"""Exports box bounds based on the object"""
physical.boundsType = plSimDefs.kBoxBounds
- vertices = self._convert_mesh_data(bo, physical, local_space, mat, indices=False)
+ vertices = self._convert_mesh_data(
+ bo, physical, local_space, mat, indices=False
+ )
physical.calcBoxBounds(vertices)
def _export_hull(self, bo, physical, local_space, mat):
@@ -285,7 +333,9 @@ class PhysicsConverter:
else:
mesh.transform(mat)
- result = bmesh.ops.convex_hull(mesh, input=mesh.verts, use_existing_faces=False)
+ result = bmesh.ops.convex_hull(
+ mesh, input=mesh.verts, use_existing_faces=False
+ )
BMVert = bmesh.types.BMVert
verts = itertools.takewhile(lambda x: isinstance(x, BMVert), result["geom"])
physical.verts = [hsVector3(*i.co) for i in verts]
@@ -294,7 +344,9 @@ class PhysicsConverter:
"""Exports sphere bounds based on the object"""
physical.boundsType = plSimDefs.kSphereBounds
- vertices = self._convert_mesh_data(bo, physical, local_space, mat, indices=False)
+ vertices = self._convert_mesh_data(
+ bo, physical, local_space, mat, indices=False
+ )
physical.calcSphereBounds(vertices)
def _export_trimesh(self, bo, physical, local_space, mat):
@@ -304,7 +356,9 @@ class PhysicsConverter:
mod = bo.plasma_modifiers.collision
if mod.enabled and mod.proxy_object is not None:
physical.boundsType = plSimDefs.kProxyBounds
- vertices, indices = self._convert_mesh_data(mod.proxy_object, physical, local_space, mat)
+ vertices, indices = self._convert_mesh_data(
+ mod.proxy_object, physical, local_space, mat
+ )
else:
physical.boundsType = plSimDefs.kExplicitBounds
vertices, indices = self._convert_mesh_data(bo, physical, local_space, mat)
diff --git a/korman/exporter/python.py b/korman/exporter/python.py
index e397d95..17901e7 100644
--- a/korman/exporter/python.py
+++ b/korman/exporter/python.py
@@ -22,6 +22,7 @@ from . import logger
from .. import korlib
from ..plasma_magic import plasma_python_glue, very_very_special_python
+
class PythonPackageExporter:
def __init__(self, filepath, version):
self._filepath = filepath
@@ -52,9 +53,13 @@ class PythonPackageExporter:
code = source
code = "{}\n\n{}\n".format(code, plasma_python_glue)
- success, result = korlib.compyle(filename, code, py_version, report, indent=1)
+ success, result = korlib.compyle(
+ filename, code, py_version, report, indent=1
+ )
if not success:
- raise ExportError("Failed to compyle '{}':\n{}".format(filename, result))
+ raise ExportError(
+ "Failed to compyle '{}':\n{}".format(filename, result)
+ )
py_code.append((filename, result))
inc_progress()
@@ -68,9 +73,13 @@ class PythonPackageExporter:
code = source
# no glue needed here, ma!
- success, result = korlib.compyle(filename, code, py_version, report, indent=1)
+ success, result = korlib.compyle(
+ filename, code, py_version, report, indent=1
+ )
if not success:
- raise ExportError("Failed to compyle '{}':\n{}".format(filename, result))
+ raise ExportError(
+ "Failed to compyle '{}':\n{}".format(filename, result)
+ )
py_code.append((filename, result))
inc_progress()
@@ -88,12 +97,19 @@ class PythonPackageExporter:
if age_py.plasma_text.package or age.python_method == "all":
self._pfms[py_filename] = age_py
else:
- report.warn("AgeSDL Python Script provided, but not requested for packing... Using default Python.", indent=1)
- self._pfms[py_filename] = very_very_special_python.format(age_name=fixed_agename)
+ report.warn(
+ "AgeSDL Python Script provided, but not requested for packing... Using default Python.",
+ indent=1,
+ )
+ self._pfms[py_filename] = very_very_special_python.format(
+ age_name=fixed_agename
+ )
else:
report.msg("Packing default AgeSDL Python", indent=1)
very_very_special_python.format(age_name=age_props.age_name)
- self._pfms[py_filename] = very_very_special_python.format(age_name=fixed_agename)
+ self._pfms[py_filename] = very_very_special_python.format(
+ age_name=fixed_agename
+ )
def _harvest_pfms(self, report):
objects = bpy.context.scene.objects
@@ -131,8 +147,14 @@ class PythonPackageExporter:
def run(self):
"""Runs a stripped-down version of the Exporter that only handles Python files"""
age_props = bpy.context.scene.world.plasma_age
- log = logger.ExportVerboseLogger if age_props.verbose else logger.ExportProgressLogger
- with korlib.ConsoleToggler(age_props.show_console), log(self._filepath) as report:
+ log = (
+ logger.ExportVerboseLogger
+ if age_props.verbose
+ else logger.ExportProgressLogger
+ )
+ with korlib.ConsoleToggler(age_props.show_console), log(
+ self._filepath
+ ) as report:
report.progress_add_step("Harvesting Plasma PythonFileMods")
report.progress_add_step("Harvesting Helper Python Modules")
report.progress_add_step("Compyling Python Code")
@@ -170,7 +192,9 @@ class PythonPackageExporter:
if enc is None:
stream = hsFileStream(self._version).open(self._filepath, fmCreate)
else:
- stream = plEncryptedStream(self._version).open(self._filepath, fmCreate, enc)
+ stream = plEncryptedStream(self._version).open(
+ self._filepath, fmCreate, enc
+ )
try:
korlib.package_python(stream, py_code)
finally:
diff --git a/korman/exporter/rtlight.py b/korman/exporter/rtlight.py
index ca97a77..d3faf50 100644
--- a/korman/exporter/rtlight.py
+++ b/korman/exporter/rtlight.py
@@ -36,6 +36,7 @@ SHADOW_RESOLUTION = {
"HIGH": 512,
}
+
class LightConverter:
def __init__(self, exporter):
self._exporter = weakref.ref(exporter)
@@ -101,7 +102,7 @@ class LightConverter:
pl.spotOuter = spot_size
blend = max(0.001, bl.spot_blend)
- pl.spotInner = spot_size - (blend*spot_size)
+ pl.spotInner = spot_size - (blend * spot_size)
if bl.use_halo:
pl.falloff = bl.halo_intensity
@@ -182,7 +183,9 @@ class LightConverter:
sv_bo = rtlamp.lamp_region
sv_mod = sv_bo.plasma_modifiers.softvolume
if not sv_mod.enabled:
- raise ExportError("'{}': '{}' is not a SoftVolume".format(bo.name, sv_bo.name))
+ raise ExportError(
+ "'{}': '{}' is not a SoftVolume".format(bo.name, sv_bo.name)
+ )
sv_key = sv_mod.get_key(self._exporter())
pl_light.softVolume = sv_key
@@ -207,7 +210,12 @@ class LightConverter:
# projection Lamp with our own faux Material. Unfortunately, Plasma only supports projecting
# one layer. We could exploit the fUnderLay and fOverLay system to export everything, but meh.
if len(tex_slots) > 1:
- self._report.warn("Only one texture slot can be exported per Lamp. Picking the first one: '{}'".format(slot.name), indent=3)
+ self._report.warn(
+ "Only one texture slot can be exported per Lamp. Picking the first one: '{}'".format(
+ slot.name
+ ),
+ indent=3,
+ )
layer = mat.export_texture_slot(bo, None, None, slot, 0, blend_flags=False)
state = layer.state
@@ -230,13 +238,21 @@ class LightConverter:
pl_light.setProperty(plLightInfo.kLPOverAll, True)
elif slot.blend_type == "MULTIPLY":
# From PlasmaMAX
- state.blendFlags |= hsGMatState.kBlendMult | hsGMatState.kBlendInvertColor | hsGMatState.kBlendInvertFinalColor
+ state.blendFlags |= (
+ hsGMatState.kBlendMult
+ | hsGMatState.kBlendInvertColor
+ | hsGMatState.kBlendInvertFinalColor
+ )
pl_light.setProperty(plLightInfo.kLPOverAll, True)
pl_light.projection = layer.key
def _export_shadow_master(self, bo, rtlamp, pl_light):
- pClass = plDirectShadowMaster if isinstance(pl_light, plDirectionalLightInfo) else plPointShadowMaster
+ pClass = (
+ plDirectShadowMaster
+ if isinstance(pl_light, plDirectionalLightInfo)
+ else plPointShadowMaster
+ )
shadow = self.mgr.find_create_object(pClass, bl=bo)
shadow.attenDist = rtlamp.shadow_falloff
@@ -249,7 +265,7 @@ class LightConverter:
def find_material_light_keys(self, bo, bm):
"""Given a blender material, we find the keys of all matching Plasma RT Lights.
- NOTE: We return a tuple of lists: ([permaLights], [permaProjs])"""
+ NOTE: We return a tuple of lists: ([permaLights], [permaProjs])"""
self._report.msg("Searching for runtime lights...", indent=1)
permaLights = []
permaProjs = []
@@ -279,21 +295,31 @@ class LightConverter:
break
else:
# didn't find a layer where both lamp and object were, skip it.
- self._report.msg("[{}] '{}': not in same layer, skipping...",
- lamp.type, obj.name, indent=2)
+ self._report.msg(
+ "[{}] '{}': not in same layer, skipping...",
+ lamp.type,
+ obj.name,
+ indent=2,
+ )
continue
# This is probably where PermaLight vs PermaProj should be sorted out...
pl_light = self.get_light_key(obj, lamp, None)
if self._is_projection_lamp(lamp):
- self._report.msg("[{}] PermaProj '{}'", lamp.type, obj.name, indent=2)
+ self._report.msg(
+ "[{}] PermaProj '{}'", lamp.type, obj.name, indent=2
+ )
permaProjs.append(pl_light)
else:
- self._report.msg("[{}] PermaLight '{}'", lamp.type, obj.name, indent=2)
+ self._report.msg(
+ "[{}] PermaLight '{}'", lamp.type, obj.name, indent=2
+ )
permaLights.append(pl_light)
if len(permaLights) > 8:
- self._report.warn("More than 8 RT lamps on material: '{}'", bm.name, indent=1)
+ self._report.warn(
+ "More than 8 RT lamps on material: '{}'", bm.name, indent=1
+ )
return (permaLights, permaProjs)
@@ -302,7 +328,9 @@ class LightConverter:
xlate = _BL2PL[bl_light.type]
return self.mgr.find_create_key(xlate, bl=bo, so=so)
except LookupError:
- raise BlenderOptionNotSupportedError("Object ('{}') lamp type '{}'".format(bo.name, bl_light.type))
+ raise BlenderOptionNotSupportedError(
+ "Object ('{}') lamp type '{}'".format(bo.name, bl_light.type)
+ )
def get_projectors(self, bl_light):
for tex in bl_light.texture_slots:
diff --git a/korman/exporter/utils.py b/korman/exporter/utils.py
index 296228a..6a9c0b5 100644
--- a/korman/exporter/utils.py
+++ b/korman/exporter/utils.py
@@ -22,6 +22,7 @@ from contextlib import contextmanager
from PyHSPlasma import *
+
def affine_parts(xform):
# Decompose the matrix into the 90s-era 3ds max affine parts sillyness
# All that's missing now is something like "(c) 1998 HeadSpin" oh wait...
@@ -35,10 +36,12 @@ def affine_parts(xform):
affine.U = quaternion(rot)
return affine
+
def color(blcolor, alpha=1.0):
"""Converts a Blender Color into an hsColorRGBA"""
return hsColorRGBA(blcolor.r, blcolor.g, blcolor.b, alpha)
+
def matrix44(blmat):
"""Converts a mathutils.Matrix to an hsMatrix44"""
hsmat = hsMatrix44()
@@ -49,15 +52,16 @@ def matrix44(blmat):
hsmat[i, 3] = blmat[i][3]
return hsmat
+
def quaternion(blquat):
"""Converts a mathutils.Quaternion to an hsQuat"""
return hsQuat(blquat.x, blquat.y, blquat.z, blquat.w)
@contextmanager
-def bmesh_temporary_object(name : str, factory : Callable, page_name : str=None):
+def bmesh_temporary_object(name: str, factory: Callable, page_name: str = None):
"""Creates a temporary object and mesh that exists only for the duration of
- the context"""
+ the context"""
mesh = bpy.data.meshes.new(name)
obj = bpy.data.objects.new(name, mesh)
obj.draw_type = "WIRE"
@@ -74,10 +78,11 @@ def bmesh_temporary_object(name : str, factory : Callable, page_name : str=None)
bm.free()
bpy.context.scene.objects.unlink(obj)
+
@contextmanager
def bmesh_object(name: str) -> Iterator[Tuple[bpy.types.Object, bmesh.types.BMesh]]:
"""Creates an object and mesh that will be removed if the context is exited
- due to an error"""
+ due to an error"""
mesh = bpy.data.meshes.new(name)
obj = bpy.data.objects.new(name, mesh)
obj.draw_type = "WIRE"
@@ -95,13 +100,16 @@ def bmesh_object(name: str) -> Iterator[Tuple[bpy.types.Object, bmesh.types.BMes
finally:
bm.free()
+
@contextmanager
-def temporary_mesh_object(source : bpy.types.Object) -> bpy.types.Object:
+def temporary_mesh_object(source: bpy.types.Object) -> bpy.types.Object:
"""Creates a temporary mesh object from a nonmesh object that will only exist for the duration
- of the context."""
+ of the context."""
assert source.type != "MESH"
- obj = bpy.data.objects.new(source.name, source.to_mesh(bpy.context.scene, True, "RENDER"))
+ obj = bpy.data.objects.new(
+ source.name, source.to_mesh(bpy.context.scene, True, "RENDER")
+ )
obj.draw_type = "WIRE"
obj.parent = source.parent
obj.matrix_local, obj.matrix_world = source.matrix_local, source.matrix_world
@@ -112,6 +120,7 @@ def temporary_mesh_object(source : bpy.types.Object) -> bpy.types.Object:
finally:
bpy.data.objects.remove(obj)
+
def transform_mesh(mesh: bpy.types.Mesh, matrix: mathutils.Matrix):
# There is a disparity in terms of how negative scaling is displayed in Blender versus how it is
# applied (Ctrl+A) in that the normals are different. Even though negative scaling is evil, we
diff --git a/korman/helpers.py b/korman/helpers.py
index 31c1d20..c9ea7c8 100644
--- a/korman/helpers.py
+++ b/korman/helpers.py
@@ -18,6 +18,7 @@ import bpy
from contextlib import contextmanager
import math
+
@contextmanager
def bmesh_from_object(bl):
"""Converts a Blender Object to a BMesh with modifiers applied."""
@@ -29,6 +30,7 @@ def bmesh_from_object(bl):
finally:
mesh.free()
+
class GoodNeighbor:
"""Leave Things the Way You Found Them! (TM)"""
@@ -63,6 +65,7 @@ class TemporaryObject:
class UiHelper:
"""This fun little helper makes sure that we don't wreck the UI"""
+
def __init__(self, context):
self.active_object = context.active_object
self.selected_objects = context.selected_objects
@@ -82,7 +85,7 @@ class UiHelper:
def __exit__(self, type, value, traceback):
for i in bpy.data.objects:
- i.select = (i in self.selected_objects)
+ i.select = i in self.selected_objects
scene = bpy.context.scene
scene.objects.active = self.active_object
@@ -94,8 +97,10 @@ class UiHelper:
def ensure_power_of_two(value):
return pow(2, math.floor(math.log(value, 2)))
+
def fetch_fcurves(id_data, data_fcurves=True):
"""Given a Blender ID, yields its FCurves"""
+
def _fetch(source):
if source is not None and source.action is not None:
for i in source.action.fcurves:
@@ -108,6 +113,7 @@ def fetch_fcurves(id_data, data_fcurves=True):
for i in _fetch(id_data.data.animation_data):
yield i
+
def find_modifier(bo, modid):
"""Given a Blender Object, finds a given modifier and returns it or None"""
if bo is not None:
diff --git a/korman/idprops.py b/korman/idprops.py
index 40cc474..f83b614 100644
--- a/korman/idprops.py
+++ b/korman/idprops.py
@@ -16,6 +16,7 @@
import bpy
from bpy.props import *
+
class IDPropMixin:
"""
So, here's the rub.
@@ -43,7 +44,9 @@ class IDPropMixin:
# Let's make sure no one is trying to access an old version...
if attr in _getattribute("_idprop_mapping")().values():
- raise AttributeError("'{}' has been deprecated... Please use the ID Property".format(attr))
+ raise AttributeError(
+ "'{}' has been deprecated... Please use the ID Property".format(attr)
+ )
# I have some bad news for you... Unfortunately, this might have been called
# during Blender's draw() context. Blender locks all properties during the draw loop.
@@ -64,7 +67,9 @@ class IDPropMixin:
# Disallow any attempts to set the old string property
if attr in idprops.values():
- raise AttributeError("'{}' has been deprecated... Please use the ID Property".format(attr))
+ raise AttributeError(
+ "'{}' has been deprecated... Please use the ID Property".format(attr)
+ )
# Inappropriate touching?
super().__getattribute__("_try_upgrade_idprops")()
@@ -77,14 +82,18 @@ class IDPropMixin:
if hasattr(super(), "register"):
super().register()
- cls.idprops_upgraded = BoolProperty(name="INTERNAL: ID Property Upgrader HACK",
- description="HAAAX *throws CRT monitor*",
- get=cls._try_upgrade_idprops,
- options={"HIDDEN"})
- cls.idprops_upgraded_value = BoolProperty(name="INTERNAL: ID Property Upgrade Status",
- description="Have old StringProperties been upgraded to ID Datablock Properties?",
- default=False,
- options={"HIDDEN"})
+ cls.idprops_upgraded = BoolProperty(
+ name="INTERNAL: ID Property Upgrader HACK",
+ description="HAAAX *throws CRT monitor*",
+ get=cls._try_upgrade_idprops,
+ options={"HIDDEN"},
+ )
+ cls.idprops_upgraded_value = BoolProperty(
+ name="INTERNAL: ID Property Upgrade Status",
+ description="Have old StringProperties been upgraded to ID Datablock Properties?",
+ default=False,
+ options={"HIDDEN"},
+ )
for str_prop in cls._idprop_mapping().values():
setattr(cls, str_prop, StringProperty(description="deprecated"))
@@ -115,36 +124,45 @@ class IDPropObjectMixin(IDPropMixin):
# NOTE: bad problems result when using super() here, so we'll manually reference object
cls = object.__getattribute__(self, "__class__")
idprops = cls._idprop_mapping()
- return { i: bpy.data.objects for i in idprops.values() }
+ return {i: bpy.data.objects for i in idprops.values()}
def poll_animated_objects(self, value):
return value.plasma_object.has_animation_data
+
def poll_camera_objects(self, value):
return value.type == "CAMERA"
+
def poll_drawable_objects(self, value):
return value.type == "MESH" and any(value.data.materials)
+
def poll_empty_objects(self, value):
return value.type == "EMPTY"
+
def poll_mesh_objects(self, value):
return value.type == "MESH"
+
def poll_softvolume_objects(self, value):
return value.plasma_modifiers.softvolume.enabled
+
def poll_subworld_objects(self, value):
return value.plasma_modifiers.subworld_def.enabled
+
def poll_visregion_objects(self, value):
return value.plasma_modifiers.visregion.enabled
+
def poll_envmap_textures(self, value):
return isinstance(value, bpy.types.EnvironmentMapTexture)
+
@bpy.app.handlers.persistent
def _upgrade_node_trees(dummy):
"""
@@ -160,4 +178,6 @@ def _upgrade_node_trees(dummy):
for node in tree.nodes:
if isinstance(node, IDPropMixin):
assert node._try_upgrade_idprops()
+
+
bpy.app.handlers.load_post.append(_upgrade_node_trees)
diff --git a/korman/korlib/__init__.py b/korman/korlib/__init__.py
index 1b6270d..f4d2f08 100644
--- a/korman/korlib/__init__.py
+++ b/korman/korlib/__init__.py
@@ -17,6 +17,7 @@ _KORLIB_API_VERSION = 2
try:
from _korlib import _KORLIB_API_VERSION as _C_API_VERSION
+
if _KORLIB_API_VERSION != _C_API_VERSION:
raise ImportError()
@@ -24,10 +25,12 @@ except ImportError as ex:
from .texture import *
if "_C_API_VERSION" in locals():
- msg = "Korlib C Module Version mismatch (expected {}, got {}).".format(_KORLIB_API_VERSION, _C_API_VERSION)
+ msg = "Korlib C Module Version mismatch (expected {}, got {}).".format(
+ _KORLIB_API_VERSION, _C_API_VERSION
+ )
else:
msg = "Korlib C Module did not load correctly."
- print(msg, "Using PyKorlib :(", sep=' ')
+ print(msg, "Using PyKorlib :(", sep=" ")
def create_bump_LUT(mipmap):
kLUTHeight = 16
@@ -41,33 +44,52 @@ except ImportError as ex:
doneH = 0
doneH = startH * kLUTWidth * 4
- buf[0:doneH] = [b for x in range(kLUTWidth) for b in (0, 0, int((x / denom) * 255.9), 255)] * startH
+ buf[0:doneH] = [
+ b for x in range(kLUTWidth) for b in (0, 0, int((x / denom) * 255.9), 255)
+ ] * startH
startH = doneH
doneH += delH * kLUTWidth * 4
- buf[startH:doneH] = [b for x in range(kLUTWidth) for b in (127, 127, int((x / denom) * 255.9), 255)] * delH
+ buf[startH:doneH] = [
+ b
+ for x in range(kLUTWidth)
+ for b in (127, 127, int((x / denom) * 255.9), 255)
+ ] * delH
startH = doneH
doneH += delH * kLUTWidth * 4
- buf[startH:doneH] = [b for x in range(kLUTWidth) for b in (0, int((x / denom) * 255.9), 0, 255)] * delH
+ buf[startH:doneH] = [
+ b for x in range(kLUTWidth) for b in (0, int((x / denom) * 255.9), 0, 255)
+ ] * delH
startH = doneH
doneH += delH * kLUTWidth * 4
- buf[startH:doneH] = [b for x in range(kLUTWidth) for b in (127, int((x / denom) * 255.9), 127, 255)] * delH
+ buf[startH:doneH] = [
+ b
+ for x in range(kLUTWidth)
+ for b in (127, int((x / denom) * 255.9), 127, 255)
+ ] * delH
startH = doneH
doneH += delH * kLUTWidth * 4
- buf[startH:doneH] = [b for x in range(kLUTWidth) for b in (int((x / denom) * 255.9), 0, 0, 255)] * delH
+ buf[startH:doneH] = [
+ b for x in range(kLUTWidth) for b in (int((x / denom) * 255.9), 0, 0, 255)
+ ] * delH
startH = doneH
doneH += delH * kLUTWidth * 4
- buf[startH:doneH] = [b for x in range(kLUTWidth) for b in (int((x / denom) * 255.9), 127, 127, 255)] * startH
+ buf[startH:doneH] = [
+ b
+ for x in range(kLUTWidth)
+ for b in (int((x / denom) * 255.9), 127, 127, 255)
+ ] * startH
mipmap.setRawImage(bytes(buf))
def inspect_voribsfile(stream, header):
raise NotImplementedError("Ogg Vorbis not supported unless _korlib is compiled")
+
else:
from _korlib import *
from .texture import TextureAlpha
@@ -77,8 +99,13 @@ finally:
from .python import *
from .texture import TEX_DETAIL_ALPHA, TEX_DETAIL_ADD, TEX_DETAIL_MULTIPLY
- _IDENTIFIER_RANGES = ((ord('0'), ord('9')), (ord('A'), ord('Z')), (ord('a'), ord('z')))
+ _IDENTIFIER_RANGES = (
+ (ord("0"), ord("9")),
+ (ord("A"), ord("Z")),
+ (ord("a"), ord("z")),
+ )
from keyword import kwlist as _kwlist
+
_KEYWORDS = set(_kwlist)
# Python 2.x keywords
_KEYWORDS.add("exec")
@@ -128,10 +155,19 @@ finally:
def process(identifier):
# No leading digits in identifiers, so skip the first range element (0...9)
- yield next((identifier[0] for low, high in _IDENTIFIER_RANGES[1:]
- if low <= ord(identifier[0]) <= high), '_')
+ yield next(
+ (
+ identifier[0]
+ for low, high in _IDENTIFIER_RANGES[1:]
+ if low <= ord(identifier[0]) <= high
+ ),
+ "_",
+ )
for i in identifier[1:]:
- yield next((i for low, high in _IDENTIFIER_RANGES if low <= ord(i) <= high), '_')
+ yield next(
+ (i for low, high in _IDENTIFIER_RANGES if low <= ord(i) <= high),
+ "_",
+ )
if identifier:
return "".join(process(identifier))
diff --git a/korman/korlib/console.py b/korman/korlib/console.py
index 19995b2..560574f 100644
--- a/korman/korlib/console.py
+++ b/korman/korlib/console.py
@@ -19,20 +19,26 @@ import math
import sys
if sys.platform == "win32":
+
class _Coord(ctypes.Structure):
- _fields_ = [("x", ctypes.c_short),
- ("y", ctypes.c_short)]
+ _fields_ = [("x", ctypes.c_short), ("y", ctypes.c_short)]
+
class _SmallRect(ctypes.Structure):
- _fields_ = [("Left", ctypes.c_short),
- ("Top", ctypes.c_short),
- ("Right", ctypes.c_short),
- ("Bottom", ctypes.c_short),]
+ _fields_ = [
+ ("Left", ctypes.c_short),
+ ("Top", ctypes.c_short),
+ ("Right", ctypes.c_short),
+ ("Bottom", ctypes.c_short),
+ ]
+
class _ConsoleScreenBufferInfo(ctypes.Structure):
- _fields_ = [("dwSize", _Coord),
- ("dwCursorPosition", _Coord),
- ("wAttributes", ctypes.c_ushort),
- ("srWindow", _SmallRect),
- ("dwMaximumWindowSize", _Coord)]
+ _fields_ = [
+ ("dwSize", _Coord),
+ ("dwCursorPosition", _Coord),
+ ("wAttributes", ctypes.c_ushort),
+ ("srWindow", _SmallRect),
+ ("dwMaximumWindowSize", _Coord),
+ ]
class _ConsoleCursor:
def __init__(self):
@@ -42,7 +48,9 @@ if sys.platform == "win32":
@property
def _screen_buffer_info(self):
info = _ConsoleScreenBufferInfo()
- ctypes.windll.kernel32.GetConsoleScreenBufferInfo(self._handle, ctypes.pointer(info))
+ ctypes.windll.kernel32.GetConsoleScreenBufferInfo(
+ self._handle, ctypes.pointer(info)
+ )
return info
def clear(self):
@@ -53,19 +61,28 @@ if sys.platform == "win32":
num_chars = (info.dwSize.x * num_cols) + num_rows
if num_chars:
nWrite = ctypes.c_ulong()
- empty_char = ctypes.c_char(b' ')
- ctypes.windll.kernel32.FillConsoleOutputCharacterA(self._handle, empty_char,
- num_chars, self.position,
- ctypes.pointer(nWrite))
+ empty_char = ctypes.c_char(b" ")
+ ctypes.windll.kernel32.FillConsoleOutputCharacterA(
+ self._handle,
+ empty_char,
+ num_chars,
+ self.position,
+ ctypes.pointer(nWrite),
+ )
def reset(self):
ctypes.windll.kernel32.SetConsoleCursorPosition(self._handle, self.position)
def update(self):
info = _ConsoleScreenBufferInfo()
- ctypes.windll.kernel32.GetConsoleScreenBufferInfo(self._handle, ctypes.pointer(info))
+ ctypes.windll.kernel32.GetConsoleScreenBufferInfo(
+ self._handle, ctypes.pointer(info)
+ )
self.position = info.dwCursorPosition
+
+
else:
+
class _ConsoleCursor:
def clear(self):
# Only clears the current line, unfortunately.
diff --git a/korman/korlib/python.py b/korman/korlib/python.py
index 5869539..1e1205a 100644
--- a/korman/korlib/python.py
+++ b/korman/korlib/python.py
@@ -13,13 +13,14 @@
# You should have received a copy of the GNU General Public License
# along with Korman. If not, see .
-from __future__ import generators # Python 2.2
+from __future__ import generators # Python 2.2
import marshal
import os.path
import sys
_python_executables = {}
+
class PythonNotAvailableError(Exception):
pass
@@ -30,11 +31,11 @@ def compyle(file_name, py_code, py_version, report=None, indent=0):
assert my_version == (2, 7) or my_version[0] > 2
# Remember: Python 2.2 file, so no single line if statements...
- idx = file_name.find('.')
+ idx = file_name.find(".")
if idx == -1:
module_name = file_name
else:
- module_name = file_name[:idx]
+ module_name = file_name[:idx]
if report is not None:
report.msg("Compyling {}", file_name, indent=indent)
@@ -48,26 +49,31 @@ def compyle(file_name, py_code, py_version, report=None, indent=0):
py_code = py_code.encode("utf-8")
except UnicodeError:
if report is not None:
- report.error("Could not encode '{}'", file_name, indent=indent+1)
+ report.error("Could not encode '{}'", file_name, indent=indent + 1)
return (False, "Could not encode file")
- result = subprocess.run(args, input=py_code, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+ result = subprocess.run(
+ args, input=py_code, stdout=subprocess.PIPE, stderr=subprocess.STDOUT
+ )
if result.returncode != 0:
try:
- error = result.stdout.decode("utf-8").replace('\r\n', '\n')
+ error = result.stdout.decode("utf-8").replace("\r\n", "\n")
except UnicodeError:
error = result.stdout
if report is not None:
- report.error("Compylation Error in '{}'\n{}", file_name, error, indent=indent+1)
+ report.error(
+ "Compylation Error in '{}'\n{}", file_name, error, indent=indent + 1
+ )
return (result.returncode == 0, result.stdout)
else:
raise NotImplementedError()
+
def _compyle(module_name, py_code):
# Old python versions have major issues with Windows style newlines.
# Also, bad things happen if there is no newline at the end.
- py_code += '\n' # sigh, this is slow on old Python...
- py_code = py_code.replace('\r\n', '\n')
- py_code = py_code.replace('\r', '\n')
+ py_code += "\n" # sigh, this is slow on old Python...
+ py_code = py_code.replace("\r\n", "\n")
+ py_code = py_code.replace("\r", "\n")
code_object = compile(py_code, module_name, "exec")
# The difference between us and the py_compile module is twofold:
@@ -79,6 +85,7 @@ def _compyle(module_name, py_code):
# Therefore, we simply return the marshalled data as a string.
return marshal.dumps(code_object)
+
def _find_python(py_version):
def find_executable(py_version):
# First, try to use Blender to find the Python executable
@@ -88,7 +95,9 @@ def _find_python(py_version):
pass
else:
userprefs = bpy.context.user_preferences.addons["korman"].preferences
- py_executable = getattr(userprefs, "python{}{}_executable".format(*py_version), None)
+ py_executable = getattr(
+ userprefs, "python{}{}_executable".format(*py_version), None
+ )
if verify_python(py_version, py_executable):
return py_executable
@@ -108,14 +117,18 @@ def _find_python(py_version):
# I give up, you win.
return None
- py_executable = _python_executables.setdefault(py_version, find_executable(py_version))
+ py_executable = _python_executables.setdefault(
+ py_version, find_executable(py_version)
+ )
if py_executable:
return py_executable
else:
raise PythonNotAvailableError("{}.{}".format(*py_version))
+
def _find_python_reg(reg_key, py_version):
import winreg
+
subkey_name = "Software\\Python\\PythonCore\\{}.{}\\InstallPath".format(*py_version)
try:
python_dir = winreg.QueryValue(reg_key, subkey_name)
@@ -124,6 +137,7 @@ def _find_python_reg(reg_key, py_version):
else:
return os.path.join(python_dir, "python.exe")
+
def package_python(stream, pyc_objects):
# Python.pak format:
# uint32_t numFiles
@@ -139,17 +153,17 @@ def package_python(stream, pyc_objects):
# `stream` might be a plEncryptedStream, which doesn't seek very well at all.
# Therefore, we will go ahead and calculate the size of the index block so
# there is no need to seek around to write offset values
- base_offset = 4 # uint32_t numFiles
+ base_offset = 4 # uint32_t numFiles
data_offset = 0
- pyc_info = [] # sad, but makes life easier...
+ pyc_info = [] # sad, but makes life easier...
for module_name, compyled_code in pyc_objects:
pyc_info.append((module_name, data_offset, compyled_code))
# index offset overall
- base_offset += 2 # writeSafeStr length
+ base_offset += 2 # writeSafeStr length
# NOTE: This assumes that libHSPlasma's hsStream::writeSafeStr converts
# the Python unicode/string object to UTF-8. Currently, this is true.
- base_offset += len(module_name.encode("utf-8")) # writeSafeStr
+ base_offset += len(module_name.encode("utf-8")) # writeSafeStr
base_offset += 4
# current file data offset
@@ -165,14 +179,18 @@ def package_python(stream, pyc_objects):
stream.writeInt(len(compyled_code))
stream.write(compyled_code)
+
def verify_python(py_version, py_exe):
if not py_exe:
return False
import subprocess
+
try:
args = (py_exe, "-V")
- result = subprocess.run(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, timeout=5)
+ result = subprocess.run(
+ args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, timeout=5
+ )
except OSError:
return False
else:
@@ -186,11 +204,13 @@ def verify_python(py_version, py_exe):
return False
return "{}.{}".format(*py_version) == py_check
+
if __name__ == "__main__":
# Python tries to be "helpful" on Windows by converting \n to \r\n.
# Therefore we must change the mode of stdout.
if sys.platform == "win32":
import os, msvcrt
+
msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
try:
diff --git a/korman/korlib/texture.py b/korman/korlib/texture.py
index e1d9948..ed4a342 100644
--- a/korman/korlib/texture.py
+++ b/korman/korlib/texture.py
@@ -28,6 +28,7 @@ TEX_DETAIL_ALPHA = 0
TEX_DETAIL_ADD = 1
TEX_DETAIL_MULTIPLY = 2
+
def scale_image(buf, srcW, srcH, dstW, dstH):
"""Scales an RGBA image using the algorithm from CWE's plMipmap::ScaleNicely"""
dst, dst_idx = bytearray(dstW * dstH * 4), 0
@@ -44,7 +45,7 @@ def scale_image(buf, srcW, srcH, dstW, dstH):
srcY_start = int(max(srcY - filterH, 0))
srcY_end = int(min(srcY + filterH, srcH - 1))
- #weightsY = { i - srcY_start: 1.0 - abs(i - srcY) / scaleY \
+ # weightsY = { i - srcY_start: 1.0 - abs(i - srcY) / scaleY \
# for i in range(srcY_start, srcY_end+1, 1) if i - srcY_start < 16 }
for i in range(16):
idx = i + srcY_start
@@ -57,7 +58,7 @@ def scale_image(buf, srcW, srcH, dstW, dstH):
srcX_start = int(max(srcX - filterW, 0))
srcX_end = int(min(srcX + filterW, srcW - 1))
- #weightsX = { i - srcX_start: 1.0 - abs(i - srcX) / scaleX \
+ # weightsX = { i - srcX_start: 1.0 - abs(i - srcX) / scaleX \
# for i in range(srcX_start, srcX_end+1, 1) if i - srcX_start < 16 }
for i in range(16):
idx = i + srcX_start
@@ -67,15 +68,23 @@ def scale_image(buf, srcW, srcH, dstW, dstH):
accum_color = [0.0, 0.0, 0.0, 0.0]
weight_total = 0.0
- for i in range(srcY_start, srcY_end+1, 1):
+ for i in range(srcY_start, srcY_end + 1, 1):
weightY_idx = i - srcY_start
- weightY = weightsY[weightY_idx] if weightY_idx < 16 else 1.0 - abs(i - srcY) / filterH
+ weightY = (
+ weightsY[weightY_idx]
+ if weightY_idx < 16
+ else 1.0 - abs(i - srcY) / filterH
+ )
weightY = 1.0 - abs(i - srcY) / filterH
src_idx = (i * src_rowspan) + (srcX_start * 4)
- for j in range(srcX_start, srcX_end+1, 1):
+ for j in range(srcX_start, srcX_end + 1, 1):
weightX_idx = j - srcX_start
- weightX = weightsX[weightX_idx] if weightX_idx < 16 else 1.0 - abs(j - srcX) / filterW
+ weightX = (
+ weightsX[weightX_idx]
+ if weightX_idx < 16
+ else 1.0 - abs(j - srcX) / filterW
+ )
weight = weightY * weightX
if weight > 0.0:
@@ -83,14 +92,14 @@ def scale_image(buf, srcW, srcH, dstW, dstH):
# function. I know this function is supposed to be slow, but dayum... I've unrolled it
# to avoid all the extra allocations.
for k in range(4):
- accum_color[k] = accum_color[k] + buf[src_idx+k] * weight
+ accum_color[k] = accum_color[k] + buf[src_idx + k] * weight
weight_total += weight
src_idx += 4
weight_total = max(weight_total, 0.0001)
for i in range(4):
accum_color[i] = int(accum_color[i] * (1.0 / weight_total))
- dst[dst_idx:dst_idx+4] = accum_color
+ dst[dst_idx : dst_idx + 4] = accum_color
dst_idx += 4
return bytes(dst)
@@ -123,7 +132,7 @@ class GLTexture:
if self._blimg.gl_load() != 0:
raise RuntimeError("failed to load image")
previous_texture = self._get_integer(bgl.GL_TEXTURE_BINDING_2D)
- changed_state = (previous_texture != self._blimg.bindcode[0])
+ changed_state = previous_texture != self._blimg.bindcode[0]
if changed_state:
bgl.glBindTexture(bgl.GL_TEXTURE_2D, self._blimg.bindcode[0])
@@ -155,14 +164,18 @@ class GLTexture:
@property
def _detail_falloff(self):
num_levels = self.num_levels
- return ((self._texkey.detail_fade_start / 100.0) * num_levels,
- (self._texkey.detail_fade_stop / 100.0) * num_levels,
- self._texkey.detail_opacity_start / 100.0,
- self._texkey.detail_opacity_stop / 100.0)
-
- def get_level_data(self, level=0, calc_alpha=False, report=None, indent=2, fast=False):
+ return (
+ (self._texkey.detail_fade_start / 100.0) * num_levels,
+ (self._texkey.detail_fade_stop / 100.0) * num_levels,
+ self._texkey.detail_opacity_start / 100.0,
+ self._texkey.detail_opacity_stop / 100.0,
+ )
+
+ def get_level_data(
+ self, level=0, calc_alpha=False, report=None, indent=2, fast=False
+ ):
"""Gets the uncompressed pixel data for a requested mip level, optionally calculating the alpha
- channel from the image color data
+ channel from the image color data
"""
# Previously, we would leave the texture bound in OpenGL and use it to do the mipmapping, using
@@ -190,7 +203,6 @@ class GLTexture:
else:
buf = bytearray(self._image_data)
-
if self._image_inverted:
buf = self._invert_image(eWidth, eHeight, buf)
@@ -207,11 +219,15 @@ class GLTexture:
# Do we need to calculate the alpha component?
if calc_alpha:
for i in range(0, size, 4):
- buf[i+3] = int(sum(buf[i:i+3]) / 3)
+ buf[i + 3] = int(sum(buf[i : i + 3]) / 3)
return bytes(buf)
- def _get_detail_alpha(self, level, dropoff_start, dropoff_stop, detail_max, detail_min):
- alpha = (level - dropoff_start) * (detail_min - detail_max) / (dropoff_stop - dropoff_start) + detail_max
+ def _get_detail_alpha(
+ self, level, dropoff_start, dropoff_stop, detail_max, detail_min
+ ):
+ alpha = (level - dropoff_start) * (detail_min - detail_max) / (
+ dropoff_stop - dropoff_start
+ ) + detail_max
if detail_min < detail_max:
return min(detail_max, max(detail_min, alpha))
else:
@@ -242,8 +258,10 @@ class GLTexture:
def _get_image_data(self):
return (self._width, self._height, self._image_data)
+
def _set_image_data(self, value):
self._width, self._height, self._image_data = value
+
image_data = property(_get_image_data, _set_image_data)
def _invert_image(self, width, height, buf):
@@ -251,30 +269,36 @@ class GLTexture:
finalBuf = bytearray(size)
row_stride = width * 4
for i in range(height):
- src, dst = i * row_stride, (height - (i+1)) * row_stride
- finalBuf[dst:dst+row_stride] = buf[src:src+row_stride]
+ src, dst = i * row_stride, (height - (i + 1)) * row_stride
+ finalBuf[dst : dst + row_stride] = buf[src : src + row_stride]
return bytes(finalBuf)
def _make_detail_map_add(self, data, level):
dropoff_start, dropoff_stop, detail_max, detail_min = self._detail_falloff
- alpha = self._get_detail_alpha(level, dropoff_start, dropoff_stop, detail_max, detail_min)
+ alpha = self._get_detail_alpha(
+ level, dropoff_start, dropoff_stop, detail_max, detail_min
+ )
for i in range(0, len(data), 4):
data[i] = int(data[i] * alpha)
- data[i+1] = int(data[i+1] * alpha)
- data[i+2] = int(data[i+2] * alpha)
+ data[i + 1] = int(data[i + 1] * alpha)
+ data[i + 2] = int(data[i + 2] * alpha)
def _make_detail_map_alpha(self, data, level):
dropoff_start, dropoff_end, detail_max, detail_min = self._detail_falloff
- alpha = self._get_detail_alpha(level, dropoff_start, dropoff_end, detail_max, detail_min)
+ alpha = self._get_detail_alpha(
+ level, dropoff_start, dropoff_end, detail_max, detail_min
+ )
for i in range(0, len(data), 4):
- data[i+3] = int(data[i+3] * alpha)
+ data[i + 3] = int(data[i + 3] * alpha)
def _make_detail_map_mult(self, data, level):
dropoff_start, dropoff_end, detail_max, detail_min = self._detail_falloff
- alpha = self._get_detail_alpha(level, dropoff_start, dropoff_end, detail_max, detail_min)
+ alpha = self._get_detail_alpha(
+ level, dropoff_start, dropoff_end, detail_max, detail_min
+ )
invert_alpha = (1.0 - alpha) * 255.0
for i in range(0, len(data), 4):
- data[i+3] = int(invert_alpha + data[i+3] * alpha)
+ data[i + 3] = int(invert_alpha + data[i + 3] * alpha)
@property
def num_levels(self):
diff --git a/korman/nodes/__init__.py b/korman/nodes/__init__.py
index 57aa758..868f01e 100644
--- a/korman/nodes/__init__.py
+++ b/korman/nodes/__init__.py
@@ -29,12 +29,14 @@ from .node_python import *
from .node_responder import *
from .node_softvolume import *
+
class PlasmaNodeCategory(NodeCategory):
"""Plasma Node Category"""
@classmethod
def poll(cls, context):
- return (context.space_data.tree_type == "PlasmaNodeTree")
+ return context.space_data.tree_type == "PlasmaNodeTree"
+
# Here's what you need to know about this...
# If you add a new category, put the pretty name here!
@@ -50,6 +52,7 @@ _kategory_names = {
"SV": "Soft Volume",
}
+
class PlasmaNodeItem(NodeItem):
def __init__(self, **kwargs):
self._poll_add = kwargs.pop("poll_add", None)
@@ -91,11 +94,17 @@ for cls in dict(globals()).values():
_actual_kategories = []
for i in sorted(_kategories.keys(), key=lambda x: _kategory_names[x]):
# Note that even though we're sorting the category names, Blender appears to not care...
- _kat_items = [PlasmaNodeItem(**j) for j in sorted(_kategories[i], key=lambda x: x["label"])]
- _actual_kategories.append(PlasmaNodeCategory(i, _kategory_names[i], items=_kat_items))
+ _kat_items = [
+ PlasmaNodeItem(**j) for j in sorted(_kategories[i], key=lambda x: x["label"])
+ ]
+ _actual_kategories.append(
+ PlasmaNodeCategory(i, _kategory_names[i], items=_kat_items)
+ )
+
def register():
nodeitems_utils.register_node_categories("PLASMA_NODES", _actual_kategories)
+
def unregister():
nodeitems_utils.unregister_node_categories("PLASMA_NODES")
diff --git a/korman/nodes/node_avatar.py b/korman/nodes/node_avatar.py
index 0049bb4..22f258e 100644
--- a/korman/nodes/node_avatar.py
+++ b/korman/nodes/node_avatar.py
@@ -22,6 +22,7 @@ from .node_core import PlasmaNodeBase, PlasmaNodeSocketBase
from ..properties.modifiers.avatar import sitting_approach_flags
from ..exporter.explosions import ExportError
+
class PlasmaSittingBehaviorNode(PlasmaNodeBase, bpy.types.Node):
bl_category = "AVATAR"
bl_idname = "PlasmaSittingBehaviorNode"
@@ -30,26 +31,41 @@ class PlasmaSittingBehaviorNode(PlasmaNodeBase, bpy.types.Node):
pl_attrib = {"ptAttribActivator", "ptAttribActivatorList", "ptAttribNamedActivator"}
- approach = EnumProperty(name="Approach",
- description="Directions an avatar can approach the seat from",
- items=sitting_approach_flags,
- default={"kApproachFront", "kApproachLeft", "kApproachRight"},
- options={"ENUM_FLAG"})
-
- input_sockets = OrderedDict([
- ("condition", {
- "text": "Condition",
- "type": "PlasmaConditionSocket",
- }),
- ])
-
- output_sockets = OrderedDict([
- ("satisfies", {
- "text": "Satisfies",
- "type": "PlasmaConditionSocket",
- "valid_link_sockets": {"PlasmaConditionSocket", "PlasmaPythonFileNodeSocket"},
- }),
- ])
+ approach = EnumProperty(
+ name="Approach",
+ description="Directions an avatar can approach the seat from",
+ items=sitting_approach_flags,
+ default={"kApproachFront", "kApproachLeft", "kApproachRight"},
+ options={"ENUM_FLAG"},
+ )
+
+ input_sockets = OrderedDict(
+ [
+ (
+ "condition",
+ {
+ "text": "Condition",
+ "type": "PlasmaConditionSocket",
+ },
+ ),
+ ]
+ )
+
+ output_sockets = OrderedDict(
+ [
+ (
+ "satisfies",
+ {
+ "text": "Satisfies",
+ "type": "PlasmaConditionSocket",
+ "valid_link_sockets": {
+ "PlasmaConditionSocket",
+ "PlasmaPythonFileNodeSocket",
+ },
+ },
+ ),
+ ]
+ )
def draw_buttons(self, context, layout):
col = layout.column()
@@ -70,7 +86,12 @@ class PlasmaSittingBehaviorNode(PlasmaNodeBase, bpy.types.Node):
if i is not None:
sitmod.addNotifyKey(i.get_key(exporter, so))
else:
- exporter.report.warn("'{}' Node '{}' doesn't expose a key. It won't be triggered by '{}'!".format(i.bl_idname, i.name, self.name), indent=3)
+ exporter.report.warn(
+ "'{}' Node '{}' doesn't expose a key. It won't be triggered by '{}'!".format(
+ i.bl_idname, i.name, self.name
+ ),
+ indent=3,
+ )
@property
def requires_actor(self):
@@ -80,9 +101,11 @@ class PlasmaSittingBehaviorNode(PlasmaNodeBase, bpy.types.Node):
class PlasmaAnimStageAdvanceSocketIn(PlasmaNodeSocketBase, bpy.types.NodeSocket):
bl_color = (0.412, 0.2, 0.055, 1.0)
- auto_advance = BoolProperty(name="Advance to Next Stage",
- description="Automatically advance to the next stage when the animation completes instead of halting",
- default=True)
+ auto_advance = BoolProperty(
+ name="Advance to Next Stage",
+ description="Automatically advance to the next stage when the animation completes instead of halting",
+ default=True,
+ )
def draw_content(self, context, layout, node, text):
if not self.is_linked:
@@ -97,9 +120,11 @@ class PlasmaAnimStageAdvanceSocketIn(PlasmaNodeSocketBase, bpy.types.NodeSocket)
class PlasmaAnimStageRegressSocketIn(PlasmaNodeSocketBase, bpy.types.NodeSocket):
bl_color = (0.412, 0.2, 0.055, 1.0)
- auto_regress = BoolProperty(name="Regress to Previous Stage",
- description="Automatically regress to the previous stage when the animation completes instead of halting",
- default=True)
+ auto_regress = BoolProperty(
+ name="Regress to Previous Stage",
+ description="Automatically regress to the previous stage when the animation completes instead of halting",
+ default=True,
+ )
def draw_content(self, context, layout, node, text):
if not self.is_linked:
@@ -115,17 +140,59 @@ class PlasmaAnimStageOrderSocketOut(PlasmaNodeSocketBase, bpy.types.NodeSocket):
bl_color = (0.412, 0.2, 0.055, 1.0)
-anim_play_flags = [("kPlayNone", "None", "Play stage only when directed by a message"),
- ("kPlayKey", "Keyboard", "Play stage when the user presses the forward/backward key"),
- ("kPlayAuto", "Automatic", "Play stage automatically")]
-anim_stage_adv_flags = [("kAdvanceNone", "None", "Advance to the next stage only when directed by a message"),
- ("kAdvanceOnMove", "Movement", "Advance to the next stage when the user presses a movement key"),
- ("kAdvanceAuto", "Automatic", "Advance to the next stage automatically when this one completes"),
- ("kAdvanceOnAnyKey", "Any Keypress", "Advance to the next stage when the user presses any key")]
-anim_stage_rgr_flags = [("kAdvanceNone", "None", "Regress to the previous stage only when directed by a message"),
- ("kAdvanceOnMove", "Movement", "Regress to the previous stage when the user presses a movement key"),
- ("kAdvanceAuto", "Automatic", "Regress to the previous stage automatically when this one completes"),
- ("kAdvanceOnAnyKey", "Any Keypress", "Regress to the previous stage when the user presses any key")]
+anim_play_flags = [
+ ("kPlayNone", "None", "Play stage only when directed by a message"),
+ (
+ "kPlayKey",
+ "Keyboard",
+ "Play stage when the user presses the forward/backward key",
+ ),
+ ("kPlayAuto", "Automatic", "Play stage automatically"),
+]
+anim_stage_adv_flags = [
+ (
+ "kAdvanceNone",
+ "None",
+ "Advance to the next stage only when directed by a message",
+ ),
+ (
+ "kAdvanceOnMove",
+ "Movement",
+ "Advance to the next stage when the user presses a movement key",
+ ),
+ (
+ "kAdvanceAuto",
+ "Automatic",
+ "Advance to the next stage automatically when this one completes",
+ ),
+ (
+ "kAdvanceOnAnyKey",
+ "Any Keypress",
+ "Advance to the next stage when the user presses any key",
+ ),
+]
+anim_stage_rgr_flags = [
+ (
+ "kAdvanceNone",
+ "None",
+ "Regress to the previous stage only when directed by a message",
+ ),
+ (
+ "kAdvanceOnMove",
+ "Movement",
+ "Regress to the previous stage when the user presses a movement key",
+ ),
+ (
+ "kAdvanceAuto",
+ "Automatic",
+ "Regress to the previous stage automatically when this one completes",
+ ),
+ (
+ "kAdvanceOnAnyKey",
+ "Any Keypress",
+ "Regress to the previous stage when the user presses any key",
+ ),
+]
class PlasmaAnimStageSettingsSocket(PlasmaNodeSocketBase, bpy.types.NodeSocket):
@@ -138,63 +205,94 @@ class PlasmaAnimStageSettingsNode(PlasmaNodeBase, bpy.types.Node):
bl_label = "Animation Stage Settings"
bl_width_default = 325
- forward = EnumProperty(name="Forward",
- description="Selects which events cause this stage to play forward",
- items=anim_play_flags,
- default="kPlayNone")
- backward = EnumProperty(name="Backward",
- description="Selects which events cause this stage to play backward",
- items=anim_play_flags,
- default="kPlayNone")
- stage_advance = EnumProperty(name="Stage Advance",
- description="Selects which events cause this stage to advance to the next stage",
- items=anim_stage_adv_flags,
- default="kAdvanceNone")
- stage_regress = EnumProperty(name="Stage Regress",
- description="Selects which events cause this stage to regress to the previous stage",
- items=anim_stage_rgr_flags,
- default="kAdvanceNone")
-
- notify_on = EnumProperty(name="Notify",
- description="Which events should send notifications",
- items=[
- ("kNotifyEnter", "Enter",
- "Send notification when animation first begins to play"),
- ("kNotifyLoop", "Loop",
- "Send notification when animation starts a loop"),
- ("kNotifyAdvance", "Advance",
- "Send notification when animation is advanced"),
- ("kNotifyRegress", "Regress",
- "Send notification when animation is regressed")
- ],
- default={"kNotifyEnter"},
- options={"ENUM_FLAG"})
-
- input_sockets = OrderedDict([
- ("advance_to", {
- "text": "Advance to Stage",
- "type": "PlasmaAnimStageAdvanceSocketIn",
- "valid_link_nodes": "PlasmaAnimStageNode",
- "valid_link_sockets": "PlasmaAnimStageOrderSocketOut",
- "link_limit": 1,
- }),
- ("regress_to", {
- "text": "Regress to Stage",
- "type": "PlasmaAnimStageRegressSocketIn",
- "valid_link_nodes": "PlasmaAnimStageNode",
- "valid_link_sockets": "PlasmaAnimStageOrderSocketOut",
- "link_limit": 1,
- }),
- ])
-
- output_sockets = OrderedDict([
- ("stage", {
- "text": "Stage",
- "type": "PlasmaAnimStageSettingsSocket",
- "valid_link_nodes": "PlasmaAnimStageNode",
- "valid_link_sockets": "PlasmaAnimStageSettingsSocket",
- }),
- ])
+ forward = EnumProperty(
+ name="Forward",
+ description="Selects which events cause this stage to play forward",
+ items=anim_play_flags,
+ default="kPlayNone",
+ )
+ backward = EnumProperty(
+ name="Backward",
+ description="Selects which events cause this stage to play backward",
+ items=anim_play_flags,
+ default="kPlayNone",
+ )
+ stage_advance = EnumProperty(
+ name="Stage Advance",
+ description="Selects which events cause this stage to advance to the next stage",
+ items=anim_stage_adv_flags,
+ default="kAdvanceNone",
+ )
+ stage_regress = EnumProperty(
+ name="Stage Regress",
+ description="Selects which events cause this stage to regress to the previous stage",
+ items=anim_stage_rgr_flags,
+ default="kAdvanceNone",
+ )
+
+ notify_on = EnumProperty(
+ name="Notify",
+ description="Which events should send notifications",
+ items=[
+ (
+ "kNotifyEnter",
+ "Enter",
+ "Send notification when animation first begins to play",
+ ),
+ ("kNotifyLoop", "Loop", "Send notification when animation starts a loop"),
+ (
+ "kNotifyAdvance",
+ "Advance",
+ "Send notification when animation is advanced",
+ ),
+ (
+ "kNotifyRegress",
+ "Regress",
+ "Send notification when animation is regressed",
+ ),
+ ],
+ default={"kNotifyEnter"},
+ options={"ENUM_FLAG"},
+ )
+
+ input_sockets = OrderedDict(
+ [
+ (
+ "advance_to",
+ {
+ "text": "Advance to Stage",
+ "type": "PlasmaAnimStageAdvanceSocketIn",
+ "valid_link_nodes": "PlasmaAnimStageNode",
+ "valid_link_sockets": "PlasmaAnimStageOrderSocketOut",
+ "link_limit": 1,
+ },
+ ),
+ (
+ "regress_to",
+ {
+ "text": "Regress to Stage",
+ "type": "PlasmaAnimStageRegressSocketIn",
+ "valid_link_nodes": "PlasmaAnimStageNode",
+ "valid_link_sockets": "PlasmaAnimStageOrderSocketOut",
+ "link_limit": 1,
+ },
+ ),
+ ]
+ )
+
+ output_sockets = OrderedDict(
+ [
+ (
+ "stage",
+ {
+ "text": "Stage",
+ "type": "PlasmaAnimStageSettingsSocket",
+ "valid_link_nodes": "PlasmaAnimStageNode",
+ "valid_link_sockets": "PlasmaAnimStageSettingsSocket",
+ },
+ ),
+ ]
+ )
def draw_buttons(self, context, layout):
layout.prop(self, "forward")
@@ -215,45 +313,66 @@ class PlasmaAnimStageNode(PlasmaNodeBase, bpy.types.Node):
bl_label = "Animation Stage"
bl_width_default = 325
- pl_attrib = ("ptAttribAnimation")
-
- anim_name = StringProperty(name="Animation Name",
- description="Name of animation to play")
-
- loop_option = EnumProperty(name="Looping",
- description="Loop options for animation playback",
- items=[("kDontLoop", "Don't Loop", "Don't loop the animation"),
- ("kLoop", "Loop", "Loop the animation a finite number of times"),
- ("kLoopForever", "Loop Forever", "Continue playing animation indefinitely")],
- default="kDontLoop")
- num_loops = IntProperty(name="Num Loops",
- description="Number of times to loop animation",
- default=0)
-
- input_sockets = OrderedDict([
- ("stage_settings", {
- "text": "Stage Settings",
- "type": "PlasmaAnimStageSettingsSocket",
- "valid_link_nodes": "PlasmaAnimStageSettingsNode",
- "valid_link_sockets": "PlasmaAnimStageSettingsSocket",
- "link_limit": 1,
- }),
- ])
-
- output_sockets = OrderedDict([
- ("stage", {
- "text": "Behavior",
- "type": "PlasmaAnimStageRefSocket",
- "valid_link_nodes": "PlasmaMultiStageBehaviorNode",
- "valid_link_sockets": "PlasmaAnimStageRefSocket",
- }),
- ("stage_reference", {
- "text": "Stage Progression",
- "type": "PlasmaAnimStageOrderSocketOut",
- "valid_link_nodes": "PlasmaAnimStageSettingsNode",
- "valid_link_sockets": {"PlasmaAnimStageAdvanceSocketIn", "PlasmaAnimStageRegressSocketIn"} ,
- }),
- ])
+ pl_attrib = "ptAttribAnimation"
+
+ anim_name = StringProperty(
+ name="Animation Name", description="Name of animation to play"
+ )
+
+ loop_option = EnumProperty(
+ name="Looping",
+ description="Loop options for animation playback",
+ items=[
+ ("kDontLoop", "Don't Loop", "Don't loop the animation"),
+ ("kLoop", "Loop", "Loop the animation a finite number of times"),
+ ("kLoopForever", "Loop Forever", "Continue playing animation indefinitely"),
+ ],
+ default="kDontLoop",
+ )
+ num_loops = IntProperty(
+ name="Num Loops", description="Number of times to loop animation", default=0
+ )
+
+ input_sockets = OrderedDict(
+ [
+ (
+ "stage_settings",
+ {
+ "text": "Stage Settings",
+ "type": "PlasmaAnimStageSettingsSocket",
+ "valid_link_nodes": "PlasmaAnimStageSettingsNode",
+ "valid_link_sockets": "PlasmaAnimStageSettingsSocket",
+ "link_limit": 1,
+ },
+ ),
+ ]
+ )
+
+ output_sockets = OrderedDict(
+ [
+ (
+ "stage",
+ {
+ "text": "Behavior",
+ "type": "PlasmaAnimStageRefSocket",
+ "valid_link_nodes": "PlasmaMultiStageBehaviorNode",
+ "valid_link_sockets": "PlasmaAnimStageRefSocket",
+ },
+ ),
+ (
+ "stage_reference",
+ {
+ "text": "Stage Progression",
+ "type": "PlasmaAnimStageOrderSocketOut",
+ "valid_link_nodes": "PlasmaAnimStageSettingsNode",
+ "valid_link_sockets": {
+ "PlasmaAnimStageAdvanceSocketIn",
+ "PlasmaAnimStageRegressSocketIn",
+ },
+ },
+ ),
+ ]
+ )
def draw_buttons(self, context, layout):
layout.prop(self, "anim_name")
@@ -270,7 +389,15 @@ class PlasmaAnimStageNode(PlasmaNodeBase, bpy.types.Node):
stage_socket = self.find_output_socket("stage")
if stage_socket.is_linked:
msbmod = stage_socket.links[0].to_node
- idx = next((idx for idx, socket in enumerate(msbmod.find_input_sockets("stage_refs")) if socket.is_linked and socket.links[0].from_node == self))
+ idx = next(
+ (
+ idx
+ for idx, socket in enumerate(
+ msbmod.find_input_sockets("stage_refs")
+ )
+ if socket.is_linked and socket.links[0].from_node == self
+ )
+ )
return idx
@@ -284,48 +411,69 @@ class PlasmaMultiStageBehaviorNode(PlasmaNodeBase, bpy.types.Node):
bl_label = "Multistage Behavior"
bl_width_default = 200
- pl_attrib = ("ptAttribBehavior")
-
- freeze_phys = BoolProperty(name="Freeze Physical",
- description="Freeze physical at end",
- default=False)
- reverse_control = BoolProperty(name="Reverse Controls",
- description="Reverse forward/back controls at end",
- default=False)
-
- input_sockets = OrderedDict([
- ("seek_target", {
- "text": "Seek Target",
- "type": "PlasmaSeekTargetSocketIn",
- "valid_link_sockets": "PlasmaSeekTargetSocketOut",
- }),
- ("stage_refs", {
- "text": "Stage",
- "type": "PlasmaAnimStageRefSocket",
- "valid_link_nodes": "PlasmaAnimStageNode",
- "valid_link_sockets": "PlasmaAnimStageRefSocket",
- "link_limit": 1,
- "spawn_empty": True,
- }),
- ("condition", {
- "text": "Triggered By",
- "type": "PlasmaConditionSocket",
- "spawn_empty": True,
- }),
- ])
-
- output_sockets = OrderedDict([
- ("hosts", {
- "text": "Host Script",
- "type": "PlasmaBehaviorSocket",
- "valid_link_nodes": {"PlasmaPythonFileNode"},
- "spawn_empty": True,
- }),
- ("satisfies", {
- "text": "Trigger",
- "type": "PlasmaConditionSocket",
- })
- ])
+ pl_attrib = "ptAttribBehavior"
+
+ freeze_phys = BoolProperty(
+ name="Freeze Physical", description="Freeze physical at end", default=False
+ )
+ reverse_control = BoolProperty(
+ name="Reverse Controls",
+ description="Reverse forward/back controls at end",
+ default=False,
+ )
+
+ input_sockets = OrderedDict(
+ [
+ (
+ "seek_target",
+ {
+ "text": "Seek Target",
+ "type": "PlasmaSeekTargetSocketIn",
+ "valid_link_sockets": "PlasmaSeekTargetSocketOut",
+ },
+ ),
+ (
+ "stage_refs",
+ {
+ "text": "Stage",
+ "type": "PlasmaAnimStageRefSocket",
+ "valid_link_nodes": "PlasmaAnimStageNode",
+ "valid_link_sockets": "PlasmaAnimStageRefSocket",
+ "link_limit": 1,
+ "spawn_empty": True,
+ },
+ ),
+ (
+ "condition",
+ {
+ "text": "Triggered By",
+ "type": "PlasmaConditionSocket",
+ "spawn_empty": True,
+ },
+ ),
+ ]
+ )
+
+ output_sockets = OrderedDict(
+ [
+ (
+ "hosts",
+ {
+ "text": "Host Script",
+ "type": "PlasmaBehaviorSocket",
+ "valid_link_nodes": {"PlasmaPythonFileNode"},
+ "spawn_empty": True,
+ },
+ ),
+ (
+ "satisfies",
+ {
+ "text": "Trigger",
+ "type": "PlasmaConditionSocket",
+ },
+ ),
+ ]
+ )
def draw_buttons(self, context, layout):
layout.prop(self, "freeze_phys")
@@ -337,7 +485,9 @@ class PlasmaMultiStageBehaviorNode(PlasmaNodeBase, bpy.types.Node):
if seek_socket.is_linked:
seek_target = seek_socket.links[0].from_node.target
if seek_target is not None:
- seek_object = exporter.mgr.find_create_object(plSceneObject, bl=seek_target)
+ seek_object = exporter.mgr.find_create_object(
+ plSceneObject, bl=seek_target
+ )
else:
self.raise_error("MultiStage Behavior's seek point object is invalid")
else:
@@ -349,7 +499,9 @@ class PlasmaMultiStageBehaviorNode(PlasmaNodeBase, bpy.types.Node):
seek_socket = self.find_input_socket("seek_target")
msbmod = self.get_key(exporter, so).object
- msbmod.smartSeek = True if seek_socket.is_linked or seek_socket.auto_target else False
+ msbmod.smartSeek = (
+ True if seek_socket.is_linked or seek_socket.auto_target else False
+ )
msbmod.freezePhys = self.freeze_phys
msbmod.reverseFBControlsOnRelease = self.reverse_control
@@ -365,7 +517,7 @@ class PlasmaMultiStageBehaviorNode(PlasmaNodeBase, bpy.types.Node):
settings = stage.find_input("stage_settings")
if settings:
animstage.forwardType = getattr(plAnimStage, settings.forward)
- animstage.backType =getattr(plAnimStage, settings.backward)
+ animstage.backType = getattr(plAnimStage, settings.backward)
animstage.advanceType = getattr(plAnimStage, settings.stage_advance)
animstage.regressType = getattr(plAnimStage, settings.stage_regress)
for flag in settings.notify_on:
@@ -395,13 +547,20 @@ class PlasmaMultiStageBehaviorNode(PlasmaNodeBase, bpy.types.Node):
msbmod.addStage(animstage)
- receivers = ((i, i.get_key(exporter, so)) for i in self.find_outputs("satisfies"))
+ receivers = (
+ (i, i.get_key(exporter, so)) for i in self.find_outputs("satisfies")
+ )
for node, key in receivers:
if key is not None:
msbmod.addReceiver(key)
else:
- exporter.report.warn("'{}' Node '{}' doesn't expose a key. It won't be triggered by '{}'!",
- node.bl_idname, node.name, self.name, indent=3)
+ exporter.report.warn(
+ "'{}' Node '{}' doesn't expose a key. It won't be triggered by '{}'!",
+ node.bl_idname,
+ node.name,
+ self.name,
+ indent=3,
+ )
@property
def requires_actor(self):
@@ -423,7 +582,15 @@ class PlasmaAnimStageRefSocket(PlasmaNodeSocketBase, bpy.types.NodeSocket):
def draw_content(self, context, layout, node, text):
if isinstance(node, PlasmaMultiStageBehaviorNode):
try:
- idx = next((idx for idx, socket in enumerate(node.find_input_sockets("stage_refs")) if socket == self))
+ idx = next(
+ (
+ idx
+ for idx, socket in enumerate(
+ node.find_input_sockets("stage_refs")
+ )
+ if socket == self
+ )
+ )
except StopIteration:
layout.label(text)
else:
@@ -434,9 +601,11 @@ class PlasmaAnimStageRefSocket(PlasmaNodeSocketBase, bpy.types.NodeSocket):
class PlasmaSeekTargetSocketIn(PlasmaNodeSocketBase, bpy.types.NodeSocket):
bl_color = (0.180, 0.350, 0.180, 1.0)
- auto_target = BoolProperty(name="Auto Smart Seek",
- description="Smart Seek causes the avatar to seek to the provided position before starting the behavior ('auto' will use the current object as the seek point)",
- default=False)
+ auto_target = BoolProperty(
+ name="Auto Smart Seek",
+ description="Smart Seek causes the avatar to seek to the provided position before starting the behavior ('auto' will use the current object as the seek point)",
+ default=False,
+ )
def draw_content(self, context, layout, node, text):
if not self.is_linked:
@@ -459,18 +628,28 @@ class PlasmaSeekTargetNode(PlasmaNodeBase, bpy.types.Node):
bl_label = "Seek Target"
bl_width_default = 200
- target = PointerProperty(name="Position",
- description="Object defining the Seek Point's position",
- type=bpy.types.Object)
-
- output_sockets = OrderedDict([
- ("seekers", {
- "text": "Seekers",
- "type": "PlasmaSeekTargetSocketOut",
- "valid_link_nodes": {"PlasmaMultiStageBehaviorNode", "PlasmaOneShotMsgNode"},
- "valid_link_sockets": {"PlasmaSeekTargetSocketIn"},
- })
- ])
+ target = PointerProperty(
+ name="Position",
+ description="Object defining the Seek Point's position",
+ type=bpy.types.Object,
+ )
+
+ output_sockets = OrderedDict(
+ [
+ (
+ "seekers",
+ {
+ "text": "Seekers",
+ "type": "PlasmaSeekTargetSocketOut",
+ "valid_link_nodes": {
+ "PlasmaMultiStageBehaviorNode",
+ "PlasmaOneShotMsgNode",
+ },
+ "valid_link_sockets": {"PlasmaSeekTargetSocketIn"},
+ },
+ )
+ ]
+ )
def draw_buttons(self, context, layout):
col = layout.column()
diff --git a/korman/nodes/node_conditions.py b/korman/nodes/node_conditions.py
index 5ea931f..525d894 100644
--- a/korman/nodes/node_conditions.py
+++ b/korman/nodes/node_conditions.py
@@ -23,6 +23,7 @@ from .node_core import *
from ..properties.modifiers.physics import bounds_types
from .. import idprops
+
class PlasmaClickableNode(idprops.IDPropObjectMixin, PlasmaNodeBase, bpy.types.Node):
bl_category = "CONDITIONS"
bl_idname = "PlasmaClickableNode"
@@ -32,38 +33,61 @@ class PlasmaClickableNode(idprops.IDPropObjectMixin, PlasmaNodeBase, bpy.types.N
# These are the Python attributes we can fill in
pl_attrib = {"ptAttribActivator", "ptAttribActivatorList", "ptAttribNamedActivator"}
- clickable_object = PointerProperty(name="Clickable",
- description="Mesh object that is clickable",
- type=bpy.types.Object,
- poll=idprops.poll_mesh_objects)
- bounds = EnumProperty(name="Bounds",
- description="Clickable's bounds (NOTE: only used if your clickable is not a collider)",
- items=bounds_types,
- default="hull")
-
- input_sockets = OrderedDict([
- ("region", {
- "text": "Avatar Inside Region",
- "type": "PlasmaClickableRegionSocket",
- }),
- ("facing", {
- "text": "Avatar Facing Target",
- "type": "PlasmaFacingTargetSocket",
- }),
- ("message", {
- "text": "Message",
- "type": "PlasmaEnableMessageSocket",
- "spawn_empty": True,
- }),
- ])
-
- output_sockets = OrderedDict([
- ("satisfies", {
- "text": "Satisfies",
- "type": "PlasmaConditionSocket",
- "valid_link_sockets": {"PlasmaConditionSocket", "PlasmaPythonFileNodeSocket"},
- }),
- ])
+ clickable_object = PointerProperty(
+ name="Clickable",
+ description="Mesh object that is clickable",
+ type=bpy.types.Object,
+ poll=idprops.poll_mesh_objects,
+ )
+ bounds = EnumProperty(
+ name="Bounds",
+ description="Clickable's bounds (NOTE: only used if your clickable is not a collider)",
+ items=bounds_types,
+ default="hull",
+ )
+
+ input_sockets = OrderedDict(
+ [
+ (
+ "region",
+ {
+ "text": "Avatar Inside Region",
+ "type": "PlasmaClickableRegionSocket",
+ },
+ ),
+ (
+ "facing",
+ {
+ "text": "Avatar Facing Target",
+ "type": "PlasmaFacingTargetSocket",
+ },
+ ),
+ (
+ "message",
+ {
+ "text": "Message",
+ "type": "PlasmaEnableMessageSocket",
+ "spawn_empty": True,
+ },
+ ),
+ ]
+ )
+
+ output_sockets = OrderedDict(
+ [
+ (
+ "satisfies",
+ {
+ "text": "Satisfies",
+ "type": "PlasmaConditionSocket",
+ "valid_link_sockets": {
+ "PlasmaConditionSocket",
+ "PlasmaPythonFileNodeSocket",
+ },
+ },
+ ),
+ ]
+ )
def draw_buttons(self, context, layout):
layout.prop(self, "clickable_object", icon="MESH_DATA")
@@ -74,8 +98,12 @@ class PlasmaClickableNode(idprops.IDPropObjectMixin, PlasmaNodeBase, bpy.types.N
if clickable_bo is None:
clickable_bo = parent_bo
- interface = self._find_create_object(plInterfaceInfoModifier, exporter, bl=clickable_bo, so=clickable_so)
- logicmod = self._find_create_key(plLogicModifier, exporter, bl=clickable_bo, so=clickable_so)
+ interface = self._find_create_object(
+ plInterfaceInfoModifier, exporter, bl=clickable_bo, so=clickable_so
+ )
+ logicmod = self._find_create_key(
+ plLogicModifier, exporter, bl=clickable_bo, so=clickable_so
+ )
interface.addIntfKey(logicmod)
# Matches data seen in Cyan's PRPs...
interface.addIntfKey(logicmod)
@@ -91,17 +119,25 @@ class PlasmaClickableNode(idprops.IDPropObjectMixin, PlasmaNodeBase, bpy.types.N
bounds = phys_mod.bounds if phys_mod.enabled else self.bounds
# The actual physical object that does the cursor LOS
- exporter.physics.generate_physical(clickable_bo, clickable_so, bounds=bounds,
- member_group="kGroupLOSOnly",
- properties=["kPinned"],
- losdbs=["kLOSDBUIItems"])
+ exporter.physics.generate_physical(
+ clickable_bo,
+ clickable_so,
+ bounds=bounds,
+ member_group="kGroupLOSOnly",
+ properties=["kPinned"],
+ losdbs=["kLOSDBUIItems"],
+ )
# Picking Detector -- detect when the physical is clicked
- detector = self._find_create_object(plPickingDetector, exporter, bl=clickable_bo, so=clickable_so)
+ detector = self._find_create_object(
+ plPickingDetector, exporter, bl=clickable_bo, so=clickable_so
+ )
detector.addReceiver(logicmod.key)
# Clickable
- activator = self._find_create_object(plActivatorConditionalObject, exporter, bl=clickable_bo, so=clickable_so)
+ activator = self._find_create_object(
+ plActivatorConditionalObject, exporter, bl=clickable_bo, so=clickable_so
+ )
activator.addActivator(detector.key)
logicmod.addCondition(activator.key)
logicmod.setLogicFlag(plLogicModifier.kLocalElement, True)
@@ -124,7 +160,9 @@ class PlasmaClickableNode(idprops.IDPropObjectMixin, PlasmaNodeBase, bpy.types.N
def get_key(self, exporter, parent_so):
# careful... we really make lots of keys...
clickable_bo, clickable_so = self._get_objects(exporter, parent_so)
- key = self._find_create_key(plLogicModifier, exporter, bl=clickable_bo, so=clickable_so)
+ key = self._find_create_key(
+ plLogicModifier, exporter, bl=clickable_bo, so=clickable_so
+ )
return key
def _get_objects(self, exporter, parent_so):
@@ -132,7 +170,9 @@ class PlasmaClickableNode(idprops.IDPropObjectMixin, PlasmaNodeBase, bpy.types.N
# We do this because we might be exporting from a BO that is not actually the clickable object.
# Case: sitting modifier (exports from sit position empty)
if self.clickable_object:
- clickable_so = exporter.mgr.find_create_object(plSceneObject, bl=self.clickable_object)
+ clickable_so = exporter.mgr.find_create_object(
+ plSceneObject, bl=self.clickable_object
+ )
return (self.clickable_object, clickable_so)
else:
return (None, parent_so)
@@ -150,27 +190,38 @@ class PlasmaClickableNode(idprops.IDPropObjectMixin, PlasmaNodeBase, bpy.types.N
return {"clickable_object": "clickable"}
-class PlasmaClickableRegionNode(idprops.IDPropObjectMixin, PlasmaNodeBase, bpy.types.Node):
+class PlasmaClickableRegionNode(
+ idprops.IDPropObjectMixin, PlasmaNodeBase, bpy.types.Node
+):
bl_category = "CONDITIONS"
bl_idname = "PlasmaClickableRegionNode"
bl_label = "Clickable Region Settings"
bl_width_default = 200
- region_object = PointerProperty(name="Region",
- description="Object that defines the region mesh",
- type=bpy.types.Object,
- poll=idprops.poll_mesh_objects)
- bounds = EnumProperty(name="Bounds",
- description="Physical object's bounds (NOTE: only used if your clickable is not a collider)",
- items=bounds_types,
- default="hull")
-
- output_sockets = OrderedDict([
- ("satisfies", {
- "text": "Satisfies",
- "type": "PlasmaClickableRegionSocket",
- }),
- ])
+ region_object = PointerProperty(
+ name="Region",
+ description="Object that defines the region mesh",
+ type=bpy.types.Object,
+ poll=idprops.poll_mesh_objects,
+ )
+ bounds = EnumProperty(
+ name="Bounds",
+ description="Physical object's bounds (NOTE: only used if your clickable is not a collider)",
+ items=bounds_types,
+ default="hull",
+ )
+
+ output_sockets = OrderedDict(
+ [
+ (
+ "satisfies",
+ {
+ "text": "Satisfies",
+ "type": "PlasmaClickableRegionSocket",
+ },
+ ),
+ ]
+ )
def draw_buttons(self, context, layout):
layout.prop(self, "region_object", icon="MESH_DATA")
@@ -188,22 +239,30 @@ class PlasmaClickableRegionNode(idprops.IDPropObjectMixin, PlasmaNodeBase, bpy.t
bounds = phys_mod.bounds if phys_mod.enabled else self.bounds
# Our physical is a detector and it only detects avatars...
- exporter.physics.generate_physical(region_bo, region_so, bounds=bounds,
- member_group="kGroupDetector",
- report_groups=["kGroupAvatar"])
+ exporter.physics.generate_physical(
+ region_bo,
+ region_so,
+ bounds=bounds,
+ member_group="kGroupDetector",
+ report_groups=["kGroupAvatar"],
+ )
# I'm glad this crazy mess made sense to someone at Cyan...
# ObjectInVolumeDetector can notify multiple logic mods. This implies we could share this
# one detector for many unrelated logic mods. However, LogicMods and Conditions appear to
# assume they pwn each other... so we need a unique detector. This detector must be attached
# as a modifier to the region's SO however.
- detector = self._find_create_object(plObjectInVolumeDetector, exporter, bl=region_bo, so=region_so)
+ detector = self._find_create_object(
+ plObjectInVolumeDetector, exporter, bl=region_bo, so=region_so
+ )
detector.addReceiver(logicmod.key)
detector.type = plObjectInVolumeDetector.kTypeAny
# Now, the conditional object. At this point, these seem very silly. At least it's not a plModifier.
# All they really do is hold a satisfied boolean...
- objinbox_key = self._find_create_key(plObjectInBoxConditionalObject, exporter, bl=region_bo, so=parent_so)
+ objinbox_key = self._find_create_key(
+ plObjectInBoxConditionalObject, exporter, bl=region_bo, so=parent_so
+ )
objinbox_key.object.satisfied = True
logicmod.addCondition(objinbox_key)
@@ -225,19 +284,26 @@ class PlasmaFacingTargetNode(PlasmaNodeBase, bpy.types.Node):
bl_idname = "PlasmaFacingTargetNode"
bl_label = "Facing Target"
- directional = BoolProperty(name="Directional",
- description="TODO",
- default=True)
- tolerance = IntProperty(name="Degrees",
- description="How far away from the target the avatar can turn (in degrees)",
- min=-180, max=180, default=45)
-
- output_sockets = OrderedDict([
- ("satisfies", {
- "text": "Satisfies",
- "type": "PlasmaFacingTargetSocket",
- }),
- ])
+ directional = BoolProperty(name="Directional", description="TODO", default=True)
+ tolerance = IntProperty(
+ name="Degrees",
+ description="How far away from the target the avatar can turn (in degrees)",
+ min=-180,
+ max=180,
+ default=45,
+ )
+
+ output_sockets = OrderedDict(
+ [
+ (
+ "satisfies",
+ {
+ "text": "Satisfies",
+ "type": "PlasmaFacingTargetSocket",
+ },
+ ),
+ ]
+ )
def draw_buttons(self, context, layout):
layout.prop(self, "directional")
@@ -247,9 +313,11 @@ class PlasmaFacingTargetNode(PlasmaNodeBase, bpy.types.Node):
class PlasmaFacingTargetSocket(PlasmaNodeSocketBase, bpy.types.NodeSocket):
bl_color = (0.0, 0.267, 0.247, 1.0)
- allow_simple = BoolProperty(name="Facing Target",
- description="Avatar must be facing the target object",
- default=True)
+ allow_simple = BoolProperty(
+ name="Facing Target",
+ description="Avatar must be facing the target object",
+ default=True,
+ )
def draw_content(self, context, layout, node, text):
if self.simple_mode:
@@ -274,7 +342,9 @@ class PlasmaFacingTargetSocket(PlasmaNodeSocketBase, bpy.types.NodeSocket):
# This is a programmer failure, so we need a traceback.
raise RuntimeError("Tried to export an unused PlasmaFacingTargetSocket")
- facing_key = node._find_create_key(plFacingConditionalObject, exporter, bl=bo, so=so)
+ facing_key = node._find_create_key(
+ plFacingConditionalObject, exporter, bl=bo, so=so
+ )
facing = facing_key.object
facing.directional = directional
facing.satisfied = True
@@ -283,13 +353,13 @@ class PlasmaFacingTargetSocket(PlasmaNodeSocketBase, bpy.types.NodeSocket):
@property
def enable_condition(self):
- return ((self.simple_mode and self.allow_simple) or self.is_linked)
+ return (self.simple_mode and self.allow_simple) or self.is_linked
@property
def simple_mode(self):
"""Simple mode allows a user to click a button on input sockets to automatically generate a
- Facing Target condition"""
- return (not self.is_linked and not self.is_output)
+ Facing Target condition"""
+ return not self.is_linked and not self.is_output
class PlasmaVolumeReportNode(PlasmaNodeBase, bpy.types.Node):
@@ -297,22 +367,37 @@ class PlasmaVolumeReportNode(PlasmaNodeBase, bpy.types.Node):
bl_idname = "PlasmaVolumeReportNode"
bl_label = "Region Trigger Settings"
- report_when = EnumProperty(name="When",
- description="When the region should trigger",
- items=[("each", "Each Event", "The region will trigger on every enter/exit"),
- ("first", "First Event", "The region will trigger on the first event only"),
- ("count", "Population", "When the region has a certain number of objects inside it")])
- threshold = IntProperty(name="Threshold",
- description="How many objects should be in the region for it to trigger",
- min=0)
-
- output_sockets = OrderedDict([
- ("settings", {
- "text": "Trigger Settings",
- "type": "PlasmaVolumeSettingsSocketOut",
- "valid_link_sockets": {"PlasmaVolumeSettingsSocketIn"},
- }),
- ])
+ report_when = EnumProperty(
+ name="When",
+ description="When the region should trigger",
+ items=[
+ ("each", "Each Event", "The region will trigger on every enter/exit"),
+ ("first", "First Event", "The region will trigger on the first event only"),
+ (
+ "count",
+ "Population",
+ "When the region has a certain number of objects inside it",
+ ),
+ ],
+ )
+ threshold = IntProperty(
+ name="Threshold",
+ description="How many objects should be in the region for it to trigger",
+ min=0,
+ )
+
+ output_sockets = OrderedDict(
+ [
+ (
+ "settings",
+ {
+ "text": "Trigger Settings",
+ "type": "PlasmaVolumeSettingsSocketOut",
+ "valid_link_sockets": {"PlasmaVolumeSettingsSocketIn"},
+ },
+ ),
+ ]
+ )
def draw_buttons(self, context, layout):
layout.prop(self, "report_when")
@@ -332,47 +417,76 @@ class PlasmaVolumeSensorNode(idprops.IDPropObjectMixin, PlasmaNodeBase, bpy.type
pl_attrib = {"ptAttribActivator", "ptAttribActivatorList", "ptAttribNamedActivator"}
# Region Mesh
- region_object = PointerProperty(name="Region",
- description="Object that defines the region mesh",
- type=bpy.types.Object,
- poll=idprops.poll_mesh_objects)
- bounds = EnumProperty(name="Bounds",
- description="Physical object's bounds",
- items=bounds_types)
+ region_object = PointerProperty(
+ name="Region",
+ description="Object that defines the region mesh",
+ type=bpy.types.Object,
+ poll=idprops.poll_mesh_objects,
+ )
+ bounds = EnumProperty(
+ name="Bounds", description="Physical object's bounds", items=bounds_types
+ )
# Detector Properties
- report_on = EnumProperty(name="Triggerers",
- description="What triggers this region?",
- options={"ANIMATABLE", "ENUM_FLAG"},
- items=[("kGroupAvatar", "Avatars", "Avatars trigger this region"),
- ("kGroupDynamic", "Dynamics", "Any non-avatar dynamic physical object (eg kickables)")],
- default={"kGroupAvatar"})
-
- input_sockets = OrderedDict([
- ("enter", {
- "text": "Trigger on Enter",
- "type": "PlasmaVolumeSettingsSocketIn",
- "valid_link_sockets": {"PlasmaVolumeSettingsSocketOut"},
- }),
- ("exit", {
- "text": "Trigger on Exit",
- "type": "PlasmaVolumeSettingsSocketIn",
- "valid_link_sockets": {"PlasmaVolumeSettingsSocketOut"},
- }),
- ("message", {
- "text": "Message",
- "type": "PlasmaEnableMessageSocket",
- "spawn_empty": True,
- }),
- ])
-
- output_sockets = OrderedDict([
- ("satisfies", {
- "text": "Satisfies",
- "type": "PlasmaConditionSocket",
- "valid_link_sockets": {"PlasmaConditionSocket", "PlasmaPythonFileNodeSocket"},
- }),
- ])
+ report_on = EnumProperty(
+ name="Triggerers",
+ description="What triggers this region?",
+ options={"ANIMATABLE", "ENUM_FLAG"},
+ items=[
+ ("kGroupAvatar", "Avatars", "Avatars trigger this region"),
+ (
+ "kGroupDynamic",
+ "Dynamics",
+ "Any non-avatar dynamic physical object (eg kickables)",
+ ),
+ ],
+ default={"kGroupAvatar"},
+ )
+
+ input_sockets = OrderedDict(
+ [
+ (
+ "enter",
+ {
+ "text": "Trigger on Enter",
+ "type": "PlasmaVolumeSettingsSocketIn",
+ "valid_link_sockets": {"PlasmaVolumeSettingsSocketOut"},
+ },
+ ),
+ (
+ "exit",
+ {
+ "text": "Trigger on Exit",
+ "type": "PlasmaVolumeSettingsSocketIn",
+ "valid_link_sockets": {"PlasmaVolumeSettingsSocketOut"},
+ },
+ ),
+ (
+ "message",
+ {
+ "text": "Message",
+ "type": "PlasmaEnableMessageSocket",
+ "spawn_empty": True,
+ },
+ ),
+ ]
+ )
+
+ output_sockets = OrderedDict(
+ [
+ (
+ "satisfies",
+ {
+ "text": "Satisfies",
+ "type": "PlasmaConditionSocket",
+ "valid_link_sockets": {
+ "PlasmaConditionSocket",
+ "PlasmaPythonFileNodeSocket",
+ },
+ },
+ ),
+ ]
+ )
def draw_buttons(self, context, layout):
layout.prop(self, "report_on")
@@ -390,9 +504,13 @@ class PlasmaVolumeSensorNode(idprops.IDPropObjectMixin, PlasmaNodeBase, bpy.type
parent_key = parent_so.key
if self.report_enters:
- rgn_enter = self._find_create_key(plLogicModifier, exporter, suffix="Enter", bl=bo, so=so)
+ rgn_enter = self._find_create_key(
+ plLogicModifier, exporter, suffix="Enter", bl=bo, so=so
+ )
if self.report_exits:
- rgn_exit = self._find_create_key(plLogicModifier, exporter, suffix="Exit", bl=bo, so=so)
+ rgn_exit = self._find_create_key(
+ plLogicModifier, exporter, suffix="Exit", bl=bo, so=so
+ )
if rgn_enter is None:
return rgn_exit
@@ -411,42 +529,78 @@ class PlasmaVolumeSensorNode(idprops.IDPropObjectMixin, PlasmaNodeBase, bpy.type
self.raise_error("Region cannot be empty")
region_so = exporter.mgr.find_create_object(plSceneObject, bl=region_bo)
- interface = self._find_create_object(plInterfaceInfoModifier, exporter, bl=region_bo, so=region_so)
+ interface = self._find_create_object(
+ plInterfaceInfoModifier, exporter, bl=region_bo, so=region_so
+ )
# Region Enters
enter_simple = self.find_input_socket("enter").allow
enter_settings = self.find_input("enter", "PlasmaVolumeReportNode")
if enter_simple or enter_settings is not None:
- key = self._export_volume_event(exporter, region_bo, region_so, parent_so, plVolumeSensorConditionalObject.kTypeEnter, enter_settings)
+ key = self._export_volume_event(
+ exporter,
+ region_bo,
+ region_so,
+ parent_so,
+ plVolumeSensorConditionalObject.kTypeEnter,
+ enter_settings,
+ )
interface.addIntfKey(key)
# Region Exits
exit_simple = self.find_input_socket("exit").allow
exit_settings = self.find_input("exit", "PlasmaVolumeReportNode")
if exit_simple or exit_settings is not None:
- key = self._export_volume_event(exporter, region_bo, region_so, parent_so, plVolumeSensorConditionalObject.kTypeExit, exit_settings)
+ key = self._export_volume_event(
+ exporter,
+ region_bo,
+ region_so,
+ parent_so,
+ plVolumeSensorConditionalObject.kTypeExit,
+ exit_settings,
+ )
interface.addIntfKey(key)
# Don't forget to export the physical object itself!
- exporter.physics.generate_physical(region_bo, region_so, bounds=self.bounds,
- member_group="kGroupDetector",
- report_groups=self.report_on)
-
- def _export_volume_event(self, exporter, region_bo, region_so, parent_so, event, settings):
+ exporter.physics.generate_physical(
+ region_bo,
+ region_so,
+ bounds=self.bounds,
+ member_group="kGroupDetector",
+ report_groups=self.report_on,
+ )
+
+ def _export_volume_event(
+ self, exporter, region_bo, region_so, parent_so, event, settings
+ ):
if event == plVolumeSensorConditionalObject.kTypeEnter:
suffix = "Enter"
else:
suffix = "Exit"
- logicKey = self._find_create_key(plLogicModifier, exporter, suffix=suffix, bl=region_bo, so=region_so)
+ logicKey = self._find_create_key(
+ plLogicModifier, exporter, suffix=suffix, bl=region_bo, so=region_so
+ )
logicmod = logicKey.object
logicmod.setLogicFlag(plLogicModifier.kMultiTrigger, True)
logicmod.notify = self.generate_notify_msg(exporter, parent_so, "satisfies")
# Now, the detector objects
- det = self._find_create_object(plObjectInVolumeDetector, exporter, suffix=suffix, bl=region_bo, so=region_so)
-
- volKey = self._find_create_key(plVolumeSensorConditionalObject, exporter, suffix=suffix, bl=region_bo, so=region_so)
+ det = self._find_create_object(
+ plObjectInVolumeDetector,
+ exporter,
+ suffix=suffix,
+ bl=region_bo,
+ so=region_so,
+ )
+
+ volKey = self._find_create_key(
+ plVolumeSensorConditionalObject,
+ exporter,
+ suffix=suffix,
+ bl=region_bo,
+ so=region_so,
+ )
volsens = volKey.object
volsens.type = event
@@ -474,13 +628,17 @@ class PlasmaVolumeSensorNode(idprops.IDPropObjectMixin, PlasmaNodeBase, bpy.type
@property
def report_enters(self):
- return (self.find_input_socket("enter").allow or
- self.find_input("enter", "PlasmaVolumeReportNode") is not None)
+ return (
+ self.find_input_socket("enter").allow
+ or self.find_input("enter", "PlasmaVolumeReportNode") is not None
+ )
@property
def report_exits(self):
- return (self.find_input_socket("exit").allow or
- self.find_input("exit", "PlasmaVolumeReportNode") is not None)
+ return (
+ self.find_input_socket("exit").allow
+ or self.find_input("exit", "PlasmaVolumeReportNode") is not None
+ )
class PlasmaVolumeSettingsSocket(PlasmaNodeSocketBase):
diff --git a/korman/nodes/node_core.py b/korman/nodes/node_core.py
index 86a2d3f..a784310 100644
--- a/korman/nodes/node_core.py
+++ b/korman/nodes/node_core.py
@@ -21,14 +21,20 @@ import time
from ..exporter import ExportError
+
class PlasmaNodeBase:
def generate_notify_msg(self, exporter, so, socket_id, idname=None):
notify = plNotifyMsg()
- notify.BCastFlags = (plMessage.kNetPropagate | plMessage.kLocalPropagate)
+ notify.BCastFlags = plMessage.kNetPropagate | plMessage.kLocalPropagate
for i in self.find_outputs(socket_id, idname):
key = i.get_key(exporter, so)
if key is None:
- exporter.report.warn(" '{}' Node '{}' doesn't expose a key. It won't be triggered by '{}'!".format(i.bl_idname, i.name, self.name), indent=3)
+ exporter.report.warn(
+ " '{}' Node '{}' doesn't expose a key. It won't be triggered by '{}'!".format(
+ i.bl_idname, i.name, self.name
+ ),
+ indent=3,
+ )
elif isinstance(key, tuple):
for i in key:
notify.addReceiver(key)
@@ -44,7 +50,9 @@ class PlasmaNodeBase:
if single:
name = bl.name if bl is not None else so.key.name
if suffix:
- working_name = "{}_{}_{}_{}".format(name, self.id_data.name, self.name, suffix)
+ working_name = "{}_{}_{}_{}".format(
+ name, self.id_data.name, self.name, suffix
+ )
else:
working_name = "{}_{}_{}".format(name, self.id_data.name, self.name)
else:
@@ -70,17 +78,23 @@ class PlasmaNodeBase:
def _find_create_object(self, pClass, exporter, **kwargs):
"""Finds or creates an hsKeyedObject specific to this node."""
assert "name" not in kwargs
- kwargs["name"] = self.get_key_name(issubclass(pClass, (plObjInterface, plSingleModifier)),
- kwargs.pop("suffix", ""), kwargs.get("bl"),
- kwargs.get("so"))
+ kwargs["name"] = self.get_key_name(
+ issubclass(pClass, (plObjInterface, plSingleModifier)),
+ kwargs.pop("suffix", ""),
+ kwargs.get("bl"),
+ kwargs.get("so"),
+ )
return exporter.mgr.find_create_object(pClass, **kwargs)
def _find_create_key(self, pClass, exporter, **kwargs):
"""Finds or creates a plKey specific to this node."""
assert "name" not in kwargs
- kwargs["name"] = self.get_key_name(issubclass(pClass, (plObjInterface, plSingleModifier)),
- kwargs.pop("suffix", ""), kwargs.get("bl"),
- kwargs.get("so"))
+ kwargs["name"] = self.get_key_name(
+ issubclass(pClass, (plObjInterface, plSingleModifier)),
+ kwargs.pop("suffix", ""),
+ kwargs.get("bl"),
+ kwargs.get("so"),
+ )
return exporter.mgr.find_create_key(pClass, **kwargs)
def find_input(self, key, idname=None):
@@ -181,19 +195,27 @@ class PlasmaNodeBase:
"""Generates valid node sockets that can be linked to a specific socket on this node."""
from .node_deprecated import PlasmaDeprecatedNode
- source_socket_props = getattr(self.__class__, "output_sockets", {}) if is_output else \
- getattr(self.__class__, "input_sockets", {})
+ source_socket_props = (
+ getattr(self.__class__, "output_sockets", {})
+ if is_output
+ else getattr(self.__class__, "input_sockets", {})
+ )
source_socket_def = source_socket_props.get(socket.alias, {})
valid_dest_sockets = source_socket_def.get("valid_link_sockets")
valid_dest_nodes = source_socket_def.get("valid_link_nodes")
for dest_node_cls in bpy.types.Node.__subclasses__():
- if not issubclass(dest_node_cls, PlasmaNodeBase) or issubclass(dest_node_cls, PlasmaDeprecatedNode):
+ if not issubclass(dest_node_cls, PlasmaNodeBase) or issubclass(
+ dest_node_cls, PlasmaDeprecatedNode
+ ):
continue
# Korman standard node socket definitions
- socket_defs = getattr(dest_node_cls, "input_sockets", {}) if is_output else \
- getattr(dest_node_cls, "output_sockets", {})
+ socket_defs = (
+ getattr(dest_node_cls, "input_sockets", {})
+ if is_output
+ else getattr(dest_node_cls, "output_sockets", {})
+ )
for socket_name, socket_def in socket_defs.items():
if socket_def.get("can_link") is False:
continue
@@ -201,17 +223,29 @@ class PlasmaNodeBase:
continue
# Can this socket link to the socket_def on the destination node?
- if valid_dest_nodes is not None and dest_node_cls.bl_idname not in valid_dest_nodes:
+ if (
+ valid_dest_nodes is not None
+ and dest_node_cls.bl_idname not in valid_dest_nodes
+ ):
continue
- if valid_dest_sockets is not None and socket_def["type"] not in valid_dest_sockets:
+ if (
+ valid_dest_sockets is not None
+ and socket_def["type"] not in valid_dest_sockets
+ ):
continue
# Can the socket_def on the destination node link to this socket?
valid_source_nodes = socket_def.get("valid_link_nodes")
valid_source_sockets = socket_def.get("valid_link_sockets")
- if valid_source_nodes is not None and self.bl_idname not in valid_source_nodes:
+ if (
+ valid_source_nodes is not None
+ and self.bl_idname not in valid_source_nodes
+ ):
continue
- if valid_source_sockets is not None and socket.bl_idname not in valid_source_sockets:
+ if (
+ valid_source_sockets is not None
+ and socket.bl_idname not in valid_source_sockets
+ ):
continue
if valid_source_sockets is None and valid_source_nodes is None:
if socket.bl_idname != socket_def["type"]:
@@ -222,10 +256,12 @@ class PlasmaNodeBase:
if poll_add is not None and not poll_add(context):
continue
- yield { "node_idname": dest_node_cls.bl_idname,
- "node_text": dest_node_cls.bl_label,
- "socket_name": socket_name,
- "socket_text": socket_def["text"] }
+ yield {
+ "node_idname": dest_node_cls.bl_idname,
+ "node_text": dest_node_cls.bl_label,
+ "socket_name": socket_name,
+ "socket_text": socket_def["text"],
+ }
# Some node types (eg Python) may auto-generate their own sockets, so we ask them now.
for i in dest_node_cls.generate_valid_links_to(context, socket, is_output):
@@ -273,10 +309,12 @@ class PlasmaNodeBase:
@classmethod
def poll(cls, context):
- return (context.bl_idname == "PlasmaNodeTree")
+ return context.bl_idname == "PlasmaNodeTree"
def raise_error(self, message):
- final = "Plasma Node Tree '{}' Node '{}': {}".format(self.id_data.name, self.name, message)
+ final = "Plasma Node Tree '{}' Node '{}': {}".format(
+ self.id_data.name, self.name, message
+ )
raise ExportError(final)
@property
@@ -285,8 +323,10 @@ class PlasmaNodeBase:
@property
def _socket_defs(self):
- return (getattr(self.__class__, "input_sockets", {}),
- getattr(self.__class__, "output_sockets", {}))
+ return (
+ getattr(self.__class__, "input_sockets", {}),
+ getattr(self.__class__, "output_sockets", {}),
+ )
def _spawn_socket(self, key, options, sockets):
socket = sockets.new(options["type"], options["text"], key)
@@ -299,7 +339,11 @@ class PlasmaNodeBase:
def _tattle(self, socket, link, reason):
direction = "->" if socket.is_output else "<-"
- print("Removing {} {} {} {}".format(link.from_node.name, direction, link.to_node.name, reason))
+ print(
+ "Removing {} {} {} {}".format(
+ link.from_node.name, direction, link.to_node.name, reason
+ )
+ )
def unlink_outputs(self, alias, reason=None):
links = self.id_data.links
@@ -320,7 +364,9 @@ class PlasmaNodeBase:
def _update_init_sockets(self, defs, sockets):
# Create any missing sockets and spawn any required empties.
for alias, options in defs.items():
- working_sockets = [(i, socket) for i, socket in enumerate(sockets) if socket.alias == alias]
+ working_sockets = [
+ (i, socket) for i, socket in enumerate(sockets) if socket.alias == alias
+ ]
if not working_sockets:
self._spawn_socket(alias, options, sockets)
elif options.get("spawn_empty", False):
@@ -382,7 +428,9 @@ class PlasmaNodeBase:
if allowed_sockets or allowed_nodes:
for link in socket.links:
if allowed_nodes:
- to_from_node = link.to_node if socket.is_output else link.from_node
+ to_from_node = (
+ link.to_node if socket.is_output else link.from_node
+ )
if to_from_node.bl_idname not in allowed_nodes:
try:
self._tattle(socket, link, "(bad node)")
@@ -392,8 +440,13 @@ class PlasmaNodeBase:
pass
continue
if allowed_sockets:
- to_from_socket = link.to_socket if socket.is_output else link.from_socket
- if to_from_socket is None or to_from_socket.bl_idname not in allowed_sockets:
+ to_from_socket = (
+ link.to_socket if socket.is_output else link.from_socket
+ )
+ if (
+ to_from_socket is None
+ or to_from_socket.bl_idname not in allowed_sockets
+ ):
try:
self._tattle(socket, link, "(bad socket)")
self.id_data.links.remove(link)
@@ -407,16 +460,21 @@ class PlasmaNodeBase:
def _whine(self, msg, *args):
if args:
msg = msg.format(*args)
- print("'{}' Node '{}': Whinging about {}".format(self.bl_idname, self.name, msg))
+ print(
+ "'{}' Node '{}': Whinging about {}".format(self.bl_idname, self.name, msg)
+ )
class PlasmaTreeOutputNodeBase(PlasmaNodeBase):
"""Represents the final output of a node tree"""
+
@classmethod
def poll_add(cls, context):
# There can only be one of these nodes per tree, so we will only allow this to be
# added if no other output nodes are found.
- return not any((isinstance(node, cls) for node in context.space_data.node_tree.nodes))
+ return not any(
+ (isinstance(node, cls) for node in context.space_data.node_tree.nodes)
+ )
class PlasmaNodeSocketBase:
@@ -424,9 +482,9 @@ class PlasmaNodeSocketBase:
def alias(self):
"""Blender appends .000 stuff if it's a dupe. We don't care about dupe identifiers..."""
ident = self.identifier
- if ident.find('.') == -1:
+ if ident.find(".") == -1:
return ident
- return ident.rsplit('.', 1)[0]
+ return ident.rsplit(".", 1)[0]
def draw(self, context, layout, node, text):
if not self.is_output:
@@ -462,9 +520,11 @@ class PlasmaNodeSocketBase:
# loaded. So, only check in that case.
hval = str(hash((i for i in bpy.data.texts)))
if hval != self.possible_links_texts_hash:
- self.has_possible_links_value = any(self.node.generate_valid_links_for(bpy.context,
- self,
- self.is_output))
+ self.has_possible_links_value = any(
+ self.node.generate_valid_links_for(
+ bpy.context, self, self.is_output
+ )
+ )
self.possible_links_texts_hash = hval
self.possible_links_update_time = tval
return self.has_possible_links_value
@@ -475,8 +535,9 @@ class PlasmaNodeSocketBase:
@classmethod
def register(cls):
- cls.has_possible_links = BoolProperty(options={"HIDDEN", "SKIP_SAVE"},
- get=cls._has_possible_links)
+ cls.has_possible_links = BoolProperty(
+ options={"HIDDEN", "SKIP_SAVE"}, get=cls._has_possible_links
+ )
cls.has_possible_links_value = BoolProperty(options={"HIDDEN", "SKIP_SAVE"})
cls.possible_links_update_time = FloatProperty(options={"HIDDEN", "SKIP_SAVE"})
cls.possible_links_texts_hash = StringProperty(options={"HIDDEN", "SKIP_SAVE"})
@@ -484,6 +545,7 @@ class PlasmaNodeSocketBase:
class PlasmaNodeSocketInputGeneral(PlasmaNodeSocketBase, bpy.types.NodeSocket):
"""A general input socket that will steal the output's color"""
+
def draw_color(self, context, node):
if self.is_linked:
return self.links[0].from_socket.draw_color(context, node)
@@ -516,12 +578,16 @@ class PlasmaNodeTree(bpy.types.NodeTree):
if harvest_method is not None:
actors.update(harvest_method())
elif not isinstance(node, PlasmaNodeBase):
- raise ExportError("Plasma Node Tree '{}' Node '{}': is not a valid node for this tree".format(self.id_data.name, node.name))
+ raise ExportError(
+ "Plasma Node Tree '{}' Node '{}': is not a valid node for this tree".format(
+ self.id_data.name, node.name
+ )
+ )
return actors
@classmethod
def poll(cls, context):
- return (context.scene.render.engine == "PLASMA_GAME")
+ return context.scene.render.engine == "PLASMA_GAME"
@property
def requires_actor(self):
@@ -539,4 +605,6 @@ def _nuke_plasma_nodes(dummy):
for i in bpy.data.node_groups:
if isinstance(i, PlasmaNodeTree):
i.nodes.clear()
+
+
bpy.app.handlers.load_pre.append(_nuke_plasma_nodes)
diff --git a/korman/nodes/node_deprecated.py b/korman/nodes/node_deprecated.py
index d3d5bdd..919e8ac 100644
--- a/korman/nodes/node_deprecated.py
+++ b/korman/nodes/node_deprecated.py
@@ -20,6 +20,7 @@ from collections import OrderedDict
from .node_core import *
+
class PlasmaDeprecatedNode(PlasmaNodeBase):
@abc.abstractmethod
def upgrade(self):
@@ -53,28 +54,44 @@ class PlasmaResponderCommandNode(PlasmaDeprecatedNode, bpy.types.Node):
bl_idname = "PlasmaResponderCommandNode"
bl_label = "Responder Command"
- input_sockets = OrderedDict([
- ("whodoneit", {
- "text": "Condition",
- "type": "PlasmaRespCommandSocket",
- }),
- ])
-
- output_sockets = OrderedDict([
- ("msg", {
- "link_limit": 1,
- "text": "Message",
- "type": "PlasmaMessageSocket",
- }),
- ("trigger", {
- "text": "Trigger",
- "type": "PlasmaRespCommandSocket",
- }),
- ("reenable", {
- "text": "Local Reenable",
- "type": "PlasmaEnableMessageSocket",
- }),
- ])
+ input_sockets = OrderedDict(
+ [
+ (
+ "whodoneit",
+ {
+ "text": "Condition",
+ "type": "PlasmaRespCommandSocket",
+ },
+ ),
+ ]
+ )
+
+ output_sockets = OrderedDict(
+ [
+ (
+ "msg",
+ {
+ "link_limit": 1,
+ "text": "Message",
+ "type": "PlasmaMessageSocket",
+ },
+ ),
+ (
+ "trigger",
+ {
+ "text": "Trigger",
+ "type": "PlasmaRespCommandSocket",
+ },
+ ),
+ (
+ "reenable",
+ {
+ "text": "Local Reenable",
+ "type": "PlasmaEnableMessageSocket",
+ },
+ ),
+ ]
+ )
def _find_message_sender_node(self, parentCmdNode=None):
if parentCmdNode is None:
@@ -103,7 +120,6 @@ class PlasmaResponderCommandNode(PlasmaDeprecatedNode, bpy.types.Node):
self._whine("unexpected command node type '{}'", parentCmdNode.bl_idname)
return None
-
def upgrade(self):
senderNode = self._find_message_sender_node()
if senderNode is None:
@@ -130,6 +146,7 @@ class PlasmaResponderCommandNode(PlasmaDeprecatedNode, bpy.types.Node):
continue
tree.links.new(link.to_socket, fromSocket)
+
@bpy.app.handlers.persistent
def _upgrade_node_trees(dummy):
for tree in bpy.data.node_groups:
@@ -150,4 +167,6 @@ def _upgrade_node_trees(dummy):
# toss deprecated nodes
for node in nuke:
tree.nodes.remove(node)
+
+
bpy.app.handlers.load_post.append(_upgrade_node_trees)
diff --git a/korman/nodes/node_logic.py b/korman/nodes/node_logic.py
index a9a38e3..ba42f34 100644
--- a/korman/nodes/node_logic.py
+++ b/korman/nodes/node_logic.py
@@ -19,10 +19,17 @@ from collections import OrderedDict
from PyHSPlasma import *
from .node_core import *
-from ..properties.modifiers.physics import bounds_types, bounds_type_index, bounds_type_str
+from ..properties.modifiers.physics import (
+ bounds_types,
+ bounds_type_index,
+ bounds_type_str,
+)
from .. import idprops
-class PlasmaExcludeRegionNode(idprops.IDPropObjectMixin, PlasmaNodeBase, bpy.types.Node):
+
+class PlasmaExcludeRegionNode(
+ idprops.IDPropObjectMixin, PlasmaNodeBase, bpy.types.Node
+):
bl_category = "LOGIC"
bl_idname = "PlasmaExcludeRegionNode"
bl_label = "Exclude Region"
@@ -33,46 +40,70 @@ class PlasmaExcludeRegionNode(idprops.IDPropObjectMixin, PlasmaNodeBase, bpy.typ
def _get_bounds(self):
if self.region_object is not None:
- return bounds_type_index(self.region_object.plasma_modifiers.collision.bounds)
+ return bounds_type_index(
+ self.region_object.plasma_modifiers.collision.bounds
+ )
return bounds_type_index("hull")
+
def _set_bounds(self, value):
if self.region_object is not None:
- self.region_object.plasma_modifiers.collision.bounds = bounds_type_str(value)
-
- region_object = PointerProperty(name="Region",
- description="Region object's name",
- type=bpy.types.Object,
- poll=idprops.poll_mesh_objects)
- bounds = EnumProperty(name="Bounds",
- description="Region bounds",
- items=bounds_types,
- get=_get_bounds,
- set=_set_bounds)
- block_cameras = BoolProperty(name="Block Cameras",
- description="The region blocks cameras when it has been cleared")
-
- input_sockets = OrderedDict([
- ("safe_point", {
- "type": "PlasmaExcludeSafePointSocket",
- "text": "Safe Point",
- "spawn_empty": True,
- # This never links to anything...
- "valid_link_sockets": frozenset(),
- }),
- ("msg", {
- "type": "PlasmaExcludeMessageSocket",
- "text": "Message",
- "spawn_empty": True,
- }),
- ])
-
- output_sockets = OrderedDict([
- ("keyref", {
- "text": "References",
- "type": "PlasmaPythonReferenceNodeSocket",
- "valid_link_nodes": {"PlasmaPythonFileNode"},
- }),
- ])
+ self.region_object.plasma_modifiers.collision.bounds = bounds_type_str(
+ value
+ )
+
+ region_object = PointerProperty(
+ name="Region",
+ description="Region object's name",
+ type=bpy.types.Object,
+ poll=idprops.poll_mesh_objects,
+ )
+ bounds = EnumProperty(
+ name="Bounds",
+ description="Region bounds",
+ items=bounds_types,
+ get=_get_bounds,
+ set=_set_bounds,
+ )
+ block_cameras = BoolProperty(
+ name="Block Cameras",
+ description="The region blocks cameras when it has been cleared",
+ )
+
+ input_sockets = OrderedDict(
+ [
+ (
+ "safe_point",
+ {
+ "type": "PlasmaExcludeSafePointSocket",
+ "text": "Safe Point",
+ "spawn_empty": True,
+ # This never links to anything...
+ "valid_link_sockets": frozenset(),
+ },
+ ),
+ (
+ "msg",
+ {
+ "type": "PlasmaExcludeMessageSocket",
+ "text": "Message",
+ "spawn_empty": True,
+ },
+ ),
+ ]
+ )
+
+ output_sockets = OrderedDict(
+ [
+ (
+ "keyref",
+ {
+ "text": "References",
+ "type": "PlasmaPythonReferenceNodeSocket",
+ "valid_link_nodes": {"PlasmaPythonFileNode"},
+ },
+ ),
+ ]
+ )
def draw_buttons(self, context, layout):
layout.prop(self, "region_object", icon="MESH_DATA")
@@ -82,21 +113,31 @@ class PlasmaExcludeRegionNode(idprops.IDPropObjectMixin, PlasmaNodeBase, bpy.typ
def get_key(self, exporter, parent_so):
if self.region_object is None:
self.raise_error("Region must be set")
- return self._find_create_key(plExcludeRegionModifier, exporter, bl=self.region_object)
+ return self._find_create_key(
+ plExcludeRegionModifier, exporter, bl=self.region_object
+ )
def harvest_actors(self):
- return (i.safepoint.name for i in self.find_input_sockets("safe_points") if i.safepoint is not None)
+ return (
+ i.safepoint.name
+ for i in self.find_input_sockets("safe_points")
+ if i.safepoint is not None
+ )
def export(self, exporter, bo, parent_so):
excludergn = self.get_key(exporter, parent_so).object
excludergn.setFlag(plExcludeRegionModifier.kBlockCameras, self.block_cameras)
- region_so = exporter.mgr.find_create_object(plSceneObject, bl=self.region_object)
+ region_so = exporter.mgr.find_create_object(
+ plSceneObject, bl=self.region_object
+ )
# Safe points
for i in self.find_input_sockets("safe_point"):
safept = i.safepoint_object
if safept:
- excludergn.addSafePoint(exporter.mgr.find_create_key(plSceneObject, bl=safept))
+ excludergn.addSafePoint(
+ exporter.mgr.find_create_key(plSceneObject, bl=safept)
+ )
# Ensure the region is exported
if exporter.mgr.getVer() <= pvPots:
@@ -105,11 +146,15 @@ class PlasmaExcludeRegionNode(idprops.IDPropObjectMixin, PlasmaNodeBase, bpy.typ
else:
member_group = "kGroupStatic"
collide_groups = []
- exporter.physics.generate_physical(self.region_object, region_so, bounds=self.bounds,
- properties=["kPinned"],
- losdbs=["kLOSDBUIBlockers"],
- member_group=member_group,
- collide_groups=collide_groups)
+ exporter.physics.generate_physical(
+ self.region_object,
+ region_so,
+ bounds=self.bounds,
+ properties=["kPinned"],
+ losdbs=["kLOSDBUIBlockers"],
+ member_group=member_group,
+ collide_groups=collide_groups,
+ )
@property
def export_once(self):
@@ -120,12 +165,16 @@ class PlasmaExcludeRegionNode(idprops.IDPropObjectMixin, PlasmaNodeBase, bpy.typ
return {"region_object": "region"}
-class PlasmaExcludeSafePointSocket(idprops.IDPropObjectMixin, PlasmaNodeSocketBase, bpy.types.NodeSocket):
+class PlasmaExcludeSafePointSocket(
+ idprops.IDPropObjectMixin, PlasmaNodeSocketBase, bpy.types.NodeSocket
+):
bl_color = (0.0, 0.0, 0.0, 0.0)
- safepoint_object = PointerProperty(name="Safe Point",
- description="A point outside of this exclude region to move the avatar to",
- type=bpy.types.Object)
+ safepoint_object = PointerProperty(
+ name="Safe Point",
+ description="A point outside of this exclude region to move the avatar to",
+ type=bpy.types.Object,
+ )
def draw(self, context, layout, node, text):
layout.prop(self, "safepoint_object", icon="EMPTY_DATA")
diff --git a/korman/nodes/node_messages.py b/korman/nodes/node_messages.py
index a42196c..934e00c 100644
--- a/korman/nodes/node_messages.py
+++ b/korman/nodes/node_messages.py
@@ -23,21 +23,29 @@ from ..properties.modifiers.region import footstep_surfaces, footstep_surface_id
from ..exporter import ExportError
from .. import idprops
+
class PlasmaMessageSocketBase(PlasmaNodeSocketBase):
bl_color = (0.004, 0.282, 0.349, 1.0)
+
+
class PlasmaMessageSocket(PlasmaMessageSocketBase, bpy.types.NodeSocket):
pass
class PlasmaMessageNode(PlasmaNodeBase):
- input_sockets = OrderedDict([
- ("sender", {
- "text": "Sender",
- "type": "PlasmaMessageSocket",
- "valid_link_sockets": "PlasmaMessageSocket",
- "spawn_empty": True,
- }),
- ])
+ input_sockets = OrderedDict(
+ [
+ (
+ "sender",
+ {
+ "text": "Sender",
+ "type": "PlasmaMessageSocket",
+ "valid_link_sockets": "PlasmaMessageSocket",
+ "spawn_empty": True,
+ },
+ ),
+ ]
+ )
@property
def has_callbacks(self):
@@ -46,14 +54,19 @@ class PlasmaMessageNode(PlasmaNodeBase):
class PlasmaMessageWithCallbacksNode(PlasmaMessageNode):
- output_sockets = OrderedDict([
- ("msgs", {
- "can_link": "can_link_callback",
- "text": "Send On Completion",
- "type": "PlasmaMessageSocket",
- "valid_link_sockets": "PlasmaMessageSocket",
- }),
- ])
+ output_sockets = OrderedDict(
+ [
+ (
+ "msgs",
+ {
+ "can_link": "can_link_callback",
+ "text": "Send On Completion",
+ "type": "PlasmaMessageSocket",
+ "valid_link_sockets": "PlasmaMessageSocket",
+ },
+ ),
+ ]
+ )
@property
def can_link_callback(self):
@@ -93,17 +106,23 @@ class PlasmaMessageWithCallbacksNode(PlasmaMessageNode):
return self.find_output("msgs") is not None
-class PlasmaAnimCmdMsgNode(idprops.IDPropMixin, PlasmaMessageWithCallbacksNode, bpy.types.Node):
+class PlasmaAnimCmdMsgNode(
+ idprops.IDPropMixin, PlasmaMessageWithCallbacksNode, bpy.types.Node
+):
bl_category = "MSG"
bl_idname = "PlasmaAnimCmdMsgNode"
bl_label = "Animation Command"
bl_width_default = 190
- anim_type = EnumProperty(name="Type",
- description="Animation type to affect",
- items=[("OBJECT", "Object", "Mesh Action"),
- ("TEXTURE", "Texture", "Texture Action")],
- default="OBJECT")
+ anim_type = EnumProperty(
+ name="Type",
+ description="Animation type to affect",
+ items=[
+ ("OBJECT", "Object", "Mesh Action"),
+ ("TEXTURE", "Texture", "Texture Action"),
+ ],
+ default="OBJECT",
+ )
def _poll_texture(self, value):
# must be a legal option... but is it a member of this material... or, if no material,
@@ -111,8 +130,14 @@ class PlasmaAnimCmdMsgNode(idprops.IDPropMixin, PlasmaMessageWithCallbacksNode,
if self.target_material is not None:
return value.name in self.target_material.texture_slots
elif self.target_object is not None:
- for i in (slot.material for slot in self.target_object.material_slots if slot and slot.material):
- if value in (slot.texture for slot in i.texture_slots if slot and slot.texture):
+ for i in (
+ slot.material
+ for slot in self.target_object.material_slots
+ if slot and slot.material
+ ):
+ if value in (
+ slot.texture for slot in i.texture_slots if slot and slot.texture
+ ):
return True
return False
else:
@@ -123,115 +148,204 @@ class PlasmaAnimCmdMsgNode(idprops.IDPropMixin, PlasmaMessageWithCallbacksNode,
# in that you would have to clear the texture selection before being able to select
# certain materials.
if self.target_object is not None:
- object_materials = (slot.material for slot in self.target_object.material_slots if slot and slot.material)
+ object_materials = (
+ slot.material
+ for slot in self.target_object.material_slots
+ if slot and slot.material
+ )
return value in object_materials
return True
- target_object = PointerProperty(name="Object",
- description="Target object",
- type=bpy.types.Object)
- target_material = PointerProperty(name="Material",
- description="Target material",
- type=bpy.types.Material,
- poll=_poll_material)
- target_texture = PointerProperty(name="Texture",
- description="Target texture",
- type=bpy.types.Texture,
- poll=_poll_texture)
-
- go_to = EnumProperty(name="Go To",
- description="Where should the animation start?",
- items=[("kGoToBegin", "Beginning", "The beginning"),
- ("kGoToLoopBegin", "Loop Beginning", "The beginning of the active loop"),
- ("CURRENT", "(Don't Change)", "The current position"),
- ("kGoToEnd", "Ending", "The end"),
- ("kGoToLoopEnd", "Loop Ending", "The end of the active loop")],
- default="CURRENT")
- action = EnumProperty(name="Action",
- description="What do you want the animation to do?",
- items=[("kContinue", "Play", "Plays the animation"),
- ("kPlayToPercent", "Play to Percent", "Plays the animation until a given percent is complete"),
- ("kPlayToTime", "Play to Frame", "Plays the animation up to a given frame number"),
- ("kStop", "Stop", "Stops the animation",),
- ("kToggleState", "Toggle", "Toggles between Play and Stop"),
- ("CURRENT", "(Don't Change)", "Don't change the animation's playing state")],
- default="CURRENT")
- play_direction = EnumProperty(name="Direction",
- description="Which direction do you want to play from?",
- items=[("kSetForwards", "Forward", "Play forwards"),
- ("kSetBackwards", "Backwards", "Play backwards"),
- ("CURRENT", "(Don't Change)", "Don't change the play direction")],
- default="CURRENT")
- play_to_percent = IntProperty(name="Play To",
- description="Percentage at which to stop the animation",
- subtype="PERCENTAGE",
- min=0, max=100, default=50)
- play_to_frame = IntProperty(name="Play To",
- description="Frame at which to stop the animation",
- min=0)
+ target_object = PointerProperty(
+ name="Object", description="Target object", type=bpy.types.Object
+ )
+ target_material = PointerProperty(
+ name="Material",
+ description="Target material",
+ type=bpy.types.Material,
+ poll=_poll_material,
+ )
+ target_texture = PointerProperty(
+ name="Texture",
+ description="Target texture",
+ type=bpy.types.Texture,
+ poll=_poll_texture,
+ )
+
+ go_to = EnumProperty(
+ name="Go To",
+ description="Where should the animation start?",
+ items=[
+ ("kGoToBegin", "Beginning", "The beginning"),
+ ("kGoToLoopBegin", "Loop Beginning", "The beginning of the active loop"),
+ ("CURRENT", "(Don't Change)", "The current position"),
+ ("kGoToEnd", "Ending", "The end"),
+ ("kGoToLoopEnd", "Loop Ending", "The end of the active loop"),
+ ],
+ default="CURRENT",
+ )
+ action = EnumProperty(
+ name="Action",
+ description="What do you want the animation to do?",
+ items=[
+ ("kContinue", "Play", "Plays the animation"),
+ (
+ "kPlayToPercent",
+ "Play to Percent",
+ "Plays the animation until a given percent is complete",
+ ),
+ (
+ "kPlayToTime",
+ "Play to Frame",
+ "Plays the animation up to a given frame number",
+ ),
+ (
+ "kStop",
+ "Stop",
+ "Stops the animation",
+ ),
+ ("kToggleState", "Toggle", "Toggles between Play and Stop"),
+ ("CURRENT", "(Don't Change)", "Don't change the animation's playing state"),
+ ],
+ default="CURRENT",
+ )
+ play_direction = EnumProperty(
+ name="Direction",
+ description="Which direction do you want to play from?",
+ items=[
+ ("kSetForwards", "Forward", "Play forwards"),
+ ("kSetBackwards", "Backwards", "Play backwards"),
+ ("CURRENT", "(Don't Change)", "Don't change the play direction"),
+ ],
+ default="CURRENT",
+ )
+ play_to_percent = IntProperty(
+ name="Play To",
+ description="Percentage at which to stop the animation",
+ subtype="PERCENTAGE",
+ min=0,
+ max=100,
+ default=50,
+ )
+ play_to_frame = IntProperty(
+ name="Play To", description="Frame at which to stop the animation", min=0
+ )
def _set_loop_name(self, context):
"""Updates loop_begin and loop_end when the loop name is changed"""
pass
- looping = EnumProperty(name="Looping",
- description="Is the animation looping?",
- items=[("kSetLooping", "Yes", "The animation is looping",),
- ("CURRENT", "(Don't Change)", "Don't change the loop status"),
- ("kSetUnLooping", "No", "The animation is NOT looping")],
- default="CURRENT")
- loop_name = StringProperty(name="Active Loop",
- description="Name of the active loop",
- update=_set_loop_name)
- loop_begin = IntProperty(name="Loop Begin",
- description="Frame number at which the loop begins",
- min=0)
- loop_end = IntProperty(name="Loop End",
- description="Frame number at which the loop ends",
- min=0)
-
- event = EnumProperty(name="Callback",
- description="Event upon which to callback the Responder",
- items=[("kEnd", "End", "When the action ends"),
- ("NONE", "(None)", "Don't notify the Responder at all"),
- ("kStop", "Stop", "When the action is stopped by a message")],
- default="kEnd")
+ looping = EnumProperty(
+ name="Looping",
+ description="Is the animation looping?",
+ items=[
+ (
+ "kSetLooping",
+ "Yes",
+ "The animation is looping",
+ ),
+ ("CURRENT", "(Don't Change)", "Don't change the loop status"),
+ ("kSetUnLooping", "No", "The animation is NOT looping"),
+ ],
+ default="CURRENT",
+ )
+ loop_name = StringProperty(
+ name="Active Loop", description="Name of the active loop", update=_set_loop_name
+ )
+ loop_begin = IntProperty(
+ name="Loop Begin", description="Frame number at which the loop begins", min=0
+ )
+ loop_end = IntProperty(
+ name="Loop End", description="Frame number at which the loop ends", min=0
+ )
+
+ event = EnumProperty(
+ name="Callback",
+ description="Event upon which to callback the Responder",
+ items=[
+ ("kEnd", "End", "When the action ends"),
+ ("NONE", "(None)", "Don't notify the Responder at all"),
+ ("kStop", "Stop", "When the action is stopped by a message"),
+ ],
+ default="kEnd",
+ )
# Blender memory workaround
_ENTIRE_ANIMATION = "(Entire Animation)"
+
def _get_anim_names(self, context):
if self.anim_type == "OBJECT":
- items = [(anim.animation_name, anim.animation_name, "")
- for anim in self.target_object.plasma_modifiers.animation.subanimations]
+ items = [
+ (anim.animation_name, anim.animation_name, "")
+ for anim in self.target_object.plasma_modifiers.animation.subanimations
+ ]
elif self.anim_type == "TEXTURE":
if self.target_texture is not None:
- items = [(anim.animation_name, anim.animation_name, "")
- for anim in self.target_texture.plasma_layer.subanimations]
+ items = [
+ (anim.animation_name, anim.animation_name, "")
+ for anim in self.target_texture.plasma_layer.subanimations
+ ]
elif self.target_material is not None or self.target_object is not None:
if self.target_material is None:
- materials = (i.material for i in self.target_object.material_slots if i and i.material)
+ materials = (
+ i.material
+ for i in self.target_object.material_slots
+ if i and i.material
+ )
else:
materials = (self.target_material,)
- layer_props = (i.texture.plasma_layer for mat in materials for i in mat.texture_slots if i and i.texture)
- all_anims = frozenset((anim.animation_name for i in layer_props for anim in i.subanimations))
+ layer_props = (
+ i.texture.plasma_layer
+ for mat in materials
+ for i in mat.texture_slots
+ if i and i.texture
+ )
+ all_anims = frozenset(
+ (
+ anim.animation_name
+ for i in layer_props
+ for anim in i.subanimations
+ )
+ )
items = [(i, i, "") for i in all_anims]
else:
- items = [(PlasmaAnimCmdMsgNode._ENTIRE_ANIMATION, PlasmaAnimCmdMsgNode._ENTIRE_ANIMATION, "")]
+ items = [
+ (
+ PlasmaAnimCmdMsgNode._ENTIRE_ANIMATION,
+ PlasmaAnimCmdMsgNode._ENTIRE_ANIMATION,
+ "",
+ )
+ ]
else:
raise RuntimeError()
# We always want "(Entire Animation)", if it exists, to be the first item.
- entire = items.index((PlasmaAnimCmdMsgNode._ENTIRE_ANIMATION, PlasmaAnimCmdMsgNode._ENTIRE_ANIMATION, ""))
+ entire = items.index(
+ (
+ PlasmaAnimCmdMsgNode._ENTIRE_ANIMATION,
+ PlasmaAnimCmdMsgNode._ENTIRE_ANIMATION,
+ "",
+ )
+ )
if entire not in (-1, 0):
items.pop(entire)
- items.insert(0, (PlasmaAnimCmdMsgNode._ENTIRE_ANIMATION, PlasmaAnimCmdMsgNode._ENTIRE_ANIMATION, ""))
+ items.insert(
+ 0,
+ (
+ PlasmaAnimCmdMsgNode._ENTIRE_ANIMATION,
+ PlasmaAnimCmdMsgNode._ENTIRE_ANIMATION,
+ "",
+ ),
+ )
return items
- anim_name = EnumProperty(name="Animation",
- description="Name of the animation to control",
- items=_get_anim_names,
- options=set())
+ anim_name = EnumProperty(
+ name="Animation",
+ description="Name of the animation to control",
+ items=_get_anim_names,
+ options=set(),
+ )
def draw_buttons(self, context, layout):
layout.prop(self, "anim_type")
@@ -240,7 +354,9 @@ class PlasmaAnimCmdMsgNode(idprops.IDPropMixin, PlasmaMessageWithCallbacksNode,
if self.anim_type == "OBJECT":
col.alert = self.target_object is None
else:
- col.alert = not any((self.target_object, self.target_material, self.target_texture))
+ col.alert = not any(
+ (self.target_object, self.target_material, self.target_texture)
+ )
col.prop(self, "target_object")
if self.anim_type != "OBJECT":
col.prop(self, "target_material")
@@ -261,7 +377,11 @@ class PlasmaAnimCmdMsgNode(idprops.IDPropMixin, PlasmaMessageWithCallbacksNode,
if self.anim_type != "OBJECT":
loops = None
else:
- loops = None if self.target_object is None else self.target_object.plasma_modifiers.animation_loop
+ loops = (
+ None
+ if self.target_object is None
+ else self.target_object.plasma_modifiers.animation_loop
+ )
if loops is not None and loops.enabled:
layout.prop_search(self, "loop_name", loops, "loops", icon="PMARKER_ACT")
else:
@@ -293,10 +413,18 @@ class PlasmaAnimCmdMsgNode(idprops.IDPropMixin, PlasmaMessageWithCallbacksNode,
material = self.target_material
texture = self.target_texture
if obj is None and material is None and texture is None:
- self.raise_error("At least one of: target object, material, texture MUST be specified")
- target = exporter.mesh.material.get_texture_animation_key(obj, material, texture, self.anim_name)
-
- target = [i for i in target if not isinstance(i.object, (plAgeGlobalAnim, plLayerSDLAnimation))]
+ self.raise_error(
+ "At least one of: target object, material, texture MUST be specified"
+ )
+ target = exporter.mesh.material.get_texture_animation_key(
+ obj, material, texture, self.anim_name
+ )
+
+ target = [
+ i
+ for i in target
+ if not isinstance(i.object, (plAgeGlobalAnim, plLayerSDLAnimation))
+ ]
if not target:
self.raise_error("No controllable animations were found.")
for i in target:
@@ -332,14 +460,18 @@ class PlasmaAnimCmdMsgNode(idprops.IDPropMixin, PlasmaMessageWithCallbacksNode,
@classmethod
def _idprop_mapping(cls):
- return {"target_object": "object_name",
- "target_material": "material_name",
- "target_texture": "texture_name"}
+ return {
+ "target_object": "object_name",
+ "target_material": "material_name",
+ "target_texture": "texture_name",
+ }
def _idprop_sources(self):
- return {"object_name": bpy.data.objects,
- "material_name": bpy.data.materials,
- "texture_name": bpy.data.textures}
+ return {
+ "object_name": bpy.data.objects,
+ "material_name": bpy.data.materials,
+ "texture_name": bpy.data.textures,
+ }
class PlasmaCameraMsgNode(PlasmaMessageNode, bpy.types.Node):
@@ -348,20 +480,40 @@ class PlasmaCameraMsgNode(PlasmaMessageNode, bpy.types.Node):
bl_label = "Camera"
bl_width_default = 200
- cmd = EnumProperty(name="Command",
- description="Command to send to the camera system",
- items=[("push", "Push Camera", "Pushes a new camera onto the camera stack and transitions to it"),
- ("pop", "Pop Camera", "Pops the camera off the camera stack"),
- ("disablefp", "Disable First Person", "Forces the camera into third person if it is currently in first person and disables first person mode"),
- ("enablefp", "Enable First Person", "Reenables the first person camera and switches back to it if the player was in first person previously")],
- options=set())
- camera = PointerProperty(name="Camera",
- type=bpy.types.Object,
- poll=idprops.poll_camera_objects,
- options=set())
- cut = BoolProperty(name="Cut Transition",
- description="Immediately swap over to the new camera without a transition animation",
- options=set())
+ cmd = EnumProperty(
+ name="Command",
+ description="Command to send to the camera system",
+ items=[
+ (
+ "push",
+ "Push Camera",
+ "Pushes a new camera onto the camera stack and transitions to it",
+ ),
+ ("pop", "Pop Camera", "Pops the camera off the camera stack"),
+ (
+ "disablefp",
+ "Disable First Person",
+ "Forces the camera into third person if it is currently in first person and disables first person mode",
+ ),
+ (
+ "enablefp",
+ "Enable First Person",
+ "Reenables the first person camera and switches back to it if the player was in first person previously",
+ ),
+ ],
+ options=set(),
+ )
+ camera = PointerProperty(
+ name="Camera",
+ type=bpy.types.Object,
+ poll=idprops.poll_camera_objects,
+ options=set(),
+ )
+ cut = BoolProperty(
+ name="Cut Transition",
+ description="Immediately swap over to the new camera without a transition animation",
+ options=set(),
+ )
def convert_message(self, exporter, so):
msg = plCameraMsg()
@@ -373,8 +525,11 @@ class PlasmaCameraMsgNode(PlasmaMessageNode, bpy.types.Node):
# the presence of the kResponderTrigger command.
msg.setCmd(plCameraMsg.kResponderTrigger, self.cmd == "push")
msg.setCmd(plCameraMsg.kRegionPushCamera, True)
- msg.setCmd(plCameraMsg.kSetAsPrimary, self.camera is None
- or self.camera.data.plasma_camera.settings.primary_camera)
+ msg.setCmd(
+ plCameraMsg.kSetAsPrimary,
+ self.camera is None
+ or self.camera.data.plasma_camera.settings.primary_camera,
+ )
msg.setCmd(plCameraMsg.kCut, self.cut)
elif self.cmd == "disablefp":
msg.setCmd(plCameraMsg.kResponderSetThirdPerson)
@@ -396,31 +551,49 @@ class PlasmaEnableMsgNode(PlasmaMessageNode, bpy.types.Node):
bl_idname = "PlasmaEnableMsgNode"
bl_label = "Enable/Disable"
- output_sockets = OrderedDict([
- ("receivers", {
- "text": "Send To",
- "type": "PlasmaEnableMessageSocket",
- "valid_link_sockets": {"PlasmaEnableMessageSocket", "PlasmaNodeSocketInputGeneral"},
- }),
- ])
-
- cmd = EnumProperty(name="Command",
- description="How should we affect the object's state?",
- items=[("kDisable", "Disable", "Deactivate the object"),
- ("kEnable", "Enable", "Activate the object")],
- default="kEnable")
- settings = EnumProperty(name="Affects",
- description="Which attributes should we change",
- items=[("kAudible", "Audio", "Sounds played by this object"),
- ("kPhysical", "Physics", "Physical simulation of the object"),
- ("kDrawable", "Visibility", "Visible geometry/light of the object"),
- ("kModifiers", "Modifiers", "Modifiers attached to the object")],
- options={"ENUM_FLAG"},
- default={"kAudible", "kDrawable", "kPhysical", "kModifiers"})
- bcast_to_children = BoolProperty(name="Send to Children",
- description="Send the message to objects parented to the object",
- default=False,
- options=set())
+ output_sockets = OrderedDict(
+ [
+ (
+ "receivers",
+ {
+ "text": "Send To",
+ "type": "PlasmaEnableMessageSocket",
+ "valid_link_sockets": {
+ "PlasmaEnableMessageSocket",
+ "PlasmaNodeSocketInputGeneral",
+ },
+ },
+ ),
+ ]
+ )
+
+ cmd = EnumProperty(
+ name="Command",
+ description="How should we affect the object's state?",
+ items=[
+ ("kDisable", "Disable", "Deactivate the object"),
+ ("kEnable", "Enable", "Activate the object"),
+ ],
+ default="kEnable",
+ )
+ settings = EnumProperty(
+ name="Affects",
+ description="Which attributes should we change",
+ items=[
+ ("kAudible", "Audio", "Sounds played by this object"),
+ ("kPhysical", "Physics", "Physical simulation of the object"),
+ ("kDrawable", "Visibility", "Visible geometry/light of the object"),
+ ("kModifiers", "Modifiers", "Modifiers attached to the object"),
+ ],
+ options={"ENUM_FLAG"},
+ default={"kAudible", "kDrawable", "kPhysical", "kModifiers"},
+ )
+ bcast_to_children = BoolProperty(
+ name="Send to Children",
+ description="Send the message to objects parented to the object",
+ default=False,
+ options=set(),
+ )
def convert_message(self, exporter, so):
settings = self.settings
@@ -491,18 +664,21 @@ class PlasmaExcludeRegionMsg(PlasmaMessageNode, bpy.types.Node):
bl_idname = "PlasmaExcludeRegionMsg"
bl_label = "Exclude Region"
- output_sockets = OrderedDict([
- ("region", {
- "text": "Region",
- "type": "PlasmaExcludeMessageSocket"
- }),
- ])
-
- cmd = EnumProperty(name="Command",
- description="Exclude Region State",
- items=[("kClear", "Clear", "Clear all avatars from the region"),
- ("kRelease", "Release", "Allow avatars to enter the region")],
- default="kClear")
+ output_sockets = OrderedDict(
+ [
+ ("region", {"text": "Region", "type": "PlasmaExcludeMessageSocket"}),
+ ]
+ )
+
+ cmd = EnumProperty(
+ name="Command",
+ description="Exclude Region State",
+ items=[
+ ("kClear", "Clear", "Clear all avatars from the region"),
+ ("kRelease", "Release", "Allow avatars to enter the region"),
+ ],
+ default="kClear",
+ )
def convert_message(self, exporter, so):
msg = plExcludeRegionMsg()
@@ -521,29 +697,60 @@ class PlasmaLinkToAgeMsg(PlasmaMessageNode, bpy.types.Node):
bl_label = "Link to Age"
bl_width_default = 280
- rules = EnumProperty(name="Rules",
- description="Rules describing which age instance to link to",
- items=[("kOriginalBook", "Original Age", "Links to a personally owned instance, creating if none exists"),
- ("kOwnedBook", "Owned Age", "Links to a personally owned instance, fails if none exists"),
- ("kChildAgeBook", "Child Age", "Links to an age instance parented to another personal age"),
- ("kSubAgeBook", "Sub Age", "Links to an age instance owned by the current age instance"),
- ("kBasicLink", "Basic", "Links to a specific age instance")])
- parent_filename = StringProperty(name="Parent Age",
- description="Filename of the age that owns the age instance we're linking to")
-
- age_filename = StringProperty(name="Age Filename",
- description="Filename of the age to link to (eg 'Garden'")
- age_instance = StringProperty(name="Age Instance",
- description="Instance name of the age to link to (eg 'Eder Kemo')")
- age_uuid = StringProperty(name="Age Guid",
- description="Instance GUID to link to (eg 'ea489821-6c35-4bd0-9dae-bb17c585e680')")
-
- spawn_title = StringProperty(name="Spawn Title",
- description="Title of the Spawn Point to use",
- default="Default")
- spawn_point = StringProperty(name="Spawn Point",
- description="Name of the Spawn Point's Plasma Object",
- default="LinkInPointDefault")
+ rules = EnumProperty(
+ name="Rules",
+ description="Rules describing which age instance to link to",
+ items=[
+ (
+ "kOriginalBook",
+ "Original Age",
+ "Links to a personally owned instance, creating if none exists",
+ ),
+ (
+ "kOwnedBook",
+ "Owned Age",
+ "Links to a personally owned instance, fails if none exists",
+ ),
+ (
+ "kChildAgeBook",
+ "Child Age",
+ "Links to an age instance parented to another personal age",
+ ),
+ (
+ "kSubAgeBook",
+ "Sub Age",
+ "Links to an age instance owned by the current age instance",
+ ),
+ ("kBasicLink", "Basic", "Links to a specific age instance"),
+ ],
+ )
+ parent_filename = StringProperty(
+ name="Parent Age",
+ description="Filename of the age that owns the age instance we're linking to",
+ )
+
+ age_filename = StringProperty(
+ name="Age Filename", description="Filename of the age to link to (eg 'Garden'"
+ )
+ age_instance = StringProperty(
+ name="Age Instance",
+ description="Instance name of the age to link to (eg 'Eder Kemo')",
+ )
+ age_uuid = StringProperty(
+ name="Age Guid",
+ description="Instance GUID to link to (eg 'ea489821-6c35-4bd0-9dae-bb17c585e680')",
+ )
+
+ spawn_title = StringProperty(
+ name="Spawn Title",
+ description="Title of the Spawn Point to use",
+ default="Default",
+ )
+ spawn_point = StringProperty(
+ name="Spawn Point",
+ description="Name of the Spawn Point's Plasma Object",
+ default="LinkInPointDefault",
+ )
def convert_message(self, exporter, so):
msg = plLinkToAgeMsg()
@@ -554,7 +761,9 @@ class PlasmaLinkToAgeMsg(PlasmaMessageNode, bpy.types.Node):
if self.rules == "kChildAgeBook":
als.parentAgeFilename = self.parent_filename
ais.ageFilename = self.age_filename
- ais.ageInstanceName = self.age_instance if self.age_instance else self.age_filename
+ ais.ageInstanceName = (
+ self.age_instance if self.age_instance else self.age_filename
+ )
if self.rules == "kBasicLink":
try:
ais.ageInstanceGuid = self.age_uuid
@@ -602,32 +811,45 @@ class PlasmaLinkToAgeMsg(PlasmaMessageNode, bpy.types.Node):
layout.prop(self, "spawn_point")
-class PlasmaOneShotMsgNode(idprops.IDPropObjectMixin, PlasmaMessageWithCallbacksNode, bpy.types.Node):
+class PlasmaOneShotMsgNode(
+ idprops.IDPropObjectMixin, PlasmaMessageWithCallbacksNode, bpy.types.Node
+):
bl_category = "MSG"
bl_idname = "PlasmaOneShotMsgNode"
bl_label = "One Shot"
bl_width_default = 210
- pos_object = PointerProperty(name="Position",
- description="Object defining the OneShot position",
- type=bpy.types.Object)
- seek = EnumProperty(name="Seek",
- description="How the avatar should approach the OneShot position",
- items=[("SMART", "Smart Seek", "Let the engine figure out the best path"),
- ("DUMB", "Seek", "Shuffle to the OneShot position"),
- ("NONE", "Warp", "Warp the avatar to the OneShot position")],
- default="SMART")
-
- animation = StringProperty(name="Animation",
- description="Name of the animation the avatar should execute")
- marker = StringProperty(name="Marker",
- description="Name of the marker specifying when to notify the Responder")
- drivable = BoolProperty(name="Drivable",
- description="Player retains control of the avatar during the OneShot",
- default=False)
- reversable = BoolProperty(name="Reversable",
- description="Player can reverse the OneShot",
- default=False)
+ pos_object = PointerProperty(
+ name="Position",
+ description="Object defining the OneShot position",
+ type=bpy.types.Object,
+ )
+ seek = EnumProperty(
+ name="Seek",
+ description="How the avatar should approach the OneShot position",
+ items=[
+ ("SMART", "Smart Seek", "Let the engine figure out the best path"),
+ ("DUMB", "Seek", "Shuffle to the OneShot position"),
+ ("NONE", "Warp", "Warp the avatar to the OneShot position"),
+ ],
+ default="SMART",
+ )
+
+ animation = StringProperty(
+ name="Animation", description="Name of the animation the avatar should execute"
+ )
+ marker = StringProperty(
+ name="Marker",
+ description="Name of the marker specifying when to notify the Responder",
+ )
+ drivable = BoolProperty(
+ name="Drivable",
+ description="Player retains control of the avatar during the OneShot",
+ default=False,
+ )
+ reversable = BoolProperty(
+ name="Reversable", description="Player can reverse the OneShot", default=False
+ )
def convert_callback_message(self, exporter, so, msg, target, wait):
msg.addCallback(self.marker, target, wait)
@@ -682,31 +904,42 @@ class PlasmaOneShotMsgNode(idprops.IDPropObjectMixin, PlasmaMessageWithCallbacks
class PlasmaOneShotCallbackSocket(PlasmaMessageSocketBase, bpy.types.NodeSocket):
- marker = StringProperty(name="Marker",
- description="Marker specifying the time at which to send a callback to this Responder")
+ marker = StringProperty(
+ name="Marker",
+ description="Marker specifying the time at which to send a callback to this Responder",
+ )
def draw(self, context, layout, node, text):
layout.prop(self, "marker")
-class PlasmaSceneObjectMsgRcvrNode(idprops.IDPropObjectMixin, PlasmaNodeBase, bpy.types.Node):
+class PlasmaSceneObjectMsgRcvrNode(
+ idprops.IDPropObjectMixin, PlasmaNodeBase, bpy.types.Node
+):
bl_category = "MSG"
bl_idname = "PlasmaSceneObjectMsgRcvrNode"
bl_label = "Send To Object"
bl_width_default = 190
- input_sockets = OrderedDict([
- ("message", {
- "text": "Message",
- "type": "PlasmaNodeSocketInputGeneral",
- "valid_link_sockets": {"PlasmaEnableMessageSocket"},
- "spawn_empty": True,
- }),
- ])
-
- target_object = PointerProperty(name="Object",
- description="Object to send the message to",
- type=bpy.types.Object)
+ input_sockets = OrderedDict(
+ [
+ (
+ "message",
+ {
+ "text": "Message",
+ "type": "PlasmaNodeSocketInputGeneral",
+ "valid_link_sockets": {"PlasmaEnableMessageSocket"},
+ "spawn_empty": True,
+ },
+ ),
+ ]
+ )
+
+ target_object = PointerProperty(
+ name="Object",
+ description="Object to send the message to",
+ type=bpy.types.Object,
+ )
def draw_buttons(self, context, layout):
layout.prop(self, "target_object")
@@ -723,7 +956,9 @@ class PlasmaSceneObjectMsgRcvrNode(idprops.IDPropObjectMixin, PlasmaNodeBase, bp
return {"target_object": "object_name"}
-class PlasmaSoundMsgNode(idprops.IDPropObjectMixin, PlasmaMessageWithCallbacksNode, bpy.types.Node):
+class PlasmaSoundMsgNode(
+ idprops.IDPropObjectMixin, PlasmaMessageWithCallbacksNode, bpy.types.Node
+):
bl_category = "MSG"
bl_idname = "PlasmaSoundMsgNode"
bl_label = "Sound"
@@ -732,48 +967,82 @@ class PlasmaSoundMsgNode(idprops.IDPropObjectMixin, PlasmaMessageWithCallbacksNo
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")
-
- go_to = EnumProperty(name="Go To",
- description="Where should the sound start?",
- items=[("BEGIN", "Beginning", "The beginning"),
- ("CURRENT", "(Don't Change)", "The current position"),
- ("TIME", "Time", "The time specified in seconds")],
- default="CURRENT")
- looping = EnumProperty(name="Looping",
- description="Is the sound looping?",
- items=[("kSetLooping", "Yes", "The sound is looping",),
- ("CURRENT", "(Don't Change)", "Don't change the loop status"),
- ("kUnSetLooping", "No", "The sound is NOT looping")],
- default="CURRENT")
- action = EnumProperty(name="Action",
- description="What do you want the sound to do?",
- items=[("kPlay", "Play", "Plays the sound"),
- ("kStop", "Stop", "Stops the sound",),
- ("kToggleState", "Toggle", "Toggles between Play and Stop"),
- ("CURRENT", "(Don't Change)", "Don't change the sound's playing state")],
- default="CURRENT")
- volume = EnumProperty(name="Volume",
- description="What should happen to the volume?",
- items=[("MUTE", "Mute", "Mutes the volume"),
- ("CURRENT", "(Don't Change)", "Don't change the volume"),
- ("CUSTOM", "Custom", "Manually specify the volume")],
- default="CURRENT")
-
- time = FloatProperty(name="Time",
- description="Time in seconds to begin playing from",
- min=0.0, default=0.0,
- options=set(), subtype="TIME", unit="TIME")
- volume_pct = IntProperty(name="Volume Level",
- description="Volume to play the sound",
- min=0, max=100, default=100,
- options=set(),
- subtype="PERCENTAGE")
+ 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")
+
+ go_to = EnumProperty(
+ name="Go To",
+ description="Where should the sound start?",
+ items=[
+ ("BEGIN", "Beginning", "The beginning"),
+ ("CURRENT", "(Don't Change)", "The current position"),
+ ("TIME", "Time", "The time specified in seconds"),
+ ],
+ default="CURRENT",
+ )
+ looping = EnumProperty(
+ name="Looping",
+ description="Is the sound looping?",
+ items=[
+ (
+ "kSetLooping",
+ "Yes",
+ "The sound is looping",
+ ),
+ ("CURRENT", "(Don't Change)", "Don't change the loop status"),
+ ("kUnSetLooping", "No", "The sound is NOT looping"),
+ ],
+ default="CURRENT",
+ )
+ action = EnumProperty(
+ name="Action",
+ description="What do you want the sound to do?",
+ items=[
+ ("kPlay", "Play", "Plays the sound"),
+ (
+ "kStop",
+ "Stop",
+ "Stops the sound",
+ ),
+ ("kToggleState", "Toggle", "Toggles between Play and Stop"),
+ ("CURRENT", "(Don't Change)", "Don't change the sound's playing state"),
+ ],
+ default="CURRENT",
+ )
+ volume = EnumProperty(
+ name="Volume",
+ description="What should happen to the volume?",
+ items=[
+ ("MUTE", "Mute", "Mutes the volume"),
+ ("CURRENT", "(Don't Change)", "Don't change the volume"),
+ ("CUSTOM", "Custom", "Manually specify the volume"),
+ ],
+ default="CURRENT",
+ )
+
+ time = FloatProperty(
+ name="Time",
+ description="Time in seconds to begin playing from",
+ min=0.0,
+ default=0.0,
+ options=set(),
+ subtype="TIME",
+ unit="TIME",
+ )
+ volume_pct = IntProperty(
+ name="Volume Level",
+ description="Volume to play the sound",
+ min=0,
+ max=100,
+ default=100,
+ options=set(),
+ subtype="PERCENTAGE",
+ )
def convert_callback_message(self, exporter, so, msg, target, wait):
assert not self.is_random_sound, "Callbacks are not available for random sounds"
@@ -789,7 +1058,9 @@ class PlasmaSoundMsgNode(idprops.IDPropObjectMixin, PlasmaMessageWithCallbacksNo
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.emitter_object.name))
+ self.raise_error(
+ "'{}' is not a valid Sound Emitter".format(self.emitter_object.name)
+ )
if self.is_random_sound:
yield from self._convert_random_sound_msg(exporter, so)
@@ -820,12 +1091,22 @@ class PlasmaSoundMsgNode(idprops.IDPropObjectMixin, PlasmaMessageWithCallbacksNo
# 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.emitter_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=self.emitter_object)
- indices = (-1,) if not self.sound_name or len(soundemit.sounds) == 1 else soundemit.get_sound_indices(self.sound_name)
+ 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()
msg.addReceiver(audible_key)
@@ -866,7 +1147,9 @@ class PlasmaSoundMsgNode(idprops.IDPropObjectMixin, PlasmaMessageWithCallbacksNo
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")
+ layout.prop_search(
+ self, "sound_name", soundemit, "sounds", icon="SOUND"
+ )
else:
layout.label("Not a Sound Emitter", icon="ERROR")
@@ -901,10 +1184,12 @@ class PlasmaTimerCallbackMsgNode(PlasmaMessageWithCallbacksNode, bpy.types.Node)
bl_idname = "PlasmaTimerCallbackMsgNode"
bl_label = "Timed Callback"
- delay = FloatProperty(name="Delay",
- description="Time (in seconds) to wait until continuing",
- min=0.1,
- default=1.0)
+ delay = FloatProperty(
+ name="Delay",
+ description="Time (in seconds) to wait until continuing",
+ min=0.1,
+ default=1.0,
+ )
def draw_buttons(self, context, layout):
layout.prop(self, "delay")
@@ -924,15 +1209,20 @@ class PlasmaTriggerMultiStageMsgNode(PlasmaMessageNode, bpy.types.Node):
bl_idname = "PlasmaTriggerMultiStageMsgNode"
bl_label = "Trigger MultiStage"
- output_sockets = OrderedDict([
- ("satisfies", {
- "text": "Trigger",
- "type": "PlasmaConditionSocket",
- "valid_link_nodes": "PlasmaMultiStageBehaviorNode",
- "valid_link_sockets": "PlasmaConditionSocket",
- "link_limit": 1,
- })
- ])
+ output_sockets = OrderedDict(
+ [
+ (
+ "satisfies",
+ {
+ "text": "Trigger",
+ "type": "PlasmaConditionSocket",
+ "valid_link_nodes": "PlasmaMultiStageBehaviorNode",
+ "valid_link_sockets": "PlasmaConditionSocket",
+ "link_limit": 1,
+ },
+ )
+ ]
+ )
def convert_message(self, exporter, so):
# Yeah, this is not a REAL Plasma message, but the Korman way is to try to hide these little
@@ -952,16 +1242,18 @@ class PlasmaFootstepSoundMsgNode(PlasmaMessageNode, bpy.types.Node):
bl_idname = "PlasmaFootstepSoundMsgNode"
bl_label = "Footstep Sound"
- surface = EnumProperty(name="Surface",
- description="What kind of surface are we walking on?",
- items=footstep_surfaces,
- default="stone")
+ surface = EnumProperty(
+ name="Surface",
+ description="What kind of surface are we walking on?",
+ items=footstep_surfaces,
+ default="stone",
+ )
def draw_buttons(self, context, layout):
layout.prop(self, "surface")
def convert_message(self, exporter, so):
msg = plArmatureEffectStateMsg()
- msg.BCastFlags |= (plMessage.kPropagateToModifiers | plMessage.kNetPropagate)
+ msg.BCastFlags |= plMessage.kPropagateToModifiers | plMessage.kNetPropagate
msg.surface = footstep_surface_ids[self.surface]
return msg
diff --git a/korman/nodes/node_python.py b/korman/nodes/node_python.py
index dd0b94a..74a7bc0 100644
--- a/korman/nodes/node_python.py
+++ b/korman/nodes/node_python.py
@@ -27,10 +27,23 @@ from .. import idprops
from ..plasma_attributes import get_attributes_from_str
_single_user_attribs = {
- "ptAttribBoolean", "ptAttribInt", "ptAttribFloat", "ptAttribString", "ptAttribDropDownList",
- "ptAttribSceneobject", "ptAttribDynamicMap", "ptAttribGUIDialog", "ptAttribExcludeRegion",
- "ptAttribWaveSet", "ptAttribSwimCurrent", "ptAttribAnimation", "ptAttribBehavior",
- "ptAttribMaterial", "ptAttribMaterialAnimation", "ptAttribGUIPopUpMenu", "ptAttribGUISkin",
+ "ptAttribBoolean",
+ "ptAttribInt",
+ "ptAttribFloat",
+ "ptAttribString",
+ "ptAttribDropDownList",
+ "ptAttribSceneobject",
+ "ptAttribDynamicMap",
+ "ptAttribGUIDialog",
+ "ptAttribExcludeRegion",
+ "ptAttribWaveSet",
+ "ptAttribSwimCurrent",
+ "ptAttribAnimation",
+ "ptAttribBehavior",
+ "ptAttribMaterial",
+ "ptAttribMaterialAnimation",
+ "ptAttribGUIPopUpMenu",
+ "ptAttribGUISkin",
"ptAttribGrassShader",
}
@@ -83,9 +96,11 @@ _attrib_key_types = {
"ptAttribGUIPopUpMenu": plFactory.ClassIndex("pfGUIPopUpMenu"),
"ptAttribGUISkin": plFactory.ClassIndex("pfGUISkin"),
"ptAttribWaveSet": plFactory.ClassIndex("plWaveSet7"),
- "ptAttribSwimCurrent": (plFactory.ClassIndex("plSwimRegionInterface"),
- plFactory.ClassIndex("plSwimCircularCurrentRegion"),
- plFactory.ClassIndex("plSwimStraightCurrentRegion")),
+ "ptAttribSwimCurrent": (
+ plFactory.ClassIndex("plSwimRegionInterface"),
+ plFactory.ClassIndex("plSwimCircularCurrentRegion"),
+ plFactory.ClassIndex("plSwimStraightCurrentRegion"),
+ ),
"ptAttribClusterList": plFactory.ClassIndex("plClusterGroup"),
"ptAttribMaterialAnimation": plFactory.ClassIndex("plLayerAnimation"),
"ptAttribGrassShader": plFactory.ClassIndex("plGrassShaderMod"),
@@ -174,8 +189,10 @@ class PlasmaAttribute(bpy.types.PropertyGroup):
def _get_simple_value(self):
return getattr(self, self._simple_attrs[self.attribute_type])
+
def _set_simple_value(self, value):
setattr(self, self._simple_attrs[self.attribute_type], value)
+
simple_value = property(_get_simple_value, _set_simple_value)
@@ -203,17 +220,21 @@ class PlasmaPythonFileNode(PlasmaVersionedNode, bpy.types.Node):
self.attributes.clear()
self.inputs.clear()
if self.text_id is not None:
- bpy.ops.node.plasma_attributes_to_node(node_path=self.node_path, text_path=self.text_id.name)
+ bpy.ops.node.plasma_attributes_to_node(
+ node_path=self.node_path, text_path=self.text_id.name
+ )
- filename = StringProperty(name="File Name",
- description="Python Filename",
- update=_update_pyfile)
+ filename = StringProperty(
+ name="File Name", description="Python Filename", update=_update_pyfile
+ )
filepath = StringProperty(options={"HIDDEN"})
- text_id = PointerProperty(name="Script File",
- description="Script file datablock",
- type=bpy.types.Text,
- poll=_poll_pytext,
- update=_update_pytext)
+ text_id = PointerProperty(
+ name="Script File",
+ description="Script file datablock",
+ type=bpy.types.Text,
+ poll=_poll_pytext,
+ update=_update_pytext,
+ )
# This property exists for UI purposes ONLY
package = BoolProperty(options={"HIDDEN", "SKIP_SAVE"})
@@ -223,7 +244,7 @@ class PlasmaPythonFileNode(PlasmaVersionedNode, bpy.types.Node):
@property
def attribute_map(self):
- return { i.attribute_id: i for i in self.attributes }
+ return {i.attribute_id: i for i in self.attributes}
def draw_buttons(self, context, layout):
main_row = layout.row(align=True)
@@ -233,7 +254,9 @@ class PlasmaPythonFileNode(PlasmaVersionedNode, bpy.types.Node):
# open operator
sel_text = "Load Script" if self.text_id is None else ""
- operator = main_row.operator("file.plasma_file_picker", icon="FILESEL", text=sel_text)
+ operator = main_row.operator(
+ "file.plasma_file_picker", icon="FILESEL", text=sel_text
+ )
operator.filter_glob = "*.py"
operator.data_path = self.node_path
operator.filename_property = "filename"
@@ -251,14 +274,19 @@ class PlasmaPythonFileNode(PlasmaVersionedNode, bpy.types.Node):
# rescan operator
row = main_row.row(align=True)
row.enabled = self.text_id is not None
- operator = row.operator("node.plasma_attributes_to_node", icon="FILE_REFRESH", text="")
+ operator = row.operator(
+ "node.plasma_attributes_to_node", icon="FILE_REFRESH", text=""
+ )
if self.text_id is not None:
operator.text_path = self.text_id.name
operator.node_path = self.node_path
# This could happen on an upgrade
if self.text_id is None and self.filename:
- layout.label(text="Script '{}' is not loaded in Blender".format(self.filename), icon="ERROR")
+ layout.label(
+ text="Script '{}' is not loaded in Blender".format(self.filename),
+ icon="ERROR",
+ )
def get_key(self, exporter, so):
return self._find_create_key(plPythonFileMod, exporter, so=so)
@@ -279,12 +307,16 @@ class PlasmaPythonFileNode(PlasmaVersionedNode, bpy.types.Node):
# Check to see if we should pack this file
if exporter.output.want_py_text(self.text_id):
- exporter.report.msg("Including Python '{}' for package", self.filename, indent=3)
+ exporter.report.msg(
+ "Including Python '{}' for package", self.filename, indent=3
+ )
exporter.output.add_python_mod(self.filename, text_id=self.text_id)
# PFMs can have their own SDL...
sdl_text = bpy.data.texts.get("{}.sdl".format(py_name), None)
if sdl_text is not None:
- exporter.report.msg("Including corresponding SDL '{}'", sdl_text.name, indent=3)
+ exporter.report.msg(
+ "Including corresponding SDL '{}'", sdl_text.name, indent=3
+ )
exporter.output.add_sdl(sdl_text.name, text_id=sdl_text)
# Handle exporting the Python Parameters
@@ -292,7 +324,11 @@ class PlasmaPythonFileNode(PlasmaVersionedNode, bpy.types.Node):
for socket in attrib_sockets:
from_node = socket.links[0].from_node
- value = from_node.value if socket.is_simple_value else from_node.get_key(exporter, so)
+ value = (
+ from_node.value
+ if socket.is_simple_value
+ else from_node.get_key(exporter, so)
+ )
if isinstance(value, str) or not isinstance(value, Iterable):
value = (value,)
for i in value:
@@ -312,14 +348,23 @@ class PlasmaPythonFileNode(PlasmaVersionedNode, bpy.types.Node):
# an animated lamp.
if not bool(bo.users_group):
for light in exporter.mgr.find_interfaces(plLightInfo, so):
- exporter.report.msg("Marking RT light '{}' as animated due to usage in a Python File node",
- so.key.name, indent=3)
+ exporter.report.msg(
+ "Marking RT light '{}' as animated due to usage in a Python File node",
+ so.key.name,
+ indent=3,
+ )
light.setProperty(plLightInfo.kLPMovable, True)
- def _export_key_attrib(self, exporter, bo, so : plSceneObject, key : plKey, socket) -> None:
+ def _export_key_attrib(
+ self, exporter, bo, so: plSceneObject, key: plKey, socket
+ ) -> None:
if key is None:
- exporter.report.warn("Attribute '{}' didn't return a key and therefore will be unavailable to Python",
- self.id_data.name, socket.links[0].name, indent=3)
+ exporter.report.warn(
+ "Attribute '{}' didn't return a key and therefore will be unavailable to Python",
+ self.id_data.name,
+ socket.links[0].name,
+ indent=3,
+ )
return
key_type = _attrib_key_types[socket.attribute_type]
@@ -328,9 +373,13 @@ class PlasmaPythonFileNode(PlasmaVersionedNode, bpy.types.Node):
else:
good_key = key.type == key_type
if not good_key:
- exporter.report.warn("'{}' Node '{}' returned an unexpected key type '{}'",
- self.id_data.name, socket.links[0].from_node.name,
- plFactory.ClassName(key.type), indent=3)
+ exporter.report.warn(
+ "'{}' Node '{}' returned an unexpected key type '{}'",
+ self.id_data.name,
+ socket.links[0].from_node.name,
+ plFactory.ClassName(key.type),
+ indent=3,
+ )
if isinstance(key.object, plSceneObject):
self._export_ancillary_sceneobject(exporter, bo, key.object)
@@ -352,10 +401,12 @@ class PlasmaPythonFileNode(PlasmaVersionedNode, bpy.types.Node):
if attrib_type in node_attrib_types:
if issubclass(i, PlasmaAttribNodeBase):
- yield { "node_idname": i.bl_idname,
- "node_text": i.bl_label,
- "socket_name": "pfm",
- "socket_text": "Python File" }
+ yield {
+ "node_idname": i.bl_idname,
+ "node_text": i.bl_label,
+ "socket_name": "pfm",
+ "socket_text": "Python File",
+ }
else:
for socket_name, socket_def in i.output_sockets.items():
if socket_def.get("hidden") is True:
@@ -365,15 +416,23 @@ class PlasmaPythonFileNode(PlasmaVersionedNode, bpy.types.Node):
valid_link_nodes = socket_def.get("valid_link_nodes")
valid_link_sockets = socket_def.get("valid_link_sockets")
- if valid_link_nodes is not None and self.bl_idname not in valid_link_nodes:
+ if (
+ valid_link_nodes is not None
+ and self.bl_idname not in valid_link_nodes
+ ):
continue
- if valid_link_sockets is not None and "PlasmaPythonFileNodeSocket" not in valid_link_sockets:
+ if (
+ valid_link_sockets is not None
+ and "PlasmaPythonFileNodeSocket" not in valid_link_sockets
+ ):
continue
- yield { "node_idname": i.bl_idname,
- "node_text": i.bl_label,
- "socket_name": socket_name,
- "socket_text": socket_def["text"] }
+ yield {
+ "node_idname": i.bl_idname,
+ "node_text": i.bl_label,
+ "socket_name": socket_name,
+ "socket_text": socket_def["text"],
+ }
@classmethod
def generate_valid_links_to(cls, context, socket, is_output):
@@ -394,9 +453,15 @@ class PlasmaPythonFileNode(PlasmaVersionedNode, bpy.types.Node):
return
valid_link_sockets = socket_def.get("valid_link_sockets")
valid_link_nodes = socket_def.get("valid_link_nodes")
- if valid_link_sockets is not None and "PlasmaPythonFileNodeSocket" not in valid_link_sockets:
+ if (
+ valid_link_sockets is not None
+ and "PlasmaPythonFileNodeSocket" not in valid_link_sockets
+ ):
return
- if valid_link_nodes is not None and "PlasmaPythonFileNode" not in valid_link_nodes:
+ if (
+ valid_link_nodes is not None
+ and "PlasmaPythonFileNode" not in valid_link_nodes
+ ):
return
# Ok, apparently this thing can connect as a ptAttribute. The only problem with that is
@@ -414,15 +479,20 @@ class PlasmaPythonFileNode(PlasmaVersionedNode, bpy.types.Node):
continue
# *gulp*
- yield { "node_idname": "PlasmaPythonFileNode",
- "node_text": text_id.name,
- "node_settings": { "filename": text_id.name },
- "socket_name": attrib["name"],
- "socket_text": attrib["name"] }
+ yield {
+ "node_idname": "PlasmaPythonFileNode",
+ "node_text": text_id.name,
+ "node_settings": {"filename": text_id.name},
+ "socket_name": attrib["name"],
+ "socket_text": attrib["name"],
+ }
def harvest_actors(self):
for i in self.inputs:
- if not i.is_linked or i.attribute_type not in {"ptAttribSceneobject", "ptAttribSceneobjectList"}:
+ if not i.is_linked or i.attribute_type not in {
+ "ptAttribSceneobject",
+ "ptAttribSceneobjectList",
+ }:
continue
node = i.links[0].from_node
if node.target_object is not None:
@@ -486,7 +556,11 @@ class PlasmaPythonFileNode(PlasmaVersionedNode, bpy.types.Node):
if not inputs:
self._make_attrib_socket(attrib, empty)
elif attrib.attribute_type not in _single_user_attribs:
- unconnected = [socket for socket in inputs if not socket.is_linked or socket in toasty_sockets]
+ unconnected = [
+ socket
+ for socket in inputs
+ if not socket.is_linked or socket in toasty_sockets
+ ]
if not unconnected:
self._make_attrib_socket(attrib, empty)
while len(unconnected) > 1:
@@ -643,9 +717,13 @@ class PlasmaAttribDropDownListNode(PlasmaAttribNodeBase, bpy.types.Node):
def _list_items(self, context):
attrib = self.to_socket
if attrib is not None:
- return [(option.value, option.value, "") for option in attrib.attribute_arguments.options]
+ return [
+ (option.value, option.value, "")
+ for option in attrib.attribute_arguments.options
+ ]
else:
return []
+
value = EnumProperty(items=_list_items)
def draw_buttons(self, context, layout):
@@ -665,8 +743,10 @@ class PlasmaAttribIntNode(PlasmaAttribNodeBase, bpy.types.Node):
def _get_int(self):
return round(self.value_float)
+
def _set_int(self, value):
self.value_float = float(value)
+
def _on_update_float(self, context):
self.inited = True
@@ -710,35 +790,59 @@ class PlasmaAttribIntNode(PlasmaAttribNodeBase, bpy.types.Node):
return self.value_int
else:
return self.value_float
+
def _set_value(self, value):
self.value_float = value
+
value = property(_get_value, _set_value)
def _range_label(self, layout):
attrib = self.to_socket
- layout.label(text="Range: [{}, {}]".format(attrib.attribute_arguments.range_values[0], attrib.attribute_arguments.range_values[1]))
+ layout.label(
+ text="Range: [{}, {}]".format(
+ attrib.attribute_arguments.range_values[0],
+ attrib.attribute_arguments.range_values[1],
+ )
+ )
def _out_of_range(self, value):
attrib = self.to_socket
- if attrib.attribute_arguments.range_values[0] == attrib.attribute_arguments.range_values[1]:
+ if (
+ attrib.attribute_arguments.range_values[0]
+ == attrib.attribute_arguments.range_values[1]
+ ):
# Ignore degenerate intervals
return False
- if attrib.attribute_arguments.range_values[0] <= value <= attrib.attribute_arguments.range_values[1]:
+ if (
+ attrib.attribute_arguments.range_values[0]
+ <= value
+ <= attrib.attribute_arguments.range_values[1]
+ ):
return False
return True
-class PlasmaAttribObjectNode(idprops.IDPropObjectMixin, PlasmaAttribNodeBase, bpy.types.Node):
+class PlasmaAttribObjectNode(
+ idprops.IDPropObjectMixin, PlasmaAttribNodeBase, bpy.types.Node
+):
bl_category = "PYTHON"
bl_idname = "PlasmaAttribObjectNode"
bl_label = "Object Attribute"
- pl_attrib = ("ptAttribSceneobject", "ptAttribSceneobjectList", "ptAttribAnimation",
- "ptAttribSwimCurrent", "ptAttribWaveSet", "ptAttribGrassShader")
-
- target_object = PointerProperty(name="Object",
- description="Object containing the required data",
- type=bpy.types.Object)
+ pl_attrib = (
+ "ptAttribSceneobject",
+ "ptAttribSceneobjectList",
+ "ptAttribAnimation",
+ "ptAttribSwimCurrent",
+ "ptAttribWaveSet",
+ "ptAttribGrassShader",
+ )
+
+ target_object = PointerProperty(
+ name="Object",
+ description="Object containing the required data",
+ type=bpy.types.Object,
+ )
def init(self, context):
super().init(context)
@@ -765,8 +869,12 @@ class PlasmaAttribObjectNode(idprops.IDPropObjectMixin, PlasmaAttribNodeBase, bp
return ref_so_key
elif attrib == "ptAttribAnimation":
anim = bo.plasma_modifiers.animation
- agmod = exporter.mgr.find_create_key(plAGModifier, so=ref_so, name=anim.key_name)
- agmaster = exporter.mgr.find_create_key(plAGMasterModifier, so=ref_so, name=anim.key_name)
+ agmod = exporter.mgr.find_create_key(
+ plAGModifier, so=ref_so, name=anim.key_name
+ )
+ agmaster = exporter.mgr.find_create_key(
+ plAGMasterModifier, so=ref_so, name=anim.key_name
+ )
return agmaster
elif attrib == "ptAttribSwimCurrent":
swimregion = bo.plasma_modifiers.swimregion
@@ -774,17 +882,22 @@ class PlasmaAttribObjectNode(idprops.IDPropObjectMixin, PlasmaAttribNodeBase, bp
elif attrib == "ptAttribWaveSet":
waveset = bo.plasma_modifiers.water_basic
if not waveset.enabled:
- self.raise_error("water modifier not enabled on '{}'".format(self.object_name))
+ self.raise_error(
+ "water modifier not enabled on '{}'".format(self.object_name)
+ )
return exporter.mgr.find_create_key(plWaveSet7, so=ref_so, bl=bo)
elif attrib == "ptAttribGrassShader":
grass_shader = bo.plasma_modifiers.grass_shader
if not grass_shader.enabled:
- self.raise_error("grass shader modifier not enabled on '{}'".format(self.object_name))
+ self.raise_error(
+ "grass shader modifier not enabled on '{}'".format(self.object_name)
+ )
if exporter.mgr.getVer() <= pvPots:
return None
- return [exporter.mgr.find_create_key(plGrassShaderMod, so=ref_so, name=i.name)
- for i in exporter.mesh.material.get_materials(bo)]
-
+ return [
+ exporter.mgr.find_create_key(plGrassShaderMod, so=ref_so, name=i.name)
+ for i in exporter.mesh.material.get_materials(bo)
+ ]
@classmethod
def _idprop_mapping(cls):
@@ -810,21 +923,31 @@ class PlasmaAttribStringNode(PlasmaAttribNodeBase, bpy.types.Node):
self.value = attrib.simple_value
-class PlasmaAttribTextureNode(idprops.IDPropMixin, PlasmaAttribNodeBase, bpy.types.Node):
+class PlasmaAttribTextureNode(
+ idprops.IDPropMixin, PlasmaAttribNodeBase, bpy.types.Node
+):
bl_category = "PYTHON"
bl_idname = "PlasmaAttribTextureNode"
bl_label = "Texture Attribute"
bl_width_default = 175
- pl_attrib = ("ptAttribMaterial", "ptAttribMaterialList",
- "ptAttribDynamicMap", "ptAttribMaterialAnimation")
+ pl_attrib = (
+ "ptAttribMaterial",
+ "ptAttribMaterialList",
+ "ptAttribDynamicMap",
+ "ptAttribMaterialAnimation",
+ )
def _poll_material(self, value: bpy.types.Material) -> bool:
# Don't filter materials by texture - this would (potentially) result in surprising UX
# in that you would have to clear the texture selection before being able to select
# certain materials.
if self.target_object is not None:
- object_materials = (slot.material for slot in self.target_object.material_slots if slot and slot.material)
+ object_materials = (
+ slot.material
+ for slot in self.target_object.material_slots
+ if slot and slot.material
+ )
return value in object_materials
return True
@@ -845,25 +968,37 @@ class PlasmaAttribTextureNode(idprops.IDPropMixin, PlasmaAttribNodeBase, bpy.typ
if self.material is not None:
return value.name in self.material.texture_slots
elif self.target_object is not None:
- for i in (slot.material for slot in self.target_object.material_slots if slot and slot.material):
- if value in (slot.texture for slot in i.texture_slots if slot and slot.texture):
+ for i in (
+ slot.material
+ for slot in self.target_object.material_slots
+ if slot and slot.material
+ ):
+ if value in (
+ slot.texture for slot in i.texture_slots if slot and slot.texture
+ ):
return True
return False
else:
return True
- target_object = PointerProperty(name="Object",
- description="",
- type=bpy.types.Object,
- poll=idprops.poll_drawable_objects)
- material = PointerProperty(name="Material",
- description="Material the texture is attached to",
- type=bpy.types.Material,
- poll=_poll_material)
- texture = PointerProperty(name="Texture",
- description="Texture to expose to Python",
- type=bpy.types.Texture,
- poll=_poll_texture)
+ target_object = PointerProperty(
+ name="Object",
+ description="",
+ type=bpy.types.Object,
+ poll=idprops.poll_drawable_objects,
+ )
+ material = PointerProperty(
+ name="Material",
+ description="Material the texture is attached to",
+ type=bpy.types.Material,
+ poll=_poll_material,
+ )
+ texture = PointerProperty(
+ name="Texture",
+ description="Texture to expose to Python",
+ type=bpy.types.Texture,
+ poll=_poll_texture,
+ )
def init(self, context):
super().init(context)
@@ -872,14 +1007,26 @@ class PlasmaAttribTextureNode(idprops.IDPropMixin, PlasmaAttribNodeBase, bpy.typ
def draw_buttons(self, context, layout):
if self.target_object is not None:
- iter_materials = lambda: (i.material for i in self.target_object.material_slots if i and i.material)
+ iter_materials = lambda: (
+ i.material
+ for i in self.target_object.material_slots
+ if i and i.material
+ )
if self.material is not None:
if self.material not in iter_materials():
- layout.label("The selected material is not linked to the target object.", icon="ERROR")
+ layout.label(
+ "The selected material is not linked to the target object.",
+ icon="ERROR",
+ )
layout.alert = True
if self.texture is not None:
- if not frozenset(self.texture.users_material) & frozenset(iter_materials()):
- layout.label("The selected texture is not on a material linked to the target object.", icon="ERROR")
+ if not frozenset(self.texture.users_material) & frozenset(
+ iter_materials()
+ ):
+ layout.label(
+ "The selected texture is not on a material linked to the target object.",
+ icon="ERROR",
+ )
layout.alert = True
layout.prop(self, "target_object")
@@ -888,31 +1035,51 @@ class PlasmaAttribTextureNode(idprops.IDPropMixin, PlasmaAttribNodeBase, bpy.typ
def get_key(self, exporter, so):
if not any((self.target_object, self.material, self.texture)):
- self.raise_error("At least one of: target object, material, or texture must be specified.")
+ self.raise_error(
+ "At least one of: target object, material, or texture must be specified."
+ )
attrib = self.to_socket
if attrib is None:
self.raise_error("must be connected to a Python File node!")
attrib = attrib.attribute_type
- layer_generator = exporter.mesh.material.get_layers(self.target_object, self.material, self.texture)
+ layer_generator = exporter.mesh.material.get_layers(
+ self.target_object, self.material, self.texture
+ )
bottom_layers = (i.object.bottomOfStack for i in layer_generator)
if attrib == "ptAttribDynamicMap":
- yield from filter(lambda x: x and isinstance(x.object, plDynamicTextMap),
- (i.object.texture for i in layer_generator))
+ yield from filter(
+ lambda x: x and isinstance(x.object, plDynamicTextMap),
+ (i.object.texture for i in layer_generator),
+ )
elif attrib == "ptAttribMaterialAnimation":
- yield from filter(lambda x: x and isinstance(x.object, plLayerAnimationBase), layer_generator)
+ yield from filter(
+ lambda x: x and isinstance(x.object, plLayerAnimationBase),
+ layer_generator,
+ )
elif attrib == "ptAttribMaterialList":
- yield from filter(lambda x: x and not isinstance(x.object, plLayerAnimationBase), bottom_layers)
+ yield from filter(
+ lambda x: x and not isinstance(x.object, plLayerAnimationBase),
+ bottom_layers,
+ )
elif attrib == "ptAttribMaterial":
# Only return the first key; warn about others.
- result_gen = filter(lambda x: x and not isinstance(x.object, plLayerAnimationBase), bottom_layers)
+ result_gen = filter(
+ lambda x: x and not isinstance(x.object, plLayerAnimationBase),
+ bottom_layers,
+ )
result = next(result_gen, None)
remainder = sum((1 for i in result))
if remainder > 1:
- exporter.report.warn("'{}.{}': Expected a single layer, but mapped to {}. Make the settings more specific.",
- self.id_data.name, self.path_from_id(), remainder + 1, indent=2)
+ exporter.report.warn(
+ "'{}.{}': Expected a single layer, but mapped to {}. Make the settings more specific.",
+ self.id_data.name,
+ self.path_from_id(),
+ remainder + 1,
+ indent=2,
+ )
if result is not None:
yield result
else:
@@ -920,16 +1087,19 @@ class PlasmaAttribTextureNode(idprops.IDPropMixin, PlasmaAttribNodeBase, bpy.typ
@classmethod
def _idprop_mapping(cls):
- return {"material": "material_name",
- "texture": "texture_name"}
+ return {"material": "material_name", "texture": "texture_name"}
def _idprop_sources(self):
- return {"material_name": bpy.data.materials,
- "texture_name": bpy.data.textures}
+ return {"material_name": bpy.data.materials, "texture_name": bpy.data.textures}
def _is_animated(self, material, texture):
- return ((material.animation_data is not None and material.animation_data.action is not None)
- or (texture.animation_data is not None and texture.animation_data.action is not None))
+ return (
+ material.animation_data is not None
+ and material.animation_data.action is not None
+ ) or (
+ texture.animation_data is not None
+ and texture.animation_data.action is not None
+ )
def _is_dyntext(self, texture):
return texture.type == "IMAGE" and texture.image is None
@@ -947,7 +1117,6 @@ _attrib_colors = {
"ptAttribResponder": (0.031, 0.110, 0.290, 1.0),
"ptAttribResponderList": (0.031, 0.110, 0.290, 1.0),
"ptAttribString": (0.675, 0.659, 0.494, 1.0),
-
PlasmaAttribIntNode.pl_attrib: (0.443, 0.439, 0.392, 1.0),
PlasmaAttribObjectNode.pl_attrib: (0.565, 0.267, 0.0, 1.0),
PlasmaAttribTextureNode.pl_attrib: (0.035, 0.353, 0.0, 1.0),
diff --git a/korman/nodes/node_responder.py b/korman/nodes/node_responder.py
index 10ccf69..0fdd675 100644
--- a/korman/nodes/node_responder.py
+++ b/korman/nodes/node_responder.py
@@ -23,6 +23,7 @@ import uuid
from .node_core import *
from .node_deprecated import PlasmaVersionedNode
+
class PlasmaResponderNode(PlasmaVersionedNode, bpy.types.Node):
bl_category = "LOGIC"
bl_idname = "PlasmaResponderNode"
@@ -32,50 +33,70 @@ class PlasmaResponderNode(PlasmaVersionedNode, bpy.types.Node):
# These are the Python attributes we can fill in
pl_attrib = {"ptAttribResponder", "ptAttribResponderList", "ptAttribNamedResponder"}
- detect_trigger = BoolProperty(name="Detect Trigger",
- description="When notified, trigger the Responder",
- default=True)
- detect_untrigger = BoolProperty(name="Detect UnTrigger",
- description="When notified, untrigger the Responder",
- default=False)
- no_ff_sounds = BoolProperty(name="Don't F-Fwd Sounds",
- description="When fast-forwarding, play sound effects",
- default=False)
- default_state = IntProperty(name="Default State Index",
- options=set())
-
- input_sockets = OrderedDict([
- ("condition", {
- "text": "Condition",
- "type": "PlasmaConditionSocket",
- "spawn_empty": True,
- }),
- ])
-
- output_sockets = OrderedDict([
- ("keyref", {
- "text": "References",
- "type": "PlasmaPythonReferenceNodeSocket",
- "valid_link_nodes": {"PlasmaPythonFileNode"},
- }),
- ("state_refs", {
- "text": "State",
- "type": "PlasmaRespStateRefSocket",
- "valid_link_nodes": "PlasmaResponderStateNode",
- "valid_link_sockets": "PlasmaRespStateRefSocket",
- "link_limit": 1,
- "spawn_empty": True,
- }),
-
- # This version of the states socket has been deprecated.
- # We need to be able to track 1 socket -> 1 state to manage
- # responder state IDs
- ("states", {
- "text": "States",
- "type": "PlasmaRespStateSocket",
- "hidden": True,
- }),
- ])
+ detect_trigger = BoolProperty(
+ name="Detect Trigger",
+ description="When notified, trigger the Responder",
+ default=True,
+ )
+ detect_untrigger = BoolProperty(
+ name="Detect UnTrigger",
+ description="When notified, untrigger the Responder",
+ default=False,
+ )
+ no_ff_sounds = BoolProperty(
+ name="Don't F-Fwd Sounds",
+ description="When fast-forwarding, play sound effects",
+ default=False,
+ )
+ default_state = IntProperty(name="Default State Index", options=set())
+
+ input_sockets = OrderedDict(
+ [
+ (
+ "condition",
+ {
+ "text": "Condition",
+ "type": "PlasmaConditionSocket",
+ "spawn_empty": True,
+ },
+ ),
+ ]
+ )
+
+ output_sockets = OrderedDict(
+ [
+ (
+ "keyref",
+ {
+ "text": "References",
+ "type": "PlasmaPythonReferenceNodeSocket",
+ "valid_link_nodes": {"PlasmaPythonFileNode"},
+ },
+ ),
+ (
+ "state_refs",
+ {
+ "text": "State",
+ "type": "PlasmaRespStateRefSocket",
+ "valid_link_nodes": "PlasmaResponderStateNode",
+ "valid_link_sockets": "PlasmaRespStateRefSocket",
+ "link_limit": 1,
+ "spawn_empty": True,
+ },
+ ),
+ # This version of the states socket has been deprecated.
+ # We need to be able to track 1 socket -> 1 state to manage
+ # responder state IDs
+ (
+ "states",
+ {
+ "text": "States",
+ "type": "PlasmaRespStateSocket",
+ "hidden": True,
+ },
+ ),
+ ]
+ )
def draw_buttons(self, context, layout):
layout.prop(self, "detect_trigger")
@@ -154,6 +175,7 @@ class PlasmaResponderNode(PlasmaVersionedNode, bpy.types.Node):
# linked to other states will be converted at the end of the list.
if self.version == 1:
states = set()
+
def _link_states(state):
if state in states:
return
@@ -162,6 +184,7 @@ class PlasmaResponderNode(PlasmaVersionedNode, bpy.types.Node):
goto = state.find_output("gotostate")
if goto is not None:
_link_states(goto)
+
for i in self.find_outputs("states"):
_link_states(i)
self.unlink_outputs("states", "socket deprecated (upgrade complete)")
@@ -177,63 +200,98 @@ class PlasmaResponderStateNode(PlasmaNodeBase, bpy.types.Node):
resp_node = self.find_input("resp")
if resp_node is not None:
try:
- state_idx = next((idx for idx, node in enumerate(resp_node.find_outputs("state_refs")) if node == self))
+ state_idx = next(
+ (
+ idx
+ for idx, node in enumerate(resp_node.find_outputs("state_refs"))
+ if node == self
+ )
+ )
except StopIteration:
return False
else:
return resp_node.default_state == state_idx
return False
+
def _set_default_state(self, value):
if value:
resp_node = self.find_input("resp")
if resp_node is not None:
try:
- state_idx = next((idx for idx, node in enumerate(resp_node.find_outputs("state_refs")) if node == self))
+ state_idx = next(
+ (
+ idx
+ for idx, node in enumerate(
+ resp_node.find_outputs("state_refs")
+ )
+ if node == self
+ )
+ )
except StopIteration:
self._whine("unable to set default state on responder")
else:
resp_node.default_state = state_idx
- default_state = BoolProperty(name="Default State",
- description="This state is the responder's default",
- get=_get_default_state,
- set=_set_default_state,
- options=set())
-
- input_sockets = OrderedDict([
- ("condition", {
- "text": "Triggers State",
- "type": "PlasmaRespStateSocket",
- "spawn_empty": True,
- }),
- ("resp", {
- "text": "Responder",
- "type": "PlasmaRespStateRefSocket",
- "valid_link_nodes": "PlasmaResponderNode",
- "valid_link_sockets": "PlasmaRespStateRefSocket",
- }),
- ])
-
- output_sockets = OrderedDict([
- # This socket has been deprecated.
- ("cmds", {
- "text": "Commands",
- "type": "PlasmaRespCommandSocket",
- "hidden": True,
- }),
-
- # These sockets are valid.
- ("msgs", {
- "text": "Send Message",
- "type": "PlasmaMessageSocket",
- "valid_link_sockets": "PlasmaMessageSocket",
- }),
- ("gotostate", {
- "link_limit": 1,
- "text": "Triggers State",
- "type": "PlasmaRespStateSocket",
- }),
- ])
+ default_state = BoolProperty(
+ name="Default State",
+ description="This state is the responder's default",
+ get=_get_default_state,
+ set=_set_default_state,
+ options=set(),
+ )
+
+ input_sockets = OrderedDict(
+ [
+ (
+ "condition",
+ {
+ "text": "Triggers State",
+ "type": "PlasmaRespStateSocket",
+ "spawn_empty": True,
+ },
+ ),
+ (
+ "resp",
+ {
+ "text": "Responder",
+ "type": "PlasmaRespStateRefSocket",
+ "valid_link_nodes": "PlasmaResponderNode",
+ "valid_link_sockets": "PlasmaRespStateRefSocket",
+ },
+ ),
+ ]
+ )
+
+ output_sockets = OrderedDict(
+ [
+ # This socket has been deprecated.
+ (
+ "cmds",
+ {
+ "text": "Commands",
+ "type": "PlasmaRespCommandSocket",
+ "hidden": True,
+ },
+ ),
+ # These sockets are valid.
+ (
+ "msgs",
+ {
+ "text": "Send Message",
+ "type": "PlasmaMessageSocket",
+ "valid_link_sockets": "PlasmaMessageSocket",
+ },
+ ),
+ (
+ "gotostate",
+ {
+ "link_limit": 1,
+ "text": "Triggers State",
+ "type": "PlasmaRespStateSocket",
+ },
+ ),
+ ]
+ )
def draw_buttons(self, context, layout):
layout.active = self.find_input("resp") is not None
@@ -274,11 +332,17 @@ class PlasmaResponderStateNode(PlasmaNodeBase, bpy.types.Node):
return -1
def find_create_wait(self, exporter, so, node):
- i, cmd = next(((i, cmd) for i, cmd in enumerate(self.commands) if cmd[0] == node))
- wait = next((key for key, value in self.waits.items() if value == i), None)
+ i, cmd = next(
+ ((i, cmd) for i, cmd in enumerate(self.commands) if cmd[0] == node)
+ )
+ wait = next(
+ (key for key, value in self.waits.items() if value == i), None
+ )
if wait is None:
wait = self.add_wait(i)
- node.convert_callback_message(exporter, so, cmd[1].msg, self.responder.key, wait)
+ node.convert_callback_message(
+ exporter, so, cmd[1].msg, self.responder.key, wait
+ )
return wait
def save(self, state):
@@ -317,7 +381,9 @@ class PlasmaResponderStateNode(PlasmaNodeBase, bpy.types.Node):
pfmNotify.addEvent(proCallbackEventData())
state.addCommand(pfmNotify, lastWait)
- def _generate_command(self, exporter, so, responder, commandMgr, msgNode, waitOn=-1):
+ def _generate_command(
+ self, exporter, so, responder, commandMgr, msgNode, waitOn=-1
+ ):
def prepare_message(exporter, so, responder, commandMgr, waitOn, msg):
idx, command = commandMgr.add_command(msgNode, waitOn)
if msg.sender is None:
@@ -342,7 +408,9 @@ class PlasmaResponderStateNode(PlasmaNodeBase, bpy.types.Node):
commandMgr.add_waitable_node(msgNode)
if msgNode.has_linked_callbacks:
childWaitOn = commandMgr.add_wait(idx)
- msgNode.convert_callback_message(exporter, so, msg, responder.key, childWaitOn)
+ msgNode.convert_callback_message(
+ exporter, so, msg, responder.key, childWaitOn
+ )
else:
childWaitOn = waitOn
@@ -352,11 +420,14 @@ class PlasmaResponderStateNode(PlasmaNodeBase, bpy.types.Node):
def _get_child_messages(self, node=None):
"""Returns a list of the message nodes sent by `node`. The list is sorted such that any
- messages with callbacks are last in the list, allowing proper wait generation.
+ messages with callbacks are last in the list, allowing proper wait generation.
"""
if node is None:
node = self
- return sorted(node.find_outputs("msgs"), key=lambda x: x.has_callbacks and x.has_linked_callbacks)
+ return sorted(
+ node.find_outputs("msgs"),
+ key=lambda x: x.has_callbacks and x.has_linked_callbacks,
+ )
class PlasmaRespStateSocket(PlasmaNodeSocketBase, bpy.types.NodeSocket):
@@ -369,7 +440,15 @@ class PlasmaRespStateRefSocket(PlasmaNodeSocketBase, bpy.types.NodeSocket):
def draw_content(self, context, layout, node, text):
if isinstance(node, PlasmaResponderNode):
try:
- idx = next((idx for idx, socket in enumerate(node.find_output_sockets("state_refs")) if socket == self))
+ idx = next(
+ (
+ idx
+ for idx, socket in enumerate(
+ node.find_output_sockets("state_refs")
+ )
+ if socket == self
+ )
+ )
except StopIteration:
layout.label(text)
else:
diff --git a/korman/nodes/node_softvolume.py b/korman/nodes/node_softvolume.py
index 06b8144..decca86 100644
--- a/korman/nodes/node_softvolume.py
+++ b/korman/nodes/node_softvolume.py
@@ -21,17 +21,23 @@ from PyHSPlasma import *
from .node_core import PlasmaNodeBase, PlasmaNodeSocketBase, PlasmaTreeOutputNodeBase
from .. import idprops
+
class PlasmaSoftVolumeOutputNode(PlasmaTreeOutputNodeBase, bpy.types.Node):
bl_category = "SV"
bl_idname = "PlasmaSoftVolumeOutputNode"
bl_label = "Soft Volume Output"
- input_sockets = OrderedDict([
- ("input", {
- "text": "Final Volume",
- "type": "PlasmaSoftVolumeNodeSocket",
- }),
- ])
+ input_sockets = OrderedDict(
+ [
+ (
+ "input",
+ {
+ "text": "Final Volume",
+ "type": "PlasmaSoftVolumeNodeSocket",
+ },
+ ),
+ ]
+ )
def get_key(self, exporter, so):
svNode = self.find_input("input")
@@ -42,6 +48,8 @@ class PlasmaSoftVolumeOutputNode(PlasmaTreeOutputNodeBase, bpy.types.Node):
class PlasmaSoftVolumeNodeSocket(PlasmaNodeSocketBase, bpy.types.NodeSocket):
bl_color = (0.133, 0.094, 0.345, 1.0)
+
+
class PlasmaSoftVolumePropertiesNodeSocket(PlasmaNodeSocketBase, bpy.types.NodeSocket):
bl_color = (0.067, 0.40, 0.067, 1.0)
@@ -51,17 +59,31 @@ class PlasmaSoftVolumePropertiesNode(PlasmaNodeBase, bpy.types.Node):
bl_idname = "PlasmaSoftVolumePropertiesNode"
bl_label = "Soft Volume Properties"
- output_sockets = OrderedDict([
- ("target", {
- "text": "Volume",
- "type": "PlasmaSoftVolumePropertiesNodeSocket"
- }),
- ])
-
- inside_strength = IntProperty(name="Inside", description="Strength inside the region",
- subtype="PERCENTAGE", default=100, min=0, max=100)
- outside_strength = IntProperty(name="Outside", description="Strength outside the region",
- subtype="PERCENTAGE", default=0, min=0, max=100)
+ output_sockets = OrderedDict(
+ [
+ (
+ "target",
+ {"text": "Volume", "type": "PlasmaSoftVolumePropertiesNodeSocket"},
+ ),
+ ]
+ )
+
+ inside_strength = IntProperty(
+ name="Inside",
+ description="Strength inside the region",
+ subtype="PERCENTAGE",
+ default=100,
+ min=0,
+ max=100,
+ )
+ outside_strength = IntProperty(
+ name="Outside",
+ description="Strength outside the region",
+ subtype="PERCENTAGE",
+ default=0,
+ min=0,
+ max=100,
+ )
def draw_buttons(self, context, layout):
layout.prop(self, "inside_strength")
@@ -72,23 +94,26 @@ class PlasmaSoftVolumePropertiesNode(PlasmaNodeBase, bpy.types.Node):
softvolume.outsideStrength = self.outside_strength / 100
-class PlasmaSoftVolumeReferenceNode(idprops.IDPropObjectMixin, PlasmaNodeBase, bpy.types.Node):
+class PlasmaSoftVolumeReferenceNode(
+ idprops.IDPropObjectMixin, PlasmaNodeBase, bpy.types.Node
+):
bl_category = "SV"
bl_idname = "PlasmaSoftVolumeReferenceNode"
bl_label = "Soft Region"
bl_width_default = 150
- output_sockets = OrderedDict([
- ("output", {
- "text": "Volume",
- "type": "PlasmaSoftVolumeNodeSocket"
- }),
- ])
+ output_sockets = OrderedDict(
+ [
+ ("output", {"text": "Volume", "type": "PlasmaSoftVolumeNodeSocket"}),
+ ]
+ )
- soft_volume = PointerProperty(name="Soft Volume",
- description="Object whose Soft Volume modifier we should use",
- type=bpy.types.Object,
- poll=idprops.poll_softvolume_objects)
+ soft_volume = PointerProperty(
+ name="Soft Volume",
+ description="Object whose Soft Volume modifier we should use",
+ type=bpy.types.Object,
+ poll=idprops.poll_softvolume_objects,
+ )
def draw_buttons(self, context, layout):
layout.prop(self, "soft_volume", text="")
@@ -111,23 +136,30 @@ class PlasmaSoftVolumeInvertNode(PlasmaNodeBase, bpy.types.Node):
bl_label = "Soft Volume Invert"
# The only difference between this and PlasmaSoftVolumeLinkNode is this can only have ONE input
- input_sockets = OrderedDict([
- ("properties", {
- "text": "Properties",
- "type": "PlasmaSoftVolumePropertiesNodeSocket",
- }),
- ("input", {
- "text": "Input Volume",
- "type": "PlasmaSoftVolumeNodeSocket",
- }),
- ])
-
- output_sockets = OrderedDict([
- ("output", {
- "text": "Output Volume",
- "type": "PlasmaSoftVolumeNodeSocket"
- }),
- ])
+ input_sockets = OrderedDict(
+ [
+ (
+ "properties",
+ {
+ "text": "Properties",
+ "type": "PlasmaSoftVolumePropertiesNodeSocket",
+ },
+ ),
+ (
+ "input",
+ {
+ "text": "Input Volume",
+ "type": "PlasmaSoftVolumeNodeSocket",
+ },
+ ),
+ ]
+ )
+
+ output_sockets = OrderedDict(
+ [
+ ("output", {"text": "Output Volume", "type": "PlasmaSoftVolumeNodeSocket"}),
+ ]
+ )
def get_key(self, exporter, so):
return self._find_create_key(plSoftVolumeInvert, exporter, so=so)
@@ -153,24 +185,31 @@ class PlasmaSoftVolumeInvertNode(PlasmaNodeBase, bpy.types.Node):
class PlasmaSoftVolumeLinkNode(PlasmaNodeBase):
- input_sockets = OrderedDict([
- ("properties", {
- "text": "Properties",
- "type": "PlasmaSoftVolumePropertiesNodeSocket",
- }),
- ("input", {
- "text": "Input Volume",
- "type": "PlasmaSoftVolumeNodeSocket",
- "spawn_empty": True,
- }),
- ])
-
- output_sockets = OrderedDict([
- ("output", {
- "text": "Output Volume",
- "type": "PlasmaSoftVolumeNodeSocket"
- }),
- ])
+ input_sockets = OrderedDict(
+ [
+ (
+ "properties",
+ {
+ "text": "Properties",
+ "type": "PlasmaSoftVolumePropertiesNodeSocket",
+ },
+ ),
+ (
+ "input",
+ {
+ "text": "Input Volume",
+ "type": "PlasmaSoftVolumeNodeSocket",
+ "spawn_empty": True,
+ },
+ ),
+ ]
+ )
+
+ output_sockets = OrderedDict(
+ [
+ ("output", {"text": "Output Volume", "type": "PlasmaSoftVolumeNodeSocket"}),
+ ]
+ )
def export(self, exporter, bo, so):
sv = self.get_key(exporter, so).object
diff --git a/korman/operators/__init__.py b/korman/operators/__init__.py
index 7ff172a..9314448 100644
--- a/korman/operators/__init__.py
+++ b/korman/operators/__init__.py
@@ -24,8 +24,10 @@ from . import op_toolbox as toolbox
from . import op_ui as ui
from . import op_world as world
+
def register():
exporter.register()
+
def unregister():
exporter.unregister()
diff --git a/korman/operators/op_export.py b/korman/operators/op_export.py
index 5f41e18..8473058 100644
--- a/korman/operators/op_export.py
+++ b/korman/operators/op_export.py
@@ -27,6 +27,7 @@ from ..helpers import UiHelper
from .. import korlib, plasma_launcher
from ..properties.prop_world import PlasmaAge
+
class ExportOperator:
def _get_default_path(self, context):
blend_filepath = context.blend_data.filepath
@@ -55,94 +56,212 @@ class PlasmaAgeExportOperator(ExportOperator, bpy.types.Operator):
# over on the PlasmaAge world properties. We've got a helper so we can access them like they're actually on us...
# If you want a volatile property, register it directly on this operator!
_properties = {
- "verbose": (BoolProperty, {"name": "Display Verbose Log",
- "description": "Shows the verbose export log in the console",
- "default": False}),
-
- "show_console": (BoolProperty, {"name": "Display Log Console",
- "description": "Forces the Blender System Console open during the export",
- "default": True}),
-
- "texcache_path": (StringProperty, {"name": "Texture Cache Path",
- "description": "Texture Cache Filepath"}),
-
- "texcache_method": (EnumProperty, {"name": "Texture Cache",
- "description": "Texture Cache Settings",
- "items": [("skip", "Don't Use Texture Cache", "The texture cache is neither used nor updated."),
- ("use", "Use Texture Cache", "Use (and update, if needed) cached textures."),
- ("rebuild", "Rebuild Texture Cache", "Rebuilds the texture cache from scratch.")],
- "default": "use"}),
-
- "lighting_method": (EnumProperty, {"name": "Static Lighting",
- "description": "Static Lighting Settings",
- "items": [("skip", "Don't Bake Lighting", "Static lighting is not baked during this export (fastest export)"),
- ("bake", "Bake Lighting", "Static lighting is baked according to your specifications"),
- ("force_vcol", "Force Vertex Color Bake", "All static lighting is baked as vertex colors (faster export)"),
- ("force_lightmap", "Force Lightmap Bake", "All static lighting is baked as lightmaps (slower export)")],
- "default": "bake"}),
-
- "envmap_method": (EnumProperty, {"name": "Environment Maps",
- "description": "Environment Map Settings",
- "items": [("skip", "Don't Export EnvMaps", "Environment Maps are not exported"),
- ("dcm2dem", "Downgrade Planar EnvMaps", "When the engine doesn't support them, Planar Environment Maps are downgraded to Cube Maps"),
- ("perengine", "Export Supported EnvMaps", "Only environment maps supported by the selected game engine are exported")],
- "default": "dcm2dem"}),
-
- "python_method": (EnumProperty, {"name": "Python",
- "description": "Specifies how Python should be packed",
- "items": [("none", "Pack Nothing", "Don't pack any Python files."),
- ("as_requested", "Pack Requested Scripts", "Packs any script both linked as a Text file and requested for packaging."),
- ("all", "Pack All Scripts", "Packs all Python files linked as a Text file.")],
- "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"}}),
+ "verbose": (
+ BoolProperty,
+ {
+ "name": "Display Verbose Log",
+ "description": "Shows the verbose export log in the console",
+ "default": False,
+ },
+ ),
+ "show_console": (
+ BoolProperty,
+ {
+ "name": "Display Log Console",
+ "description": "Forces the Blender System Console open during the export",
+ "default": True,
+ },
+ ),
+ "texcache_path": (
+ StringProperty,
+ {"name": "Texture Cache Path", "description": "Texture Cache Filepath"},
+ ),
+ "texcache_method": (
+ EnumProperty,
+ {
+ "name": "Texture Cache",
+ "description": "Texture Cache Settings",
+ "items": [
+ (
+ "skip",
+ "Don't Use Texture Cache",
+ "The texture cache is neither used nor updated.",
+ ),
+ (
+ "use",
+ "Use Texture Cache",
+ "Use (and update, if needed) cached textures.",
+ ),
+ (
+ "rebuild",
+ "Rebuild Texture Cache",
+ "Rebuilds the texture cache from scratch.",
+ ),
+ ],
+ "default": "use",
+ },
+ ),
+ "lighting_method": (
+ EnumProperty,
+ {
+ "name": "Static Lighting",
+ "description": "Static Lighting Settings",
+ "items": [
+ (
+ "skip",
+ "Don't Bake Lighting",
+ "Static lighting is not baked during this export (fastest export)",
+ ),
+ (
+ "bake",
+ "Bake Lighting",
+ "Static lighting is baked according to your specifications",
+ ),
+ (
+ "force_vcol",
+ "Force Vertex Color Bake",
+ "All static lighting is baked as vertex colors (faster export)",
+ ),
+ (
+ "force_lightmap",
+ "Force Lightmap Bake",
+ "All static lighting is baked as lightmaps (slower export)",
+ ),
+ ],
+ "default": "bake",
+ },
+ ),
+ "envmap_method": (
+ EnumProperty,
+ {
+ "name": "Environment Maps",
+ "description": "Environment Map Settings",
+ "items": [
+ (
+ "skip",
+ "Don't Export EnvMaps",
+ "Environment Maps are not exported",
+ ),
+ (
+ "dcm2dem",
+ "Downgrade Planar EnvMaps",
+ "When the engine doesn't support them, Planar Environment Maps are downgraded to Cube Maps",
+ ),
+ (
+ "perengine",
+ "Export Supported EnvMaps",
+ "Only environment maps supported by the selected game engine are exported",
+ ),
+ ],
+ "default": "dcm2dem",
+ },
+ ),
+ "python_method": (
+ EnumProperty,
+ {
+ "name": "Python",
+ "description": "Specifies how Python should be packed",
+ "items": [
+ ("none", "Pack Nothing", "Don't pack any Python files."),
+ (
+ "as_requested",
+ "Pack Requested Scripts",
+ "Packs any script both linked as a Text file and requested for packaging.",
+ ),
+ (
+ "all",
+ "Pack All Scripts",
+ "Packs all Python files linked as a Text file.",
+ ),
+ ],
+ "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"},
+ },
+ ),
}
# This wigs out and very bad things happen if it's not directly on the operator...
filepath = StringProperty(subtype="FILE_PATH")
- filter_glob = StringProperty(default="*.age;*.zip", options={'HIDDEN'})
-
- version = EnumProperty(name="Version",
- description="Plasma version to export this age for",
- items=game_versions,
- default="pvPots",
- options=set())
-
- dat_only = BoolProperty(name="Export Only PRPs",
- description="Only the Age PRPs should be exported",
- default=True,
- options={"HIDDEN"})
-
- actions = EnumProperty(name="Actions",
- description="Actions for the exporter to perform",
- default={"EXPORT"},
- items=[("EXPORT", "Export", "Export the age data"),
- ("PROFILE", "Profile", "Profile the exporter"),
- ("LAUNCH", "Launch Age", "Launch the age in Plasma")],
- options={"ENUM_FLAG"})
-
- ki = IntProperty(name="KI",
- description="KI Number of the player to use when launching the game",
- options=set())
-
- player = StringProperty(name="Player",
- description="Name of the player to use when launching the game",
- options=set())
-
- serverini = StringProperty(name="Server INI",
- description="Name of the server configuation to use when launching the game",
- options=set())
+ filter_glob = StringProperty(default="*.age;*.zip", options={"HIDDEN"})
+
+ version = EnumProperty(
+ name="Version",
+ description="Plasma version to export this age for",
+ items=game_versions,
+ default="pvPots",
+ options=set(),
+ )
+
+ dat_only = BoolProperty(
+ name="Export Only PRPs",
+ description="Only the Age PRPs should be exported",
+ default=True,
+ options={"HIDDEN"},
+ )
+
+ actions = EnumProperty(
+ name="Actions",
+ description="Actions for the exporter to perform",
+ default={"EXPORT"},
+ items=[
+ ("EXPORT", "Export", "Export the age data"),
+ ("PROFILE", "Profile", "Profile the exporter"),
+ ("LAUNCH", "Launch Age", "Launch the age in Plasma"),
+ ],
+ options={"ENUM_FLAG"},
+ )
+
+ ki = IntProperty(
+ name="KI",
+ description="KI Number of the player to use when launching the game",
+ options=set(),
+ )
+
+ player = StringProperty(
+ name="Player",
+ description="Name of the player to use when launching the game",
+ options=set(),
+ )
+
+ serverini = StringProperty(
+ name="Server INI",
+ description="Name of the server configuation to use when launching the game",
+ options=set(),
+ )
def draw(self, context):
layout = self.layout
@@ -204,7 +323,10 @@ class PlasmaAgeExportOperator(ExportOperator, bpy.types.Operator):
ageName = path.stem
if korlib.is_python_keyword(ageName):
- self.report({"ERROR"}, "The Age name conflicts with the Python keyword '{}'".format(ageName))
+ self.report(
+ {"ERROR"},
+ "The Age name conflicts with the Python keyword '{}'".format(ageName),
+ )
return {"CANCELLED"}
# This prevents us from finding out at the very end that very, very bad things happened...
@@ -222,8 +344,12 @@ class PlasmaAgeExportOperator(ExportOperator, bpy.types.Operator):
try:
self.export_active = True
if "PROFILE" in self.actions:
- profile_path = str(path.with_name("{}_cProfile".format(ageName)))
- profile = cProfile.runctx("e.run()", globals(), locals(), profile_path)
+ profile_path = str(
+ path.with_name("{}_cProfile".format(ageName))
+ )
+ profile = cProfile.runctx(
+ "e.run()", globals(), locals(), profile_path
+ )
else:
e.run()
except exporter.ExportError as error:
@@ -274,13 +400,19 @@ class PlasmaAgeExportOperator(ExportOperator, bpy.types.Operator):
def _sanity_check_run_plasma(self):
if not bpy.app.binary_path_python:
- raise exporter.PlasmaLaunchError("Can't Launch Plasma: No Python executable available")
+ raise exporter.PlasmaLaunchError(
+ "Can't Launch Plasma: No Python executable available"
+ )
if self.version == "pvMoul":
if not self.ki:
- raise exporter.PlasmaLaunchError("Can't Launch Plasma: Player KI not set")
+ raise exporter.PlasmaLaunchError(
+ "Can't Launch Plasma: Player KI not set"
+ )
else:
if not self.player:
- raise exporter.PlasmaLaunchError("Can't Launch Plasma: Player Name not set")
+ raise exporter.PlasmaLaunchError(
+ "Can't Launch Plasma: Player Name not set"
+ )
def _run_plasma(self, context):
path = Path(self.filepath)
@@ -289,8 +421,13 @@ class PlasmaAgeExportOperator(ExportOperator, bpy.types.Operator):
# It would be nice to launch URU right here. Unfortunately, for single player URUs, we will
# need to actually wait for the whole rigamaroll to finish. Therefore, we need to kick
# open a separate python exe to launch URU and wait.
- args = [bpy.app.binary_path_python, plasma_launcher.__file__,
- str(client_dir), path.stem, self.version]
+ args = [
+ bpy.app.binary_path_python,
+ plasma_launcher.__file__,
+ str(client_dir),
+ path.stem,
+ self.version,
+ ]
if self.version == "pvMoul":
if self.serverini:
args.append("--serverini")
@@ -300,8 +437,13 @@ class PlasmaAgeExportOperator(ExportOperator, bpy.types.Operator):
args.append(self.player)
with exporter.ExportVerboseLogger() as log:
- proc = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
- cwd=str(client_dir), universal_newlines=True)
+ proc = subprocess.Popen(
+ args,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ cwd=str(client_dir),
+ universal_newlines=True,
+ )
while True:
line = proc.stdout.readline().strip()
if line == "DIE":
@@ -320,13 +462,15 @@ class PlasmaLocalizationExportOperator(ExportOperator, bpy.types.Operator):
bl_description = "Export Age localization data"
filepath = StringProperty(subtype="DIR_PATH")
- filter_glob = StringProperty(default="*.pak", options={'HIDDEN'})
+ filter_glob = StringProperty(default="*.pak", options={"HIDDEN"})
- version = EnumProperty(name="Version",
- description="Plasma version to export this age for",
- items=game_versions,
- default="pvPots",
- options=set())
+ version = EnumProperty(
+ name="Version",
+ description="Plasma version to export this age for",
+ items=game_versions,
+ default="pvPots",
+ options=set(),
+ )
def execute(self, context):
path = Path(self.filepath)
@@ -344,12 +488,16 @@ class PlasmaLocalizationExportOperator(ExportOperator, bpy.types.Operator):
# Age names cannot be python keywords
age_name = context.scene.world.plasma_age.age_name
if korlib.is_python_keyword(age_name):
- self.report({"ERROR"}, "The Age name conflicts with the Python keyword '{}'".format(age_name))
+ self.report(
+ {"ERROR"},
+ "The Age name conflicts with the Python keyword '{}'".format(age_name),
+ )
return {"CANCELLED"}
# Bonus Fun: Implement Profile-mode here (later...)
- e = exporter.LocalizationConverter(age_name=age_name, path=self.filepath,
- version=globals()[self.version])
+ e = exporter.LocalizationConverter(
+ age_name=age_name, path=self.filepath, version=globals()[self.version]
+ )
try:
e.run()
except exporter.ExportError as error:
@@ -368,13 +516,15 @@ class PlasmaPythonExportOperator(ExportOperator, bpy.types.Operator):
bl_description = "Export Age python script package"
filepath = StringProperty(subtype="FILE_PATH")
- filter_glob = StringProperty(default="*.pak", options={'HIDDEN'})
+ filter_glob = StringProperty(default="*.pak", options={"HIDDEN"})
- version = EnumProperty(name="Version",
- description="Plasma version to export this age for",
- items=game_versions,
- default="pvPots",
- options=set())
+ version = EnumProperty(
+ name="Version",
+ description="Plasma version to export this age for",
+ items=game_versions,
+ default="pvPots",
+ options=set(),
+ )
def draw(self, context):
layout = self.layout
@@ -388,7 +538,7 @@ class PlasmaPythonExportOperator(ExportOperator, bpy.types.Operator):
row = layout.row()
row.enabled = korlib.ConsoleToggler.is_platform_supported()
row.prop(age, "show_console")
- layout.prop(age, "verbose")
+ layout.prop(age, "verbose")
def execute(self, context):
path = Path(self.filepath)
@@ -407,12 +557,16 @@ class PlasmaPythonExportOperator(ExportOperator, bpy.types.Operator):
# Age names cannot be python keywords
age_name = context.scene.world.plasma_age.age_name
if korlib.is_python_keyword(age_name):
- self.report({"ERROR"}, "The Age name conflicts with the Python keyword '{}'".format(age_name))
+ self.report(
+ {"ERROR"},
+ "The Age name conflicts with the Python keyword '{}'".format(age_name),
+ )
return {"CANCELLED"}
# Bonus Fun: Implement Profile-mode here (later...)
- e = exporter.PythonPackageExporter(filepath=self.filepath,
- version=globals()[self.version])
+ e = exporter.PythonPackageExporter(
+ filepath=self.filepath, version=globals()[self.version]
+ )
try:
e.run()
except exporter.ExportError as error:
@@ -439,12 +593,17 @@ class PlasmaPythonExportOperator(ExportOperator, bpy.types.Operator):
def menu_cb(self, context):
if context.scene.render.engine == "PLASMA_GAME":
self.layout.operator_context = "INVOKE_DEFAULT"
- self.layout.operator(PlasmaAgeExportOperator.bl_idname, text="Plasma Age (.age)")
- self.layout.operator(PlasmaPythonExportOperator.bl_idname, text="Plasma Scripts (.pak)")
+ self.layout.operator(
+ PlasmaAgeExportOperator.bl_idname, text="Plasma Age (.age)"
+ )
+ self.layout.operator(
+ PlasmaPythonExportOperator.bl_idname, text="Plasma Scripts (.pak)"
+ )
def register():
bpy.types.INFO_MT_file_export.append(menu_cb)
+
def unregister():
bpy.types.INFO_MT_file_export.remove(menu_cb)
diff --git a/korman/operators/op_image.py b/korman/operators/op_image.py
index d7b39de..3cc22f6 100644
--- a/korman/operators/op_image.py
+++ b/korman/operators/op_image.py
@@ -33,6 +33,7 @@ _CUBE_FACES = {
"frontFace": "FR",
}
+
class ImageOperator:
@classmethod
def poll(cls, context):
@@ -44,19 +45,25 @@ class PlasmaBuildCubeMapOperator(ImageOperator, bpy.types.Operator):
bl_label = "Build Cubemap"
bl_description = "Builds a Blender cubemap from six images"
- overwrite_existing = BoolProperty(name="Check Existing",
- description="Checks for an existing image and overwrites it",
- default=True,
- options=set())
+ overwrite_existing = BoolProperty(
+ name="Check Existing",
+ description="Checks for an existing image and overwrites it",
+ default=True,
+ options=set(),
+ )
filepath = StringProperty(subtype="FILE_PATH")
- require_cube = BoolProperty(name="Require Square Faces",
- description="Resize cubemap faces to be square if they are not",
- default=True,
- options=set())
- texture_name = StringProperty(name="Texture",
- description="Environment Map Texture to stuff this into",
- default="",
- options={"HIDDEN"})
+ require_cube = BoolProperty(
+ name="Require Square Faces",
+ description="Resize cubemap faces to be square if they are not",
+ default=True,
+ options=set(),
+ )
+ texture_name = StringProperty(
+ name="Texture",
+ description="Environment Map Texture to stuff this into",
+ default="",
+ options={"HIDDEN"},
+ )
def __init__(self):
self._report = ExportProgressLogger()
@@ -91,15 +98,17 @@ class PlasmaBuildCubeMapOperator(ImageOperator, bpy.types.Operator):
face_widths, face_heights, face_data = zip(*face_data)
# All widths and heights must be the same... so, if needed, scale the stupid images.
- width, height, face_data = self._scale_images(face_widths, face_heights, face_data)
+ width, height, face_data = self._scale_images(
+ face_widths, face_heights, face_data
+ )
# Now generate the stoopid cube map
image_name = Path(self.filepath).name
- idx = image_name.rfind('_')
+ idx = image_name.rfind("_")
if idx != -1:
- suffix = image_name[idx+1:idx+3]
+ suffix = image_name[idx + 1 : idx + 3]
if suffix in _CUBE_FACES.values():
- image_name = image_name[:idx] + image_name[idx+3:]
+ image_name = image_name[:idx] + image_name[idx + 3 :]
cubemap_image = self._generate_cube_map(image_name, width, height, face_data)
# If a texture was provided, we can assign this generated cube map to it...
@@ -116,18 +125,22 @@ class PlasmaBuildCubeMapOperator(ImageOperator, bpy.types.Operator):
self._report.progress_range = len(BLENDER_CUBE_MAP)
self._report.msg("Searching for cubemap faces...")
- idx = filepath.rfind('_')
+ idx = filepath.rfind("_")
if idx != -1:
files = []
for key in BLENDER_CUBE_MAP:
suffix = _CUBE_FACES[key]
- face_path = filepath[:idx+1] + suffix + filepath[idx+3:]
+ face_path = filepath[: idx + 1] + suffix + filepath[idx + 3 :]
face_name = key[:-4].upper()
if Path(face_path).is_file():
- self._report.msg("Found face '{}': {}", face_name, face_path, indent=1)
+ self._report.msg(
+ "Found face '{}': {}", face_name, face_path, indent=1
+ )
files.append(face_path)
else:
- self._report.warn("Using default face data for face '{}'", face_name, indent=1)
+ self._report.warn(
+ "Using default face data for face '{}'", face_name, indent=1
+ )
files.append(None)
self._report.progress_increment()
return tuple(files)
@@ -138,7 +151,9 @@ class PlasmaBuildCubeMapOperator(ImageOperator, bpy.types.Operator):
self._report.msg("Generating cubemap image...")
# If a texture was provided, we should check to see if we have an image we can replace...
- image = bpy.data.textures[self.texture_name].image if self.texture_name else None
+ image = (
+ bpy.data.textures[self.texture_name].image if self.texture_name else None
+ )
# Init our image
image_width = face_width * 3
@@ -167,9 +182,13 @@ class PlasmaBuildCubeMapOperator(ImageOperator, bpy.types.Operator):
for row_current in range(row_start, row_end, 1):
src_start_idx = (row_current - row_start) * face_width * 4
src_end_idx = src_start_idx + (face_width * 4)
- dst_start_idx = (row_current * image_width * 4) + (col_id * face_width * 4)
+ dst_start_idx = (row_current * image_width * 4) + (
+ col_id * face_width * 4
+ )
dst_end_idx = dst_start_idx + (face_width * 4)
- image_data[dst_start_idx:dst_end_idx] = face_data[j][src_start_idx:src_end_idx]
+ image_data[dst_start_idx:dst_end_idx] = face_data[j][
+ src_start_idx:src_end_idx
+ ]
# FFFUUUUU... Blender wants a list of floats
pixels = [None] * image_datasz
@@ -183,7 +202,6 @@ class PlasmaBuildCubeMapOperator(ImageOperator, bpy.types.Operator):
image.plasma_image.texcache_method = "rebuild"
return image
-
def invoke(self, context, event):
context.window_manager.fileselect_add(self)
return {"RUNNING_MODAL"}
@@ -230,10 +248,17 @@ class PlasmaBuildCubeMapOperator(ImageOperator, bpy.types.Operator):
face_width, face_height = face_widths[i], face_heights[i]
if face_width != min_width or face_height != min_height:
face_name = BLENDER_CUBE_MAP[i][:-4].upper()
- self._report.msg("Resizing face '{}' from {}x{} to {}x{}", face_name,
- face_width, face_height, min_width, min_height,
- indent=1)
- result_data[i] = scale_image(face_data[i], face_width, face_height,
- min_width, min_height)
+ self._report.msg(
+ "Resizing face '{}' from {}x{} to {}x{}",
+ face_name,
+ face_width,
+ face_height,
+ min_width,
+ min_height,
+ indent=1,
+ )
+ result_data[i] = scale_image(
+ face_data[i], face_width, face_height, min_width, min_height
+ )
self._report.progress_increment()
return min_width, min_height, tuple(result_data)
diff --git a/korman/operators/op_lightmap.py b/korman/operators/op_lightmap.py
index 7d98f91..9eb5d85 100644
--- a/korman/operators/op_lightmap.py
+++ b/korman/operators/op_lightmap.py
@@ -25,6 +25,7 @@ from ..exporter.explosions import ExportError
from ..helpers import UiHelper
from ..korlib import ConsoleToggler
+
class _LightingOperator:
@contextmanager
def _oven(self, context):
@@ -34,7 +35,9 @@ class _LightingOperator:
else:
verbose = False
console = True
- with UiHelper(context), ConsoleToggler(console), LightBaker(verbose=verbose) as oven:
+ with UiHelper(context), ConsoleToggler(console), LightBaker(
+ verbose=verbose
+ ) as oven:
yield oven
@classmethod
@@ -63,7 +66,11 @@ class LightmapAutobakePreviewOperator(_LightingOperator, bpy.types.Operator):
bake.lightmap_uvtex_name = "LIGHTMAPGEN_PREVIEW"
bake.force = True
bake.retain_lightmap_uvtex = self.final
- if not bake.bake_static_lighting([context.object,]):
+ if not bake.bake_static_lighting(
+ [
+ context.object,
+ ]
+ ):
self.report({"WARNING"}, "No valid lights found to bake.")
return {"FINISHED"}
@@ -89,9 +96,11 @@ class LightmapBakeMultiOperator(_LightingOperator, bpy.types.Operator):
bl_label = "Bake Lighting"
bl_description = "Bake scene lighting to object(s)"
- bake_selection = BoolProperty(name="Bake Selection",
- description="Bake only the selected objects (else all objects)",
- options=set())
+ bake_selection = BoolProperty(
+ name="Bake Selection",
+ description="Bake only the selected objects (else all objects)",
+ options=set(),
+ )
def __init__(self):
super().__init__()
@@ -101,7 +110,9 @@ class LightmapBakeMultiOperator(_LightingOperator, bpy.types.Operator):
try:
if profile_me:
- cProfile.runctx("self._run(context)", globals(), locals(), "bake_cProfile")
+ cProfile.runctx(
+ "self._run(context)", globals(), locals(), "bake_cProfile"
+ )
else:
self._run(context)
except ExportError as error:
@@ -116,8 +127,12 @@ class LightmapBakeMultiOperator(_LightingOperator, bpy.types.Operator):
return {"FINISHED"}
def _run(self, context):
- all_objects = context.selected_objects if self.bake_selection else context.scene.objects
- filtered_objects = [i for i in all_objects if i.type == "MESH" and i.plasma_object.enabled]
+ all_objects = (
+ context.selected_objects if self.bake_selection else context.scene.objects
+ )
+ filtered_objects = [
+ i for i in all_objects if i.type == "MESH" and i.plasma_object.enabled
+ ]
with self._oven(context) as bake:
bake.force = True
@@ -136,21 +151,32 @@ class LightmapClearMultiOperator(_LightingOperator, bpy.types.Operator):
bl_label = "Clear Lighting"
bl_description = "Clear baked lighting"
- clear_selection = BoolProperty(name="Clear Selection",
- description="Clear only the selected objects (else all objects)",
- options=set())
+ clear_selection = BoolProperty(
+ name="Clear Selection",
+ description="Clear only the selected objects (else all objects)",
+ options=set(),
+ )
def __init__(self):
super().__init__()
def _iter_lightmaps(self, objects):
- yield from filter(lambda x: x.type == "MESH" and x.plasma_modifiers.lightmap.bake_lightmap, objects)
+ yield from filter(
+ lambda x: x.type == "MESH" and x.plasma_modifiers.lightmap.bake_lightmap,
+ objects,
+ )
def _iter_vcols(self, objects):
- yield from filter(lambda x: x.type == "MESH" and not x.plasma_modifiers.lightmap.bake_lightmap, objects)
+ yield from filter(
+ lambda x: x.type == "MESH"
+ and not x.plasma_modifiers.lightmap.bake_lightmap,
+ objects,
+ )
def execute(self, context):
- all_objects = context.selected_objects if self.clear_selection else context.scene.objects
+ all_objects = (
+ context.selected_objects if self.clear_selection else context.scene.objects
+ )
for i in self._iter_lightmaps(all_objects):
i.plasma_modifiers.lightmap.image = None
@@ -179,5 +205,6 @@ def _toss_garbage(scene):
if uvtex is not None:
i.uv_textures.remove(uvtex)
+
# collects light baking garbage
bpy.app.handlers.save_pre.append(_toss_garbage)
diff --git a/korman/operators/op_mesh.py b/korman/operators/op_mesh.py
index 5a5a98e..31b7a2c 100644
--- a/korman/operators/op_mesh.py
+++ b/korman/operators/op_mesh.py
@@ -20,6 +20,7 @@ import mathutils
from ..exporter import utils
+
class PlasmaMeshOperator:
@classmethod
def poll(cls, context):
@@ -37,18 +38,25 @@ class PlasmaAddFlareOperator(PlasmaMeshOperator, bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
# Allows user to specify their own name stem
- flare_name = bpy.props.StringProperty(name="Name",
- description="Flare name stem",
- default="Flare",
- options=set())
- flare_distance = bpy.props.FloatProperty(name="Distance",
- description="Flare's distance from the illuminating object",
- min=0.1, max=2.0, step=10, precision=1, default=1.0,
- options=set())
- flare_material_name = bpy.props.StringProperty(name="Material",
- description="A specially-crafted material to use for this flare",
- default=FLARE_MATERIAL_BASE_NAME,
- options=set())
+ flare_name = bpy.props.StringProperty(
+ name="Name", description="Flare name stem", default="Flare", options=set()
+ )
+ flare_distance = bpy.props.FloatProperty(
+ name="Distance",
+ description="Flare's distance from the illuminating object",
+ min=0.1,
+ max=2.0,
+ step=10,
+ precision=1,
+ default=1.0,
+ options=set(),
+ )
+ flare_material_name = bpy.props.StringProperty(
+ name="Material",
+ description="A specially-crafted material to use for this flare",
+ default=FLARE_MATERIAL_BASE_NAME,
+ options=set(),
+ )
@classmethod
def poll(cls, context):
@@ -100,14 +108,26 @@ class PlasmaAddFlareOperator(PlasmaMeshOperator, bpy.types.Operator):
flare_root.plasma_modifiers.viewfacemod.preset_options = "Sprite"
# Create a textured Plane
- with utils.bmesh_object("{}_Visible".format(self.name_stem)) as (flare_plane, bm):
+ with utils.bmesh_object("{}_Visible".format(self.name_stem)) as (
+ flare_plane,
+ bm,
+ ):
flare_plane.hide_render = True
flare_plane.plasma_object.enabled = True
bpyscene.objects.active = flare_plane
# Make the actual plane mesh, facing away from the empty
- bmesh.ops.create_grid(bm, size=(0.5 + self.flare_distance * 0.5), matrix=mathutils.Matrix.Rotation(math.radians(180.0), 4, 'X'))
- bmesh.ops.transform(bm, matrix=mathutils.Matrix.Translation((0.0, 0.0, -self.flare_distance)), space=flare_plane.matrix_world, verts=bm.verts)
+ bmesh.ops.create_grid(
+ bm,
+ size=(0.5 + self.flare_distance * 0.5),
+ matrix=mathutils.Matrix.Rotation(math.radians(180.0), 4, "X"),
+ )
+ bmesh.ops.transform(
+ bm,
+ matrix=mathutils.Matrix.Translation((0.0, 0.0, -self.flare_distance)),
+ space=flare_plane.matrix_world,
+ verts=bm.verts,
+ )
bpy.ops.object.origin_set(type="ORIGIN_GEOMETRY")
# Give the plane a basic UV unwrap, so that it's texture-ready
@@ -141,7 +161,9 @@ class PlasmaAddFlareOperator(PlasmaMeshOperator, bpy.types.Operator):
auto_mat.use_cast_shadows = False
self.flare_material_name = auto_mat.name
- auto_tex = bpy.data.textures.new(name=FLARE_MATERIAL_BASE_NAME, type="IMAGE")
+ auto_tex = bpy.data.textures.new(
+ name=FLARE_MATERIAL_BASE_NAME, type="IMAGE"
+ )
auto_tex.use_alpha = True
auto_tex.plasma_layer.skip_depth_write = True
auto_tex.plasma_layer.skip_depth_test = True
@@ -166,60 +188,104 @@ class PlasmaAddLadderMeshOperator(PlasmaMeshOperator, bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
# Allows user to specify their own name stem
- ladder_name = bpy.props.StringProperty(name="Name",
- description="Ladder name stem",
- default="Ladder",
- options=set())
+ ladder_name = bpy.props.StringProperty(
+ name="Name", description="Ladder name stem", default="Ladder", options=set()
+ )
# Basic stats
- ladder_height = bpy.props.FloatProperty(name="Height",
- description="Height of ladder in feet",
- min=6, max=1000, step=200, precision=0, default=6,
- unit="LENGTH", subtype="DISTANCE",
- options=set())
- ladder_width = bpy.props.FloatProperty(name="Width",
- description="Width of ladder in inches",
- min=30, max=42, step=100, precision=0, default=30,
- options=set())
- rung_height = bpy.props.FloatProperty(name="Rung height",
- description="Height of rungs in inches",
- min=1, max=6, step=100, precision=0, default=6,
- options=set())
+ ladder_height = bpy.props.FloatProperty(
+ name="Height",
+ description="Height of ladder in feet",
+ min=6,
+ max=1000,
+ step=200,
+ precision=0,
+ default=6,
+ unit="LENGTH",
+ subtype="DISTANCE",
+ options=set(),
+ )
+ ladder_width = bpy.props.FloatProperty(
+ name="Width",
+ description="Width of ladder in inches",
+ min=30,
+ max=42,
+ step=100,
+ precision=0,
+ default=30,
+ options=set(),
+ )
+ rung_height = bpy.props.FloatProperty(
+ name="Rung height",
+ description="Height of rungs in inches",
+ min=1,
+ max=6,
+ step=100,
+ precision=0,
+ default=6,
+ options=set(),
+ )
# Template generation
- gen_back_guide = bpy.props.BoolProperty(name="Ladder",
- description="Generates helper object where ladder back should be placed",
- default=True,
- options=set())
- gen_ground_guides = bpy.props.BoolProperty(name="Ground",
- description="Generates helper objects where ground should be placed",
- default=True,
- options=set())
- gen_rung_guides = bpy.props.BoolProperty(name="Rungs",
- description="Generates helper objects where rungs should be placed",
- default=True,
- options=set())
- rung_width_type = bpy.props.EnumProperty(name="Rung Width",
- description="Type of rungs to generate",
- items=[("FULL", "Full Width Rungs", "The rungs cross the entire width of the ladder"),
- ("HALF", "Half Width Rungs", "The rungs only cross half the ladder's width, on the side where the avatar will contact them"),],
- default="FULL",
- options=set())
+ gen_back_guide = bpy.props.BoolProperty(
+ name="Ladder",
+ description="Generates helper object where ladder back should be placed",
+ default=True,
+ options=set(),
+ )
+ gen_ground_guides = bpy.props.BoolProperty(
+ name="Ground",
+ description="Generates helper objects where ground should be placed",
+ default=True,
+ options=set(),
+ )
+ gen_rung_guides = bpy.props.BoolProperty(
+ name="Rungs",
+ description="Generates helper objects where rungs should be placed",
+ default=True,
+ options=set(),
+ )
+ rung_width_type = bpy.props.EnumProperty(
+ name="Rung Width",
+ description="Type of rungs to generate",
+ items=[
+ (
+ "FULL",
+ "Full Width Rungs",
+ "The rungs cross the entire width of the ladder",
+ ),
+ (
+ "HALF",
+ "Half Width Rungs",
+ "The rungs only cross half the ladder's width, on the side where the avatar will contact them",
+ ),
+ ],
+ default="FULL",
+ options=set(),
+ )
# Game options
- has_upper_entry = bpy.props.BoolProperty(name="Has Upper Entry Point",
- description="Specifies whether the ladder has an upper entry",
- default=True,
- options=set())
- upper_entry_enabled = bpy.props.BoolProperty(name="Upper Entry Enabled",
- description="Specifies whether the ladder's upper entry is enabled by default at Age start",
- default=True,
- options=set())
- has_lower_entry = bpy.props.BoolProperty(name="Has Lower Entry Point",
- description="Specifies whether the ladder has a lower entry",
- default=True,
- options=set())
- lower_entry_enabled = bpy.props.BoolProperty(name="Lower Entry Enabled",
- description="Specifies whether the ladder's lower entry is enabled by default at Age start",
- default=True,
- options=set())
+ has_upper_entry = bpy.props.BoolProperty(
+ name="Has Upper Entry Point",
+ description="Specifies whether the ladder has an upper entry",
+ default=True,
+ options=set(),
+ )
+ upper_entry_enabled = bpy.props.BoolProperty(
+ name="Upper Entry Enabled",
+ description="Specifies whether the ladder's upper entry is enabled by default at Age start",
+ default=True,
+ options=set(),
+ )
+ has_lower_entry = bpy.props.BoolProperty(
+ name="Has Lower Entry Point",
+ description="Specifies whether the ladder has a lower entry",
+ default=True,
+ options=set(),
+ )
+ lower_entry_enabled = bpy.props.BoolProperty(
+ name="Lower Entry Enabled",
+ description="Specifies whether the ladder's lower entry is enabled by default at Age start",
+ default=True,
+ options=set(),
+ )
def draw(self, context):
layout = self.layout
@@ -269,7 +335,9 @@ class PlasmaAddLadderMeshOperator(PlasmaMeshOperator, bpy.types.Operator):
else:
row = layout.row()
- row.label("Warning: Operator does not work in local view mode", icon="ERROR")
+ row.label(
+ "Warning: Operator does not work in local view mode", icon="ERROR"
+ )
def execute(self, context):
if context.space_data.local_view:
@@ -292,15 +360,18 @@ class PlasmaAddLadderMeshOperator(PlasmaMeshOperator, bpy.types.Operator):
rung_yoffset = rung_width_ft / 4
rungs_scale = mathutils.Matrix(
- ((0.5, 0.0, 0.0),
- (0.0, rung_width, 0.0),
- (0.0, 0.0, rung_height_ft)))
+ ((0.5, 0.0, 0.0), (0.0, rung_width, 0.0), (0.0, 0.0, rung_height_ft))
+ )
for rung_num in range(0, int(self.ladder_height)):
side = "L" if (rung_num % 2) == 0 else "R"
- mesh = bpy.data.meshes.new("{}_Rung_{}_{}".format(self.name_stem, side, rung_num))
- rungs = bpy.data.objects.new("{}_Rung_{}_{}".format(self.name_stem, side, rung_num), mesh)
+ mesh = bpy.data.meshes.new(
+ "{}_Rung_{}_{}".format(self.name_stem, side, rung_num)
+ )
+ rungs = bpy.data.objects.new(
+ "{}_Rung_{}_{}".format(self.name_stem, side, rung_num), mesh
+ )
rungs.hide_render = True
rungs.draw_type = "BOUNDS"
@@ -314,11 +385,27 @@ class PlasmaAddLadderMeshOperator(PlasmaMeshOperator, bpy.types.Operator):
# Move each rung up, based on:
# its place in the array, aligned to the top of the rung position, shifted up to start at the ladder's base
if (rung_num % 2) == 0:
- rung_pos = mathutils.Matrix.Translation((0.5, -rung_yoffset, rung_num + (1.0 - rung_height_ft) + (rung_height_ft / 2)))
+ rung_pos = mathutils.Matrix.Translation(
+ (
+ 0.5,
+ -rung_yoffset,
+ rung_num + (1.0 - rung_height_ft) + (rung_height_ft / 2),
+ )
+ )
else:
- rung_pos = mathutils.Matrix.Translation((0.5, rung_yoffset, rung_num + (1.0 - rung_height_ft) + (rung_height_ft / 2)))
- bmesh.ops.transform(bm, matrix=cursor_shift, space=rungs.matrix_world, verts=bm.verts)
- bmesh.ops.transform(bm, matrix=rung_pos, space=rungs.matrix_world, verts=bm.verts)
+ rung_pos = mathutils.Matrix.Translation(
+ (
+ 0.5,
+ rung_yoffset,
+ rung_num + (1.0 - rung_height_ft) + (rung_height_ft / 2),
+ )
+ )
+ bmesh.ops.transform(
+ bm, matrix=cursor_shift, space=rungs.matrix_world, verts=bm.verts
+ )
+ bmesh.ops.transform(
+ bm, matrix=rung_pos, space=rungs.matrix_world, verts=bm.verts
+ )
bm.to_mesh(mesh)
bm.free()
@@ -341,15 +428,22 @@ class PlasmaAddLadderMeshOperator(PlasmaMeshOperator, bpy.types.Operator):
# Construct the bmesh and assign it to the blender mesh.
bm = bmesh.new()
ladder_scale = mathutils.Matrix(
- ((0.5, 0.0, 0.0),
- (0.0, self.ladder_width / 12, 0.0),
- (0.0, 0.0, self.ladder_height)))
+ (
+ (0.5, 0.0, 0.0),
+ (0.0, self.ladder_width / 12, 0.0),
+ (0.0, 0.0, self.ladder_height),
+ )
+ )
bmesh.ops.create_cube(bm, size=(1.0), matrix=ladder_scale)
# Shift the ladder up so that its base is at the 3D cursor
back_pos = mathutils.Matrix.Translation((0.0, 0.0, self.ladder_height / 2))
- bmesh.ops.transform(bm, matrix=cursor_shift, space=back.matrix_world, verts=bm.verts)
- bmesh.ops.transform(bm, matrix=back_pos, space=back.matrix_world, verts=bm.verts)
+ bmesh.ops.transform(
+ bm, matrix=cursor_shift, space=back.matrix_world, verts=bm.verts
+ )
+ bmesh.ops.transform(
+ bm, matrix=back_pos, space=back.matrix_world, verts=bm.verts
+ )
bm.to_mesh(mesh)
bm.free()
@@ -374,17 +468,28 @@ class PlasmaAddLadderMeshOperator(PlasmaMeshOperator, bpy.types.Operator):
bm = bmesh.new()
ground_depth = 3.0
ground_scale = mathutils.Matrix(
- ((ground_depth, 0.0, 0.0),
- (0.0, self.ladder_width / 12, 0.0),
- (0.0, 0.0, 0.5)))
+ (
+ (ground_depth, 0.0, 0.0),
+ (0.0, self.ladder_width / 12, 0.0),
+ (0.0, 0.0, 0.5),
+ )
+ )
bmesh.ops.create_cube(bm, size=(1.0), matrix=ground_scale)
if pos == "Upper":
- ground_pos = mathutils.Matrix.Translation((-(ground_depth / 2) + 0.25, 0.0, self.ladder_height + 0.25))
+ ground_pos = mathutils.Matrix.Translation(
+ (-(ground_depth / 2) + 0.25, 0.0, self.ladder_height + 0.25)
+ )
else:
- ground_pos = mathutils.Matrix.Translation(((ground_depth / 2) + 0.25, 0.0, 0.25))
- bmesh.ops.transform(bm, matrix=cursor_shift, space=ground.matrix_world, verts=bm.verts)
- bmesh.ops.transform(bm, matrix=ground_pos, space=ground.matrix_world, verts=bm.verts)
+ ground_pos = mathutils.Matrix.Translation(
+ ((ground_depth / 2) + 0.25, 0.0, 0.25)
+ )
+ bmesh.ops.transform(
+ bm, matrix=cursor_shift, space=ground.matrix_world, verts=bm.verts
+ )
+ bmesh.ops.transform(
+ bm, matrix=ground_pos, space=ground.matrix_world, verts=bm.verts
+ )
bm.to_mesh(mesh)
bm.free()
@@ -408,14 +513,17 @@ class PlasmaAddLadderMeshOperator(PlasmaMeshOperator, bpy.types.Operator):
# Construct the bmesh and assign it to the blender mesh.
bm = bmesh.new()
rgn_scale = mathutils.Matrix(
- ((self.ladder_width / 12, 0.0, 0.0),
- (0.0, 2.5, 0.0),
- (0.0, 0.0, 2.0)))
+ ((self.ladder_width / 12, 0.0, 0.0), (0.0, 2.5, 0.0), (0.0, 0.0, 2.0))
+ )
bmesh.ops.create_cube(bm, size=(1.0), matrix=rgn_scale)
rgn_pos = mathutils.Matrix.Translation((-1.80, 0.0, 1.5 + self.ladder_height))
- bmesh.ops.transform(bm, matrix=cursor_shift, space=upper_rgn.matrix_world, verts=bm.verts)
- bmesh.ops.transform(bm, matrix=rgn_pos, space=upper_rgn.matrix_world, verts=bm.verts)
+ bmesh.ops.transform(
+ bm, matrix=cursor_shift, space=upper_rgn.matrix_world, verts=bm.verts
+ )
+ bmesh.ops.transform(
+ bm, matrix=rgn_pos, space=upper_rgn.matrix_world, verts=bm.verts
+ )
bm.to_mesh(mesh)
bm.free()
@@ -449,14 +557,17 @@ class PlasmaAddLadderMeshOperator(PlasmaMeshOperator, bpy.types.Operator):
# Construct the bmesh and assign it to the blender mesh.
bm = bmesh.new()
rgn_scale = mathutils.Matrix(
- ((self.ladder_width / 12, 0.0, 0.0),
- (0.0, 2.5, 0.0),
- (0.0, 0.0, 2.0)))
+ ((self.ladder_width / 12, 0.0, 0.0), (0.0, 2.5, 0.0), (0.0, 0.0, 2.0))
+ )
bmesh.ops.create_cube(bm, size=(1.0), matrix=rgn_scale)
rgn_pos = mathutils.Matrix.Translation((2.70, 0.0, 1.5))
- bmesh.ops.transform(bm, matrix=cursor_shift, space=lower_rgn.matrix_world, verts=bm.verts)
- bmesh.ops.transform(bm, matrix=rgn_pos, space=lower_rgn.matrix_world, verts=bm.verts)
+ bmesh.ops.transform(
+ bm, matrix=cursor_shift, space=lower_rgn.matrix_world, verts=bm.verts
+ )
+ bmesh.ops.transform(
+ bm, matrix=rgn_pos, space=lower_rgn.matrix_world, verts=bm.verts
+ )
bm.to_mesh(mesh)
bm.free()
@@ -495,6 +606,7 @@ class PlasmaAddLadderMeshOperator(PlasmaMeshOperator, bpy.types.Operator):
def name_stem(self):
return self.ladder_name if self.ladder_name else "Ladder"
+
def origin_to_bottom(obj):
# Modified from https://blender.stackexchange.com/a/42110/3055
mw = obj.matrix_world
@@ -532,16 +644,30 @@ class PlasmaAddLinkingBookMeshOperator(PlasmaMeshOperator, bpy.types.Operator):
}
# Allows user to specify their own name stem
- panel_name = bpy.props.StringProperty(name="Name",
- description="Linking Book name stem",
- default="LinkingBook",
- options=set())
- link_anim_type = bpy.props.EnumProperty(name="Link Animation",
- description="Type of Linking Animation to use",
- items=[("LinkOut", "Standing", "The avatar steps up to the book and places their hand on the panel"),
- ("FishBookLinkOut", "Kneeling", "The avatar kneels in front of the book and places their hand on the panel"),],
- default="LinkOut",
- options=set())
+ panel_name = bpy.props.StringProperty(
+ name="Name",
+ description="Linking Book name stem",
+ default="LinkingBook",
+ options=set(),
+ )
+ link_anim_type = bpy.props.EnumProperty(
+ name="Link Animation",
+ description="Type of Linking Animation to use",
+ items=[
+ (
+ "LinkOut",
+ "Standing",
+ "The avatar steps up to the book and places their hand on the panel",
+ ),
+ (
+ "FishBookLinkOut",
+ "Kneeling",
+ "The avatar kneels in front of the book and places their hand on the panel",
+ ),
+ ],
+ default="LinkOut",
+ options=set(),
+ )
def draw(self, context):
layout = self.layout
@@ -558,7 +684,9 @@ class PlasmaAddLinkingBookMeshOperator(PlasmaMeshOperator, bpy.types.Operator):
row.prop(self, "link_anim_type", text="Type")
else:
row = layout.row()
- row.label("Warning: Operator does not work in local view mode", icon="ERROR")
+ row.label(
+ "Warning: Operator does not work in local view mode", icon="ERROR"
+ )
def execute(self, context):
if context.space_data.local_view:
@@ -587,7 +715,9 @@ class PlasmaAddLinkingBookMeshOperator(PlasmaMeshOperator, bpy.types.Operator):
bpy.context.scene.objects.link(seek_point)
seek_point.show_name = True
seek_point.empty_draw_type = "ARROWS"
- link_anim_offset = mathutils.Matrix.Translation(self.anim_offsets[self.link_anim_type])
+ link_anim_offset = mathutils.Matrix.Translation(
+ self.anim_offsets[self.link_anim_type]
+ )
seek_point.matrix_local = link_anim_offset
seek_point.plasma_object.enabled = True
@@ -595,7 +725,9 @@ class PlasmaAddLinkingBookMeshOperator(PlasmaMeshOperator, bpy.types.Operator):
clk_rgn_name = "{}_ClkRegion".format(self.name_stem)
clk_rgn_size = 6.0
with utils.bmesh_object(clk_rgn_name) as (clk_rgn, bm):
- bmesh.ops.create_cube(bm, size=(1.0), matrix=(mathutils.Matrix.Scale(clk_rgn_size, 4)))
+ bmesh.ops.create_cube(
+ bm, size=(1.0), matrix=(mathutils.Matrix.Scale(clk_rgn_size, 4))
+ )
clk_rgn.hide_render = True
clk_rgn.plasma_object.enabled = True
@@ -625,5 +757,6 @@ class PlasmaAddLinkingBookMeshOperator(PlasmaMeshOperator, bpy.types.Operator):
def register():
bpy.utils.register_module(__name__)
+
def unregister():
bpy.utils.unregister_module(__name__)
diff --git a/korman/operators/op_modifier.py b/korman/operators/op_modifier.py
index 9fb18cc..faf5fb7 100644
--- a/korman/operators/op_modifier.py
+++ b/korman/operators/op_modifier.py
@@ -20,6 +20,7 @@ import time
from ..ordered_set import OrderedSet
from ..properties import modifiers
+
def _fetch_modifiers():
items = []
@@ -27,16 +28,19 @@ def _fetch_modifiers():
for i in sorted(mapping.keys()):
items.append(("", i, ""))
items.extend(mapping[i])
- #yield ("", i, "")
- #yield mapping[i]
+ # yield ("", i, "")
+ # yield mapping[i]
return items
+
class ModifierOperator:
def _get_modifier(self, context):
if self.active_modifier == -1:
return None
pl_mods = context.object.plasma_modifiers.modifiers
- pl_mod = next((i for i in pl_mods if self.active_modifier == i.display_order), None)
+ pl_mod = next(
+ (i for i in pl_mods if self.active_modifier == i.display_order), None
+ )
if pl_mod is None:
raise IndexError(self.active_modifier)
return pl_mod
@@ -51,9 +55,11 @@ class ModifierAddOperator(ModifierOperator, bpy.types.Operator):
bl_label = "Add Modifier"
bl_description = "Adds a Plasma Modifier"
- types = EnumProperty(name="Modifier Type",
- description="The type of modifier we add to the list",
- items=_fetch_modifiers())
+ types = EnumProperty(
+ name="Modifier Type",
+ description="The type of modifier we add to the list",
+ items=_fetch_modifiers(),
+ )
def execute(self, context):
plmods = context.object.plasma_modifiers
@@ -122,9 +128,9 @@ class ModifierCopyOperator(ModifierOperator, bpy.types.Operator):
bl_label = "Copy Modifiers"
bl_description = "Copy Modifiers from an Object"
- active_modifier = IntProperty(name="Modifier Display Order",
- default=-1,
- options={"HIDDEN"})
+ active_modifier = IntProperty(
+ name="Modifier Display Order", default=-1, options={"HIDDEN"}
+ )
def execute(self, context):
pl_scene = context.scene.plasma_scene
@@ -181,7 +187,9 @@ class ModifierPasteOperator(ModifierClipboard, ModifierOperator, bpy.types.Opera
@classmethod
def poll(cls, context):
pl_scene = context.scene.plasma_scene
- return super().poll(context) and pl_scene.is_property_set("modifier_copy_object")
+ return super().poll(context) and pl_scene.is_property_set(
+ "modifier_copy_object"
+ )
class ModifierRemoveOperator(ModifierOperator, bpy.types.Operator):
@@ -189,9 +197,9 @@ class ModifierRemoveOperator(ModifierOperator, bpy.types.Operator):
bl_label = "Remove Modifier"
bl_description = "Removes this Plasma Modifier"
- active_modifier = IntProperty(name="Modifier Display Order",
- default=-1,
- options={"HIDDEN"})
+ active_modifier = IntProperty(
+ name="Modifier Display Order", default=-1, options={"HIDDEN"}
+ )
mods2delete = CollectionProperty(type=modifiers.PlasmaModifierSpec, options=set())
@@ -203,11 +211,15 @@ class ModifierRemoveOperator(ModifierOperator, bpy.types.Operator):
layout = layout.column_flow(align=True)
for i in self.mods2delete:
mod = getattr(mods, i.name)
- layout.label(" {}".format(mod.bl_label), icon=getattr(mod, "bl_icon", "NONE"))
+ layout.label(
+ " {}".format(mod.bl_label), icon=getattr(mod, "bl_icon", "NONE")
+ )
def execute(self, context):
want2delete = set((i.name for i in self.mods2delete))
- mods = sorted(context.object.plasma_modifiers.modifiers, key=lambda x: x.display_order)
+ mods = sorted(
+ context.object.plasma_modifiers.modifiers, key=lambda x: x.display_order
+ )
subtract = 0
for mod in mods:
@@ -225,7 +237,9 @@ class ModifierRemoveOperator(ModifierOperator, bpy.types.Operator):
want2delete = OrderedSet()
if self.active_modifier == -1:
- want2delete.update((i.pl_id for i in context.object.plasma_modifiers.modifiers))
+ want2delete.update(
+ (i.pl_id for i in context.object.plasma_modifiers.modifiers)
+ )
else:
want2delete.add(self._get_modifier(context).pl_id)
@@ -254,9 +268,9 @@ class ModifierResetOperator(ModifierOperator, bpy.types.Operator):
bl_label = "Reset the modifier(s) to the default state?"
bl_description = "Reset the modifier(s) to the default state"
- active_modifier = IntProperty(name="Modifier Display Order",
- default=-1,
- options={"HIDDEN"})
+ active_modifier = IntProperty(
+ name="Modifier Display Order", default=-1, options={"HIDDEN"}
+ )
def draw(self, context):
pass
@@ -297,15 +311,17 @@ class ModifierMoveUpOperator(ModifierMoveOperator, bpy.types.Operator):
bl_label = "Move Up"
bl_description = "Move the modifier up in the stack"
- active_modifier = IntProperty(name="Modifier Display Order",
- default=-1,
- options={"HIDDEN"})
+ active_modifier = IntProperty(
+ name="Modifier Display Order", default=-1, options={"HIDDEN"}
+ )
def execute(self, context):
assert self.active_modifier >= 0
if self.active_modifier > 0:
plmods = context.object.plasma_modifiers
- self.swap_modifier_ids(plmods, self.active_modifier, self.active_modifier-1)
+ self.swap_modifier_ids(
+ plmods, self.active_modifier, self.active_modifier - 1
+ )
return {"FINISHED"}
@@ -314,9 +330,9 @@ class ModifierMoveDownOperator(ModifierMoveOperator, bpy.types.Operator):
bl_label = "Move Down"
bl_description = "Move the modifier down in the stack"
- active_modifier = IntProperty(name="Modifier Display Order",
- default=-1,
- options={"HIDDEN"})
+ active_modifier = IntProperty(
+ name="Modifier Display Order", default=-1, options={"HIDDEN"}
+ )
def execute(self, context):
assert self.active_modifier >= 0
@@ -324,7 +340,9 @@ class ModifierMoveDownOperator(ModifierMoveOperator, bpy.types.Operator):
plmods = context.object.plasma_modifiers
last = max([mod.display_order for mod in plmods.modifiers])
if self.active_modifier < last:
- self.swap_modifier_ids(plmods, self.active_modifier, self.active_modifier+1)
+ self.swap_modifier_ids(
+ plmods, self.active_modifier, self.active_modifier + 1
+ )
return {"FINISHED"}
@@ -348,5 +366,5 @@ class ModifierLogicWizOperator(ModifierOperator, bpy.types.Operator):
start = time.process_time()
mod.create_logic(obj)
end = time.process_time()
- print("\nLogicWiz finished in {:.2f} seconds".format(end-start))
+ print("\nLogicWiz finished in {:.2f} seconds".format(end - start))
return {"FINISHED"}
diff --git a/korman/operators/op_nodes.py b/korman/operators/op_nodes.py
index 67e1196..c5b1114 100644
--- a/korman/operators/op_nodes.py
+++ b/korman/operators/op_nodes.py
@@ -18,6 +18,7 @@ from bpy.props import *
import itertools
import pickle
+
class NodeOperator:
@classmethod
def poll(cls, context):
@@ -40,7 +41,9 @@ class CreateLinkNodeOperator(NodeOperator, bpy.types.Operator):
_hack = []
def _link_search_list(self, context):
- CreateLinkNodeOperator._hack = list(CreateLinkNodeOperator._link_search_list_imp(self, context))
+ CreateLinkNodeOperator._hack = list(
+ CreateLinkNodeOperator._link_search_list_imp(self, context)
+ )
return CreateLinkNodeOperator._hack
def _link_search_list_imp(self, context):
@@ -50,14 +53,18 @@ class CreateLinkNodeOperator(NodeOperator, bpy.types.Operator):
src_node = tree.nodes[self.node_name]
src_socket = CreateLinkNodeOperator._find_source_socket(self, src_node)
- links = list(src_node.generate_valid_links_for(context, src_socket, self.is_output))
+ links = list(
+ src_node.generate_valid_links_for(context, src_socket, self.is_output)
+ )
max_node = max((len(i["node_text"]) for i in links)) if links else 0
for i, link in enumerate(links):
# Pickle protocol 0 uses only ASCII bytes, so we can pretend it's a string easily...
id_string = pickle.dumps(link, protocol=0).decode()
- desc_string = "{node}:{node_sock_space}{sock}".format(node=link["node_text"],
+ desc_string = "{node}:{node_sock_space}{sock}".format(
+ node=link["node_text"],
node_sock_space=(" " * (max_node - len(link["node_text"]) + 4)),
- sock=link["socket_text"])
+ sock=link["socket_text"],
+ )
yield (id_string, desc_string, "", i)
node_item = EnumProperty(items=_link_search_list)
@@ -101,7 +108,11 @@ class CreateLinkNodeOperator(NodeOperator, bpy.types.Operator):
src_node = tree.nodes[self.node_name]
src_socket = self._find_source_socket(src_node)
# We need to use Korman's functions because they may generate a node socket.
- find_socket = dest_node.find_input_socket if self.is_output else dest_node.find_output_socket
+ find_socket = (
+ dest_node.find_input_socket
+ if self.is_output
+ else dest_node.find_output_socket
+ )
dest_socket = find_socket(link["socket_name"], True)
if self.is_output:
@@ -113,7 +124,9 @@ class CreateLinkNodeOperator(NodeOperator, bpy.types.Operator):
def modal(self, context, event):
# Ugh. The Blender API sucks so much. We can only get the cursor pos from here???
- context.space_data.cursor_location_from_region(event.mouse_region_x, event.mouse_region_y)
+ context.space_data.cursor_location_from_region(
+ event.mouse_region_x, event.mouse_region_y
+ )
if len(self._hack) == 1:
self._create_link_node(context, self._hack[0][0])
self._hack.clear()
@@ -132,9 +145,12 @@ class CreateLinkNodeOperator(NodeOperator, bpy.types.Operator):
def poll(cls, context):
space = context.space_data
# needs active node editor and a tree to add nodes to
- return (space.type == 'NODE_EDITOR' and
- space.edit_tree and not space.edit_tree.library and
- context.scene.render.engine == "PLASMA_GAME")
+ return (
+ space.type == "NODE_EDITOR"
+ and space.edit_tree
+ and not space.edit_tree.library
+ and context.scene.render.engine == "PLASMA_GAME"
+ )
class SelectFileOperator(NodeOperator, bpy.types.Operator):
@@ -147,14 +163,23 @@ class SelectFileOperator(NodeOperator, bpy.types.Operator):
filename = StringProperty(options={"HIDDEN"})
data_path = StringProperty(options={"HIDDEN"})
- filepath_property = StringProperty(description="Name of property to store filepath in", options={"HIDDEN"})
- filename_property = StringProperty(description="Name of property to store filename in", options={"HIDDEN"})
+ filepath_property = StringProperty(
+ description="Name of property to store filepath in", options={"HIDDEN"}
+ )
+ filename_property = StringProperty(
+ description="Name of property to store filename in", options={"HIDDEN"}
+ )
def execute(self, context):
if bpy.data.texts.get(self.filename, None) is None:
bpy.data.texts.load(self.filepath)
else:
- self.report({"WARNING"}, "A file named '{}' is already loaded. It will be used.".format(self.filename))
+ self.report(
+ {"WARNING"},
+ "A file named '{}' is already loaded. It will be used.".format(
+ self.filename
+ ),
+ )
dest = eval(self.data_path)
if self.filepath_property:
@@ -167,46 +192,28 @@ class SelectFileOperator(NodeOperator, bpy.types.Operator):
context.window_manager.fileselect_add(self)
return {"RUNNING_MODAL"}
-pyAttribArgMap= {
- "ptAttribute":
- ["vislistid", "visliststates"],
- "ptAttribBoolean":
- ["default"],
- "ptAttribInt":
- ["default", "rang"],
- "ptAttribFloat":
- ["default", "rang"],
- "ptAttribString":
- ["default"],
- "ptAttribDropDownList":
- ["options"],
- "ptAttribSceneobject":
- ["netForce"],
- "ptAttribSceneobjectList":
- ["byObject", "netForce"],
- "ptAttributeKeyList":
- ["byObject", "netForce"],
- "ptAttribActivator":
- ["byObject", "netForce"],
- "ptAttribActivatorList":
- ["byObject", "netForce"],
- "ptAttribResponder":
- ["stateList", "byObject", "netForce", "netPropagate"],
- "ptAttribResponderList":
- ["stateList", "byObject", "netForce", "netPropagate"],
- "ptAttribNamedActivator":
- ["byObject", "netForce"],
- "ptAttribNamedResponder":
- ["stateList", "byObject", "netForce", "netPropagate"],
- "ptAttribDynamicMap":
- ["netForce"],
- "ptAttribAnimation":
- ["byObject", "netForce"],
- "ptAttribBehavior":
- ["netForce", "netProp"],
- "ptAttribMaterialList":
- ["byObject", "netForce"],
- }
+
+pyAttribArgMap = {
+ "ptAttribute": ["vislistid", "visliststates"],
+ "ptAttribBoolean": ["default"],
+ "ptAttribInt": ["default", "rang"],
+ "ptAttribFloat": ["default", "rang"],
+ "ptAttribString": ["default"],
+ "ptAttribDropDownList": ["options"],
+ "ptAttribSceneobject": ["netForce"],
+ "ptAttribSceneobjectList": ["byObject", "netForce"],
+ "ptAttributeKeyList": ["byObject", "netForce"],
+ "ptAttribActivator": ["byObject", "netForce"],
+ "ptAttribActivatorList": ["byObject", "netForce"],
+ "ptAttribResponder": ["stateList", "byObject", "netForce", "netPropagate"],
+ "ptAttribResponderList": ["stateList", "byObject", "netForce", "netPropagate"],
+ "ptAttribNamedActivator": ["byObject", "netForce"],
+ "ptAttribNamedResponder": ["stateList", "byObject", "netForce", "netPropagate"],
+ "ptAttribDynamicMap": ["netForce"],
+ "ptAttribAnimation": ["byObject", "netForce"],
+ "ptAttribBehavior": ["netForce", "netProp"],
+ "ptAttribMaterialList": ["byObject", "netForce"],
+}
class PlPyAttributeNodeOperator(NodeOperator, bpy.types.Operator):
@@ -220,6 +227,7 @@ class PlPyAttributeNodeOperator(NodeOperator, bpy.types.Operator):
def execute(self, context):
from ..plasma_attributes import get_attributes_from_str
+
text_id = bpy.data.texts[self.text_path]
attribs = get_attributes_from_str(text_id.as_string())
@@ -250,12 +258,26 @@ class PlPyAttributeNodeOperator(NodeOperator, bpy.types.Operator):
# Load our default argument mapping
if args is not None:
if cached.attribute_type in pyAttribArgMap.keys():
- argmap.update(dict(zip(pyAttribArgMap[cached.attribute_type], args)))
+ argmap.update(
+ dict(zip(pyAttribArgMap[cached.attribute_type], args))
+ )
else:
- print("Found ptAttribute type '{}' with unknown arguments: {}".format(cached.attribute_type, args))
+ print(
+ "Found ptAttribute type '{}' with unknown arguments: {}".format(
+ cached.attribute_type, args
+ )
+ )
# Add in/set any arguments provided by keyword
- if cached.attribute_type in pyAttribArgMap.keys() and not set(pyAttribArgMap[cached.attribute_type]).isdisjoint(attrib.keys()):
- argmap.update({key: attrib[key] for key in attrib if key in pyAttribArgMap[cached.attribute_type]})
+ if cached.attribute_type in pyAttribArgMap.keys() and not set(
+ pyAttribArgMap[cached.attribute_type]
+ ).isdisjoint(attrib.keys()):
+ argmap.update(
+ {
+ key: attrib[key]
+ for key in attrib
+ if key in pyAttribArgMap[cached.attribute_type]
+ }
+ )
# Attach the arguments to the attribute
if argmap:
cached.attribute_arguments.set_arguments(argmap)
diff --git a/korman/operators/op_sound.py b/korman/operators/op_sound.py
index 911a562..0ef97b3 100644
--- a/korman/operators/op_sound.py
+++ b/korman/operators/op_sound.py
@@ -16,6 +16,7 @@
import bpy
from bpy.props import *
+
class SoundOperator:
@classmethod
def poll(cls, context):
@@ -69,13 +70,18 @@ class PlasmaSoundUnpackOperator(SoundOperator, bpy.types.Operator):
bl_label = "Unpack"
bl_options = {"INTERNAL"}
- method = EnumProperty(name="Method", description="How to unpack",
- # See blender/makesrna/intern/rna_packedfile.c
- items=[("USE_LOCAL", "Use local file", "", 5),
- ("WRITE_LOCAL", "Write Local File (overwrite existing)", "", 4),
- ("USE_ORIGINAL", "Use Original File", "", 6),
- ("WRITE_ORIGINAL", "Write Original File (overwrite existing)", "", 3)],
- options=set())
+ method = EnumProperty(
+ name="Method",
+ description="How to unpack",
+ # See blender/makesrna/intern/rna_packedfile.c
+ items=[
+ ("USE_LOCAL", "Use local file", "", 5),
+ ("WRITE_LOCAL", "Write Local File (overwrite existing)", "", 4),
+ ("USE_ORIGINAL", "Use Original File", "", 6),
+ ("WRITE_ORIGINAL", "Write Original File (overwrite existing)", "", 3),
+ ],
+ options=set(),
+ )
def execute(self, context):
soundemit = context.active_object.plasma_modifiers.soundemit
diff --git a/korman/operators/op_toolbox.py b/korman/operators/op_toolbox.py
index a5ffb64..a82ffe9 100644
--- a/korman/operators/op_toolbox.py
+++ b/korman/operators/op_toolbox.py
@@ -18,6 +18,7 @@ from bpy.props import *
import pickle
import itertools
+
class ToolboxOperator:
@classmethod
def poll(cls, context):
@@ -40,7 +41,9 @@ class PageSearchOperator(ToolboxOperator):
pages = [(pickle.dumps(i.name, 0).decode(), i.name, "") for i in page_defns]
# Ensure an entry exists for the default page
- manual_default_page = next((i.name for i in page_defns if i.seq_suffix == 0), None)
+ manual_default_page = next(
+ (i.name for i in page_defns if i.seq_suffix == 0), None
+ )
if not manual_default_page:
pages.append((pickle.dumps("", 0).decode(), "Default", "Default Page"))
@@ -87,7 +90,7 @@ class PlasmaConvertPlasmaObjectOperator(ToolboxOperator, bpy.types.Operator):
# is either inserted into a valid page using the old-style text properties or is lacking
# a page property. Unfortunately, unless we start bundling some YAML interpreter, we cannot
# use the old AlcScript schtuff.
- pages = { i.seq_suffix: i.name for i in context.scene.world.plasma_age.pages }
+ pages = {i.seq_suffix: i.name for i in context.scene.world.plasma_age.pages}
for i in bpy.data.objects:
pageid = i.game.properties.get("page_num", None)
if pageid is None:
@@ -99,7 +102,11 @@ class PlasmaConvertPlasmaObjectOperator(ToolboxOperator, bpy.types.Operator):
# a common hack to prevent exporting in PyPRP was to set page_num == -1,
# so don't warn about that.
if pageid.value != -1:
- print("Object '{}' in page_num '{}', which is not available :/".format(i.name, pageid.value))
+ print(
+ "Object '{}' in page_num '{}', which is not available :/".format(
+ i.name, pageid.value
+ )
+ )
else:
i.plasma_object.enabled = True
i.plasma_object.page = page_name
@@ -130,10 +137,12 @@ class PlasmaMovePageObjectsOperator(PageSearchOperator, bpy.types.Operator):
bl_description = "Moves all selected objects to a new page"
bl_property = "page"
- page = EnumProperty(name="Page",
- description= "Page whose objects should be selected",
- items=PageSearchOperator._get_pages,
- options=set())
+ page = EnumProperty(
+ name="Page",
+ description="Page whose objects should be selected",
+ items=PageSearchOperator._get_pages,
+ options=set(),
+ )
def execute(self, context):
desired_page = self.desired_page
@@ -148,10 +157,12 @@ class PlasmaSelectPageObjectsOperator(PageSearchOperator, bpy.types.Operator):
bl_description = "Selects all objects in a specific page"
bl_property = "page"
- page = EnumProperty(name="Page",
- description= "Page whose objects should be selected",
- items=PageSearchOperator._get_pages,
- options=set())
+ page = EnumProperty(
+ name="Page",
+ description="Page whose objects should be selected",
+ items=PageSearchOperator._get_pages,
+ options=set(),
+ )
def execute(self, context):
desired_page = self.desired_page
@@ -172,14 +183,14 @@ class PlasmaToggleAllPlasmaObjectsOperator(ToolboxOperator, bpy.types.Operator):
i.plasma_object.enabled = self.enable
return {"FINISHED"}
-
+
class PlasmaToggleDoubleSidedOperator(ToolboxOperator, bpy.types.Operator):
bl_idname = "mesh.plasma_toggle_double_sided"
bl_label = "Toggle All Double Sided"
bl_description = "Toggles all meshes to be double sided"
-
+
enable = BoolProperty(name="Enable", description="Enable Double Sided")
-
+
def execute(self, context):
enable = self.enable
for mesh in bpy.data.meshes:
@@ -191,7 +202,7 @@ class PlasmaToggleDoubleSidedSelectOperator(ToolboxOperator, bpy.types.Operator)
bl_idname = "mesh.plasma_toggle_double_sided_selected"
bl_label = "Toggle Selected Double Sided"
bl_description = "Toggles selected meshes double sided value"
-
+
@classmethod
def poll(cls, context):
return super().poll(context) and hasattr(bpy.context, "selected_objects")
@@ -232,7 +243,9 @@ class PlasmaTogglePlasmaObjectsOperator(ToolboxOperator, bpy.types.Operator):
return super().poll(context) and hasattr(bpy.context, "selected_objects")
def execute(self, context):
- enable = not all((i.plasma_object.enabled for i in bpy.context.selected_objects))
+ enable = not all(
+ (i.plasma_object.enabled for i in bpy.context.selected_objects)
+ )
for i in context.selected_objects:
i.plasma_object.enabled = enable
return {"FINISHED"}
@@ -242,9 +255,9 @@ class PlasmaToggleSoundExportOperator(ToolboxOperator, bpy.types.Operator):
bl_idname = "object.plasma_toggle_sound_export"
bl_label = "Toggle Sound Export"
bl_description = "Toggles the Export function of all sound emitters' files"
-
+
enable = BoolProperty(name="Enable", description="Sound Export Enable")
-
+
def execute(self, context):
enable = self.enable
for i in bpy.data.objects:
@@ -259,13 +272,21 @@ class PlasmaToggleSoundExportSelectedOperator(ToolboxOperator, bpy.types.Operato
bl_idname = "object.plasma_toggle_sound_export_selected"
bl_label = "Toggle Selected Sound Export"
bl_description = "Toggles the Export function of selected sound emitters' files."
-
+
@classmethod
def poll(cls, context):
return super().poll(context) and hasattr(bpy.context, "selected_objects")
-
+
def execute(self, context):
- enable = not all((i.package for i in itertools.chain.from_iterable(i.plasma_modifiers.soundemit.sounds for i in bpy.context.selected_objects)))
+ enable = not all(
+ (
+ i.package
+ for i in itertools.chain.from_iterable(
+ i.plasma_modifiers.soundemit.sounds
+ for i in bpy.context.selected_objects
+ )
+ )
+ )
for i in context.selected_objects:
if i.plasma_modifiers.soundemit is None:
continue
diff --git a/korman/operators/op_ui.py b/korman/operators/op_ui.py
index ffa3b17..89378a2 100644
--- a/korman/operators/op_ui.py
+++ b/korman/operators/op_ui.py
@@ -17,6 +17,7 @@ import addon_utils
import bpy
from bpy.props import *
+
class UIOperator:
@classmethod
def poll(cls, context):
@@ -28,25 +29,37 @@ class CollectionAddOperator(UIOperator, bpy.types.Operator):
bl_label = "Add Item"
bl_description = "Adds an item to the collection"
- context = StringProperty(name="ID Path",
- description="Path to the relevant datablock from the current context",
- options=set())
- group_path = StringProperty(name="Property Group Path",
- description="Path to the property group from the ID",
- options=set())
- collection_prop = StringProperty(name="Collection Property",
- description="Name of the collection property",
- options=set())
- index_prop = StringProperty(name="Index Property",
- description="Name of the active element index property",
- options=set())
- name_prefix = StringProperty(name="Name Prefix",
- description="Prefix for autogenerated item names",
- default="Item",
- options=set())
- name_prop = StringProperty(name="Name Property",
- description="Attribute name of the item name property",
- options=set())
+ context = StringProperty(
+ name="ID Path",
+ description="Path to the relevant datablock from the current context",
+ options=set(),
+ )
+ group_path = StringProperty(
+ name="Property Group Path",
+ description="Path to the property group from the ID",
+ options=set(),
+ )
+ collection_prop = StringProperty(
+ name="Collection Property",
+ description="Name of the collection property",
+ options=set(),
+ )
+ index_prop = StringProperty(
+ name="Index Property",
+ description="Name of the active element index property",
+ options=set(),
+ )
+ name_prefix = StringProperty(
+ name="Name Prefix",
+ description="Prefix for autogenerated item names",
+ default="Item",
+ options=set(),
+ )
+ name_prop = StringProperty(
+ name="Name Property",
+ description="Attribute name of the item name property",
+ options=set(),
+ )
def execute(self, context):
props = getattr(context, self.context).path_resolve(self.group_path)
@@ -54,7 +67,11 @@ class CollectionAddOperator(UIOperator, bpy.types.Operator):
idx = len(collection)
collection.add()
if self.name_prop:
- setattr(collection[idx], self.name_prop, "{} {}".format(self.name_prefix, idx+1))
+ setattr(
+ collection[idx],
+ self.name_prop,
+ "{} {}".format(self.name_prefix, idx + 1),
+ )
if self.index_prop:
setattr(props, self.index_prop, idx)
return {"FINISHED"}
@@ -65,18 +82,26 @@ class CollectionRemoveOperator(UIOperator, bpy.types.Operator):
bl_label = "Remove Item"
bl_description = "Removes an item from the collection"
- context = StringProperty(name="ID Path",
- description="Path to the relevant datablock from the current context",
- options=set())
- group_path = StringProperty(name="Property Group Path",
- description="Path to the property group from the ID",
- options=set())
- collection_prop = StringProperty(name="Collection Property",
- description="Name of the collection property",
- options=set())
- index_prop = StringProperty(name="Index Property",
- description="Name of the active element index property",
- options=set())
+ context = StringProperty(
+ name="ID Path",
+ description="Path to the relevant datablock from the current context",
+ options=set(),
+ )
+ group_path = StringProperty(
+ name="Property Group Path",
+ description="Path to the property group from the ID",
+ options=set(),
+ )
+ collection_prop = StringProperty(
+ name="Collection Property",
+ description="Name of the collection property",
+ options=set(),
+ )
+ index_prop = StringProperty(
+ name="Index Property",
+ description="Name of the active element index property",
+ options=set(),
+ )
def execute(self, context):
props = getattr(context, self.context).path_resolve(self.group_path)
diff --git a/korman/operators/op_world.py b/korman/operators/op_world.py
index 866a580..3bbdceb 100644
--- a/korman/operators/op_world.py
+++ b/korman/operators/op_world.py
@@ -17,6 +17,7 @@ import bpy
from bpy.props import *
from pathlib import Path
+
class AgeOperator:
@classmethod
def poll(cls, context):
@@ -40,7 +41,9 @@ class GameAddOperator(AgeOperator, bpy.types.Operator):
# Blendsucks likes to tack filenames onto our doggone directories...
if not path.is_dir():
path = path.parent
- if not ((path / "UruExplorer.exe").is_file() or (path / "plClient.exe").is_file()):
+ if not (
+ (path / "UruExplorer.exe").is_file() or (path / "plClient.exe").is_file()
+ ):
self.report({"ERROR"}, "The selected directory is not a copy of URU.")
return {"CANCELLED"}
@@ -62,7 +65,6 @@ class GameAddOperator(AgeOperator, bpy.types.Operator):
return {"FINISHED"}
-
def invoke(self, context, event):
context.window_manager.fileselect_add(self)
return {"RUNNING_MODAL"}
diff --git a/korman/ordered_set.py b/korman/ordered_set.py
index eb333f6..ccefcab 100644
--- a/korman/ordered_set.py
+++ b/korman/ordered_set.py
@@ -19,7 +19,7 @@ Updated for Python 3.5 by Adam Johnson
import collections.abc
SLICE_ALL = slice(None)
-__version__ = '2.0.1'
+__version__ = "2.0.1"
def is_iterable(obj):
@@ -35,7 +35,11 @@ def is_iterable(obj):
We don't need to check for the Python 2 `unicode` type, because it doesn't
have an `__iter__` attribute anyway.
"""
- return hasattr(obj, '__iter__') and not isinstance(obj, str) and not isinstance(obj, tuple)
+ return (
+ hasattr(obj, "__iter__")
+ and not isinstance(obj, str)
+ and not isinstance(obj, tuple)
+ )
class OrderedSet(collections.abc.MutableSet):
@@ -43,6 +47,7 @@ class OrderedSet(collections.abc.MutableSet):
An OrderedSet is a custom MutableSet that remembers its order, so that
every entry has an index that can be looked up.
"""
+
def __init__(self, iterable=None):
self.items = []
self.map = {}
@@ -66,7 +71,7 @@ class OrderedSet(collections.abc.MutableSet):
"""
if index == SLICE_ALL:
return self
- elif hasattr(index, '__index__') or isinstance(index, slice):
+ elif hasattr(index, "__index__") or isinstance(index, slice):
result = self.items[index]
if isinstance(result, list):
return OrderedSet(result)
@@ -75,8 +80,7 @@ class OrderedSet(collections.abc.MutableSet):
elif is_iterable(index):
return OrderedSet([self.items[i] for i in index])
else:
- raise TypeError("Don't know how to index an OrderedSet by %r" %
- index)
+ raise TypeError("Don't know how to index an OrderedSet by %r" % index)
def copy(self):
return OrderedSet(self)
@@ -113,6 +117,7 @@ class OrderedSet(collections.abc.MutableSet):
self.map[key] = len(self.items)
self.items.append(key)
return self.map[key]
+
append = add
def update(self, sequence):
@@ -125,7 +130,9 @@ class OrderedSet(collections.abc.MutableSet):
for item in sequence:
item_index = self.add(item)
except TypeError:
- raise ValueError('Argument needs to be an iterable, got %s' % type(sequence))
+ raise ValueError(
+ "Argument needs to be an iterable, got %s" % type(sequence)
+ )
return item_index
def index(self, key):
@@ -147,7 +154,7 @@ class OrderedSet(collections.abc.MutableSet):
Raises KeyError if the set is empty.
"""
if not self.items:
- raise KeyError('Set is empty')
+ raise KeyError("Set is empty")
elem = self.items[-1]
del self.items[-1]
@@ -184,8 +191,8 @@ class OrderedSet(collections.abc.MutableSet):
def __repr__(self):
if not self:
- return '%s()' % (self.__class__.__name__,)
- return '%s(%r)' % (self.__class__.__name__, list(self))
+ return "%s()" % (self.__class__.__name__,)
+ return "%s(%r)" % (self.__class__.__name__, list(self))
def __eq__(self, other):
if isinstance(other, OrderedSet):
@@ -197,4 +204,3 @@ class OrderedSet(collections.abc.MutableSet):
return False
else:
return set(self) == other_as_set
-
diff --git a/korman/plasma_attributes.py b/korman/plasma_attributes.py
index 70408fb..71e2fab 100644
--- a/korman/plasma_attributes.py
+++ b/korman/plasma_attributes.py
@@ -37,10 +37,12 @@ class PlasmaAttributeVisitor(ast.NodeVisitor):
# - assignments with targets
# - that are taking a function call (the ptAttrib Constructor)
# - whose name starts with ptAttrib
- if (len(assign.targets) == 1
+ if (
+ len(assign.targets) == 1
and isinstance(assign.value, ast.Call)
and hasattr(assign.value.func, "id")
- and assign.value.func.id.startswith("ptAttrib")):
+ and assign.value.func.id.startswith("ptAttrib")
+ ):
# Start pulling apart that delicious information
ptVar = assign.targets[0].id
ptType = assign.value.func.id
@@ -53,7 +55,11 @@ class PlasmaAttributeVisitor(ast.NodeVisitor):
# which only have an index. We don't want those.
if len(ptArgs) > 1:
# Add the common arguments as named items.
- self._attributes[ptArgs[0]] = {"name": ptVar, "type": ptType, "desc": ptArgs[1]}
+ self._attributes[ptArgs[0]] = {
+ "name": ptVar,
+ "type": ptType,
+ "desc": ptArgs[1],
+ }
# Add the class-specific arguments under the 'args' item.
if ptArgs[2:]:
self._attributes[ptArgs[0]]["args"] = ptArgs[2:]
@@ -61,13 +67,15 @@ class PlasmaAttributeVisitor(ast.NodeVisitor):
# Add the keyword arguments, if any.
if assign.value.keywords:
for keyword in assign.value.keywords:
- self._attributes[ptArgs[0]][keyword.arg] = self.visit(keyword.value)
+ self._attributes[ptArgs[0]][keyword.arg] = self.visit(
+ keyword.value
+ )
return self.generic_visit(node)
def visit_Name(self, node):
# Workaround for old Cyan scripts: replace variables named "true" or "false"
# with the respective constant values True or False.
- if node.id.lower() in {"true", "false"}:
+ if node.id.lower() in {"true", "false"}:
return ast.literal_eval(node.id.capitalize())
return node.id
@@ -104,10 +112,11 @@ class PlasmaAttributeVisitor(ast.NodeVisitor):
def get_attributes_from_file(filepath):
"""Scan the file for assignments matching our regex, let our visitor parse them, and return the
- file's ptAttribs, if any."""
+ file's ptAttribs, if any."""
with open(str(filepath)) as script:
return get_attributes_from_str(script.read())
+
def get_attributes_from_str(code):
results = funcregex.findall(code)
if results:
@@ -119,6 +128,7 @@ def get_attributes_from_str(code):
return v._attributes
return {}
+
if __name__ == "__main__":
import json
from pathlib import Path
diff --git a/korman/plasma_launcher.py b/korman/plasma_launcher.py
index 4937b87..30c5fc9 100644
--- a/korman/plasma_launcher.py
+++ b/korman/plasma_launcher.py
@@ -26,7 +26,10 @@ main_parser = argparse.ArgumentParser(description="Korman Plasma Launcher")
main_parser.add_argument("cwd", type=Path, help="Working directory of the client")
main_parser.add_argument("age", type=str, help="Name of the age to launch into")
-sub_parsers = main_parser.add_subparsers(title="Plasma Version", dest="version",)
+sub_parsers = main_parser.add_subparsers(
+ title="Plasma Version",
+ dest="version",
+)
moul_parser = sub_parsers.add_parser("pvMoul")
moul_parser.add_argument("ki", type=int, help="KI Number of the desired player")
moul_parser.add_argument("--serverini", type=str, default="server.ini")
@@ -36,15 +39,10 @@ sp_parser.add_argument("player", type=str, help="Name of the desired player")
autolink_chron_name = "OfflineKIAutoLink"
if sys.platform == "win32":
- client_executables = {
- "pvMoul": "plClient.exe",
- "pvPots": "UruExplorer.exe"
- }
+ client_executables = {"pvMoul": "plClient.exe", "pvPots": "UruExplorer.exe"}
else:
- client_executables = {
- "pvMoul": "plClient",
- "pvPots": "UruExplorer"
- }
+ client_executables = {"pvMoul": "plClient", "pvPots": "UruExplorer"}
+
def die(*args, **kwargs):
assert args
@@ -55,6 +53,7 @@ def die(*args, **kwargs):
sys.stdout.write("DIE\n")
sys.exit(1)
+
def write(*args, **kwargs):
assert args
if len(args) == 1 and not kwargs:
@@ -65,18 +64,32 @@ def write(*args, **kwargs):
# And this is why we aren't using print()...
sys.stdout.flush()
+
def backup_vault_dat(path):
backup_path = path.with_suffix(".dat.korman_backup")
shutil.copy2(str(path), str(backup_path))
write("DBG: Copied vault backup: {}", backup_path)
+
def set_link_chronicle(store, new_value, cond_value=None):
- chron_folder = next((i for i in store.getChildren(store.firstNodeID)
- if getattr(i, "folderType", None) == plVault.kChronicleFolder), None)
+ chron_folder = next(
+ (
+ i
+ for i in store.getChildren(store.firstNodeID)
+ if getattr(i, "folderType", None) == plVault.kChronicleFolder
+ ),
+ None,
+ )
if chron_folder is None:
die("Could not locate vault chronicle folder.")
- autolink_chron = next((i for i in store.getChildren(chron_folder.nodeID)
- if getattr(i, "entryName", None) == autolink_chron_name), None)
+ autolink_chron = next(
+ (
+ i
+ for i in store.getChildren(chron_folder.nodeID)
+ if getattr(i, "entryName", None) == autolink_chron_name
+ ),
+ None,
+ )
if autolink_chron is None:
write("DBG: Creating AutoLink chronicle...")
autolink_chron = plVaultChronicleNode()
@@ -93,10 +106,15 @@ def set_link_chronicle(store, new_value, cond_value=None):
autolink_chron.entryValue = new_value
store.addNode(autolink_chron)
else:
- write("DBG: ***Not*** changing chronicle! AutoLink = '{}' (expected: '{}')", previous_value, cond_value)
+ write(
+ "DBG: ***Not*** changing chronicle! AutoLink = '{}' (expected: '{}')",
+ previous_value,
+ cond_value,
+ )
return previous_value
+
def find_player_vault(cwd, name):
sav_dir = cwd.joinpath("sav")
if not sav_dir.is_dir():
@@ -121,6 +139,7 @@ def find_player_vault(cwd, name):
return vault_dat, store
die("Could not locate the requested player vault.")
+
def main():
print("DBG: alive")
args = main_parser.parse_args()
@@ -139,11 +158,13 @@ def main():
# Update init file for this schtuff...
init_path = args.cwd.joinpath("init", "net_age.fni")
- with plEncryptedStream().open(str(init_path), fmWrite, plEncryptedStream.kEncXtea) as ini:
+ with plEncryptedStream().open(
+ str(init_path), fmWrite, plEncryptedStream.kEncXtea
+ ) as ini:
ini.writeLine("# This file was automatically generated by Korman.")
ini.writeLine("Nav.PageInHoldList GlobalAnimations")
ini.writeLine("Net.SetPlayer {}".format(vault_store.firstNodeID))
- ini.writeLine("Net.SetPlayerByName \"{}\"".format(args.player))
+ ini.writeLine('Net.SetPlayerByName "{}"'.format(args.player))
# BUT WHY??? You ask...
# Because, sayeth Hoikas, if this command is not executed, you will remain ensconsed
# in the black void of the Link... forever... Sadly, it accepts no arguments and determines
@@ -161,8 +182,14 @@ def main():
plasma_args = [str(executable), "-iinit", "To_Dni"]
else:
write("DBG: Using a superior client :) :) :)")
- plasma_args = [str(executable), "-LocalData", "-SkipLoginDialog", "-ServerIni={}".format(args.serverini),
- "-PlayerId={}".format(args.ki), "-Age={}".format(args.age)]
+ plasma_args = [
+ str(executable),
+ "-LocalData",
+ "-SkipLoginDialog",
+ "-ServerIni={}".format(args.serverini),
+ "-PlayerId={}".format(args.ki),
+ "-Age={}".format(args.age),
+ ]
try:
proc = subprocess.Popen(plasma_args, cwd=str(args.cwd), shell=True)
@@ -180,7 +207,9 @@ def main():
vault_store = plVaultStore()
vault_store.Import(str(vault_path))
- new_prev_autolink = set_link_chronicle(vault_store, vault_prev_autolink, args.age)
+ new_prev_autolink = set_link_chronicle(
+ vault_store, vault_prev_autolink, args.age
+ )
if new_prev_autolink != args.age:
write("DBG: ***Not*** resaving the vault!")
else:
@@ -191,6 +220,7 @@ def main():
write("DONE")
sys.exit(0)
+
if __name__ == "__main__":
try:
main()
diff --git a/korman/properties/modifiers/__init__.py b/korman/properties/modifiers/__init__.py
index ba179df..259d83f 100644
--- a/korman/properties/modifiers/__init__.py
+++ b/korman/properties/modifiers/__init__.py
@@ -26,6 +26,7 @@ from .render import *
from .sound import *
from .water import *
+
class PlasmaModifiers(bpy.types.PropertyGroup):
def determine_next_id(self):
"""Gets the ID for the next modifier in the UI"""
@@ -40,7 +41,7 @@ class PlasmaModifiers(bpy.types.PropertyGroup):
@property
def modifiers(self):
"""Generates all of the enabled modifiers.
- NOTE: We do not promise to return modifiers in their display_order!
+ NOTE: We do not promise to return modifiers in their display_order!
"""
for i in dir(self):
attr = getattr(self, i, None)
@@ -66,7 +67,7 @@ class PlasmaModifiers(bpy.types.PropertyGroup):
setattr(cls, i.pl_id, bpy.props.PointerProperty(type=i))
bpy.types.Object.plasma_modifiers = bpy.props.PointerProperty(type=cls)
- def test_property(self, property : str) -> bool:
+ def test_property(self, property: str) -> bool:
"""Tests a property on all enabled Plasma modifiers"""
return any((getattr(i, property) for i in self.modifiers))
@@ -79,17 +80,24 @@ def modifier_mapping():
"""This returns a dict mapping Plasma Modifier categories to names"""
d = {}
- sorted_modifiers = sorted(PlasmaModifierProperties.__subclasses__(), key=lambda x: x.bl_label)
+ sorted_modifiers = sorted(
+ PlasmaModifierProperties.__subclasses__(), key=lambda x: x.bl_label
+ )
for i, mod in enumerate(sorted_modifiers):
- pl_id, category, label, description = mod.pl_id, mod.bl_category, mod.bl_label, mod.bl_description
+ pl_id, category, label, description = (
+ mod.pl_id,
+ mod.bl_category,
+ mod.bl_label,
+ mod.bl_description,
+ )
icon = getattr(mod, "bl_icon", "")
# The modifier might include the cateogry name in its name, so we'll strip that.
if label != category:
if label.startswith(category):
- label = label[len(category)+1:]
+ label = label[len(category) + 1 :]
if label.endswith(category):
- label = label[:-len(category)-1]
+ label = label[: -len(category) - 1]
tup = (pl_id, label, description, icon, i)
d_cat = d.setdefault(category, [])
diff --git a/korman/properties/modifiers/anim.py b/korman/properties/modifiers/anim.py
index 63b8572..74db6a7 100644
--- a/korman/properties/modifiers/anim.py
+++ b/korman/properties/modifiers/anim.py
@@ -25,10 +25,12 @@ from ..prop_anim import PlasmaAnimationCollection
from ...exporter import ExportError, utils
from ... import idprops
+
def _convert_frame_time(frame_num):
fps = bpy.context.scene.render.fps
return frame_num / fps
+
class ActionModifier:
@property
def blender_action(self):
@@ -36,19 +38,30 @@ class ActionModifier:
if bo.animation_data is not None and bo.animation_data.action is not None:
return bo.animation_data.action
if bo.data is not None:
- if bo.data.animation_data is not None and bo.data.animation_data.action is not None:
+ if (
+ bo.data.animation_data is not None
+ and bo.data.animation_data.action is not None
+ ):
# we will not use this action for any animation logic. that must be stored on the Object
# datablock for simplicity's sake.
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:
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,
+ )
if self.id_data.type == "CAMERA":
if not self.id_data.data.plasma_camera.allow_animations:
- raise ExportError("'{}': Animation modifiers are not allowed on this camera type.", self.id_data.name)
+ raise ExportError(
+ "'{}': Animation modifiers are not allowed on this camera type.",
+ self.id_data.name,
+ )
class PlasmaAnimationModifier(ActionModifier, PlasmaModifierProperties):
@@ -67,7 +80,9 @@ class PlasmaAnimationModifier(ActionModifier, PlasmaModifierProperties):
so = exporter.mgr.find_create_object(plSceneObject, bl=bo)
self.convert_object_animations(exporter, bo, so, self.subanimations)
- def convert_object_animations(self, exporter, bo, so, anims: Optional[Iterable] = None):
+ def convert_object_animations(
+ self, exporter, bo, so, anims: Optional[Iterable] = None
+ ):
if not anims:
anims = [self.subanimations.entire_animation]
aganims = list(self._export_ag_anims(exporter, bo, so, anims))
@@ -98,16 +113,25 @@ class PlasmaAnimationModifier(ActionModifier, PlasmaModifierProperties):
else:
start, end = None, None
- applicators = converter.convert_object_animations(bo, so, anim_name, start=start, end=end)
+ applicators = converter.convert_object_animations(
+ bo, so, anim_name, start=start, end=end
+ )
if not applicators:
- exporter.report.warn("Animation '{}' generated no applicators. Nothing will be exported.",
- anim_name, indent=2)
+ exporter.report.warn(
+ "Animation '{}' generated no applicators. Nothing will be exported.",
+ anim_name,
+ indent=2,
+ )
continue
pClass = plAgeGlobalAnim if anim.sdl_var else plATCAnim
- aganim = exporter.mgr.find_create_object(pClass, bl=bo, so=so, name="{}_{}".format(bo.name, anim_name))
+ aganim = exporter.mgr.find_create_object(
+ pClass, bl=bo, so=so, name="{}_{}".format(bo.name, anim_name)
+ )
aganim.name = anim_name
- aganim.start, aganim.end = converter.get_frame_time_range(*applicators, so=so)
+ aganim.start, aganim.end = converter.get_frame_time_range(
+ *applicators, so=so
+ )
for i in applicators:
aganim.addApplicator(i)
@@ -119,18 +143,24 @@ class PlasmaAnimationModifier(ActionModifier, PlasmaModifierProperties):
markers = action.pose_markers
initial_marker = markers.get(anim.initial_marker)
if initial_marker is not None:
- aganim.initial = converter.convert_frame_time(initial_marker.frame)
+ aganim.initial = converter.convert_frame_time(
+ initial_marker.frame
+ )
else:
aganim.initial = -1.0
if anim.loop:
loop_start = markers.get(anim.loop_start)
if loop_start is not None:
- aganim.loopStart = converter.convert_frame_time(loop_start.frame)
+ aganim.loopStart = converter.convert_frame_time(
+ loop_start.frame
+ )
else:
aganim.loopStart = aganim.start
loop_end = markers.get(anim.loop_end)
if loop_end is not None:
- aganim.loopEnd = converter.convert_frame_time(loop_end.frame)
+ aganim.loopEnd = converter.convert_frame_time(
+ loop_end.frame
+ )
else:
aganim.loopEnd = aganim.end
else:
@@ -157,10 +187,12 @@ class PlasmaAnimationModifier(ActionModifier, PlasmaModifierProperties):
class AnimGroupObject(idprops.IDPropObjectMixin, bpy.types.PropertyGroup):
- child_anim = PointerProperty(name="Child Animation",
- description="Object whose action is a child animation",
- type=bpy.types.Object,
- poll=idprops.poll_animated_objects)
+ child_anim = PointerProperty(
+ name="Child Animation",
+ description="Object whose action is a child animation",
+ type=bpy.types.Object,
+ poll=idprops.poll_animated_objects,
+ )
@classmethod
def _idprop_mapping(cls):
@@ -175,19 +207,25 @@ class PlasmaAnimationFilterModifier(PlasmaModifierProperties):
bl_description = "Filter animation components"
bl_icon = "UNLINKED"
- no_rotation = BoolProperty(name="Filter Rotation",
- description="Filter rotations",
- options=set())
-
- no_transX = BoolProperty(name="Filter X Translation",
- description="Filter the X component of translations",
- options=set())
- no_transY = BoolProperty(name="Filter Y Translation",
- description="Filter the Y component of translations",
- options=set())
- no_transZ = BoolProperty(name="Filter Z Translation",
- description="Filter the Z component of translations",
- options=set())
+ no_rotation = BoolProperty(
+ name="Filter Rotation", description="Filter rotations", options=set()
+ )
+
+ no_transX = BoolProperty(
+ name="Filter X Translation",
+ description="Filter the X component of translations",
+ options=set(),
+ )
+ no_transY = BoolProperty(
+ name="Filter Y Translation",
+ description="Filter the Y component of translations",
+ options=set(),
+ )
+ no_transZ = BoolProperty(
+ name="Filter Z Translation",
+ description="Filter the Z component of translations",
+ options=set(),
+ )
def export(self, exporter, bo, so):
# By this point, the object should already have a plFilterCoordInterface
@@ -219,9 +257,11 @@ class PlasmaAnimationGroupModifier(ActionModifier, PlasmaModifierProperties):
bl_description = "Defines related animations"
bl_icon = "GROUP"
- children = CollectionProperty(name="Child Animations",
- description="Animations that will execute the same commands as this one",
- type=AnimGroupObject)
+ children = CollectionProperty(
+ name="Child Animations",
+ description="Animations that will execute the same commands as this one",
+ type=AnimGroupObject,
+ )
active_child_index = IntProperty(options={"HIDDEN"})
def export(self, exporter, bo, so):
@@ -229,7 +269,9 @@ class PlasmaAnimationGroupModifier(ActionModifier, PlasmaModifierProperties):
raise ExportError("'{}': Object is not animated".format(bo.name))
# The message forwarder is the guy that makes sure that everybody knows WTF is going on
- msgfwd = exporter.mgr.find_create_object(plMsgForwarder, so=so, name=self.key_name)
+ msgfwd = exporter.mgr.find_create_object(
+ plMsgForwarder, so=so, name=self.key_name
+ )
# Now, this is da swhiz...
agmod, agmaster = exporter.animation.get_anigraph_objects(bo, so)
@@ -250,7 +292,9 @@ class PlasmaAnimationGroupModifier(ActionModifier, PlasmaModifierProperties):
msg = "Animation Group '{}' specifies an object '{}' with no Plasma Animation modifier. Ignoring..."
exporter.report.warn(msg, self.key_name, child_bo.name, indent=2)
continue
- child_agmod, child_agmaster = exporter.animation.get_anigraph_objects(bo=child_bo)
+ child_agmod, child_agmaster = exporter.animation.get_anigraph_objects(
+ bo=child_bo
+ )
msgfwd.addForwardKey(child_agmaster.key)
msgfwd.addForwardKey(agmaster.key)
@@ -260,12 +304,13 @@ class PlasmaAnimationGroupModifier(ActionModifier, PlasmaModifierProperties):
class LoopMarker(bpy.types.PropertyGroup):
- loop_name = StringProperty(name="Loop Name",
- description="Name of this loop")
- loop_start = StringProperty(name="Loop Start",
- description="Marker name from whence the loop begins")
- loop_end = StringProperty(name="Loop End",
- description="Marker name from whence the loop ends")
+ loop_name = StringProperty(name="Loop Name", description="Name of this loop")
+ loop_start = StringProperty(
+ name="Loop Start", description="Marker name from whence the loop begins"
+ )
+ loop_end = StringProperty(
+ name="Loop End", description="Marker name from whence the loop ends"
+ )
class PlasmaAnimationLoopModifier(ActionModifier, PlasmaModifierProperties):
@@ -277,9 +322,9 @@ class PlasmaAnimationLoopModifier(ActionModifier, PlasmaModifierProperties):
bl_description = "Animation loop settings"
bl_icon = "PMARKER_SEL"
- loops = CollectionProperty(name="Loops",
- description="Loop points within the animation",
- type=LoopMarker)
+ loops = CollectionProperty(
+ name="Loops", description="Loop points within the animation", type=LoopMarker
+ )
active_loop_index = IntProperty(options={"HIDDEN"})
def export(self, exporter, bo, so):
@@ -293,11 +338,23 @@ class PlasmaAnimationLoopModifier(ActionModifier, PlasmaModifierProperties):
start = markers.get(loop.loop_start)
end = markers.get(loop.loop_end)
if start is None:
- exporter.report.warn("Animation '{}' Loop '{}': Marker '{}' not found. This loop will not be exported".format(
- action.name, loop.loop_name, loop.loop_start), indent=2)
+ exporter.report.warn(
+ "Animation '{}' Loop '{}': Marker '{}' not found. This loop will not be exported".format(
+ action.name, loop.loop_name, loop.loop_start
+ ),
+ indent=2,
+ )
if end is None:
- exporter.report.warn("Animation '{}' Loop '{}': Marker '{}' not found. This loop will not be exported".format(
- action.name, loop.loop_name, loop.loop_end), indent=2)
+ exporter.report.warn(
+ "Animation '{}' Loop '{}': Marker '{}' not found. This loop will not be exported".format(
+ action.name, loop.loop_name, loop.loop_end
+ ),
+ indent=2,
+ )
if start is None or end is None:
continue
- atcanim.setLoop(loop.loop_name, _convert_frame_time(start.frame), _convert_frame_time(end.frame))
+ atcanim.setLoop(
+ loop.loop_name,
+ _convert_frame_time(start.frame),
+ _convert_frame_time(end.frame),
+ )
diff --git a/korman/properties/modifiers/avatar.py b/korman/properties/modifiers/avatar.py
index 00455e6..f5cb029 100644
--- a/korman/properties/modifiers/avatar.py
+++ b/korman/properties/modifiers/avatar.py
@@ -32,21 +32,32 @@ class PlasmaLadderModifier(PlasmaModifierProperties):
bl_description = "Climbable Ladder"
bl_icon = "COLLAPSEMENU"
- is_enabled = BoolProperty(name="Enabled",
- description="Ladder enabled by default at Age start",
- default=True)
- direction = EnumProperty(name="Direction",
- description="Direction of climb",
- items=[("UP", "Up", "The avatar will mount the ladder and climb upward"),
- ("DOWN", "Down", "The avatar will mount the ladder and climb downward"),],
- default="DOWN")
- num_loops = IntProperty(name="Loops",
- description="How many full animation loops after the first to play before dismounting",
- min=0, default=4)
- facing_object = PointerProperty(name="Facing Object",
- description="Target object the avatar must be facing through this region to trigger climb (optional)",
- type=bpy.types.Object,
- poll=idprops.poll_mesh_objects)
+ is_enabled = BoolProperty(
+ name="Enabled",
+ description="Ladder enabled by default at Age start",
+ default=True,
+ )
+ direction = EnumProperty(
+ name="Direction",
+ description="Direction of climb",
+ items=[
+ ("UP", "Up", "The avatar will mount the ladder and climb upward"),
+ ("DOWN", "Down", "The avatar will mount the ladder and climb downward"),
+ ],
+ default="DOWN",
+ )
+ num_loops = IntProperty(
+ name="Loops",
+ description="How many full animation loops after the first to play before dismounting",
+ min=0,
+ default=4,
+ )
+ facing_object = PointerProperty(
+ name="Facing Object",
+ description="Target object the avatar must be facing through this region to trigger climb (optional)",
+ type=bpy.types.Object,
+ poll=idprops.poll_mesh_objects,
+ )
def export(self, exporter, bo, so):
# Create the ladder modifier
@@ -61,7 +72,10 @@ class PlasmaLadderModifier(PlasmaModifierProperties):
# engine-defined (45 degree) tolerance
if self.facing_object is not None:
# Use object if one has been selected
- ladderVec = self.facing_object.matrix_world.translation - bo.matrix_world.translation
+ ladderVec = (
+ self.facing_object.matrix_world.translation
+ - bo.matrix_world.translation
+ )
else:
# Make our own artificial target -1.0 units back on the local Y axis.
ladderVec = mathutils.Vector((0, -1, 0)) * bo.matrix_world.inverted()
@@ -69,48 +83,75 @@ class PlasmaLadderModifier(PlasmaModifierProperties):
mod.ladderView.normalize()
# Generate the detector's physical bounds
- bounds = "hull" if not bo.plasma_modifiers.collision.enabled else bo.plasma_modifiers.collision.bounds
- exporter.physics.generate_physical(bo, so, bounds=bounds, member_group="kGroupDetector",
- report_groups=["kGroupAvatar"], properties=["kPinned"])
+ bounds = (
+ "hull"
+ if not bo.plasma_modifiers.collision.enabled
+ else bo.plasma_modifiers.collision.bounds
+ )
+ exporter.physics.generate_physical(
+ bo,
+ so,
+ bounds=bounds,
+ member_group="kGroupDetector",
+ report_groups=["kGroupAvatar"],
+ properties=["kPinned"],
+ )
@property
def requires_actor(self):
return True
-sitting_approach_flags = [("kApproachFront", "Front", "Approach from the font"),
- ("kApproachLeft", "Left", "Approach from the left"),
- ("kApproachRight", "Right", "Approach from the right"),
- ("kApproachRear", "Rear", "Approach from the rear guard")]
+sitting_approach_flags = [
+ ("kApproachFront", "Front", "Approach from the font"),
+ ("kApproachLeft", "Left", "Approach from the left"),
+ ("kApproachRight", "Right", "Approach from the right"),
+ ("kApproachRear", "Rear", "Approach from the rear guard"),
+]
-class PlasmaSittingBehavior(idprops.IDPropObjectMixin, PlasmaModifierProperties, PlasmaModifierLogicWiz):
+
+class PlasmaSittingBehavior(
+ idprops.IDPropObjectMixin, PlasmaModifierProperties, PlasmaModifierLogicWiz
+):
pl_id = "sittingmod"
bl_category = "Avatar"
bl_label = "Sitting Behavior"
bl_description = "Avatar sitting position"
- approach = EnumProperty(name="Approach",
- description="Directions an avatar can approach the seat from",
- items=sitting_approach_flags,
- default={"kApproachFront", "kApproachLeft", "kApproachRight"},
- options={"ENUM_FLAG"})
-
- clickable_object = PointerProperty(name="Clickable",
- description="Object that defines the clickable area",
- type=bpy.types.Object,
- poll=idprops.poll_mesh_objects)
- region_object = PointerProperty(name="Region",
- description="Object that defines the region mesh",
- type=bpy.types.Object,
- poll=idprops.poll_mesh_objects)
-
- facing_enabled = BoolProperty(name="Avatar Facing",
- description="The avatar must be facing the clickable's Y-axis",
- default=True)
- facing_degrees = IntProperty(name="Tolerance",
- description="How far away we will tolerate the avatar facing the clickable",
- min=-180, max=180, default=45)
+ approach = EnumProperty(
+ name="Approach",
+ description="Directions an avatar can approach the seat from",
+ items=sitting_approach_flags,
+ default={"kApproachFront", "kApproachLeft", "kApproachRight"},
+ options={"ENUM_FLAG"},
+ )
+
+ clickable_object = PointerProperty(
+ name="Clickable",
+ description="Object that defines the clickable area",
+ type=bpy.types.Object,
+ poll=idprops.poll_mesh_objects,
+ )
+ region_object = PointerProperty(
+ name="Region",
+ description="Object that defines the region mesh",
+ type=bpy.types.Object,
+ poll=idprops.poll_mesh_objects,
+ )
+
+ facing_enabled = BoolProperty(
+ name="Avatar Facing",
+ description="The avatar must be facing the clickable's Y-axis",
+ default=True,
+ )
+ facing_degrees = IntProperty(
+ name="Tolerance",
+ description="How far away we will tolerate the avatar facing the clickable",
+ min=-180,
+ max=180,
+ default=45,
+ )
def harvest_actors(self):
if self.facing_enabled:
@@ -153,8 +194,7 @@ class PlasmaSittingBehavior(idprops.IDPropObjectMixin, PlasmaModifierProperties,
@classmethod
def _idprop_mapping(cls):
- return {"clickable_object": "clickable_obj",
- "region_object": "region_obj"}
+ return {"clickable_object": "clickable_obj", "region_object": "region_obj"}
@property
def key_name(self):
@@ -168,4 +208,8 @@ class PlasmaSittingBehavior(idprops.IDPropObjectMixin, PlasmaModifierProperties,
def sanity_check(self):
# The user absolutely MUST specify a clickable or this won't export worth crap.
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
+ )
+ )
diff --git a/korman/properties/modifiers/base.py b/korman/properties/modifiers/base.py
index 1e4827b..a3ae44b 100644
--- a/korman/properties/modifiers/base.py
+++ b/korman/properties/modifiers/base.py
@@ -19,6 +19,7 @@ from bpy.props import *
import abc
from typing import Any, Dict, Generator, Optional
+
class PlasmaModifierProperties(bpy.types.PropertyGroup):
@property
def copy_material(self):
@@ -52,8 +53,8 @@ class PlasmaModifierProperties(bpy.types.PropertyGroup):
def export(self, exporter, bo, so):
"""This is the main phase of the modifier export where most, if not all, PRP objects should
- be generated. No new Blender objects should be created unless their lifespan is constrained
- to the duration of this method.
+ be generated. No new Blender objects should be created unless their lifespan is constrained
+ to the duration of this method.
"""
pass
@@ -86,7 +87,7 @@ class PlasmaModifierProperties(bpy.types.PropertyGroup):
@property
def no_span_sort(self):
"""Indicates that the geometry's Spans should never be sorted with those from other
- Drawables that will render in the same pass"""
+ Drawables that will render in the same pass"""
return False
# This is temporarily commented out to prevent MRO failure. Revisit in Python 3.7
@@ -110,24 +111,35 @@ class PlasmaModifierProperties(bpy.types.PropertyGroup):
# you see... So, we'll store our definitions in a dict and make those properties on each subclass
# at runtime. What joy. Python FTW. See register() in __init__.py
_subprops = {
- "display_order": (IntProperty, {"name": "INTERNAL: Display Ordering",
- "description": "Position in the list of buttons",
- "default": -1,
- "options": {"HIDDEN"}}),
- "show_expanded": (BoolProperty, {"name": "INTERNAL: Actually draw the modifier",
- "default": True,
- "options": {"HIDDEN"}}),
- "current_version": (IntProperty, {"name": "INTERNAL: Modifier version",
- "default": 1,
- "options": {"HIDDEN"}}),
+ "display_order": (
+ IntProperty,
+ {
+ "name": "INTERNAL: Display Ordering",
+ "description": "Position in the list of buttons",
+ "default": -1,
+ "options": {"HIDDEN"},
+ },
+ ),
+ "show_expanded": (
+ BoolProperty,
+ {
+ "name": "INTERNAL: Actually draw the modifier",
+ "default": True,
+ "options": {"HIDDEN"},
+ },
+ ),
+ "current_version": (
+ IntProperty,
+ {"name": "INTERNAL: Modifier version", "default": 1, "options": {"HIDDEN"}},
+ ),
}
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."""
+ 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")
@@ -140,7 +152,9 @@ class PlasmaModifierLogicWiz:
else:
return tree
- def _create_python_file_node(self, tree, filename: str, attributes: Dict[str, Any]) -> bpy.types.Node:
+ def _create_python_file_node(
+ self, tree, filename: str, attributes: Dict[str, Any]
+ ) -> bpy.types.Node:
pfm_node = tree.nodes.new("PlasmaPythonFileNode")
with pfm_node.NoUpdate():
pfm_node.filename = filename
@@ -152,19 +166,36 @@ class PlasmaModifierLogicWiz:
pfm_node.update()
return pfm_node
- def _create_python_attribute(self, pfm_node, attribute_name: str, attribute_type: Optional[str] = None, **kwargs):
+ 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."""
+ 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`"
+ 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(
+ 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
)
@@ -180,7 +211,7 @@ class PlasmaModifierLogicWiz:
def pre_export(self, exporter, bo):
"""Default implementation of the pre_export phase for logic wizards that simply triggers
- the logic nodes to be created and for their export to be scheduled."""
+ the logic nodes to be created and for their export to be scheduled."""
yield self.convert_logic(bo)
@@ -213,10 +244,13 @@ def _restore_properties(dummy):
# Unregistered propertes are a sequence of (property function,
# property keyword arguments). Interesting design decision :)
prop_cb, prop_kwargs = getattr(mod_cls, prop_name)
- del prop_kwargs["attr"] # Prevents proper registration
+ del prop_kwargs["attr"] # Prevents proper registration
setattr(mod_cls, prop_name, prop_cb(**prop_kwargs))
+
+
bpy.app.handlers.load_pre.append(_restore_properties)
+
@bpy.app.handlers.persistent
def _upgrade_modifiers(dummy):
# First, run all the upgrades
@@ -231,4 +265,6 @@ def _upgrade_modifiers(dummy):
for mod_cls in PlasmaModifierUpgradable.__subclasses__():
for prop in mod_cls.deprecated_properties:
RemoveProperty(mod_cls, attr=prop)
+
+
bpy.app.handlers.load_post.append(_upgrade_modifiers)
diff --git a/korman/properties/modifiers/gui.py b/korman/properties/modifiers/gui.py
index f69996a..4cad13e 100644
--- a/korman/properties/modifiers/gui.py
+++ b/korman/properties/modifiers/gui.py
@@ -25,65 +25,75 @@ from PyHSPlasma import *
from ...addon_prefs import game_versions
from ...exporter import ExportError, utils
-from .base import PlasmaModifierProperties, PlasmaModifierLogicWiz, PlasmaModifierUpgradable
+from .base import (
+ PlasmaModifierProperties,
+ PlasmaModifierLogicWiz,
+ PlasmaModifierUpgradable,
+)
from ... import idprops
journal_pfms = {
- pvPots : {
+ 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" },
- )
+ {"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 : {
+ 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" },
- )
+ {"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),
- ("Finnish", "Suomi", "Finnish", 2),
- ("French", "Français", "French", 3),
- ("German", "Deutsch", "German", 4),
- ("Hungarian", "Magyar", "Hungarian", 5),
- ("Italian", "Italiano ", "Italian", 6),
- # Blender 2.79b can't render 日本語 by default
- ("Japanese", "Nihongo", "Japanese", 7),
- ("Norwegian", "Norsk", "Norwegian", 8),
- ("Polish", "Polski", "Polish", 9),
- ("Romanian", "Română", "Romanian", 10),
- ("Russian", "Pyccĸий", "Russian", 11),
- ("Spanish", "Español", "Spanish", 12),
- ("Swedish", "Svenska", "Swedish", 13)]
+_languages = [
+ ("Dutch", "Nederlands", "Dutch", 0),
+ ("English", "English", "", 1),
+ ("Finnish", "Suomi", "Finnish", 2),
+ ("French", "Français", "French", 3),
+ ("German", "Deutsch", "German", 4),
+ ("Hungarian", "Magyar", "Hungarian", 5),
+ ("Italian", "Italiano ", "Italian", 6),
+ # Blender 2.79b can't render 日本語 by default
+ ("Japanese", "Nihongo", "Japanese", 7),
+ ("Norwegian", "Norsk", "Norwegian", 8),
+ ("Polish", "Polski", "Polish", 9),
+ ("Romanian", "Română", "Romanian", 10),
+ ("Russian", "Pyccĸий", "Russian", 11),
+ ("Spanish", "Español", "Spanish", 12),
+ ("Swedish", "Svenska", "Swedish", 13),
+]
languages = sorted(_languages, key=lambda x: x[1])
_DEFAULT_LANGUAGE_NAME = "English"
_DEFAULT_LANGUAGE_ID = 1
class ImageLibraryItem(bpy.types.PropertyGroup):
- image = bpy.props.PointerProperty(name="Image Item",
- description="Image stored for export.",
- type=bpy.types.Image,
- options=set())
- enabled = bpy.props.BoolProperty(name="Enabled",
- description="Specifies whether this image will be stored during export.",
- default=True,
- options=set())
+ image = bpy.props.PointerProperty(
+ name="Image Item",
+ description="Image stored for export.",
+ type=bpy.types.Image,
+ options=set(),
+ )
+ enabled = bpy.props.BoolProperty(
+ name="Enabled",
+ description="Specifies whether this image will be stored during export.",
+ default=True,
+ options=set(),
+ )
class PlasmaImageLibraryModifier(PlasmaModifierProperties):
@@ -99,43 +109,66 @@ class PlasmaImageLibraryModifier(PlasmaModifierProperties):
def export(self, exporter, bo, so):
if self.images:
- ilmod = exporter.mgr.find_create_object(plImageLibMod, so=so, name=self.key_name)
+ ilmod = exporter.mgr.find_create_object(
+ plImageLibMod, so=so, name=self.key_name
+ )
for item in self.images:
if item.image and item.enabled:
- exporter.mesh.material.export_prepared_image(owner=ilmod, image=item.image, allowed_formats={"JPG", "PNG"}, extension="hsm")
+ exporter.mesh.material.export_prepared_image(
+ owner=ilmod,
+ image=item.image,
+ allowed_formats={"JPG", "PNG"},
+ extension="hsm",
+ )
class PlasmaJournalTranslation(bpy.types.PropertyGroup):
def _poll_nonpytext(self, value):
return not value.name.endswith(".py")
- language = EnumProperty(name="Language",
- description="Language of this translation",
- items=languages,
- default=_DEFAULT_LANGUAGE_NAME,
- options=set())
- text_id = PointerProperty(name="Contents",
- description="Text data block containing the text for this language",
- type=bpy.types.Text,
- poll=_poll_nonpytext,
- options=set())
+ language = EnumProperty(
+ name="Language",
+ description="Language of this translation",
+ items=languages,
+ default=_DEFAULT_LANGUAGE_NAME,
+ options=set(),
+ )
+ 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 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)
+ 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)
+ 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))
+ 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()
@@ -153,8 +186,14 @@ class TranslationMixin:
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)
+ 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()
@@ -171,7 +210,9 @@ class TranslationMixin:
raise RuntimeError("TranslationMixin subclass needs a translation getter!")
-class PlasmaJournalBookModifier(PlasmaModifierProperties, PlasmaModifierLogicWiz, TranslationMixin):
+class PlasmaJournalBookModifier(
+ PlasmaModifierProperties, PlasmaModifierLogicWiz, TranslationMixin
+):
pl_id = "journalbookmod"
bl_category = "GUI"
@@ -179,59 +220,94 @@ class PlasmaJournalBookModifier(PlasmaModifierProperties, PlasmaModifierLogicWiz
bl_description = "Journal Book"
bl_icon = "WORDWRAP_ON"
- versions = EnumProperty(name="Export Targets",
- description="Plasma versions for which this journal exports",
- items=game_versions,
- options={"ENUM_FLAG"},
- default={"pvMoul"})
- start_state = EnumProperty(name="Start",
- description="State of journal when activated",
- items=[("OPEN", "Open", "Journal will start opened to first page"),
- ("CLOSED", "Closed", "Journal will start closed showing cover")],
- default="CLOSED")
- book_type = EnumProperty(name="Book Type",
- description="GUI type to be used for the journal",
- items=[("bkBook", "Book", "A journal written on worn, yellowed paper"),
- ("bkNotebook", "Notebook", "A journal written on white, lined paper")],
- default="bkBook")
- book_scale_w = IntProperty(name="Book Width Scale",
- description="Width scale",
- default=100, min=0, max=100,
- subtype="PERCENTAGE")
- book_scale_h = IntProperty(name="Book Height Scale",
- description="Height scale",
- default=100, min=0, max=100,
- subtype="PERCENTAGE")
- clickable_region = PointerProperty(name="Region",
- description="Region inside which the avatar must stand to be able to open the journal (optional)",
- type=bpy.types.Object,
- poll=idprops.poll_mesh_objects)
-
- journal_translations = CollectionProperty(name="Journal Translations",
- type=PlasmaJournalTranslation,
- options=set())
+ versions = EnumProperty(
+ name="Export Targets",
+ description="Plasma versions for which this journal exports",
+ items=game_versions,
+ options={"ENUM_FLAG"},
+ default={"pvMoul"},
+ )
+ start_state = EnumProperty(
+ name="Start",
+ description="State of journal when activated",
+ items=[
+ ("OPEN", "Open", "Journal will start opened to first page"),
+ ("CLOSED", "Closed", "Journal will start closed showing cover"),
+ ],
+ default="CLOSED",
+ )
+ book_type = EnumProperty(
+ name="Book Type",
+ description="GUI type to be used for the journal",
+ items=[
+ ("bkBook", "Book", "A journal written on worn, yellowed paper"),
+ ("bkNotebook", "Notebook", "A journal written on white, lined paper"),
+ ],
+ default="bkBook",
+ )
+ book_scale_w = IntProperty(
+ name="Book Width Scale",
+ description="Width scale",
+ default=100,
+ min=0,
+ max=100,
+ subtype="PERCENTAGE",
+ )
+ book_scale_h = IntProperty(
+ name="Book Height Scale",
+ description="Height scale",
+ default=100,
+ min=0,
+ max=100,
+ subtype="PERCENTAGE",
+ )
+ clickable_region = PointerProperty(
+ name="Region",
+ description="Region inside which the avatar must stand to be able to open the journal (optional)",
+ type=bpy.types.Object,
+ poll=idprops.poll_mesh_objects,
+ )
+
+ journal_translations = CollectionProperty(
+ name="Journal 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())
+ active_translation = EnumProperty(
+ name="Language",
+ description="Language of this translation",
+ items=languages,
+ get=TranslationMixin._get_translation,
+ set=TranslationMixin._set_translation,
+ options=set(),
+ )
def pre_export(self, exporter, bo):
our_versions = (globals()[j] for j in self.versions)
version = exporter.mgr.getVer()
if version not in our_versions:
# We aren't needed here
- exporter.report.port("Object '{}' has a JournalMod not enabled for export to the selected engine. Skipping.",
- bo.name, version, indent=2)
+ exporter.report.port(
+ "Object '{}' has a JournalMod not enabled for export to the selected engine. Skipping.",
+ bo.name,
+ version,
+ indent=2,
+ )
return
if self.clickable_region is None:
- with utils.bmesh_object("{}_Journal_ClkRgn".format(self.key_name)) as (rgn_obj, bm):
+ with utils.bmesh_object("{}_Journal_ClkRgn".format(self.key_name)) as (
+ rgn_obj,
+ bm,
+ ):
bmesh.ops.create_cube(bm, size=(6.0))
- bmesh.ops.transform(bm, matrix=mathutils.Matrix.Translation(bo.matrix_world.translation - rgn_obj.matrix_world.translation),
- space=rgn_obj.matrix_world, verts=bm.verts)
+ bmesh.ops.transform(
+ bm,
+ matrix=mathutils.Matrix.Translation(
+ bo.matrix_world.translation - rgn_obj.matrix_world.translation
+ ),
+ space=rgn_obj.matrix_world,
+ verts=bm.verts,
+ )
rgn_obj.plasma_object.enabled = True
rgn_obj.hide_render = True
yield rgn_obj
@@ -240,18 +316,24 @@ class PlasmaJournalBookModifier(PlasmaModifierProperties, PlasmaModifierLogicWiz
rgn_obj = self.clickable_region
# Generate the logic nodes
- yield self.convert_logic(bo, age_name=exporter.age_name, rgn_obj=rgn_obj, version=version)
+ yield self.convert_logic(
+ bo, age_name=exporter.age_name, rgn_obj=rgn_obj, version=version
+ )
def logicwiz(self, bo, tree, age_name, rgn_obj, 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"])
+ journalnode = self._create_python_file_node(
+ tree, journal_pfm["filename"], journal_pfm["attribs"]
+ )
if version <= pvPots:
self._create_pots_nodes(bo, tree.nodes, journalnode, age_name, rgn_obj)
else:
self._create_moul_nodes(bo, tree.nodes, journalnode, age_name, rgn_obj)
- def _create_pots_nodes(self, clickable_object, nodes, journalnode, age_name, rgn_obj):
+ def _create_pots_nodes(
+ self, clickable_object, nodes, journalnode, age_name, rgn_obj
+ ):
clickable_region = nodes.new("PlasmaClickableRegionNode")
clickable_region.region_object = rgn_obj
@@ -281,7 +363,9 @@ class PlasmaJournalBookModifier(PlasmaModifierProperties, PlasmaModifierLogicWiz
height.link_output(journalnode, "pfm", "BookHeight")
height.value_float = self.book_scale_h / 100.0
- def _create_moul_nodes(self, clickable_object, nodes, journalnode, age_name, rgn_obj):
+ def _create_moul_nodes(
+ self, clickable_object, nodes, journalnode, age_name, rgn_obj
+ ):
clickable_region = nodes.new("PlasmaClickableRegionNode")
clickable_region.region_object = rgn_obj
@@ -309,7 +393,9 @@ class PlasmaJournalBookModifier(PlasmaModifierProperties, PlasmaModifierLogicWiz
locpath = nodes.new("PlasmaAttribStringNode")
locpath.link_output(journalnode, "pfm", "LocPath")
- locpath.value = "{}.{}.{}".format(age_name, self.localization_set, self.key_name)
+ locpath.value = "{}.{}.{}".format(
+ age_name, self.localization_set, self.key_name
+ )
guitype = nodes.new("PlasmaAttribStringNode")
guitype.link_output(journalnode, "pfm", "GUIType")
@@ -331,38 +417,38 @@ class PlasmaJournalBookModifier(PlasmaModifierProperties, PlasmaModifierLogicWiz
linking_pfms = {
- pvPots : {
+ 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" },
- )
+ {"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 : {
+ 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" },
- )
+ {"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"},
+ ),
},
}
@@ -375,81 +461,138 @@ class PlasmaLinkingBookModifier(PlasmaModifierProperties, PlasmaModifierLogicWiz
bl_description = "Linking Book"
bl_icon = "FILE_IMAGE"
- versions = EnumProperty(name="Export Targets",
- description="Plasma versions for which this journal exports",
- items=game_versions,
- options={"ENUM_FLAG"},
- default={"pvMoul"})
+ versions = EnumProperty(
+ name="Export Targets",
+ description="Plasma versions for which this journal exports",
+ items=game_versions,
+ options={"ENUM_FLAG"},
+ default={"pvMoul"},
+ )
# Link Info
- link_type = EnumProperty(name="Linking Type",
- description="The type of Link this Linking Book will use",
- items=[
- ("kBasicLink", "Public Link", "Links to a public instance of the specified Age"),
- ("kOriginalBook", "Private Link", "Links to a new or existing private instance of the specified Age"),
- ("kSubAgeBook", "Closed Loop Link", "Links between instances of the specifed Age and the current one"),],
- options=set(),
- default="kOriginalBook")
- age_name = StringProperty(name="Age Name",
- description="Filename of the Age to link to (e.g. Garrison)",)
- age_instance = StringProperty(name="Age Instance",
- description="Friendly name of the Age to link to (e.g. Gahreesen)",)
- age_uuid = StringProperty(name="Age GUID",
- description="GUID for a specific instance (used with public Ages)",)
- age_parent = StringProperty(name="Parent Age",
- description="Name of the Child Age's parent Age",)
-
- spawn_title = StringProperty(name="Spawn Title",
- description="Title of the Spawn Point",
- default="Default")
- spawn_point = StringProperty(name="Spawn Point",
- description="Name of the Spawn Point to arrive at after the link",
- default="LinkInPointDefault")
- anim_type = EnumProperty(name="Link Animation",
- description="Type of Linking Animation to use",
- items=[("LinkOut", "Standing", "The avatar steps up to the book and places their hand on the panel"),
- ("FishBookLinkOut", "Kneeling", "The avatar kneels in front of the book and places their hand on the panel"),],
- default="LinkOut",
- options=set())
- link_destination = StringProperty(name="Linking Panel Name",
- description="Optional: Name of Linking Panel to use for this link-in point if it differs from the Age Name",)
+ link_type = EnumProperty(
+ name="Linking Type",
+ description="The type of Link this Linking Book will use",
+ items=[
+ (
+ "kBasicLink",
+ "Public Link",
+ "Links to a public instance of the specified Age",
+ ),
+ (
+ "kOriginalBook",
+ "Private Link",
+ "Links to a new or existing private instance of the specified Age",
+ ),
+ (
+ "kSubAgeBook",
+ "Closed Loop Link",
+ "Links between instances of the specifed Age and the current one",
+ ),
+ ],
+ options=set(),
+ default="kOriginalBook",
+ )
+ age_name = StringProperty(
+ name="Age Name",
+ description="Filename of the Age to link to (e.g. Garrison)",
+ )
+ age_instance = StringProperty(
+ name="Age Instance",
+ description="Friendly name of the Age to link to (e.g. Gahreesen)",
+ )
+ age_uuid = StringProperty(
+ name="Age GUID",
+ description="GUID for a specific instance (used with public Ages)",
+ )
+ age_parent = StringProperty(
+ name="Parent Age",
+ description="Name of the Child Age's parent Age",
+ )
+
+ spawn_title = StringProperty(
+ name="Spawn Title", description="Title of the Spawn Point", default="Default"
+ )
+ spawn_point = StringProperty(
+ name="Spawn Point",
+ description="Name of the Spawn Point to arrive at after the link",
+ default="LinkInPointDefault",
+ )
+ anim_type = EnumProperty(
+ name="Link Animation",
+ description="Type of Linking Animation to use",
+ items=[
+ (
+ "LinkOut",
+ "Standing",
+ "The avatar steps up to the book and places their hand on the panel",
+ ),
+ (
+ "FishBookLinkOut",
+ "Kneeling",
+ "The avatar kneels in front of the book and places their hand on the panel",
+ ),
+ ],
+ default="LinkOut",
+ options=set(),
+ )
+ link_destination = StringProperty(
+ name="Linking Panel Name",
+ description="Optional: Name of Linking Panel to use for this link-in point if it differs from the Age Name",
+ )
# Interactables
- seek_point = PointerProperty(name="Seek Point",
- description="The point the avatar will seek to before opening the Linking Book GUI",
- type=bpy.types.Object,
- poll=idprops.poll_empty_objects)
- clickable_region = PointerProperty(name="Clickable Region",
- description="The region in which the avatar must be standing before they can click on the Linking Book",
- type=bpy.types.Object,
- poll=idprops.poll_mesh_objects)
- clickable = PointerProperty(name="Clickable",
- description="The object the avatar will click on to activate the Linking Book GUI",
- type=bpy.types.Object,
- poll=idprops.poll_mesh_objects)
+ seek_point = PointerProperty(
+ name="Seek Point",
+ description="The point the avatar will seek to before opening the Linking Book GUI",
+ type=bpy.types.Object,
+ poll=idprops.poll_empty_objects,
+ )
+ clickable_region = PointerProperty(
+ name="Clickable Region",
+ description="The region in which the avatar must be standing before they can click on the Linking Book",
+ type=bpy.types.Object,
+ poll=idprops.poll_mesh_objects,
+ )
+ clickable = PointerProperty(
+ name="Clickable",
+ description="The object the avatar will click on to activate the Linking Book GUI",
+ type=bpy.types.Object,
+ poll=idprops.poll_mesh_objects,
+ )
# -- Path of the Shell options --
# Popup Appearance
- book_cover_image = PointerProperty(name="Book Cover",
- description="Image to use for the Linking Book's cover (Optional: book starts open if left blank)",
- type=bpy.types.Image,
- options=set())
- link_panel_image = PointerProperty(name="Linking Panel",
- description="Image to use for the Linking Panel",
- type=bpy.types.Image,
- options=set())
- stamp_image = PointerProperty(name="Stamp Image",
- description="Image to use for the stamp on the page opposite the book's linking panel, if any",
- type=bpy.types.Image,
- options=set())
- stamp_x = IntProperty(name="Stamp Position X",
- description="X position of Stamp",
- default=140,
- subtype="UNSIGNED")
- stamp_y = IntProperty(name="Stamp Position Y",
- description="Y position of Stamp",
- default=255,
- subtype="UNSIGNED")
+ book_cover_image = PointerProperty(
+ name="Book Cover",
+ description="Image to use for the Linking Book's cover (Optional: book starts open if left blank)",
+ type=bpy.types.Image,
+ options=set(),
+ )
+ link_panel_image = PointerProperty(
+ name="Linking Panel",
+ description="Image to use for the Linking Panel",
+ type=bpy.types.Image,
+ options=set(),
+ )
+ stamp_image = PointerProperty(
+ name="Stamp Image",
+ description="Image to use for the stamp on the page opposite the book's linking panel, if any",
+ type=bpy.types.Image,
+ options=set(),
+ )
+ stamp_x = IntProperty(
+ name="Stamp Position X",
+ description="X position of Stamp",
+ default=140,
+ subtype="UNSIGNED",
+ )
+ stamp_y = IntProperty(
+ name="Stamp Position Y",
+ description="Y position of Stamp",
+ default=255,
+ subtype="UNSIGNED",
+ )
def _check_version(self, *args) -> bool:
our_versions = frozenset((globals()[j] for j in self.versions))
@@ -458,34 +601,63 @@ class PlasmaLinkingBookModifier(PlasmaModifierProperties, PlasmaModifierLogicWiz
def pre_export(self, exporter, bo):
if not self._check_version(exporter.mgr.getVer()):
# We aren't needed here
- exporter.report.port("Object '{}' has a LinkingBookMod not enabled for export to the selected engine. Skipping.",
- self.id_data.name, indent=2)
+ exporter.report.port(
+ "Object '{}' has a LinkingBookMod not enabled for export to the selected engine. Skipping.",
+ self.id_data.name,
+ indent=2,
+ )
return
# Auto-generate a six-foot cube region around the clickable if none was provided.
if self.clickable_region is None:
- with utils.bmesh_object("{}_LinkingBook_ClkRgn".format(self.key_name)) as (rgn_obj, bm):
+ with utils.bmesh_object("{}_LinkingBook_ClkRgn".format(self.key_name)) as (
+ rgn_obj,
+ bm,
+ ):
bmesh.ops.create_cube(bm, size=(6.0))
- rgn_offset = mathutils.Matrix.Translation(self.clickable.matrix_world.translation - rgn_obj.matrix_world.translation)
- bmesh.ops.transform(bm, matrix=rgn_offset, space=rgn_obj.matrix_world, verts=bm.verts)
+ rgn_offset = mathutils.Matrix.Translation(
+ self.clickable.matrix_world.translation
+ - rgn_obj.matrix_world.translation
+ )
+ bmesh.ops.transform(
+ bm, matrix=rgn_offset, space=rgn_obj.matrix_world, verts=bm.verts
+ )
rgn_obj.plasma_object.enabled = True
rgn_obj.hide_render = True
yield rgn_obj
else:
rgn_obj = self.clickable_region
- yield self.convert_logic(bo, age_name=exporter.age_name, version=exporter.mgr.getVer(), region=rgn_obj)
+ yield self.convert_logic(
+ bo,
+ age_name=exporter.age_name,
+ version=exporter.mgr.getVer(),
+ region=rgn_obj,
+ )
def export(self, exporter, bo, so):
if self._check_version(pvPrime, pvPots):
# Create ImageLibraryMod in which to store the Cover, Linking Panel, and Stamp images
- ilmod = exporter.mgr.find_create_object(plImageLibMod, so=so, name=self.key_name)
-
- user_images = (i for i in (self.book_cover_image, self.link_panel_image, self.stamp_image)
- if i is not None)
+ ilmod = exporter.mgr.find_create_object(
+ plImageLibMod, so=so, name=self.key_name
+ )
+
+ user_images = (
+ i
+ for i in (
+ self.book_cover_image,
+ self.link_panel_image,
+ self.stamp_image,
+ )
+ if i is not None
+ )
for image in user_images:
- exporter.mesh.material.export_prepared_image(owner=ilmod, image=image,
- allowed_formats={"JPG", "PNG"}, extension="hsm")
+ exporter.mesh.material.export_prepared_image(
+ owner=ilmod,
+ image=image,
+ allowed_formats={"JPG", "PNG"},
+ extension="hsm",
+ )
def harvest_actors(self):
if self.seek_point is not None:
@@ -494,13 +666,17 @@ class PlasmaLinkingBookModifier(PlasmaModifierProperties, PlasmaModifierLogicWiz
def logicwiz(self, bo, tree, age_name, version, region):
# 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"])
+ linkingnode = self._create_python_file_node(
+ tree, linking_pfm["filename"], linking_pfm["attribs"]
+ )
if version <= pvPots:
self._create_pots_nodes(bo, tree.nodes, linkingnode, age_name, region)
else:
self._create_moul_nodes(bo, tree.nodes, linkingnode, age_name, region)
- def _create_pots_nodes(self, clickable_object, nodes, linkingnode, age_name, clk_region):
+ def _create_pots_nodes(
+ self, clickable_object, nodes, linkingnode, age_name, clk_region
+ ):
# Clickable
clickable_region = nodes.new("PlasmaClickableRegionNode")
clickable_region.region_object = clk_region
@@ -524,19 +700,25 @@ class PlasmaLinkingBookModifier(PlasmaModifierProperties, PlasmaModifierLogicWiz
# Book Cover Image
if self.book_cover_image:
book_cover_name = nodes.new("PlasmaAttribStringNode")
- book_cover_name.value = str(Path(self.book_cover_image.name).with_suffix(".hsm"))
+ book_cover_name.value = str(
+ Path(self.book_cover_image.name).with_suffix(".hsm")
+ )
book_cover_name.link_output(linkingnode, "pfm", "bookCover")
# Linking Panel Image
if self.link_panel_image:
linking_panel_name = nodes.new("PlasmaAttribStringNode")
- linking_panel_name.value = str(Path(self.link_panel_image.name).with_suffix(".hsm"))
+ linking_panel_name.value = str(
+ Path(self.link_panel_image.name).with_suffix(".hsm")
+ )
linking_panel_name.link_output(linkingnode, "pfm", "linkPanel")
# Stamp Image
if self.stamp_image:
stamp_texture_name = nodes.new("PlasmaAttribStringNode")
- stamp_texture_name.value = str(Path(self.stamp_image.name).with_suffix(".hsm"))
+ stamp_texture_name.value = str(
+ Path(self.stamp_image.name).with_suffix(".hsm")
+ )
stamp_texture_name.link_output(linkingnode, "pfm", "stampTexture")
# Stamp X Position
@@ -578,7 +760,9 @@ class PlasmaLinkingBookModifier(PlasmaModifierProperties, PlasmaModifierLogicWiz
responder.link_output(responder_state, "state_refs", "resp")
responder.link_output(linkingnode, "keyref", "respOneShot")
- def _create_moul_nodes(self, clickable_object, nodes, linkingnode, age_name, clk_region):
+ def _create_moul_nodes(
+ self, clickable_object, nodes, linkingnode, age_name, clk_region
+ ):
# Clickable
clickable_region = nodes.new("PlasmaClickableRegionNode")
clickable_region.region_object = clk_region
@@ -630,11 +814,17 @@ class PlasmaLinkingBookModifier(PlasmaModifierProperties, PlasmaModifierLogicWiz
# Linking Panel Name
linking_panel_name = nodes.new("PlasmaAttribStringNode")
- linking_panel_name.value = self.link_destination if self.link_destination else self.age_name
+ linking_panel_name.value = (
+ self.link_destination if self.link_destination else self.age_name
+ )
linking_panel_name.link_output(linkingnode, "pfm", "TargetAge")
def sanity_check(self):
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:
- raise ExportError("{}: Linking Book modifier requires a seek point!", self.id_data.name)
+ raise ExportError(
+ "{}: Linking Book modifier requires a seek point!", self.id_data.name
+ )
diff --git a/korman/properties/modifiers/logic.py b/korman/properties/modifiers/logic.py
index 2fcec98..9761274 100644
--- a/korman/properties/modifiers/logic.py
+++ b/korman/properties/modifiers/logic.py
@@ -22,15 +22,18 @@ from .base import PlasmaModifierProperties
from ...exporter import ExportError
from ... import idprops
+
class PlasmaVersionedNodeTree(idprops.IDPropMixin, bpy.types.PropertyGroup):
- version = EnumProperty(name="Version",
- description="Plasma versions this node tree exports under",
- items=game_versions,
- options={"ENUM_FLAG"},
- default=set(list(zip(*game_versions))[0]))
- node_tree = PointerProperty(name="Node Tree",
- description="Node Tree to export",
- type=bpy.types.NodeTree)
+ version = EnumProperty(
+ name="Version",
+ description="Plasma versions this node tree exports under",
+ items=game_versions,
+ options={"ENUM_FLAG"},
+ default=set(list(zip(*game_versions))[0]),
+ )
+ node_tree = PointerProperty(
+ name="Node Tree", description="Node Tree to export", type=bpy.types.NodeTree
+ )
@classmethod
def _idprop_mapping(cls):
@@ -57,7 +60,11 @@ class PlasmaAdvancedLogic(PlasmaModifierProperties):
our_versions = [globals()[j] for j in i.version]
if version in our_versions:
if i.node_tree is None:
- raise ExportError("'{}': Advanced Logic is missing a node tree for '{}'".format(bo.name, i.name))
+ raise ExportError(
+ "'{}': Advanced Logic is missing a node tree for '{}'".format(
+ bo.name, i.name
+ )
+ )
# Defer node tree export until all trees are harvested.
exporter.want_node_trees[i.node_tree.name].add((bo, so))
@@ -71,7 +78,9 @@ class PlasmaAdvancedLogic(PlasmaModifierProperties):
@property
def requires_actor(self):
- return any((i.node_tree.requires_actor for i in self.logic_groups if i.node_tree))
+ return any(
+ (i.node_tree.requires_actor for i in self.logic_groups if i.node_tree)
+ )
class PlasmaSpawnPoint(PlasmaModifierProperties):
@@ -96,22 +105,37 @@ class PlasmaMaintainersMarker(PlasmaModifierProperties):
bl_category = "Logic"
bl_label = "Maintainer's Marker"
- bl_description = "Designates an object as the D'ni coordinate origin point of the Age."
+ bl_description = (
+ "Designates an object as the D'ni coordinate origin point of the Age."
+ )
bl_icon = "OUTLINER_DATA_EMPTY"
- calibration = EnumProperty(name="Calibration",
- description="State of repair for the Marker",
- items=[
- ("kBroken", "Broken",
- "A marker which reports scrambled coordinates to the KI."),
- ("kRepaired", "Repaired",
- "A marker which reports blank coordinates to the KI."),
- ("kCalibrated", "Calibrated",
- "A marker which reports accurate coordinates to the KI.")
- ])
+ calibration = EnumProperty(
+ name="Calibration",
+ description="State of repair for the Marker",
+ items=[
+ (
+ "kBroken",
+ "Broken",
+ "A marker which reports scrambled coordinates to the KI.",
+ ),
+ (
+ "kRepaired",
+ "Repaired",
+ "A marker which reports blank coordinates to the KI.",
+ ),
+ (
+ "kCalibrated",
+ "Calibrated",
+ "A marker which reports accurate coordinates to the KI.",
+ ),
+ ],
+ )
def export(self, exporter, bo, so):
- maintmark = exporter.mgr.add_object(pl=plMaintainersMarkerModifier, so=so, name=self.key_name)
+ maintmark = exporter.mgr.add_object(
+ pl=plMaintainersMarkerModifier, so=so, name=self.key_name
+ )
maintmark.calibration = getattr(plMaintainersMarkerModifier, self.calibration)
@property
diff --git a/korman/properties/modifiers/physics.py b/korman/properties/modifiers/physics.py
index bf4c7ff..5c8c5fb 100644
--- a/korman/properties/modifiers/physics.py
+++ b/korman/properties/modifiers/physics.py
@@ -27,7 +27,7 @@ bounds_types = (
("box", "Bounding Box", "Use a perfect bounding box"),
("sphere", "Bounding Sphere", "Use a perfect bounding sphere"),
("hull", "Convex Hull", "Use a convex set encompasing all vertices"),
- ("trimesh", "Triangle Mesh", "Use the exact triangle mesh (SLOW!)")
+ ("trimesh", "Triangle Mesh", "Use the exact triangle mesh (SLOW!)"),
)
# These are the collision sound surface types
@@ -49,12 +49,15 @@ surface_types = (
("kUser3", "User 3", ""),
)
+
def bounds_type_index(key):
return list(zip(*bounds_types))[0].index(key)
+
def bounds_type_str(idx):
return bounds_types[idx][0]
+
def _set_phys_prop(prop, sim, phys, value=True):
"""Sets properties on plGenericPhysical and plSimulationInterface (seeing as how they are duped)"""
sim.setProperty(prop, value)
@@ -69,29 +72,58 @@ class PlasmaCollider(PlasmaModifierProperties):
bl_icon = "MOD_PHYSICS"
bl_description = "Simple physical collider"
- bounds = EnumProperty(name="Bounds Type", description="", items=bounds_types, default="hull")
+ bounds = EnumProperty(
+ name="Bounds Type", description="", items=bounds_types, default="hull"
+ )
- avatar_blocker = BoolProperty(name="Blocks Avatars", description="Object blocks avatars", default=True)
- camera_blocker = BoolProperty(name="Blocks Camera LOS", description="Object blocks camera line-of-sight", default=True)
+ avatar_blocker = BoolProperty(
+ name="Blocks Avatars", description="Object blocks avatars", default=True
+ )
+ camera_blocker = BoolProperty(
+ name="Blocks Camera LOS",
+ description="Object blocks camera line-of-sight",
+ default=True,
+ )
friction = FloatProperty(name="Friction", min=0.0, default=0.5)
- restitution = FloatProperty(name="Restitution", description="Coefficient of collision elasticity", min=0.0, max=1.0)
- terrain = BoolProperty(name="Terrain", description="Object represents the ground", default=False)
-
- dynamic = BoolProperty(name="Dynamic", description="Object can be influenced by other objects (ie is kickable)", default=False)
- mass = FloatProperty(name="Mass", description="Mass of object in pounds", min=0.0, default=1.0)
- start_asleep = BoolProperty(name="Start Asleep", description="Object is not active until influenced by another object", default=False)
-
- proxy_object = PointerProperty(name="Proxy",
- description="Object used as the collision geometry",
- type=bpy.types.Object,
- poll=idprops.poll_mesh_objects)
-
- surface = EnumProperty(name="Surface Type",
- description="Type of surface sound effect to play on collision",
- items=surface_types,
- default="kNone",
- options=set())
+ restitution = FloatProperty(
+ name="Restitution",
+ description="Coefficient of collision elasticity",
+ min=0.0,
+ max=1.0,
+ )
+ terrain = BoolProperty(
+ name="Terrain", description="Object represents the ground", default=False
+ )
+
+ dynamic = BoolProperty(
+ name="Dynamic",
+ description="Object can be influenced by other objects (ie is kickable)",
+ default=False,
+ )
+ mass = FloatProperty(
+ name="Mass", description="Mass of object in pounds", min=0.0, default=1.0
+ )
+ start_asleep = BoolProperty(
+ name="Start Asleep",
+ description="Object is not active until influenced by another object",
+ default=False,
+ )
+
+ proxy_object = PointerProperty(
+ name="Proxy",
+ description="Object used as the collision geometry",
+ type=bpy.types.Object,
+ poll=idprops.poll_mesh_objects,
+ )
+
+ surface = EnumProperty(
+ name="Surface Type",
+ description="Type of surface sound effect to play on collision",
+ items=surface_types,
+ default="kNone",
+ options=set(),
+ )
def export(self, exporter, bo, so):
# All modifier properties are examined by this little stinker...
@@ -110,17 +142,34 @@ class PlasmaSubworld(PlasmaModifierProperties):
bl_description = "Subworld definition"
bl_icon = "WORLD"
- sub_type = EnumProperty(name="Subworld Type",
- description="Specifies the physics strategy to use for this subworld",
- items=[("auto", "Auto", "Korman will decide which physics strategy to use"),
- ("dynamicav", "Dynamic Avatar", "Allows the avatar to affected by dynamic physicals"),
- ("subworld", "Separate World", "Causes all objects to be placed in a separate physics simulation")],
- default="auto",
- options=set())
- gravity = FloatVectorProperty(name="Gravity",
+ sub_type = EnumProperty(
+ name="Subworld Type",
+ description="Specifies the physics strategy to use for this subworld",
+ items=[
+ ("auto", "Auto", "Korman will decide which physics strategy to use"),
+ (
+ "dynamicav",
+ "Dynamic Avatar",
+ "Allows the avatar to affected by dynamic physicals",
+ ),
+ (
+ "subworld",
+ "Separate World",
+ "Causes all objects to be placed in a separate physics simulation",
+ ),
+ ],
+ default="auto",
+ options=set(),
+ )
+ gravity = FloatVectorProperty(
+ name="Gravity",
description="Subworld's gravity defined in feet per second squared",
- size=3, default=(0.0, 0.0, -32.174), precision=3,
- subtype="ACCELERATION", unit="ACCELERATION")
+ size=3,
+ default=(0.0, 0.0, -32.174),
+ precision=3,
+ subtype="ACCELERATION",
+ unit="ACCELERATION",
+ )
def export(self, exporter, bo, so):
if self.is_dedicated_subworld(exporter):
@@ -147,9 +196,22 @@ class PlasmaSubworld(PlasmaModifierProperties):
# plCoordinateInterface::IGetRoot. Not really sure why this happens (nor do I care),
# but we definitely don't want it to happen.
if bo.type != "EMPTY":
- exporter.report.warn("Subworld '{}' is attached to a '{}'--this should be an empty.", bo.name, bo.type, indent=1)
+ exporter.report.warn(
+ "Subworld '{}' is attached to a '{}'--this should be an empty.",
+ bo.name,
+ bo.type,
+ indent=1,
+ )
if so.sim:
if exporter.mgr.getVer() > pvPots:
- exporter.report.port("Subworld '{}' has physics data--this will cause PotS to crash.", bo.name, indent=1)
+ exporter.report.port(
+ "Subworld '{}' has physics data--this will cause PotS to crash.",
+ bo.name,
+ indent=1,
+ )
else:
- raise ExportError("Subworld '{}' cannot have physics data (should be an empty).".format(bo.name))
+ raise ExportError(
+ "Subworld '{}' cannot have physics data (should be an empty).".format(
+ bo.name
+ )
+ )
diff --git a/korman/properties/modifiers/region.py b/korman/properties/modifiers/region.py
index 8c887de..b565b04 100644
--- a/korman/properties/modifiers/region.py
+++ b/korman/properties/modifiers/region.py
@@ -48,17 +48,20 @@ footstep_surface_ids = {
# 18 = swimming (why would you want this?)
}
-footstep_surfaces = [("dirt", "Dirt", "Dirt"),
- ("grass", "Grass", "Grass"),
- ("metal", "Metal", "Metal Catwalk"),
- ("puddle", "Puddle", "Shallow Water"),
- ("rope", "Rope", "Rope Ladder"),
- ("rug", "Rug", "Carpet Rug"),
- ("stone", "Stone", "Stone Tile"),
- ("water", "Water", "Deep Water"),
- ("woodbridge", "Wood Bridge", "Wood Bridge"),
- ("woodfloor", "Wood Floor", "Wood Floor"),
- ("woodladder", "Wood Ladder", "Wood Ladder")]
+footstep_surfaces = [
+ ("dirt", "Dirt", "Dirt"),
+ ("grass", "Grass", "Grass"),
+ ("metal", "Metal", "Metal Catwalk"),
+ ("puddle", "Puddle", "Shallow Water"),
+ ("rope", "Rope", "Rope Ladder"),
+ ("rug", "Rug", "Carpet Rug"),
+ ("stone", "Stone", "Stone Tile"),
+ ("water", "Water", "Deep Water"),
+ ("woodbridge", "Wood Bridge", "Wood Bridge"),
+ ("woodfloor", "Wood Floor", "Wood Floor"),
+ ("woodladder", "Wood Ladder", "Wood Ladder"),
+]
+
class PlasmaCameraRegion(PlasmaModifierProperties):
pl_id = "camera_rgn"
@@ -68,24 +71,40 @@ class PlasmaCameraRegion(PlasmaModifierProperties):
bl_description = "Camera Region"
bl_icon = "CAMERA_DATA"
- camera_type = EnumProperty(name="Camera Type",
- description="What kind of camera should be used?",
- items=[("auto_follow", "Auto Follow Camera", "Automatically generated follow camera"),
- ("manual", "Manual Camera", "User specified camera object")],
- default="manual",
- options=set())
- camera_object = PointerProperty(name="Camera",
- description="Switches to this camera",
- type=bpy.types.Object,
- poll=idprops.poll_camera_objects,
- options=set())
+ camera_type = EnumProperty(
+ name="Camera Type",
+ description="What kind of camera should be used?",
+ items=[
+ (
+ "auto_follow",
+ "Auto Follow Camera",
+ "Automatically generated follow camera",
+ ),
+ ("manual", "Manual Camera", "User specified camera object"),
+ ],
+ default="manual",
+ options=set(),
+ )
+ camera_object = PointerProperty(
+ name="Camera",
+ description="Switches to this camera",
+ type=bpy.types.Object,
+ poll=idprops.poll_camera_objects,
+ options=set(),
+ )
auto_camera = PointerProperty(type=PlasmaCameraProperties, options=set())
def export(self, exporter, bo, so):
if self.camera_type == "manual":
if self.camera_object is None:
- raise ExportError("Camera Modifier '{}' does not specify a valid camera object".format(self.id_data.name))
- camera_so_key = exporter.mgr.find_create_key(plSceneObject, bl=self.camera_object)
+ raise ExportError(
+ "Camera Modifier '{}' does not specify a valid camera object".format(
+ self.id_data.name
+ )
+ )
+ camera_so_key = exporter.mgr.find_create_key(
+ plSceneObject, bl=self.camera_object
+ )
camera_props = self.camera_object.data.plasma_camera.settings
else:
assert self.camera_type[:4] == "auto"
@@ -98,9 +117,13 @@ class PlasmaCameraRegion(PlasmaModifierProperties):
# Setup physical stuff
phys_mod = bo.plasma_modifiers.collision
- exporter.physics.generate_physical(bo, so, member_group="kGroupDetector",
- report_groups=["kGroupAvatar"],
- properties=["kPinned"])
+ exporter.physics.generate_physical(
+ bo,
+ so,
+ member_group="kGroupDetector",
+ report_groups=["kGroupAvatar"],
+ properties=["kPinned"],
+ )
# I don't feel evil enough to make this generate a logic tree...
msg = plCameraMsg()
@@ -116,8 +139,14 @@ class PlasmaCameraRegion(PlasmaModifierProperties):
actors = set()
if self.camera_type == "manual":
if self.camera_object is None:
- raise ExportError("Camera Modifier '{}' does not specify a valid camera object".format(self.id_data.name))
- actors.update(self.camera_object.data.plasma_camera.settings.harvest_actors())
+ raise ExportError(
+ "Camera Modifier '{}' does not specify a valid camera object".format(
+ self.id_data.name
+ )
+ )
+ actors.update(
+ self.camera_object.data.plasma_camera.settings.harvest_actors()
+ )
else:
actors.update(self.auto_camera.harvest_actors())
return actors
@@ -134,14 +163,18 @@ class PlasmaFootstepRegion(PlasmaModifierProperties, PlasmaModifierLogicWiz):
bl_label = "Footstep"
bl_description = "Footstep Region"
- surface = EnumProperty(name="Surface",
- description="What kind of surface are we walking on?",
- items=footstep_surfaces,
- default="stone")
- bounds = EnumProperty(name="Region Bounds",
- description="Physical object's bounds",
- items=bounds_types,
- default="hull")
+ surface = EnumProperty(
+ name="Surface",
+ description="What kind of surface are we walking on?",
+ items=footstep_surfaces,
+ default="stone",
+ )
+ bounds = EnumProperty(
+ name="Region Bounds",
+ description="Physical object's bounds",
+ items=bounds_types,
+ default="hull",
+ )
def logicwiz(self, bo, tree):
nodes = tree.nodes
@@ -178,13 +211,16 @@ class PlasmaPanicLinkRegion(PlasmaModifierProperties):
bl_label = "Panic Link"
bl_description = "Panic Link Region"
- play_anim = BoolProperty(name="Play Animation",
- description="Play the link-out animation when panic linking",
- default=True)
+ play_anim = BoolProperty(
+ name="Play Animation",
+ description="Play the link-out animation when panic linking",
+ default=True,
+ )
def export(self, exporter, bo, so):
- exporter.physics.generate_physical(bo, so, member_group="kGroupDetector",
- report_groups=["kGroupAvatar"])
+ exporter.physics.generate_physical(
+ bo, so, member_group="kGroupDetector", report_groups=["kGroupAvatar"]
+ )
# Finally, the panic link region proper
reg = exporter.mgr.add_object(plPanicLinkRegion, name=self.key_name, so=so)
@@ -207,22 +243,38 @@ class PlasmaSoftVolume(idprops.IDPropMixin, PlasmaModifierProperties):
bl_description = "Soft-Boundary Region"
# Advanced
- use_nodes = BoolProperty(name="Use Nodes",
- description="Make this a node-based Soft Volume",
- default=False)
- node_tree = PointerProperty(name="Node Tree",
- description="Node Tree detailing soft volume logic",
- type=bpy.types.NodeTree)
+ use_nodes = BoolProperty(
+ name="Use Nodes",
+ description="Make this a node-based Soft Volume",
+ default=False,
+ )
+ node_tree = PointerProperty(
+ name="Node Tree",
+ description="Node Tree detailing soft volume logic",
+ type=bpy.types.NodeTree,
+ )
# Basic
- invert = BoolProperty(name="Invert",
- description="Invert the soft region")
- inside_strength = IntProperty(name="Inside", description="Strength inside the region",
- subtype="PERCENTAGE", default=100, min=0, max=100)
- outside_strength = IntProperty(name="Outside", description="Strength outside the region",
- subtype="PERCENTAGE", default=0, min=0, max=100)
- soft_distance = FloatProperty(name="Distance", description="Soft Distance",
- default=0.0, min=0.0, max=500.0)
+ invert = BoolProperty(name="Invert", description="Invert the soft region")
+ inside_strength = IntProperty(
+ name="Inside",
+ description="Strength inside the region",
+ subtype="PERCENTAGE",
+ default=100,
+ min=0,
+ max=100,
+ )
+ outside_strength = IntProperty(
+ name="Outside",
+ description="Strength outside the region",
+ subtype="PERCENTAGE",
+ default=0,
+ min=0,
+ max=100,
+ )
+ soft_distance = FloatProperty(
+ name="Distance", description="Soft Distance", default=0.0, min=0.0, max=500.0
+ )
def _apply_settings(self, sv):
sv.insideStrength = self.inside_strength / 100.0
@@ -237,7 +289,11 @@ class PlasmaSoftVolume(idprops.IDPropMixin, PlasmaModifierProperties):
tree = self.get_node_tree()
output = tree.find_output("PlasmaSoftVolumeOutputNode")
if output is None:
- raise ExportError("SoftVolume '{}' Node Tree '{}' has no output node!".format(self.key_name, tree.name))
+ raise ExportError(
+ "SoftVolume '{}' Node Tree '{}' has no output node!".format(
+ self.key_name, tree.name
+ )
+ )
return output.get_key(exporter, so)
else:
pClass = plSoftVolumeInvert if self.invert else plSoftVolumeSimple
@@ -251,7 +307,11 @@ class PlasmaSoftVolume(idprops.IDPropMixin, PlasmaModifierProperties):
def _export_convex_region(self, exporter, bo, so):
if bo.type != "MESH":
- raise ExportError("SoftVolume '{}': Simple SoftVolumes can only be meshes!".format(bo.name))
+ raise ExportError(
+ "SoftVolume '{}': Simple SoftVolumes can only be meshes!".format(
+ bo.name
+ )
+ )
# Grab the SoftVolume KO
sv = self.get_key(exporter, so).object
@@ -296,7 +356,11 @@ class PlasmaSoftVolume(idprops.IDPropMixin, PlasmaModifierProperties):
def get_node_tree(self):
if self.node_tree is None:
- raise ExportError("SoftVolume '{}' does not specify a valid Node Tree!".format(self.key_name))
+ raise ExportError(
+ "SoftVolume '{}' does not specify a valid Node Tree!".format(
+ self.key_name
+ )
+ )
return self.node_tree
@classmethod
@@ -314,34 +378,47 @@ class PlasmaSubworldRegion(PlasmaModifierProperties):
bl_label = "Subworld Region"
bl_description = "Subworld transition region"
- subworld = PointerProperty(name="Subworld",
- description="Subworld to transition into",
- type=bpy.types.Object,
- poll=idprops.poll_subworld_objects)
- transition = EnumProperty(name="Transition",
- description="When to transition to the new subworld",
- items=[("enter", "On Enter", "Transition when the avatar enters the region"),
- ("exit", "On Exit", "Transition when the avatar exits the region")],
- default="enter",
- options=set())
+ subworld = PointerProperty(
+ name="Subworld",
+ description="Subworld to transition into",
+ type=bpy.types.Object,
+ poll=idprops.poll_subworld_objects,
+ )
+ transition = EnumProperty(
+ name="Transition",
+ description="When to transition to the new subworld",
+ items=[
+ ("enter", "On Enter", "Transition when the avatar enters the region"),
+ ("exit", "On Exit", "Transition when the avatar exits the region"),
+ ],
+ default="enter",
+ options=set(),
+ )
def export(self, exporter, bo, so):
# Due to the fact that our subworld modifier can produce both RidingAnimatedPhysical
- # and [HK|PX]Subworlds depending on the situation, this could get hairy, fast.
+ # and [HK|PX]Subworlds depending on the situation, this could get hairy, fast.
# Start by surveying the lay of the land.
from_sub, to_sub = bo.plasma_object.subworld, self.subworld
from_isded = exporter.physics.is_dedicated_subworld(from_sub)
to_isded = exporter.physics.is_dedicated_subworld(to_sub)
if 1:
+
def get_log_text(bo, isded):
main = "[Main World]" if bo is None else bo.name
sub = "Subworld" if isded or bo is None else "RidingAnimatedPhysical"
return main, sub
+
from_name, from_type = get_log_text(from_sub, from_isded)
to_name, to_type = get_log_text(to_sub, to_isded)
- exporter.report.msg("Transition from '{}' ({}) to '{}' ({})",
- from_name, from_type, to_name, to_type,
- indent=2)
+ exporter.report.msg(
+ "Transition from '{}' ({}) to '{}' ({})",
+ from_name,
+ from_type,
+ to_name,
+ to_type,
+ indent=2,
+ )
# I think the best solution here is to not worry about the excitement mentioned above.
# If we encounter anything truly interesting, we can fix it in CWE more easily IMO because
@@ -353,7 +430,9 @@ class PlasmaSubworldRegion(PlasmaModifierProperties):
region.onExit = self.transition == "exit"
else:
msg = plRideAnimatedPhysMsg()
- msg.BCastFlags |= plMessage.kLocalPropagate | plMessage.kPropagateToModifiers
+ msg.BCastFlags |= (
+ plMessage.kLocalPropagate | plMessage.kPropagateToModifiers
+ )
msg.sender = so.key
msg.entering = to_sub is not None
@@ -362,7 +441,9 @@ class PlasmaSubworldRegion(PlasmaModifierProperties):
# reverts on region exit. We're going for an approach that is backwards compatible
# with subworlds, so our enter/exit regions are separate. Here, enter/exit message
# corresponds with when we should trigger the transition.
- region = exporter.mgr.find_create_object(plRidingAnimatedPhysicalDetector, so=so)
+ region = exporter.mgr.find_create_object(
+ plRidingAnimatedPhysicalDetector, so=so
+ )
if self.transition == "enter":
region.enterMsg = msg
elif self.transition == "exit":
@@ -371,5 +452,6 @@ class PlasmaSubworldRegion(PlasmaModifierProperties):
raise ExportAssertionError()
# Fancy pants region collider type shit
- exporter.physics.generate_physical(bo, so, member_group="kGroupDetector",
- report_groups=["kGroupAvatar"])
+ exporter.physics.generate_physical(
+ bo, so, member_group="kGroupDetector", report_groups=["kGroupAvatar"]
+ )
diff --git a/korman/properties/modifiers/render.py b/korman/properties/modifiers/render.py
index 7ec6493..eedff33 100644
--- a/korman/properties/modifiers/render.py
+++ b/korman/properties/modifiers/render.py
@@ -19,22 +19,27 @@ from bpy.props import *
import functools
from PyHSPlasma import *
-from .base import PlasmaModifierProperties, PlasmaModifierLogicWiz, 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):
- blend_onto = PointerProperty(name="Blend Onto",
- description="Object to render first",
- options=set(),
- type=bpy.types.Object,
- poll=idprops.poll_drawable_objects)
- enabled = BoolProperty(name="Enabled",
- default=True,
- options=set())
+ blend_onto = PointerProperty(
+ name="Blend Onto",
+ description="Object to render first",
+ options=set(),
+ type=bpy.types.Object,
+ poll=idprops.poll_drawable_objects,
+ )
+ enabled = BoolProperty(name="Enabled", default=True, options=set())
class PlasmaBlendMod(PlasmaModifierProperties):
@@ -44,19 +49,39 @@ class PlasmaBlendMod(PlasmaModifierProperties):
bl_label = "Blending"
bl_description = "Advanced Blending Options"
- render_level = EnumProperty(name="Render Pass",
- description="Suggested render pass for this object.",
- items=[("AUTO", "(Auto)", "Let Korman decide when to render this object."),
- ("OPAQUE", "Before Avatar", "Prefer for the object to draw before the avatar."),
- ("FRAMEBUF", "Frame Buffer", "Prefer for the object to draw after the avatar but before other blended objects."),
- ("BLEND", "Blended", "Prefer for the object to draw after most other geometry in the blended pass.")],
- options=set())
- sort_faces = EnumProperty(name="Sort Faces",
- description="",
- items=[("AUTO", "(Auto)", "Let Korman decide if faces should be sorted."),
- ("ALWAYS", "Always", "Force the object's faces to be sorted."),
- ("NEVER", "Never", "Force the object's faces to never be sorted.")],
- options=set())
+ render_level = EnumProperty(
+ name="Render Pass",
+ description="Suggested render pass for this object.",
+ items=[
+ ("AUTO", "(Auto)", "Let Korman decide when to render this object."),
+ (
+ "OPAQUE",
+ "Before Avatar",
+ "Prefer for the object to draw before the avatar.",
+ ),
+ (
+ "FRAMEBUF",
+ "Frame Buffer",
+ "Prefer for the object to draw after the avatar but before other blended objects.",
+ ),
+ (
+ "BLEND",
+ "Blended",
+ "Prefer for the object to draw after most other geometry in the blended pass.",
+ ),
+ ],
+ options=set(),
+ )
+ sort_faces = EnumProperty(
+ name="Sort Faces",
+ description="",
+ items=[
+ ("AUTO", "(Auto)", "Let Korman decide if faces should be sorted."),
+ ("ALWAYS", "Always", "Force the object's faces to be sorted."),
+ ("NEVER", "Never", "Force the object's faces to never be sorted."),
+ ],
+ options=set(),
+ )
dependencies = CollectionProperty(type=PlasmaBlendOntoObject)
active_dependency_index = IntProperty(options={"HIDDEN"})
@@ -110,21 +135,24 @@ class PlasmaBlendMod(PlasmaModifierProperties):
return False
def iter_dependencies(self):
- 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
def sanity_check(self):
if self.has_circular_dependency:
- raise ExportError("'{}': Circular Render Dependency detected!".format(self.name))
+ raise ExportError(
+ "'{}': Circular Render Dependency detected!".format(self.name)
+ )
class PlasmaDecalManagerRef(bpy.types.PropertyGroup):
- enabled = BoolProperty(name="Enabled",
- default=True,
- options=set())
+ enabled = BoolProperty(name="Enabled", default=True, options=set())
- name = StringProperty(name="Decal Name",
- options=set())
+ name = StringProperty(name="Decal Name", options=set())
class PlasmaDecalMod:
@@ -146,25 +174,30 @@ class PlasmaDecalPrintMod(PlasmaDecalMod, PlasmaModifierProperties):
bl_label = "Print Decal"
bl_description = "Prints a decal onto an object"
- decal_type = EnumProperty(name="Decal Type",
- description="Type of decal to print onto another object",
- items=[("DYNAMIC", "Dynamic", "This object prints a decal onto dynamic decal surfaces"),
- ("STATIC", "Static", "This object is a decal itself")],
- options=set())
+ decal_type = EnumProperty(
+ name="Decal Type",
+ description="Type of decal to print onto another object",
+ items=[
+ (
+ "DYNAMIC",
+ "Dynamic",
+ "This object prints a decal onto dynamic decal surfaces",
+ ),
+ ("STATIC", "Static", "This object is a decal itself"),
+ ],
+ options=set(),
+ )
# Dynamic Decals
- length = FloatProperty(name="Length",
- min=0.1, soft_max=30.0, precision=2,
- default=0.45,
- options=set())
- width = FloatProperty(name="Width",
- min=0.1, soft_max=30.0, precision=2,
- default=0.9,
- options=set())
- height = FloatProperty(name="Height",
- min=0.1, soft_max=30.0, precision=2,
- default=1.0,
- options=set())
+ length = FloatProperty(
+ name="Length", min=0.1, soft_max=30.0, precision=2, default=0.45, options=set()
+ )
+ width = FloatProperty(
+ name="Width", min=0.1, soft_max=30.0, precision=2, default=0.9, options=set()
+ )
+ height = FloatProperty(
+ name="Height", min=0.1, soft_max=30.0, precision=2, default=1.0, options=set()
+ )
@property
def copy_material(self):
@@ -172,7 +205,11 @@ class PlasmaDecalPrintMod(PlasmaDecalMod, PlasmaModifierProperties):
def get_key(self, exporter, so):
if self.decal_type == "DYNAMIC":
- pClass = plActivePrintShape if any((i.enabled for i in self.managers)) else plPrintShape
+ pClass = (
+ plActivePrintShape
+ if any((i.enabled for i in self.managers))
+ else plPrintShape
+ )
return exporter.mgr.find_create_key(pClass, so=so)
def export(self, exporter, bo, so):
@@ -190,6 +227,7 @@ class PlasmaDecalPrintMod(PlasmaDecalMod, PlasmaModifierProperties):
f = functools.partial(exporter.decal.export_active_print_shape, print_shape)
self._iter_decals(f)
+
class PlasmaDecalReceiveMod(PlasmaDecalMod, PlasmaModifierProperties):
pl_id = "decal_receive"
@@ -216,55 +254,99 @@ class PlasmaFadeMod(PlasmaModifierProperties):
bl_label = "Opacity Fader"
bl_description = "Fades an object based on distance or line-of-sight"
- fader_type = EnumProperty(name="Fader Type",
- description="Type of opacity fade",
- items=[("DistOpacity", "Distance", "Fade based on distance to object"),
- ("FadeOpacity", "Line-of-Sight", "Fade based on line-of-sight to object"),
- ("SimpleDist", "Simple Distance", "Fade for use as Great Zero Markers")],
- default="SimpleDist")
-
- fade_in_time = FloatProperty(name="Fade In Time",
- description="Number of seconds before the object is fully visible",
- min=0.0, max=5.0, default=0.5, subtype="TIME", unit="TIME")
- fade_out_time = FloatProperty(name="Fade Out Time",
- description="Number of seconds before the object is fully invisible",
- min=0.0, max=5.0, default=0.5, subtype="TIME", unit="TIME")
- bounds_center = BoolProperty(name="Use Mesh Midpoint",
- description="Use mesh's midpoint to calculate LOS instead of object origin",
- default=False)
-
- near_trans = FloatProperty(name="Near Transparent",
- description="Nearest distance at which the object is fully transparent",
- min=0.0, default=0.0, subtype="DISTANCE", unit="LENGTH")
- near_opaq = FloatProperty(name="Near Opaque",
- description="Nearest distance at which the object is fully opaque",
- min=0.0, default=0.0, subtype="DISTANCE", unit="LENGTH")
- far_opaq = FloatProperty(name="Far Opaque",
- description="Farthest distance at which the object is fully opaque",
- min=0.0, default=15.0, subtype="DISTANCE", unit="LENGTH")
- far_trans = FloatProperty(name="Far Transparent",
- description="Farthest distance at which the object is fully transparent",
- min=0.0, default=20.0, subtype="DISTANCE", unit="LENGTH")
+ fader_type = EnumProperty(
+ name="Fader Type",
+ description="Type of opacity fade",
+ items=[
+ ("DistOpacity", "Distance", "Fade based on distance to object"),
+ ("FadeOpacity", "Line-of-Sight", "Fade based on line-of-sight to object"),
+ ("SimpleDist", "Simple Distance", "Fade for use as Great Zero Markers"),
+ ],
+ default="SimpleDist",
+ )
+
+ fade_in_time = FloatProperty(
+ name="Fade In Time",
+ description="Number of seconds before the object is fully visible",
+ min=0.0,
+ max=5.0,
+ default=0.5,
+ subtype="TIME",
+ unit="TIME",
+ )
+ fade_out_time = FloatProperty(
+ name="Fade Out Time",
+ description="Number of seconds before the object is fully invisible",
+ min=0.0,
+ max=5.0,
+ default=0.5,
+ subtype="TIME",
+ unit="TIME",
+ )
+ bounds_center = BoolProperty(
+ name="Use Mesh Midpoint",
+ description="Use mesh's midpoint to calculate LOS instead of object origin",
+ default=False,
+ )
+
+ near_trans = FloatProperty(
+ name="Near Transparent",
+ description="Nearest distance at which the object is fully transparent",
+ min=0.0,
+ default=0.0,
+ subtype="DISTANCE",
+ unit="LENGTH",
+ )
+ near_opaq = FloatProperty(
+ name="Near Opaque",
+ description="Nearest distance at which the object is fully opaque",
+ min=0.0,
+ default=0.0,
+ subtype="DISTANCE",
+ unit="LENGTH",
+ )
+ far_opaq = FloatProperty(
+ name="Far Opaque",
+ description="Farthest distance at which the object is fully opaque",
+ min=0.0,
+ default=15.0,
+ subtype="DISTANCE",
+ unit="LENGTH",
+ )
+ far_trans = FloatProperty(
+ name="Far Transparent",
+ description="Farthest distance at which the object is fully transparent",
+ min=0.0,
+ default=20.0,
+ subtype="DISTANCE",
+ unit="LENGTH",
+ )
def export(self, exporter, bo, so):
if self.fader_type == "DistOpacity":
- mod = exporter.mgr.find_create_object(plDistOpacityMod, so=so, name=self.key_name)
+ mod = exporter.mgr.find_create_object(
+ plDistOpacityMod, so=so, name=self.key_name
+ )
mod.nearTrans = self.near_trans
mod.nearOpaq = self.near_opaq
mod.farOpaq = self.far_opaq
mod.farTrans = self.far_trans
elif self.fader_type == "FadeOpacity":
- mod = exporter.mgr.find_create_object(plFadeOpacityMod, so=so, name=self.key_name)
+ mod = exporter.mgr.find_create_object(
+ plFadeOpacityMod, so=so, name=self.key_name
+ )
mod.fadeUp = self.fade_in_time
mod.fadeDown = self.fade_out_time
mod.boundsCenter = self.bounds_center
elif self.fader_type == "SimpleDist":
- mod = exporter.mgr.find_create_object(plDistOpacityMod, so=so, name=self.key_name)
+ mod = exporter.mgr.find_create_object(
+ plDistOpacityMod, so=so, name=self.key_name
+ )
mod.nearTrans = 0.0
mod.nearOpaq = 0.0
mod.farOpaq = self.far_opaq
mod.farTrans = self.far_trans
-
+
@property
def requires_actor(self):
return self.fader_type == "FadeOpacity"
@@ -277,29 +359,33 @@ class PlasmaFollowMod(idprops.IDPropObjectMixin, PlasmaModifierProperties):
bl_label = "Follow"
bl_description = "Follow the movement of the camera, player, or another object"
- follow_mode = EnumProperty(name="Mode",
- description="Leader's movement to follow",
- items=[
- ("kPositionX", "X Axis", "Follow the leader's X movements"),
- ("kPositionY", "Y Axis", "Follow the leader's Y movements"),
- ("kPositionZ", "Z Axis", "Follow the leader's Z movements"),
- ("kRotate", "Rotation", "Follow the leader's rotation movements"),
- ],
- default={"kPositionX", "kPositionY", "kPositionZ"},
- options={"ENUM_FLAG"})
-
- leader_type = EnumProperty(name="Leader Type",
- description="Leader to follow",
- items=[
- ("kFollowCamera", "Camera", "Follow the camera"),
- ("kFollowListener", "Listener", "Follow listeners"),
- ("kFollowPlayer", "Player", "Follow the local player"),
- ("kFollowObject", "Object", "Follow an object"),
- ])
-
- leader = PointerProperty(name="Leader Object",
- description="Object to follow",
- type=bpy.types.Object)
+ follow_mode = EnumProperty(
+ name="Mode",
+ description="Leader's movement to follow",
+ items=[
+ ("kPositionX", "X Axis", "Follow the leader's X movements"),
+ ("kPositionY", "Y Axis", "Follow the leader's Y movements"),
+ ("kPositionZ", "Z Axis", "Follow the leader's Z movements"),
+ ("kRotate", "Rotation", "Follow the leader's rotation movements"),
+ ],
+ default={"kPositionX", "kPositionY", "kPositionZ"},
+ options={"ENUM_FLAG"},
+ )
+
+ leader_type = EnumProperty(
+ name="Leader Type",
+ description="Leader to follow",
+ items=[
+ ("kFollowCamera", "Camera", "Follow the camera"),
+ ("kFollowListener", "Listener", "Follow listeners"),
+ ("kFollowPlayer", "Player", "Follow the local player"),
+ ("kFollowObject", "Object", "Follow an object"),
+ ],
+ )
+
+ leader = PointerProperty(
+ name="Leader Object", description="Object to follow", type=bpy.types.Object
+ )
def export(self, exporter, bo, so):
fm = exporter.mgr.find_create_object(plFollowMod, so=so, name=self.key_name)
@@ -315,7 +401,11 @@ class PlasmaFollowMod(idprops.IDPropObjectMixin, PlasmaModifierProperties):
if self.leader:
fm.leader = exporter.mgr.find_create_key(plSceneObject, bl=self.leader)
else:
- raise ExportError("'{}': Follow's leader object must be selected".format(self.key_name))
+ raise ExportError(
+ "'{}': Follow's leader object must be selected".format(
+ self.key_name
+ )
+ )
@classmethod
def _idprop_mapping(cls):
@@ -327,23 +417,25 @@ class PlasmaFollowMod(idprops.IDPropObjectMixin, PlasmaModifierProperties):
class PlasmaGrassWave(bpy.types.PropertyGroup):
- distance = FloatVectorProperty(name="Distance",
- size=3,
- default=(0.2, 0.2, 0.1),
- subtype="XYZ",
- unit="LENGTH",
- options=set())
- direction = FloatVectorProperty(name="Direction",
- size=2,
- default=(0.2, 0.05),
- soft_min=0.0, soft_max=1.0,
- unit="LENGTH",
- subtype="XYZ",
- options=set())
- speed = FloatProperty(name="Speed",
- default=0.1,
- unit="VELOCITY",
- options=set())
+ distance = FloatVectorProperty(
+ name="Distance",
+ size=3,
+ default=(0.2, 0.2, 0.1),
+ subtype="XYZ",
+ unit="LENGTH",
+ options=set(),
+ )
+ direction = FloatVectorProperty(
+ name="Direction",
+ size=2,
+ default=(0.2, 0.05),
+ soft_min=0.0,
+ soft_max=1.0,
+ unit="LENGTH",
+ subtype="XYZ",
+ options=set(),
+ )
+ speed = FloatProperty(name="Speed", default=0.1, unit="VELOCITY", options=set())
class PlasmaGrassShaderMod(PlasmaModifierProperties):
@@ -359,12 +451,16 @@ class PlasmaGrassShaderMod(PlasmaModifierProperties):
wave4 = PointerProperty(type=PlasmaGrassWave)
# UI Accessor
- wave_selector = EnumProperty(items=[("wave1", "Wave 1", ""),
- ("wave2", "Wave 2", ""),
- ("wave3", "Wave 3", ""),
- ("wave4", "Wave 4", "")],
- name="Waves",
- options=set())
+ wave_selector = EnumProperty(
+ items=[
+ ("wave1", "Wave 1", ""),
+ ("wave2", "Wave 2", ""),
+ ("wave3", "Wave 3", ""),
+ ("wave4", "Wave 4", ""),
+ ],
+ name="Waves",
+ options=set(),
+ )
@property
def copy_material(self):
@@ -379,23 +475,33 @@ class PlasmaGrassShaderMod(PlasmaModifierProperties):
materials = exporter.mesh.material.get_materials(bo)
if not materials:
- exporter.report.warning("No materials are associated with this object, no grass shader exported!",
- indent=3)
+ exporter.report.warning(
+ "No materials are associated with this object, no grass shader exported!",
+ indent=3,
+ )
return
elif len(materials) > 1:
- exporter.report.warning("Ah, a multiple material grass shader, eh. You like living dangerously...",
- indent=3)
+ exporter.report.warning(
+ "Ah, a multiple material grass shader, eh. You like living dangerously...",
+ indent=3,
+ )
for material in materials:
- mod = exporter.mgr.find_create_object(plGrassShaderMod, so=so, name=material.name)
+ mod = exporter.mgr.find_create_object(
+ plGrassShaderMod, so=so, name=material.name
+ )
mod.material = material
- for mod_wave, settings in zip(mod.waves, (self.wave1, self.wave2, self.wave3, self.wave4)):
+ for mod_wave, settings in zip(
+ mod.waves, (self.wave1, self.wave2, self.wave3, self.wave4)
+ ):
mod_wave.dist = hsVector3(*settings.distance)
mod_wave.dirX, mod_wave.dirY = settings.direction
mod_wave.speed = settings.speed
-class PlasmaLightMapGen(idprops.IDPropMixin, PlasmaModifierProperties, PlasmaModifierUpgradable):
+class PlasmaLightMapGen(
+ idprops.IDPropMixin, PlasmaModifierProperties, PlasmaModifierUpgradable
+):
pl_id = "lightmap"
bl_category = "Render"
@@ -404,45 +510,56 @@ class PlasmaLightMapGen(idprops.IDPropMixin, PlasmaModifierProperties, PlasmaMod
deprecated_properties = {"render_layers"}
- quality = EnumProperty(name="Quality",
- description="Resolution of lightmap",
- items=[
- ("128", "128px", "128x128 pixels"),
- ("256", "256px", "256x256 pixels"),
- ("512", "512px", "512x512 pixels"),
- ("1024", "1024px", "1024x1024 pixels"),
- ("2048", "2048px", "2048x2048 pixels"),
- ])
-
- bake_type = EnumProperty(name="Bake To",
- description="Destination for baked lighting data",
- items=[
- ("lightmap", "Lightmap Texture", "Bakes lighting to a lightmap texture"),
- ("vcol", "Vertex Colors", "Bakes lighting to vertex colors"),
- ],
- options=set())
-
- render_layers = BoolVectorProperty(name="Layers",
- description="DEPRECATED: Render layers to use for baking",
- options={"HIDDEN"},
- subtype="LAYER",
- size=_NUM_RENDER_LAYERS,
- default=((True,) * _NUM_RENDER_LAYERS))
-
- bake_pass_name = StringProperty(name="Bake Pass",
- description="Pass in which to bake lighting",
- options=set())
-
- lights = PointerProperty(name="Light Group",
- description="Group that defines the collection of lights to bake",
- type=bpy.types.Group)
-
- uv_map = StringProperty(name="UV Texture",
- description="UV Texture used as the basis for the lightmap")
-
- image = PointerProperty(name="Baked Image",
- description="Use this image instead of re-baking the lighting each export",
- type=bpy.types.Image)
+ quality = EnumProperty(
+ name="Quality",
+ description="Resolution of lightmap",
+ items=[
+ ("128", "128px", "128x128 pixels"),
+ ("256", "256px", "256x256 pixels"),
+ ("512", "512px", "512x512 pixels"),
+ ("1024", "1024px", "1024x1024 pixels"),
+ ("2048", "2048px", "2048x2048 pixels"),
+ ],
+ )
+
+ bake_type = EnumProperty(
+ name="Bake To",
+ description="Destination for baked lighting data",
+ items=[
+ ("lightmap", "Lightmap Texture", "Bakes lighting to a lightmap texture"),
+ ("vcol", "Vertex Colors", "Bakes lighting to vertex colors"),
+ ],
+ options=set(),
+ )
+
+ render_layers = BoolVectorProperty(
+ name="Layers",
+ description="DEPRECATED: Render layers to use for baking",
+ options={"HIDDEN"},
+ subtype="LAYER",
+ size=_NUM_RENDER_LAYERS,
+ default=((True,) * _NUM_RENDER_LAYERS),
+ )
+
+ bake_pass_name = StringProperty(
+ name="Bake Pass", description="Pass in which to bake lighting", options=set()
+ )
+
+ lights = PointerProperty(
+ name="Light Group",
+ description="Group that defines the collection of lights to bake",
+ type=bpy.types.Group,
+ )
+
+ uv_map = StringProperty(
+ name="UV Texture", description="UV Texture used as the basis for the lightmap"
+ )
+
+ image = PointerProperty(
+ name="Baked Image",
+ description="Use this image instead of re-baking the lighting each export",
+ type=bpy.types.Image,
+ )
@property
def bake_lightmap(self):
@@ -470,7 +587,9 @@ class PlasmaLightMapGen(idprops.IDPropMixin, PlasmaModifierProperties, PlasmaMod
# If no lightmap image is found, then either lightmap generation failed (error raised by oven)
# or baking is turned off. Either way, bail out.
- lightmap_im = self.image if self.image is not None else exporter.oven.get_lightmap(bo)
+ lightmap_im = (
+ self.image if self.image is not None else exporter.oven.get_lightmap(bo)
+ )
if lightmap_im is None:
return
mat_mgr = exporter.mesh.material
@@ -478,12 +597,25 @@ class PlasmaLightMapGen(idprops.IDPropMixin, PlasmaModifierProperties, PlasmaMod
# Find the stupid UVTex
uvtex_name = exporter.oven.lightmap_uvtex_name
- uvw_src = next((i for i, uvtex in enumerate(bo.data.uv_textures) if uvtex.name == uvtex_name), None)
+ uvw_src = next(
+ (
+ i
+ for i, uvtex in enumerate(bo.data.uv_textures)
+ if uvtex.name == uvtex_name
+ ),
+ None,
+ )
if uvw_src is None:
- raise ExportError("'{}': Lightmap UV Texture '{}' seems to be missing. Did you delete it?", bo.name, uvtex_name)
+ raise ExportError(
+ "'{}': Lightmap UV Texture '{}' seems to be missing. Did you delete it?",
+ bo.name,
+ uvtex_name,
+ )
for matKey in materials:
- layer = exporter.mgr.add_object(plLayer, name="{}_LIGHTMAPGEN".format(matKey.name), so=so)
+ layer = exporter.mgr.add_object(
+ plLayer, name="{}_LIGHTMAPGEN".format(matKey.name), so=so
+ )
layer.UVWSrc = uvw_src
# Colors science'd from PRPs
@@ -494,7 +626,7 @@ class PlasmaLightMapGen(idprops.IDPropMixin, PlasmaModifierProperties, PlasmaMod
# GMatState
gstate = layer.state
gstate.blendFlags |= hsGMatState.kBlendMult
- gstate.clampFlags |= (hsGMatState.kClampTextureU | hsGMatState.kClampTextureV)
+ gstate.clampFlags |= hsGMatState.kClampTextureU | hsGMatState.kClampTextureV
gstate.ZFlags |= hsGMatState.kZNoZWrite
gstate.miscFlags |= hsGMatState.kMiscLightMap
@@ -503,11 +635,14 @@ class PlasmaLightMapGen(idprops.IDPropMixin, PlasmaModifierProperties, PlasmaMod
mat.addPiggyBack(layer.key)
# Mmm... cheating
- mat_mgr.export_prepared_image(owner=layer, image=lightmap_im,
- allowed_formats={"PNG", "JPG"},
- extension="hsm",
- ephemeral=True,
- indent=2)
+ mat_mgr.export_prepared_image(
+ owner=layer,
+ image=lightmap_im,
+ allowed_formats={"PNG", "JPG"},
+ extension="hsm",
+ ephemeral=True,
+ indent=2,
+ )
@classmethod
def _idprop_mapping(cls):
@@ -537,7 +672,10 @@ class PlasmaLightMapGen(idprops.IDPropMixin, PlasmaModifierProperties, PlasmaMod
render_layers = tuple(self.render_layers)
# Try to find a render pass matching, if possible...
- bake_pass = next((i for i in bake_passes if tuple(i.render_layers) == render_layers), None)
+ bake_pass = next(
+ (i for i in bake_passes if tuple(i.render_layers) == render_layers),
+ None,
+ )
if bake_pass is None:
bake_pass = bake_passes.add()
bake_pass.display_name = "Pass {}".format(len(bake_passes))
@@ -554,14 +692,18 @@ class PlasmaLightingMod(PlasmaModifierProperties):
bl_label = "Lighting Info"
bl_description = "Fine tune Plasma lighting settings"
- force_rt_lights = BoolProperty(name="Force RT Lighting",
- description="Unleashes satan by forcing the engine to dynamically light this object",
- default=False,
- options=set())
- force_preshade = BoolProperty(name="Force Vertex Shading",
- description="Ensures vertex lights are baked, even if illogical",
- default=False,
- options=set())
+ force_rt_lights = BoolProperty(
+ name="Force RT Lighting",
+ description="Unleashes satan by forcing the engine to dynamically light this object",
+ default=False,
+ options=set(),
+ )
+ force_preshade = BoolProperty(
+ name="Force Vertex Shading",
+ description="Ensures vertex lights are baked, even if illogical",
+ default=False,
+ options=set(),
+ )
@property
def allow_preshade(self):
@@ -613,29 +755,36 @@ class PlasmaLightingMod(PlasmaModifierProperties):
_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" },
+ {
+ "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": 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):
+
+class PlasmaLocalizedTextModifier(
+ PlasmaModifierProperties, PlasmaModifierLogicWiz, TranslationMixin
+):
pl_id = "dynatext"
bl_category = "Render"
@@ -643,16 +792,18 @@ class PlasmaLocalizedTextModifier(PlasmaModifierProperties, PlasmaModifierLogicW
bl_description = ""
bl_icon = "TEXT"
- translations = CollectionProperty(name="Translations",
- type=PlasmaJournalTranslation,
- options=set())
+ 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())
+ 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":
@@ -660,54 +811,60 @@ class PlasmaLocalizedTextModifier(PlasmaModifierProperties, PlasmaModifierLogicW
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)))
+ 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())
+ 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())
+ 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())
+ 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
@@ -715,19 +872,44 @@ class PlasmaLocalizedTextModifier(PlasmaModifierProperties, PlasmaModifierLogicW
# 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)
+ 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)
+ 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)
+ 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)
@@ -737,7 +919,9 @@ class PlasmaLocalizedTextModifier(PlasmaModifierProperties, PlasmaModifierLogicW
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, "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)
@@ -753,7 +937,9 @@ class PlasmaLocalizedTextModifier(PlasmaModifierProperties, PlasmaModifierLogicW
def sanity_check(self):
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
+ )
class PlasmaShadowCasterMod(PlasmaModifierProperties):
@@ -763,30 +949,51 @@ class PlasmaShadowCasterMod(PlasmaModifierProperties):
bl_label = "Cast RT Shadow"
bl_description = "Cast runtime shadows"
- blur = IntProperty(name="Blur",
- description="Blur factor for the shadow map",
- min=0, max=100, default=0,
- subtype="PERCENTAGE", options=set())
- boost = IntProperty(name="Boost",
- description="Multiplies the shadow's power",
- min=0, max=5000, default=100,
- subtype="PERCENTAGE", options=set())
- falloff = IntProperty(name="Falloff",
- description="Multiplier for each lamp's falloff value",
- min=10, max=1000, default=100,
- subtype="PERCENTAGE", options=set())
-
- limit_resolution = BoolProperty(name="Limit Resolution",
- description="Increase performance by halving the resolution of the shadow map",
- default=False,
- options=set())
- self_shadow = BoolProperty(name="Self Shadow",
- description="Object can cast shadows on itself",
- default=False,
- options=set())
+ blur = IntProperty(
+ name="Blur",
+ description="Blur factor for the shadow map",
+ min=0,
+ max=100,
+ default=0,
+ subtype="PERCENTAGE",
+ options=set(),
+ )
+ boost = IntProperty(
+ name="Boost",
+ description="Multiplies the shadow's power",
+ min=0,
+ max=5000,
+ default=100,
+ subtype="PERCENTAGE",
+ options=set(),
+ )
+ falloff = IntProperty(
+ name="Falloff",
+ description="Multiplier for each lamp's falloff value",
+ min=10,
+ max=1000,
+ default=100,
+ subtype="PERCENTAGE",
+ options=set(),
+ )
+
+ limit_resolution = BoolProperty(
+ name="Limit Resolution",
+ description="Increase performance by halving the resolution of the shadow map",
+ default=False,
+ options=set(),
+ )
+ self_shadow = BoolProperty(
+ name="Self Shadow",
+ description="Object can cast shadows on itself",
+ default=False,
+ options=set(),
+ )
def export(self, exporter, bo, so):
- caster = exporter.mgr.find_create_object(plShadowCaster, so=so, name=self.key_name)
+ caster = exporter.mgr.find_create_object(
+ plShadowCaster, so=so, name=self.key_name
+ )
caster.attenScale = self.falloff / 100.0
caster.blurScale = self.blur / 100.0
caster.boost = self.boost / 100.0
@@ -803,39 +1010,49 @@ class PlasmaViewFaceMod(idprops.IDPropObjectMixin, PlasmaModifierProperties):
bl_label = "Swivel"
bl_description = "Swivel object to face the camera, player, or another object"
- preset_options = EnumProperty(name="Type",
- description="Type of Facing",
- items=[
- ("Billboard", "Billboard", "Face the camera (Y Axis only)"),
- ("Sprite", "Sprite", "Face the camera (All Axis)"),
- ("Custom", "Custom", "Custom Swivel"),
- ])
-
- follow_mode = EnumProperty(name="Target Type",
- description="Target of the swivel",
- items=[
- ("kFaceCam", "Camera", "Face the camera"),
- ("kFaceList", "Listener", "Face listeners"),
- ("kFacePlay", "Player", "Face the local player"),
- ("kFaceObj", "Object", "Face an object"),
- ])
- target = PointerProperty(name="Target Object",
- description="Object to face",
- type=bpy.types.Object)
-
- pivot_on_y = BoolProperty(name="Pivot on local Y",
- description="Swivel only around the local Y axis",
- default=False)
+ preset_options = EnumProperty(
+ name="Type",
+ description="Type of Facing",
+ items=[
+ ("Billboard", "Billboard", "Face the camera (Y Axis only)"),
+ ("Sprite", "Sprite", "Face the camera (All Axis)"),
+ ("Custom", "Custom", "Custom Swivel"),
+ ],
+ )
+
+ follow_mode = EnumProperty(
+ name="Target Type",
+ description="Target of the swivel",
+ items=[
+ ("kFaceCam", "Camera", "Face the camera"),
+ ("kFaceList", "Listener", "Face listeners"),
+ ("kFacePlay", "Player", "Face the local player"),
+ ("kFaceObj", "Object", "Face an object"),
+ ],
+ )
+ target = PointerProperty(
+ name="Target Object", description="Object to face", type=bpy.types.Object
+ )
+
+ pivot_on_y = BoolProperty(
+ name="Pivot on local Y",
+ description="Swivel only around the local Y axis",
+ default=False,
+ )
offset = BoolProperty(name="Offset", description="Use offset vector", default=False)
- offset_local = BoolProperty(name="Local", description="Use local coordinates", default=False)
+ offset_local = BoolProperty(
+ name="Local", description="Use local coordinates", default=False
+ )
offset_coord = FloatVectorProperty(name="", subtype="XYZ")
def export(self, exporter, bo, so):
- vfm = exporter.mgr.find_create_object(plViewFaceModifier, so=so, name=self.key_name)
+ vfm = exporter.mgr.find_create_object(
+ plViewFaceModifier, so=so, name=self.key_name
+ )
# Set a default scaling (libHSPlasma will set this to 0 otherwise).
- vfm.scale = hsVector3(1,1,1)
+ vfm.scale = hsVector3(1, 1, 1)
l2p = utils.matrix44(bo.matrix_local)
vfm.localToParent = l2p
vfm.parentToLocal = l2p.inverse()
@@ -857,9 +1074,15 @@ class PlasmaViewFaceMod(idprops.IDPropObjectMixin, PlasmaModifierProperties):
# If this swivel is following an object, make sure that the
# target has been selected and is a valid SO.
if self.target:
- vfm.faceObj = exporter.mgr.find_create_key(plSceneObject, bl=self.target)
+ vfm.faceObj = exporter.mgr.find_create_key(
+ plSceneObject, bl=self.target
+ )
else:
- raise ExportError("'{}': Swivel's target object must be selected".format(self.key_name))
+ raise ExportError(
+ "'{}': Swivel's target object must be selected".format(
+ self.key_name
+ )
+ )
if self.pivot_on_y:
vfm.setFlag(plViewFaceModifier.kPivotY, True)
@@ -887,18 +1110,38 @@ class PlasmaVisControl(idprops.IDPropObjectMixin, PlasmaModifierProperties):
bl_label = "Visibility Control"
bl_description = "Controls object visibility using VisRegions"
- mode = EnumProperty(name="Mode",
- description="Purpose of the VisRegion",
- items=[("normal", "Normal", "Objects are only visible when the camera is inside this region"),
- ("exclude", "Exclude", "Objects are only visible when the camera is outside this region"),
- ("fx", "Special FX", "This is a list of objects used for special effects only")])
- soft_region = PointerProperty(name="Region",
- description="Object defining the SoftVolume for this VisRegion",
- type=bpy.types.Object,
- poll=idprops.poll_softvolume_objects)
- replace_normal = BoolProperty(name="Hide Drawables",
- description="Hides drawables attached to this region",
- default=True)
+ mode = EnumProperty(
+ name="Mode",
+ description="Purpose of the VisRegion",
+ items=[
+ (
+ "normal",
+ "Normal",
+ "Objects are only visible when the camera is inside this region",
+ ),
+ (
+ "exclude",
+ "Exclude",
+ "Objects are only visible when the camera is outside this region",
+ ),
+ (
+ "fx",
+ "Special FX",
+ "This is a list of objects used for special effects only",
+ ),
+ ],
+ )
+ soft_region = PointerProperty(
+ name="Region",
+ description="Object defining the SoftVolume for this VisRegion",
+ type=bpy.types.Object,
+ poll=idprops.poll_softvolume_objects,
+ )
+ replace_normal = BoolProperty(
+ name="Hide Drawables",
+ description="Hides drawables attached to this region",
+ default=True,
+ )
def export(self, exporter, bo, so):
rgn = exporter.mgr.find_create_object(plVisRegion, bl=bo, so=so)
@@ -913,12 +1156,20 @@ class PlasmaVisControl(idprops.IDPropObjectMixin, PlasmaModifierProperties):
rgn.region = this_sv.get_key(exporter, so)
else:
if not self.soft_region:
- raise ExportError("'{}': Visibility Control must have a Soft Volume selected".format(self.key_name))
+ raise ExportError(
+ "'{}': Visibility Control must have a Soft Volume selected".format(
+ self.key_name
+ )
+ )
sv_bo = self.soft_region
sv = sv_bo.plasma_modifiers.softvolume
exporter.report.msg("[VisRegion] SoftVolume '{}'", sv_bo.name, indent=1)
if not sv.enabled:
- raise ExportError("'{}': '{}' is not a SoftVolume".format(self.key_name, sv_bo.name))
+ raise ExportError(
+ "'{}': '{}' is not a SoftVolume".format(
+ self.key_name, sv_bo.name
+ )
+ )
rgn.region = sv.get_key(exporter)
rgn.setProperty(plVisRegion.kIsNot, self.mode == "exclude")
@@ -929,10 +1180,12 @@ class PlasmaVisControl(idprops.IDPropObjectMixin, PlasmaModifierProperties):
class VisRegion(idprops.IDPropObjectMixin, bpy.types.PropertyGroup):
enabled = BoolProperty(default=True)
- control_region = PointerProperty(name="Control",
- description="Object defining a Plasma Visibility Control",
- type=bpy.types.Object,
- poll=idprops.poll_visregion_objects)
+ control_region = PointerProperty(
+ name="Control",
+ description="Object defining a Plasma Visibility Control",
+ type=bpy.types.Object,
+ poll=idprops.poll_visregion_objects,
+ )
@classmethod
def _idprop_mapping(cls):
@@ -946,8 +1199,7 @@ class PlasmaVisibilitySet(PlasmaModifierProperties):
bl_label = "Visibility Set"
bl_description = "Defines areas where this object is visible"
- regions = CollectionProperty(name="Visibility Regions",
- type=VisRegion)
+ regions = CollectionProperty(name="Visibility Regions", type=VisRegion)
active_region_index = IntProperty(options={"HIDDEN"})
def export(self, exporter, bo, so):
@@ -967,5 +1219,11 @@ class PlasmaVisibilitySet(PlasmaModifierProperties):
if not region.enabled:
continue
if not region.control_region:
- raise ExportError("{}: Not all Visibility Controls are set up properly in Visibility Set".format(bo.name))
- addRegion(exporter.mgr.find_create_key(plVisRegion, bl=region.control_region))
+ raise ExportError(
+ "{}: Not all Visibility Controls are set up properly in Visibility Set".format(
+ bo.name
+ )
+ )
+ addRegion(
+ exporter.mgr.find_create_key(plVisRegion, bl=region.control_region)
+ )
diff --git a/korman/properties/modifiers/sound.py b/korman/properties/modifiers/sound.py
index 0559d08..2f3dcbe 100644
--- a/korman/properties/modifiers/sound.py
+++ b/korman/properties/modifiers/sound.py
@@ -30,9 +30,10 @@ _randomsound_modes = {
"normal": plRandomSoundMod.kNormal,
"norepeat": plRandomSoundMod.kNoRepeats,
"coverall": plRandomSoundMod.kCoverall | plRandomSoundMod.kNoRepeats,
- "sequential": plRandomSoundMod.kSequential
+ "sequential": plRandomSoundMod.kSequential,
}
+
class PlasmaRandomSound(PlasmaModifierProperties):
pl_id = "random_sound"
pl_depends = {"soundemit"}
@@ -41,55 +42,101 @@ class PlasmaRandomSound(PlasmaModifierProperties):
bl_label = "Random Sound"
bl_description = ""
- mode = EnumProperty(name="Mode",
- description="Playback Type",
- items=[("random", "Random Time", "Plays a random sound from the emitter at a random time"),
- ("collision", "Collision Surface", "Plays a random sound when the object's parent collides")],
- default="random",
- options=set())
+ mode = EnumProperty(
+ name="Mode",
+ description="Playback Type",
+ items=[
+ (
+ "random",
+ "Random Time",
+ "Plays a random sound from the emitter at a random time",
+ ),
+ (
+ "collision",
+ "Collision Surface",
+ "Plays a random sound when the object's parent collides",
+ ),
+ ],
+ default="random",
+ options=set(),
+ )
# Physical (read: collision) sounds
- play_on = EnumProperty(name="Play On",
- description="Play sounds on this collision event",
- items=[("slide", "Slide", "Plays a random sound on object slide"),
- ("impact", "Impact", "Plays a random sound on object slide")],
- options=set())
- surfaces = EnumProperty(name="Play Against",
- description="Sounds are played on collision against these surfaces",
- items=surface_types[1:],
- options={"ENUM_FLAG"})
+ play_on = EnumProperty(
+ name="Play On",
+ description="Play sounds on this collision event",
+ items=[
+ ("slide", "Slide", "Plays a random sound on object slide"),
+ ("impact", "Impact", "Plays a random sound on object slide"),
+ ],
+ options=set(),
+ )
+ surfaces = EnumProperty(
+ name="Play Against",
+ description="Sounds are played on collision against these surfaces",
+ items=surface_types[1:],
+ options={"ENUM_FLAG"},
+ )
# Timed random sounds
- auto_start = BoolProperty(name="Auto Start",
- description="Start playing when the Age loads",
- default=True,
- options=set())
- play_mode = EnumProperty(name="Play Mode",
- description="",
- items=[("normal", "Any", "Plays any attached sound"),
- ("norepeat", "No Repeats", "Do not replay a sound immediately after itself"),
- ("coverall", "Full Set", "Once a sound is played, do not replay it until after all sounds are played"),
- ("sequential", "Sequential", "Play sounds in the order they appear in the emitter")],
- default="norepeat",
- options=set())
- stop_after_set = BoolProperty(name="Stop After Set",
- description="Stop playing after all sounds are played",
- default=False,
- options=set())
- stop_after_play = BoolProperty(name="Stop After Play",
- description="Stop playing after one sound is played",
- default=False,
- options=set())
- min_delay = FloatProperty(name="Min Delay",
- description="Minimum delay length",
- min=0.0,
- subtype="TIME", unit="TIME",
- options=set())
- max_delay = FloatProperty(name="Max Delay",
- description="Maximum delay length",
- min=0.0,
- subtype="TIME", unit="TIME",
- options=set())
+ auto_start = BoolProperty(
+ name="Auto Start",
+ description="Start playing when the Age loads",
+ default=True,
+ options=set(),
+ )
+ play_mode = EnumProperty(
+ name="Play Mode",
+ description="",
+ items=[
+ ("normal", "Any", "Plays any attached sound"),
+ (
+ "norepeat",
+ "No Repeats",
+ "Do not replay a sound immediately after itself",
+ ),
+ (
+ "coverall",
+ "Full Set",
+ "Once a sound is played, do not replay it until after all sounds are played",
+ ),
+ (
+ "sequential",
+ "Sequential",
+ "Play sounds in the order they appear in the emitter",
+ ),
+ ],
+ default="norepeat",
+ options=set(),
+ )
+ stop_after_set = BoolProperty(
+ name="Stop After Set",
+ description="Stop playing after all sounds are played",
+ default=False,
+ options=set(),
+ )
+ stop_after_play = BoolProperty(
+ name="Stop After Play",
+ description="Stop playing after one sound is played",
+ default=False,
+ options=set(),
+ )
+ min_delay = FloatProperty(
+ name="Min Delay",
+ description="Minimum delay length",
+ min=0.0,
+ subtype="TIME",
+ unit="TIME",
+ options=set(),
+ )
+ max_delay = FloatProperty(
+ name="Max Delay",
+ description="Maximum delay length",
+ min=0.0,
+ subtype="TIME",
+ unit="TIME",
+ options=set(),
+ )
def export(self, exporter, bo, so):
rndmod = exporter.mgr.find_create_object(plRandomSoundMod, bl=bo, so=so)
@@ -116,15 +163,23 @@ class PlasmaRandomSound(PlasmaModifierProperties):
if self.mode == "collision" and self.surfaces:
parent_bo = bo.parent
if parent_bo is None:
- raise ExportError("[{}]: Collision sound objects MUST be parented directly to the collider object.", bo.name)
+ raise ExportError(
+ "[{}]: Collision sound objects MUST be parented directly to the collider object.",
+ bo.name,
+ )
phys = exporter.mgr.find_object(plGenericPhysical, bl=parent_bo)
if phys is None:
- raise ExportError("[{}]: Collision sound objects MUST be parented directly to the collider object.", bo.name)
+ raise ExportError(
+ "[{}]: Collision sound objects MUST be parented directly to the collider object.",
+ bo.name,
+ )
# The soundGroup on the physical may or may not be the generic "this is my surface type"
# soundGroup with no actual sounds attached. So, we need to lookup the actual one.
sndgroup = exporter.mgr.find_create_object(plPhysicalSndGroup, bl=parent_bo)
- sndgroup.group = getattr(plPhysicalSndGroup, parent_bo.plasma_modifiers.collision.surface)
+ sndgroup.group = getattr(
+ plPhysicalSndGroup, parent_bo.plasma_modifiers.collision.surface
+ )
phys.soundGroup = sndgroup.key
rndmod = exporter.mgr.find_key(plRandomSoundMod, bl=bo, so=so)
@@ -135,32 +190,55 @@ class PlasmaRandomSound(PlasmaModifierProperties):
else:
raise RuntimeError()
- sounds = { i: sound for i, sound in enumerate(getattr(sndgroup, groupattr)) }
+ sounds = {i: sound for i, sound in enumerate(getattr(sndgroup, groupattr))}
for surface_name in self.surfaces:
surface_id = getattr(plPhysicalSndGroup, surface_name)
if surface_id in sounds:
- exporter.report.warn("Overwriting physical {} surface '{}' ID:{}",
- groupattr, surface_name, surface_id, indent=2)
+ exporter.report.warn(
+ "Overwriting physical {} surface '{}' ID:{}",
+ groupattr,
+ surface_name,
+ surface_id,
+ indent=2,
+ )
else:
- exporter.report.msg("Got physical {} surface '{}' ID:{}",
- groupattr, surface_name, surface_id, indent=2)
+ exporter.report.msg(
+ "Got physical {} surface '{}' ID:{}",
+ groupattr,
+ surface_name,
+ surface_id,
+ indent=2,
+ )
sounds[surface_id] = rndmod
# Keeps the LUT (or should that be lookup vector?) as small as possible
- setattr(sndgroup, groupattr, [sounds.get(i) for i in range(max(sounds.keys()) + 1)])
+ setattr(
+ sndgroup,
+ groupattr,
+ [sounds.get(i) for i in range(max(sounds.keys()) + 1)],
+ )
class PlasmaSfxFade(bpy.types.PropertyGroup):
- fade_type = EnumProperty(name="Type",
- description="Fade Type",
- items=[("NONE", "[Disable]", "Don't fade"),
- ("kLinear", "Linear", "Linear fade"),
- ("kLogarithmic", "Logarithmic", "Log fade"),
- ("kExponential", "Exponential", "Exponential fade")],
- options=set())
- length = FloatProperty(name="Length",
- description="Seconds to spend fading",
- default=1.0, min=0.0,
- options=set(), subtype="TIME", unit="TIME")
+ fade_type = EnumProperty(
+ name="Type",
+ description="Fade Type",
+ items=[
+ ("NONE", "[Disable]", "Don't fade"),
+ ("kLinear", "Linear", "Linear fade"),
+ ("kLogarithmic", "Logarithmic", "Log fade"),
+ ("kExponential", "Exponential", "Exponential fade"),
+ ],
+ options=set(),
+ )
+ length = FloatProperty(
+ name="Length",
+ description="Seconds to spend fading",
+ default=1.0,
+ min=0.0,
+ options=set(),
+ subtype="TIME",
+ unit="TIME",
+ )
class PlasmaSound(idprops.IDPropMixin, bpy.types.PropertyGroup):
@@ -195,86 +273,125 @@ class PlasmaSound(idprops.IDPropMixin, bpy.types.PropertyGroup):
def _update_name(self, context=None):
if self.is_stereo and self.channel != {"L", "R"}:
- self.name = "{}:{}".format(self._sound_name, "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_name
enabled = BoolProperty(name="Enabled", default=True, options=set())
- sound = PointerProperty(name="Sound",
- description="Sound Datablock",
- type=bpy.types.Sound,
- update=_update_sound)
- updating_sound = BoolProperty(default=False,
- options={"HIDDEN", "SKIP_SAVE"})
+ sound = PointerProperty(
+ name="Sound",
+ description="Sound Datablock",
+ type=bpy.types.Sound,
+ update=_update_sound,
+ )
+ updating_sound = BoolProperty(default=False, options={"HIDDEN", "SKIP_SAVE"})
is_stereo = BoolProperty(default=True, options={"HIDDEN"})
is_valid = BoolProperty(default=False, options={"HIDDEN"})
- sfx_region = PointerProperty(name="Soft Volume",
- description="Soft region this sound can be heard in",
- type=bpy.types.Object,
- poll=idprops.poll_softvolume_objects)
-
- sfx_type = EnumProperty(name="Category",
- description="Describes the purpose of this sound",
- items=[("kSoundFX", "3D", "3D Positional SoundFX"),
- ("kAmbience", "Ambience", "Ambient Sounds"),
- ("kBackgroundMusic", "Music", "Background Music"),
- ("kGUISound", "GUI", "GUI Effect"),
- ("kNPCVoices", "NPC", "NPC Speech")],
- options=set())
- channel = EnumProperty(name="Channel",
- description="Which channel(s) to play",
- items=[("L", "Left", "Left Channel"),
- ("R", "Right", "Right Channel")],
- options={"ENUM_FLAG"},
- default={"L", "R"},
- update=_update_name)
-
- auto_start = BoolProperty(name="Auto Start",
- description="Start playing when the age is loaded",
- default=False,
- options=set())
- incidental = BoolProperty(name="Incidental",
- description="Sound is a low-priority incident and the engine may forgo playback",
- default=False,
- options=set())
- loop = BoolProperty(name="Loop",
- description="Loop the sound",
- default=False,
- options=set())
-
- inner_cone = FloatProperty(name="Inner Angle",
- description="Angle of the inner cone from the negative Z-axis",
- min=0, max=math.radians(360), default=0, step=100,
- options=set(),
- subtype="ANGLE")
- outer_cone = FloatProperty(name="Outer Angle",
- description="Angle of the outer cone from the negative Z-axis",
- min=0, max=math.radians(360), default=math.radians(360), step=100,
- options=set(),
- subtype="ANGLE")
- outside_volume = IntProperty(name="Outside Volume",
- description="Sound's volume when outside the outer cone",
- min=0, max=100, default=100,
- options=set(),
- subtype="PERCENTAGE")
-
- min_falloff = IntProperty(name="Begin Falloff",
- description="Distance where volume attenuation begins",
- min=0, max=1000000000, default=1,
- options=set(),
- subtype="DISTANCE")
- max_falloff = IntProperty(name="End Falloff",
- description="Distance where the sound is inaudible",
- min=0, max=1000000000, default=1000,
- options=set(),
- subtype="DISTANCE")
- volume = IntProperty(name="Volume",
- description="Volume to play the sound",
- min=0, max=100, default=100,
- options={"ANIMATABLE"},
- subtype="PERCENTAGE")
+ sfx_region = PointerProperty(
+ name="Soft Volume",
+ description="Soft region this sound can be heard in",
+ type=bpy.types.Object,
+ poll=idprops.poll_softvolume_objects,
+ )
+
+ sfx_type = EnumProperty(
+ name="Category",
+ description="Describes the purpose of this sound",
+ items=[
+ ("kSoundFX", "3D", "3D Positional SoundFX"),
+ ("kAmbience", "Ambience", "Ambient Sounds"),
+ ("kBackgroundMusic", "Music", "Background Music"),
+ ("kGUISound", "GUI", "GUI Effect"),
+ ("kNPCVoices", "NPC", "NPC Speech"),
+ ],
+ options=set(),
+ )
+ channel = EnumProperty(
+ name="Channel",
+ description="Which channel(s) to play",
+ items=[("L", "Left", "Left Channel"), ("R", "Right", "Right Channel")],
+ options={"ENUM_FLAG"},
+ default={"L", "R"},
+ update=_update_name,
+ )
+
+ auto_start = BoolProperty(
+ name="Auto Start",
+ description="Start playing when the age is loaded",
+ default=False,
+ options=set(),
+ )
+ incidental = BoolProperty(
+ name="Incidental",
+ description="Sound is a low-priority incident and the engine may forgo playback",
+ default=False,
+ options=set(),
+ )
+ loop = BoolProperty(
+ name="Loop", description="Loop the sound", default=False, options=set()
+ )
+
+ inner_cone = FloatProperty(
+ name="Inner Angle",
+ description="Angle of the inner cone from the negative Z-axis",
+ min=0,
+ max=math.radians(360),
+ default=0,
+ step=100,
+ options=set(),
+ subtype="ANGLE",
+ )
+ outer_cone = FloatProperty(
+ name="Outer Angle",
+ description="Angle of the outer cone from the negative Z-axis",
+ min=0,
+ max=math.radians(360),
+ default=math.radians(360),
+ step=100,
+ options=set(),
+ subtype="ANGLE",
+ )
+ outside_volume = IntProperty(
+ name="Outside Volume",
+ description="Sound's volume when outside the outer cone",
+ min=0,
+ max=100,
+ default=100,
+ options=set(),
+ subtype="PERCENTAGE",
+ )
+
+ min_falloff = IntProperty(
+ name="Begin Falloff",
+ description="Distance where volume attenuation begins",
+ min=0,
+ max=1000000000,
+ default=1,
+ options=set(),
+ subtype="DISTANCE",
+ )
+ max_falloff = IntProperty(
+ name="End Falloff",
+ description="Distance where the sound is inaudible",
+ min=0,
+ max=1000000000,
+ default=1000,
+ options=set(),
+ subtype="DISTANCE",
+ )
+ volume = IntProperty(
+ name="Volume",
+ description="Volume to play the sound",
+ min=0,
+ max=100,
+ default=100,
+ options={"ANIMATABLE"},
+ subtype="PERCENTAGE",
+ )
fade_in = PointerProperty(type=PlasmaSfxFade, options=set())
fade_out = PointerProperty(type=PlasmaSfxFade, options=set())
@@ -291,10 +408,13 @@ class PlasmaSound(idprops.IDPropMixin, bpy.types.PropertyGroup):
# This is really a property of the sound itself, not of this particular emitter instance.
# However, to prevent weird UI inconsistencies where the button might be missing or change
# states when clearing the sound pointer, we'll cache the actual value here.
- package = BoolProperty(name="Export",
- description="Package this file in the age export",
- get=_get_package_value, set=_set_package_value,
- options=set())
+ package = BoolProperty(
+ name="Export",
+ description="Package this file in the age export",
+ get=_get_package_value,
+ set=_set_package_value,
+ options=set(),
+ )
package_value = BoolProperty(options={"HIDDEN", "SKIP_SAVE"})
@property
@@ -331,10 +451,23 @@ class PlasmaSound(idprops.IDPropMixin, bpy.types.PropertyGroup):
header.blockAlign = int(header.blockAlign / 2)
dataSize = int(dataSize / 2)
if self.is_3d_stereo:
- audible.addSound(self._convert_sound(exporter, so, pClass, header, dataSize, channel="L"))
- audible.addSound(self._convert_sound(exporter, so, pClass, header, dataSize, channel="R"))
+ audible.addSound(
+ self._convert_sound(exporter, so, pClass, header, dataSize, channel="L")
+ )
+ audible.addSound(
+ self._convert_sound(exporter, so, pClass, header, dataSize, channel="R")
+ )
else:
- audible.addSound(self._convert_sound(exporter, so, pClass, header, dataSize, channel=self.channel_override))
+ audible.addSound(
+ self._convert_sound(
+ exporter,
+ so,
+ pClass,
+ header,
+ dataSize,
+ channel=self.channel_override,
+ )
+ )
def _convert_sound(self, exporter, so, pClass, wavHeader, dataSize, channel=None):
if channel is None:
@@ -352,10 +485,18 @@ class PlasmaSound(idprops.IDPropMixin, bpy.types.PropertyGroup):
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_name, self.sfx_region.name))
+ 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
+ sv_key.object.listenState |= (
+ plSoftVolume.kListenCheck
+ | plSoftVolume.kListenDirty
+ | plSoftVolume.kListenRegistered
+ )
sound.softRegion = sv_key
# Sound
@@ -368,7 +509,9 @@ class PlasmaSound(idprops.IDPropMixin, bpy.types.PropertyGroup):
sound.properties |= plSound.kPropLooping
if self.incidental:
sound.properties |= plSound.kPropIncidental
- sound.dataBuffer = self._find_sound_buffer(exporter, so, wavHeader, dataSize, channel)
+ sound.dataBuffer = self._find_sound_buffer(
+ exporter, so, wavHeader, dataSize, channel
+ )
# Cone effect
# I have observed that Blender 2.77's UI doesn't show the appropriate unit (degrees) for
@@ -473,20 +616,26 @@ class PlasmaSound(idprops.IDPropMixin, bpy.types.PropertyGroup):
@classmethod
def _idprop_mapping(cls):
- return {"sound": "sound_data",
- "sfx_region": "soft_region"}
+ return {"sound": "sound_data", "sfx_region": "soft_region"}
def _idprop_sources(self):
- return {"sound_data": bpy.data.sounds,
- "soft_region": bpy.data.objects}
+ 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
+ return (
+ self.sfx_type == "kSoundFX"
+ and self.channel == {"L", "R"}
+ and self.is_stereo
+ )
def _raise_error(self, msg):
if self.sound:
- raise ExportError("SoundEmitter '{}': Sound '{}' {}".format(self.id_data.name, self.sound.name, msg))
+ raise ExportError(
+ "SoundEmitter '{}': Sound '{}' {}".format(
+ self.id_data.name, self.sound.name, msg
+ )
+ )
else:
raise ExportError("SoundEmitter '{}': {}".format(self.id_data.name, msg))
@@ -515,9 +664,13 @@ class PlasmaSoundEmitter(PlasmaModifierProperties):
active_sound_index = IntProperty(options={"HIDDEN"})
def export(self, exporter, bo, so):
- winaud = exporter.mgr.find_create_object(plWinAudible, so=so, name=self.key_name)
+ winaud = exporter.mgr.find_create_object(
+ plWinAudible, so=so, name=self.key_name
+ )
winaud.sceneNode = exporter.mgr.get_scene_node(so.key.location)
- aiface = exporter.mgr.find_create_object(plAudioInterface, so=so, name=self.key_name)
+ aiface = exporter.mgr.find_create_object(
+ plAudioInterface, so=so, name=self.key_name
+ )
aiface.audible = winaud.key
# Pass this off to each individual sound for conversion
@@ -527,7 +680,7 @@ class PlasmaSoundEmitter(PlasmaModifierProperties):
def get_sound_indices(self, name=None, sound=None):
"""Returns the index of the given sound in the plWin32Sound. This is needed because stereo
- 3D sounds export as two mono sound objects -- wheeeeee"""
+ 3D sounds export as two mono sound objects -- wheeeeee"""
assert name or sound
idx = 0
diff --git a/korman/properties/modifiers/water.py b/korman/properties/modifiers/water.py
index 891c7fb..41642e5 100644
--- a/korman/properties/modifiers/water.py
+++ b/korman/properties/modifiers/water.py
@@ -22,7 +22,10 @@ from .base import PlasmaModifierProperties
from ...exporter import ExportError, ExportAssertionError
from ... import idprops
-class PlasmaSwimRegion(idprops.IDPropObjectMixin, PlasmaModifierProperties, bpy.types.PropertyGroup):
+
+class PlasmaSwimRegion(
+ idprops.IDPropObjectMixin, PlasmaModifierProperties, bpy.types.PropertyGroup
+):
pl_id = "swimregion"
bl_category = "Water"
@@ -36,54 +39,94 @@ class PlasmaSwimRegion(idprops.IDPropObjectMixin, PlasmaModifierProperties, bpy.
"STRAIGHT": plSwimStraightCurrentRegion,
}
- region = PointerProperty(name="Region",
- description="Swimming detector region",
- type=bpy.types.Object,
- poll=idprops.poll_mesh_objects)
-
- down_buoyancy = FloatProperty(name="Downward Buoyancy",
- description="Distance the avatar sinks into the water",
- min=0.0, max=100.0, default=3.0,
- options=set())
- up_buoyancy = FloatProperty(name="Up Buoyancy",
- description="Distance the avatar rises up after sinking",
- min=0.0, max=100.0, default=0.05,
- options=set())
- up_velocity = FloatProperty(name="Up Velcocity",
- description="Rate at which the avatar rises",
- min=0.0, max=100.0, default=3.0,
- options=set())
-
- current_type = EnumProperty(name="Water Current",
- description="",
- items=[("NONE", "None", "No current"),
- ("CIRCULAR", "Circular", "Circular current"),
- ("STRAIGHT", "Straight", "Straight current")],
- options=set())
- rotation = FloatProperty(name="Rotation",
- description="Rate of rotation about the current object",
- min=-100.0, max=100.0, default=1.0,
- options=set())
- near_distance = FloatProperty(name="Near Distance",
- description="Maximum distance at which the current is at the Near Velocity rate",
- min=0.0, max=10000.0, default=1.0,
- options=set())
- far_distance = FloatProperty(name="Far Distance",
- description="Distance at which the current is at the Far Velocity rate",
- min=0.0, max=10000.0, default=1.0,
- options=set())
- near_velocity = FloatProperty(name="Near Velocity",
- description="Current velocity near the region center",
- min=-100.0, max=100.0, default=0.0,
- options=set())
- far_velocity = FloatProperty(name="Far Velocity",
- description="Current velocity far from the region center",
- min=-100.0, max=100.0, default=0.0,
- options=set())
- current = PointerProperty(name="Current Object",
- description="Object whose Y-axis defines the direction of the current",
- type=bpy.types.Object,
- poll=idprops.poll_empty_objects)
+ region = PointerProperty(
+ name="Region",
+ description="Swimming detector region",
+ type=bpy.types.Object,
+ poll=idprops.poll_mesh_objects,
+ )
+
+ down_buoyancy = FloatProperty(
+ name="Downward Buoyancy",
+ description="Distance the avatar sinks into the water",
+ min=0.0,
+ max=100.0,
+ default=3.0,
+ options=set(),
+ )
+ up_buoyancy = FloatProperty(
+ name="Up Buoyancy",
+ description="Distance the avatar rises up after sinking",
+ min=0.0,
+ max=100.0,
+ default=0.05,
+ options=set(),
+ )
+ up_velocity = FloatProperty(
+ name="Up Velcocity",
+ description="Rate at which the avatar rises",
+ min=0.0,
+ max=100.0,
+ default=3.0,
+ options=set(),
+ )
+
+ current_type = EnumProperty(
+ name="Water Current",
+ description="",
+ items=[
+ ("NONE", "None", "No current"),
+ ("CIRCULAR", "Circular", "Circular current"),
+ ("STRAIGHT", "Straight", "Straight current"),
+ ],
+ options=set(),
+ )
+ rotation = FloatProperty(
+ name="Rotation",
+ description="Rate of rotation about the current object",
+ min=-100.0,
+ max=100.0,
+ default=1.0,
+ options=set(),
+ )
+ near_distance = FloatProperty(
+ name="Near Distance",
+ description="Maximum distance at which the current is at the Near Velocity rate",
+ min=0.0,
+ max=10000.0,
+ default=1.0,
+ options=set(),
+ )
+ far_distance = FloatProperty(
+ name="Far Distance",
+ description="Distance at which the current is at the Far Velocity rate",
+ min=0.0,
+ max=10000.0,
+ default=1.0,
+ options=set(),
+ )
+ near_velocity = FloatProperty(
+ name="Near Velocity",
+ description="Current velocity near the region center",
+ min=-100.0,
+ max=100.0,
+ default=0.0,
+ options=set(),
+ )
+ far_velocity = FloatProperty(
+ name="Far Velocity",
+ description="Current velocity far from the region center",
+ min=-100.0,
+ max=100.0,
+ default=0.0,
+ options=set(),
+ )
+ current = PointerProperty(
+ name="Current Object",
+ description="Object whose Y-axis defines the direction of the current",
+ type=bpy.types.Object,
+ poll=idprops.poll_empty_objects,
+ )
def export(self, exporter, bo, so):
swimIface = self.get_key(exporter, so).object
@@ -101,10 +144,18 @@ class PlasmaSwimRegion(idprops.IDPropObjectMixin, PlasmaModifierProperties, bpy.
swimIface.farDist = self.far_distance
swimIface.nearVel = self.near_velocity
swimIface.farVel = self.far_velocity
- if isinstance(swimIface, (plSwimCircularCurrentRegion, plSwimStraightCurrentRegion)):
+ if isinstance(
+ swimIface, (plSwimCircularCurrentRegion, plSwimStraightCurrentRegion)
+ ):
if self.current is None:
- raise ExportError("Swimming Surface '{}' does not specify a current object".format(bo.name))
- swimIface.currentObj = exporter.mgr.find_create_key(plSceneObject, bl=self.current)
+ raise ExportError(
+ "Swimming Surface '{}' does not specify a current object".format(
+ bo.name
+ )
+ )
+ swimIface.currentObj = exporter.mgr.find_create_key(
+ plSceneObject, bl=self.current
+ )
# The surface needs bounds for LOS -- this is generally a flat plane, or I would think...
# NOTE: If the artist has this on a WaveSet, they probably intend for the avatar to swim on
@@ -112,25 +163,38 @@ class PlasmaSwimRegion(idprops.IDPropObjectMixin, PlasmaModifierProperties, bpy.
# pool. Therefore, we need to flatten out a temporary mesh in that case.
# Ohey! CWE doesn't let you swim at all if the surface isn't flat...
losdbs = ["kLOSDBSwimRegion"]
- member_group = "kGroupLOSOnly" if exporter.mgr.getVer() != pvMoul else "kGroupStatic"
+ member_group = (
+ "kGroupLOSOnly" if exporter.mgr.getVer() != pvMoul else "kGroupStatic"
+ )
if bo.plasma_modifiers.water_basic.enabled:
- exporter.physics.generate_flat_proxy(bo, so, z_coord=bo.matrix_world.translation[2],
- member_group=member_group,
- losdbs=losdbs)
+ exporter.physics.generate_flat_proxy(
+ bo,
+ so,
+ z_coord=bo.matrix_world.translation[2],
+ member_group=member_group,
+ losdbs=losdbs,
+ )
else:
- exporter.physics.generate_physical(bo, so, bounds="trimesh",
- member_group=member_group,
- losdbs=losdbs)
+ exporter.physics.generate_physical(
+ bo, so, bounds="trimesh", member_group=member_group, losdbs=losdbs
+ )
# Detector region bounds
if self.region is not None:
region_so = exporter.mgr.find_create_object(plSceneObject, bl=self.region)
# Good news: if this phys has already been exported, this is basically a noop
- member_group = "kGroupDetector" if exporter.mgr.getVer() == "pvMoul" else "kGroupLOSOnly"
- exporter.physics.generate_physical(self.region, region_so,
- member_group=member_group,
- report_groups=["kGroupAvatar"])
+ member_group = (
+ "kGroupDetector"
+ if exporter.mgr.getVer() == "pvMoul"
+ else "kGroupLOSOnly"
+ )
+ exporter.physics.generate_physical(
+ self.region,
+ region_so,
+ member_group=member_group,
+ report_groups=["kGroupAvatar"],
+ )
# I am a little concerned if we already have a plSwimDetector... I am not certain how
# well Plasma would tolerate having a plSwimMsg with multiple regions referenced.
@@ -141,7 +205,9 @@ class PlasmaSwimRegion(idprops.IDPropObjectMixin, PlasmaModifierProperties, bpy.
if exporter.mgr.find_key(plSwimDetector, so=region_so) is None:
enter_msg, exit_msg = plSwimMsg(), plSwimMsg()
for i in (enter_msg, exit_msg):
- i.BCastFlags = plMessage.kLocalPropagate | plMessage.kPropagateToModifiers
+ i.BCastFlags = (
+ plMessage.kLocalPropagate | plMessage.kPropagateToModifiers
+ )
i.sender = region_so.key
i.swimRegion = swimIface.key
enter_msg.isEntering = True
@@ -156,7 +222,12 @@ class PlasmaSwimRegion(idprops.IDPropObjectMixin, PlasmaModifierProperties, bpy.
# swimming surface should have a detector. m'kay? But still, we might want to make note
# of this sitation. Just in case someone is like "WTF! Why am I not swimming?!?!1111111"
# Because you need to have a detector, dummy.
- exporter.report.warn("Swimming Surface '{}' does not specify a detector region".format(bo.name), indent=2)
+ exporter.report.warn(
+ "Swimming Surface '{}' does not specify a detector region".format(
+ bo.name
+ ),
+ indent=2,
+ )
def get_key(self, exporter, so=None):
pClass = self._CURRENTS[self.current_type]
@@ -169,72 +240,66 @@ class PlasmaSwimRegion(idprops.IDPropObjectMixin, PlasmaModifierProperties, bpy.
@classmethod
def _idprop_mapping(cls):
- return {"current": "current_object",
- "region": "region_name"}
+ return {"current": "current_object", "region": "region_name"}
-class PlasmaWaterModifier(idprops.IDPropMixin, PlasmaModifierProperties, bpy.types.PropertyGroup):
+class PlasmaWaterModifier(
+ idprops.IDPropMixin, PlasmaModifierProperties, bpy.types.PropertyGroup
+):
pl_id = "water_basic"
bl_category = "Water"
bl_label = "Basic Water"
bl_description = "Basic water properties"
- wind_object = PointerProperty(name="Wind Object",
- description="Object whose Y axis represents the wind direction",
- type=bpy.types.Object,
- poll=idprops.poll_empty_objects)
- wind_speed = FloatProperty(name="Wind Speed",
- description="Magnitude of the wind",
- default=1.0)
- envmap = PointerProperty(name="EnvMap",
- description="Texture defining an environment map for this water object",
- type=bpy.types.Texture,
- poll=idprops.poll_envmap_textures)
- envmap_radius = FloatProperty(name="Environment Sphere Radius",
- description="How far away the first object you want to see is",
- min=5.0, max=10000.0,
- default=500.0)
-
- specular_tint = FloatVectorProperty(name="Specular Tint",
- subtype="COLOR",
- min=0.0, max=1.0,
- default=(1.0, 1.0, 1.0))
- specular_alpha = FloatProperty(name="Specular Alpha",
- min=0.0, max=1.0,
- default=0.3)
- noise = IntProperty(name="Noise",
- subtype="PERCENTAGE",
- min=0, max=300,
- default=50)
- specular_start = FloatProperty(name="Specular Start",
- min=0.0, max=1000.0,
- default=50.0)
- specular_end = FloatProperty(name="Specular End",
- min=0.0, max=10000.0,
- default=1000.0)
- ripple_scale = FloatProperty(name="Ripple Scale",
- min=5.0, max=1000.0,
- default=25.0)
-
- depth_opacity = FloatProperty(name="Opacity End",
- min=0.5, max=20.0,
- default=3.0)
- depth_reflection = FloatProperty(name="Reflection End",
- min=0.5, max=20.0,
- default=3.0)
- depth_wave = FloatProperty(name="Wave End",
- min=0.5, max=20.0,
- default=4.0)
- zero_opacity = FloatProperty(name="Opacity Start",
- min=-10.0, max=10.0,
- default=-1.0)
- zero_reflection = FloatProperty(name="Reflection Start",
- min=-10.0, max=10.0,
- default=0.0)
- zero_wave = FloatProperty(name="Wave Start",
- min=-10.0, max=10.0,
- default=0.0)
+ wind_object = PointerProperty(
+ name="Wind Object",
+ description="Object whose Y axis represents the wind direction",
+ type=bpy.types.Object,
+ poll=idprops.poll_empty_objects,
+ )
+ wind_speed = FloatProperty(
+ name="Wind Speed", description="Magnitude of the wind", default=1.0
+ )
+ envmap = PointerProperty(
+ name="EnvMap",
+ description="Texture defining an environment map for this water object",
+ type=bpy.types.Texture,
+ poll=idprops.poll_envmap_textures,
+ )
+ envmap_radius = FloatProperty(
+ name="Environment Sphere Radius",
+ description="How far away the first object you want to see is",
+ min=5.0,
+ max=10000.0,
+ default=500.0,
+ )
+
+ specular_tint = FloatVectorProperty(
+ name="Specular Tint", subtype="COLOR", min=0.0, max=1.0, default=(1.0, 1.0, 1.0)
+ )
+ specular_alpha = FloatProperty(name="Specular Alpha", min=0.0, max=1.0, default=0.3)
+ noise = IntProperty(name="Noise", subtype="PERCENTAGE", min=0, max=300, default=50)
+ specular_start = FloatProperty(
+ name="Specular Start", min=0.0, max=1000.0, default=50.0
+ )
+ specular_end = FloatProperty(
+ name="Specular End", min=0.0, max=10000.0, default=1000.0
+ )
+ ripple_scale = FloatProperty(name="Ripple Scale", min=5.0, max=1000.0, default=25.0)
+
+ depth_opacity = FloatProperty(name="Opacity End", min=0.5, max=20.0, default=3.0)
+ depth_reflection = FloatProperty(
+ name="Reflection End", min=0.5, max=20.0, default=3.0
+ )
+ depth_wave = FloatProperty(name="Wave End", min=0.5, max=20.0, default=4.0)
+ zero_opacity = FloatProperty(
+ name="Opacity Start", min=-10.0, max=10.0, default=-1.0
+ )
+ zero_reflection = FloatProperty(
+ name="Reflection Start", min=-10.0, max=10.0, default=0.0
+ )
+ zero_wave = FloatProperty(name="Wave Start", min=-10.0, max=10.0, default=0.0)
@property
def copy_material(self):
@@ -243,14 +308,21 @@ class PlasmaWaterModifier(idprops.IDPropMixin, PlasmaModifierProperties, bpy.typ
def export(self, exporter, bo, so):
waveset = exporter.mgr.find_create_object(plWaveSet7, name=bo.name, so=so)
if self.wind_object:
- if self.wind_object.plasma_object.enabled and self.wind_object.plasma_modifiers.animation.enabled:
- waveset.refObj = exporter.mgr.find_create_key(plSceneObject, bl=self.wind_object)
+ if (
+ self.wind_object.plasma_object.enabled
+ and self.wind_object.plasma_modifiers.animation.enabled
+ ):
+ waveset.refObj = exporter.mgr.find_create_key(
+ plSceneObject, bl=self.wind_object
+ )
waveset.setFlag(plWaveSet7.kHasRefObject, True)
# This is much like what happened in PyPRP
speed = self.wind_speed
matrix = self.wind_object.matrix_world
- wind_dir = hsVector3(matrix[1][0] * speed, matrix[1][1] * speed, matrix[1][2] * speed)
+ wind_dir = hsVector3(
+ matrix[1][0] * speed, matrix[1][1] * speed, matrix[1][2] * speed
+ )
else:
# Stolen shamelessly from PyPRP
wind_dir = hsVector3(0.0871562, 0.996195, 0.0)
@@ -260,15 +332,23 @@ class PlasmaWaterModifier(idprops.IDPropMixin, PlasmaModifierProperties, bpy.typ
state.rippleScale = self.ripple_scale
state.waterHeight = bo.matrix_world.translation[2]
state.windDir = wind_dir
- state.specVector = hsVector3(self.noise / 100.0, self.specular_start, self.specular_end)
+ state.specVector = hsVector3(
+ self.noise / 100.0, self.specular_start, self.specular_end
+ )
state.specularTint = hsColorRGBA(*self.specular_tint, alpha=self.specular_alpha)
- state.waterOffset = hsVector3(self.zero_opacity * -1.0, self.zero_reflection * -1.0, self.zero_wave * -1.0)
- state.depthFalloff = hsVector3(self.depth_opacity, self.depth_reflection, self.depth_wave)
+ state.waterOffset = hsVector3(
+ self.zero_opacity * -1.0, self.zero_reflection * -1.0, self.zero_wave * -1.0
+ )
+ state.depthFalloff = hsVector3(
+ self.depth_opacity, self.depth_reflection, self.depth_wave
+ )
# Environment Map
if self.envmap:
# maybe, just maybe, we're absuing our privledges?
- dem = exporter.mesh.material.export_dynamic_env(bo, None, self.envmap, plDynamicEnvMap)
+ dem = exporter.mesh.material.export_dynamic_env(
+ bo, None, self.envmap, plDynamicEnvMap
+ )
waveset.envMap = dem.key
state.envCenter = dem.position
state.envRefresh = dem.refreshRate
@@ -296,12 +376,10 @@ class PlasmaWaterModifier(idprops.IDPropMixin, PlasmaModifierProperties, bpy.typ
@classmethod
def _idprop_mapping(cls):
- return {"wind_object": "wind_object_name",
- "envmap": "envmap_name"}
+ return {"wind_object": "wind_object_name", "envmap": "envmap_name"}
def _idprop_sources(self):
- return {"wind_object_name": bpy.data.objects,
- "envmap_name": bpy.data.textures}
+ return {"wind_object_name": bpy.data.objects, "envmap_name": bpy.data.textures}
@property
def key_name(self):
@@ -310,10 +388,12 @@ class PlasmaWaterModifier(idprops.IDPropMixin, PlasmaModifierProperties, bpy.typ
class PlasmaShoreObject(idprops.IDPropObjectMixin, bpy.types.PropertyGroup):
display_name = StringProperty(name="Display Name")
- shore_object = PointerProperty(name="Shore Object",
- description="Object that waves crash upon",
- type=bpy.types.Object,
- poll=idprops.poll_mesh_objects)
+ shore_object = PointerProperty(
+ name="Shore Object",
+ description="Object that waves crash upon",
+ type=bpy.types.Object,
+ poll=idprops.poll_mesh_objects,
+ )
@classmethod
def _idprop_mapping(cls):
@@ -340,37 +420,50 @@ class PlasmaWaterShoreModifier(PlasmaModifierProperties):
shores = CollectionProperty(type=PlasmaShoreObject)
active_shore_index = IntProperty(options={"HIDDEN"})
- shore_tint = FloatVectorProperty(name="Shore Tint",
- subtype="COLOR",
- min=0.0, max=1.0,
- default=_shore_tint_default)
- shore_opacity = IntProperty(name="Shore Opacity",
- subtype="PERCENTAGE",
- min=0, max=100,
- default=_shore_opacity_default)
- wispiness = IntProperty(name="Wispiness",
- subtype="PERCENTAGE",
- min=0, max=200,
- default=_wispiness_default)
-
- period = FloatProperty(name="Period",
- min=0.0, max=200.0,
- default=_period_default)
- finger = FloatProperty(name="Finger",
- min=50.0, max=300.0,
- default=_finger_default)
- edge_opacity = IntProperty(name="Edge Opacity",
- subtype="PERCENTAGE",
- min=0, max=100,
- default=_edge_opacity_default)
- edge_radius = FloatProperty(name="Edge Radius",
- subtype="PERCENTAGE",
- min=50, max=300,
- default=_edge_radius_default)
+ shore_tint = FloatVectorProperty(
+ name="Shore Tint",
+ subtype="COLOR",
+ min=0.0,
+ max=1.0,
+ default=_shore_tint_default,
+ )
+ shore_opacity = IntProperty(
+ name="Shore Opacity",
+ subtype="PERCENTAGE",
+ min=0,
+ max=100,
+ default=_shore_opacity_default,
+ )
+ wispiness = IntProperty(
+ name="Wispiness",
+ subtype="PERCENTAGE",
+ min=0,
+ max=200,
+ default=_wispiness_default,
+ )
+
+ period = FloatProperty(name="Period", min=0.0, max=200.0, default=_period_default)
+ finger = FloatProperty(name="Finger", min=50.0, max=300.0, default=_finger_default)
+ edge_opacity = IntProperty(
+ name="Edge Opacity",
+ subtype="PERCENTAGE",
+ min=0,
+ max=100,
+ default=_edge_opacity_default,
+ )
+ edge_radius = FloatProperty(
+ name="Edge Radius",
+ subtype="PERCENTAGE",
+ min=50,
+ max=300,
+ default=_edge_radius_default,
+ )
def convert_default(self, wavestate):
wavestate.wispiness = self._wispiness_default / 100.0
- wavestate.minColor = hsColorRGBA(*self._shore_tint_default, alpha=(self._shore_opacity_default / 100.0))
+ wavestate.minColor = hsColorRGBA(
+ *self._shore_tint_default, alpha=(self._shore_opacity_default / 100.0)
+ )
wavestate.edgeOpacity = self._edge_opacity_default / 100.0
wavestate.edgeRadius = self._edge_radius_default / 100.0
wavestate.period = self._period_default / 100.0
@@ -382,11 +475,19 @@ class PlasmaWaterShoreModifier(PlasmaModifierProperties):
for i in self.shores:
if i.shore_object is None:
- raise ExportError("'{}': Shore Object for '{}' is invalid".format(self.key_name, i.display_name))
- waveset.addShore(exporter.mgr.find_create_key(plSceneObject, bl=i.shore_object))
+ raise ExportError(
+ "'{}': Shore Object for '{}' is invalid".format(
+ self.key_name, i.display_name
+ )
+ )
+ waveset.addShore(
+ exporter.mgr.find_create_key(plSceneObject, bl=i.shore_object)
+ )
wavestate.wispiness = self.wispiness / 100.0
- wavestate.minColor = hsColorRGBA(*self.shore_tint, alpha=(self.shore_opacity / 100.0))
+ wavestate.minColor = hsColorRGBA(
+ *self.shore_tint, alpha=(self.shore_opacity / 100.0)
+ )
wavestate.edgeOpacity = self.edge_opacity / 100.0
wavestate.edgeRadius = self.edge_radius / 100.0
wavestate.period = self.period / 100.0
@@ -413,28 +514,43 @@ class PlasmaWaveState:
@classmethod
def register(cls):
- cls.min_length = FloatProperty(name="Min Length",
- description="Smallest wave length",
- min=0.1, max=50.0,
- default=cls._min_length_default)
- cls.max_length = FloatProperty(name="Max Length",
- description="Largest wave length",
- min=0.1, max=50.0,
- default=cls._max_length_default)
- cls.amplitude = IntProperty(name="Amplitude",
- description="Multiplier for wave height",
- subtype="PERCENTAGE",
- min=0, max=100,
- default=cls._amplitude_default)
- cls.chop = IntProperty(name="Choppiness",
- description="Sharpness of wave crests",
- subtype="PERCENTAGE",
- min=0, max=500,
- default=cls._chop_default)
- cls.angle_dev = FloatProperty(name="Wave Spread",
- subtype="ANGLE",
- min=math.radians(0.0), max=math.radians(180.0),
- default=cls._angle_dev_default)
+ cls.min_length = FloatProperty(
+ name="Min Length",
+ description="Smallest wave length",
+ min=0.1,
+ max=50.0,
+ default=cls._min_length_default,
+ )
+ cls.max_length = FloatProperty(
+ name="Max Length",
+ description="Largest wave length",
+ min=0.1,
+ max=50.0,
+ default=cls._max_length_default,
+ )
+ cls.amplitude = IntProperty(
+ name="Amplitude",
+ description="Multiplier for wave height",
+ subtype="PERCENTAGE",
+ min=0,
+ max=100,
+ default=cls._amplitude_default,
+ )
+ cls.chop = IntProperty(
+ name="Choppiness",
+ description="Sharpness of wave crests",
+ subtype="PERCENTAGE",
+ min=0,
+ max=500,
+ default=cls._chop_default,
+ )
+ cls.angle_dev = FloatProperty(
+ name="Wave Spread",
+ subtype="ANGLE",
+ min=math.radians(0.0),
+ max=math.radians(180.0),
+ default=cls._angle_dev_default,
+ )
class PlasmaWaveGeoState(PlasmaWaveState, PlasmaModifierProperties):
diff --git a/korman/properties/prop_anim.py b/korman/properties/prop_anim.py
index 47fb593..cf3aaea 100644
--- a/korman/properties/prop_anim.py
+++ b/korman/properties/prop_anim.py
@@ -23,6 +23,7 @@ import functools
import itertools
from typing import Iterable, Iterator
+
class PlasmaAnimation(bpy.types.PropertyGroup):
ENTIRE_ANIMATION = "(Entire Animation)"
@@ -103,7 +104,7 @@ class PlasmaAnimation(bpy.types.PropertyGroup):
"entire_animation": {
bpy.types.Object: "plasma_modifiers.animation.initial_marker",
bpy.types.Texture: "plasma_layer.anim_initial_marker",
- }
+ },
},
"loop_start": {
"type": StringProperty,
@@ -144,10 +145,16 @@ class PlasmaAnimation(bpy.types.PropertyGroup):
def iter_frame_numbers(cls, id_data) -> Iterator[int]:
# It would be nice if this could use self.iter_fcurves, but the property that uses this
# is not actually of type PlasmaAnimation. Meaning that self is some other object (great).
- fcurves = itertools.chain.from_iterable((id.animation_data.action.fcurves
- for id in cls._iter_my_ids(id_data)
- if id.animation_data and id.animation_data.action))
- frame_numbers = (keyframe.co[0] for fcurve in fcurves for keyframe in fcurve.keyframe_points)
+ fcurves = itertools.chain.from_iterable(
+ (
+ id.animation_data.action.fcurves
+ for id in cls._iter_my_ids(id_data)
+ if id.animation_data and id.animation_data.action
+ )
+ )
+ frame_numbers = (
+ keyframe.co[0] for fcurve in fcurves for keyframe in fcurve.keyframe_points
+ )
yield from frame_numbers
@classmethod
@@ -203,13 +210,14 @@ class PlasmaAnimation(bpy.types.PropertyGroup):
if self.is_entire_animation:
attr_path = cls._get_from_class_lut(self.id_data, lut)
if attr_path is not None:
- prop_delim = attr_path.rfind('.')
+ prop_delim = attr_path.rfind(".")
prop_group = self.id_data.path_resolve(attr_path[:prop_delim])
- return getattr(prop_group, attr_path[prop_delim+1:])
+ return getattr(prop_group, attr_path[prop_delim + 1 :])
else:
return default
else:
return getattr(self, "{}_value".format(prop_name))
+
return proc
@classmethod
@@ -218,11 +226,12 @@ class PlasmaAnimation(bpy.types.PropertyGroup):
if self.is_entire_animation:
attr_path = cls._get_from_class_lut(self.id_data, lut)
if attr_path is not None:
- prop_delim = attr_path.rfind('.')
+ prop_delim = attr_path.rfind(".")
prop_group = self.id_data.path_resolve(attr_path[:prop_delim])
- setattr(prop_group, attr_path[prop_delim+1:], value)
+ setattr(prop_group, attr_path[prop_delim + 1 :], value)
else:
setattr(self, "{}_value".format(prop_name), value)
+
return proc
@classmethod
@@ -238,27 +247,39 @@ class PlasmaAnimation(bpy.types.PropertyGroup):
value_kwargs = deepcopy(kwargs)
value_kwargs["options"].add("HIDDEN")
- value_props = { key: value for key, value in props.items() if key not in {"get", "set", "update"} }
- setattr(cls, "{}_value".format(prop_name), definitions["type"](**value_props, **value_kwargs))
+ value_props = {
+ key: value
+ for key, value in props.items()
+ if key not in {"get", "set", "update"}
+ }
+ setattr(
+ cls,
+ "{}_value".format(prop_name),
+ definitions["type"](**value_props, **value_kwargs),
+ )
needs_accessors = "get" not in props and "set" not in props
if needs_accessors:
# We have to use these weirdo wrappers because Blender only accepts function objects
# for its property callbacks, not arbitrary callables eg lambdas, functools.partials.
- kwargs["get"] = cls._make_prop_getter(prop_name, definitions["entire_animation"], props.get("default"))
- kwargs["set"] = cls._make_prop_setter(prop_name, definitions["entire_animation"])
+ kwargs["get"] = cls._make_prop_getter(
+ prop_name, definitions["entire_animation"], props.get("default")
+ )
+ kwargs["set"] = cls._make_prop_setter(
+ prop_name, definitions["entire_animation"]
+ )
setattr(cls, prop_name, definitions["type"](**props, **kwargs))
@classmethod
def register_entire_animation(cls, id_type, rna_type):
"""Registers all of the properties for the old style single animation per ID animations onto
- the property group given by `rna_type`. These were previously directly registered but are
- now abstracted away to serve as the backing store for the new "entire animation" method."""
+ the property group given by `rna_type`. These were previously directly registered but are
+ now abstracted away to serve as the backing store for the new "entire animation" method."""
for prop_name, definitions in cls._PROPERTIES.items():
lut = definitions.get("entire_animation", {})
path_from_id = lut.get(id_type)
if path_from_id:
- attr_name = path_from_id[path_from_id.rfind('.')+1:]
+ attr_name = path_from_id[path_from_id.rfind(".") + 1 :]
kwargs = deepcopy(definitions["property"])
kwargs.update(cls._ENTIRE_ANIMATION_PROPERTIES.get(prop_name, {}))
setattr(rna_type, attr_name, definitions["type"](**kwargs))
@@ -277,8 +298,9 @@ class PlasmaAnimationCollection(bpy.types.PropertyGroup):
def _set_active_index(self, value: int) -> None:
self.active_animation_index_value = value
- active_animation_index = IntProperty(get=_get_active_index, set=_set_active_index,
- options={"HIDDEN"})
+ active_animation_index = IntProperty(
+ get=_get_active_index, set=_set_active_index, options={"HIDDEN"}
+ )
active_animation_index_value = IntProperty(options={"HIDDEN"})
# Animations backing store--don't use this except to display the list in Blender's UI.
@@ -298,7 +320,9 @@ class PlasmaAnimationCollection(bpy.types.PropertyGroup):
# want to observe it in the UI. That restriction is dropped, however, when RNA poperties are
# being observed or set. So, this will allow us to initialize the entire animation in the
# UI phase at the penalty of potentially having to loop through the animation collection twice.
- request_entire_animation = BoolProperty(get=_get_hack, set=_set_hack, options={"HIDDEN"})
+ request_entire_animation = BoolProperty(
+ get=_get_hack, set=_set_hack, options={"HIDDEN"}
+ )
@property
def animations(self) -> Iterable[PlasmaAnimation]:
diff --git a/korman/properties/prop_camera.py b/korman/properties/prop_camera.py
index 0be191f..7ef0434 100644
--- a/korman/properties/prop_camera.py
+++ b/korman/properties/prop_camera.py
@@ -19,213 +19,395 @@ import math
from .. import idprops
-camera_types = [("circle", "Circle Camera", "The camera circles a fixed point"),
- ("follow", "Follow Camera", "The camera follows an object"),
- ("fixed", "Fixed Camera", "The camera is fixed in one location"),
- ("rail", "Rail Camera", "The camera follows an object by moving along a line"),
- ("firstperson", "First Person", "Simulates first person view and disappears avatar")]
+camera_types = [
+ ("circle", "Circle Camera", "The camera circles a fixed point"),
+ ("follow", "Follow Camera", "The camera follows an object"),
+ ("fixed", "Fixed Camera", "The camera is fixed in one location"),
+ ("rail", "Rail Camera", "The camera follows an object by moving along a line"),
+ (
+ "firstperson",
+ "First Person",
+ "Simulates first person view and disappears avatar",
+ ),
+]
+
class PlasmaTransition(bpy.types.PropertyGroup):
- poa_acceleration = FloatProperty(name="PoA Acceleration",
- description="Rate the camera's Point of Attention tracking velocity increases in feet per second squared",
- min=-100.0, max=100.0, precision=0, default=60.0,
- unit="ACCELERATION", options=set())
- poa_deceleration = FloatProperty(name="PoA Deceleration",
- description="Rate the camera's Point of Attention tracking velocity decreases in feet per second squared",
- min=-100.0, max=100.0, precision=0, default=60.0,
- unit="ACCELERATION", options=set())
- poa_velocity = FloatProperty(name="PoA Velocity",
- description="Maximum velocity of the camera's Point of Attention tracking",
- min=-100.0, max=100.0, precision=0, default=60.0,
- unit="VELOCITY", options=set())
- poa_cut = BoolProperty(name="Cut",
- description="The camera immediately begins tracking the Point of Attention",
- options=set())
+ poa_acceleration = FloatProperty(
+ name="PoA Acceleration",
+ description="Rate the camera's Point of Attention tracking velocity increases in feet per second squared",
+ min=-100.0,
+ max=100.0,
+ precision=0,
+ default=60.0,
+ unit="ACCELERATION",
+ options=set(),
+ )
+ poa_deceleration = FloatProperty(
+ name="PoA Deceleration",
+ description="Rate the camera's Point of Attention tracking velocity decreases in feet per second squared",
+ min=-100.0,
+ max=100.0,
+ precision=0,
+ default=60.0,
+ unit="ACCELERATION",
+ options=set(),
+ )
+ poa_velocity = FloatProperty(
+ name="PoA Velocity",
+ description="Maximum velocity of the camera's Point of Attention tracking",
+ min=-100.0,
+ max=100.0,
+ precision=0,
+ default=60.0,
+ unit="VELOCITY",
+ options=set(),
+ )
+ poa_cut = BoolProperty(
+ name="Cut",
+ description="The camera immediately begins tracking the Point of Attention",
+ options=set(),
+ )
- pos_acceleration = FloatProperty(name="Position Acceleration",
- description="Rate the camera's positional velocity increases in feet per second squared",
- min=-100.0, max=100.0, precision=0, default=60.0,
- unit="ACCELERATION", options=set())
- pos_deceleration = FloatProperty(name="Position Deceleration",
- description="Rate the camera's positional velocity decreases in feet per second squared",
- min=-100.0, max=100.0, precision=0, default=60.0,
- unit="ACCELERATION", options=set())
- pos_velocity = FloatProperty(name="Position Max Velocity",
- description="Maximum positional velocity of the camera",
- min=-100.0, max=100.0, precision=0, default=60.0,
- unit="VELOCITY", options=set())
- pos_cut = BoolProperty(name="Cut",
- description="The camera immediately moves to its new position",
- options=set())
+ pos_acceleration = FloatProperty(
+ name="Position Acceleration",
+ description="Rate the camera's positional velocity increases in feet per second squared",
+ min=-100.0,
+ max=100.0,
+ precision=0,
+ default=60.0,
+ unit="ACCELERATION",
+ options=set(),
+ )
+ pos_deceleration = FloatProperty(
+ name="Position Deceleration",
+ description="Rate the camera's positional velocity decreases in feet per second squared",
+ min=-100.0,
+ max=100.0,
+ precision=0,
+ default=60.0,
+ unit="ACCELERATION",
+ options=set(),
+ )
+ pos_velocity = FloatProperty(
+ name="Position Max Velocity",
+ description="Maximum positional velocity of the camera",
+ min=-100.0,
+ max=100.0,
+ precision=0,
+ default=60.0,
+ unit="VELOCITY",
+ options=set(),
+ )
+ pos_cut = BoolProperty(
+ name="Cut",
+ description="The camera immediately moves to its new position",
+ options=set(),
+ )
class PlasmaManualTransition(bpy.types.PropertyGroup):
- camera = PointerProperty(name="Camera",
- description="The camera from which this transition is intended",
- type=bpy.types.Object,
- poll=idprops.poll_camera_objects,
- options=set())
+ camera = PointerProperty(
+ name="Camera",
+ description="The camera from which this transition is intended",
+ type=bpy.types.Object,
+ poll=idprops.poll_camera_objects,
+ options=set(),
+ )
transition = PointerProperty(type=PlasmaTransition, options=set())
- mode = EnumProperty(name="Transition Mode",
- description="Type of transition that should occur between the two cameras",
- items=[("ignore", "Ignore Camera", "Ignore this camera and do not transition"),
- ("auto", "Auto", "Auto transition as defined by the two cameras' properies"),
- ("manual", "Manual", "Manually defined transition")],
- default="auto",
- options=set())
- enabled = BoolProperty(name="Enabled",
- description="Export this transition",
- default=True,
- options=set())
+ mode = EnumProperty(
+ name="Transition Mode",
+ description="Type of transition that should occur between the two cameras",
+ items=[
+ ("ignore", "Ignore Camera", "Ignore this camera and do not transition"),
+ (
+ "auto",
+ "Auto",
+ "Auto transition as defined by the two cameras' properies",
+ ),
+ ("manual", "Manual", "Manually defined transition"),
+ ],
+ default="auto",
+ options=set(),
+ )
+ enabled = BoolProperty(
+ name="Enabled",
+ description="Export this transition",
+ default=True,
+ options=set(),
+ )
class PlasmaCameraProperties(bpy.types.PropertyGroup):
# Point of Attention
- poa_type = EnumProperty(name="Point of Attention",
- description="The point of attention that this camera tracks",
- items=[("avatar", "Track Local Player", "Camera tracks the player's avatar"),
- ("object", "Track Object", "Camera tracks an object in the scene"),
- ("none", "Don't Track", "Camera does not track anything")],
- options=set())
- poa_object = PointerProperty(name="PoA Object",
- description="Object the camera should track as its Point of Attention",
- type=bpy.types.Object,
- options=set())
- poa_offset = FloatVectorProperty(name="PoA Offset",
- description="Offset from the point of attention's origin to track",
- soft_min=-50.0, soft_max=50.0,
- size=3, default=(0.0, 0.0, 3.0),
- options=set())
- poa_worldspace = BoolProperty(name="Worldspace Offset",
- description="Point of Attention Offset is in worldspace coordinates",
- options=set())
+ poa_type = EnumProperty(
+ name="Point of Attention",
+ description="The point of attention that this camera tracks",
+ items=[
+ ("avatar", "Track Local Player", "Camera tracks the player's avatar"),
+ ("object", "Track Object", "Camera tracks an object in the scene"),
+ ("none", "Don't Track", "Camera does not track anything"),
+ ],
+ options=set(),
+ )
+ poa_object = PointerProperty(
+ name="PoA Object",
+ description="Object the camera should track as its Point of Attention",
+ type=bpy.types.Object,
+ options=set(),
+ )
+ poa_offset = FloatVectorProperty(
+ name="PoA Offset",
+ description="Offset from the point of attention's origin to track",
+ soft_min=-50.0,
+ soft_max=50.0,
+ size=3,
+ default=(0.0, 0.0, 3.0),
+ options=set(),
+ )
+ poa_worldspace = BoolProperty(
+ name="Worldspace Offset",
+ description="Point of Attention Offset is in worldspace coordinates",
+ options=set(),
+ )
# Position Offset
- pos_offset = FloatVectorProperty(name="Position Offset",
- description="Offset the camera's position",
- soft_min=-50.0, soft_max=50.0,
- size=3, default=(0.0, 10.0, 3.0),
- options=set())
- pos_worldspace = BoolProperty(name="Worldspace Offset",
- description="Position offset is in worldspace coordinates",
- options=set())
+ pos_offset = FloatVectorProperty(
+ name="Position Offset",
+ description="Offset the camera's position",
+ soft_min=-50.0,
+ soft_max=50.0,
+ size=3,
+ default=(0.0, 10.0, 3.0),
+ options=set(),
+ )
+ pos_worldspace = BoolProperty(
+ name="Worldspace Offset",
+ description="Position offset is in worldspace coordinates",
+ options=set(),
+ )
# Default Transition
transition = PointerProperty(type=PlasmaTransition, options=set())
# Limit Panning
- x_pan_angle = FloatProperty(name="X Degrees",
- description="Maximum camera pan angle in the X direction",
- min=0.0, max=math.radians(180.0), precision=0, default=math.radians(90.0),
- subtype="ANGLE", options=set())
- y_pan_angle = FloatProperty(name="Y Degrees",
- description="Maximum camera pan angle in the Y direction",
- min=0.0, max=math.radians(180.0), precision=0, default=math.radians(90.0),
- subtype="ANGLE", options=set())
- pan_rate = FloatProperty(name="Pan Velocity",
- description="",
- min=0.0, precision=1, default=50.0,
- unit="VELOCITY", options=set())
+ x_pan_angle = FloatProperty(
+ name="X Degrees",
+ description="Maximum camera pan angle in the X direction",
+ min=0.0,
+ max=math.radians(180.0),
+ precision=0,
+ default=math.radians(90.0),
+ subtype="ANGLE",
+ options=set(),
+ )
+ y_pan_angle = FloatProperty(
+ name="Y Degrees",
+ description="Maximum camera pan angle in the Y direction",
+ min=0.0,
+ max=math.radians(180.0),
+ precision=0,
+ default=math.radians(90.0),
+ subtype="ANGLE",
+ options=set(),
+ )
+ pan_rate = FloatProperty(
+ name="Pan Velocity",
+ description="",
+ min=0.0,
+ precision=1,
+ default=50.0,
+ unit="VELOCITY",
+ options=set(),
+ )
# Zooming
- fov = FloatProperty(name="Default FOV",
- description="Horizontal Field of View angle",
- min=0.0, max=math.radians(180.0), precision=0, default=math.radians(70.0),
- subtype="ANGLE")
- limit_zoom = BoolProperty(name="Limit Zoom",
- description="The camera allows zooming per artist limitations",
- options=set())
- zoom_max = FloatProperty(name="Max FOV",
- description="Maximum camera FOV when zooming",
- min=0.0, max=math.radians(180.0), precision=0, default=math.radians(120.0),
- subtype="ANGLE", options=set())
- zoom_min = FloatProperty(name="Min FOV",
- description="Minimum camera FOV when zooming",
- min=0.0, max=math.radians(180.0), precision=0, default=math.radians(35.0),
- subtype="ANGLE", options=set())
- zoom_rate = FloatProperty(name="Zoom Velocity",
- description="Velocity of the camera's zoom in degrees per second",
- min=0.0, max=180.0, precision=0, default=90.0,
- unit="VELOCITY", options=set())
+ fov = FloatProperty(
+ name="Default FOV",
+ description="Horizontal Field of View angle",
+ min=0.0,
+ max=math.radians(180.0),
+ precision=0,
+ default=math.radians(70.0),
+ subtype="ANGLE",
+ )
+ limit_zoom = BoolProperty(
+ name="Limit Zoom",
+ description="The camera allows zooming per artist limitations",
+ options=set(),
+ )
+ zoom_max = FloatProperty(
+ name="Max FOV",
+ description="Maximum camera FOV when zooming",
+ min=0.0,
+ max=math.radians(180.0),
+ precision=0,
+ default=math.radians(120.0),
+ subtype="ANGLE",
+ options=set(),
+ )
+ zoom_min = FloatProperty(
+ name="Min FOV",
+ description="Minimum camera FOV when zooming",
+ min=0.0,
+ max=math.radians(180.0),
+ precision=0,
+ default=math.radians(35.0),
+ subtype="ANGLE",
+ options=set(),
+ )
+ zoom_rate = FloatProperty(
+ name="Zoom Velocity",
+ description="Velocity of the camera's zoom in degrees per second",
+ min=0.0,
+ max=180.0,
+ precision=0,
+ default=90.0,
+ unit="VELOCITY",
+ options=set(),
+ )
# Miscellaneous Movement Props
- maintain_los = BoolProperty(name="Maintain LOS",
- description="The camera should move to maintain line-of-sight with the object it's tracking",
- default=True,
- options=set())
- fall_vertical = BoolProperty(name="Fall Camera",
- description="The camera will orient itself vertically when the local player begins falling",
- options=set())
- fast_run = BoolProperty(name="Faster When Falling",
- description="The camera's velocity will be increased when the local player is falling",
- options=set())
- ignore_subworld = BoolProperty(name="Ignore Subworld Movement",
- description="The camera will not be parented to any subworlds",
- options=set())
+ maintain_los = BoolProperty(
+ name="Maintain LOS",
+ description="The camera should move to maintain line-of-sight with the object it's tracking",
+ default=True,
+ options=set(),
+ )
+ fall_vertical = BoolProperty(
+ name="Fall Camera",
+ description="The camera will orient itself vertically when the local player begins falling",
+ options=set(),
+ )
+ fast_run = BoolProperty(
+ name="Faster When Falling",
+ description="The camera's velocity will be increased when the local player is falling",
+ options=set(),
+ )
+ ignore_subworld = BoolProperty(
+ name="Ignore Subworld Movement",
+ description="The camera will not be parented to any subworlds",
+ options=set(),
+ )
# Core Type Properties
- primary_camera = BoolProperty(name="Primary Camera",
- description="The camera should be considered the Age's primary camera.",
- options=set())
+ primary_camera = BoolProperty(
+ name="Primary Camera",
+ description="The camera should be considered the Age's primary camera.",
+ options=set(),
+ )
# Cricle Camera
def _get_circle_radius(self):
# This is coming from the UI, so we need to get the active object from
# Blender's context and pass that on to the actual getter.
return self.get_circle_radius(bpy.context.object)
+
def _set_circle_radius(self, value):
# Don't really care about error checking...
self.circle_radius_value = value
- circle_center = PointerProperty(name="Center",
- description="Center of the circle camera's orbit",
- type=bpy.types.Object,
- options=set())
- circle_pos = EnumProperty(name="Position on Circle",
- description="The point on the circle the camera moves to",
- items=[("closest", "Closest Point", "The camera moves to the point on the circle closest to the Point of Attention"),
- ("farthest", "Farthest Point", "The camera moves to the point on the circle farthest from the Point of Attention")],
- options=set())
- circle_velocity = FloatProperty(name="Velocity",
- description="Velocity of the circle camera in degrees per second",
- min=0.0, max=math.radians(360.0), precision=0, default=math.radians(36.0),
- subtype="ANGLE", options=set())
- circle_radius_ui = FloatProperty(name="Radius",
- description="Radius at which the circle camera should orbit the Point of Attention",
- min=0.0, get=_get_circle_radius, set=_set_circle_radius, options=set())
- circle_radius_value = FloatProperty(name="INTERNAL: Radius",
- description="Radius at which the circle camera should orbit the Point of Attention",
- min=0.0, default=8.5, options={"HIDDEN"})
+ circle_center = PointerProperty(
+ name="Center",
+ description="Center of the circle camera's orbit",
+ type=bpy.types.Object,
+ options=set(),
+ )
+ circle_pos = EnumProperty(
+ name="Position on Circle",
+ description="The point on the circle the camera moves to",
+ items=[
+ (
+ "closest",
+ "Closest Point",
+ "The camera moves to the point on the circle closest to the Point of Attention",
+ ),
+ (
+ "farthest",
+ "Farthest Point",
+ "The camera moves to the point on the circle farthest from the Point of Attention",
+ ),
+ ],
+ options=set(),
+ )
+ circle_velocity = FloatProperty(
+ name="Velocity",
+ description="Velocity of the circle camera in degrees per second",
+ min=0.0,
+ max=math.radians(360.0),
+ precision=0,
+ default=math.radians(36.0),
+ subtype="ANGLE",
+ options=set(),
+ )
+ circle_radius_ui = FloatProperty(
+ name="Radius",
+ description="Radius at which the circle camera should orbit the Point of Attention",
+ min=0.0,
+ get=_get_circle_radius,
+ set=_set_circle_radius,
+ options=set(),
+ )
+ circle_radius_value = FloatProperty(
+ name="INTERNAL: Radius",
+ description="Radius at which the circle camera should orbit the Point of Attention",
+ min=0.0,
+ default=8.5,
+ options={"HIDDEN"},
+ )
# Animation
- anim_enabled = BoolProperty(name="Animation Enabled",
- description="Export the camera's animation",
- default=True,
- options=set())
- start_on_push = BoolProperty(name="Start on Push",
- description="Start playing the camera's animation when the camera is activated",
- default=True,
- options=set())
- stop_on_pop = BoolProperty(name="Pause on Pop",
- description="Pauses the camera's animation when the camera is no longer activated",
- default=True,
- options=set())
- reset_on_pop = BoolProperty(name="Reset on Pop",
- description="Reset the camera's animation to the beginning when the camera is no longer activated",
- options=set())
+ anim_enabled = BoolProperty(
+ name="Animation Enabled",
+ description="Export the camera's animation",
+ default=True,
+ options=set(),
+ )
+ start_on_push = BoolProperty(
+ name="Start on Push",
+ description="Start playing the camera's animation when the camera is activated",
+ default=True,
+ options=set(),
+ )
+ stop_on_pop = BoolProperty(
+ name="Pause on Pop",
+ description="Pauses the camera's animation when the camera is no longer activated",
+ default=True,
+ options=set(),
+ )
+ reset_on_pop = BoolProperty(
+ name="Reset on Pop",
+ description="Reset the camera's animation to the beginning when the camera is no longer activated",
+ options=set(),
+ )
# Rail
- rail_pos = EnumProperty(name="Position on Rail",
- description="The point on the rail the camera moves to",
- items=[("closest", "Closest Point", "The camera moves to the point on the rail closest to the Point of Attention"),
- ("farthest", "Farthest Point", "The camera moves to the point on the rail farthest from the Point of Attention")],
- options=set())
+ rail_pos = EnumProperty(
+ name="Position on Rail",
+ description="The point on the rail the camera moves to",
+ items=[
+ (
+ "closest",
+ "Closest Point",
+ "The camera moves to the point on the rail closest to the Point of Attention",
+ ),
+ (
+ "farthest",
+ "Farthest Point",
+ "The camera moves to the point on the rail farthest from the Point of Attention",
+ ),
+ ],
+ options=set(),
+ )
def get_circle_radius(self, bo):
"""Gets the circle camera radius for this camera when it is attached to the given Object"""
assert bo is not None
if self.circle_center is not None:
- vec = bo.matrix_world.translation - self.circle_center.matrix_world.translation
+ vec = (
+ bo.matrix_world.translation
+ - self.circle_center.matrix_world.translation
+ )
return vec.magnitude
return self.circle_radius_value
@@ -236,22 +418,23 @@ class PlasmaCameraProperties(bpy.types.PropertyGroup):
# Dang! We need to escape out to the object to figure out if this is a circle camera...
data = self.id_data
- if isinstance(data, bpy.types.Camera) and data.plasma_camera.camera_type == "circle":
+ if (
+ isinstance(data, bpy.types.Camera)
+ and data.plasma_camera.camera_type == "circle"
+ ):
if self.circle_center is not None:
actors.add(self.circle_center.name)
return actors
class PlasmaCamera(bpy.types.PropertyGroup):
- camera_type = EnumProperty(name="Camera Type",
- description="",
- items=camera_types,
- options=set())
+ camera_type = EnumProperty(
+ name="Camera Type", description="", items=camera_types, options=set()
+ )
settings = PointerProperty(type=PlasmaCameraProperties, options=set())
- transitions = CollectionProperty(type=PlasmaManualTransition,
- name="Transitions",
- description="",
- options=set())
+ transitions = CollectionProperty(
+ type=PlasmaManualTransition, name="Transitions", description="", options=set()
+ )
active_transition_index = IntProperty(options={"HIDDEN"})
@property
diff --git a/korman/properties/prop_image.py b/korman/properties/prop_image.py
index 14646d7..f530ea8 100644
--- a/korman/properties/prop_image.py
+++ b/korman/properties/prop_image.py
@@ -16,11 +16,20 @@
import bpy
from bpy.props import *
+
class PlasmaImage(bpy.types.PropertyGroup):
- texcache_method = EnumProperty(name="Texture Cache",
- description="Texture Cache Settings",
- items=[("skip", "Don't Cache Image", "This image is never cached."),
- ("use", "Use Image Cache", "This image should be cached."),
- ("rebuild", "Refresh Image Cache", "Forces this image to be recached on the next export.")],
- default="use",
- options=set())
+ texcache_method = EnumProperty(
+ name="Texture Cache",
+ description="Texture Cache Settings",
+ items=[
+ ("skip", "Don't Cache Image", "This image is never cached."),
+ ("use", "Use Image Cache", "This image should be cached."),
+ (
+ "rebuild",
+ "Refresh Image Cache",
+ "Forces this image to be recached on the next export.",
+ ),
+ ],
+ default="use",
+ options=set(),
+ )
diff --git a/korman/properties/prop_lamp.py b/korman/properties/prop_lamp.py
index c1c4d33..9eefe32 100644
--- a/korman/properties/prop_lamp.py
+++ b/korman/properties/prop_lamp.py
@@ -18,51 +18,78 @@ from bpy.props import *
from .. import idprops
+
class PlasmaLamp(idprops.IDPropObjectMixin, bpy.types.PropertyGroup):
- affect_characters = BoolProperty(name="Affect Avatars",
- description="This lamp affects avatars",
- options=set(),
- default=True)
+ affect_characters = BoolProperty(
+ name="Affect Avatars",
+ description="This lamp affects avatars",
+ options=set(),
+ default=True,
+ )
# Shadow settings
- cast_shadows = BoolProperty(name="Cast",
- description="This lamp casts runtime shadows",
- default=True)
- shadow_falloff = FloatProperty(name="Falloff",
- description="Distance from the Lamp past which we don't cast shadows",
- min=5.0, max=50.0, default=10.0,
- options=set())
- shadow_distance = FloatProperty(name="Fade Distance",
- description="Distance at which the shadow has completely faded out",
- min=0.0, max=500.0, default=0.0,
- options=set())
- shadow_power = IntProperty(name="Power",
- description="Multiplier for the shadow's intensity",
- min=0, max=200, default=100,
- options=set(), subtype="PERCENTAGE")
- shadow_self = BoolProperty(name="Self-Shadow",
- description="This light can cause objects to cast shadows on themselves",
- default=False,
- options=set())
- shadow_quality = EnumProperty(name="Shadow Quality",
- description="Maximum resolution the shadow is rendered",
- items=[("ABYSMAL", "Abysmal Quality", "64x64 pixels"),
- ("LOW", "Low Quality", "128x128 pixels"),
- ("NORMAL", "Normal Quality", "256x256 pixels"),
- ("HIGH", "High Quality", "512x512 pixels")],
- default="NORMAL",
- options=set())
+ cast_shadows = BoolProperty(
+ name="Cast", description="This lamp casts runtime shadows", default=True
+ )
+ shadow_falloff = FloatProperty(
+ name="Falloff",
+ description="Distance from the Lamp past which we don't cast shadows",
+ min=5.0,
+ max=50.0,
+ default=10.0,
+ options=set(),
+ )
+ shadow_distance = FloatProperty(
+ name="Fade Distance",
+ description="Distance at which the shadow has completely faded out",
+ min=0.0,
+ max=500.0,
+ default=0.0,
+ options=set(),
+ )
+ shadow_power = IntProperty(
+ name="Power",
+ description="Multiplier for the shadow's intensity",
+ min=0,
+ max=200,
+ default=100,
+ options=set(),
+ subtype="PERCENTAGE",
+ )
+ shadow_self = BoolProperty(
+ name="Self-Shadow",
+ description="This light can cause objects to cast shadows on themselves",
+ default=False,
+ options=set(),
+ )
+ shadow_quality = EnumProperty(
+ name="Shadow Quality",
+ description="Maximum resolution the shadow is rendered",
+ items=[
+ ("ABYSMAL", "Abysmal Quality", "64x64 pixels"),
+ ("LOW", "Low Quality", "128x128 pixels"),
+ ("NORMAL", "Normal Quality", "256x256 pixels"),
+ ("HIGH", "High Quality", "512x512 pixels"),
+ ],
+ default="NORMAL",
+ options=set(),
+ )
- lamp_region = PointerProperty(name="Soft Volume",
- description="Soft region this light is active inside",
- type=bpy.types.Object,
- poll=idprops.poll_softvolume_objects)
+ lamp_region = PointerProperty(
+ name="Soft Volume",
+ description="Soft region this light is active inside",
+ type=bpy.types.Object,
+ poll=idprops.poll_softvolume_objects,
+ )
# For LimitedDirLights
- size_height = FloatProperty(name="Height",
- description="Size of the area for the Area Lamp in the Z direction",
- min=0.0, default=200.0,
- options=set())
+ size_height = FloatProperty(
+ name="Height",
+ description="Size of the area for the Area Lamp in the Z direction",
+ min=0.0,
+ default=200.0,
+ options=set(),
+ )
def has_light_group(self, bo):
return bool(bo.users_group)
diff --git a/korman/properties/prop_object.py b/korman/properties/prop_object.py
index 3498479..3a29ea6 100644
--- a/korman/properties/prop_object.py
+++ b/korman/properties/prop_object.py
@@ -17,6 +17,7 @@ import bpy
from bpy.props import *
from PyHSPlasma import *
+
class PlasmaObject(bpy.types.PropertyGroup):
def _enabled(self, context):
if not self.is_property_set("page"):
@@ -37,18 +38,18 @@ class PlasmaObject(bpy.types.PropertyGroup):
o.plasma_object.page = page.name
break
-
- enabled = BoolProperty(name="Export",
- description="Export this as a discrete object",
- default=False,
- update=_enabled)
- page = StringProperty(name="Page",
- description="Page this object will be exported to")
+ enabled = BoolProperty(
+ name="Export",
+ description="Export this as a discrete object",
+ default=False,
+ update=_enabled,
+ )
+ page = StringProperty(
+ name="Page", description="Page this object will be exported to"
+ )
# DEAD - leaving in just in case external code uses it
- is_inited = BoolProperty(description="DEAD",
- default=False,
- options={"HIDDEN"})
+ is_inited = BoolProperty(description="DEAD", default=False, options={"HIDDEN"})
@property
def ci_type(self):
@@ -75,8 +76,16 @@ class PlasmaObject(bpy.types.PropertyGroup):
bo = self.id_data
if bo.animation_data is not None:
if bo.animation_data.action is not None:
- data_paths = frozenset((i.data_path for i in bo.animation_data.action.fcurves))
- return {"location", "rotation_euler", "rotation_quaternion", "rotation_axis_angle", "scale"} & data_paths
+ data_paths = frozenset(
+ (i.data_path for i in bo.animation_data.action.fcurves)
+ )
+ return {
+ "location",
+ "rotation_euler",
+ "rotation_quaternion",
+ "rotation_axis_angle",
+ "scale",
+ } & data_paths
return False
@property
@@ -91,9 +100,11 @@ class PlasmaObject(bpy.types.PropertyGroup):
class PlasmaNet(bpy.types.PropertyGroup):
- manual_sdl = BoolProperty(name="Override SDL",
- description="ADVANCED: Manually track high level states on this object",
- default=False)
+ manual_sdl = BoolProperty(
+ name="Override SDL",
+ description="ADVANCED: Manually track high level states on this object",
+ default=False,
+ )
sdl_names = set()
def propagate_synch_options(self, scnobj, synobj):
@@ -108,14 +119,16 @@ class PlasmaNet(bpy.types.PropertyGroup):
else:
# This SynchedObject may have already excluded or volatile'd everything
# If so, bail.
- if synobj.synchFlags & plSynchedObject.kExcludeAllPersistentState or \
- synobj.synchFlags & plSynchedObject.kAllStateIsVolatile:
+ if (
+ synobj.synchFlags & plSynchedObject.kExcludeAllPersistentState
+ or synobj.synchFlags & plSynchedObject.kAllStateIsVolatile
+ ):
return
# Is this a kickable?
if scnobj.sim is not None:
phys = scnobj.sim.object.physical.object
- has_kickable = (phys.memberGroup == plSimDefs.kGroupDynamic)
+ has_kickable = phys.memberGroup == plSimDefs.kGroupDynamic
else:
has_kickable = False
@@ -159,16 +172,27 @@ class PlasmaNet(bpy.types.PropertyGroup):
@classmethod
def register(cls):
def SdlEnumProperty(name):
- value = bpy.props.EnumProperty(name=name,
- description="{} state synchronization".format(name),
- items=[
- ("save", "Save to Server", "Save state on the server"),
- ("volatile", "Volatile on Server", "Throw away state when the age shuts down"),
- ("exclude", "Don't Send to Server", "Don't synchronize with the server"),
- ],
- default="exclude")
+ value = bpy.props.EnumProperty(
+ name=name,
+ description="{} state synchronization".format(name),
+ items=[
+ ("save", "Save to Server", "Save state on the server"),
+ (
+ "volatile",
+ "Volatile on Server",
+ "Throw away state when the age shuts down",
+ ),
+ (
+ "exclude",
+ "Don't Send to Server",
+ "Don't synchronize with the server",
+ ),
+ ],
+ default="exclude",
+ )
setattr(PlasmaNet, name, value)
PlasmaNet.sdl_names.add(name)
+
agmaster = SdlEnumProperty("AGMaster")
avatar = SdlEnumProperty("Avatar")
avatar_phys = SdlEnumProperty("AvatarPhysical")
diff --git a/korman/properties/prop_scene.py b/korman/properties/prop_scene.py
index 81cb2c5..3116faa 100644
--- a/korman/properties/prop_scene.py
+++ b/korman/properties/prop_scene.py
@@ -19,9 +19,11 @@ import itertools
from ..exporter.etlight import _NUM_RENDER_LAYERS
+
class PlasmaBakePass(bpy.types.PropertyGroup):
def _get_display_name(self):
return self.name
+
def _set_display_name(self, value):
for i in bpy.data.objects:
lm = i.plasma_modifiers.lightmap
@@ -29,32 +31,32 @@ class PlasmaBakePass(bpy.types.PropertyGroup):
lm.bake_pass_name = value
self.name = value
- display_name = StringProperty(name="Pass Name",
- get=_get_display_name,
- set=_set_display_name,
- options=set())
+ display_name = StringProperty(
+ name="Pass Name", get=_get_display_name, set=_set_display_name, options=set()
+ )
- render_layers = BoolVectorProperty(name="Layers to Bake",
- description="Render layers to use for baking",
- options=set(),
- subtype="LAYER",
- size=_NUM_RENDER_LAYERS,
- default=((True,) * _NUM_RENDER_LAYERS))
+ render_layers = BoolVectorProperty(
+ name="Layers to Bake",
+ description="Render layers to use for baking",
+ options=set(),
+ subtype="LAYER",
+ size=_NUM_RENDER_LAYERS,
+ default=((True,) * _NUM_RENDER_LAYERS),
+ )
class PlasmaWetDecalRef(bpy.types.PropertyGroup):
- enabled = BoolProperty(name="Enabled",
- default=True,
- options=set())
+ enabled = BoolProperty(name="Enabled", default=True, options=set())
- name = StringProperty(name="Decal Name",
- description="Wet decal manager",
- options=set())
+ name = StringProperty(
+ name="Decal Name", description="Wet decal manager", options=set()
+ )
class PlasmaDecalManager(bpy.types.PropertyGroup):
def _get_display_name(self):
return self.name
+
def _set_display_name(self, value):
prev_value = self.name
for i in bpy.data.objects:
@@ -69,59 +71,88 @@ class PlasmaDecalManager(bpy.types.PropertyGroup):
j.name = value
self.name = value
- name = StringProperty(name="Decal Name",
- options=set())
- display_name = StringProperty(name="Display Name",
- get=_get_display_name,
- set=_set_display_name,
- options=set())
-
- decal_type = EnumProperty(name="Decal Type",
- description="",
- items=[("footprint_dry", "Footprint (Dry)", ""),
- ("footprint_wet", "Footprint (Wet)", ""),
- ("puddle", "Water Ripple (Shallow)", ""),
- ("ripple", "Water Ripple (Deep)", "")],
- default="footprint_dry",
- options=set())
- image = PointerProperty(name="Image",
- description="",
- type=bpy.types.Image,
- options=set())
- blend = EnumProperty(name="Blend Mode",
- description="",
- items=[("kBlendAdd", "Add", ""),
- ("kBlendAlpha", "Alpha", ""),
- ("kBlendMADD", "Brighten", ""),
- ("kBlendMult", "Multiply", "")],
- default="kBlendAlpha",
- options=set())
-
- length = IntProperty(name="Length",
- description="",
- subtype="PERCENTAGE",
- min=0, soft_min=25, soft_max=400, default=100,
- options=set())
- width = IntProperty(name="Width",
- description="",
- subtype="PERCENTAGE",
- min=0, soft_min=25, soft_max=400, default=100,
- options=set())
- intensity = IntProperty(name="Intensity",
- description="",
- subtype="PERCENTAGE",
- min=0, soft_max=100, default=100,
- options=set())
- life_span = FloatProperty(name="Life Span",
- description="",
- subtype="TIME", unit="TIME",
- min=0.0, soft_max=300.0, default=30.0,
- options=set())
- wet_time = FloatProperty(name="Wet Time",
- description="How long the decal print shapes stay wet after losing contact with this surface",
- subtype="TIME", unit="TIME",
- min=0.0, soft_max=300.0, default=10.0,
- options=set())
+ name = StringProperty(name="Decal Name", options=set())
+ display_name = StringProperty(
+ name="Display Name", get=_get_display_name, set=_set_display_name, options=set()
+ )
+
+ decal_type = EnumProperty(
+ name="Decal Type",
+ description="",
+ items=[
+ ("footprint_dry", "Footprint (Dry)", ""),
+ ("footprint_wet", "Footprint (Wet)", ""),
+ ("puddle", "Water Ripple (Shallow)", ""),
+ ("ripple", "Water Ripple (Deep)", ""),
+ ],
+ default="footprint_dry",
+ options=set(),
+ )
+ image = PointerProperty(
+ name="Image", description="", type=bpy.types.Image, options=set()
+ )
+ blend = EnumProperty(
+ name="Blend Mode",
+ description="",
+ items=[
+ ("kBlendAdd", "Add", ""),
+ ("kBlendAlpha", "Alpha", ""),
+ ("kBlendMADD", "Brighten", ""),
+ ("kBlendMult", "Multiply", ""),
+ ],
+ default="kBlendAlpha",
+ options=set(),
+ )
+
+ length = IntProperty(
+ name="Length",
+ description="",
+ subtype="PERCENTAGE",
+ min=0,
+ soft_min=25,
+ soft_max=400,
+ default=100,
+ options=set(),
+ )
+ width = IntProperty(
+ name="Width",
+ description="",
+ subtype="PERCENTAGE",
+ min=0,
+ soft_min=25,
+ soft_max=400,
+ default=100,
+ options=set(),
+ )
+ intensity = IntProperty(
+ name="Intensity",
+ description="",
+ subtype="PERCENTAGE",
+ min=0,
+ soft_max=100,
+ default=100,
+ options=set(),
+ )
+ life_span = FloatProperty(
+ name="Life Span",
+ description="",
+ subtype="TIME",
+ unit="TIME",
+ min=0.0,
+ soft_max=300.0,
+ default=30.0,
+ options=set(),
+ )
+ wet_time = FloatProperty(
+ name="Wet Time",
+ description="How long the decal print shapes stay wet after losing contact with this surface",
+ subtype="TIME",
+ unit="TIME",
+ min=0.0,
+ soft_max=300.0,
+ default=10.0,
+ options=set(),
+ )
# Footprints to wet-ize
wet_managers = CollectionProperty(type=PlasmaWetDecalRef)
@@ -135,8 +166,11 @@ class PlasmaScene(bpy.types.PropertyGroup):
decal_managers = CollectionProperty(type=PlasmaDecalManager)
active_decal_index = IntProperty(options={"HIDDEN"})
- modifier_copy_object = PointerProperty(name="INTERNAL: Object to copy modifiers from",
- options={"HIDDEN", "SKIP_SAVE"},
- type=bpy.types.Object)
- modifier_copy_id = StringProperty(name="INTERNAL: Modifier to copy from",
- options={"HIDDEN", "SKIP_SAVE"})
+ modifier_copy_object = PointerProperty(
+ name="INTERNAL: Object to copy modifiers from",
+ options={"HIDDEN", "SKIP_SAVE"},
+ type=bpy.types.Object,
+ )
+ modifier_copy_id = StringProperty(
+ name="INTERNAL: Modifier to copy from", options={"HIDDEN", "SKIP_SAVE"}
+ )
diff --git a/korman/properties/prop_sound.py b/korman/properties/prop_sound.py
index a340b5d..7f410b1 100644
--- a/korman/properties/prop_sound.py
+++ b/korman/properties/prop_sound.py
@@ -16,8 +16,11 @@
import bpy
from bpy.props import *
+
class PlasmaSound(bpy.types.PropertyGroup):
- package = BoolProperty(name="Export",
- description="Package this file in the age export",
- default=True,
- options=set())
+ package = BoolProperty(
+ name="Export",
+ description="Package this file in the age export",
+ default=True,
+ options=set(),
+ )
diff --git a/korman/properties/prop_text.py b/korman/properties/prop_text.py
index 3746cf7..69a3cd8 100644
--- a/korman/properties/prop_text.py
+++ b/korman/properties/prop_text.py
@@ -16,7 +16,8 @@
import bpy
from bpy.props import *
+
class PlasmaText(bpy.types.PropertyGroup):
- package = BoolProperty(name="Export",
- description="Package this file in the age export",
- options=set())
+ package = BoolProperty(
+ name="Export", description="Package this file in the age export", options=set()
+ )
diff --git a/korman/properties/prop_texture.py b/korman/properties/prop_texture.py
index 78d83ad..48182eb 100644
--- a/korman/properties/prop_texture.py
+++ b/korman/properties/prop_texture.py
@@ -19,12 +19,15 @@ from bpy.props import *
from .. import idprops
from .prop_anim import PlasmaAnimationCollection
+
class EnvMapVisRegion(idprops.IDPropObjectMixin, bpy.types.PropertyGroup):
enabled = BoolProperty(default=True)
- control_region = PointerProperty(name="Control",
- description="Object defining a Plasma Visibility Control",
- type=bpy.types.Object,
- poll=idprops.poll_visregion_objects)
+ control_region = PointerProperty(
+ name="Control",
+ description="Object defining a Plasma Visibility Control",
+ type=bpy.types.Object,
+ poll=idprops.poll_visregion_objects,
+ )
@classmethod
def _idprop_mapping(cls):
@@ -34,71 +37,113 @@ class EnvMapVisRegion(idprops.IDPropObjectMixin, bpy.types.PropertyGroup):
class PlasmaLayer(bpy.types.PropertyGroup):
bl_idname = "texture.plasma_layer"
- opacity = FloatProperty(name="Layer Opacity",
- description="Opacity of the texture",
- default=100.0, min=0.0, max=100.0,
- precision=0, subtype="PERCENTAGE")
- alpha_halo = BoolProperty(name="High Alpha Test",
- description="Fixes halos seen around semitransparent objects resulting from sorting errors",
- default=False)
-
- envmap_color = FloatVectorProperty(name="Environment Map Color",
- description="The default background color rendered onto the Environment Map",
- min=0.0,
- max=1.0,
- default=(1.0, 1.0, 1.0),
- subtype="COLOR")
-
- envmap_addavatar = BoolProperty(name="Render Avatars",
- description="Toggle the rendering of avatars in the environment map",
- default=True)
-
- vis_regions = CollectionProperty(name="Visibility Regions",
- type=EnvMapVisRegion)
+ opacity = FloatProperty(
+ name="Layer Opacity",
+ description="Opacity of the texture",
+ default=100.0,
+ min=0.0,
+ max=100.0,
+ precision=0,
+ subtype="PERCENTAGE",
+ )
+ alpha_halo = BoolProperty(
+ name="High Alpha Test",
+ description="Fixes halos seen around semitransparent objects resulting from sorting errors",
+ default=False,
+ )
+
+ envmap_color = FloatVectorProperty(
+ name="Environment Map Color",
+ description="The default background color rendered onto the Environment Map",
+ min=0.0,
+ max=1.0,
+ default=(1.0, 1.0, 1.0),
+ subtype="COLOR",
+ )
+
+ envmap_addavatar = BoolProperty(
+ name="Render Avatars",
+ description="Toggle the rendering of avatars in the environment map",
+ default=True,
+ )
+
+ vis_regions = CollectionProperty(name="Visibility Regions", type=EnvMapVisRegion)
active_region_index = IntProperty(options={"HIDDEN"})
- is_detail_map = BoolProperty(name="Detail Fade",
- description="Texture fades out as distance from the camera increases",
- default=False,
- options=set())
- detail_fade_start = IntProperty(name="Falloff Start",
- description="",
- min=0, max=100, default=0,
- options=set(), subtype="PERCENTAGE")
- detail_fade_stop = IntProperty(name="Falloff Stop",
- description="",
- min=0, max=100, default=100,
- options=set(), subtype="PERCENTAGE")
- detail_opacity_start = IntProperty(name="Opacity Start",
- description="",
- min=0, max=100, default=50,
- options=set(), subtype="PERCENTAGE")
- detail_opacity_stop = IntProperty(name="Opacity Stop",
- description="",
- min=0, max=100, default=0,
- options=set(), subtype="PERCENTAGE")
-
- z_bias = BoolProperty(name="Z Bias",
- description="Request Z bias offset to defeat Z-fighting",
- default=False,
- options=set())
- skip_depth_test = BoolProperty(name="Skip Depth Test",
- description="Causes this layer to be rendered, even if behind others",
- default=False,
- options=set())
- skip_depth_write = BoolProperty(name="Skip Depth Write",
- description="Don't save the depth information, allowing rendering of layers behind this one",
- default=False,
- options=set())
-
- dynatext_resolution = EnumProperty(name="Dynamic Text Map Resolution",
- description="Size of the Dynamic Text Map's underlying image",
- items=[("128", "128x128", ""),
- ("256", "256x256", ""),
- ("512", "512x512", ""),
- ("1024", "1024x1024", "")],
- default="1024",
- options=set())
+ is_detail_map = BoolProperty(
+ name="Detail Fade",
+ description="Texture fades out as distance from the camera increases",
+ default=False,
+ options=set(),
+ )
+ detail_fade_start = IntProperty(
+ name="Falloff Start",
+ description="",
+ min=0,
+ max=100,
+ default=0,
+ options=set(),
+ subtype="PERCENTAGE",
+ )
+ detail_fade_stop = IntProperty(
+ name="Falloff Stop",
+ description="",
+ min=0,
+ max=100,
+ default=100,
+ options=set(),
+ subtype="PERCENTAGE",
+ )
+ detail_opacity_start = IntProperty(
+ name="Opacity Start",
+ description="",
+ min=0,
+ max=100,
+ default=50,
+ options=set(),
+ subtype="PERCENTAGE",
+ )
+ detail_opacity_stop = IntProperty(
+ name="Opacity Stop",
+ description="",
+ min=0,
+ max=100,
+ default=0,
+ options=set(),
+ subtype="PERCENTAGE",
+ )
+
+ z_bias = BoolProperty(
+ name="Z Bias",
+ description="Request Z bias offset to defeat Z-fighting",
+ default=False,
+ options=set(),
+ )
+ skip_depth_test = BoolProperty(
+ name="Skip Depth Test",
+ description="Causes this layer to be rendered, even if behind others",
+ default=False,
+ options=set(),
+ )
+ skip_depth_write = BoolProperty(
+ name="Skip Depth Write",
+ description="Don't save the depth information, allowing rendering of layers behind this one",
+ default=False,
+ options=set(),
+ )
+
+ dynatext_resolution = EnumProperty(
+ name="Dynamic Text Map Resolution",
+ description="Size of the Dynamic Text Map's underlying image",
+ items=[
+ ("128", "128x128", ""),
+ ("256", "256x256", ""),
+ ("512", "512x512", ""),
+ ("1024", "1024x1024", ""),
+ ],
+ default="1024",
+ options=set(),
+ )
subanimations = PointerProperty(type=PlasmaAnimationCollection)
diff --git a/korman/properties/prop_world.py b/korman/properties/prop_world.py
index 75191b2..9824750 100644
--- a/korman/properties/prop_world.py
+++ b/korman/properties/prop_world.py
@@ -19,42 +19,44 @@ from PyHSPlasma import *
from ..addon_prefs import game_versions
+
class PlasmaFni(bpy.types.PropertyGroup):
bl_idname = "world.plasma_fni"
- fog_color = FloatVectorProperty(name="Fog Color",
- description="The default fog color used in your age",
- default=(0.4, 0.3, 0.1),
- min=0.0,
- max=1.0,
- subtype="COLOR")
- fog_method = EnumProperty(name="Fog Type",
- items=[
- ("linear", "Linear", "Fog Based on Linear Distance"),
- ("exp", "Exponential", "Fog Based on Exponential Distance"),
- ("exp2", "Exponential 2", "Fog Based on Exponential Distance Squared"),
- ("none", "None", "No Fog")
- ])
- fog_start = FloatProperty(name="Start",
- description="",
- default=-1500.0)
- fog_end = FloatProperty(name="End",
- description="",
- default=20000.0)
- fog_density = FloatProperty(name="Density",
- description="",
- default=1.0,
- min=0.0)
- clear_color = FloatVectorProperty(name="Clear Color",
- description="The default background color rendered in your age",
- min=0.0,
- max=1.0,
- subtype="COLOR")
- yon = IntProperty(name="Draw Distance",
- description="The distance (in feet) Plasma will draw",
- default=100000,
- soft_min=100,
- min=1)
+ fog_color = FloatVectorProperty(
+ name="Fog Color",
+ description="The default fog color used in your age",
+ default=(0.4, 0.3, 0.1),
+ min=0.0,
+ max=1.0,
+ subtype="COLOR",
+ )
+ fog_method = EnumProperty(
+ name="Fog Type",
+ items=[
+ ("linear", "Linear", "Fog Based on Linear Distance"),
+ ("exp", "Exponential", "Fog Based on Exponential Distance"),
+ ("exp2", "Exponential 2", "Fog Based on Exponential Distance Squared"),
+ ("none", "None", "No Fog"),
+ ],
+ )
+ fog_start = FloatProperty(name="Start", description="", default=-1500.0)
+ fog_end = FloatProperty(name="End", description="", default=20000.0)
+ fog_density = FloatProperty(name="Density", description="", default=1.0, min=0.0)
+ clear_color = FloatVectorProperty(
+ name="Clear Color",
+ description="The default background color rendered in your age",
+ min=0.0,
+ max=1.0,
+ subtype="COLOR",
+ )
+ yon = IntProperty(
+ name="Draw Distance",
+ description="The distance (in feet) Plasma will draw",
+ default=100000,
+ soft_min=100,
+ min=1,
+ )
class PlasmaGames(bpy.types.PropertyGroup):
@@ -114,37 +116,47 @@ class PlasmaPage(bpy.types.PropertyGroup):
self.name = "Page%02i" % suffix
self.check_suffixes = True
- name = StringProperty(name="Name",
- description="Name of the specified page",
- update=_rename_page)
- seq_suffix = IntProperty(name="ID",
- description="A numerical ID for this page",
- soft_min=0, # Negatives indicate global--advanced users only
- default=0, # The add operator will autogen a default
- update=_check_suffix)
- auto_load = BoolProperty(name="Auto Load",
- description="Load this page on link-in",
- default=True)
- local_only = BoolProperty(name="Local Only",
- description="This page should not synchronize with the server",
- default=False)
- enabled = BoolProperty(name="Export Page",
- description="Export this page",
- default=True)
- version = EnumProperty(name="Export Versions",
- description="Plasma versions this page exports under",
- items=game_versions,
- options={"ENUM_FLAG"},
- default=set(list(zip(*game_versions))[0]))
+ name = StringProperty(
+ name="Name", description="Name of the specified page", update=_rename_page
+ )
+ seq_suffix = IntProperty(
+ name="ID",
+ description="A numerical ID for this page",
+ soft_min=0, # Negatives indicate global--advanced users only
+ default=0, # The add operator will autogen a default
+ update=_check_suffix,
+ )
+ auto_load = BoolProperty(
+ name="Auto Load", description="Load this page on link-in", default=True
+ )
+ local_only = BoolProperty(
+ name="Local Only",
+ description="This page should not synchronize with the server",
+ default=False,
+ )
+ enabled = BoolProperty(
+ name="Export Page", description="Export this page", default=True
+ )
+ version = EnumProperty(
+ name="Export Versions",
+ description="Plasma versions this page exports under",
+ items=game_versions,
+ options={"ENUM_FLAG"},
+ default=set(list(zip(*game_versions))[0]),
+ )
# Implementation details...
- last_name = StringProperty(description="INTERNAL: Cached page name",
- options={"HIDDEN"})
- last_seq_suffix = IntProperty(description="INTERNAL: Cached sequence suffix",
- options={"HIDDEN"})
- check_suffixes = BoolProperty(description="INTERNAL: Should we sanity-check suffixes?",
- options={"HIDDEN"},
- default=False)
+ last_name = StringProperty(
+ description="INTERNAL: Cached page name", options={"HIDDEN"}
+ )
+ last_seq_suffix = IntProperty(
+ description="INTERNAL: Cached sequence suffix", options={"HIDDEN"}
+ )
+ check_suffixes = BoolProperty(
+ description="INTERNAL: Should we sanity-check suffixes?",
+ options={"HIDDEN"},
+ default=False,
+ )
class PlasmaAge(bpy.types.PropertyGroup):
@@ -153,14 +165,20 @@ class PlasmaAge(bpy.types.PropertyGroup):
log_func = exporter.report.warn
else:
log_func = exporter.report.port
- if self.seq_prefix <= self.MOUL_PREFIX_RANGE[0] or self.seq_prefix >= self.MOUL_PREFIX_RANGE[1]:
- log_func("Age Sequence Prefix {} is potentially out of range (should be between {} and {})",
- self.seq_prefix, *self.MOUL_PREFIX_RANGE)
+ if (
+ self.seq_prefix <= self.MOUL_PREFIX_RANGE[0]
+ or self.seq_prefix >= self.MOUL_PREFIX_RANGE[1]
+ ):
+ log_func(
+ "Age Sequence Prefix {} is potentially out of range (should be between {} and {})",
+ self.seq_prefix,
+ *self.MOUL_PREFIX_RANGE
+ )
_age_info = plAgeInfo()
_age_info.dayLength = self.day_length
- _age_info.lingerTime = 180 # this is fairly standard
- _age_info.maxCapacity = 50 # the server currently ignores this
+ _age_info.lingerTime = 180 # this is fairly standard
+ _age_info.maxCapacity = 50 # the server currently ignores this
_age_info.name = exporter.age_name
_age_info.seqPrefix = self.seq_prefix
_age_info.startDateTime = self.start_time
@@ -170,35 +188,47 @@ class PlasmaAge(bpy.types.PropertyGroup):
MOUL_PREFIX_RANGE = ((pow(2, 16) - pow(2, 15)) * -1, pow(2, 15) - 1)
SP_PRFIX_RANGE = ((pow(2, 24) - pow(2, 23)) * -1, pow(2, 23) - 1)
- day_length = FloatProperty(name="Day Length",
- description="Length of a day (in hours) on this age",
- default=30.230000,
- soft_min=0.1,
- min=0.0)
- start_time = IntProperty(name="Start Time",
- description="Seconds from 1/1/1970 until the first day on this age",
- subtype="UNSIGNED",
- default=672211080,
- min=0)
- seq_prefix = IntProperty(name="Sequence Prefix",
- description="A unique numerical ID for this age",
- min=SP_PRFIX_RANGE[0],
- soft_min=0, # Negative indicates global--advanced users only
- soft_max=MOUL_PREFIX_RANGE[1],
- max=SP_PRFIX_RANGE[1],
- default=100)
- pages = CollectionProperty(name="Pages",
- description="Registry pages for this age",
- type=PlasmaPage)
- age_sdl = BoolProperty(name="Age Global SDL",
- description="This age has its own SDL file",
- default=False)
- use_texture_page = BoolProperty(name="Use Textures Page",
- description="Exports all textures to a dedicated Textures page",
- default=True)
- age_name = StringProperty(name="Age Name",
- description="Name of the Age to be used for data files",
- options=set())
+ day_length = FloatProperty(
+ name="Day Length",
+ description="Length of a day (in hours) on this age",
+ default=30.230000,
+ soft_min=0.1,
+ min=0.0,
+ )
+ start_time = IntProperty(
+ name="Start Time",
+ description="Seconds from 1/1/1970 until the first day on this age",
+ subtype="UNSIGNED",
+ default=672211080,
+ min=0,
+ )
+ seq_prefix = IntProperty(
+ name="Sequence Prefix",
+ description="A unique numerical ID for this age",
+ min=SP_PRFIX_RANGE[0],
+ soft_min=0, # Negative indicates global--advanced users only
+ soft_max=MOUL_PREFIX_RANGE[1],
+ max=SP_PRFIX_RANGE[1],
+ default=100,
+ )
+ pages = CollectionProperty(
+ name="Pages", description="Registry pages for this age", type=PlasmaPage
+ )
+ age_sdl = BoolProperty(
+ name="Age Global SDL",
+ description="This age has its own SDL file",
+ default=False,
+ )
+ use_texture_page = BoolProperty(
+ name="Use Textures Page",
+ description="Exports all textures to a dedicated Textures page",
+ default=True,
+ )
+ age_name = StringProperty(
+ name="Age Name",
+ description="Name of the Age to be used for data files",
+ options=set(),
+ )
# Implementation details
active_page_index = IntProperty(name="Active Page Index")
diff --git a/korman/render.py b/korman/render.py
index 2ce7de6..06a9cba 100644
--- a/korman/render.py
+++ b/korman/render.py
@@ -26,6 +26,7 @@ class PlasmaRenderEngine(bpy.types.RenderEngine):
# Explicitly whitelist compatible Blender panels...
from bl_ui import properties_material
+
properties_material.MATERIAL_PT_context_material.COMPAT_ENGINES.add("PLASMA_GAME")
properties_material.MATERIAL_PT_diffuse.COMPAT_ENGINES.add("PLASMA_GAME")
properties_material.MATERIAL_PT_shading.COMPAT_ENGINES.add("PLASMA_GAME")
@@ -37,18 +38,22 @@ properties_material.MATERIAL_PT_shadow.COMPAT_ENGINES.add("PLASMA_GAME")
del properties_material
from bl_ui import properties_data_mesh
+
properties_data_mesh.DATA_PT_normals.COMPAT_ENGINES.add("PLASMA_GAME")
properties_data_mesh.DATA_PT_uv_texture.COMPAT_ENGINES.add("PLASMA_GAME")
properties_data_mesh.DATA_PT_vertex_colors.COMPAT_ENGINES.add("PLASMA_GAME")
del properties_data_mesh
+
def _whitelist_all(mod):
for i in dir(mod):
attr = getattr(mod, i)
if hasattr(attr, "COMPAT_ENGINES"):
getattr(attr, "COMPAT_ENGINES").add("PLASMA_GAME")
+
from bl_ui import properties_data_lamp
+
properties_data_lamp.DATA_PT_context_lamp.COMPAT_ENGINES.add("PLASMA_GAME")
properties_data_lamp.DATA_PT_preview.COMPAT_ENGINES.add("PLASMA_GAME")
properties_data_lamp.DATA_PT_lamp.COMPAT_ENGINES.add("PLASMA_GAME")
@@ -60,14 +65,17 @@ properties_data_lamp.DATA_PT_custom_props_lamp.COMPAT_ENGINES.add("PLASMA_GAME")
del properties_data_lamp
from bl_ui import properties_render
+
_whitelist_all(properties_render)
del properties_render
from bl_ui import properties_texture
+
_whitelist_all(properties_texture)
del properties_texture
from bl_ui import properties_world
+
properties_world.WORLD_PT_context_world.COMPAT_ENGINES.add("PLASMA_GAME")
properties_world.WORLD_PT_ambient_occlusion.COMPAT_ENGINES.add("PLASMA_GAME")
properties_world.WORLD_PT_environment_lighting.COMPAT_ENGINES.add("PLASMA_GAME")
diff --git a/korman/ui/__init__.py b/korman/ui/__init__.py
index d5a2ac6..f0102c7 100644
--- a/korman/ui/__init__.py
+++ b/korman/ui/__init__.py
@@ -32,5 +32,6 @@ from .ui_world import *
def register():
ui_menus.register()
+
def unregister():
ui_menus.unregister()
diff --git a/korman/ui/modifiers/anim.py b/korman/ui/modifiers/anim.py
index f781d35..effbac5 100644
--- a/korman/ui/modifiers/anim.py
+++ b/korman/ui/modifiers/anim.py
@@ -18,6 +18,7 @@ import bpy
from .. import ui_list
from .. import ui_anim
+
def _check_for_anim(layout, modifier):
try:
action = modifier.blender_action
@@ -27,6 +28,7 @@ def _check_for_anim(layout, modifier):
else:
return action if action is not None else False
+
def animation(modifier, layout, context):
action = _check_for_anim(layout, modifier)
if action is None:
@@ -34,11 +36,14 @@ def animation(modifier, layout, context):
if modifier.id_data.type == "CAMERA":
if not modifier.id_data.data.plasma_camera.allow_animations:
- layout.label("Animation modifiers are not allowed on this camera type!", icon="ERROR")
+ layout.label(
+ "Animation modifiers are not allowed on this camera type!", icon="ERROR"
+ )
return
ui_anim.draw_multi_animation(layout, "object", modifier, "subanimations")
+
def animation_filter(modifier, layout, context):
split = layout.split()
@@ -52,9 +57,25 @@ def animation_filter(modifier, layout, context):
col.label("Rotation:")
col.prop(modifier, "no_rotation", text="Filter Rotation")
+
class GroupListUI(bpy.types.UIList):
- def draw_item(self, context, layout, data, item, icon, active_data, active_property, index=0, flt_flag=0):
- label = item.child_anim.name if item.child_anim is not None else "[No Child Specified]"
+ def draw_item(
+ self,
+ context,
+ layout,
+ data,
+ item,
+ icon,
+ active_data,
+ active_property,
+ index=0,
+ flt_flag=0,
+ ):
+ label = (
+ item.child_anim.name
+ if item.child_anim is not None
+ else "[No Child Specified]"
+ )
icon = "ACTION" if item.child_anim is not None else "ERROR"
layout.label(text=label, icon=icon)
@@ -64,14 +85,34 @@ def animation_group(modifier, layout, context):
if action is None:
return
- ui_list.draw_modifier_list(layout, "GroupListUI", modifier, "children",
- "active_child_index", rows=3, maxrows=4)
+ ui_list.draw_modifier_list(
+ layout,
+ "GroupListUI",
+ modifier,
+ "children",
+ "active_child_index",
+ rows=3,
+ maxrows=4,
+ )
if modifier.children:
- layout.prop(modifier.children[modifier.active_child_index], "child_anim", icon="ACTION")
+ layout.prop(
+ modifier.children[modifier.active_child_index], "child_anim", icon="ACTION"
+ )
class LoopListUI(bpy.types.UIList):
- def draw_item(self, context, layout, data, item, icon, active_data, active_property, index=0, flt_flag=0):
+ def draw_item(
+ self,
+ context,
+ layout,
+ data,
+ item,
+ icon,
+ active_data,
+ active_property,
+ index=0,
+ flt_flag=0,
+ ):
layout.prop(item, "loop_name", emboss=False, text="", icon="PMARKER_ACT")
@@ -83,9 +124,17 @@ def animation_loop(modifier, layout, context):
elif action is None:
return
- ui_list.draw_modifier_list(layout, "LoopListUI", modifier, "loops",
- "active_loop_index", name_prefix="Loop",
- name_prop="loop_name", rows=2, maxrows=3)
+ ui_list.draw_modifier_list(
+ layout,
+ "LoopListUI",
+ modifier,
+ "loops",
+ "active_loop_index",
+ name_prefix="Loop",
+ name_prop="loop_name",
+ rows=2,
+ maxrows=3,
+ )
# Modify the loop points
if modifier.loops:
loop = modifier.loops[modifier.active_loop_index]
diff --git a/korman/ui/modifiers/avatar.py b/korman/ui/modifiers/avatar.py
index 11db188..61409c0 100644
--- a/korman/ui/modifiers/avatar.py
+++ b/korman/ui/modifiers/avatar.py
@@ -17,6 +17,7 @@ import bpy
from ...helpers import find_modifier
+
def laddermod(modifier, layout, context):
layout.label(text="Avatar climbs facing negative Y.")
@@ -26,6 +27,7 @@ def laddermod(modifier, layout, context):
layout.prop(modifier, "facing_object", icon="MESH_DATA")
+
def sittingmod(modifier, layout, context):
layout.row().prop(modifier, "approach")
diff --git a/korman/ui/modifiers/gui.py b/korman/ui/modifiers/gui.py
index 3577cd5..94b3026 100644
--- a/korman/ui/modifiers/gui.py
+++ b/korman/ui/modifiers/gui.py
@@ -18,21 +18,47 @@ from pathlib import Path
from . import ui_list
+
class ImageListUI(bpy.types.UIList):
- def draw_item(self, context, layout, data, item, icon, active_data, active_property, index=0, flt_flag=0):
+ def draw_item(
+ self,
+ context,
+ layout,
+ data,
+ item,
+ icon,
+ active_data,
+ active_property,
+ index=0,
+ flt_flag=0,
+ ):
if item.image is None:
layout.label("[No Image Specified]", icon="ERROR")
else:
- layout.label(str(Path(item.image.name).with_suffix(".hsm")), icon_value=item.image.preview.icon_id)
+ layout.label(
+ str(Path(item.image.name).with_suffix(".hsm")),
+ icon_value=item.image.preview.icon_id,
+ )
layout.prop(item, "enabled", text="")
def imagelibmod(modifier, layout, context):
- ui_list.draw_modifier_list(layout, "ImageListUI", modifier, "images", "active_image_index", rows=3, maxrows=6)
+ ui_list.draw_modifier_list(
+ layout,
+ "ImageListUI",
+ modifier,
+ "images",
+ "active_image_index",
+ rows=3,
+ maxrows=6,
+ )
if modifier.images:
row = layout.row(align=True)
- row.template_ID(modifier.images[modifier.active_image_index], "image", open="image.open")
+ row.template_ID(
+ modifier.images[modifier.active_image_index], "image", open="image.open"
+ )
+
def journalbookmod(modifier, layout, context):
layout.prop_menu_enum(modifier, "versions")
@@ -68,6 +94,7 @@ def journalbookmod(modifier, layout, context):
main_col.label("Clickable Region:")
main_col.prop(modifier, "clickable_region", text="")
+
def linkingbookmod(modifier, layout, context):
def row_alert(prop_name, **kwargs):
row = layout.row()
diff --git a/korman/ui/modifiers/logic.py b/korman/ui/modifiers/logic.py
index d657b5f..d9f69ef 100644
--- a/korman/ui/modifiers/logic.py
+++ b/korman/ui/modifiers/logic.py
@@ -17,8 +17,20 @@ import bpy
from .. import ui_list
+
class LogicListUI(bpy.types.UIList):
- def draw_item(self, context, layout, data, item, icon, active_data, active_property, index=0, flt_flag=0):
+ def draw_item(
+ self,
+ context,
+ layout,
+ data,
+ item,
+ icon,
+ active_data,
+ active_property,
+ index=0,
+ flt_flag=0,
+ ):
if item.node_tree:
# Using layout.prop on the pointer prevents clicking on the item O.o
layout.label(item.node_tree.name, icon="NODETREE")
@@ -27,8 +39,15 @@ class LogicListUI(bpy.types.UIList):
def advanced_logic(modifier, layout, context):
- ui_list.draw_modifier_list(layout, "LogicListUI", modifier, "logic_groups",
- "active_group_index", rows=2, maxrows=3)
+ ui_list.draw_modifier_list(
+ layout,
+ "LogicListUI",
+ modifier,
+ "logic_groups",
+ "active_group_index",
+ rows=2,
+ maxrows=3,
+ )
# Modify the logic groups
if modifier.logic_groups:
@@ -36,9 +55,11 @@ def advanced_logic(modifier, layout, context):
layout.row().prop_menu_enum(logic, "version")
layout.prop(logic, "node_tree", icon="NODETREE")
+
def spawnpoint(modifier, layout, context):
layout.label(text="Avatar faces negative Y.")
+
def maintainersmarker(modifier, layout, context):
layout.label(text="Positive Y is North, positive Z is up.")
layout.prop(modifier, "calibration")
diff --git a/korman/ui/modifiers/physics.py b/korman/ui/modifiers/physics.py
index c8d83a2..e27656f 100644
--- a/korman/ui/modifiers/physics.py
+++ b/korman/ui/modifiers/physics.py
@@ -13,6 +13,7 @@
# You should have received a copy of the GNU General Public License
# along with Korman. If not, see .
+
def collision(modifier, layout, context):
layout.prop(modifier, "bounds")
layout.prop(modifier, "surface")
@@ -45,6 +46,7 @@ def collision(modifier, layout, context):
row.active = modifier.bounds == "trimesh"
row.prop(modifier, "proxy_object")
+
def subworld_def(modifier, layout, context):
layout.prop(modifier, "sub_type")
if modifier.sub_type != "dynamicav":
diff --git a/korman/ui/modifiers/region.py b/korman/ui/modifiers/region.py
index 52eb6e7..f3731c8 100644
--- a/korman/ui/modifiers/region.py
+++ b/korman/ui/modifiers/region.py
@@ -16,6 +16,7 @@
import bpy
from .. import ui_camera
+
def camera_rgn(modifier, layout, context):
layout.prop(modifier, "camera_type")
if modifier.camera_type == "manual":
@@ -29,20 +30,28 @@ def camera_rgn(modifier, layout, context):
layout.separator()
i(layout, cam_type, cam_props)
- _draw_props(layout, (ui_camera.draw_camera_mode_props,
- ui_camera.draw_camera_poa_props,
- ui_camera.draw_camera_pos_props,
- ui_camera.draw_camera_manipulation_props))
+ _draw_props(
+ layout,
+ (
+ ui_camera.draw_camera_mode_props,
+ ui_camera.draw_camera_poa_props,
+ ui_camera.draw_camera_pos_props,
+ ui_camera.draw_camera_manipulation_props,
+ ),
+ )
+
def footstep(modifier, layout, context):
layout.prop(modifier, "bounds")
layout.prop(modifier, "surface")
+
def paniclink(modifier, layout, context):
phys_mod = context.object.plasma_modifiers.collision
layout.prop(phys_mod, "bounds")
layout.prop(modifier, "play_anim")
+
def softvolume(modifier, layout, context):
row = layout.row()
row.prop(modifier, "use_nodes", text="", icon="NODETREE")
@@ -59,6 +68,7 @@ def softvolume(modifier, layout, context):
col.prop(modifier, "invert")
col.prop(modifier, "soft_distance")
+
def subworld_rgn(modifier, layout, context):
layout.prop(modifier, "subworld")
collision_mod = modifier.id_data.plasma_modifiers.collision
diff --git a/korman/ui/modifiers/render.py b/korman/ui/modifiers/render.py
index b660872..ce21f2e 100644
--- a/korman/ui/modifiers/render.py
+++ b/korman/ui/modifiers/render.py
@@ -18,8 +18,20 @@ import bpy
from .. import ui_list
from ...exporter.mesh import _VERTEX_COLOR_LAYERS
+
class BlendOntoListUI(bpy.types.UIList):
- def draw_item(self, context, layout, data, item, icon, active_data, active_property, index=0, flt_flag=0):
+ def draw_item(
+ self,
+ context,
+ layout,
+ data,
+ item,
+ icon,
+ active_data,
+ active_property,
+ index=0,
+ flt_flag=0,
+ ):
if item.blend_onto is None:
layout.label("[No Object Specified]", icon="ERROR")
else:
@@ -37,8 +49,15 @@ def blend(modifier, layout, context):
layout.separator()
layout.label("Render Dependencies:")
- ui_list.draw_modifier_list(layout, "BlendOntoListUI", modifier, "dependencies",
- "active_dependency_index", rows=2, maxrows=4)
+ ui_list.draw_modifier_list(
+ layout,
+ "BlendOntoListUI",
+ modifier,
+ "dependencies",
+ "active_dependency_index",
+ rows=2,
+ maxrows=4,
+ )
try:
dependency_ref = modifier.dependencies[modifier.active_dependency_index]
except:
@@ -49,7 +68,18 @@ def blend(modifier, layout, context):
class DecalMgrListUI(bpy.types.UIList):
- def draw_item(self, context, layout, data, item, icon, active_data, active_property, index=0, flt_flag=0):
+ def draw_item(
+ self,
+ context,
+ layout,
+ data,
+ item,
+ icon,
+ active_data,
+ active_property,
+ index=0,
+ flt_flag=0,
+ ):
if item.name:
layout.label(item.name, icon="BRUSH_DATA")
layout.prop(item, "enabled", text="")
@@ -69,34 +99,54 @@ def decal_print(modifier, layout, context):
row.prop(modifier, "height")
layout.separator()
- ui_list.draw_modifier_list(layout, "DecalMgrListUI", modifier, "managers",
- "active_manager_index", rows=2, maxrows=3)
+ ui_list.draw_modifier_list(
+ layout,
+ "DecalMgrListUI",
+ modifier,
+ "managers",
+ "active_manager_index",
+ rows=2,
+ maxrows=3,
+ )
try:
mgr_ref = modifier.managers[modifier.active_manager_index]
except:
pass
else:
scene = context.scene.plasma_scene
- decal_mgr = next((i for i in scene.decal_managers if i.display_name == mgr_ref), None)
+ decal_mgr = next(
+ (i for i in scene.decal_managers if i.display_name == mgr_ref), None
+ )
layout.alert = decal_mgr is None
layout.prop_search(mgr_ref, "name", scene, "decal_managers", icon="BRUSH_DATA")
layout.alert = False
+
def decal_receive(modifier, layout, context):
- ui_list.draw_modifier_list(layout, "DecalMgrListUI", modifier, "managers",
- "active_manager_index", rows=2, maxrows=3)
+ ui_list.draw_modifier_list(
+ layout,
+ "DecalMgrListUI",
+ modifier,
+ "managers",
+ "active_manager_index",
+ rows=2,
+ maxrows=3,
+ )
try:
mgr_ref = modifier.managers[modifier.active_manager_index]
except:
pass
else:
scene = context.scene.plasma_scene
- decal_mgr = next((i for i in scene.decal_managers if i.display_name == mgr_ref), None)
+ decal_mgr = next(
+ (i for i in scene.decal_managers if i.display_name == mgr_ref), None
+ )
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
@@ -128,12 +178,16 @@ def dynatext(modifier, layout, context):
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.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.alert = modifier.margin_left + modifier.margin_right >= int(
+ modifier.texture.plasma_layer.dynatext_resolution
+ )
col.prop(modifier, "margin_left")
col.prop(modifier, "margin_right")
@@ -142,6 +196,7 @@ def dynatext(modifier, layout, context):
flow.prop_menu_enum(modifier, "justify")
flow.prop(modifier, "line_spacing")
+
def fademod(modifier, layout, context):
layout.prop(modifier, "fader_type")
@@ -162,17 +217,23 @@ def fademod(modifier, layout, context):
col.prop(modifier, "far_opaq")
col.prop(modifier, "far_trans")
- if (modifier.fader_type in ("SimpleDist", "DistOpacity") and
- not (modifier.near_trans <= modifier.near_opaq <= modifier.far_opaq <= modifier.far_trans)):
+ if modifier.fader_type in ("SimpleDist", "DistOpacity") and not (
+ modifier.near_trans
+ <= modifier.near_opaq
+ <= modifier.far_opaq
+ <= modifier.far_trans
+ ):
# Warn the user that the values are not recommended.
layout.label("Distance values must be equal or increasing!", icon="ERROR")
+
def followmod(modifier, layout, context):
layout.row().prop(modifier, "follow_mode", expand=True)
layout.prop(modifier, "leader_type")
if modifier.leader_type == "kFollowObject":
layout.prop(modifier, "leader", icon="OUTLINER_OB_MESH")
+
def grass_shader(modifier, layout, context):
layout.prop(modifier, "wave_selector", icon="SMOOTHCURVE")
layout.separator()
@@ -188,6 +249,7 @@ def grass_shader(modifier, layout, context):
col.prop(wave, "direction", text="")
box.prop(wave, "speed")
+
def lighting(modifier, layout, context):
split = layout.split()
col = split.column()
@@ -208,34 +270,65 @@ def lighting(modifier, layout, context):
col.label("Satan remains ensconced deep in the abyss...", icon="GHOST_ENABLED")
col.label("Animated lights will be cast at runtime.", icon="LAYER_USED")
col.label("Projection lights will be cast at runtime.", icon="LAYER_USED")
- col.label("Specular lights will be cast to specular materials at runtime.", icon="LAYER_USED")
- col.label("Other Plasma lights {} be cast at runtime.".format("will" if modifier.rt_lights else "will NOT"),
- icon="LAYER_USED")
+ col.label(
+ "Specular lights will be cast to specular materials at runtime.",
+ icon="LAYER_USED",
+ )
+ col.label(
+ "Other Plasma lights {} be cast at runtime.".format(
+ "will" if modifier.rt_lights else "will NOT"
+ ),
+ icon="LAYER_USED",
+ )
map_type = "a lightmap" if lightmap.bake_lightmap else "vertex colors"
if lightmap.enabled and lightmap.lights:
- col.label("All '{}' lights will be baked to {}".format(lightmap.lights.name, map_type),
- icon="LAYER_USED")
+ col.label(
+ "All '{}' lights will be baked to {}".format(
+ lightmap.lights.name, map_type
+ ),
+ icon="LAYER_USED",
+ )
elif have_static_lights:
light_type = "Blender-only" if modifier.rt_lights else "unanimated"
- col.label("Other {} lights will be baked to {} (if applicable).".format(light_type, map_type), icon="LAYER_USED")
+ col.label(
+ "Other {} lights will be baked to {} (if applicable).".format(
+ light_type, map_type
+ ),
+ icon="LAYER_USED",
+ )
else:
col.label("No static lights will be baked.", icon="LAYER_USED")
+
def lightmap(modifier, layout, context):
pl_scene = context.scene.plasma_scene
is_texture = modifier.bake_type == "lightmap"
layout.prop(modifier, "bake_type")
if modifier.bake_type == "vcol":
- col_layer = next((i for i in modifier.id_data.data.vertex_colors if i.name.lower() in _VERTEX_COLOR_LAYERS), None)
+ col_layer = next(
+ (
+ i
+ for i in modifier.id_data.data.vertex_colors
+ if i.name.lower() in _VERTEX_COLOR_LAYERS
+ ),
+ None,
+ )
if col_layer is not None:
- layout.label("Mesh color layer '{}' will override this lighting.".format(col_layer.name), icon="ERROR")
+ layout.label(
+ "Mesh color layer '{}' will override this lighting.".format(
+ col_layer.name
+ ),
+ icon="ERROR",
+ )
col = layout.column()
col.active = is_texture
col.prop(modifier, "quality")
- layout.prop_search(modifier, "bake_pass_name", pl_scene, "bake_passes", icon="RENDERLAYERS")
+ layout.prop_search(
+ modifier, "bake_pass_name", pl_scene, "bake_passes", icon="RENDERLAYERS"
+ )
layout.prop(modifier, "lights")
col = layout.column()
col.active = is_texture
@@ -247,15 +340,23 @@ def lightmap(modifier, layout, context):
col.prop(modifier, "image", icon="IMAGE_RGB")
# Lightmaps can only be applied to objects with opaque materials.
- if is_texture and any((i.use_transparency for i in modifier.id_data.data.materials if i is not None)):
+ if is_texture and any(
+ (i.use_transparency for i in modifier.id_data.data.materials if i is not None)
+ ):
layout.label("Transparent objects cannot be lightmapped.", icon="ERROR")
else:
row = layout.row(align=True)
if modifier.bake_lightmap:
- row.operator("object.plasma_lightmap_preview", "Preview", icon="RENDER_STILL").final = False
- row.operator("object.plasma_lightmap_preview", "Bake for Export", icon="RENDER_STILL").final = True
+ row.operator(
+ "object.plasma_lightmap_preview", "Preview", icon="RENDER_STILL"
+ ).final = False
+ row.operator(
+ "object.plasma_lightmap_preview", "Bake for Export", icon="RENDER_STILL"
+ ).final = True
else:
- row.operator("object.plasma_lightmap_preview", "Bake", icon="RENDER_STILL").final = True
+ row.operator(
+ "object.plasma_lightmap_preview", "Bake", icon="RENDER_STILL"
+ ).final = True
# Kind of clever stuff to show the user a preview...
# We can't show images, so we make a hidden ImageTexture called LIGHTMAPGEN_PREVIEW. We check
@@ -264,10 +365,13 @@ def lightmap(modifier, layout, context):
if is_texture:
tex = bpy.data.textures.get("LIGHTMAPGEN_PREVIEW")
if tex is not None and tex.image is not None:
- im_name = "{}_LIGHTMAPGEN_PREVIEW.png".format(context.active_object.name)
+ im_name = "{}_LIGHTMAPGEN_PREVIEW.png".format(
+ context.active_object.name
+ )
if tex.image.name == im_name:
layout.template_preview(tex, show_buttons=False)
+
def rtshadow(modifier, layout, context):
split = layout.split()
col = split.column()
@@ -279,6 +383,7 @@ def rtshadow(modifier, layout, context):
col.prop(modifier, "limit_resolution")
col.prop(modifier, "self_shadow")
+
def viewfacemod(modifier, layout, context):
layout.prop(modifier, "preset_options")
@@ -302,8 +407,20 @@ def viewfacemod(modifier, layout, context):
col.enabled = modifier.offset
col.prop(modifier, "offset_coord")
+
class VisRegionListUI(bpy.types.UIList):
- def draw_item(self, context, layout, data, item, icon, active_data, active_property, index=0, flt_flag=0):
+ def draw_item(
+ self,
+ context,
+ layout,
+ data,
+ item,
+ icon,
+ active_data,
+ active_property,
+ index=0,
+ flt_flag=0,
+ ):
if item.control_region is None:
layout.label("[No Object Specified]", icon="ERROR")
else:
@@ -312,12 +429,20 @@ class VisRegionListUI(bpy.types.UIList):
def visibility(modifier, layout, context):
- ui_list.draw_modifier_list(layout, "VisRegionListUI", modifier, "regions",
- "active_region_index", rows=2, maxrows=3)
+ ui_list.draw_modifier_list(
+ layout,
+ "VisRegionListUI",
+ modifier,
+ "regions",
+ "active_region_index",
+ rows=2,
+ maxrows=3,
+ )
if modifier.regions:
layout.prop(modifier.regions[modifier.active_region_index], "control_region")
+
def visregion(modifier, layout, context):
layout.prop(modifier, "mode")
diff --git a/korman/ui/modifiers/sound.py b/korman/ui/modifiers/sound.py
index 95a7826..4f9f7be 100644
--- a/korman/ui/modifiers/sound.py
+++ b/korman/ui/modifiers/sound.py
@@ -17,10 +17,12 @@ import bpy
from .. import ui_list
+
def random_sound(modifier, layout, context):
parent_bo = modifier.id_data.parent
- collision_bad = (modifier.mode == "collision" and (parent_bo is None or
- not parent_bo.plasma_modifiers.collision.enabled))
+ collision_bad = modifier.mode == "collision" and (
+ parent_bo is None or not parent_bo.plasma_modifiers.collision.enabled
+ )
layout.alert = collision_bad
layout.prop(modifier, "mode")
if collision_bad:
@@ -50,13 +52,26 @@ def random_sound(modifier, layout, context):
layout.alert = len(modifier.surfaces) == 0
layout.prop_menu_enum(modifier, "surfaces")
+
def _draw_fade_ui(modifier, layout, label):
layout.label(label)
layout.prop(modifier, "fade_type", text="")
layout.prop(modifier, "length")
+
class SoundListUI(bpy.types.UIList):
- def draw_item(self, context, layout, data, item, icon, active_data, active_property, index=0, flt_flag=0):
+ def draw_item(
+ self,
+ context,
+ layout,
+ data,
+ item,
+ icon,
+ active_data,
+ active_property,
+ index=0,
+ flt_flag=0,
+ ):
if item.sound:
layout.prop(item, "name", emboss=False, icon="SOUND", text="")
layout.prop(item, "enabled", text="")
@@ -65,8 +80,15 @@ class SoundListUI(bpy.types.UIList):
def soundemit(modifier, layout, context):
- ui_list.draw_modifier_list(layout, "SoundListUI", modifier, "sounds",
- "active_sound_index", rows=2, maxrows=3)
+ ui_list.draw_modifier_list(
+ layout,
+ "SoundListUI",
+ modifier,
+ "sounds",
+ "active_sound_index",
+ rows=2,
+ maxrows=3,
+ )
try:
sound = modifier.sounds[modifier.active_sound_index]
@@ -89,7 +111,9 @@ def soundemit(modifier, layout, context):
if data.packed_file is None:
row.operator("sound.plasma_pack", icon="UGLYPACKAGE", text="")
else:
- row.operator_menu_enum("sound.plasma_unpack", "method", icon="PACKAGE", text="")
+ row.operator_menu_enum(
+ "sound.plasma_unpack", "method", icon="PACKAGE", text=""
+ )
col = split.column()
col.enabled = data is not None
diff --git a/korman/ui/modifiers/water.py b/korman/ui/modifiers/water.py
index 08313b9..e30f5f0 100644
--- a/korman/ui/modifiers/water.py
+++ b/korman/ui/modifiers/water.py
@@ -17,6 +17,7 @@ import bpy
from .. import ui_list
+
def swimregion(modifier, layout, context):
split = layout.split()
col = split.column()
@@ -56,6 +57,7 @@ def swimregion(modifier, layout, context):
layout.prop(modifier, "current")
+
def water_basic(modifier, layout, context):
layout.prop(modifier, "wind_object")
layout.prop(modifier, "envmap")
@@ -91,6 +93,7 @@ def water_basic(modifier, layout, context):
col.prop(modifier, "zero_wave", text="Start")
col.prop(modifier, "depth_wave", text="End")
+
def _wavestate(modifier, layout, context):
split = layout.split()
col = split.column()
@@ -104,18 +107,39 @@ def _wavestate(modifier, layout, context):
col.prop(modifier, "chop")
col.prop(modifier, "angle_dev")
+
water_geostate = _wavestate
water_texstate = _wavestate
+
class ShoreListUI(bpy.types.UIList):
- def draw_item(self, context, layout, data, item, icon, active_data, active_property, index=0, flt_flag=0):
+ def draw_item(
+ self,
+ context,
+ layout,
+ data,
+ item,
+ icon,
+ active_data,
+ active_property,
+ index=0,
+ flt_flag=0,
+ ):
layout.prop(item, "display_name", emboss=False, text="", icon="MOD_WAVE")
def water_shore(modifier, layout, context):
- ui_list.draw_modifier_list(layout, "ShoreListUI", modifier, "shores",
- "active_shore_index", name_prefix="Shore",
- name_prop="display_name", rows=2, maxrows=3)
+ ui_list.draw_modifier_list(
+ layout,
+ "ShoreListUI",
+ modifier,
+ "shores",
+ "active_shore_index",
+ name_prefix="Shore",
+ name_prop="display_name",
+ rows=2,
+ maxrows=3,
+ )
# Display the active shore
if modifier.shores:
diff --git a/korman/ui/ui_anim.py b/korman/ui/ui_anim.py
index 3741854..c3a03f9 100644
--- a/korman/ui/ui_anim.py
+++ b/korman/ui/ui_anim.py
@@ -17,20 +17,41 @@ import bpy
from . import ui_list
+
class AnimListUI(bpy.types.UIList):
- def draw_item(self, context, layout, data, item, icon, active_data, active_property, index=0, flt_flag=0):
+ def draw_item(
+ self,
+ context,
+ layout,
+ data,
+ item,
+ icon,
+ active_data,
+ active_property,
+ index=0,
+ flt_flag=0,
+ ):
layout.label(item.animation_name, icon="ANIM")
-def draw_multi_animation(layout, context_attr, prop_base, anims_collection_name, *, use_box=False, **kwargs):
+def draw_multi_animation(
+ layout, context_attr, prop_base, anims_collection_name, *, use_box=False, **kwargs
+):
# Yeah, I know this looks weird, but it lets us pretend that the PlasmaAnimationCollection
# is a first class collection property. Fancy.
anims = getattr(prop_base, anims_collection_name)
kwargs.setdefault("rows", 2)
- ui_list.draw_list(layout, "AnimListUI", context_attr, anims,
- "animation_collection", "active_animation_index",
- name_prop="animation_name", name_prefix="Animation",
- **kwargs)
+ ui_list.draw_list(
+ layout,
+ "AnimListUI",
+ context_attr,
+ anims,
+ "animation_collection",
+ "active_animation_index",
+ name_prop="animation_name",
+ name_prefix="Animation",
+ **kwargs
+ )
try:
anim = anims.animation_collection[anims.active_animation_index]
except IndexError:
@@ -39,6 +60,7 @@ def draw_multi_animation(layout, context_attr, prop_base, anims_collection_name,
sub = layout.box() if use_box else layout
draw_single_animation(sub, anim)
+
def draw_single_animation(layout, anim):
row = layout.row()
row.enabled = not anim.is_entire_animation
@@ -62,7 +84,9 @@ def draw_single_animation(layout, anim):
action = getattr(anim.id_data.animation_data, "action", None)
if action:
layout.separator()
- layout.prop_search(anim, "initial_marker", action, "pose_markers", icon="PMARKER")
+ layout.prop_search(
+ anim, "initial_marker", action, "pose_markers", icon="PMARKER"
+ )
col = layout.column()
col.active = anim.loop and not anim.sdl_var
col.prop_search(anim, "loop_start", action, "pose_markers", icon="PMARKER")
diff --git a/korman/ui/ui_camera.py b/korman/ui/ui_camera.py
index ca30deb..4c52331 100644
--- a/korman/ui/ui_camera.py
+++ b/korman/ui/ui_camera.py
@@ -18,7 +18,10 @@ import bpy
from .. import helpers
from . import ui_list
-def _draw_alert_prop(layout, props, the_prop, cam_type, alert_cam="", min=None, max=None, **kwargs):
+
+def _draw_alert_prop(
+ layout, props, the_prop, cam_type, alert_cam="", min=None, max=None, **kwargs
+):
can_alert = not alert_cam or alert_cam == cam_type
if can_alert:
value = getattr(props, the_prop)
@@ -31,6 +34,7 @@ def _draw_alert_prop(layout, props, the_prop, cam_type, alert_cam="", min=None,
else:
layout.prop(props, the_prop, **kwargs)
+
def _draw_gated_prop(layout, props, gate_prop, actual_prop):
row = layout.row(align=True)
row.prop(props, gate_prop, text="")
@@ -38,6 +42,7 @@ def _draw_gated_prop(layout, props, gate_prop, actual_prop):
row.active = getattr(props, gate_prop)
row.prop(props, actual_prop)
+
def draw_camera_manipulation_props(layout, cam_type, props):
# Camera Panning
split = layout.split()
@@ -54,6 +59,7 @@ def draw_camera_manipulation_props(layout, cam_type, props):
_draw_gated_prop(col, props, "limit_zoom", "zoom_max")
_draw_gated_prop(col, props, "limit_zoom", "zoom_rate")
+
def draw_camera_mode_props(layout, cam_type, props):
# Point of Attention
split = layout.split()
@@ -81,6 +87,7 @@ def draw_camera_mode_props(layout, cam_type, props):
col_target.active = props.poa_type != "none"
col_target.prop(props, "ignore_subworld")
+
def draw_camera_poa_props(layout, cam_type, props):
trans = props.transition
@@ -101,6 +108,7 @@ def draw_camera_poa_props(layout, cam_type, props):
col.prop(props, "poa_offset", text="")
col.prop(props, "poa_worldspace")
+
def draw_camera_pos_props(layout, cam_type, props):
trans = props.transition
@@ -111,12 +119,33 @@ def draw_camera_pos_props(layout, cam_type, props):
# Position Transitions
col.active = cam_type != "circle"
col.label("Default Position Transition:")
- _draw_alert_prop(col, trans, "pos_acceleration", cam_type,
- alert_cam="rail", max=10.0, text="Acceleration")
- _draw_alert_prop(col, trans, "pos_deceleration", cam_type,
- alert_cam="rail", max=10.0, text="Deceleration")
- _draw_alert_prop(col, trans, "pos_velocity", cam_type,
- alert_cam="rail", max=10.0, text="Maximum Velocity")
+ _draw_alert_prop(
+ col,
+ trans,
+ "pos_acceleration",
+ cam_type,
+ alert_cam="rail",
+ max=10.0,
+ text="Acceleration",
+ )
+ _draw_alert_prop(
+ col,
+ trans,
+ "pos_deceleration",
+ cam_type,
+ alert_cam="rail",
+ max=10.0,
+ text="Deceleration",
+ )
+ _draw_alert_prop(
+ col,
+ trans,
+ "pos_velocity",
+ cam_type,
+ alert_cam="rail",
+ max=10.0,
+ text="Maximum Velocity",
+ )
col = col.column()
col.active = cam_type in {"firstperson", "follow"}
col.prop(trans, "pos_cut", text="Cut Animation")
@@ -128,6 +157,7 @@ def draw_camera_pos_props(layout, cam_type, props):
col.prop(props, "pos_offset", text="")
col.prop(props, "pos_worldspace")
+
def draw_circle_camera_props(layout, props):
# Circle Camera Stuff
layout.prop(props, "circle_center")
@@ -137,6 +167,7 @@ def draw_circle_camera_props(layout, props):
row.active = props.circle_center is None
row.prop(props, "circle_radius_ui")
+
class CameraButtonsPanel:
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
@@ -144,7 +175,7 @@ class CameraButtonsPanel:
@classmethod
def poll(cls, context):
- return (context.camera and context.scene.render.engine == "PLASMA_GAME")
+ return context.camera and context.scene.render.engine == "PLASMA_GAME"
class PlasmaCameraTypePanel(CameraButtonsPanel, bpy.types.Panel):
@@ -189,7 +220,10 @@ class PlasmaCameraCirclePanel(CameraButtonsPanel, bpy.types.Panel):
@classmethod
def poll(cls, context):
- return super().poll(context) and context.camera.plasma_camera.camera_type == "circle"
+ return (
+ super().poll(context)
+ and context.camera.plasma_camera.camera_type == "circle"
+ )
class PlasmaCameraAnimationPanel(CameraButtonsPanel, bpy.types.Panel):
@@ -204,7 +238,9 @@ class PlasmaCameraAnimationPanel(CameraButtonsPanel, bpy.types.Panel):
split = layout.split()
col = split.column()
col.label("Animation:")
- anim_enabled = props.anim_enabled or context.object.plasma_modifiers.animation.enabled
+ anim_enabled = (
+ props.anim_enabled or context.object.plasma_modifiers.animation.enabled
+ )
col.active = anim_enabled and context.object.plasma_object.has_animation_data
col.prop(props, "start_on_push")
col.prop(props, "stop_on_pop")
@@ -212,17 +248,24 @@ class PlasmaCameraAnimationPanel(CameraButtonsPanel, bpy.types.Panel):
col = split.column()
col.active = camera.camera_type == "rail"
- invalid = camera.camera_type == "rail" and not context.object.plasma_object.has_transform_animation
+ invalid = (
+ camera.camera_type == "rail"
+ and not context.object.plasma_object.has_transform_animation
+ )
col.alert = invalid
col.label("Rail:")
col.prop(props, "rail_pos", text="")
if invalid:
- col.label("Rail cameras must have a transformation animation!", icon="ERROR")
+ col.label(
+ "Rail cameras must have a transformation animation!", icon="ERROR"
+ )
def draw_header(self, context):
self.layout.active = context.object.plasma_object.has_animation_data
if not context.object.plasma_modifiers.animation.enabled:
- self.layout.prop(context.camera.plasma_camera.settings, "anim_enabled", text="")
+ self.layout.prop(
+ context.camera.plasma_camera.settings, "anim_enabled", text=""
+ )
class PlasmaCameraViewPanel(CameraButtonsPanel, bpy.types.Panel):
@@ -234,7 +277,18 @@ class PlasmaCameraViewPanel(CameraButtonsPanel, bpy.types.Panel):
class TransitionListUI(bpy.types.UIList):
- def draw_item(self, context, layout, data, item, icon, active_data, active_property, index=0, flt_flag=0):
+ def draw_item(
+ self,
+ context,
+ layout,
+ data,
+ item,
+ icon,
+ active_data,
+ active_property,
+ index=0,
+ flt_flag=0,
+ ):
if item.camera is None:
layout.label("[Default Transition]")
else:
@@ -249,8 +303,16 @@ class PlasmaCameraTransitionPanel(CameraButtonsPanel, bpy.types.Panel):
layout = self.layout
camera = context.camera.plasma_camera
- ui_list.draw_list(layout, "TransitionListUI", "camera", camera, "transitions",
- "active_transition_index", rows=3, maxrows=4)
+ ui_list.draw_list(
+ layout,
+ "TransitionListUI",
+ "camera",
+ camera,
+ "transitions",
+ "active_transition_index",
+ rows=3,
+ maxrows=4,
+ )
try:
item = camera.transitions[camera.active_transition_index]
diff --git a/korman/ui/ui_image.py b/korman/ui/ui_image.py
index aa17f8b..7f06318 100644
--- a/korman/ui/ui_image.py
+++ b/korman/ui/ui_image.py
@@ -15,6 +15,7 @@
import bpy
+
class PlasmaImageEditorHeader(bpy.types.Header):
bl_space_type = "IMAGE_EDITOR"
diff --git a/korman/ui/ui_lamp.py b/korman/ui/ui_lamp.py
index 7c89b82..29b82eb 100644
--- a/korman/ui/ui_lamp.py
+++ b/korman/ui/ui_lamp.py
@@ -15,6 +15,7 @@
import bpy
+
class LampButtonsPanel:
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
@@ -22,8 +23,11 @@ class LampButtonsPanel:
@classmethod
def poll(cls, context):
- return (context.object and context.scene.render.engine == "PLASMA_GAME" and
- isinstance(context.object.data, bpy.types.Lamp))
+ return (
+ context.object
+ and context.scene.render.engine == "PLASMA_GAME"
+ and isinstance(context.object.data, bpy.types.Lamp)
+ )
class PlasmaLampPanel(LampButtonsPanel, bpy.types.Panel):
@@ -95,10 +99,12 @@ def _plasma_draw_area_lamp(self, context):
sub.prop(lamp, "size_y", text="D")
sub.prop(plasma_lamp, "size_height", text="H")
+
# Swap out the draw functions for the standard Area Shape panel
# TODO: Maybe we should consider standardizing an interface for overriding
# standard Blender panels? This seems like a really useful approach.
from bl_ui import properties_data_lamp
+
properties_data_lamp.DATA_PT_area._draw_blender = properties_data_lamp.DATA_PT_area.draw
properties_data_lamp.DATA_PT_area.draw = _draw_area_lamp
del properties_data_lamp
diff --git a/korman/ui/ui_list.py b/korman/ui/ui_list.py
index 7f7e032..ef0414f 100644
--- a/korman/ui/ui_list.py
+++ b/korman/ui/ui_list.py
@@ -15,28 +15,38 @@
import bpy
-def draw_list(layout, listtype, context_attr, prop_base, collection_name, index_name, **kwargs):
+
+def draw_list(
+ layout, listtype, context_attr, prop_base, collection_name, index_name, **kwargs
+):
"""Draws a generic UI list, including add/remove buttons. Note that in order to use this,
- the parent datablock must be available in the context provided to operators. This should
- always be true, but this is Blender...
- Arguments:
- - layout: required
- - listtype: bpy.types.UIList subclass
- - context_attr: attribute name to get the properties from in the current context
- - prop_base: property group owning the collection
- - collection_name: name of the collection property
- - index_name: name of the active element index property
- - name_prefix: (optional) prefix to apply to display name of new elements
- - name_prop: (optional) property for each element's display name
- *** any other arguments are passed as keyword arguments to the template_list call
+ the parent datablock must be available in the context provided to operators. This should
+ always be true, but this is Blender...
+ Arguments:
+ - layout: required
+ - listtype: bpy.types.UIList subclass
+ - context_attr: attribute name to get the properties from in the current context
+ - prop_base: property group owning the collection
+ - collection_name: name of the collection property
+ - index_name: name of the active element index property
+ - name_prefix: (optional) prefix to apply to display name of new elements
+ - name_prop: (optional) property for each element's display name
+ *** any other arguments are passed as keyword arguments to the template_list call
"""
prop_path = prop_base.path_from_id()
name_prefix = kwargs.pop("name_prefix", "")
name_prop = kwargs.pop("name_prop", "")
row = layout.row()
- row.template_list(listtype, collection_name, prop_base, collection_name,
- prop_base, index_name, **kwargs)
+ row.template_list(
+ listtype,
+ collection_name,
+ prop_base,
+ collection_name,
+ prop_base,
+ index_name,
+ **kwargs
+ )
col = row.column(align=True)
op = col.operator("ui.plasma_collection_add", icon="ZOOMIN", text="")
op.context = context_attr
@@ -51,5 +61,10 @@ def draw_list(layout, listtype, context_attr, prop_base, collection_name, index_
op.collection_prop = collection_name
op.index_prop = index_name
-def draw_modifier_list(layout, listtype, prop_base, collection_name, index_name, **kwargs):
- draw_list(layout, listtype, "object", prop_base, collection_name, index_name, **kwargs)
+
+def draw_modifier_list(
+ layout, listtype, prop_base, collection_name, index_name, **kwargs
+):
+ draw_list(
+ layout, listtype, "object", prop_base, collection_name, index_name, **kwargs
+ )
diff --git a/korman/ui/ui_menus.py b/korman/ui/ui_menus.py
index ad38020..44caeef 100644
--- a/korman/ui/ui_menus.py
+++ b/korman/ui/ui_menus.py
@@ -15,6 +15,7 @@
from ..operators.op_mesh import *
+
class PlasmaMenu:
@classmethod
def poll(cls, context):
@@ -31,7 +32,9 @@ class PlasmaAddMenu(PlasmaMenu, bpy.types.Menu):
layout.operator("mesh.plasma_flare_add", text="Lamp Flare", icon="PARTICLES")
layout.operator("mesh.plasma_ladder_add", text="Ladder", icon="COLLAPSEMENU")
- layout.operator("mesh.plasma_linkingbook_add", text="Linking Book", icon="FILE_IMAGE")
+ layout.operator(
+ "mesh.plasma_linkingbook_add", text="Linking Book", icon="FILE_IMAGE"
+ )
def build_menu(self, context):
if context.scene.render.engine != "PLASMA_GAME":
@@ -46,17 +49,25 @@ class PlasmaHelpMenu(PlasmaMenu, bpy.types.Menu):
def draw(self, context):
layout = self.layout
- layout.operator("wm.url_open", text="About Korman", icon="URL").url = "https://guildofwriters.org/wiki/Korman"
- layout.operator("wm.url_open", text="Getting Started", icon="URL").url = "https://guildofwriters.org/wiki/Korman:Getting_Started"
- layout.operator("wm.url_open", text="Tutorials", icon="URL").url = "https://guildofwriters.org/wiki/Category:Korman_Tutorials"
+ layout.operator(
+ "wm.url_open", text="About Korman", icon="URL"
+ ).url = "https://guildofwriters.org/wiki/Korman"
+ layout.operator(
+ "wm.url_open", text="Getting Started", icon="URL"
+ ).url = "https://guildofwriters.org/wiki/Korman:Getting_Started"
+ layout.operator(
+ "wm.url_open", text="Tutorials", icon="URL"
+ ).url = "https://guildofwriters.org/wiki/Category:Korman_Tutorials"
def build_menu(self, context):
self.layout.menu("menu.plasma_help", text="Korman", icon="URL")
+
def register():
bpy.types.INFO_MT_add.append(PlasmaAddMenu.build_menu)
bpy.types.INFO_MT_help.prepend(PlasmaHelpMenu.build_menu)
+
def unregister():
bpy.types.INFO_MT_add.remove(PlasmaAddMenu.build_menu)
bpy.types.INFO_MT_help.remove(PlasmaHelpMenu.build_menu)
diff --git a/korman/ui/ui_modifiers.py b/korman/ui/ui_modifiers.py
index 6f9b815..f8d485f 100644
--- a/korman/ui/ui_modifiers.py
+++ b/korman/ui/ui_modifiers.py
@@ -17,6 +17,7 @@ import bpy
from . import modifiers as modifier_draw
+
class ModifierButtonsPanel:
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
@@ -54,7 +55,9 @@ class PlasmaModifiersPanel(ModifierButtonsPanel, bpy.types.Panel):
# First, let's sort the list of modifiers based on their display order
# We don't do this sort in the property itself because this is really just a UI hint.
- modifiers = sorted(obj.plasma_modifiers.modifiers, key=lambda x: x.display_order)
+ modifiers = sorted(
+ obj.plasma_modifiers.modifiers, key=lambda x: x.display_order
+ )
# Inside the modifier_draw module, we have draw callbables for each modifier
# We'll loop through the list of active modifiers and call the drawprocs for the enabled mods
@@ -65,7 +68,7 @@ class PlasmaModifiersPanel(ModifierButtonsPanel, bpy.types.Panel):
def _draw_modifier_template(self, modifier):
"""This draws our lookalike modifier template and returns a UILayout object for each modifier
- to consume in order to draw its specific properties"""
+ to consume in order to draw its specific properties"""
layout = self.layout.box()
# This is the main title row. It mimics the Blender template_modifier, which (unfortunately)
@@ -77,11 +80,21 @@ class PlasmaModifiersPanel(ModifierButtonsPanel, bpy.types.Panel):
row.prop(modifier, "show_expanded", text="", icon=exicon, emboss=False)
row.label(text=modifier.bl_label, icon=getattr(modifier, "bl_icon", "NONE"))
- row.operator("object.plasma_modifier_move_up", text="", icon="TRIA_UP").active_modifier = modifier.display_order
- row.operator("object.plasma_modifier_move_down", text="", icon="TRIA_DOWN").active_modifier = modifier.display_order
- row.operator("object.plasma_modifier_copy", text="", icon="COPYDOWN").active_modifier = modifier.display_order
- row.operator("object.plasma_modifier_reset", text="", icon="FILE_REFRESH").active_modifier = modifier.display_order
- row.operator("object.plasma_modifier_remove", text="", icon="X").active_modifier = modifier.display_order
+ row.operator(
+ "object.plasma_modifier_move_up", text="", icon="TRIA_UP"
+ ).active_modifier = modifier.display_order
+ row.operator(
+ "object.plasma_modifier_move_down", text="", icon="TRIA_DOWN"
+ ).active_modifier = modifier.display_order
+ row.operator(
+ "object.plasma_modifier_copy", text="", icon="COPYDOWN"
+ ).active_modifier = modifier.display_order
+ row.operator(
+ "object.plasma_modifier_reset", text="", icon="FILE_REFRESH"
+ ).active_modifier = modifier.display_order
+ row.operator(
+ "object.plasma_modifier_remove", text="", icon="X"
+ ).active_modifier = modifier.display_order
# Now we return the modifier box, which is populated with the modifier specific properties
# by whatever insanity is in the modifier module. modifier modifier modifier...
@@ -97,8 +110,16 @@ class PlasmaModifiersSpecialMenu(ModifierButtonsPanel, bpy.types.Menu):
layout.operator("object.plasma_modifier_copy_to_selection", icon="PASTEDOWN")
layout.separator()
- layout.operator("object.plasma_modifier_copy", icon="COPYDOWN", text="Copy Modifiers").active_modifier = -1
- layout.operator("object.plasma_modifier_paste", icon="PASTEDOWN", text="Paste Modifier(s)")
+ layout.operator(
+ "object.plasma_modifier_copy", icon="COPYDOWN", text="Copy Modifiers"
+ ).active_modifier = -1
+ layout.operator(
+ "object.plasma_modifier_paste", icon="PASTEDOWN", text="Paste Modifier(s)"
+ )
layout.separator()
- layout.operator("object.plasma_modifier_reset", text="Reset Modifiers", icon="FILE_REFRESH").active_modifier = -1
- layout.operator("object.plasma_modifier_remove", text="Remove Modifiers", icon="X").active_modifier = -1
+ layout.operator(
+ "object.plasma_modifier_reset", text="Reset Modifiers", icon="FILE_REFRESH"
+ ).active_modifier = -1
+ layout.operator(
+ "object.plasma_modifier_remove", text="Remove Modifiers", icon="X"
+ ).active_modifier = -1
diff --git a/korman/ui/ui_object.py b/korman/ui/ui_object.py
index 7b679c2..368a3ed 100644
--- a/korman/ui/ui_object.py
+++ b/korman/ui/ui_object.py
@@ -15,6 +15,7 @@
import bpy
+
class ObjectButtonsPanel:
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
diff --git a/korman/ui/ui_render_layer.py b/korman/ui/ui_render_layer.py
index d87f04b..9549bf7 100644
--- a/korman/ui/ui_render_layer.py
+++ b/korman/ui/ui_render_layer.py
@@ -16,6 +16,7 @@
import bpy
from . import ui_list
+
class RenderLayerButtonsPanel:
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
@@ -27,7 +28,18 @@ class RenderLayerButtonsPanel:
class BakePassUI(bpy.types.UIList):
- def draw_item(self, context, layout, data, item, icon, active_data, active_property, index=0, flt_flag=0):
+ def draw_item(
+ self,
+ context,
+ layout,
+ data,
+ item,
+ icon,
+ active_data,
+ active_property,
+ index=0,
+ flt_flag=0,
+ ):
layout.prop(item, "display_name", emboss=False, text="", icon="RENDERLAYERS")
@@ -38,9 +50,18 @@ class PlasmaBakePassPanel(RenderLayerButtonsPanel, bpy.types.Panel):
layout = self.layout
scene = context.scene.plasma_scene
- ui_list.draw_list(layout, "BakePassUI", "scene", scene, "bake_passes",
- "active_pass_index", name_prefix="Pass",
- name_prop="display_name", rows=3, maxrows=3)
+ ui_list.draw_list(
+ layout,
+ "BakePassUI",
+ "scene",
+ scene,
+ "bake_passes",
+ "active_pass_index",
+ name_prefix="Pass",
+ name_prop="display_name",
+ rows=3,
+ maxrows=3,
+ )
active_pass_index = scene.active_pass_index
try:
diff --git a/korman/ui/ui_scene.py b/korman/ui/ui_scene.py
index ec72d82..70fef84 100644
--- a/korman/ui/ui_scene.py
+++ b/korman/ui/ui_scene.py
@@ -17,6 +17,7 @@ import bpy
import functools
from . import ui_list
+
class SceneButtonsPanel:
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
@@ -28,12 +29,34 @@ class SceneButtonsPanel:
class DecalManagerListUI(bpy.types.UIList):
- def draw_item(self, context, layout, data, item, icon, active_data, active_property, index=0, flt_flag=0):
+ def draw_item(
+ self,
+ context,
+ layout,
+ data,
+ item,
+ icon,
+ active_data,
+ active_property,
+ index=0,
+ flt_flag=0,
+ ):
layout.prop(item, "display_name", emboss=False, text="", icon="BRUSH_DATA")
class WetManagerListUI(bpy.types.UIList):
- def draw_item(self, context, layout, data, item, icon, active_data, active_property, index=0, flt_flag=0):
+ def draw_item(
+ self,
+ context,
+ layout,
+ data,
+ item,
+ icon,
+ active_data,
+ active_property,
+ index=0,
+ flt_flag=0,
+ ):
if item.name:
layout.label(item.name, icon="BRUSH_DATA")
layout.prop(item, "enabled", text="")
@@ -47,9 +70,17 @@ class PlasmaDecalManagersPanel(SceneButtonsPanel, bpy.types.Panel):
def draw(self, context):
layout, scene = self.layout, context.scene.plasma_scene
- ui_list.draw_list(layout, "DecalManagerListUI", "scene", scene, "decal_managers",
- "active_decal_index", name_prefix="Decal", name_prop="display_name",
- rows=3)
+ ui_list.draw_list(
+ layout,
+ "DecalManagerListUI",
+ "scene",
+ scene,
+ "decal_managers",
+ "active_decal_index",
+ name_prefix="Decal",
+ name_prop="display_name",
+ rows=3,
+ )
try:
decal_mgr = scene.decal_managers[scene.active_decal_index]
@@ -68,8 +99,10 @@ class PlasmaDecalManagersPanel(SceneButtonsPanel, bpy.types.Panel):
split = box.split()
col = split.column(align=True)
col.label("Scale:")
- col.alert = decal_mgr.decal_type in {"ripple", "puddle", "bullet", "torpedo"} \
- and decal_mgr.length != decal_mgr.width
+ col.alert = (
+ decal_mgr.decal_type in {"ripple", "puddle", "bullet", "torpedo"}
+ and decal_mgr.length != decal_mgr.width
+ )
col.prop(decal_mgr, "length")
col.prop(decal_mgr, "width")
@@ -77,7 +110,12 @@ class PlasmaDecalManagersPanel(SceneButtonsPanel, bpy.types.Panel):
col.label("Draw Settings:")
col.prop(decal_mgr, "intensity")
sub = col.row()
- sub.active = decal_mgr.decal_type in {"footprint_dry", "footprint_wet", "bullet", "torpedo"}
+ sub.active = decal_mgr.decal_type in {
+ "footprint_dry",
+ "footprint_wet",
+ "bullet",
+ "torpedo",
+ }
sub.prop(decal_mgr, "life_span")
sub = col.row()
sub.active = decal_mgr.decal_type in {"puddle", "ripple"}
@@ -86,15 +124,28 @@ class PlasmaDecalManagersPanel(SceneButtonsPanel, bpy.types.Panel):
if decal_mgr.decal_type in {"puddle", "ripple"}:
box.separator()
box.label("Wet Footprints:")
- ui_list.draw_list(box, "WetManagerListUI", "scene", decal_mgr, "wet_managers",
- "active_wet_index", rows=2, maxrows=3)
+ ui_list.draw_list(
+ box,
+ "WetManagerListUI",
+ "scene",
+ decal_mgr,
+ "wet_managers",
+ "active_wet_index",
+ rows=2,
+ maxrows=3,
+ )
try:
wet_ref = decal_mgr.wet_managers[decal_mgr.active_wet_index]
except:
pass
else:
- wet_mgr = next((i for i in scene.decal_managers if i.name == wet_ref.name), None)
+ wet_mgr = next(
+ (i for i in scene.decal_managers if i.name == wet_ref.name),
+ None,
+ )
box.alert = getattr(wet_mgr, "decal_type", None) == "footprint_wet"
- box.prop_search(wet_ref, "name", scene, "decal_managers", icon="NONE")
+ box.prop_search(
+ wet_ref, "name", scene, "decal_managers", icon="NONE"
+ )
if wet_ref.name == decal_mgr.name:
box.label(text="Circular reference", icon="ERROR")
diff --git a/korman/ui/ui_text.py b/korman/ui/ui_text.py
index 4d3fd12..ad2fe8f 100644
--- a/korman/ui/ui_text.py
+++ b/korman/ui/ui_text.py
@@ -15,6 +15,7 @@
import bpy
+
class PlasmaTextEditorHeader(bpy.types.Header):
bl_space_type = "TEXT_EDITOR"
diff --git a/korman/ui/ui_texture.py b/korman/ui/ui_texture.py
index a346d47..62f1a48 100644
--- a/korman/ui/ui_texture.py
+++ b/korman/ui/ui_texture.py
@@ -18,6 +18,7 @@ import bpy
from . import ui_list
from . import ui_anim
+
class TextureButtonsPanel:
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
@@ -48,15 +49,25 @@ class PlasmaEnvMapPanel(TextureButtonsPanel, bpy.types.Panel):
layout.separator()
layout.label("Visibility Sets:")
- ui_list.draw_list(layout, "VisRegionListUI", "texture", layer_props,
- "vis_regions", "active_region_index", rows=2, maxrows=3)
+ ui_list.draw_list(
+ layout,
+ "VisRegionListUI",
+ "texture",
+ layer_props,
+ "vis_regions",
+ "active_region_index",
+ rows=2,
+ maxrows=3,
+ )
rgns = layer_props.vis_regions
if layer_props.vis_regions:
layout.prop(rgns[layer_props.active_region_index], "control_region")
elif envmap.source == "IMAGE_FILE":
- op = layout.operator("image.plasma_build_cube_map",
- text="Build Cubemap from Cube Faces",
- icon="MATCUBE")
+ op = layout.operator(
+ "image.plasma_build_cube_map",
+ text="Build Cubemap from Cube Faces",
+ icon="MATCUBE",
+ )
op.texture_name = context.texture.name
@@ -128,5 +139,10 @@ class PlasmaLayerAnimationPanel(TextureButtonsPanel, bpy.types.Panel):
return False
def draw(self, context):
- ui_anim.draw_multi_animation(self.layout, "texture", context.texture.plasma_layer,
- "subanimations", use_box=True)
+ ui_anim.draw_multi_animation(
+ self.layout,
+ "texture",
+ context.texture.plasma_layer,
+ "subanimations",
+ use_box=True,
+ )
diff --git a/korman/ui/ui_toolbox.py b/korman/ui/ui_toolbox.py
index fac984a..fffaf6e 100644
--- a/korman/ui/ui_toolbox.py
+++ b/korman/ui/ui_toolbox.py
@@ -16,6 +16,7 @@
import bpy
import itertools
+
class ToolboxPanel:
bl_category = "Tools"
bl_space_type = "VIEW_3D"
@@ -35,40 +36,113 @@ class PlasmaToolboxPanel(ToolboxPanel, bpy.types.Panel):
col = layout.column(align=True)
col.label("Plasma Objects:")
- enable_all = col.operator("object.plasma_toggle_all_objects", icon="OBJECT_DATA", text="Enable All")
+ enable_all = col.operator(
+ "object.plasma_toggle_all_objects", icon="OBJECT_DATA", text="Enable All"
+ )
enable_all.enable = True
- all_plasma_objects = all((i.plasma_object.enabled for i in bpy.context.selected_objects))
- col.operator("object.plasma_toggle_selected_objects", icon="VIEW3D", text="Disable Selection" if all_plasma_objects else "Enable Selection")
- disable_all = col.operator("object.plasma_toggle_all_objects", icon="OBJECT_DATA", text="Disable All")
+ all_plasma_objects = all(
+ (i.plasma_object.enabled for i in bpy.context.selected_objects)
+ )
+ col.operator(
+ "object.plasma_toggle_selected_objects",
+ icon="VIEW3D",
+ text="Disable Selection" if all_plasma_objects else "Enable Selection",
+ )
+ disable_all = col.operator(
+ "object.plasma_toggle_all_objects", icon="OBJECT_DATA", text="Disable All"
+ )
disable_all.enable = False
col.label("Plasma Pages:")
- col.operator("object.plasma_move_selection_to_page", icon="BOOKMARKS", text="Move to Page")
- col.operator("object.plasma_select_page_objects", icon="RESTRICT_SELECT_OFF", text="Select Objects")
+ col.operator(
+ "object.plasma_move_selection_to_page",
+ icon="BOOKMARKS",
+ text="Move to Page",
+ )
+ col.operator(
+ "object.plasma_select_page_objects",
+ icon="RESTRICT_SELECT_OFF",
+ text="Select Objects",
+ )
col.label("Lighting:")
- col.operator("object.plasma_lightmap_bake", icon="RENDER_STILL", text="Bake All").bake_selection = False
- col.operator("object.plasma_lightmap_bake", icon="RENDER_REGION", text="Bake Selection").bake_selection = True
- col.operator("object.plasma_lightmap_clear", icon="X", text="Clear All").clear_selection = False
- col.operator("object.plasma_lightmap_clear", icon="X", text="Clear Selection").clear_selection = True
+ col.operator(
+ "object.plasma_lightmap_bake", icon="RENDER_STILL", text="Bake All"
+ ).bake_selection = False
+ col.operator(
+ "object.plasma_lightmap_bake", icon="RENDER_REGION", text="Bake Selection"
+ ).bake_selection = True
+ col.operator(
+ "object.plasma_lightmap_clear", icon="X", text="Clear All"
+ ).clear_selection = False
+ col.operator(
+ "object.plasma_lightmap_clear", icon="X", text="Clear Selection"
+ ).clear_selection = True
col.label("Package Sounds:")
- col.operator("object.plasma_toggle_sound_export", icon="MUTE_IPO_OFF", text="Enable All").enable = True
- all_sounds_export = all((i.package for i in itertools.chain.from_iterable(i.plasma_modifiers.soundemit.sounds for i in bpy.context.selected_objects if i.plasma_modifiers.soundemit.enabled)))
- col.operator("object.plasma_toggle_sound_export_selected", icon="OUTLINER_OB_SPEAKER", text="Disable Selection" if all_sounds_export else "Enable Selection")
- col.operator("object.plasma_toggle_sound_export", icon="MUTE_IPO_ON", text="Disable All").enable = False
+ col.operator(
+ "object.plasma_toggle_sound_export", icon="MUTE_IPO_OFF", text="Enable All"
+ ).enable = True
+ all_sounds_export = all(
+ (
+ i.package
+ for i in itertools.chain.from_iterable(
+ i.plasma_modifiers.soundemit.sounds
+ for i in bpy.context.selected_objects
+ if i.plasma_modifiers.soundemit.enabled
+ )
+ )
+ )
+ col.operator(
+ "object.plasma_toggle_sound_export_selected",
+ icon="OUTLINER_OB_SPEAKER",
+ text="Disable Selection" if all_sounds_export else "Enable Selection",
+ )
+ col.operator(
+ "object.plasma_toggle_sound_export", icon="MUTE_IPO_ON", text="Disable All"
+ ).enable = False
col.label("Textures:")
- col.operator("texture.plasma_enable_all_textures", icon="TEXTURE", text="Enable All")
- col.operator("texture.plasma_toggle_environment_maps", icon="IMAGE_RGB", text="Enable All EnvMaps").enable = True
- col.operator("texture.plasma_toggle_environment_maps", icon="IMAGE_RGB_ALPHA", text="Disable All EnvMaps").enable = False
+ col.operator(
+ "texture.plasma_enable_all_textures", icon="TEXTURE", text="Enable All"
+ )
+ col.operator(
+ "texture.plasma_toggle_environment_maps",
+ icon="IMAGE_RGB",
+ text="Enable All EnvMaps",
+ ).enable = True
+ col.operator(
+ "texture.plasma_toggle_environment_maps",
+ icon="IMAGE_RGB_ALPHA",
+ text="Disable All EnvMaps",
+ ).enable = False
# Double Sided Operators
col.label("Double Sided:")
- col.operator("mesh.plasma_toggle_double_sided", icon="MESH_DATA", text="Disable All").enable = False
- all_double_sided = all((i.data.show_double_sided for i in bpy.context.selected_objects if i.type == "MESH"))
- col.operator("mesh.plasma_toggle_double_sided_selected", icon="BORDER_RECT", text="Disable Selection" if all_double_sided else "Enable Selection")
+ col.operator(
+ "mesh.plasma_toggle_double_sided", icon="MESH_DATA", text="Disable All"
+ ).enable = False
+ all_double_sided = all(
+ (
+ i.data.show_double_sided
+ for i in bpy.context.selected_objects
+ if i.type == "MESH"
+ )
+ )
+ col.operator(
+ "mesh.plasma_toggle_double_sided_selected",
+ icon="BORDER_RECT",
+ text="Disable Selection" if all_double_sided else "Enable Selection",
+ )
col.label("Convert:")
- col.operator("object.plasma_convert_plasma_objects", icon="OBJECT_DATA", text="Plasma Objects")
- col.operator("texture.plasma_convert_layer_opacities", icon="IMAGE_RGB_ALPHA", text="Layer Opacities")
+ col.operator(
+ "object.plasma_convert_plasma_objects",
+ icon="OBJECT_DATA",
+ text="Plasma Objects",
+ )
+ col.operator(
+ "texture.plasma_convert_layer_opacities",
+ icon="IMAGE_RGB_ALPHA",
+ text="Layer Opacities",
+ )
diff --git a/korman/ui/ui_world.py b/korman/ui/ui_world.py
index 4fb0792..ffb4eba 100644
--- a/korman/ui/ui_world.py
+++ b/korman/ui/ui_world.py
@@ -86,7 +86,9 @@ class PlasmaGameExportMenu(PlasmaGameHelper, bpy.types.Menu):
row.operator_context = "EXEC_DEFAULT"
age_path = self.format_path()
row.active = legal_game and active_game.can_launch and age_path.exists()
- op = row.operator("export.plasma_age", icon="RENDER_ANIMATION", text="Launch Age")
+ op = row.operator(
+ "export.plasma_age", icon="RENDER_ANIMATION", text="Launch Age"
+ )
if active_game is not None:
op.actions = {"LAUNCH"}
op.dat_only = False
@@ -133,8 +135,15 @@ class PlasmaGamePanel(AgeButtonsPanel, PlasmaGameHelper, bpy.types.Panel):
row = layout.row()
# Remember: game storage moved to addon preferences!
- row.template_list("PlasmaGameListRO", "games", prefs, "games", games,
- "active_game_index", rows=2)
+ row.template_list(
+ "PlasmaGameListRO",
+ "games",
+ prefs,
+ "games",
+ games,
+ "active_game_index",
+ rows=2,
+ )
row.operator("ui.korman_open_prefs", icon="PREFERENCES", text="")
layout.separator()
@@ -168,20 +177,54 @@ class PlasmaGamePanel(AgeButtonsPanel, PlasmaGameHelper, bpy.types.Panel):
# Special Menu
row = row.row(align=True)
row.enabled = True
- row.menu("PlasmaGameExportMenu", icon='DOWNARROW_HLT', text="")
+ row.menu("PlasmaGameExportMenu", icon="DOWNARROW_HLT", text="")
class PlasmaGameListRO(bpy.types.UIList):
- def draw_item(self, context, layout, data, item, icon, active_data, active_property, index=0, flt_flag=0):
+ def draw_item(
+ self,
+ context,
+ layout,
+ data,
+ item,
+ icon,
+ active_data,
+ active_property,
+ index=0,
+ flt_flag=0,
+ ):
layout.label(item.name, icon="BOOKMARKS")
+
class PlasmaGameListRW(bpy.types.UIList):
- def draw_item(self, context, layout, data, item, icon, active_data, active_property, index=0, flt_flag=0):
+ def draw_item(
+ self,
+ context,
+ layout,
+ data,
+ item,
+ icon,
+ active_data,
+ active_property,
+ index=0,
+ flt_flag=0,
+ ):
layout.prop(item, "name", text="", emboss=False, icon="BOOKMARKS")
class PlasmaPageList(bpy.types.UIList):
- def draw_item(self, context, layout, data, item, icon, active_data, active_property, index=0, flt_flag=0):
+ def draw_item(
+ self,
+ context,
+ layout,
+ data,
+ item,
+ icon,
+ active_data,
+ active_property,
+ index=0,
+ flt_flag=0,
+ ):
layout.prop(item, "name", text="", emboss=False, icon="BOOKMARKS")
layout.prop(item, "enabled", text="")
@@ -195,8 +238,9 @@ class PlasmaAgePanel(AgeButtonsPanel, bpy.types.Panel):
# We want a list of pages and an editor below that
row = layout.row()
- row.template_list("PlasmaPageList", "pages", age, "pages", age,
- "active_page_index", rows=2)
+ row.template_list(
+ "PlasmaPageList", "pages", age, "pages", age, "active_page_index", rows=2
+ )
col = row.column(align=True)
col.operator("world.plasma_page_add", icon="ZOOMIN", text="")
col.operator("world.plasma_page_remove", icon="ZOOMOUT", text="")
@@ -222,8 +266,11 @@ class PlasmaAgePanel(AgeButtonsPanel, bpy.types.Panel):
# Age Names should really be legal Python 2.x identifiers for AgeSDLHooks
legal_identifier = korlib.is_legal_python2_identifier(age.age_name)
- illegal_age_name = not legal_identifier or '_' in age.age_name
- bad_prefix = age.seq_prefix >= age.MOUL_PREFIX_RANGE[1] or age.seq_prefix <= age.MOUL_PREFIX_RANGE[0]
+ illegal_age_name = not legal_identifier or "_" in age.age_name
+ bad_prefix = (
+ age.seq_prefix >= age.MOUL_PREFIX_RANGE[1]
+ or age.seq_prefix <= age.MOUL_PREFIX_RANGE[0]
+ )
# Core settings
layout.separator()
@@ -242,22 +289,36 @@ class PlasmaAgePanel(AgeButtonsPanel, bpy.types.Panel):
col.prop(age, "age_name", text="")
if age.seq_prefix >= age.MOUL_PREFIX_RANGE[1]:
- layout.label(text="Your sequence prefix is too high for Myst Online: Uru Live", icon="ERROR")
+ layout.label(
+ text="Your sequence prefix is too high for Myst Online: Uru Live",
+ icon="ERROR",
+ )
elif age.seq_prefix <= age.MOUL_PREFIX_RANGE[0]:
# Unlikely.
- layout.label(text="Your sequence prefix is too low for Myst Online: Uru Live", icon="ERROR")
+ layout.label(
+ text="Your sequence prefix is too low for Myst Online: Uru Live",
+ icon="ERROR",
+ )
# Display a hint if the identifier is illegal
if illegal_age_name:
if not age.age_name:
layout.label(text="Age names cannot be empty", icon="ERROR")
elif korlib.is_python_keyword(age.age_name):
- layout.label(text="Ages should not be named the same as a Python keyword", icon="ERROR")
+ layout.label(
+ text="Ages should not be named the same as a Python keyword",
+ icon="ERROR",
+ )
elif age.age_sdl:
fixed_identifier = korlib.replace_python2_identifier(age.age_name)
- layout.label(text="Age's SDL will use the name '{}'".format(fixed_identifier), icon="ERROR")
- if '_' in age.age_name:
- layout.label(text="Age names should not contain underscores", icon="ERROR")
+ layout.label(
+ text="Age's SDL will use the name '{}'".format(fixed_identifier),
+ icon="ERROR",
+ )
+ if "_" in age.age_name:
+ layout.label(
+ text="Age names should not contain underscores", icon="ERROR"
+ )
layout.separator()
split = layout.split()
@@ -289,8 +350,14 @@ class PlasmaEnvironmentPanel(AgeButtonsPanel, bpy.types.Panel):
fni = context.world.plasma_fni
# warn about reversed linear fog values
- if fni.fog_method == "linear" and fni.fog_start >= fni.fog_end and (fni.fog_start + fni.fog_end) != 0:
- layout.label(text="Fog Start Value should be less than the End Value", icon="ERROR")
+ if (
+ fni.fog_method == "linear"
+ and fni.fog_start >= fni.fog_end
+ and (fni.fog_start + fni.fog_end) != 0
+ ):
+ layout.label(
+ text="Fog Start Value should be less than the End Value", icon="ERROR"
+ )
# basic colors
split = layout.split()