This skill is the source of truth for writing Python against the Asset Transformer SDK (pxz). It serves two audiences:
- The local Asset Transformer MCP
run_pythontool — the primary target. Code runs in-process inside the running Pixyz session. - Other skills — notably
at-upa-scripting, which layers UPA-container conventions (argparse, workspace paths, no-Asset-Manager-write) on top of this same API. Both defer topxz-api-reference.mdin this folder for the API surface.
The API surface is documented in pxz-api-reference.md next to this file. It is authoritative: if a function, class, parameter, or constant is not listed there, it does not exist in this SDK version — do not call it. See Accuracy guardrails.
Execution context — the in-process run_python tool
The Asset Transformer MCP runs code like this:
exec(compile(code, "<mcp_run_python>", "exec"), {"pxz": pxz})
So when you write a script for run_python:
pxzis already in scope as a global. You do notimport pxz. (You may stillfrom pxz import scene, algo, io, ...for brevity, or just usepxz.scene.*— the module attributes are all reachable off the global.)- The SDK is already initialized and licensed. Never call
pxz.initialize(),core.configureLicenseServer,core.checkLicense,core.needToken, etc. If init is broken, the server is misconfigured — not your script. - The scene is stateful and persists across calls. Each
run_python(and every other AT MCP tool) operates on the same live session. A model you imported in one call is still there in the next. Do not assume an empty scene — callpxz.scene.getRoot()/getChildren(root)to see what's loaded, andclear_scene(or delete children of root) if you need a clean slate. - Only
pxzis guaranteed in the globals. The standard library is importable (import math,os,json, …), but there is no argparse-style input channel — inputs are literals in the code you write, or values you read from the live scene. - stdout is captured and returned.
print(...)is how you surface results (counts, ids, paths). Print the things the caller needs to see.
run_python vs. the UPA custom-script action
run_python (this skill's default) |
UPA "Execute custom script" (see at-upa-scripting) |
|
|---|---|---|
pxz availability |
global, pre-initialized | imported (import pxz), pre-initialized by runtime |
| Inputs | literals / live scene | argparse over sys.argv |
| Scene lifetime | stateful across tool calls | fresh per job |
| Entry point | bare statements run top-to-bottom | main() + if __name__ == "__main__" guard |
| File paths | wherever you point them | /workspace/input, /workspace/output |
| Write to Asset Manager | not this tool's job (chain an AM tool/action) | not possible (chain an AM action) |
When a user's script is clearly destined for a pipeline, switch to the at-upa-scripting conventions. When it's an interactive/one-off transformation against the live MCP session, use the in-process style below.
No Asset Transformer license?
Local run_python (and every other AT MCP tool) consumes a Pixyz license seat. If check_license fails or no license server is configured, do not stop — fall back to remote execution:
- Author the script locally using this skill's API reference as usual.
- Syntax-check it locally with a plain Python compile (
compile(code, "<script>", "exec")orpython -m py_compile) — no pxz or license needed. - Execute it remotely via a Pipeline Automation "Execute custom script" step. Follow the
at-upa-scriptingconventions for the script body (argparse,/workspacepaths,main()guard) andpipeline-authoringfor the surrounding pipeline JSON.
Tell the user clearly about the trade-off: remote execution works without a seat, but local execution and fast local iteration require a license. To unlock full local capabilities they should contact their Unity account representative or sales: https://unity.com/contact-us?reason=Speak+to+Sales&step=3&topic=Industrial+Solutions
The integer-handle model (read this once)
pxz has no object model. Every entity — occurrence, part, mesh, material, image, viewer, baking session, and every CAD BRep entity (body, face, edge, curve, surface) — is an opaque int id. There is no scene.Node, scene.Material, or cad.Body object.
- Get the root:
root = pxz.scene.getRoot(). - Most scene/algo functions take a list of occurrences, even for one:
pxz.algo.tessellate([root], ...). - Real names use verbs on the module:
scene.getOccurrenceName(occ),scene.deleteOccurrences([occ]),scene.getChildren(occ). There is noscene.getName,scene.deleteNodes,node.name. io.exportScene(fileName, root=0)infers format from the file extension — there is noformat=/binaryGLTF=/embedTextures=keyword. Per-format tuning goes throughcore.setModuleProperty("io", "<key>", "<value>")before the export call.
Script structure (in-process)
For run_python, prefer small, flat, top-to-bottom scripts that print what they did. You do not need argparse or a main() guard (though defining helper functions is fine and encouraged for anything non-trivial):
# pxz is already global and initialized — no import, no initialize().
root = pxz.scene.getRoot()
# 1. Import a model into the live scene.
model = pxz.io.importScene(r"C:\models\bracket.step")
# 2. Prepare: repair + tessellate relative to the model's size.
pxz.algo.repairCAD([model], 0.1, True)
pxz.algo.tessellateRelativelyToAABB([model], maxSag=-1, sagRatio=0.005,
maxLength=-1, maxAngle=20.0)
pxz.algo.repairMesh([model], 0.01, True, True)
# 3. Report and export.
tris = pxz.scene.getPolygonCount([model], asTriangleCount=True)
print(f"triangles after prep: {tris}")
pxz.io.exportScene(r"C:\out\bracket.glb", model)
Wrap genuinely multi-step, state-changing work in an undo/redo step so the session stays clean and reversible (the built-in AT MCP tools do this):
pxz.core.startUndoRedoStep(stepName="run_python: decimate")
try:
pxz.algo.decimateTarget([root], ["ratio", 0.5])
finally:
pxz.core.endUndoRedoStep()
Common workflows
Prefer adapting a vetted examples/ script over generating from scratch. Current examples:
import-prepare-export.py— in-process import → repair → tessellate (relative to AABB) → repair mesh → export GLB, with triangle-count reporting.inspect-scene.py— walk the live stateful scene, report per-occurrence names / triangle counts / AABB without mutating anything (safe to run first when you don't know what's loaded).
High-level building blocks (all detailed in pxz-api-reference.md):
- Import / export:
io.importScene,io.importFiles,io.exportScene(extension-driven),io.getImportFormats/io.getExportFormats. - CAD prep:
algo.repairCAD,algo.assembleCAD,algo.tessellate/algo.tessellateRelativelyToAABB,algo.repairMesh. - Optimize:
algo.decimate,algo.decimateTarget(occ, ["ratio", r])/(occ, ["polygonCount", n]),algo.removeHoles,algo.deletePatches,scene.mergePartOccurrences*,scene.convertSimilarPartOccurrencesToInstances*(viaalgo),algo.removeOccludedGeometries. - UV:
algo.automaticUVMapping,algo.unwrapUV,algo.repackUV,algo.mapUvOn*. - Baking:
algo.combineMaterials(occ, BakeOption(...))for the high-level AO-into-materials path;algo.beginBakingSession+algo.bake*Map+algo.endBakingSessionfor explicit control. - Query / edit:
scene.getChildren,scene.getPolygonCount,scene.getAABB,scene.findOccurrencesByProperty,scene.getFilteredOccurrences,scene.deleteOccurrences,scene.hide/show. - Visuals:
view.takeScreenshot(offscreen viewer);raytrace.renderImageonly when ray-traced quality is required.
CAD pipeline best practices
- Preserve assembly hierarchies when downstream consumers rely on them.
- Strip construction geometry, hidden nodes, and authoring-only metadata before export.
- Tune tessellation to the target use case — real-time viewers tolerate coarser settings than engineering review. Prefer
tessellateRelativelyToAABB(asagRatioscales with model size) over a fixedmaxSagwhen you don't know the model's scale. - Instance repeated components (
convertSimilarPartOccurrencesToInstances*) rather than duplicating mesh data. - Keep occurrence names stable across runs so downstream steps can address them.
Performance best practices
For large assemblies, prefer approaches that:
- Reduce polygon count during tessellation rather than after.
- Merge or instance repeated components.
- Remove unused nodes/occlusions early (cheaper than carrying them through every stage).
- Avoid repeated traversal of large hierarchies — gather what you need in one pass.
- Limit memory-heavy operations (full-scene bakes, redundant copies).
Because the scene is stateful in run_python, also: clean up after yourself (delete scratch occurrences, core.endUndoRedoStep) so later tool calls start from a predictable state.
When a chosen approach materially affects performance, add a one-line comment explaining why.
Code style
- Python only. Clear variable names over clever ones.
- In
run_pythonscripts, do not re-import or re-initializepxz. - Comments explain non-obvious SDK behavior, not what the code literally does.
- Prefer complete, runnable snippets over pseudocode unless the user explicitly asks for high-level design.
- When modifying a user's existing script, improve it in place — preserve their structure and naming. Don't rewrite a working script just to restyle it.
Accuracy guardrails
The full pxz API surface is documented in pxz-api-reference.md next to this file. If a function or class is not listed there, it does not exist in this SDK version. Do not generate code that calls it — verify first, or tell the user the SDK doesn't support what they're asking for.
Specifically, do not invent:
- Classes, functions, parameters, or constants (the reference is authoritative).
- Object-style APIs like
scene.Node,scene.NodeType,scene.getName,scene.deleteNodes,cad.Bodyobjects — pxz uses integer handles, not objects. Real names:scene.getOccurrenceName,scene.deleteOccurrences, etc. - Keyword arguments on
io.exportScene— the real signature isexportScene(fileName: str, root: int = 0). Format is inferred from extension; per-format options are set viacore.setModuleProperty("io", ...)before the export call.
If something is uncertain or undocumented:
- Say so explicitly.
- Suggest the closest supported approach from the reference.
- If the SDK genuinely doesn't support what the user is asking for, say that and describe the closest workflow.
Version. The reference is verified against the installed 2026.4.0.0 stubs — the exact surface run_python executes against. Prefer functions confirmed in pxz-api-reference.md; when in doubt, probe the live SDK (print([n for n in dir(pxz.algo) if "decimate" in n.lower()])) before relying on a call.
Ground-truth source for the reference: the *.pyi type stubs shipped inside the installed pxz package. Re-extract the reference if the SDK is upgraded.
Debugging checklist
When a script fails or behaves incorrectly, work through these in order:
- Scene state — the session is stateful. Is the occurrence id you're using still valid? Did a prior call already delete/transform it? Print
scene.getChildren(scene.getRoot())to see what's actually loaded. - File paths — is the input file present at the path you passed? For export, does the parent directory exist and is the extension a supported export format (
io.getExportFormats)? - SDK call shape — wrong parameter name or type is the most common silent failure. Occurrence args are
list[int], notint. Check the signature inpxz-api-reference.md. - Tessellation / prep settings — wrong settings cause wildly wrong polygon counts or import errors. Use
tessellateRelativelyToAABBwhen scale is unknown. - Export format — the destination format must support what's in the scene (instancing, materials, animation).
Identify the likely cause, explain why it fails, describe how to verify, then provide the corrected snippet.
Out of scope
- The UPA "Execute custom script" container conventions (argparse,
/workspacepaths, no-AM-write,main()guard) → seeat-upa-scripting, which uses this reference for the API surface. - Pipeline JSON, action IDs, picker metadata, fan-out syntax → see
pipeline-authoring. - Writing back to Asset Manager → chain an Asset Manager MCP tool / pipeline action; the AT scripting layer does not push to AM.