mirror of https://github.com/H-uru/korman.git
Adam Johnson
10 years ago
8 changed files with 552 additions and 0 deletions
@ -0,0 +1,261 @@ |
|||||||
|
# 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 |
||||||
|
import mathutils |
||||||
|
from PyHSPlasma import * |
||||||
|
import weakref |
||||||
|
|
||||||
|
from . import utils |
||||||
|
|
||||||
|
class AnimationConverter: |
||||||
|
def __init__(self, exporter): |
||||||
|
self._exporter = weakref.ref(exporter) |
||||||
|
self._bl_fps = bpy.context.scene.render.fps |
||||||
|
|
||||||
|
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 make_pos_controller(self, fcurves, default_xform): |
||||||
|
pos_curves = (i for i in fcurves if i.data_path == "location" and i.keyframe_points) |
||||||
|
keyframes, bez_chans = self._process_keyframes(pos_curves) |
||||||
|
if not keyframes: |
||||||
|
return None |
||||||
|
|
||||||
|
# At one point, I had some... insanity here to try to crush bezier channels and hand off to |
||||||
|
# blah blah blah... As it turns out, point3 keyframe's tangents are vector3s :) |
||||||
|
ctrl = self._make_point3_controller(keyframes, bez_chans, default_xform.to_translation()) |
||||||
|
return ctrl |
||||||
|
|
||||||
|
def make_rot_controller(self, fcurves, default_xform): |
||||||
|
# TODO: support rotation_quaternion |
||||||
|
rot_curves = (i for i in fcurves if i.data_path == "rotation_euler" and i.keyframe_points) |
||||||
|
keyframes, bez_chans = self._process_keyframes(rot_curves) |
||||||
|
if not keyframes: |
||||||
|
return None |
||||||
|
|
||||||
|
# Ugh. Unfortunately, it appears Blender's default interpolation is bezier. So who knows if |
||||||
|
# many users will actually see the benefit here? Makes me sad. |
||||||
|
if bez_chans: |
||||||
|
ctrl = self._make_scalar_controller(keyframes, bez_chans, default_xform.to_euler("XYZ")) |
||||||
|
else: |
||||||
|
ctrl = self._make_quat_controller(keyframes, default_xform.to_euler("XYZ")) |
||||||
|
return ctrl |
||||||
|
|
||||||
|
def make_scale_controller(self, fcurves, default_xform): |
||||||
|
scale_curves = (i for i in fcurves if i.data_path == "scale" and i.keyframe_points) |
||||||
|
keyframes, bez_chans = self._process_keyframes(scale_curves) |
||||||
|
if not keyframes: |
||||||
|
return None |
||||||
|
|
||||||
|
# There is no such thing as a compound scale controller... in Plasma, anyway. |
||||||
|
ctrl = self._make_scale_value_controller(keyframes, bez_chans, default_xform) |
||||||
|
return ctrl |
||||||
|
|
||||||
|
def _make_point3_controller(self, keyframes, bezier, default_xform): |
||||||
|
ctrl = plLeafController() |
||||||
|
subctrls = ("X", "Y", "Z") |
||||||
|
keyframe_type = hsKeyFrame.kBezPoint3KeyFrame if bezier else hsKeyFrame.kPoint3KeyFrame |
||||||
|
exported_frames = [] |
||||||
|
last_xform = [default_xform[0], default_xform[1], default_xform[2]] |
||||||
|
|
||||||
|
for keyframe in keyframes: |
||||||
|
exported = hsPoint3Key() |
||||||
|
exported.frame = keyframe.frame_num |
||||||
|
exported.frameTime = keyframe.frame_time |
||||||
|
exported.type = keyframe_type |
||||||
|
|
||||||
|
in_tan = hsVector3() |
||||||
|
out_tan = hsVector3() |
||||||
|
value = hsVector3() |
||||||
|
for i, subctrl in enumerate(subctrls): |
||||||
|
fkey = keyframe.values.get(i, None) |
||||||
|
if fkey is not None: |
||||||
|
v = fkey.co[1] |
||||||
|
last_xform[i] = v |
||||||
|
setattr(value, subctrl, v) |
||||||
|
setattr(in_tan, subctrl, keyframe.in_tans[i]) |
||||||
|
setattr(out_tan, subctrl, keyframe.out_tans[i]) |
||||||
|
else: |
||||||
|
setattr(value, subctrl, last_xform[i]) |
||||||
|
setattr(in_tan, subctrl, 0.0) |
||||||
|
setattr(out_tan, subctrl, 0.0) |
||||||
|
exported.inTan = in_tan |
||||||
|
exported.outTan = out_tan |
||||||
|
exported.value = value |
||||||
|
exported_frames.append(exported) |
||||||
|
ctrl.keys = (exported_frames, keyframe_type) |
||||||
|
return ctrl |
||||||
|
|
||||||
|
def _make_quat_controller(self, keyframes, default_xform): |
||||||
|
ctrl = plLeafController() |
||||||
|
keyframe_type = hsKeyFrame.kQuatKeyFrame |
||||||
|
exported_frames = [] |
||||||
|
last_xform = [default_xform[0], default_xform[1], default_xform[2]] |
||||||
|
|
||||||
|
for keyframe in keyframes: |
||||||
|
exported = hsQuatKey() |
||||||
|
exported.frame = keyframe.frame_num |
||||||
|
exported.frameTime = keyframe.frame_time |
||||||
|
exported.type = keyframe_type |
||||||
|
# NOTE: quat keyframes don't do bezier nonsense |
||||||
|
|
||||||
|
value = mathutils.Euler(last_xform, default_xform.order) |
||||||
|
for i in range(3): |
||||||
|
fkey = keyframe.values.get(i, None) |
||||||
|
if fkey is not None: |
||||||
|
v = fkey.co[1] |
||||||
|
last_xform[i] = v |
||||||
|
value[i] = v |
||||||
|
quat = value.to_quaternion() |
||||||
|
exported.value = utils.quaternion(quat) |
||||||
|
exported_frames.append(exported) |
||||||
|
ctrl.keys = (exported_frames, keyframe_type) |
||||||
|
return ctrl |
||||||
|
|
||||||
|
def _make_scalar_controller(self, keyframes, bez_chans, default_xform): |
||||||
|
ctrl = plCompoundController() |
||||||
|
subctrls = ("X", "Y", "Z") |
||||||
|
for i in subctrls: |
||||||
|
setattr(ctrl, i, plLeafController()) |
||||||
|
exported_frames = ([], [], []) |
||||||
|
|
||||||
|
for keyframe in keyframes: |
||||||
|
for i, subctrl in enumerate(subctrls): |
||||||
|
fkey = keyframe.values.get(i, None) |
||||||
|
if fkey is not None: |
||||||
|
keyframe_type = hsKeyFrame.kBezScalarKeyFrame if i in bez_chans else hsKeyFrame.kScalarKeyFrame |
||||||
|
exported = hsScalarKey() |
||||||
|
exported.frame = keyframe.frame_num |
||||||
|
exported.frameTime = keyframe.frame_time |
||||||
|
exported.inTan = keyframe.in_tans[i] |
||||||
|
exported.outTan = keyframe.out_tans[i] |
||||||
|
exported.type = keyframe_type |
||||||
|
exported.value = fkey.co[1] |
||||||
|
exported_frames[i].append(exported) |
||||||
|
for i, subctrl in enumerate(subctrls): |
||||||
|
my_keyframes = exported_frames[i] |
||||||
|
|
||||||
|
# ensure this controller has at least ONE keyframe |
||||||
|
if not my_keyframes: |
||||||
|
hack_frame = hsScalarKey() |
||||||
|
hack_frame.frame = 0 |
||||||
|
hack_frame.frameTime = 0.0 |
||||||
|
hack_frame.type = hsKeyFrame.kScalarKeyFrame |
||||||
|
hack_frame.value = default_xform[i] |
||||||
|
my_keyframes.append(hack_frame) |
||||||
|
getattr(ctrl, subctrl).keys = (my_keyframes, my_keyframes[0].type) |
||||||
|
return ctrl |
||||||
|
|
||||||
|
def _make_scale_value_controller(self, keyframes, bez_chans, default_xform): |
||||||
|
subctrls = ("X", "Y", "Z") |
||||||
|
keyframe_type = hsKeyFrame.kBezScaleKeyFrame if bez_chans else hsKeyFrame.kScaleKeyFrame |
||||||
|
exported_frames = [] |
||||||
|
|
||||||
|
_scale = default_xform.to_scale() |
||||||
|
last_xform = [_scale[0], _scale[1], _scale[2]] |
||||||
|
unit_quat = default_xform.to_quaternion() |
||||||
|
unit_quat.normalize() |
||||||
|
unit_quat = utils.quaternion(unit_quat) |
||||||
|
|
||||||
|
for keyframe in keyframes: |
||||||
|
exported = hsScaleKey() |
||||||
|
exported.frame = keyframe.frame_num |
||||||
|
exported.frameTime = keyframe.frame_time |
||||||
|
exported.type = keyframe_type |
||||||
|
|
||||||
|
in_tan = hsVector3() |
||||||
|
out_tan = hsVector3() |
||||||
|
value = hsVector3() |
||||||
|
for i, subctrl in enumerate(subctrls): |
||||||
|
fkey = keyframe.values.get(i, None) |
||||||
|
if fkey is not None: |
||||||
|
v = fkey.co[1] |
||||||
|
last_xform[i] = v |
||||||
|
setattr(value, subctrl, v) |
||||||
|
setattr(in_tan, subctrl, keyframe.in_tans[i]) |
||||||
|
setattr(out_tan, subctrl, keyframe.out_tans[i]) |
||||||
|
else: |
||||||
|
setattr(value, subctrl, last_xform[i]) |
||||||
|
setattr(in_tan, subctrl, 0.0) |
||||||
|
setattr(out_tan, subctrl, 0.0) |
||||||
|
exported.inTan = in_tan |
||||||
|
exported.outTan = out_tan |
||||||
|
exported.value = (value, unit_quat) |
||||||
|
exported_frames.append(exported) |
||||||
|
|
||||||
|
ctrl = plLeafController() |
||||||
|
ctrl.keys = (exported_frames, keyframe_type) |
||||||
|
return ctrl |
||||||
|
|
||||||
|
def _process_keyframes(self, fcurves): |
||||||
|
"""Groups all FCurves for the same frame together""" |
||||||
|
keyframe_data = type("KeyFrameData", (), {}) |
||||||
|
fps = self._bl_fps |
||||||
|
pi = math.pi |
||||||
|
|
||||||
|
keyframes = {} |
||||||
|
bez_chans = set() |
||||||
|
for fcurve in fcurves: |
||||||
|
fcurve.update() |
||||||
|
for fkey in fcurve.keyframe_points: |
||||||
|
frame_num, value = fkey.co |
||||||
|
if fps == 30.0: |
||||||
|
# hope you don't have a frame 29.9 and frame 30.0... |
||||||
|
frame_num = int(frame_num) |
||||||
|
else: |
||||||
|
frame_num = int(frame_num * (30.0 / fps)) |
||||||
|
keyframe = keyframes.get(frame_num, None) |
||||||
|
if keyframe is None: |
||||||
|
keyframe = keyframe_data() |
||||||
|
keyframe.frame_num = frame_num |
||||||
|
keyframe.frame_time = frame_num / fps |
||||||
|
keyframe.in_tans = {} |
||||||
|
keyframe.out_tans = {} |
||||||
|
keyframe.values = {} |
||||||
|
keyframes[frame_num] = keyframe |
||||||
|
idx = fcurve.array_index |
||||||
|
keyframe.values[idx] = fkey |
||||||
|
|
||||||
|
# Calculate the bezier interpolation nonsense |
||||||
|
if fkey.interpolation == "BEZIER": |
||||||
|
og_frame = fkey.co[0] |
||||||
|
keyframe.in_tans[idx] = -(value - fkey.handle_left[1]) / (og_frame - fkey.handle_left[0]) / fps / (2 * pi) |
||||||
|
keyframe.out_tans[idx] = (value - fkey.handle_right[1]) / (og_frame - fkey.handle_right[0]) / fps / (2 * pi) |
||||||
|
else: |
||||||
|
keyframe.in_tans[idx] = 0.0 |
||||||
|
keyframe.out_tans[idx] = 0.0 |
||||||
|
if keyframe.in_tans[idx] != 0.0 or keyframe.out_tans[idx] != 0.0: |
||||||
|
bez_chans.add(idx) |
||||||
|
|
||||||
|
# Return the keyframes in a sequence sorted by frame number |
||||||
|
final_keyframes = [keyframes[i] for i in sorted(keyframes)] |
||||||
|
return (final_keyframes, bez_chans) |
||||||
|
|
||||||
|
@property |
||||||
|
def _mgr(self): |
||||||
|
return self._exporter().mgr |
@ -0,0 +1,175 @@ |
|||||||
|
# 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.K = utils.vector3(matrix.to_scale()) |
||||||
|
affine.F = -1.0 if matrix.determinant() < 0.0 else 1.0 |
||||||
|
rot = matrix.to_quaternion() |
||||||
|
affine.Q = utils.quaternion(rot) |
||||||
|
rot.normalize() |
||||||
|
affine.U = utils.quaternion(rot) |
||||||
|
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