github m3trik/pythontk v0.14.1
pythontk v0.14.1

4 hours ago
  • 2026-10-12 -- A mapping template's option names the section a UI lists it under (core_utils/engines/shots/manifest/mapping/_mapping.py Mapping.option_specs gains section, new Mapping.option_sections; _spec.py validates it; default.json groups its nine options as Sheet / Objects / Shots). option_sections groups the specs by section in first-appearance order, wherever the file puts them, so a UI draws one titled group per section with no code per option (the Shot Manifest's CSV Mapping popup). A template read without validation that writes "section": null lists that option under "", never None.
    • Test(s): test_shots_manifest_core.py new test_an_option_names_the_section_a_ui_groups_it_under (+ the null section, red: None != ''), test_the_shipped_template_groups_every_option (red: no section key); test_specs_are_shaped_for_a_widget_factory pins the new key; test_surface_snapshot.py snapshot regenerated for PlannedInsert and SHOT_STATUS_LEVELS.
  • 2026-10-12 -- Shot Manifest: on a built timeline a step with no shot gets one in sheet order, the later shots moving out of its way, and a range typed for an unbuilt step moves with them (core_utils/engines/shots/manifest/manifest_engine.py new ShotManifest.plan_insertions, insert_planned, ride_inserts, hooks _sequencer, _new_shot_duration, _built_metadata; manifest_model.py new PlannedInsert; shot_sequencer.py new ShotSequencer.insert_placement, insert_shot). A Build with Hide Missing Shots off placed each unbuilt step at a stale resolved range or zero-length at its predecessor's end, so the new shots lay over the built ones -- overlapping, zero-length, out of sheet order (2026-10-12: "very broken and out of order results"; SOL_REPLACE_ASSEMBLY: 37 new shots, dozens of overlaps, 9 zero-length). plan_insertions says where each goes before anything moves: after its previous step's shot and that shot's split tails (or at the head), sized as a first build sizes it, each placed by ShotSequencer.insert_placement -- the one placement rule insert_shot now places by too, read on the timeline the earlier inserts leave -- so every plan's bounds are where its shot lands once all the inserts are in (a later insert placed before an earlier one, on a timeline holding its shots out of sheet order, moved the earlier one past its plan). A copy of the arithmetic had drifted: content trailing a moved last shot was read where it was, not where its shot went (30 frames trailing each shot: A04 planned at (280, 330), landed at (310, 360)). insert_shot moves the shots after the insert by position, not every shot starting at the next one's frame, where the shot before the insert ends at or before the next one's start: a zero-length shot on that frame moved past the insert (A01, A02, Z, A03 for A01, Z, A02, A03); where the two overlap it keeps the frame rule, so overlapping shots sharing a start move together, each with its keys (by position the first stayed, its keys gone on with the second); every other timeline moves as before. insert_placement says which rows move, and takes a start a caller knows (as insert_shot does). insert_planned inserts through the _sequencer() hook (a DCC host's: keys and audio ride) and records each shot as a first build does -- bound to its step, the step's metadata and the name it gave (BUILD_NAME), which it never wrote, so edited_by_hand never saw an inserted shot renamed by hand. Each insert goes after the nearest row before it on the plan's timeline that exists when it is made (PlannedInsert.follows; after_shot_id, after_step_id): part of a plan goes where the plan put it -- an insert after one left out took the timeline's head. A range typed in the timeline's current frames is content (maintainer 2026-10-12): ride_inserts moves every one starting at or after an insert's pivot by its push -- the typed A04 (220, 270) landed over A03, moved to [170, 270], and now lands at (280, 330). A step with a typed range (plan_insertions(typed=)) is no insert but a row of the plan's timeline at that range, moved by the inserts before it, and the next new step follows it -- an untyped step after one typed past the last shot appended after the last SHOT, over it (X at (570, 620) on P's (570, 610)); insert_planned(typed=) starts such an insert after where the range lands. A step whose name the store refuses is left for the build to report. The no-range duration rule is _new_shot_duration, shared with _compute_plan.
    • Test(s): test_shots_manifest_core.py new TestInsertInSheetOrder (12; red: no plan_insertions, then test_an_inserted_shot_carries_what_a_first_build_writes (no build_name), test_content_trailing_a_moved_last_shot_moves_with_it, test_a_shot_sharing_the_insert_point_keeps_its_place, test_part_of_a_plan_keeps_its_place_in_the_sheet, test_every_plan_is_where_its_shot_lands, test_a_typed_range_rides_the_inserts_before_it, test_a_typed_step_is_not_inserted, test_a_step_after_a_typed_one_follows_where_that_one_lands (red: X at (570, 620)), test_a_typed_step_between_shots_is_followed_and_ridden), new TestInsertPlanMatchesTheInserts (150 seeded random timelines -- zero-length shots, overlapping shots sharing a start, unpaired shots, trailing content, typed ranges -- plan against the inserts, red before the shared rule); test_shot_sequencer.py new test_insert_moves_the_shots_after_it_by_position (red: Z moved), test_overlapping_shots_sharing_a_start_move_together (red: B1 left behind), test_insert_placement_is_where_insert_shot_puts_it, test_insert_at_a_start_the_caller_knows. Full suite 7249 passed, 0 failed (38 skipped).
  • 2026-10-12 -- AudioUtils.memo_duration: a host's clip-length reader runs once per file as it is on disk (audio_utils/_audio_utils.py). Kept by the file's resolved path, modification time and size, and the frame rate: a replaced file reads again; a file that cannot be stat'ed, or a reading of no length, is never kept. The Shot Manifest's insert preview plans on every table resolve and sizes each unbuilt audio step from its clip -- a WAV header read from a synced drive each time; mayatk's AudioUtils.audio_duration_frames and blendertk's manifest reader go through it.
    • Test(s): test_audio_utils.py new TestMemoDuration (2; red: no memo_duration).
  • 2026-10-12 -- SHOT_STATUS_LEVELS names the severity each shot status is shown at (core_utils/engines/shots/shot_model.py; SHOT_PALETTE is built from it); ManifestEdits.hidden_objects() with no step lists every hidden object; StepStatus.HELP["edited"] names the Shot Manifest's Edits mode; StepStatus.ROLLUP ranks by that severity (manifest_model.py). A UI can mark a status by shape as well as colour (the Shot Manifest's glyphs). The rollup ranked behavior_conflict, an error, below ambiguous_object and not_in_shot, warnings: a step holding both was named by the warning while its row's icon showed the error.
    • Test(s): test_shots_core.py new test_every_status_names_its_severity (red: no SHOT_STATUS_LEVELS); test_manifest_edits.py new test_every_hidden_object_wherever_it_sits; test_shots_manifest_core.py new test_the_rollup_names_the_worst_severity_first (red: the order).

Don't miss a new pythontk release

NewReleases is sending notifications on new releases.