Skip to content

pythontk v0.8.86

Latest

Choose a tag to compare

@m3trik m3trik released this 12 Jul 21:40
  • 2026-07-12 — Review-pass fixes on the extraction change set (pre-commit adversarial review). (1) .gitignore's bare MANIFEST pattern silently ignored the entire new core_utils/engines/shots/manifest/ package (case-insensitive repo + unanchored pattern): committing the migration would have shipped a DEFAULT_INCLUDE and tests referencing a package git never saw — ModuleNotFoundError on every fresh clone/CI. Root-anchored to /MANIFEST (the setuptools artifact it was for); the package now surfaces as untracked. (2) TaskFactory revert-on-exception: _manage_context skipped _revert_states when a task raised mid-loop or the with-body threw at the yield — a failed export left set_* scene state permanently applied. The loop+yield now sit in a try/finally. (3) TaskFactory._is_success([]) raised IndexError on an empty tuple/list check result; an empty sequence is now a plain failed check. Both TDD'd (red first); test_task_factory.py also gained the TASK_ORDER / zero-param-flag / list+dict-splat / LIFO-revert pins its docstring promised (5 → 12). (4) Consumer leakage removed: the base pipeline no longer sets _optimize_keys_enabled (a mayatk task name hard-coded in the host-free primitive); blendertk's TaskManager — its only reader — sets it in its own _execute_tasks_and_checks override. (5) Root surface: plan_reorder joined its three sibling planners in __all__ (star-import parity with the registration), the root-registered manifest surface (parse_csv, ShotManifest, resolve_ranges, the dataclasses) is now in __all__ too, and the too-generic apply root export was dropped (consumers import shot_apply.apply by module path; the neighboring comment already stated that policy). (6) Seam coverage: compute_duration(resolve_source_fn=…) (resolution args, exception-swallow fallback) and ShotManifest._audio_grow_duration (override respected by the re-sync grow pass; pure default = max(template, measured)) are now pinned in test_shots_manifest_core.py (+4). (7) Docs de-staled: docs/README.md package table (engines tenant listed; drape/MapFactory rows corrected to RailSurface/core_utils/engines/textures), CLAUDE.md's MapFactory entry, test_polyline.py's pointer to the moved drape tests; process_stream docstrings now state the subscribe-replay reentrancy constraint and ProcessReader's binary-pipe contract.

  • 2026-07-12 — Curtain generator decomposed out of pythontk: general geo_utils/rail_surface.py::RailSurface extracted; the curtain-specific CurtainDrape moved to its DCC consumers (vendored twins, drift-guarded). The curtain drape (geo_utils/drape.py) was a single tool's domain math — too tool-specific for geo_utils' generality promise, yet (one tool, no model/planner breadth) not a domain core either, so every pythontk home misfit. Decomposed per placement rule #1 instead of relocated: NEW RailSurface (geo_utils, on the lazy root) owns the invariant rail→frames→grid machinery — frames a rail (Polyline) into a (u_segs+1)×(v_segs+1) grid and applies a caller-supplied displace(u, v, pos, tan, normal) -> Vec field; any curve-driven surface generator (banner/ribbon/terrain strip) is a new displacement over it, not a new framework. CurtainDrape (the pleat/fold/catenary/crease displacement + seeded feature precompute + resolution policy) left the package: it now lives with its two consumers as code-identical vendored twins (mayatk/blendertk edit_utils/_curtain_drape.py — they can't import each other, and extapps' test_vendor_sync.py gained a TestCurtainEngineDccSync drift guard), shedding its unused LoggingMixin base and dead _lerp/_unit aliases in transit. ptk.CurtainDrape is no longer exported (mayatk's CurtainMesh subclasses the local twin; blendertk's create_curtain builds from it; the class contract — __init__/prepare/drape/grid_points — is unchanged). The engines tenant rule is sharpened in CLAUDE.md: a single tool's math is not an engine — extract the general primitive, vendor the remainder. Tests: new test_rail_surface.py (8) pins the primitive (degenerate rail, seg floors, frame count, row-major grid, normalized u/v, verbatim displace output, vertical-drop field); the 12 drape tests moved to mayatk's test_curtain_drape.py (pure Python — runs under pytest and the in-Maya runner). Verified: full suite green (1849 — the 12 moved tests exactly account for 1861→1849); extapps 441 (the guard's +2); the moved drape tests 12/12 under pytest; fresh headless Blender 25/25 (identical build through the vendored twin, drift 0.00e+00); fresh mayapy standalone 8/8 (CurtainMesh subclasses the local twin and builds a matching mesh; ptk.CurtainDrape confirmed gone, ptk.RailSurface resolves).

  • 2026-07-12 — Shots engine: two injection seams added while migrating mayatk onto it (the engine's first production adoption beyond blendertk). Auditing the engine against the working Maya code it replaces surfaced two behaviors the extraction had no seam for; both are pure-default hooks so blendertk is unaffected: (1) behaviors.compute_duration gains resolve_source_fn(entry_name, entry_kind) — Maya's audio_clips tracks carry paths independently of the manifest CSV, so an audio entry with no source_path can still resolve one via its normalized track id; without the seam that fallback (and the clip-length sizing it feeds) was silently lost. (2) ShotManifest._audio_grow_duration(audio_objs) — the audio-grow pass in _compute_plan is now an overridable hook (pure default: template duration maxed with _measure_audio, exactly the previous inline code); mayatk overrides it to route through its own compute_duration binding, which both preserves the original sum-based semantics and keeps the established ...behaviors.compute_duration mock seam working against the inherited planner. Also fixed three stale "YAML" docstrings in behaviors._behaviors (the store is JSON). Engine suites green: test_shots_core + test_shots_manifest_core 110/110; full run green.

  • 2026-07-12 — Cross-DCC extractions: process_stream primitives, TaskFactory, blendshape Weights — while the app-specific Marmoset/Substance engines stay with their consumers.

    • core_utils/process_stream.pyOutputStream (thread-safe multi-consumer (source, line) pub/sub with bounded replayable history + wait_for), ProcessReader (subprocess-pipe reader thread), and LogTailer (rotation-aware log-file tail thread): the generic stream mechanism extracted out of the Substance bridges' connection.py, whose mayatk/blendertk copies now compose it. Exposed on the lazy root (pythontk.OutputStream/ProcessReader/LogTailer) beside the other app-agnostic mechanism (app_handoff, app_launcher, net_utils.rpc, script_template). New test/test_process_stream.py (20) pins the pub/sub, replay, wait_for, pipe-reader, and tail/truncate/rotation contracts DCC-free.
    • core_utils/task_factory.pyTaskFactory, the generic reflection-based task/check pipeline (getattr/inspect.signature dispatch, TASK_ORDER ordering, LIFO set/revert, check-result processing) that mayatk's and blendertk's scene-exporter TaskManager were vendoring byte-identical (~230 LOC). A general orchestration base (no domain model or planner), so it lives in core_utils beside app_handoffnot engines/ — per the "not every shared framework is an engine" rule. Exposed as pythontk.TaskFactory; new test/test_task_factory.py (5) pins the dispatch/revert/check contract DCC-free (its only prior coverage was DCC-coupled).
    • math_utils/weights.pyWeights (blendShape / shape-key morph weight math: frame→weight, evenly-spaced generation, 3-dp rounding), byte-identical in both blendshape_animators; blendertk's own docstring said "promote to pythontk when a second consumer shows up" — mayatk was the second consumer. Exposed as pythontk.Weights.
    • Placement rule applied, both directions: the Marmoset Toolbag and Substance Painter engines were evaluated for a pythontk home and deliberately kept vendored downstream (mayatk / blendertk / extapps' Marmoset panel) — host-agnostic but not app-agnostic (a named commercial product's exe discovery, protocol dialect, in-app plugin, templates), they fail the bottom-of-stack charter; core_utils/engines/ stays domain cores only (shots / instancing / textures), and app_handoff's own docstring already places app deliverers ("Unity copy-to-Assets, Painter/Toolbag RPC") "with their app glue". The vendored copies are drift-guarded by extapps' test_vendor_sync.py (extended: mayatk↔blendertk code-parity for both engines, on top of the original extapps↔mayatk byte-parity).
      Verified: full suite green (1853 passed); mayatk + blendertk each subclass the shared TaskFactory and use the shared Weights in fresh Maya and fresh Blender (is-identity checks); both DCC connection.py copies compose the shared stream primitives (fresh-instance is-identity probes in both DCCs); vendor-sync guard 5/5; extapps suite 439.
  • 2026-07-11 — Two more domain engines relocated into core_utils/engines/: instancing/, textures/ (public API unchanged). Following the namespace's charter (model + algorithm/planner + DI seams, domain-specific, needed by ≥2 downstream packages that can't import each other), two frameworks that were sitting in generality-promising *_utils buckets graduated to engines/ (the curtain generator was weighed for the same move and decomposed instead — see the 2026-07-12 entry above):

    • engines/instancing/geo_utils/assembly_sorter.pyAssemblySorter, the separated-part clustering core behind auto-instancing (feature-dict-in → index-plan-out). Only the sorter moved: the general PCA/NN/cluster math stays as geo_utils.PointCloud (the domain was already decomposed per placement rule #1).
    • engines/textures/ ← the whole PBR cluster img_utils/{map_registry, map_factory/, map_optimizer, output_template, mat_report, map_compositor} — model (map_registry taxonomy/presets) + planner/strategies (map_factory: ConversionRegistry/TextureProcessor/handlers/prepare_maps) + plan/apply optimizer + export-config + report view. Shared by mayatk, blendertk, and the standalone extapps texture tools. Moved as one domain (incl. map_compositor, which rides on map_registry.WF) rather than split. map_registry is the clean model (imports only core_utils), so no import cycle despite the engine composing ImgUtils outward.

    Relocation, not a rename — every public symbol resolves through the lazy root exactly as before (from pythontk import MapFactory, AssemblySorter, …); only DEFAULT_INCLUDE keys and internal module paths changed. Clean break, no back-compat shims (matching the geo_utils Polyline/PointCloud precedent): the 27 files that imported the old internal paths (pythontk.img_utils.map_factory, …) were repointed — mayatk (shader_templates, texture_path_editor), extapps (converter/packer/compositor), the blendertk PIL-injection sys.modules keys, pythontk's own _img_utils lazy imports, and ~10 test files across the four repos. Consumers on the root ptk.X surface were untouched. img_utils keeps ImgUtils + the photogrammetry-ingest primitives (MaskGenerator/ExposureEqualizer/ImageCurator).

    app_handoff.py was evaluated and deliberately NOT moved — it clears "shared by both DCCs" but is a general Template-Method/Strategy orchestration base with no domain-model or algorithm layer (its own docstring calls it "Generic"), so it stays in core_utils beside app_launcher/preset_store. The "not every shared framework is an engine" rule now lives in CLAUDE.md. Verification: full suite green (1821 passed / 1838, 17 skipped); the relocated tests (textures 328, instancing 77) pass at the new paths; every mayatk/extapps/blendertk edited source is py_compile-clean and its new pythontk import path resolves headlessly.

  • 2026-07-11 — core_utils/engines/ domain-engine namespace; the shots system's pure core consolidated here. New charter'd home for domain engines — a complete, pure app core (model + planning + algorithms + DI seams) that is domain-specific by design and needed by ≥2 downstream packages that can't import each other (mayatk + blendertk), so it can't live in a generality-promising *_utils layer nor be duplicated downstream (rule + rationale in pythontk CLAUDE.md). First tenant: engines/shots/shot_model (ShotStore/ShotBlock + overridable scene hooks, SHOT_PALETTE), shot_plan (plan_respace/plan_ripple_downstream/plan_ripple_upstream/plan_reorderMovePlan; collision-safe apply with an injected key-mover), shot_detection (pure cluster_segments_by_gap/boundaries_from_key_entries), shot_apply, and the Shot Manifest core engines/shots/manifest/manifest_model (parse_csv/ColumnMap/BuilderStep/BuilderObject/PlannedShot/StepStatus/detect_behaviors), range_resolver (resolve_ranges/prune_to_top_boundaries), mapping/ (CSV column-map templates over TemplateSet), behaviors/ (keying-recipe templates; JSON not YAML so Blender's PyYAML-less bundled Python can load them), and manifest_engine (ShotManifest — a hooked base whose sync/update/assess/_compute_plan are pure, with DCC scene seams — fps/audio-measure/scene-discovery/key-emission — overridden by the mayatk/blendertk adapters). Root exports the public surface (ShotStore, ShotManifest, parse_csv, resolve_ranges, plan_reorder, SHOT_PALETTE, store-event dataclasses, …). Backs both mayatk's and blendertk's shot toolsets from one implementation. Fixed the existing-shot audio-grow path in manifest_engine (it sized via compute_duration without the _measure_audio hook, so an existing audio shot never re-grew when its clip lengthened — a behavioral divergence from the measured mayatk path; now routes through the hook, pure-default no-op preserved). Tests: test_shots_manifest_core.py 42; full suite green.