| name | ara3d-authoring |
| description | Author Ara 3D Studio tools — generators (make geometry) and modifiers (transform geometry) — as standalone C# scripts. Use when the user says "write a modifier", "create a generator", "make an Ara 3D tool", "new Studio script", asks how modifiers/generators/Eval/parameters/EvalContext work, or wants to add a mesh operation, deformer, analyzer, or heat-map to Ara 3D Studio. |
Authoring Ara 3D Studio tools (generators & modifiers)
Ara 3D Studio users extend the app with standalone C# scripts. Drop a .cs file into the
Scripts folder; Studio compiles it on save and surfaces every tool it finds in the UI with an
auto-generated parameter panel. No project, no build step, no restart.
Two roles cover almost everything:
- Generator — makes new geometry from parameters. Implements
IGenerator, has an Eval()
that takes no geometry input.
- Modifier — transforms geometry flowing through the graph. Implements
IModifier, has an
Eval(input) that takes an upstream geometry object.
Everything below is the contract the host actually enforces (by reflection), followed by the
attributes that shape the UI, the valid input/output types, deployment, and two worked examples.
Paths below are relative to the ara3d-sdk repo root (where this skill lives). A few cited
files live in the Ara 3D Studio host app repo, not the SDK — those are marked (host repo).
1. The runtime contract (what the host requires)
The host wraps your object in EvaluatorWrapper and calls it by reflection. The rules:
- Marker interface. Implement
IGenerator or IModifier (namespace Ara3D.Studio.API).
Both are empty marker interfaces — they only tag the class for discovery.
- A public method named exactly
Eval. The wrapper does type.GetMethod("Eval"). The name,
casing, and public visibility all matter. Exactly one Eval per class.
Eval arguments define the input:
- Generator:
Eval() — zero geometry args. May take a single EvalContext.
- Modifier:
Eval(TInput) — the first parameter is the geometry it consumes. The host
converts the upstream graph object to TInput (see §4).
- Optional
EvalContext may be added as an extra parameter (generator: the only arg;
modifier: the second arg). Use it for RefreshUI, animation time, and host access.
- Return type = output. Whatever
Eval returns becomes the object flowing downstream and
gets rendered. Must be a renderable/convertible geometry type (see §4).
- Parameters = public fields or properties. Every public read/write field or property becomes
an editable control in the panel. Read-only properties (
{ get; private set; }) become
non-editable read-outs.
Minimal shapes:
public class MyGenerator : IGenerator
{
[Range(1, 10)] public int Count = 3;
public TriangleMesh3D Eval() => ;
}
public class MyModifier : IModifier
{
[Range(0f, 1f)] public float Amount = 0.5f;
public TriangleMesh3D Eval(TriangleMesh3D mesh) => ;
}
2. Parameters and UI attributes
Parameters are plain public fields/properties. Fields and properties both work — pick the house
style of the surrounding file. Attributes refine how they render:
| Attribute | Applies to | Effect | Source namespace |
|---|
[Range(min, max)] | numeric field/prop | slider with bounds | System.ComponentModel.DataAnnotations |
[Options(nameof(Factory))] | int field/prop | dropdown; Factory is a method/prop returning IEnumerable<string>, selected value is the index | Ara3D.PropKit |
[Category(nameof(Categories.X))] | the class | groups the tool under a named category in the UI | System.ComponentModel (+ your Categories enum) |
[OnDemand] | the class | do not re-evaluate on every parameter change; run only when explicitly requested | Ara3D.Studio.API |
[Animated] | the class | tick every frame; read ctx.AnimationTime (or implement IAnimated) | Ara3D.Studio.API |
[PointerTracking] | the class | re-evaluate while the pointer or camera moves; read ctx.Services.ViewportInput (null on headless hosts) | Ara3D.Studio.API |
Notes:
- Default (no
[OnDemand]) = live: changing any parameter re-runs Eval immediately.
Categories is an ordinary enum you define/extend per script set. The examples project's is
examples/Ara3D.Studio.Examples/Categories.cs (Meshes, Curves, Sdf, Topology, …).
- Supported parameter types include the numeric primitives,
bool, string, and SDK vector types
like Vector3 / Vector2 (see Transform, Box in the examples). A bool renders as a
checkbox; a Vector3 renders as three fields.
Read-out values (computing stats back to the panel)
Expose computed results as read-only properties and push them to the panel from inside Eval:
public int VertexCountBefore { get; private set; }
public int VertexCountAfter { get; private set; }
public TriangleMesh3D Eval(TriangleMesh3D mesh, EvalContext ctx)
{
var r = mesh.WeldVertices();
VertexCountBefore = mesh.Points.Count;
VertexCountAfter = r.Points.Count;
ctx.Services.RefreshUI(this);
return r;
}
This is the whole WeldVertices example (examples/Ara3D.Studio.Examples/Modifiers/WeldVertices.cs).
3. Optional EvalContext
EvalContext (src/Ara3D.Studio.API/EvalContext.cs) carries:
Services — the IEvalServices host surface (RefreshUI(this), ViewportInput,
DerivedDataCache).
AnimationTime — seconds, for [Animated] tools.
Input — the raw upstream FlowObject (attachments, presentation) if you need more than the
converted geometry — e.g. BIM metadata via Input.GetAttachment<T>().
Services.ViewportInput — pointer state for in-canvas widgets (IViewportInput: normalized
CursorUV, world-space CursorRay, HoverObjectId, PrimaryDown, ConsumePrimaryClick()).
Null on hosts without a viewport, so always keep a fallback. Pair with [PointerTracking] so
the host re-evaluates the node as the pointer moves. A "click" is press+release without
dragging — drags orbit the camera. Worked example: examples/.../Demos/GridSnapPickDemo.cs.
Add it only when you need it. Signatures the host accepts:
QuadMesh3D Eval();
QuadMesh3D Eval(EvalContext ctx);
TriangleMesh3D Eval(TriangleMesh3D mesh);
TriangleMesh3D Eval(TriangleMesh3D mesh, EvalContext ctx);
4. Valid input and output types
Input (modifier first parameter). The host converts the upstream object to your parameter type
via FlowObjectConverters (src/Ara3D.Studio.API/FlowObjectConverters.cs). The two most flexible
choices:
IModel3D — accepts a TriangleMesh3D, ColoredTriangleMesh3D, QuadMesh3D, QuadGrid3D,
Model3D, or RenderModelData upstream. Use this for model-level work (instances,
materials, whole assemblies).
TriangleMesh3D — accepts QuadMesh3D, QuadGrid3D, ColoredTriangleMesh3D, or an IModel3D
(flattened). Use this for single-mesh algorithms.
Any other exact type (e.g. LineMesh3D) is accepted only when the upstream object already is
that type — no conversion is defined, so the graph must actually produce it.
Output (return type). Return a geometry type the host can render or pass on:
TriangleMesh3D, QuadMesh3D, QuadGrid3D — meshes/surfaces.
ColoredTriangleMesh3D — per-vertex colored mesh; this is the analyzer / heat-map pattern
(compute a scalar per vertex/face, map to color). See Modifiers/ColorSharpEdges.cs.
IModel3D / Model3D — instanced models with materials; build with Model3DBuilder.
LineMesh3D — line overlays (boundaries, arrows, diagrams).
Model-level ops iterate model.Instances, remap meshes through a Model3DBuilder, and prefilter
by model.GetInstanceBounds() — see the ModelHorizontalSlice half of the PlaneCut example.
5. Two worked examples (read these first)
Generator — BoxFrame
examples/Ara3D.Studio.Examples/Generators/MeshGenerators.cs
public class BoxFrame : IGenerator
{
[Range(0f, 10f)] public float SizeX = 1;
[Range(0f, 10f)] public float SizeY = 1;
[Range(0f, 10f)] public float SizeZ = 1;
[Range(0f, 0.5f)] public float FrameRatio = 0.1f;
public bool EmptyTop = true;
public bool EmptyBottom = true;
public bool EmptySides = true;
public bool ConnectLegs = true;
public QuadMesh3D Eval()
=> new BoxFrameMeshBuilder(FrameRatio, EmptyTop, EmptyBottom, EmptySides, ConnectLegs)
.Mesh.Scale((SizeX, SizeY, SizeZ));
}
Shows: marker interface, [Range] sliders, bool checkboxes, Eval() with no input returning a
mesh. The whole tool is one method building geometry from parameters.
Modifier — MeshHorizontalSlice
examples/Ara3D.Studio.Examples/Modifiers/PlaneCut.cs
[Category(nameof(Categories.Meshes))]
public class MeshHorizontalSlice : IModifier
{
[Range(0f, 1f)] public float Height { get; set; } = 0.5f;
public TriangleMesh3D Eval(TriangleMesh3D mesh)
{
var bounds = mesh.Bounds;
if (Height == 0) return ([], []);
var z = bounds.Min.Z.Lerp(bounds.Max.Z, Height);
var plane = new Plane(Vector3.UnitZ, -z);
return mesh.ClipBelow(plane);
}
}
Shows: [Category] grouping, a single [Range] parameter, Eval(TriangleMesh3D) returning a
mesh, and reuse of an existing library op (ClipBelow). The same file's ModelHorizontalSlice is
the IModel3D (instance-level) counterpart — read it to learn the Model3DBuilder pattern.
More examples worth copying
Modifiers/WeldVertices.cs — read-outs + ctx.Application.RefreshUI(this).
Modifiers/RefineAndCoarsen.cs — several modifiers + a helper class in one file; the
ColorByTriangleQuality class is the heat-map/analyzer pattern (ColoredTriangleMesh3D).
Modifiers/FaceNormalArrows.cs — building an IModel3D overlay with instanced arrow meshes.
- Studio's built-in coding prompt (host repo:
src/Ara3D.Studio/Ara3DStudio/codingprompt.txt)
contains a large gallery of small generators and deformers (Cylinder, Torus, Twist,
Noise, GridClone, …) — the canonical "cheat sheet" of idioms.
6. Reusable idioms (don't reinvent)
mesh.Bounds, new Topology(mesh), new MeshAttributes(mesh, topo) — topology/adjacency, normals.
mesh.WeldVertices(), mesh.Deform(p => ...), mesh.WithPoints(...), mesh.Scale(v),
mesh.Triangulate() — common mesh transforms.
new Model3DBuilder() + AddInstance / AddMeshWithoutInstance / AddModel — assemble models.
mesh.Clone(material, transforms) — instance a mesh across many transforms.
- Return
ColoredTriangleMesh3D for analyzers; map a per-element scalar to color yourself
(see ColorByTriangleQuality).
- The SDK ships list-preserving LINQ (
Select, Map, Slice, Zip, …) that returns
IReadOnlyList with O(1) count/indexing — prefer it over IEnumerable where it exists.
- Selection sets (
Ara3D.Studio.API.SelectionSet, examples Selection/ folder):
a selection-creator must return FlowObject (e.g. via the examples'
ctx.SelectFaces(mesh, combine, predicate) helper) — returning a bare mesh loses the
selection attribute at the pipeline boundary. A selection-consumer reads
ctx.GetFaceSelection(mesh); treat null as "whole mesh" so the tool still works
without a selection. Topology-changing modifiers drop selections automatically
(WithNewContent default); pass an AttributeDomainMask if indexing was preserved.
Pure index-list ops: mesh.ExtractFaces/DeleteFaces/SplitByFaces (MeshSelectionOps).
7. Deploying a script
- Scripts folder. Default
Documents/Ara 3D/Scripts (override with the SCRIPTS_PATH
environment variable). In Studio: menu Compiler → Open Scripts Folder.
- Drop the
.cs file in that folder (subfolders are watched too). A DirectoryWatcher
recompiles automatically on every save; the tool appears/updates live.
- No
using directives needed. A GlobalUsings.cs in the Scripts folder pre-imports the
common namespaces (Ara3D.Geometry, Ara3D.Models, Ara3D.Studio.API, Ara3D.PropKit,
System.Linq, System.ComponentModel.DataAnnotations, …). Canonical list:
examples/Ara3D.Studio.Examples/GlobalUsings.cs. Write scripts with no namespace and no
usings, just the class(es) — matching codingprompt.txt.
- Extra dependencies. Put additional referenced DLLs in the Scripts folder's
Libraries
subfolder (or list paths in refs.txt); they are loaded at compile time.
- Compile errors surface in Studio's compilation panel; the offending tool just won't appear.
8. Other component roles (when a modifier/generator isn't the right shape)
From src/Ara3D.Studio.API/Interfaces.cs:
IScriptedCommand / IModelCommand — one-shot actions (Execute/CanExecute), the right
home for reports and audits that don't return geometry (e.g. a mesh-statistics dump).
ITool — modeless UI tool.
IExporter / ILoader — custom file write/read.
IAsset / IAssetSource — data that enters the graph (with optional attachments like BIM data).
There is no IAnalyzer interface: an analyzer is just a modifier returning
ColoredTriangleMesh3D.
9. Authoring checklist
Maintenance (keep this skill true to the code)
This skill documents a reflection-based contract. If any of these change, update the matching
section here. Files marked (host repo) live in the Ara 3D Studio application repository, not this
SDK.
| Source of truth | Governs |
|---|
src/Ara3D.Studio.API/Interfaces.cs | the marker interfaces / component roles (§1, §8) |
src/Ara3D.Studio.WpfControls/EvaluatorWrapper.cs (host repo) | how Eval is found + args bound; RefreshUI (§1, §2, §3) |
src/Ara3D.Studio.API/FlowObjectConverters.cs | valid input/output conversions (§4) |
src/Ara3D.Studio.API/EvalContext.cs | what EvalContext exposes (§3) |
src/Ara3D.Studio.API/Attributes.cs, src/Ara3D.PropKit/OptionsAttribute.cs | [OnDemand]/[Animated]/[Options] (§2) |
Services/CompilerService.cs, Services/FolderService.cs (host repo) | Scripts folder, watching, references (§7) |
examples/Ara3D.Studio.Examples/ | the example files cited in §5 |