Conversation
New cfg parsing option will add all animations into NLA tracks which is faster than just importing all frames the usual way. Also added an option to export either from timeline markers or from NLA strips. TODO: Add custom properties for sequence looping and fps to the actions and maybe add a custom panel to change these in NLA view (cherry picked from commit 3a05dbc)
Adds a panel in the nla editor, so editing the fps and looping from the overview of all animations is possible and added a second panel in the actions section of the dopesheet editor. So working on an action directly also shows the options directly. (cherry picked from commit 891c5d1)
(cherry picked from commit 52c9575)
Strips got action slots in 4.0 I think. These need to be set now else the animations won't play. (cherry picked from commit e335fce)
(cherry picked from commit 40d936b)
(cherry picked from commit 1964423)
(cherry picked from commit 459cc5f)
(cherry picked from commit 12cf8c6)
(cherry picked from commit 2bc46cb)
Add missing JAFilesystem.PathToFile (used by animation.cfg lookup but never ported), assert non-None where Optional bpy types are known present, and use a local animData variable instead of repeated armature.animation_data access.
format runs autopep8 in-place; pep8 runs pycodestyle in check mode against the same file set, reusing .pep8's existing config.
pep8 job mirrors typecheck's structure; added to nightly/release's needs list alongside smoke-test/typecheck.
.pep8's `ignore = E501` was replacing pycodestyle's own default ignore list rather than extending it, silently enabling E121/E123/E126/E226/ E24/E704/W503/W504 repo-wide. Restate those defaults alongside E501. Also hand-fix the remaining genuine violations (E711/E712/E713/E741, mixed tabs) in the actively-maintained core; ASE/ROFF/MD3/Patch stay excluded from format/pep8, matching pyrightconfig.json's existing exclusion of those unverified formats.
Generic counterpart to blender-tests, which only runs the fixed test suite. Needed to generate the simpleskel_nla.blend fixture.
…e 10 Covers all 21 existing frames (0-9, 10-20) for the upcoming NLA import/export test cases.
Action.slots/AnimData.action_slot/NlaStrip.action_slot were only introduced in Blender 4.4 -- the ported code assumed they always exist, breaking NLA-mode import on 4.1 (this add-on's declared minimum). Found by actually generating a fixture on 4.1.
Captures the NLA-track state animation.cfg/CFG-mode import produces for simpleskel.gla (two Actions/strips split at frame 10, per the animation.cfg fixture), for the upcoming NLA export/roundtrip test cases. Generated via the new blender-run-script skill on Blender 4.1 (the add-on's minimum) so it stays openable on every supported version.
…port With animData.action assigned directly (not via NLA tweak mode), pose.visual_transform_apply evaluates the active action at whatever frame the scene is currently on, not the frame passed to keyframe_insert -- leaving the scene's frame stale from the previous sequence corrupted exactly frame 0 of every subsequent sequence. Mirror the ALL-mode path's scene.frame_set() call. Found via case_nla_roundtrip failing only at the frame-10 sequence boundary; regenerated simpleskel_nla.blend with the fix.
Exercise the animation.cfg/NLA import-export path added by the recent cherry-picks: exporting NLA strips back to animation.cfg metadata, and round-tripping the NLA-materialized skeleton back to an identical .gla.
Both were 20, so case_nla_export's fps comparison would pass even if export mixed up or hardcoded per-sequence fps. test_seq_2 is now 24, making the roundtrip check on that field meaningful. Regenerated simpleskel_nla.blend to match.
Add a "next version" changelog entry, describe the new Cfg animation import mode, and document GLAMetaExport's NLA vs Markers source setting.
| @@ -0,0 +1,3 @@ | |||
| // name start length loop fps | |||
| test_seq_1 0 10 -1 20 | |||
| test_seq_2 10 11 -1 24 | |||
There was a problem hiding this comment.
add a trailing comment that the fps differs deliberately
| import bpy | ||
|
|
||
| sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "..")) | ||
| import testutil # noqa: E402 |
There was a problem hiding this comment.
document why the exception is necessary
| testutil.check(testutil.compare_glm(actual_glm, expected_glm) + testutil.compare_gla(actual_gla, expected_gla)) | ||
|
|
||
|
|
||
| def _load_animation_cfg(cfg_dir): |
There was a problem hiding this comment.
use type annotations for arguments and return values in this file
| same per-frame bone transforms as the original file, since Blender's NLA stack evaluation | ||
| combines strips transparently regardless of how the animation is organized in the editor.""" | ||
| import bpy | ||
| bpy.ops.wm.open_mainfile(filepath=os.path.join(TESTDATA, "simpleskel_nla.blend")) |
There was a problem hiding this comment.
the roundtrip should start from .gla + .cfg, import and re-export both, instead of using the .blend file
| actual_by_name = {seq.name: seq for seq in export_cfg.sequences} | ||
| expected_by_name = {seq.name: seq for seq in expected_cfg.sequences} | ||
| if set(actual_by_name) != set(expected_by_name): | ||
| mismatches.append( | ||
| f"sequence names differ: actual={set(actual_by_name)} expected={set(expected_by_name)}") | ||
|
|
||
| for name in sorted(set(actual_by_name) & set(expected_by_name)): | ||
| actual_seq = actual_by_name[name] | ||
| expected_seq = expected_by_name[name] | ||
| for field in ("start_frame", "num_frames", "loop", "fps"): | ||
| actual_value = getattr(actual_seq, field) | ||
| expected_value = getattr(expected_seq, field) | ||
| if actual_value != expected_value: | ||
| mismatches.append( | ||
| f"sequence '{name}': {field} differs: actual={actual_value} expected={expected_value}") |
There was a problem hiding this comment.
refactor this into a helper function similar to compare_gla
| self.fps = -1 | ||
|
|
||
| def __str__(self): | ||
| return "{name}\t\t{start}\t{frames}\t{loop}\t{fps}".format( |
| def from_cfg_line(cls, txt_line): | ||
| try: | ||
| # remove comments inline first, someone might have annotated these | ||
| line = txt_line.split("//")[0] |
There was a problem hiding this comment.
this parser is over simplified, a mid-token // does not initiate a comment, and there are also multi-line comments. Tokens may also be quoted. Numbers can have trailing garbage. I'm not sure if an entry must even stay on the same line?
Build a generator-based token parser and use that instead. Use https://github.com/mrwonko/ghoul2-browser-tools/blob/main/src/commonTokenizer.ts for reference, but note that we don't need to retain whitespace and comments here, we can just yield a string for each token. Unit-test the generic token parser using our fixture.
You can also look at mrwonko/ghoul2-browser-tools#28 for animation.cfg parsing reference, but it has not been reviewed yet, so don't trust it completely. The canonical source is BG_ParseAnimationFile in https://github.com/jedis/jediacademy/blob/master/codemp/game/bg_panimate.c, you already have a local copy of that repo at ~/jediacademy.
Do test-driven development: first add a unit test to persist the current parse result for our fixture, plus maybe an additional case for partial final line. Make sure it passes. Then adjust the parser in a separate commit and verify it does not regress.
| pose_bone = bones[index] | ||
| # pose_bone.matrix = transformation * scaleMatrix | ||
| pose_bone.matrix = transformation | ||
| # in the _humanoid face, the scale gets changed. that messes the re-export up. FIXME: understand why. Is there a problem? |
There was a problem hiding this comment.
File an issue for this, if there isn't one already. Could it be connected to the bug where we incorrectly marked bones as connected? If so, it may be fixed, we should check.
| Python formatter and pyright type checking at `standard` mode. `make format` runs `autopep8 --in-place` | ||
| over the same file set; `make pep8` runs `pycodestyle` in check-only mode (what CI's `pep8` job runs). | ||
|
|
||
| ## Writing comments, commit messages, and PR descriptions |
There was a problem hiding this comment.
this also extends to issues
| \item Added a faster \texttt{.gla} animation import mode that reads sequences from an | ||
| \texttt{animation.cfg} file into separate NLA strips/actions, instead of importing every frame | ||
| as one large action. | ||
| \item The Ghoul 2 Animation Metadata exporter can now export from NLA strips, in addition to | ||
| timeline markers. |
There was a problem hiding this comment.
Credit SomaZ for these contributions.
animation.cfg: comment that test_seq_2's fps intentionally differs from test_seq_1's. generate_simpleskel_nla_blend.py: explain the E402 noqa like run_tests.py already does.
TYPE_CHECKING-guarded imports matching testutil.py's existing pattern; annotate every function's parameters and return type.
Add testutil.compare_animation_cfg (mirrors compare_gla/compare_glm), used by both tests instead of the ad-hoc diff previously inlined in case_nla_export. case_nla_export now also re-exports and checks the .gla (previously only checked animation.cfg). case_nla_roundtrip now builds its Blender state from simpleskel.gla + animation.cfg directly (the same CFG-mode import generate_simpleskel_nla_blend.py performs) instead of opening the pre-baked .blend fixture, and checks both re-exported artifacts too.
New case_animation_cfg_parse: fixture parse result + a synthetic partial-final-line case. Regression safety net before replacing the naive split()-based parser with a proper tokenizer.
The old txt_line.split("//")[0] + .split() parser didn't match the
real engine (bg_panimate.c/q_shared.c COM_ParseExt): // and /* only
start a comment as the first character(s) of a new token (not
mid-token), quoted tokens can contain whitespace, numbers tolerate
trailing garbage (atoi-style), and entries can span multiple lines.
New common_tokenizer.py implements those rules as a generator; ships
with the addon (added to Makefile's PY_FILES). load_from_cfg now reads
the whole file once and pulls 5 tokens per entry instead of iterating
line by line, which that per-line loop couldn't represent correctly
for multi-line entries/comments in the first place.
Extends case_animation_cfg_parse (added in the prior commit to pin
current behavior) with the edge cases the old parser got wrong;
confirmed passing with no regression on the cases it already covered.
CLAUDE.md's comments/commits/PR-descriptions section also covers issue descriptions now. Manual changelog credits SomaZ for the two NLA import/export bullets.
Summary
3a05dbcchief commit through its typo/type fixups), adapted to master's conventions (noImportHelpermixin,assert-based None-narrowing, PointerProperty for the newg2_sequence_propinstead of rawbpy.propsattribute assignment). AddsAnimationLoadMode.CFG: importing a.glanext to ananimation.cfgnow creates one Action/NLA strip per named sequence instead of dumping every frame into one Action, plus a matching NLA-strips-basedanimation.cfgexporter.JAFilesystem.PathToFilehelper the code depended on but the cherry-pick never brought over, andAction.slots/action_slotusage that crashes on Blender 4.1 (Action Slots only exist from 4.4+) -- both fixed.scene.frame_set()call.make format/make pep8targets + a newpep8CI job, plus a real fix to.pep8itself (ignore = E501was silently replacing pycodestyle's entire default-ignore list rather than extending it) and the ~24 genuine violations that exposed in the actively-maintained core.blender-run-scriptskill (generic counterpart toblender-tests) for one-off headless Blender work, used to generate the newsimpleskel_nla.blendfixture on Blender 4.1 (the add-on's minimum).case_nla_export/case_nla_roundtriptest cases, backed by a newanimation.cfgfixture forsimpleskel.gla(two sequences split at frame 10, with deliberately different fps per sequence so the export comparison isn't vacuous).Test plan
.claude/skills/blender-tests/run_blender_tests.sh 4.1 5.2-- all 9 cases pass on bothpyright --pythonpath "$(command -v python3)"-- cleanmake pep8-- cleansimpleskel_nla.blend/ NLA editor behavior in the Blender GUI