Browse Source

Add lamp color and energy animations

This makes things incredibly more complex because Blender stores those
animations on the ObData ID instead of the Object ID data block. Dang. So,
the animation modifier's detection code had to be pretty much scrapped.
The newer code is a little hacky in places. Hopefully we can address this
soon-ish.
pull/40/head
Adam Johnson 8 years ago
parent
commit
3f98e6ea9d
  1. 105
      korman/exporter/animation.py
  2. 1
      korman/exporter/rtlight.py
  3. 3
      korman/nodes/node_messages.py
  4. 42
      korman/properties/modifiers/anim.py
  5. 26
      korman/ui/modifiers/anim.py

105
korman/exporter/animation.py

@ -31,24 +31,32 @@ class AnimationConverter:
return frame_num / self._bl_fps return frame_num / self._bl_fps
def convert_object_animations(self, bo, so): def convert_object_animations(self, bo, so):
anim = bo.animation_data if not self.is_animated(bo):
if anim is None:
return
action = anim.action
if action is None:
return
fcurves = action.fcurves
if not fcurves:
return return
def fetch_animation_data(id_data):
if id_data is not None:
if id_data.animation_data is not None:
action = id_data.animation_data.action
return action, getattr(action, "fcurves", None)
return None, None
# TODO: At some point, we should consider supporting NLA stuff.
# But for now, this seems sufficient.
obj_action, obj_fcurves = fetch_animation_data(bo)
data_action, data_fcurves = fetch_animation_data(bo.data)
# We're basically just going to throw all the FCurves at the controller converter (read: wall) # We're basically just going to throw all the FCurves at the controller converter (read: wall)
# and see what sticks. PlasmaMAX has some nice animation channel stuff that allows for some # and see what sticks. PlasmaMAX has some nice animation channel stuff that allows for some
# form of separation, but Blender's NLA editor is way confusing and appears to not work with # form of separation, but Blender's NLA editor is way confusing and appears to not work with
# things that aren't the typical position, rotation, scale animations. # things that aren't the typical position, rotation, scale animations.
applicators = [] applicators = []
applicators.append(self._convert_transform_animation(bo.name, fcurves, bo.matrix_basis)) applicators.append(self._convert_transform_animation(bo.name, obj_fcurves, bo.matrix_basis))
if bo.plasma_modifiers.soundemit.enabled: if bo.plasma_modifiers.soundemit.enabled:
applicators.extend(self._convert_sound_volume_animation(bo.name, fcurves, bo.plasma_modifiers.soundemit)) applicators.extend(self._convert_sound_volume_animation(bo.name, obj_fcurves, bo.plasma_modifiers.soundemit))
if isinstance(bo.data, bpy.types.Lamp):
lamp = bo.data
applicators.extend(self._convert_lamp_color_animation(bo.name, data_fcurves, lamp))
# Check to make sure we have some valid animation applicators before proceeding. # Check to make sure we have some valid animation applicators before proceeding.
if not any(applicators): if not any(applicators):
@ -68,13 +76,18 @@ class AnimationConverter:
# This was previously part of the Animation Modifier, however, there can be lots of animations # This was previously part of the Animation Modifier, however, there can be lots of animations
# Therefore we move it here. # Therefore we move it here.
markers = action.pose_markers def get_ranges(*args, **kwargs):
index = kwargs.get("index", 0)
for i in args:
if i is not None:
yield i.frame_range[index]
atcanim.name = "(Entire Animation)" atcanim.name = "(Entire Animation)"
atcanim.start = self._convert_frame_time(action.frame_range[0]) atcanim.start = self._convert_frame_time(min(get_ranges(obj_action, data_action, index=0)))
atcanim.end = self._convert_frame_time(action.frame_range[1]) atcanim.end = self._convert_frame_time(max(get_ranges(obj_action, data_action, index=1)))
# Marker points # Marker points
for marker in markers: if obj_action is not None:
for marker in obj_action.pose_markers:
atcanim.setMarker(marker.name, self._convert_frame_time(marker.frame)) atcanim.setMarker(marker.name, self._convert_frame_time(marker.frame))
# Fixme? Not sure if we really need to expose this... # Fixme? Not sure if we really need to expose this...
@ -85,7 +98,55 @@ class AnimationConverter:
atcanim.easeOutMax = 1.0 atcanim.easeOutMax = 1.0
atcanim.easeOutLength = 1.0 atcanim.easeOutLength = 1.0
def _convert_lamp_color_animation(self, name, fcurves, lamp):
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 = sorted((i for i in fcurves if i.data_path == "color" and i.keyframe_points), key=lambda x: x.array_index)
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)
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)
return None
# OK Specular is easy. We just toss out the color as a point3.
color_keyframes, color_bez = self._process_keyframes(color_curves, convert=lambda x: x * -1.0 if lamp.use_negative else None)
if color_keyframes and lamp.use_specular:
channel = plPointControllerChannel()
channel.controller = self._make_point3_controller(color_curves, color_keyframes, color_bez, lamp.color)
applicator = plLightSpecularApplicator()
applicator.channelName = name
applicator.channel = channel
yield applicator
# Hey, look, it's a third way to process FCurves. YAY!
def convert_diffuse_animation(color, energy):
if lamp.use_negative:
return { key: (0.0 - value) * energy[0] for key, value in color.items() }
else:
return { key: value * energy[0] for key, value in color.items() }
diffuse_defaults = { "color": lamp.color, "energy": lamp.energy }
diffuse_fcurves = color_curves + [energy_curve,]
diffuse_keyframes = self._process_fcurves(diffuse_fcurves, convert_diffuse_animation, diffuse_defaults)
if not diffuse_keyframes:
return None
# Whew.
channel = plPointControllerChannel()
channel.controller = self._make_point3_controller([], diffuse_keyframes, False, [])
applicator = plLightDiffuseApplicator()
applicator.channelName = name
applicator.channel = channel
yield applicator
def _convert_sound_volume_animation(self, name, fcurves, soundemit): def _convert_sound_volume_animation(self, name, fcurves, soundemit):
if not fcurves:
return None
def convert_volume(value): def convert_volume(value):
if value == 0.0: if value == 0.0:
return 0.0 return 0.0
@ -111,6 +172,9 @@ class AnimationConverter:
yield applicator yield applicator
def _convert_transform_animation(self, name, fcurves, xform): def _convert_transform_animation(self, name, fcurves, xform):
if not fcurves:
return None
pos = self.make_pos_controller(fcurves, xform) pos = self.make_pos_controller(fcurves, xform)
rot = self.make_rot_controller(fcurves, xform) rot = self.make_rot_controller(fcurves, xform)
scale = self.make_scale_controller(fcurves, xform) scale = self.make_scale_controller(fcurves, xform)
@ -152,6 +216,17 @@ class AnimationConverter:
master = self._mgr.find_create_object(plAGMasterMod, so=so, bl=bo) master = self._mgr.find_create_object(plAGMasterMod, so=so, bl=bo)
return mod, master return mod, master
def is_animated(self, bo):
if bo.animation_data is not None:
if bo.animation_data.action is not None:
return True
data = getattr(bo, "data", None)
if data is not None:
if data.animation_data is not None:
if data.animation_data.action is not None:
return True
return False
def make_matrix44_controller(self, pos_fcurves, scale_fcurves, default_pos, default_scale): def make_matrix44_controller(self, pos_fcurves, scale_fcurves, default_pos, default_scale):
pos_keyframes, pos_bez = self._process_keyframes(pos_fcurves) pos_keyframes, pos_bez = self._process_keyframes(pos_fcurves)
scale_keyframes, scale_bez = self._process_keyframes(scale_fcurves) scale_keyframes, scale_bez = self._process_keyframes(scale_fcurves)
@ -160,7 +235,7 @@ class AnimationConverter:
# Matrix keyframes cannot do bezier schtuff # Matrix keyframes cannot do bezier schtuff
if pos_bez or scale_bez: if pos_bez or scale_bez:
self._exporter().report.warn("This animation cannot use bezier keyframes--forcing linear", indent=3) self._exporter().report.warn("Matrix44 controllers cannot use bezier keyframes--forcing linear", indent=3)
# Let's pair up the pos and scale schtuff based on frame numbers. I realize that we're creating # Let's pair up the pos and scale schtuff based on frame numbers. I realize that we're creating
# a lot of temporary objects, but until I see profiling results that this is terrible, I prefer # a lot of temporary objects, but until I see profiling results that this is terrible, I prefer

