mirror of https://github.com/H-uru/korman.git
Adam Johnson
10 years ago
8 changed files with 407 additions and 0 deletions
@ -0,0 +1,119 @@
|
||||
# This file is part of Korman. |
||||
# |
||||
# Korman is free software: you can redistribute it and/or modify |
||||
# it under the terms of the GNU General Public License as published by |
||||
# the Free Software Foundation, either version 3 of the License, or |
||||
# (at your option) any later version. |
||||
# |
||||
# Korman is distributed in the hope that it will be useful, |
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
# GNU General Public License for more details. |
||||
# |
||||
# You should have received a copy of the GNU General Public License |
||||
# along with Korman. If not, see <http://www.gnu.org/licenses/>. |
||||
|
||||
import bpy |
||||
import math |
||||
from PyHSPlasma import * |
||||
import weakref |
||||
|
||||
class AnimationConverter: |
||||
def __init__(self, exporter): |
||||
self._exporter = weakref.ref(exporter) |
||||
self._bl_fps = bpy.context.scene.render.fps |
||||
|
||||
def _check_scalar_subcontrollers(self, ctrl, default_xform): |
||||
"""Ensures that all scalar subcontrollers have at least one keyframe in the default state""" |
||||
for i in ("X", "Y", "Z"): |
||||
sub = getattr(ctrl, i) |
||||
if not sub.hasKeys(): |
||||
keyframe = hsScalarKey() |
||||
keyframe.frame = 0 |
||||
keyframe.frameTime = 0.0 |
||||
keyframe.type = hsKeyFrame.kScalarKeyFrame |
||||
keyframe.value = getattr(default_xform, i.lower()) |
||||
sub.keys = ([keyframe,], hsKeyFrame.kScalarKeyFrame) |
||||
|
||||
def convert_action2tm(self, action, default_xform): |
||||
"""Converts a Blender Action to a plCompoundController.""" |
||||
fcurves = action.fcurves |
||||
if not fcurves: |
||||
return None |
||||
|
||||
# NOTE: plCompoundController is from Myst 5 and was backported to MOUL. |
||||
# Worry not however... libHSPlasma will do the conversion for us. |
||||
tm = plCompoundController() |
||||
tm.X = self.make_pos_controller(fcurves, default_xform) |
||||
tm.Y = self.make_rot_controller(fcurves, default_xform) |
||||
tm.Z = self.make_scale_controller(fcurves, default_xform) |
||||
return tm |
||||
|
||||
def _is_bezier_curve(self, keyframes): |
||||
for i in keyframes: |
||||
if i.interpolation == "BEZIER": |
||||
return True |
||||
return False |
||||
|
||||
def make_pos_controller(self, fcurves, default_xform): |
||||
pos_curves = (i for i in fcurves if i.data_path == "location" and i.keyframe_points) |
||||
ctrl = self.make_scalar_controller(pos_curves) |
||||
if ctrl is not None and default_xform is not None: |
||||
self._check_scalar_subcontrollers(ctrl, default_xform.to_transpose()) |
||||
|
||||
def make_rot_controller(self, fcurves, default_xform): |
||||
rot_curves = (i for i in fcurves if i.data_path == "rotation_euler" and i.keyframe_points) |
||||
ctrl = self.make_scalar_controller(rot_curves) |
||||
if ctrl is not None and default_xform is not None: |
||||
self._check_scalar_subcontrollers(ctrl, default_xform.to_euler("XYZ")) |
||||
return ctrl |
||||
|
||||
def make_scale_controller(self, fcurves, default_xform): |
||||
# ... TODO ... |
||||
# who needs this anyway? |
||||
return None |
||||
|
||||
def make_scalar_controller(self, fcurves): |
||||
ctrl = plCompoundController() |
||||
subctls = ("X", "Y", "Z") |
||||
|
||||
# this ensures that all subcontrollers are populated -- otherwise KABLOOEY! |
||||
for i in subctls: |
||||
setattr(ctrl, i, plLeafController()) |
||||
|
||||
for fcurve in fcurves: |
||||
fcurve.update() |
||||
if self._is_bezier_curve(fcurve.keyframe_points): |
||||
key_type = hsKeyFrame.kScalarKeyFrame |
||||
else: |
||||
key_type = hsKeyFrame.kBezScalarKeyFrame |
||||
frames = [] |
||||
pi = math.pi |
||||
fps = self._bl_fps |
||||
|
||||
for i in fcurve.keyframe_points: |
||||
bl_frame_num, value = i.co |
||||
frame = hsScalarKey() |
||||
if i.interpolation == "BEZIER": |
||||
frame.inTan = -(value - i.handle_left[1]) / (bl_frame_num - i.handle_left[0]) / fps / (2 * pi) |
||||
frame.outTan = (value - i.handle_right[1]) / (bl_frame_num - i.handle_right[0]) / fps / (2 * pi) |
||||
else: |
||||
frame.inTan = 0.0 |
||||
frame.outTan = 0.0 |
||||
frame.type = key_type |
||||
frame.frame = int(bl_frame_num * (30.0 / fps)) |
||||
frame.frameTime = bl_frame_num / fps |
||||
frame.value = value |
||||
frames.append(frame) |
||||
controller = plLeafController() |
||||
getattr(ctrl, subctls[fcurve.array_index]).keys = (frames, key_type) |
||||
|
||||
# Compact this bamf |
||||
if not ctrl.X.hasKeys() and not ctrl.Y.hasKeys() and not ctrl.Z.hasKeys(): |
||||
return None |
||||
else: |
||||
return ctrl |
||||
|
||||
@property |
||||
def _mgr(self): |
||||
return self._exporter().mgr |
@ -0,0 +1,172 @@
|
||||
# This file is part of Korman. |
||||
# |
||||
# Korman is free software: you can redistribute it and/or modify |
||||
# it under the terms of the GNU General Public License as published by |
||||
# the Free Software Foundation, either version 3 of the License, or |
||||
# (at your option) any later version. |
||||
# |
||||
# Korman is distributed in the hope that it will be useful, |
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
# GNU General Public License for more details. |
||||
# |
||||
# You should have received a copy of the GNU General Public License |
||||
# along with Korman. If not, see <http://www.gnu.org/licenses/>. |
||||
|
||||
import bpy |
||||
from bpy.props import * |
||||
from PyHSPlasma import * |
||||
|
||||
from .base import PlasmaModifierProperties |
||||
from ...exporter import ExportError, utils |
||||
|
||||
def _convert_frame_time(frame_num): |
||||
fps = bpy.context.scene.render.fps |
||||
return frame_num / fps |
||||
|
||||
def _get_blender_action(bo): |
||||
if bo.animation_data is None or bo.animation_data.action is None: |
||||
raise ExportError("Object '{}' has no Action to export".format(bo.name)) |
||||
return bo.animation_data.action |
||||
|
||||
class PlasmaAnimationModifier(PlasmaModifierProperties): |
||||
pl_id = "animation" |
||||
|
||||
bl_category = "Animation" |
||||
bl_label = "Animation" |
||||
bl_description = "Object animation" |
||||
bl_icon = "ACTION" |
||||
|
||||
auto_start = BoolProperty(name="Auto Start", |
||||
description="Automatically start this animation on link-in", |
||||
default=True) |
||||
loop = BoolProperty(name="Loop Anim", |
||||
description="Loop the animation", |
||||
default=True) |
||||
|
||||
initial_marker = StringProperty(name="Start Marker", |
||||
description="Marker indicating the default start point") |
||||
loop_start = StringProperty(name="Loop Start", |
||||
description="Marker indicating where the default loop begins") |
||||
loop_end = StringProperty(name="Loop End", |
||||
description="Marker indicating where the default loop ends") |
||||
|
||||
def created(self, obj): |
||||
self.display_name = "{}_(Entire Animation)".format(obj.name) |
||||
|
||||
@property |
||||
def requires_actor(self): |
||||
return True |
||||
|
||||
def export(self, exporter, bo, so): |
||||
action = _get_blender_action(bo) |
||||
markers = action.pose_markers |
||||
|
||||
atcanim = exporter.mgr.find_create_key(plATCAnim, so=so, name=self.display_name).object |
||||
atcanim.autoStart = self.auto_start |
||||
atcanim.loop = self.loop |
||||
atcanim.name = "(Entire Animation)" |
||||
atcanim.start = 0.0 |
||||
atcanim.end = _convert_frame_time(action.frame_range[1]) |
||||
|
||||
# Simple start and loop info |
||||
initial_marker = markers.get(self.initial_marker) |
||||
if initial_marker is not None: |
||||
atcanim.initial = _convert_fame_time(initial_marker.frame) |
||||
else: |
||||
atcanim.initial = -1.0 |
||||
if self.loop: |
||||
loop_start = markers.get(self.loop_start) |
||||
if loop_start is not None: |
||||
atcanim.loopStart = _convert_frame_time(loop_start.frame) |
||||
else: |
||||
atcanim.loopStart = 0.0 |
||||
loop_end = markers.get(self.loop_end) |
||||
if loop_end is not None: |
||||
atcanim.loopEnd = _convert_frame_time(loop_end.frame) |
||||
else: |
||||
atcanim.loopEnd = _convert_frame_time(action.frame_range[1]) |
||||
|
||||
# Marker points |
||||
for marker in markers: |
||||
atcanim.setMarker(marker.name, _convert_frame_time(marker.frame)) |
||||
|
||||
# Fixme? Not sure if we really need to expose this... |
||||
atcanim.easeInMin = 1.0 |
||||
atcanim.easeInMax = 1.0 |
||||
atcanim.easeInLength = 1.0 |
||||
atcanim.easeOutMin = 1.0 |
||||
atcanim.easeOutMax = 1.0 |
||||
atcanim.easeOutLength = 1.0 |
||||
|
||||
# Now for the animation data. We're mostly just going to hand this off to the controller code |
||||
matrix = bo.matrix_basis |
||||
applicator = plMatrixChannelApplicator() |
||||
applicator.enabled = True |
||||
applicator.channelName = bo.name |
||||
channel = plMatrixControllerChannel() |
||||
channel.controller = exporter.animation.convert_action2tm(action, matrix) |
||||
applicator.channel = channel |
||||
atcanim.addApplicator(applicator) |
||||
|
||||
# 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... |
||||
affine = hsAffineParts() |
||||
affine.T = utils.vector3(matrix.to_translation()) |
||||
affine.Q = utils.quaternion(matrix.to_quaternion()) |
||||
affine.K = utils.vector3(matrix.to_scale()) |
||||
affine.F = -1.0 if matrix.determinant() < 0.0 else 1.0 |
||||
channel.affine = affine |
||||
|
||||
# We need both an AGModifier and an AGMasterMod |
||||
# TODO: grouped animations (eg one door, two objects) |
||||
agmod = exporter.mgr.add_object(plAGModifier, so=so, name=self.display_name) |
||||
agmod.channelName = bo.name |
||||
agmaster = exporter.mgr.add_object(plAGMasterMod, so=so, name=self.display_name) |
||||
agmaster.addPrivateAnim(atcanim.key) |
||||
|
||||
|
||||
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") |
||||
|
||||
|
||||
class PlasmaAnimationLoopModifier(PlasmaModifierProperties): |
||||
pl_id = "animation_loop" |
||||
|
||||
bl_category = "Animation" |
||||
bl_label = "Loop Markers" |
||||
bl_description = "Animation loop settings" |
||||
bl_icon = "PMARKER_SEL" |
||||
|
||||
loops = CollectionProperty(name="Loops", |
||||
description="Loop points within the animation", |
||||
type=LoopMarker) |
||||
active_loop_index = IntProperty(options={"HIDDEN"}) |
||||
|
||||
def created(self, obj): |
||||
# who cares, this modifier creates no Keys... |
||||
self.display_name = "AnimLoops" |
||||
|
||||
def export(self, exporter, bo, so): |
||||
action = _get_blender_action(bo) |
||||
markers = action.pose_markers |
||||
|
||||
key_name = bo.plasma_modifiers.animation.display_name |
||||
atcanim = exporter.mgr.find_create_key(plATCAnim, so=so, name=key_name).object |
||||
for loop in self.loops: |
||||
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) |
||||
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) |
||||
if start is None or end is None: |
||||
continue |
||||
atcanim.setLoop(loop.loop_name, _convert_frame_time(start.frame), _convert_frame_time(end.frame)) |
@ -0,0 +1,70 @@
|
||||
# This file is part of Korman. |
||||
# |
||||
# Korman is free software: you can redistribute it and/or modify |
||||
# it under the terms of the GNU General Public License as published by |
||||
# the Free Software Foundation, either version 3 of the License, or |
||||
# (at your option) any later version. |
||||
# |
||||
# Korman is distributed in the hope that it will be useful, |
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
# GNU General Public License for more details. |
||||
# |
||||
# You should have received a copy of the GNU General Public License |
||||
# along with Korman. If not, see <http://www.gnu.org/licenses/>. |
||||
|
||||
import bpy |
||||
|
||||
def _check_for_anim(layout, context): |
||||
if context.object.animation_data is None or context.object.animation_data.action is None: |
||||
layout.label("Object has no animation data", icon="ERROR") |
||||
return False |
||||
return True |
||||
|
||||
def animation(modifier, layout, context): |
||||
if not _check_for_anim(layout, context): |
||||
return |
||||
action = context.object.animation_data.action |
||||
|
||||
split = layout.split() |
||||
col = split.column() |
||||
col.prop(modifier, "auto_start") |
||||
col = split.column() |
||||
col.prop(modifier, "loop") |
||||
|
||||
layout.prop_search(modifier, "initial_marker", action, "pose_markers", icon="PMARKER") |
||||
col = layout.column() |
||||
col.enabled = modifier.loop |
||||
col.prop_search(modifier, "loop_start", action, "pose_markers", icon="PMARKER") |
||||
col.prop_search(modifier, "loop_end", action, "pose_markers", icon="PMARKER") |
||||
|
||||
|
||||
class LoopListUI(bpy.types.UIList): |
||||
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") |
||||
|
||||
|
||||
def animation_loop(modifier, layout, context): |
||||
if not _check_for_anim(layout, context): |
||||
return |
||||
|
||||
row = layout.row() |
||||
row.template_list("LoopListUI", "loops", modifier, "loops", modifier, "active_loop_index", |
||||
rows=2, maxrows=3) |
||||
col = row.column(align=True) |
||||
op = col.operator("object.plasma_modifier_collection_add", icon="ZOOMIN", text="") |
||||
op.modifier = modifier.pl_id |
||||
op.collection = "loops" |
||||
op.name_prefix = "Loop" |
||||
op.name_prop = "loop_name" |
||||
op = col.operator("object.plasma_modifier_collection_remove", icon="ZOOMOUT", text="") |
||||
op.modifier = modifier.pl_id |
||||
op.collection = "loops" |
||||
op.index = modifier.active_loop_index |
||||
|
||||
# Modify the loop points |
||||
if modifier.loops: |
||||
action = context.object.animation_data.action |
||||
loop = modifier.loops[modifier.active_loop_index] |
||||
layout.prop_search(loop, "loop_start", action, "pose_markers", icon="PMARKER") |
||||
layout.prop_search(loop, "loop_end", action, "pose_markers", icon="PMARKER") |
Loading…
Reference in new issue