Place a valve on an SLINE from code, like a tool palette drop

Drop a Ball Valve from the tool palette onto a schematic line (SLINE) in an AutoCAD Plant 3D P&ID drawing and several things happen at once: the line opens a gap around the valve, the valve gets a tag, and a class annotation such as “Valve Label” appears beside it and follows it when it moves. Call AssetAdder.Add from a .NET plug-in and you get the symbol, and little else. This post covers how to place a valve, or any other asset, on an SLINE through the API so that the result matches a palette drop in Plant 3D 2027.

The short answer

A palette drop is a placement followed by two more steps, and each one maps to an API call:

What the palette doesAPI
Places the item and connects it to the SLINE under the insertion pointnew AssetAdder(database, true) and Add
Gives the item its type (InlineAsset, EndAsset, and so on)Asset.Type
Assigns the tag in the Assign Tag dialogFormatToolsUtil.AssignProperty
Places the class annotation after the tag is assignedStyleUtils.GetStyleIdFromName and PnIDAnnotation.Create

Step 1: place the item and connect it to the SLINE

The connection to the SLINE comes from the adder’s automation flag, the second constructor argument. With automation on, Add attaches the item to the SLINE at the insertion point: the line opens a gap around an inline item, and the gap follows the item when you drag it along the line. The insertion point must lie on the SLINE.

using Autodesk.ProcessPower.PlantInstance;

int row;
using (var adder = new AssetAdder(database, true))
{
    row = adder.Add(position, 0.0, "Ball Valve", new Scale3d(0.0));
}

ObjectId valveId = dlm.MakeAcDbObjectId(dlm.FindAcPpObjectIds(row).First.Value);

A Scale3d(0.0) asks for the style’s default scale. With automation off (new AssetAdder(database) and CreateConnectionAndNozzle = false) the item is placed on top of the SLINE and not connected to it, and the line stays whole. The setting that decides the connection is the automation flag and not Asset.Type, as the table shows for a Ball Valve placed on an SLINE:

RouteType afterOn the SLINE
new AssetAdder(db, true), then AddEndAssetGap opens, follows the grip drag
new AssetAdder(db) with CreateConnectionAndNozzle = false, then Add, then Type = InlineAssetInlineAssetNo gap at placement
new AssetAdder(db, true), then Add, then Type = InlineAssetInlineAssetGap opens, follows the grip drag

Step 2: set the type to match the palette

The palette and AssetAdder.Add do not assign the same Asset.Type for every class. The type follows the item’s class and not AssetStyle.JoinType:

ItemPaletteAssetAdder.Add
Ball Valve (hand valves family)InlineAssetEndAsset
Concentric, Eccentric, and plain Reducer (reducers family)EndAssetEndAsset

A Concentric Reducer whose style has an InLine join type is therefore an EndAsset from the palette as well as from code, because the class decides. Asset.Type is a public, writable property, so for classes the palette treats as inline, set it after Add:

using (var tr = database.TransactionManager.StartTransaction())
{
    var asset = (Asset)tr.GetObject(valveId, OpenMode.ForWrite);
    asset.Type = AssetType.InlineAsset;
    tr.Commit();
}

Step 3: assign the tag

The Assign Tag dialog writes each tag property through FormatToolsUtil.AssignProperty, and the same call works from code. Property names follow the class’s tag format: for hand valves, “Hand Valve Tag [Code-Number]” gives Code and Number, plus the combined Tag.

using Autodesk.ProcessPower.PnPCommonDbx;

var fmt = new FormatToolsUtil();
fmt.AssignProperty("HA",     "TargetObject.Code",   valveId);
fmt.AssignProperty("182",    "TargetObject.Number", valveId);
fmt.AssignProperty("HA-182", "TargetObject.Tag",    valveId);

Tags must be unique in the project. The dialog checks this, and the API call does not. Assign the tag before creating the label: a label created first reads HA-?.

Step 4: create the label

The class annotation is created with PnIDAnnotation.Create; the base class Annotation reaches the same implementation. The argument that matters is the style id, which must be the id of the style in the drawing you are editing. StyleUtils.GetStyleIdFromName returns it: it finds the style in the current drawing and, when the drawing does not have it yet, copies it in from the project.

using Autodesk.ProcessPower.PnPCommonDbx;
using Autodesk.ProcessPower.PnIDObjects;

ObjectId styleId = StyleUtils.GetStyleIdFromName("Valve Label", true, false);

if (styleId.IsNull || styleId.Database != database)
    return;

var annotation = new PnIDAnnotation();
annotation.Create(position, styleId, valveId, ObjectId.Null, true, false);

The style name is the annotation style assigned to the item’s class. The arguments after valveId are the ones the palette code passes: a null space id puts the annotation in the target’s space, true ignores the style’s auto-insert offset so the label lands at position, and false leaves the minimum-width option off. The palette calls it with no transaction open.

Do not take the style id from PnIdProject.GetAllStyles(DICTIONARY_ANNOTATIONSTYLES). That id belongs to the project’s style drawing (projSymbolStyle.dwg), not to the drawing being edited. Create cannot build the annotation from a style in another database, and instead of returning an error it terminates the host with an access violation, 0xc0000005, which managed code cannot catch. The database check in the snippet guards against it.

The result is an annotation linked to the valve by an AnnotationRelationship row, in the “Valve Label” style, with its text read from the tag. Moving or rotating the valve moves the label with it, as on a palette-placed valve.

The complete sequence

// 1. Place and connect to the SLINE
int row;
using (var adder = new AssetAdder(database, true))
{
    row = adder.Add(position, 0.0, "Ball Valve", new Scale3d(0.0));
}
ObjectId valveId = dlm.MakeAcDbObjectId(dlm.FindAcPpObjectIds(row).First.Value);

// 2. Type, for classes the palette treats as inline
using (var tr = database.TransactionManager.StartTransaction())
{
    var asset = (Asset)tr.GetObject(valveId, OpenMode.ForWrite);
    asset.Type = AssetType.InlineAsset;
    tr.Commit();
}

// 3. Tag
var fmt = new FormatToolsUtil();
fmt.AssignProperty("HA",     "TargetObject.Code",   valveId);
fmt.AssignProperty("182",    "TargetObject.Number", valveId);
fmt.AssignProperty("HA-182", "TargetObject.Tag",    valveId);

// 4. Label, with the drawing's own style id
ObjectId styleId = StyleUtils.GetStyleIdFromName("Valve Label", true, false);
if (styleId.IsNull || styleId.Database != database)
    return;

new PnIDAnnotation().Create(position, styleId, valveId, ObjectId.Null, true, false);

Run on an SLINE, the sequence gives the result of a palette drop: the line opens a gap around the valve, the tag and the “Valve Label” annotation are in place, and the gap and the label follow the valve when you drag it along the line.

A command places a Ball Valve on an SLINE, then the valve is dragged along the line: the gap in the line and the HA-190 label follow it

Questions? Reach out to ADN Support or leave a comment below.


Comments

Leave a Reply

Discover more from Autodesk Developer Blog

Subscribe now to keep reading and get access to the full archive.

Continue reading