1
korman/exporter/rtlight.py

@ -104,6 +104,7 @@ class LightConverter:
self._converter_funcs[bl_light.type](bl_light, pl_light) self._converter_funcs[bl_light.type](bl_light, pl_light)
# Light color nonsense # Light color nonsense
# Please note that these calculations are duplicated in the AnimationConverter
energy = bl_light.energy energy = bl_light.energy
if bl_light.use_negative: if bl_light.use_negative:
diff_color = [(0.0 - i) * energy for i in bl_light.color] diff_color = [(0.0 - i) * energy for i in bl_light.color]

3
korman/nodes/node_messages.py

@ -168,8 +168,7 @@ class PlasmaAnimCmdMsgNode(PlasmaMessageNode, bpy.types.Node):
obj = bpy.data.objects.get(self.object_name, None) obj = bpy.data.objects.get(self.object_name, None)
if obj is None: if obj is None:
self.raise_error("invalid object: '{}'".format(self.object_name)) self.raise_error("invalid object: '{}'".format(self.object_name))
anim = obj.plasma_modifiers.animation if not exporter.animation.is_animated(obj):
if not anim.enabled:
self.raise_error("invalid animation") self.raise_error("invalid animation")
group = obj.plasma_modifiers.animation_group group = obj.plasma_modifiers.animation_group
if group.enabled: if group.enabled:

