PixelWeaver v0.5.0 /server.src.pixelweaver.part_export
On this page

Headless part-manifest export: the single source of truth that validates a project, builds the manifest, and renders deterministic per-part PNG strips.

#server.src.pixelweaver.part_export

#server.src.pixelweaver.part_export

Headless part-manifest export (G8) -- the single source of truth for the server/CLI export path.

Given a ProjectState (loaded from disk or held in memory), this module: - validates the part registry, part-sets, and per-part compositing preconditions (validate_part_export); - builds the PixelWeaver part-manifest document (build_part_manifest), spec-ordered and byte-deterministic; - renders deterministic per-part PNG strips in both role-indexed and resolved-RGBA forms (render_part_strip); - writes the whole export to a directory (export_parts_to_dir) or a deterministic zip (export_parts_zip_bytes).

Determinism (part-manifest spec section 2.5): identical project state yields a byte-identical part-manifest.json and byte-identical strip PNGs on repeated runs, and pixel-identical strips versus the TypeScript twin in src/lib/parts/part-export.ts. The TS manifest JSON is byte-identical to this module's; strip PIXELS are identical, though compressed bytes may differ across the browser CompressionStream (documented in the TS module).

#PartExportValidationError

Client-fixable validation problems (unregistered pairings, gaps in variant indices, mixed paint on a classed part, ...). HTTP handlers map this to 422. Plain ValueErrors (invariant violations) map to 500.

#_registered_parts

python
def _registered_parts(project: ProjectState) -> list[dict[str, Any]]

Registry entries sorted by z_order ascending (snake_case dicts).

#_variant_canvases

python
def _variant_canvases(project: ProjectState, part: dict[str, Any]) -> list[tuple[str, CanvasState]]

(name, canvas) pairs for a part's variants, sorted by variant_index.

#_part_for_canvas

python
def _part_for_canvas(project: ProjectState, canvas: CanvasState) -> dict[str, Any] | None

The registered part owning a canvas (by part_set + part_name).

#validate_part_registry_for_export

python
def validate_part_registry_for_export(project: ProjectState) -> list[str]

Port of the frontend validatePartRegistryForExport (same wording).

#_validate_frame_uniformity

python
def _validate_frame_uniformity(project: ProjectState) -> tuple[list[str], dict[str, int] | None]

Return (problems, frame_spec). Frame spec is None when problems exist.

All variant canvases of all registered parts must share width/height and origin (cross-set, not just within a set).

#_visible_pixel_layers

python
def _visible_pixel_layers(canvas: CanvasState) -> list[dict[str, Any]]

Effectively-visible pixel layers, bottom-to-top.

Server layers are a flat list; group nesting is honored if present (a layer is visible iff it and every ancestor group are visible).

#_layer_opacity_full

python
def _layer_opacity_full(layer: dict[str, Any]) -> bool

True if a layer is at full opacity (server scale is 0.0-1.0).

#_validate_compositing_preconditions

python
def _validate_compositing_preconditions(project: ProjectState) -> list[str]

Section 3.3 preconditions per part / variant canvas / layer.

#_canvas_layer_has_paint

python
def _canvas_layer_has_paint(canvas: CanvasState, layer: dict[str, Any]) -> bool

True if any frame of a canvas has a non-transparent pixel on this layer.

#validate_part_export

python
def validate_part_export(project: ProjectState) -> list[str]

All export problems: part-sets + registry + frame uniformity + compositing preconditions (empty list = valid).

#_round_half_up

python
def _round_half_up(value: float) -> int

Round-half-up to mirror JavaScript Math.round for non-negative inputs.

Python's built-in round uses banker's rounding (round-half-to-even), which diverges from JS Math.round (round-half-up) for exact half values such as 1000 / 16 == 62.5. The part-export determinism guarantee (spec 2.5) requires byte-identical manifests and pixel-identical strips across the Python and TS implementations, so both sides must use the same rounding rule. All export call sites operate on non-negative values (frame durations, composited channel/alpha values), so floor(x + 0.5) matches JS Math.round exactly.

#_part_pairings

python
def _part_pairings(project: ProjectState) -> list[dict[str, str]]

Deduplicated part-level pairings, ordered (partA < partB), sorted.

#build_part_manifest

python
def build_part_manifest(project: ProjectState) -> dict[str, Any]

Build the spec-ordered part-manifest dict. Validates first.

#manifest_json_bytes

python
def manifest_json_bytes(project: ProjectState) -> bytes

Deterministic part-manifest.json bytes (2-space indent, trailing \n).

Stamps the format_version gate: this is an at-rest/serialization boundary, and strictspec requires every governed document to carry it.

#_build_index_grid

python
def _build_index_grid(project: ProjectState, part: dict[str, Any]) -> tuple[bytearray, int, int, int, int]

Build the role-index grid for a classed part.

Returns (grid, strip_width, strip_height, cell_w, cell_h). Every pixel is a master-palette index or the 0xFF transparent sentinel.

#_render_rgba_part

python
def _render_rgba_part(project: ProjectState, part: dict[str, Any]) -> bytes

RGBA strip for a pure-rgba part: source-over composite of visible rgba pixel layers per cell (normal blend, opacity applied).

#_source_over

python
def _source_over(base: bytearray, src: bytes, opacity: float) -> None

In-place straight-alpha source-over of src onto base (both wh4).

#render_part_strip

python
def render_part_strip(project: ProjectState, part_name: str, kind: str) -> bytes

Render one strip PNG. Validates the whole export first (422 on problems).

kind is "indexed" or "rgba". Unknown part/kind and indexed-on-rgba-part raise ValueError (mapped to 422 by the caller).

#_strip_files

python
def _strip_files(project: ProjectState) -> list[tuple[str, bytes]]

(filename, png_bytes) for every strip, alphabetical by filename.

#export_parts_to_dir

python
def export_parts_to_dir(project: ProjectState, out_dir: Path) -> list[Path]

Write part-manifest.json + all strips into out_dir. Returns written paths.

Overwrites the files it owns; never deletes other files in out_dir.

#export_parts_zip_bytes

python
def export_parts_zip_bytes(project: ProjectState) -> bytes

Deterministic zip: part-manifest.json first, then strips alphabetically, fixed timestamps, ZIP_DEFLATED (part-manifest spec section 2.5).

Search