Geometry — ENCY CAM API
This document covers the full geometry domain of the ENCY CAM API: the model tree, entity inspection, B-rep traversal (Face / Loop / CoEdge / Surface / Curve), mesh access, coordinate systems, geometry import, interactive picking, and the geometry tree context-menu extension point.
All .NET examples use the ComWrapper<T> pattern and the extension methods from CAMAPI.DotnetHelper. Raw IDL access is shown as secondary "direct access" notes where it differs meaningfully.
Table of contents
- Concepts and object model
- ICAMAPIGeomLibrary — factory
- ICAMAPIGeometryModel — model root
- ICAMAPIGeometryTreeNode — tree navigation
- Traversing the geometry tree
- ICAMAPIGeometryEntity — entity inspection
- B-rep structure: Face, Loop, CoEdge
- ICamApiSurface — underlying surface
- ICamApiCurve and ICamApiCurve5D
- ICamApiMesh — triangulated representation
- ICamApiCoordinateSystem
- ICAMAPIGeometryImporter — importing files
- ICamApiGeomPicker — interactive picking
- IExtensionGeomModelNodePopup — context menu
- ICMAPITurnGeneratrixExtractor — lathe generatrix
- Export helpers
- Entity type reference
- ICamApiGeometryModelSketcher — creating primitives
- ICamApiPointSnapper — snapping points to faces
1. Concepts and object model
The geometry domain is built around a tree of named nodes. Each node (ICAMAPIGeometryTreeNode) carries one ICAMAPIGeometryEntity payload that describes what the node represents (group, face, curve, coordinate system, mesh, etc.). The tree is rooted at ICAMAPIGeometryModel.RootNode.
ICAMAPIGeomLibrary — factory: creates models, importers, extractors
└─ ICAMAPIGeometryModel — the whole geometry model (owns the tree)
├─ ICAMAPIGeometryTreeNode — one node in the tree
│ └─ ICAMAPIGeometryEntity — the actual geometry payload
│ ├─ ICamApiFaceGeometryEntity → ICamApiFace
│ │ ├─ ICamApiLoop → ICamApiCoEdge → ICamApiSurfaceCurve
│ │ └─ ICamApiSurface (NURBS form available)
│ ├─ ICamApiCurveGeometryEntity → ICamApiCurve / ICamApiCurve5D
│ └─ ICamApiCSGeometryEntity (4×4 matrix)
└─ ICAMAPIGeometryTreeNodeIterator — depth-first tree walker
The library (ICAMAPIGeomLibrary) is accessed from the active project via ICamApiProject.CAMAPIGeomModel (model) and ICamApiProject.GeomImporter (importer), or created standalone through the library factory. It is not obtained through ICAMAPIGeomLibrary directly in most extension scenarios — instead the project exposes the model and importer directly.
2. ICAMAPIGeomLibrary — factory
ICAMAPIGeomLibrary is a low-level factory used when creating geometry models outside of a project context (e.g., in standalone utilities or unit tests).
Properties
| Property | Type | Access | Description |
|---|---|---|---|
HideUserMessages |
bool |
R/W | Suppress interactive dialogs during import |
VisTolerancePercent |
int |
R/W | Visualization chord tolerance, 0–100 |
SearchFontFolder |
string |
R/W | Priority folder for font files (useful in CI/VM environments) |
Methods
| Method | Returns | Description |
|---|---|---|
CreateGeometryModel() |
ICAMAPIGeometryModel |
Create a new empty geometry model |
CreateGeometryImporter() |
ICAMAPIGeometryImporter |
Create an importer bound to no model yet |
CreateTurnGeneratrixExtractor() |
ICMAPITurnGeneratrixExtractor |
Create a lathe generatrix extractor |
CreateFacesToTriangulatedFilesConverter() |
ICamApiFacesToTriangulatedFilesConverter |
Create a face-to-mesh file converter |
.NET helper usage
// geomLibCom is a ComWrapper<ICAMAPIGeomLibrary>
geomLibCom.SetHideUserMessages(true);
using var modelCom = geomLibCom.CreateGeometryModel();
using var importerCom = geomLibCom.CreateGeometryImporter();
Helper class: GeomLibraryHelper in CAMAPI.DotnetHelper.
3. ICAMAPIGeometryModel — model root
Obtained in extension code via the active project:
using var modelCom = new ComWrapper<ICAMAPIGeometryModel>(activeProject.CAMAPIGeomModel);
Properties
| Property | Type | Access | Description |
|---|---|---|---|
RootNode |
ICAMAPIGeometryTreeNode |
R | Root of the geometry tree |
ActiveNode |
ICAMAPIGeometryTreeNode |
R/W | Node that was most recently imported or activated |
Methods
| Method | Description |
|---|---|
GetNodes() |
Returns ICAMAPIGeometryTreeNodeIterator for a depth-first walk |
FindByFullName(fullName) |
Find a node by its full path name (e.g. "Root/Body/Face_1") |
Transform(node, matrix) |
Apply a TST3DMatrix transformation to a node |
ExportSelectedToSTL(fileName) |
Export selected nodes to STL |
ExportSelectedToOSD(fileName) |
Export selected nodes to OSD |
ExportSelectedToDXF(fileName) |
Export selected nodes to DXF |
DeselectAll() |
Deselect all nodes |
DeleteNode(node) |
Remove a node from the tree |
GetFaceListOfSelected() |
Returns ICamApiFaceList of selected face nodes |
AddGroup(parentNode) |
Add an empty group node under parentNode |
AddCadGroup(name, lcs?) |
Add a CAD-typed group (a fresh GeCAD model) at the given LCS; helper defaults lcs to identity. The returned node hosts an editable ICadApiModel — reach it with AsCadModel() from CADAPI.DotnetHelper |
ExportSelectedToStep(fileName) |
Export selected nodes to a STEP file (experimental) |
RegisterHandler(ident, handler, events) |
Subscribe an event handler (e.g. for SelectionChanged); ident is your key for unregistering, events is an optional IListString filter (empty = all) |
UnregisterHandler(ident) |
Remove a previously registered handler by its ident |
Helper: modelCom.ExportSelectedToStep(path), modelCom.AddCadGroup("Fixtures"). AddCadGroup
is the entry point to the CAD axis (GeCAD) that sits on top of the geometry model.
Finding a node by name
using var nodeCom = modelCom.InvokeAndWrap(m =>
(m.FindByFullName("Root/Body/Face_1", out var status), status));
Applying a transformation
// Shift a node by (100, 200, 0)
var shiftMatrix = T3DMatrix.MakeShiftMatrix(new T3DPoint { X = 100, Y = 200, Z = 0 });
// Nest Invoke so the node reference is acquired inside its own wrapper's MTA context
geomNodeCom.Invoke(node =>
modelCom.Invoke(m => m.Transform(node, shiftMatrix)));
See full example: Geometry/ExtensionUtilityGeometryModelNet/project/main/GeometryNodeTransformExample.cs
Exporting the active node
using var modelCom = new ComWrapper<ICAMAPIGeometryModel>(activeProject.CAMAPIGeomModel);
modelCom.Invoke(m => m.ExportSelectedToOSD(exportedFilePath, out var status));
See full example: Geometry/ExtensionUtilityGeometryModelNet/project/main/GeometryModelExportExample.cs
Direct access (IDL):
ExportSelectedToOSDtakes an[out] TResultStatus*parameter. The .NET helper wraps this and throws on error.
Observing selection changes
Register a handler to be notified when the set of selected geometry nodes changes — both from interactive viewport picking and from programmatic Selected / DeselectAll calls. The handler object implements ICamApiEventHandler (the generic event-handler marker) together with ICamApiHandlerSelectionChanged. AddedNodes / RemovedNodes carry the FullName of the nodes that became / stopped being selected — resolve them via FindByFullName.
class SelectionWatcher : ICamApiEventHandler, ICamApiHandlerSelectionChanged
{
public void SelectionChanged(string handlerIdent, IListString addedNodes, IListString removedNodes)
{
for (int i = 0; i < addedNodes.Count(); i++)
Console.WriteLine("selected: " + addedNodes.Get(i));
}
}
var watcher = new SelectionWatcher();
modelCom.Invoke(m => m.RegisterHandler("my-watcher", watcher, null, out var st));
// ... later
modelCom.Invoke(m => m.UnregisterHandler("my-watcher", out var st));
4. ICAMAPIGeometryTreeNode — tree navigation
Each node in the geometry tree exposes parent/child/sibling links, forming a classical linked-tree structure.
Properties
| Property | Type | Access | Description |
|---|---|---|---|
Name |
string |
R | Short name of this node |
FullName |
string |
R | Full path from root (e.g. "Root/Body/Face_1") |
Selected |
bool |
R/W | Selection state |
Visible |
bool |
R/W | Visibility state |
Parent |
ICAMAPIGeometryTreeNode |
R | Parent node (null for root) |
Child |
ICAMAPIGeometryTreeNode |
R | First child node (null if none) |
Sibling |
ICAMAPIGeometryTreeNode |
R | Next sibling node (null if none) |
GeometryEntity |
ICAMAPIGeometryEntity |
R | The geometry payload |
.NET helper
The GeometryTreeNodeHelper extension class provides typed wrappers for all properties, plus:
// Enumerate only the direct children of a node
foreach (var childCom in nodeCom.EnumerateChildren())
{
Console.WriteLine(childCom.Name());
childCom.Dispose();
}
Rendering and tessellation
| Helper method | Type | Description |
|---|---|---|
DoubleNormal() / SetDoubleNormal(v) |
bool |
Double-sided rendering — mill both sides of surfaces (vs. only the normal side) |
VisTol() / SetVisTol(v) |
double |
Visual tessellation tolerance (lower = finer mesh) |
VisMeshU() / SetVisMeshU(v) |
int |
Isoparametric curves shown in the U direction |
VisMeshV() / SetVisMeshV(v) |
int |
Isoparametric curves shown in the V direction |
User parameters on a node
Nodes carry free-form name/value parameters typed by TCAMAPIGeomParamType (aptInteger=0,
aptReal=1, aptString=2, aptBoolean=3). System parameters added by the engine or at import
time cannot be overwritten or deleted (attempting to do so throws).
| Helper method | Description |
|---|---|
ParamsCount() |
Number of parameters on the node |
GetParamName(i) / GetParamValue(i) / GetParamType(i) |
Read parameter 0..ParamsCount-1 |
AddParam(name, type, value) |
Add or overwrite a user parameter |
DeleteParam(name) |
Remove a user parameter; false if it did not exist |
nodeCom.AddParam("BatchNo", TCAMAPIGeomParamType.aptString, "A-17");
for (int i = 0; i < nodeCom.ParamsCount(); i++)
Console.WriteLine($"{nodeCom.GetParamName(i)} = {nodeCom.GetParamValue(i)}");
CAD content of a node
For a CAD-typed node, nodeCom.AsCadModel() (CADAPI.DotnetHelper) returns an ICadApiModel
for inspecting or mutating the CAD content; it is null for non-CAD nodes (mesh, body, plain
folders). This is the same CAD axis reached by ICAMAPIGeometryModel.AddCadGroup. Sketches,
extrude, cuts, fillets: cad.md.
5. Traversing the geometry tree
Preferred approach: iterator + LINQ
ICAMAPIGeometryTreeNodeIterator performs a depth-first walk. The AsEnumerable() extension method from GeometryTreeNodeIteratorHelper wraps it as a standard IEnumerable, with correct COM disposal.
using var iteratorCom = modelCom.InvokeAndWrap(m =>
(m.GetNodes(out var status), status));
foreach (var nodeCom in iteratorCom.AsEnumerable())
{
// nodeCom is a ComWrapper<ICAMAPIGeometryTreeNode>
// It is automatically disposed after the loop body
if (nodeCom.EntityType() == TCAMAPIGeometryEntityType.etFace)
Console.WriteLine(nodeCom.FullName());
}
The iterator exposes three navigation methods:
| Method | Returns | Description |
|---|---|---|
MoveToChild() |
bool |
Move down to first child; returns false if none |
MoveToSibling() |
bool |
Move to next sibling; returns false if none |
MoveToParent() |
bool |
Move up to parent; returns false at root |
Current() |
ICAMAPIGeometryTreeNode |
Current node |
Reset() |
— | Reset to tree root |
Manual low-level traversal (explicit MoveToChild / MoveToSibling)
When you need direct control of the iterator (instead of the AsEnumerable() wrapper above), use Invoke / InvokeAndWrap for every access. Navigate to the deepest first child, then walk siblings:
using var iteratorCom = modelCom.InvokeAndWrap(m => m.GetNodes(out var status));
// descend to the leaf level
while (iteratorCom.Invoke(it => it.MoveToChild())) { }
// walk all siblings at that level
do
{
using var nodeCom = iteratorCom.InvokeAndWrap(it => it.Current());
if (nodeCom.EntityType() == TCAMAPIGeometryEntityType.etFace)
Console.WriteLine(nodeCom.Name());
} while (iteratorCom.Invoke(it => it.MoveToSibling()));
See full example: Geometry/ExtensionUtilityGeometryModelNet/project/main/GeometryNodesIteratorExample.cs
Collecting selected nodes
var selectedNodes = new List<ComWrapper<ICAMAPIGeometryTreeNode>>();
foreach (var nodeCom in iteratorCom.AsEnumerable())
{
if (nodeCom.Selected())
selectedNodes.Add(nodeCom.TransferOwnership()); // take ownership away from the iterator
}
See full example: Geometry/UtilityGeometryEntityReader/project/main/ExtensionGeometryEntityReader.cs
6. ICAMAPIGeometryEntity — entity inspection
Each tree node carries one ICAMAPIGeometryEntity. The entity's EntityType property determines what additional interface is available.
ICAMAPIGeometryEntity properties
| Property | Type | Access | Description |
|---|---|---|---|
EntityClass |
string |
R | Internal class name string |
EntityType |
TCAMAPIGeometryEntityType |
R | Discriminator enum (see §17) |
Color |
int |
R/W | RGB color packed as integer |
ParentColor |
bool |
R/W | When true, entity inherits color from its parent node |
UpdateStamp |
long |
R | Monotonically increasing change counter |
GetBoundBox
// Pass an identity matrix for the world bounding box
var identity = T3DMatrix.Unit;
var box = entityCom.GetBoundBox(identity);
// box.Min and box.Max are TST3DPoint
Direct access (IDL):
GetBoundBox(in LCS: TST3DMatrix, [out] TResultStatus*): TST3DBox
Specialised entity interfaces
The raw COM object also implements one of the following interfaces depending on EntityType. Query with a direct cast inside nodeCom.Invoke(...):
ICamApiCSGeometryEntity (etCS)
nodeCom.Invoke(node =>
{
if (node.GeometryEntity is ICamApiCSGeometryEntity entityCs)
{
var matrix = entityCs.Matrix; // TST3DMatrix — position/orientation
Console.WriteLine($"CS origin: {matrix.vT.X}, {matrix.vT.Y}, {matrix.vT.Z}");
}
});
ICamApiFaceGeometryEntity (etFace)
nodeCom.Invoke(node =>
{
if (node.GeometryEntity is ICamApiFaceGeometryEntity entityFace)
{
using var faceCom = ComWrapper.Create(entityFace.Face);
// work with ICamApiFace — see §7
}
});
ICamApiCurveGeometryEntity (etCurve)
nodeCom.Invoke(node =>
{
if (node.GeometryEntity is ICamApiCurveGeometryEntity entityCurve)
{
using var curveCom = ComWrapper.Create(entityCurve.Curve);
Console.WriteLine($"Length: {curveCom.Invoke(c => c.FullLen)}");
}
});
Helper: CurveGeometryEntityHelper.Curve(entityCom) wraps the Curve property access so the returned ICamApiCurve is owned by a ComWrapper.
ICamApiImportedGeometryEntity (imported folder metadata)
A node that represents the root of an imported CAD model can be queried for the source file path and PLM metadata. This interface is implemented by the tree-node object itself (not reached through GeometryEntity), so the cast is performed on ICAMAPIGeometryTreeNode.
| Helper method | Returns | Description |
|---|---|---|
importedCom.CADModelLocalFileName() |
string |
Absolute path of the source CAD file |
importedCom.ImportedFromPLM() |
bool |
true if the model was imported from a PLM system |
importedCom.PLMObjectID() |
string |
Identifier of the imported file in PLMManager (empty when ImportedFromPLM is false) |
using var importedCom = nodeCom.InvokeAndWrap(node => node as ICamApiImportedGeometryEntity);
if (!importedCom.IsNull)
{
string src = importedCom.CADModelLocalFileName();
bool plm = importedCom.ImportedFromPLM();
string pId = importedCom.PLMObjectID();
}
Helper class: ImportedGeometryEntityHelper in CAMAPI.DotnetHelper.
See full entity-reading example: Geometry/UtilityGeometryEntityReader/project/main/ExtensionGeometryEntityReader.cs
7. B-rep structure: Face, Loop, CoEdge
B-rep topology follows the standard Face → Loop → CoEdge hierarchy.
ICamApiFace
└─ ICamApiLoop (outer + inner loops via GetNext chain)
└─ ICamApiCoEdge (via CoEdges iterator)
└─ ICamApiSurfaceCurve (trimming curve on surface)
ICamApiFace
Obtained from ICamApiFaceGeometryEntity.Face or from ICamApiFaceList.
| Member | Description |
|---|---|
Surface |
The underlying ICamApiSurface |
Orientation |
true = face normal agrees with surface normal |
GetFirstLoop() |
First ICamApiLoop; chain via loop.GetNext() |
GetMesh(tol) |
Triangulate to ICamApiMesh at given tolerance |
GetNurbsForm() |
Return face converted to NURBS representation |
IsCylindricalHole(tol, ...) |
Convenience test; returns center, axis, radius, ZMin, ZMax |
SurfaceKind |
Classified analytic kind of the surface — TCamApiSurfaceKind |
GetMinCurvatureRadius(tol) |
Minimum radius of curvature over the face; MaxFloat for a planar face |
GetMaxCurvatureRadius(tol) |
Maximum radius of curvature over the face — on a sphere this is the sphere radius. MaxFloat when no finite curved direction exists (planar face, or the straight direction of a cylinder or cone) |
GetPlane(out origin, out normal) |
If planar, returns plane origin and unit normal; true when planar |
GetCylinder(out center, out axis, out radius) |
If cylindrical, returns axis and radius; true when cylindrical |
GetCone(out center, out axis, out radius, out halfAngle) |
If conical, returns apex, axis, base radius and half-angle (rad); true when conical |
Enumerate all loops
foreach (var loopCom in faceCom.EnumerateLoops())
{
bool isOuter = loopCom.Invoke(l => l.IsOuter);
// ...
loopCom.Dispose();
}
Check for a cylindrical hole
if (faceCom.IsCylindricalHole(0.01,
out var center, out var axis, out var radius, out var zMin, out var zMax))
{
Console.WriteLine($"Hole radius={radius}, axis=({axis.X},{axis.Y},{axis.Z})");
}
Analytic surface recognition
SurfaceKind classifies the face's underlying surface. The Get* methods then extract the analytic parameters for the matching kind (they return false when the face is not of that kind).
TCamApiSurfaceKind values: skOther, skPlanar, skCylindrical, skConical, skSpherical, skToroidal.
switch (faceCom.SurfaceKind())
{
case TCamApiSurfaceKind.skPlanar:
faceCom.GetPlane(out var origin, out var normal);
break;
case TCamApiSurfaceKind.skCylindrical:
faceCom.GetCylinder(out var center, out var axis, out var radius);
break;
case TCamApiSurfaceKind.skConical:
faceCom.GetCone(out var apex, out var coneAxis, out var baseRadius, out var halfAngle);
break;
}
double rMin = faceCom.GetMinCurvatureRadius(0.01); // MaxFloat for a plane
FaceHelper exposes SurfaceKind, GetMinCurvatureRadius, GetMaxCurvatureRadius, GetPlane, GetCylinder, GetCone.
ICamApiLoop
| Member | Description |
|---|---|
IsOuter |
true for the outer (boundary) loop |
CoEdges |
ICamApiCoEdgeIterator for the co-edges in this loop |
GetNext() |
Next loop on the same face (null after last) |
GetNurbsForm(needUVCurve) |
NURBS form of the loop boundary |
ICamApiCoEdge
| Member | Description |
|---|---|
Geometry |
ICamApiSurfaceCurve — the trimming curve on the surface |
Orientation |
Whether the curve direction agrees with the loop traversal direction |
Edge |
The underlying ICamApiEdge (3D curve) this co-edge runs along |
Iterate co-edges
using var iterCom = loopCom.InvokeAndWrap(l => l.CoEdges);
foreach (var coEdgeCom in iterCom.AsEnumerable())
{
bool orientation = coEdgeCom.Orientation();
using var curveCom = coEdgeCom.Geometry();
var startPt = curveCom.Invoke(c => c.StartPoint);
coEdgeCom.Dispose();
}
Helper classes: FaceHelper, CoEdgeHelper, CoEdgeIteratorHelper in CAMAPI.DotnetHelper.
ICamApiEdge
The 3D curve running between two vertices — the geometry a co-edge sits on (coEdge.Edge()). Geometry only; adjacency and convexity come from ICamApiEdgeAnalyzer (below).
| Property | Description |
|---|---|
StartPoint, EndPoint |
Edge endpoints in model space (TST3DPoint) |
Length |
Straight-line distance between the endpoints |
Helper: EdgeHelper (StartPoint, EndPoint, Length).
ICamApiSurfaceCurve
A curve living on a surface (the 3D trim curve of a co-edge).
| Property/Method | Description |
|---|---|
TMin, TMax |
Parameter range |
StartPoint, EndPoint |
3D start/end points |
StartNormal, EndNormal |
Surface normal at start/end |
StartTangent, EndTangent |
Curve tangent at start/end |
SaveToReceiver(receiver, tMin, tMax, tol) |
Discretise into polyline via ICamApiAbstractCurveReceiver |
ICamApiFaceList
A flat list of faces, e.g. from ICAMAPIGeometryModel.GetFaceListOfSelected().
using var faceListCom = modelCom.InvokeAndWrap(m =>
(m.GetFaceListOfSelected(out var status), status));
int count = faceListCom.Count();
for (int i = 0; i < count; i++)
{
using var faceCom = faceListCom.Face(i);
// ...
}
GetMesh(index, tol, holeCappingSize, fromThread) on ICamApiFaceList triangulates the i-th face with optional hole capping.
Edge adjacency and convexity — ICamApiEdgeAnalyzer
Imported CAM geometry has no shared edges between faces — each face carries its own trim curves, so there is no built-in "which two faces meet at this edge" relation. ICamApiEdgeAnalyzer reconstructs that: it matches coincident per-face edges within a tolerance and classifies each edge as convex / concave / smooth / boundary. Pass all faces of a solid so boundary (free) edges are detected correctly.
It is a singleton extension — resolve it via EdgeAnalyzerHelper.GetSingleton().
| Interface | Member | Description |
|---|---|---|
ICamApiEdgeAnalyzer |
CreateFaceList() |
Create an empty ICamApiFaceListBuilder |
Build(faces, tolerance, out status) |
Analyze the face set → ICamApiEdgeFaceInfoList |
|
ICamApiFaceListBuilder |
AddFace(face) / AddRange(faces) |
Accumulate faces (AddFace surfaces failures via TResultStatus) |
Build() |
Immutable ICamApiFaceList snapshot |
|
ICamApiEdgeFaceInfoList |
Count, Item[i] |
Per-edge results |
GetByEdge(edge) |
Info for a given ICamApiEdge (endpoint match); null if none |
|
ICamApiEdgeFaceInfo |
FaceA, FaceB |
The two adjacent faces; FaceB is null for a boundary edge |
IsConvex, IsConcave, IsSmooth, IsBoundary |
Edge classification |
using var analyzerCom = EdgeAnalyzerHelper.GetSingleton();
using var builderCom = analyzerCom.CreateFaceList();
builderCom.AddRange(faceListCom); // ICamApiFaceList of the whole solid
using var facesCom = builderCom.Build();
using var edgeInfoListCom = analyzerCom.Build(facesCom, 0.001);
for (int i = 0; i < edgeInfoListCom.Count(); i++)
{
using var infoCom = edgeInfoListCom.Item(i);
if (infoCom.IsConcave())
{
using var faceACom = infoCom.FaceA();
using var faceBCom = infoCom.FaceB(); // IsNull for a boundary edge
// ... concave edge between faceA and faceB (fillet/pocket candidate)
}
}
Helpers: EdgeAnalyzerHelper, FaceListBuilderHelper, EdgeFaceInfoListHelper, EdgeFaceInfoHelper.
Shape descriptors — ICamApiShapeAnalyzer
Where ICamApiEdgeAnalyzer answers how faces connect, ICamApiShapeAnalyzer answers what
a shape looks like numerically: it computes shape descriptors — form characteristics of a
single face or a whole face set — intended for shape recognition and AI classification.
Also a singleton extension: ShapeAnalyzerHelper.GetSingleton().
Descriptors come at two levels:
- Level 1 — scale-dependent: areas, volume, bounding box, centroid, curvature radii, face and edge counts. Real millimetres; two parts of the same shape and different size differ here.
- Level 2 — scale-invariant: ratios, normalized inertia, sphericity, distance histogram.
Dimensionless, so the same shape at any size gives the same numbers. This is what
GetVector()packs for a classifier.
| Method | Description |
|---|---|
CreateFaceList() |
Empty ICamApiFaceListBuilder (same builder as the edge analyzer) |
BuildFaceInfo(face, tolerance) |
Descriptor of one face → ICamApiShapeFaceInfo. Throws on error |
BuildBodyInfo(faces, tolerance) |
Descriptor of a face set → ICamApiShapeBodyInfo. Throws on error |
tolerance drives tessellation, curvature sampling and edge matching — the same value used
for the edge analyzer is a reasonable choice.
ICamApiShapeFaceInfo — level 1: Area, BoxMin, BoxMax, MinCurvatureRadius,
MaxCurvatureRadius, LoopCount (1 = simple face, ≥2 = face with holes), SurfaceKind;
level 2: CurvatureRatio (≈1 when the face bends alike in both directions like a sphere or
plane, ≈0 when one direction is straight like a cylinder or cone), GetNormalizedInertia(),
GetVector().
ICamApiShapeBodyInfo — level 1: IsClosed, Volume, TotalArea, BoxMin, BoxMax,
Centroid, FaceCount, GetFaceCountByKind(kind), ConvexEdgeCount, ConcaveEdgeCount,
SmoothEdgeCount, BoundaryEdgeCount, GetPrincipalInertia(...); level 2: Sphericity
(exactly 1 for a ball), GetNormalizedInertia(), GetDistanceHistogram(), GetVector().
using var analyzerCom = ShapeAnalyzerHelper.GetSingleton();
using var builderCom = analyzerCom.CreateFaceList();
builderCom.AddRange(faceListCom); // ALL faces of the solid
using var facesCom = builderCom.Build();
using var bodyCom = analyzerCom.BuildBodyInfo(facesCom, 0.001);
if (bodyCom.IsClosed())
Console.WriteLine($"volume {bodyCom.Volume():0.##}, sphericity {bodyCom.Sphericity():0.###}");
// Scale-invariant feature vector for a classifier
double[] vector = bodyCom.GetVector();
int schema = bodyCom.VectorSchemaVersion();
// Per-face descriptor
using var faceInfoCom = analyzerCom.BuildFaceInfo(faceCom, 0.001);
if (faceInfoCom.SurfaceKind() == TCamApiSurfaceKind.skCylinder)
Console.WriteLine($"cylinder R≈{faceInfoCom.MinCurvatureRadius():0.###}");
Always check
VectorSchemaVersionbefore feedingGetVector()to a trained model — the vector's meaning and order are versioned, and a classifier trained on one version must not consume another.
Pass every face of the solid to
BuildBodyInfo. Closedness, boundary-edge count and volume are derived from the set you hand in — a partial set silently reports as an open shell withVolume = 0.
Curvature radii return
MaxFloat(not infinity, not an error) where there is no finite curved direction — a planar face, or the straight direction of a cylinder or cone. Guard for that before using them in arithmetic.
Volume,Sphericityand the volume-based inertia are meaningless for an open face set: volume and sphericity read0, and inertia falls back to surface-area weighting.
GetDistanceHistogram()is estimated from a fixed-size sample of the tessellation, so identical shapes agree closely rather than bit-exactly — compare with a tolerance.
Helpers: ShapeAnalyzerHelper, ShapeFaceInfoHelper, ShapeBodyInfoHelper (the last two
wrap the indexed VectorValue / DistanceHistogramBin properties as plain double[]).
8. ICamApiSurface — underlying surface
Each ICamApiFace owns one ICamApiSurface. The primary use is to obtain the NURBS representation.
using var surfaceCom = faceCom.Surface();
using var nurbsCom = surfaceCom.InvokeAndWrap(s => s.GetNurbsForm());
ICamApiNurbsSurface
A two-dimensional B-spline surface.
| Property | Description |
|---|---|
Degree[i] |
Polynomial degree in direction i (0=U, 1=V) |
IsClosed[i] |
Closed flag in direction i |
IsRational |
Rational (NURBS) vs. non-rational (B-spline) |
KnotCount[i] |
Number of knots in direction i |
Knot[i][j] |
j-th knot in direction i |
CPCount[i] |
Control point count in direction i |
ControlPoint[i][j] |
Control point at (i,j) as TST3DPoint |
ControlWeight[i][j] |
Weight at (i,j) (only meaningful when IsRational = true) |
9. ICamApiCurve and ICamApiCurve5D
ICamApiCurve
A parametric 3D curve with knot-point access and arc-length operations.
| Member | Description |
|---|---|
QntP |
Number of knot points |
KnotPoint[i] |
i-th knot point (TST3DPoint) |
TMin, TMax |
Parameter range |
FullLen |
Total arc length |
IsClosed |
Closed curve flag |
Box |
Bounding box (TST3DBox) |
Get_Point(t) |
Evaluate position at parameter t |
Get_UnitTangent(t, isForward) |
Normalized tangent at t |
Get_Len(t1, t2) |
Arc length between t1 and t2 |
FindNearestPoint(p, t1, t2) |
Parameter of closest point on [t1,t2] to p |
LenToParameter(len) |
Convert arc length to parameter |
MakeStepByLen(t, step, out residual) |
Walk step distance from t; returns new parameter |
SavePartToReceiver(receiver, t1, t2, tol) |
Discretise [t1,t2] into a polyline |
Inverse() |
Reverse curve direction in-place |
Sampling a curve as a polyline
// Implement ICamApiAbstractCurveReceiver to collect points
class PolylineReceiver : ICamApiAbstractCurveReceiver
{
public List<TST3DPoint> Points = new();
public void StartCurve3D(TST3DPoint p) => Points.Add(p);
public void CutTo3D(TST3DPoint p) => Points.Add(p);
public void StopCurve(bool isClosed) { }
}
var receiver = new PolylineReceiver();
curveCom.Invoke(c => c.SavePartToReceiver(receiver, c.TMin, c.TMax, 0.01));
NURBS form of a curve
using var nurbsCom = curveCom.InvokeAndWrap(c =>
((ICamApiAbstractCurve)c).GetNurbsForm());
// nurbsCom is ICamApiAbstractNurbsCurve
ICamApiAbstractNurbsCurve
| Property | Description |
|---|---|
Degree |
Polynomial degree |
Is3d |
True if 3D, false if 2D (UV) |
IsRational |
True for NURBS, false for B-spline |
KnotCount |
Total knot count |
Knot[i] |
i-th knot value |
CPCount |
Control point count |
CP[i] |
i-th control point (TST3DPoint) |
CW[i] |
i-th weight |
ICamApiCurve5D
An optional interface, also implemented by curve objects, that exposes 5-axis tool-axis information.
curveCom.Invoke(c =>
{
if (c is ICamApiCurve5D curve5D)
{
var pt5D = curve5D.Get_Point5D(t); // TST5DPoint — position + tool axis
}
});
ICamApiCurveArcsReceiver
An extended receiver that can accept arc segments in addition to line segments. Implement both ICamApiAbstractCurveReceiver and ICamApiCurveArcsReceiver to receive full arc-accurate output.
| Method | Description |
|---|---|
ArcTo3D(pc, ep, R) |
Arc from current point to ep with signed radius R (positive = CCW) |
AddCircle(pc, R) |
Full 360-degree circle at center pc |
10. ICamApiMesh — triangulated representation
A triangulated mesh obtained from ICamApiFace.GetMesh(tol) or ICamApiFaceList.GetMesh(...).
| Method | Description |
|---|---|
GetVertexCount() |
Total number of vertices |
GetTriangleCount() |
Total number of triangles |
GetVertex(i) |
TST3DPoint for the i-th vertex |
GetTriangle(i) |
TST3IPoint — three vertex indices for the i-th triangle |
GetTriangleNormal(i) |
TST3DPoint — outward normal for the i-th triangle |
using var meshCom = faceCom.GetMesh(0.05); // tolerance in model units
int vtxCount = meshCom.GetVertexCount();
int triCount = meshCom.GetTriangleCount();
for (int i = 0; i < triCount; i++)
{
var tri = meshCom.GetTriangle(i); // .X .Y .Z are indices into the vertex array
var n = meshCom.GetTriangleNormal(i);
var v0 = meshCom.GetVertex(tri.X);
}
Helper class: MeshHelper in CAMAPI.DotnetHelper.
11. ICamApiCoordinateSystem
Coordinate systems appear in the geometry tree as nodes with EntityType == etCS. They can also be managed through a dedicated ICamApiListCoordinateSystem.
ICamApiCoordinateSystem properties
| Property | Type | Access | Description |
|---|---|---|---|
Name |
string |
R | Unique name |
Parent |
ICamApiCoordinateSystem |
R | Parent CS (null = global) |
Matrix |
TST3DMatrix |
R/W | 4×4 matrix relative to global CS |
Color |
int |
R/W | Display color |
using var csCom = ComWrapper.Create(csEntity.Matrix);
var origin = csCom.Matrix().vT; // translation part
ICamApiListCoordinateSystem
Manages a collection of named coordinate systems. Typically obtained from the active project.
| Method | Description |
|---|---|
Count |
Number of CS in the list |
CoordinateSystem[index] |
Access by zero-based index |
Add(name, matrix, parentName) |
Add a new CS |
Remove(name) |
Remove a CS by name |
GetByName(name) |
Find a CS by name |
GetActive |
The currently active CS |
SetActive(name) |
Make a CS active |
// listCsCom is ComWrapper<ICamApiListCoordinateSystem>
listCsCom.Invoke(list =>
{
list.Add("MyCS", matrix, "", out var status);
list.SetActive("MyCS", out status);
});
Helper class: CoordinateSystemHelper in CAMAPI.DotnetHelper.
12. ICAMAPIGeometryImporter — importing files
The importer is obtained from the active project or from ICAMAPIGeomLibrary.CreateGeometryImporter().
// Obtain from project (most common in extensions)
using var importerCom = activeProjectCom.InvokeAndWrap(p => p.GeomImporter);
ICAMAPIGeometryImporter members
| Member | Description |
|---|---|
GeometryModel |
R/W — the target ICAMAPIGeometryModel |
ImportFile(filePath, targetFolder, showDialog) |
Import; targetFolder="" → current folder; showDialog=true → shows import options UI |
Basic import
using var importerCom = activeProjectCom.InvokeAndWrap(p => p.GeomImporter);
importerCom.ImportFile(@"C:\models\part.igs", "", false);
// After import, the imported root node is available via ActiveNode
var importedName = modelCom.Invoke(m => m.ActiveNode.FullName);
Import with dialog
Pass showDialog: true to let the user configure format-specific options:
importerCom.ImportFile(filePath, "", showDialog: true);
Using GeomImporterHelper
// Set a different target model (standalone usage)
importerCom.SetGeometryModel(anotherModelCom);
importerCom.ImportFile(filePath, "SubFolder", false);
Helper class: GeometryImporterHelper in CAMAPI.DotnetHelper.
See full example: Geometry/ExtensionUtilityGeometryImporterNet/project/main/ExtensionGeometryImporter.cs
Direct access (IDL):
ImportFilereturns aTResultStatusby value. The helper throws onrsError.
13. ICamApiGeomPicker — interactive picking
The geometry picker shows a modal dialog that lets the user select nodes from the geometry tree. Results are delivered asynchronously via a callback.
The picker is created as a system extension (not through ICAMAPIGeomLibrary):
using var geomPickerCom = SystemExtensionFactory.CreateExtension<ICamApiGeomPicker>(
"Extension.GeomPicker");
ICamApiGeomPicker members
| Member | Description |
|---|---|
AvailableEntityTypes |
Bitmask of TGeometryEntityTypeFlag values indicating which entity types the user may select |
OnClose |
An ICamApiGeomPickerOnClose callback implementation |
Show() |
Display the picker dialog (non-blocking; result arrives via callback) |
Title |
R/W — window title of the dialog; empty string keeps the default |
ButtonCount |
Number of customizable buttons |
ButtonType[i] |
Role of the button at index i — TCamApiGeomPickerButton (gpbOk, gpbCancel, gpbClear, gpbDelete) |
ButtonCaption[i], ButtonVisible[i], ButtonEnabled[i], ButtonHint[i] |
R/W per-button caption / visibility / enabled state / tooltip |
Customizing the dialog
Set Title and tweak buttons before Show(). GeomPickerHelper finds a button by its role, so you don't index by position:
geomPickerCom.SetTitle("Pick faces to protect");
geomPickerCom.SetButtonCaption(TCamApiGeomPickerButton.gpbOk, "Protect");
geomPickerCom.SetButtonVisible(TCamApiGeomPickerButton.gpbClear, false);
geomPickerCom.SetButtonHint(TCamApiGeomPickerButton.gpbDelete, "Remove from list");
Helper: GeomPickerHelper — Title/SetTitle, ButtonCount, ButtonType, FindButton, and Set{Caption,Visible,Enabled,Hint} overloads keyed by index or by TCamApiGeomPickerButton.
ICamApiGeomPickerOnClose
Implement this interface to receive the user's selection:
class MyOnClose : ICamApiGeomPickerOnClose
{
public void OnConfirm(IListString selectedItems)
{
// selectedItems contains the FullName strings of selected nodes
for (int i = 0; i < selectedItems.Count(); i++)
Console.WriteLine(selectedItems.Get(i));
}
public void OnCancel()
{
Console.WriteLine("User cancelled");
}
}
Full picker usage
if (geomPickerCom.IsNull)
throw new Exception("Could not create picker");
geomPickerCom.Invoke(picker =>
{
// Allow faces and edges
picker.AvailableEntityTypes = (ushort)(
TGeometryEntityTypeFlag.etfFace | TGeometryEntityTypeFlag.etfEdge);
picker.OnClose = new MyOnClose();
picker.Show();
});
// The extension must implement IExtensionLazyUnloadable and not unload
// until OnConfirm or OnCancel has been called.
See full example: Geometry/ExtensionUtilityGeometryPickerNet/project/main/ExtensionGeometryPicker.cs
Note: because
Show()is non-blocking, the hosting extension must implementIExtensionLazyUnloadableand setCanUnload = trueonly inside the callback.
14. IExtensionGeomModelNodePopup — context menu
This extension point lets an extension add items to the right-click context menu that appears when the user right-clicks a node in the 3D model tree.
Registration
Implement IExtensionGeomModelNodePopup in your extension class:
public class MyPopupExtension : IExtension, IExtensionGeomModelNodePopup
{
public IExtensionInfo? Info { get; set; }
public void Build(IExtensionGeomModelNodePopupBuildContext context,
out TResultStatus resultStatus)
{
resultStatus = default;
context.NodePopup.AddItem(
"MyAction", // unique identifier
"Do something", // visible caption (empty string = separator)
enabled: true,
new MyOnClicked(),
out resultStatus);
}
}
IExtensionGeomModelNodePopupBuildContext
| Property | Description |
|---|---|
SelectedNode |
The node that was right-clicked |
ActiveProject |
The currently active project |
NodePopup |
The ICamApiGeomModelNodePopup to add items to |
ICamApiGeomModelNodePopup
| Method | Description |
|---|---|
AddItem(name, caption, enabled, onClicked) |
Add a menu item |
GetItems() |
Returns IListString of item names in order |
GetItem(name) |
Find an existing item by name |
Clear() |
Remove all items |
Handling a click
public class MyOnClicked : ICamApiGeomModelNodePopupItemOnClicked
{
public void OnItemClicked(
IExtensionGeomModelNodePopupItemOnClickedContext context,
out TResultStatus resultStatus)
{
resultStatus = default;
using var nodeCom = ComWrapper.Create(context.SelectedNode);
Console.WriteLine("Clicked: " + nodeCom.FullName());
}
}
See full example: GeometryModelNodePopup/NodeFullNameAlert/project/main/
15. ICMAPITurnGeneratrixExtractor — lathe generatrix
Extracts the generatrix (profile) curve from a geometry node representing a turned part. Obtained from ICAMAPIGeomLibrary.CreateTurnGeneratrixExtractor().
Properties
| Property | Type | Description |
|---|---|---|
Receiver |
ICamApiAbstractCurveReceiver |
Receiver that collects the output polyline |
Tolerance |
double |
Chord tolerance for generatrix approximation |
SewTolerance |
double |
Tolerance for joining adjacent curve segments |
NeedJoinCurves |
bool |
Whether to merge adjacent segments into one curve |
TurnAxis |
TST5DPoint |
Rotation axis (position + direction) |
NeedCloseToAxis |
bool |
Whether to extend the profile to the rotation axis |
Usage
using var extractorCom = geomLibCom.CreateTurnGeneratrixExtractor();
extractorCom.Invoke(e =>
{
e.Tolerance = 0.01;
e.TurnAxis = myAxis;
e.Receiver = myReceiver;
});
geomNodeCom.Invoke(node =>
extractorCom.Invoke(e => e.MakeGeneratrixForNode(node, out var status)));
16. Export helpers
ICamApiFacesToTriangulatedFilesConverter
Converts a ICamApiFaceList to STL or OSD without requiring node selection. Obtained from ICAMAPIGeomLibrary.CreateFacesToTriangulatedFilesConverter().
| Member | Description |
|---|---|
Tolerance |
Chord tolerance for tessellation |
Color |
Color to embed in output file |
SaveFacesToSTL(faces, fileName) |
Write faces to STL |
SaveFacesToOSD(faces, fileName) |
Write faces to OSD |
using var converterCom = geomLibCom.InvokeAndWrap(lib =>
lib.CreateFacesToTriangulatedFilesConverter());
converterCom.Invoke(c => c.Tolerance = 0.05);
faceListCom.Invoke(list =>
converterCom.Invoke(c => c.SaveFacesToSTL(list, @"C:\out\part.stl", out var status)));
17. Entity type reference
TCAMAPIGeometryEntityType enum
| Value | Name | Specialised interface |
|---|---|---|
| 0 | etGroup |
— (container node only) |
| 1 | etPoint |
— |
| 2 | etCurve |
ICamApiCurveGeometryEntity → ICamApiCurve |
| 3 | etMesh |
— |
| 4 | etFace |
ICamApiFaceGeometryEntity → ICamApiFace |
| 5 | etVertex |
— |
| 6 | etEdge |
— |
| 7 | etCS |
ICamApiCSGeometryEntity (matrix only) |
| 8 | etPmi |
— (PMI annotation) |
| 9 | etView |
— (saved view) |
TGeometryEntityTypeFlag bitmask
Used in ICamApiGeomPicker.AvailableEntityTypes:
| Flag | Value | Meaning |
|---|---|---|
etfGroup |
1 | |
etfPoint |
2 | |
etfCurve |
4 | |
etfMesh |
8 | |
etfFace |
16 | |
etfVertex |
32 | |
etfEdge |
64 | |
etfCS |
128 | |
etfPmi |
256 | |
etfView |
512 |
18. ICamApiGeometryModelSketcher — creating primitives
The sketcher creates simple geometry (points, lines, polylines, splines) in the Job
geometry sub-tree — the same content a user would draw by hand. It is not a separate object:
the geometry model itself implements ICamApiGeometryModelSketcher, so obtain it by
QueryInterface from the model wrapper.
using var modelCom = new ComWrapper<ICAMAPIGeometryModel>(activeProject.CAMAPIGeomModel);
using var sketcherCom = modelCom.InvokeAndWrap(m => m as ICamApiGeometryModelSketcher);
Helper class: GeometryModelSketcherHelper. Every method returns the created
ICAMAPIGeometryTreeNode (dispose it) and throws on engine error.
| Helper method | Creates |
|---|---|
AddPoint(x, y, z) |
A single point |
AddLine(p1, p2) |
A line segment between two TST3DPoint |
AddNormalLine(origin, endPoint) |
A "normal line" anchored at origin, ending at endPoint |
StartPolyline() |
Begins a polyline — returns an ICamApiSpatialCurveBuilder |
StartSpline() |
Begins a smooth spline — returns an ICamApiSpatialCurveBuilder |
Sketching in a local coordinate system
By default all coordinates are Global. To sketch in a local CS instead — on a machined face, in a workpiece CS — there are two ways:
TargetCS sets a sticky CS applied to every subsequent AddPoint, AddLine,
AddNormalLine, StartPolyline and StartSpline. The matrix maps sketch-local coordinates
to world, the same convention as ICamApiCoordinateSystem.Matrix; identity means Global.
SetTargetCS accepts either a raw TST3DMatrix or a ComWrapper<ICamApiCoordinateSystem>.
sketcherCom.SetTargetCS(csCom); // everything below is CS-local
using (var ptCom = sketcherCom.AddPoint(0, 0, 0)) { } // origin of that CS
One-shot variants — pass the CS as an extra last argument and TargetCS is neither read
nor changed. Each sketch method has two such overloads, taking a TST3DMatrix or a
ComWrapper<ICamApiCoordinateSystem>:
| Helper overload | Creates |
|---|---|
AddPoint(x, y, z, cs) |
Point in cs |
AddLine(p1, p2, cs) |
Line in cs |
AddNormalLine(origin, endPoint, cs) |
Normal line in cs |
StartPolyline(cs) |
Polyline builder whose knots are in cs |
StartSpline(cs) |
Spline builder whose knots are in cs |
using var ptCom = sketcherCom.AddPoint(0, 0, 0, csCom); // TargetCS untouched
The underlying IDL methods are
AddPointInCS,AddLineInCS,AddNormalLineInCS,StartPolylineInCS,StartSplineInCS— the helpers expose them as overloads of the plain names instead.
TargetCSis sticky state on the sketcher, which is the geometry model itself — so it stays until you set it back. Prefer the one-shot overloads for occasional geometry, or restore the previous matrix (GetTargetCS()) when you are done.
Multi-knot curves — ICamApiSpatialCurveBuilder
StartPolyline / StartSpline return a builder; append knots, then Finish() to commit the
curve into the tree and receive its node. Helper class: SpatialCurveBuilderHelper.
| Helper method | Description |
|---|---|
AddKnot(p) |
Append a knot TST3DPoint (throws on error) |
Finish() |
Commit accumulated knots; returns the new node. The builder is exhausted afterwards |
TryAddKnot(p, out status) / TryFinish(out status) |
Non-throwing variants exposing the raw TResultStatus |
using var sketcherCom = modelCom.InvokeAndWrap(m => m as ICamApiGeometryModelSketcher);
// Simple primitives
using (var ptCom = sketcherCom.AddPoint(0, 0, 0)) { }
using (var lineCom = sketcherCom.AddLine(
new TST3DPoint { X = 0, Y = 0, Z = 0 },
new TST3DPoint { X = 50, Y = 0, Z = 0 })) { }
// A polyline built from several knots
using var builderCom = sketcherCom.StartPolyline();
builderCom.AddKnot(new TST3DPoint { X = 0, Y = 0, Z = 0 });
builderCom.AddKnot(new TST3DPoint { X = 50, Y = 0, Z = 0 });
builderCom.AddKnot(new TST3DPoint { X = 50, Y = 50, Z = 0 });
using var polylineNodeCom = builderCom.Finish();
19. ICamApiPointSnapper — snapping points to faces
The point snapper projects measured 3D points onto the nearest position on a set of faces —
the building block for probing / measurement alignment. It is a singleton system
extension, obtained through its helper (no direct CAMAPI.TechSolvers reference needed).
using var snapperCom = PointSnapperHelper.GetSingleton(); // ICamApiPointSnapper
Helper class: PointSnapperHelper. The snapper hands out two lightweight mutable collections:
| Helper method | Returns | Description |
|---|---|---|
CreateFaceList() |
ComWrapper<ICamApiFaceListBuilder> |
An empty, mutable face list builder to fill with target faces |
CreatePointList() |
ComWrapper<ICamApiPoint3DList> |
An empty point list |
FindNearestOnFaces(faces, points, tolerance) |
TST3DPoint[] |
For each input point, the nearest position on any face — result [i] corresponds to input [i]. faces is an ICamApiFaceList |
FindNearestOnFacesRaw(...) |
ComWrapper<ICamApiPoint3DList> |
Same, but returns the COM list (caller owns it) |
ICamApiPoint3DList (helper Point3DListHelper): Count(), Item(i), Add(point).
ICamApiFaceListBuilder (helper FaceListBuilderHelper) accumulates faces via
AddFace(face) / AddRange(faceList) and produces the immutable ICamApiFaceList snapshot
FindNearestOnFaces expects with Build().
using var snapperCom = PointSnapperHelper.GetSingleton();
using var facesBuilderCom = snapperCom.CreateFaceList();
facesBuilderCom.AddRange(selectedFacesCom); // e.g. modelCom.GetFaceListOfSelected()
using var facesCom = facesBuilderCom.Build(); // immutable ICamApiFaceList snapshot
using var pointsCom = snapperCom.CreatePointList();
pointsCom.Add(new TST3DPoint { X = 10.2, Y = 4.8, Z = 0.1 });
pointsCom.Add(new TST3DPoint { X = 22.0, Y = 4.9, Z = 0.0 });
TST3DPoint[] snapped = snapperCom.FindNearestOnFaces(facesCom, pointsCom, tolerance: 0.5);
// snapped[i] is the closest on-surface position to the i-th measured point
Direct access (IDL):
ICamApiPointSnapper,ICamApiFaceListBuilder, andICamApiPoint3DListlive inCAMAPI.TechSolvers.FindNearestOnFacescarries anout TResultStatusthat the helper checks for you.