42
korman/properties/modifiers/anim.py

@ -24,14 +24,21 @@ def _convert_frame_time(frame_num):
fps = bpy.context.scene.render.fps fps = bpy.context.scene.render.fps
return frame_num / fps return frame_num / fps
def _get_blender_action(bo): class ActionModifier:
if bo.animation_data is None or bo.animation_data.action is None: @property
raise ExportError("Object '{}' has no Action to export".format(bo.name)) def blender_action(self):
if not bo.animation_data.action.fcurves: bo = self.id_data
raise ExportError("Object '{}' is animated but has no FCurves".format(bo.name)) if bo.animation_data is not None and bo.animation_data.action is not None:
return bo.animation_data.action 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:
# 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 '{}' is not animated".format(bo.name))
class PlasmaAnimationModifier(PlasmaModifierProperties): class PlasmaAnimationModifier(ActionModifier, PlasmaModifierProperties):
pl_id = "animation" pl_id = "animation"
bl_category = "Animation" bl_category = "Animation"
@ -58,14 +65,15 @@ class PlasmaAnimationModifier(PlasmaModifierProperties):
return True return True
def export(self, exporter, bo, so): def export(self, exporter, bo, so):
action = _get_blender_action(bo) action = self.blender_action
markers = action.pose_markers
atcanim = exporter.mgr.find_create_object(plATCAnim, so=so) atcanim = exporter.mgr.find_create_object(plATCAnim, so=so)
atcanim.autoStart = self.auto_start atcanim.autoStart = self.auto_start
atcanim.loop = self.loop atcanim.loop = self.loop
# Simple start and loop info # Simple start and loop info
if action is not None:
markers = action.pose_markers
initial_marker = markers.get(self.initial_marker) initial_marker = markers.get(self.initial_marker)
if initial_marker is not None: if initial_marker is not None:
atcanim.initial = _convert_frame_time(initial_marker.frame) atcanim.initial = _convert_frame_time(initial_marker.frame)
@ -76,12 +84,16 @@ class PlasmaAnimationModifier(PlasmaModifierProperties):
if loop_start is not None: if loop_start is not None:
atcanim.loopStart = _convert_frame_time(loop_start.frame) atcanim.loopStart = _convert_frame_time(loop_start.frame)
else: else:
atcanim.loopStart = _convert_frame_time(action.frame_range[0]) atcanim.loopStart = atcanim.start
loop_end = markers.get(self.loop_end) loop_end = markers.get(self.loop_end)
if loop_end is not None: if loop_end is not None:
atcanim.loopEnd = _convert_frame_time(loop_end.frame) atcanim.loopEnd = _convert_frame_time(loop_end.frame)
else: else:
atcanim.loopEnd = _convert_frame_time(action.frame_range[1]) atcanim.loopEnd = atcanim.end
else:
if self.loop:
atcanim.loopStart = atcanim.start
atcanim.loopEnd = atcanim.end
def _make_physical_movable(self, so): def _make_physical_movable(self, so):
sim = so.sim sim = so.sim
@ -128,8 +140,8 @@ class PlasmaAnimationGroupModifier(PlasmaModifierProperties):
active_child_index = IntProperty(options={"HIDDEN"}) active_child_index = IntProperty(options={"HIDDEN"})
def export(self, exporter, bo, so): def export(self, exporter, bo, so):
action = _get_blender_action(bo) if not exporter.animation.is_animated(bo):
key_name = bo.plasma_modifiers.animation.key_name 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 # 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)
@ -171,7 +183,7 @@ class LoopMarker(bpy.types.PropertyGroup):
description="Marker name from whence the loop ends") description="Marker name from whence the loop ends")
class PlasmaAnimationLoopModifier(PlasmaModifierProperties): class PlasmaAnimationLoopModifier(ActionModifier, PlasmaModifierProperties):
pl_id = "animation_loop" pl_id = "animation_loop"
pl_depends = {"animation"} pl_depends = {"animation"}
@ -186,7 +198,9 @@ class PlasmaAnimationLoopModifier(PlasmaModifierProperties):
active_loop_index = IntProperty(options={"HIDDEN"}) active_loop_index = IntProperty(options={"HIDDEN"})
def export(self, exporter, bo, so): def export(self, exporter, bo, so):
action = _get_blender_action(bo) action = self.blender_action
if action is None:
raise ExportError("'{}': No object animation data".format(bo.name))
markers = action.pose_markers markers = action.pose_markers
atcanim = exporter.mgr.find_create_object(plATCAnim, so=so) atcanim = exporter.mgr.find_create_object(plATCAnim, so=so)

