github m3trik/pythontk v0.8.82
pythontk v0.8.82

5 hours ago
  • MapFactory review sweep — resolve-chain data-correctness fixes, non-greedy passthrough restored for Height/Opacity, and the base-name regex unified onto one registry SSoT (2026-07-08). Five defect classes fixed across the img_utils/map_factory package, each pinned by a failing-first regression test. (1) Raw-substitution in map resolution: handlers and the processor's create_* assemblers passed source types as preferred types (resolve_map("Roughness", "Specular"), ("Roughness","Smoothness","Glossiness","Specular")), so a direct step-1 match returned the raw file verbatim under the target's semantics — an un-inverted Smoothness saved as _Roughness, a raw RGB Specular packed as the metallic channel, and (via Specular's input_fallbacks=["Metallic",…]) the metallic map itself returned as "roughness" and inverted into the Mask Map's smoothness alpha. All resolution sites now request the target type only; the conversion registry performs the correct derivations (Smoothness→inverted Roughness P10, Specular→Metallic/Roughness P5, packed-map unpacks P8). Relatedly, MaskMapHandler's last-resort smoothness = metallic substitution is gone — a missing smoothness now leaves the alpha to pack_msao_texture's neutral white fill (pinned: alpha extrema (255,255), was an inverted metallic 225). (2) resolve_map None-poisoning: a converter returning None (e.g. get_roughness_from_packed on an alpha-less source) was cached into the inventory, making the type read as "present", returning None on every later lookup, and shadowing lower-priority conversions that could succeed; falsy results are no longer cached and resolution falls through. Because the unpack helpers themselves plant None for a channel the source lacks (extract_channels skips missing channels), resolve_map's direct-match and input-fallback steps plus the create_*/unpack_* inventory guards all switched from in to truthiness — a None entry reads as absent instead of short-circuiting resolution. (3) Over-broad consumed-type marking dropped user maps: NormalMapHandler.get_consumed_types claimed Bump/Height, so processing any existing normal map silently swallowed a provided Height map (which drives its own parallax/displacement slot); BaseColorHandler claimed Opacity/Transparency, so the standard (non-packing) workflow dropped a separate Opacity map. Both lists now declare only what processing renders redundant; the conversion/packing branches still mark their actual sources internally. (4) Base-name regex drift: ImgUtils.get_base_texture_name and MapFactory.get_base_texture_name carried near-duplicate suffix-strip regexes with different semantics (brick_ao.pngbrick in the factory, brick_ao in ImgUtils — grouping and extract-channel naming disagreed), and the factory's own pack_* methods used them inconsistently. The pattern now lives once on MapRegistry.get_suffix_strip_pattern() (the alias owner; cached), and both resolvers delegate — underscore-delimited suffixes case-insensitive at any length, attached short suffixes still capital-gated. (5) Robustness/cleanup: TextureProcessor.save_map no longer crashes in the dry-run and up-to-date paths when constructed without a logger (a public configuration — mayatk's shader templates build the processor directly); handlers stopped reaching into MapRegistry._maps (public get_map_types()/get() instead); ConversionRegistry.__getattr__ raises for _-prefixed names so protocol probes don't receive empty conversion lists; four unpack_* methods lost identical if save/else dead branches; examples/texture_factory_extensibility_example.py was rewritten against the real API (it imported a nonexistent map_factory_refactored module and demonstrated wrong signatures throughout); and a leftover block of editing-session deliberation comments was removed from test_map_factory.py. Tests: test_map_factory.py (+6: Height/Opacity passthrough, Mask-Map white-alpha fill, resolve_map skips failed conversions and planted-None entries, logger-less dry-run) and test_affix_hardening.py (+2 shared-table cases pinning _ao parity across both resolvers); full suite 1701 green (1684 passed, 17 skipped); consumer-side extapps texture-panel suites (converter/packer/compositor) 85 green.
  • Auto-instancing core extracted from mayatk: PointCloud.match_clouds/pca_basis/pca_eigenvalue_signature + new geo_utils/assembly_sorter.py::AssemblySorter; robust pca_transform no longer hard-requires scipy; fixed an inverted relative-transform matrix match_clouds inherited from the in-DCC code (2026-07-08). With blendertk's auto_instancer port as the second consumer, the DCC-neutral halves of mayatk's core_utils/auto_instancer moved down to this layer (both DCCs are now thin data-extraction adapters): (1) PointCloud.match_clouds(points_a, points_b, tolerance, scale_tolerance, normals_a/b, normal_threshold, uvs_identical, ...) — the full 3-stage identity test (fast ordered max-deviation → unordered K=4-twin KDTree identity with the flip-free normal gate → centered RMS-uniform-scale normalization + robust pca_transform with scale fold-back and translation composition); the UV check rides as a lazy callback so it hard-rejects exactly where the in-DCC version did (stage-1/2 positional success). (2) PointCloud.pca_basis(points) — the stabilized PCA frame (third-moment sign anchors, farthest-vertex orientation of degenerate subspaces) behind transform canonicalization, and pca_eigenvalue_signature(points) — the max-normalized quantized eigenvalue bucketing descriptor. (3) AssemblySorter — the entire separated-part clustering pipeline (relative-gap area classes, vectorized same-material bbox-touch adjacency, BFS components, fractured-fusion repair, whole-multiset repeat guard, 3-model GCD count splits with touch-first growth + internal-distance-consistency assignment + distinguishability-ordered greedy fallback, orphan recovery, exemplar-distance binding, proportional-area cross-copy support gate) operating on plain part-feature dicts (contract in the module docstring; the key-shadowing loop variable in the distance-consistency pass was renamed in transit). Bug fix with real-world impact: pca_transform's matrix maps cloud-b onto cloud-a (verified empirically, 3e-15), but the in-DCC composition assumed the opposite direction and only rebuilt the translation row — the relative transform placed a genuinely-rotated copy's replacement wrong whenever the rotation wasn't symmetric (R ≠ Rᵀ). It went unnoticed because the assembly flow canonicalizes first (rel = identity) and the leaf-mode tests only exercised identity/180° rotations. match_clouds transposes the linear block before rebuilding translation; caught by the new direction-verifying tests. scipy now optional in robust mode: the early return None gate is lifted — the existing brute-force scorer handles robust candidates (bounded memory), only the Kabsch spin refinement stays scipy-gated, and a new public PointCloud.nn_query helper (scipy KDTree, else chunked brute force — verified identical results) backs the unordered-identity stage (public because blendertk's geometry matcher consumes it directly — a cross-package private-API dependency would be brittle). Blender's bundled Python has no scipy; this makes the blendertk port work out of the box. Registered AssemblySorter in DEFAULT_INCLUDE/__all__. Tests: test_pointcloud.py +12 (match/reorder/count-mismatch/empty-clouds-identity/rotated-with-matrix-verification/scaled-strict-vs-scale-mode/UV-callback/half-flipped-normal-reject/no-scipy-fallback/NN-parity/basis-canonicalization-consistency/eigen-signature), new test_assembly_sorter.py (7 scenario mirrors of mayatk's TestAssemblySorting: stacked clasps, material bridge, one-off cluster, scaled copies, orphan recovery); full suite 1695 green. Consumer-verified: mayatk auto_instancer 55/55 + ground truth 3/3 (scene-1 precision/recall 1.000, [5,3,1] sharing) + scene-match 1/1 + full 3576/3576 on the delegated implementation; blendertk test_auto_instancer.py 38/38.
  • Color/ColorPair/Palette folded from a top-level color_utils subpackage into core_utils/color.py (2026-07-08). These three are zero-dependency (colorsys-only) immutable value types, and despite the name every consumer uses them for UI/display theming — uitk option-box/switchboard status colors, the maya+blender hierarchy-diff tree renderers, Palette.axes()/.channels() for axis and animation-channel colors — never in the image pipeline (img_utils's own color code is unrelated per-pixel sRGB/linear map math). So they belong beside the other foundational core_utils primitives (NamedTupleContainer, SymbolRecord), not as a top-level domain sibling to img_utils/file_utils. Public API unchangedfrom pythontk import Color, ColorPair, Palette still resolves through the lazy root; only the internal module path and its one DEFAULT_INCLUDE registration moved (color_utils._color_utilscore_utils.color). No code anywhere imported the submodule path directly, so there was zero call-site churn across uitk/mayatk/blendertk/tentacle. Test renamed test_color_utils.pytest_color.py (one-file-per-module) and repointed to the public root import (resilient to future internal moves); full suite 1654 green.

Don't miss a new pythontk release

NewReleases is sending notifications on new releases.