Class reference
Element
An Element is one typed record in the Document
An Element is a node in the Document's tree. It has four things: a Path that names it and places it in the hierarchy, a type token (a bare string like "group", "object", "material"), a map of properties (the numeric, animatable state), and a map of string metadata (the non-numeric, non-animatable side channel). Children are implicit: a child's Path lives under its parent's.
The split between properties and metadata is the same one USD draws between attributes and metadata. Properties hold everything that can vary over time and flow through connections — radius, albedo, a transform. Metadata holds the rest — asset references, free-form labels, a text node's body — kept separate so the animatable Value union stays purely numeric.
def object "ball" {
float radius = 1.5 # a property
float3 albedo = (0.9, 0.2, 0.2)
string label = "hero ball" # metadata
}
In C++ you hold an Element directly. In Python you almost never touch the Element view — you reach an element by Path through a chainable Handle and read or write it there. The two map one-to-one.
Element ball(Path("/world/ball"), Element::Object);
ball.set("radius", 1.5f).set("albedo", Float3(0.9, 0.2, 0.2));ball = doc.append("/world/ball", "object") # a Handle
ball.set("radius", 1.5).set("albedo", (0.9, 0.2, 0.2))Path and type
const Path& path() const / void setPath(Path)
The element's identity and place in the tree. The Path is the element's address; setting it moves the element.
Element e(Path("/world/ball"), Element::Object);
e.path().str(); // "/world/ball"
In Python the Handle carries the path; read it with .path. To move an element, call doc.rename.
h = doc.edit("/world/ball")
h.path # "/world/ball"
doc.rename("/world/ball", "/world/orb")
const std::string& type() const / void setType(std::string)
The type token: an open string, not an enum. Core names the two structural tokens it defines — Element::Group ("group") and Element::Object ("object") — so call sites don't carry bare strings. Domain libraries add their own (nodes:: in kinogakifoundation). Anything is a valid type.
e.setType(Element::Group);
e.type(); // "group"
The type is fixed at append time in Python; pass it to append. The Element view exposes it as .type.
doc.append("/world/rig", "group")
doc.element("/world/rig").type # "group"
There is no Handle method to change an element's type in place.
Properties — numeric, animatable state
Properties are the everyday surface. Each is a named Property: a default Value plus optional time samples. The map is ordered, so serialization is deterministic.
Element& set(const std::string& name, Value v)
Set a property's default value, chainable. Value's implicit constructors mean you pass a bare number, tuple, bool, or string. Existing animation stays intact — set writes the default the samples fall back to.
e.set("radius", 2.0f).set("visible", true);
In Python, Handle.set(key, value) infers the Prism type from the native value: bool → bool, int → int, float → float, str → string, and a 2/3/6/32-length number sequence → float2 / float3 / matrix / spectrum. Chainable.
doc.edit("/world/ball").set("radius", 2.0).set("visible", True)
def object "ball" {
float radius = 2.0
bool visible = true
}
Element& animate(name, {{time, Value}, ...}, Interp = Linear)
Author keyframes on a property, chainable. Each pair is a time and a Value; Interp is Linear (default), Held, or Bezier. See properties for how samples resolve.
e.animate("rotation", {{0, 0.0f}, {24, 6.2832f}});
Python's Handle.animate(key, samples, interp="linear") takes a {time: value} dict (or an iterable of (time, value) pairs); each value is a float or a 3-tuple. interp is "linear", "held", or "bezier".
doc.edit("/world/rig").animate("rotation", {0: 0.0, 24: 6.2832})
bool hasProperty(const std::string& name) const
True if the property exists.
if (e.hasProperty("radius")) { /* ... */ }
Python has no has_property; a permissive getter with a sentinel default tells you the same thing, or read the Property is absent when get_* returns the default.
void removeProperty(const std::string& name)
Delete a property and its samples.
e.removeProperty("radius");
Unbound in Python — there is no Handle method to remove a single property.
void setProperty(name, Property) / Property* property(name) / Property& ensureProperty(name)
Lower-level access to the Property object itself: setProperty replaces it wholesale, property returns a pointer (nullptr if absent), ensureProperty returns a reference, creating an empty Property if needed. Reach for these when you need the samples, not just the default. set and animate are built on ensureProperty.
e.ensureProperty("radius").setDefault(Float(1.5));
const Property* p = e.property("radius"); // nullptr if absent
const std::map<std::string, Property>& all = e.properties(); // ordered
These have no Python equivalent — Python works through native values, not Property objects. Handle.set / animate cover the same ground.
Typed reads — permissive
"Here's a float, give me a float." Each getter resolves the property at time t (default 0; static properties ignore it), coerces, and returns def if the property is missing or the type mismatches. They never throw.
float getFloat(name, float def = 0, double t = 0) const
int getInt(name, int def = 0, double t = 0) const
bool getBool(name, bool def = false, double t = 0) const
Vec2 getFloat2(name, Vec2 def = {}, double t = 0) const
Vec3 getFloat3(name, Vec3 def = {}, double t = 0) const
float r = e.getFloat("radius", 1.0f); // default 1.0 if absent/mistyped
Vec3 rgb = e.getFloat3("albedo");
float r12 = e.getFloat("radius", 1.0f, 12); // resolved at time 12
Python's Handle mirrors these as get_float, get_int, get_bool, get_float2, get_float3, plus get_str for string properties. Same signature shape: (key, default, time). A missing or mistyped property returns the default.
ball = doc.edit("/world/ball")
r = ball.get_float("radius", 1.0)
rgb = ball.get_float3("albedo")
name = ball.get_str("name", "untitled")
r12 = ball.get_float("radius", 1.0, time=12)
C++ reads strings as properties through requireProperty(...).resolve(t) and the Value API; the Python get_str is the convenience shortcut for a string-typed property.
Typed reads — strict
The require* siblings resolve the same way but throw std::invalid_argument — naming the element path and property — when the property is missing or the stored type is wrong. Use them at boundaries where an absent or mistyped value is a programmer error, not a recoverable absence.
const Property& requireProperty(name) const
float requireFloat(name, double t = 0) const
int requireInt(name, double t = 0) const
bool requireBool(name, double t = 0) const
Vec2 requireFloat2(name, double t = 0) const
Vec3 requireFloat3(name, double t = 0) const
Vec3 rgb = e.requireFloat3("albedo"); // throws "/world/ball: property 'albedo' is not a float3"
Python raises PrismError (located, loud) from require_float, require_float3, and require_str. The integer, bool, and float2 strict reads are unbound in Python — use the permissive get_* with a default, or require_float.
rgb = doc.edit("/world/ball").require_float3("albedo") # raises PrismError if absent/mistyped
Metadata — string, non-animatable
The side channel: a name → string map, ordered for deterministic output. Asset paths, labels, a text node's body — anything that isn't numeric and doesn't animate.
void setMetadata(const std::string& key, std::string value)
Write a metadata string.
e.setMetadata("label", "hero ball");
Python: Handle.set_meta(key, value), chainable. The value is coerced to a string.
doc.edit("/world/ball").set_meta("label", "hero ball")
def object "ball" {
string label = "hero ball"
}
const std::string* metadata(const std::string& key) const
Read one key — a pointer, nullptr if absent.
const std::string* lbl = e.metadata("label"); // nullptr if absent
Python: Handle.get_meta(key, default=None) returns the string or the default.
doc.edit("/world/ball").get_meta("label", "unnamed")
bool hasMetadata(key) const / void removeMetadata(key) / const std::map<...>& metadata() const
Test for a key, delete a key, or read the whole ordered map.
if (e.hasMetadata("label")) e.removeMetadata("label");
for (const auto& [k, v] : e.metadata()) { /* ... */ }
has_metadata, remove_metadata, and the full-map read are unbound in Python — use get_meta with a sentinel default to test presence.
reference — the reserved metadata
A reference pulls another Document's subtree onto this element when the Document is composed. It is built on two reserved metadata keys: reference (the asset) and referencePath (an optional element inside it). Local properties override the referenced ones.
Element& reference(std::string asset, std::string elementPath = "")
Chainable sugar that sets reference (and referencePath if given).
e.reference("car.prisma"); // the whole document
e.reference("parts.prisma", "/parts/bolt"); // one element inside it
Python: Handle.reference(asset, element_path=""), chainable.
doc.edit("/world/car").reference("car.prisma")
doc.edit("/world/bolt").reference("parts.prisma", "/parts/bolt")
def object "car" (
reference = "car.prisma"
)
Reading the reference back is just metadata: e.metadata("reference") in C++, h.get_meta("reference") in Python. Composition resolves and flattens it — compose_file / Document.load_composed.
Children — named and anonymous
Hierarchy is implicit in the Path: a child's Path sits under its parent's. There is no child list on Element itself; children are other elements in the Document. You add them through the Document.
A named child carries a leaf name (/world/ball). An anonymous child is addressed by its index among siblings (/parent/[2]) — for ordered body content like a document's paragraphs, where position is the identity and a name like "0" would just be noise.
Python: Handle.append_child(type) / Handle.child(index)
append_child adds an anonymous child of this element and returns a Handle to it. child(index) returns a Handle to the index-th child in document order (named or anonymous), or None if out of range.
para = doc.append("/doc/body", "paragraph")
run = para.append_child("run").set("text", "Once upon a time")
first = doc.edit("/doc/body").child(0) # Handle, or None
def paragraph "body" {
def run {
string text = "Once upon a time"
}
}
In C++ you set the child's Path directly — construct an Element whose Path is under the parent and add it to the Document. The anonymous-child indexing and append_child / child convenience live on the Document/Handle layer, mirrored from the C ABI (kinogaki_document_add_child, kinogaki_document_child).
isOverride — the over layer flag
bool isOverride() const / void setOverride(bool)
Marks the element as an override (over in the text format) rather than a definition (def). Meaningful only inside a proposal or overlay Document: an over element patches a Path that must already exist in the base — overlay() skips it if the Path is absent, so a typo can't silently create an orphan. A def adds the element when it's absent. Always false in a normal base Document. See composition.
Element patch(Path("/world/ball"), Element::Object);
patch.setOverride(true); // patches, never creates
patch.set("radius", 3.0f);
over "ball" {
float radius = 3.0 # patches the base; errors out if /world/ball is absent
}
The override flag is unbound in the current Python binding — author overlays in .prisma text (over) and compose them.
Equality
bool operator==(const Element&) const
Defaulted member-wise equality: two Elements are equal when their path, type, override flag, properties, and metadata all match. (Python compares whole Documents — Document.__eq__ compares serialized bytes — not individual elements.)
With the element understood, the next pieces are how its properties carry time and how one element's value flows into another over connections.