26
korman/ui/modifiers/anim.py

@ -15,16 +15,19 @@
import bpy import bpy
def _check_for_anim(layout, context): def _check_for_anim(layout, modifier):
if context.object.animation_data is None or context.object.animation_data.action is None: try:
action = modifier.blender_action
except:
layout.label("Object has no animation data", icon="ERROR") layout.label("Object has no animation data", icon="ERROR")
return False return None
return True else:
return action if action is not None else False
def animation(modifier, layout, context): def animation(modifier, layout, context):
if not _check_for_anim(layout, context): action = _check_for_anim(layout, modifier)
if action is None:
return return
action = context.object.animation_data.action
split = layout.split() split = layout.split()
col = split.column() col = split.column()
@ -32,6 +35,7 @@ def animation(modifier, layout, context):
col = split.column() col = split.column()
col.prop(modifier, "loop") col.prop(modifier, "loop")
if action:
layout.prop_search(modifier, "initial_marker", action, "pose_markers", icon="PMARKER") layout.prop_search(modifier, "initial_marker", action, "pose_markers", icon="PMARKER")
col = layout.column() col = layout.column()
col.enabled = modifier.loop col.enabled = modifier.loop
@ -45,7 +49,8 @@ class GroupListUI(bpy.types.UIList):
def animation_group(modifier, layout, context): def animation_group(modifier, layout, context):
if not _check_for_anim(layout, context): action = _check_for_anim(layout, modifier)
if not action:
return return
row = layout.row() row = layout.row()
@ -67,7 +72,11 @@ class LoopListUI(bpy.types.UIList):
def animation_loop(modifier, layout, context): def animation_loop(modifier, layout, context):
if not _check_for_anim(layout, context): action = _check_for_anim(layout, modifier)
if action is False:
layout.label("Object must be animated, not ObData", icon="ERROR")
return
elif action is None:
return return
row = layout.row() row = layout.row()
@ -86,7 +95,6 @@ def animation_loop(modifier, layout, context):
# Modify the loop points # Modify the loop points
if modifier.loops: if modifier.loops:
action = context.object.animation_data.action
loop = modifier.loops[modifier.active_loop_index] loop = modifier.loops[modifier.active_loop_index]
layout.prop_search(loop, "loop_start", action, "pose_markers", icon="PMARKER") layout.prop_search(loop, "loop_start", action, "pose_markers", icon="PMARKER")
layout.prop_search(loop, "loop_end", action, "pose_markers", icon="PMARKER") layout.prop_search(loop, "loop_end", action, "pose_markers", icon="PMARKER")

Loading…
Cancel
Save