Skip to content

Latest commit

 

History

History
140 lines (113 loc) · 6.76 KB

File metadata and controls

140 lines (113 loc) · 6.76 KB

The mohokit package — layout and refactoring notes

Until 2026-08-26 this repository was five large single-file scripts (29,967 lines in total; moho2svg.py alone was 10,913). They are now a package. This document is the map, and the record of what the split found.

1. Layout

mohokit/
  paths.py            repo/schema/corpus directory locations (one owner)
  container.py        read/write .mohoproj + .moho ZIP, preserving formatting
  integrity.py        check_integrity: the gate every write passes
  geometry.py         Vec2, Mat2D
  bezier.py           pure cubic-Bezier maths
  channel.py          Channel: animated values and their evaluation
  style.py            Color, ResolvedStyle, masking/blend enums
  curve.py            Bezier reconstruction from Moho's own representation
  pathtrace.py        re-tracing a shape's edges into walk order
  outline.py          tapered strokes and brush stamping
  skin.py             bone skinning
  svgutil.py          small helpers
  document/           the document model (kinds, skeleton, mesh, layer, document)
  render/             settings, exporter, walk, shapes  (the SVG render path)
  lottie/             constants, transform, clipping, exporter, validate
  svgin/              SVG -> Moho importer
  lottiein/           Lottie -> Moho importer
  build/              Moho-JSON writing shared by both importers
  edit/               errors, locator, schema, fields, rename, lifecycle,
                      create, move, keyframes, actions, enums, batch
  cli/                one module per command-line entry point

64 modules. Largest file: 2,341 lines, down from 10,913.

The reverse-engineering notebook that was moho2svg.py's module docstring (1,453 lines) now lives in moho-svg-internals.md; code throughout the package cites it by section name.

2. The old scripts still work

moho2svg.py, mohoedit.py, moho2lottie.py, svg2moho.py and lottie2moho.py remain, as re-export facades. Every make target, every script under tools/, and any of the author's own one-off scripts keep working unchanged. make check-package fails if a facade stops exporting something its consumers import.

New code should import from mohokit directly. The facades exist because breaking a working checkout to tidy an import path is a bad trade — not because the indirection earns its keep.

pyproject.toml is provided for installing the package (and defines the five console scripts), but nothing requires installation: the package sits at the repository root, so import mohokit works from a checkout as-is.

3. How the refactor was verified

This repository has no unit tests; correctness is defined empirically. So the split was done as a verbatim move — source slices relocated, not rewritten — and checked three ways:

  1. Byte-identical output. Five documents exported to SVG and to Lottie before the refactor; after every step, re-exported and compared byte for byte. All ten matched at every checkpoint. The two importers mint a fresh UUID per layer, so their comparison masks UUIDs first — verified that two runs of unmodified code agree exactly once masked.
  2. Source-text comparison. Every top-level definition's source in the new package was compared against its source in git HEAD. Result: nothing missing, and exactly four deliberate differences — two function-level imports that break a cycle, one path re-anchoring, and one type annotation quoted.
  3. The existing gates. All twelve editing gates plus check-integrity, check-roundtrip and check-lottie pass.

make check-package is new, and guards the structure itself.

4. What the split found

Restructuring surfaced things reading would not have.

4.1 Three real import cycles

Cycle Cause Resolution
curvepathtrace both needed the other's helpers pure Bezier maths extracted to bezier.py
skindocument.skeleton _wrap_angle/KeyedWorldState sat in the skinning section but are used only by Skeleton moved to document/skeleton.py
render.shapesrender.exporter Exporter used for one type annotation typing.TYPE_CHECKING

A fourth, edit.fieldsedit.schema, was avoided by giving every exception type its own dependency-free module, edit/errors.py.

4.2 The two importers had drifted apart

svg2moho.py and lottie2moho.py shared 20 symbol names. Comparing the code (ignoring docstrings):

  • 13 are safely shareable and now live once, in mohokit/build/common.py.
  • 2 look identical but are not shareable: fit_curve_point and pixel_to_moho each call something that genuinely differs (forward_blend_direction, V2). Sharing them would have silently bound one importer to the other's behaviour.
  • 5 differ outright: V2, WarningCounter, build_document, forward_blend_direction, main.

That middle category is the point worth remembering: identical code is only shareable when everything it depends on is shareable too. It was found by an import cycle, not by reading, and a naive deduplication would have changed one importer's output without any test noticing — neither importer has a regression gate of its own.

Whether the five real divergences are intentional is not settled here. Someone who knows both importers should decide; nothing in this refactor establishes which version is right.

4.3 Path anchors that broke silently

_REPO_ROOT and SCHEMA_DIR were computed by counting dirname() levels from a file's own location. Moving those files made both wrong, and the failure mode was an empty corpus walk rather than an error — a check that passed while walking zero documents. mohokit/paths.py now owns these locations so no other module counts levels.

5. Deliberately not done

  • Splitting Exporter (2,252 lines) and LottieExporter (2,341). Each is one cohesive class. Breaking one apart means extracting collaborator objects and rewriting call sites — a design change, not a move, in the most empirically-calibrated code in the repository. The natural seams are visible (Exporter has a 7-method brush cluster and a 6-method mask cluster), so this is a well-scoped follow-up; it just should not ride along with a move-only refactor.
  • document/skeleton.py (1,710 lines). Skeleton.world_matrices is calibrated against Moho's own renders and CLAUDE.md warns against touching it without new reference evidence. Left whole.
  • Rewriting anything for style. Correctness here rests on byte-comparison against Moho's own output; "improving" code that no test pins down is how such a codebase silently breaks.
  • Migrating tools/ to import mohokit directly. The facades make it unnecessary, and 25 edits with no offsetting benefit is churn.