Skip to content

File format

Plain JSON, version 3. Readable, diffable, and simple enough to write from another tool.

The envelope

{
  "format": "am_landmark",
  "version": 3,
  "created": "2026-08-18T14:22:07",
  "scene": "D:/show/asset/head.ma",
  "landmarks": [ … ]
}
Field Meaning
format Always am_landmark.
version 3 for files written by this build.
created Local time the file was written, ISO 8601, seconds resolution.
scene The scene it was saved from, if it had been saved. Informational.
landmarks The list, in the order they were written.

A landmark

{
  "name": "jaw_line",
  "type": "surfaceShader",
  "color": [0.9, 0.35, 0.2],
  "group": "head",
  "notes": "under the ear, both sides",
  "id": "9f2c1a7e4b8d4a1e9c3f5b2d7a6e0c14",
  "shadingGroup": "jaw_lineSG",
  "attributes": { "outTransparency": { "type": "float3", "value": [0, 0, 0] } },
  "assignments": { "|char|headShape": [[0, 128], [204, 211]] },
  "samples": { "|char|headShape": { … } }
}
Field Type Meaning
name string The shader node name.
type string The Maya node type to create. Unknown types fall back to lambert.
color [r, g, b] Display-space floats, 0..1, rounded to six places.
group string The group label. "" when unset.
notes string Free text.
id string A stable id, so a landmark can be recognised after renaming.
shadingGroup string The name the shading group had. Informational — the loader makes its own.
attributes object Curated look-dev extras; see below.
assignments object Mesh name → face ranges.
samples object Optional; mesh name → UV and/or position samples.

assignments

"assignments": {
  "|char|headShape": [[0, 128], [204, 211]],
  "|char|bodyShape": null
}

Keys are the mesh names as they were in the source scene — full DAG paths where Maya gave one.

Values are inclusive face-id ranges: [[0, 128]] means faces 0 through 128, 129 faces in total. null means the whole mesh — an object-level assignment rather than a per-face one.

samples

Optional. Written when Store UV samples or Store position samples was on.

"samples": {
  "|char|headShape": {
    "uvSet": "map1",
    "uv": [[0.512, 0.771], [0.518, 0.774], …],
    "space": "Object",
    "point": [[1.02, 14.9, 3.51], [1.06, 14.87, 3.48], …]
  }
}
Field Meaning
uvSet The UV set the UVs were read from.
uv One [u, v] per assigned face, in ascending face-id order. null for a face with no UVs. Rounded to six places.
space Object or World, for the positions.
point One [x, y, z] per assigned face, same order. Rounded to five places.

The ordering is the contract: sample n belongs to the n-th face id in the sorted expansion of that mesh's ranges.

attributes

"attributes": {
  "specularRoughness": { "type": "double", "value": 0.35 },
  "transparency":      { "type": "float3", "value": [0.1, 0.1, 0.1] }
}

Only a short whitelist per node type is written, and only plugs that are not connected — a colour driven by a texture is skipped rather than flattened. Types are Maya's own attribute types; double3 and float3 carry a three-number list.

Reading one from somewhere else

The minimum a file needs to be loadable is a name and an assignment:

{
  "format": "am_landmark",
  "version": 3,
  "landmarks": [
    { "name": "jaw", "color": [1, 0, 0],
      "assignments": { "headShape": [[0, 5]] } }
  ]
}

Missing fields take sensible defaults: type becomes surfaceShader, group and notes become empty, id is generated on creation.

Older versions

Every version of this format loads without conversion, so a file you saved a long time ago still opens.

Version How it differs
3 The current one. Adds samples, and with them the UV, Position and Colour Set match methods.
2 No samples, so such a file can only be loaded by Face Index. Everything else is the same.
1 {"version": 1, "materials": {...}} — a dict of materials keyed by name, each holding every settable attribute and the raw cmds.sets member strings.

A v1 file's member strings are parsed back into ranges on load, and its colour is recovered from whichever of color, baseColor, outColor or diffuseColor it happened to record.

Files are always written at the current version. There is no "save as v2".

From Python

from Am_landmark.core import landmark_io

document = landmark_io.read_file("d:/head.json")
entries = landmark_io.parse_document(document)     # v1, v2 or v3
for entry in entries:
    print(entry["shader"], entry["color"], sorted(entry["assignments"]))

landmark_io.write_file("d:/copy.json", landmark_io.build_document(entries))

parse_document normalises every version into the same entry shape, so a script that reads landmark files never has to know which version it was handed.