Skip to content

Commit c4513fa

Browse files
committed
Version 11.2.0 - Added interpolation mode options to animation properties and updated documentation to reflect new features. Enhanced animation export functionality to support forced interpolation modes.
1 parent 6f34101 commit c4513fa

File tree

9 files changed

+57
-11
lines changed

9 files changed

+57
-11
lines changed

docs/animations/essential_concepts.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ Let's say that in the image above, `bone_2` uses constant interpolation on frame
3030

3131
This behavior can cause unexpected results when using stepped or smooth interpolation. To ensure predictable animations, it's generally best to use linear interpolation unless a different mode is necessary.
3232

33+
```{note}
34+
If you want to force all keyframes to use a specific interpolation mode, you can use the `Interpolation mode` field in [Mcblend: Animation](mcblend-object-properties-animations).
35+
```
36+
3337
```{warning}
3438
Bézier interpolation in Blender does not exactly match smooth frames in Minecraft, which use a Catmull-Rom spline. For a similar effect in Blender's preview, use Bézier interpolation with "automatic" handle types.
3539

docs/gui/object_properties.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ The Materials panel allows you to quickly create materials that are similar to t
4747

4848
Once you've set up your render controller, you can use the `Apply materials` button to automatically create the materials for preview in Blender. This will help you get a better sense of how your model will look in Minecraft.
4949

50+
(mcblend-object-properties-animations)=
5051
### Mcblend: Animations
5152

5253
![](/img/gui/object_properties_armature_animations.png)
@@ -62,6 +63,7 @@ The `Mcblend: Animations panel` allows you to easily switch between animations i
6263
- The `Override previous animation` field directly translates to the override_previous_animation property of the Minecraft animation. It doesn't affect how the animation is rendered in Blender.
6364
- The `Loop` field directly translates to the loop property of the Minecraft animation file. There are three options: `true`, `false`, and `hold_on_last_frame`.
6465
- The `Anim Time Update` field directly translates to the anim_time_update property of the Minecraft animation file. You should either leave it empty (if you don't want to have anim_time_update in your animation) or put a Molang expression in it. It doesn't affect the animation in Mcblend because Mcblend doesn't support Molang.
66+
- The `Interpolation mode` field directly translates to the interpolation mode of the Minecraft animation file. There are four options: `linear`, `smooth`, `step`, and `auto`.
6567
- The `Frame start` field indicates the first frame of the animation. It tells Mcblend where the animation starts.
6668
- The `Frame end` field indicates the last frame of the animation. It tells Mcblend where the animation ends.
6769

-2.86 KB
Loading

mcblend/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@
116116
"author": "Artur",
117117
"description": "An addon that allows to design and animate Minecraft Bedrock Edition models",
118118
"blender": (4, 2, 0),
119-
"version": (11, 1, 0), # Remember to update the version in the "docs/conf.py"
119+
"version": (11, 2, 0), # Remember to update the version in the "docs/conf.py"
120120
"location": "",
121121
"warning": "",
122122
"category": "Object"

mcblend/object_data.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,17 @@ class MCBLEND_AnimationProperties(PropertyGroup):
199199
default="",
200200
maxlen=1024
201201
)
202+
interpolation_mode: EnumProperty(
203+
items=(
204+
('AUTO', 'Auto', 'Use the interpolation mode detected from Blender keyframes'),
205+
('LINEAR', 'Linear', 'Force all keyframes to use linear interpolation'),
206+
('SMOOTH', 'Smooth', 'Force all keyframes to use smooth interpolation'),
207+
('STEP', 'Step', 'Force all keyframes to use step interpolation')
208+
),
209+
name='Interpolation Mode',
210+
description='Control how keyframes are interpolated',
211+
default='AUTO'
212+
)
202213
loop: EnumProperty(
203214
items=(
204215
(

mcblend/object_data.pyi

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ class MCBLEND_AnimationProperties(PropertyGroup):
4848
skip_rest_poses: bool
4949
override_previous_animation: bool
5050
anim_time_update: str
51+
interpolation_mode: str
5152
loop: str
5253
frame_start: int
5354
frame_current: int

mcblend/operator_func/__init__.py

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121

2222
from .sqlite_bedrock_packs.better_json_tools import load_jsonc
2323

24-
from .animation import AnimationExport
24+
from .animation import AnimationExport, InterpolationMode
2525
from .common import (
2626
MINECRAFT_SCALE_FACTOR, CubePolygon, McblendObject, McblendObjectGroup, MeshType,
2727
apply_obj_transform_keep_origin, fix_cube_rotation, star_pattern_match,
@@ -84,21 +84,29 @@ def export_animation(
8484
if armature is None or armature.type != 'ARMATURE':
8585
# Should never happen (checked in the operator)
8686
raise ValueError("Selected object is not an armature")
87-
anim_data = get_mcblend(armature).animations[
88-
get_mcblend(armature).active_animation]
87+
model_properties = get_mcblend(armature)
88+
anim_id = model_properties.active_animation
89+
anim_data = model_properties.animations[anim_id]
8990

90-
# TODO - write this code nicer, passing world_origin as a string isn't
91-
# a perfect solution
9291
world_origin = None
93-
use_armature_origin: bool = get_mcblend(
94-
armature).model_origin == ModelOriginType.ARMATURE.value
92+
use_armature_origin: bool = model_properties.model_origin == (
93+
ModelOriginType.ARMATURE.value)
9594
if use_armature_origin:
9695
world_origin = armature
9796

9897
# Check and create object properties
9998
object_properties = McblendObjectGroup(armature, world_origin)
10099

101100
effective_fps = context.scene.render.fps/context.scene.render.fps_base
101+
# Create forced interpolation mode from string
102+
forced_interpolation = InterpolationMode.AUTO
103+
if anim_data.interpolation_mode == 'LINEAR':
104+
forced_interpolation = InterpolationMode.LINEAR
105+
elif anim_data.interpolation_mode == 'SMOOTH':
106+
forced_interpolation = InterpolationMode.SMOOTH
107+
elif anim_data.interpolation_mode == 'STEP':
108+
forced_interpolation = InterpolationMode.STEP
109+
102110
animation = AnimationExport(
103111
name=anim_data.name,
104112
length=(context.scene.frame_end-1)/effective_fps,
@@ -110,7 +118,8 @@ def export_animation(
110118
effect_events={
111119
event.name: event.get_effects_dict()
112120
for event in get_mcblend_events(context.scene)
113-
}
121+
},
122+
forced_interpolation=forced_interpolation
114123
)
115124
animation.load_poses_and_bone_states(object_properties, context)
116125
return animation.json(

mcblend/operator_func/animation.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ class InterpolationMode(Enum):
3030
STEP = 0
3131
LINEAR = 1
3232
SMOOTH = 2
33+
AUTO = 3 # Use the interpolation mode detected from Blender keyframes
3334

3435
# Must be comparable for bisect
3536
def __lt__(self, other: InterpolationMode) -> bool:
@@ -165,9 +166,12 @@ def frame_to_t(frame: float, fps: float) -> str:
165166
float, None | Tuple[str, TransformationType, InterpolationMode]]
166167

167168
class ObjectKeyframesInfo:
168-
def __init__(self, obj: Object | None):
169+
def __init__(
170+
self, obj: Object | None,
171+
forced_interpolation: InterpolationMode = InterpolationMode.AUTO):
169172
self.keyframes: set[float] = set()
170173
self.timelines: Dict[tuple[str, TransformationType], Timeline] = {}
174+
self.forced_interpolation = forced_interpolation
171175
if obj is None:
172176
return
173177
self._init_keyframes_and_timelines(obj)
@@ -183,6 +187,10 @@ def get_bone_state(
183187
:param timestamp: the timestamp of the keyframe.
184188
:returns: the interpolation mode of the bone state.
185189
'''
190+
# Return forced interpolation if not AUTO
191+
if self.forced_interpolation != InterpolationMode.AUTO:
192+
return self.forced_interpolation
193+
186194
timelines_key = (bone_name, transformation_type)
187195
if timelines_key not in self.timelines:
188196
return InterpolationMode.LINEAR
@@ -399,6 +407,9 @@ class AnimationExport:
399407
:param poses: Optional - poses of the animation (keyframes) keyed by the
400408
number of the frame. This dictionary is empty by default after the
401409
creation and it gets populated on loading the poses.
410+
:param forced_interpolation: Optional - force all keyframes to use a specific
411+
interpolation mode: InterpolationMode.LINEAR, InterpolationMode.SMOOTH,
412+
InterpolationMode.STEP, or InterpolationMode.AUTO (default).
402413
'''
403414
name: str
404415
length: float
@@ -412,6 +423,8 @@ class AnimationExport:
412423
poses: Dict[float, Pose] = field(default_factory=dict)
413424
sound_effects: Dict[int, List[Dict[Any, Any]]] = field(default_factory=dict)
414425
particle_effects: Dict[int, List[Dict[Any, Any]]] = field(default_factory=dict)
426+
forced_interpolation: InterpolationMode = field(
427+
default=InterpolationMode.AUTO)
415428

416429
def load_poses_and_bone_states(
417430
self, object_properties: McblendObjectGroup,
@@ -437,7 +450,9 @@ def load_poses_and_bone_states(
437450
# The frame value in the dictionary key doesn't really matter
438451
self.poses[keyframe] = pose
439452
else:
440-
bone_states = ObjectKeyframesInfo(context.object)
453+
bone_states = ObjectKeyframesInfo(
454+
context.object, forced_interpolation=self.forced_interpolation
455+
)
441456
for keyframe in sorted(bone_states.keyframes):
442457
if (
443458
keyframe < context.scene.frame_start or

mcblend/panel.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -734,6 +734,10 @@ def draw(self, context: Context):
734734
active_anim, # type: ignore
735735
"anim_time_update",
736736
text="Anim Time Update")
737+
col.prop(
738+
active_anim, # type: ignore
739+
"interpolation_mode",
740+
text="Interpolation Mode")
737741
col.prop(
738742
bpy.context.scene, # type: ignore
739743
"frame_start", text="Frame start")

0 commit comments

Comments
 (0)