The ADN team is hiring an DevTech engineer (C++, .NET) - based in our Bangalore office. Main responsibility is to work directly with third party developers to help them solve their problems using our APIs.
Click here to know complete job description. If interested, send your resumes to our lead recruiter Pooja Bhardwaj
Second is the section declaring our tooltips and parameters. One the first line here, because we want the script listed under the StraightNozzle and Nozzle categories, we include both.
@activate(Group="StraightNozzle,Nozzle", TooltipShort="Long Weld Neck", TooltipLong="Long Weld Neck", LengthUnit="in", Ports="2")
@param(D10=LENGTH, TooltipShort="Nominal Outside Diameter",TooltipLong="Nominal Outside Diameter")
@param(L1=LENGTH, TooltipShort="Overall Length.", TooltipLong="Overall Length")
@param(L2=LENGTH, TooltipShort="Hub Length", TooltipLong="Length of flange with weld neck.")
@param(OF=LENGTH, TooltipLong="Length between connection points.")
@param(B1=LENGTH, TooltipLong="Flange Thickness")
@param(D12=LENGTH, TooltipLong="Flange OD Width")
@param(D13=LENGTH, TooltipLong="OD of Weld neck")
Nozzle class shown by clicking advanced shape options and selecting nozzle.
To test the script, I made a tool palette icon with this in the macro portion: ^C^CPLANTREGISTERCUSTOMSCRIPTS;^C^C(command "arx" "l" "Pnp3dacpadapter");(testacpscript "nozflange");
Once the script is tested, we need to be able to test it in a catalog. For Plant 3D, all of the nozzles are read from a shared content folder path. You can find the path by running the spec editor as administrator, and then going to Tools > Modify Shared Content folder.
The default path is C:\AutoCAD Plant 3D 2016 Content\.
The default path for the nozzle catalog is "C:\AutoCAD Plant 3D 2016 Content\CPak Common\NOZZLE Catalog.acat"
To open the nozzle catalog, switch to the Catalogs tab, and go to open, and then browse to it. Once in the catalog you can click create new component and select your script. Fill in some sample data and save your catalog component and the catalog. Note that for flanged nozzles, your end type and pressure class must be set.
In a project drawing, create a piece of equipment, and then pick your nozzle.
You can change the nozzle orientation in the Change location tab.
Note that if you want the user to override the nozzle length, you should use the L parameter in your script. In this case (using L1), for a long weld neck, the catalog data will drive the length, and the user won’t be able to change it.
Because you can’t directly modify the nozzle catalog on a user’s computer without overwriting it, you should deploy a folder (eg CPak Custom Nozzles) with your nozzle catalog. The user then will be responsible to add the nozzles. Another option would be to deploy two catalogs, one with the stock nozzles + your custom nozzles, and one with just your custom nozzles. You could write an installer that lets the user pick which of the two installs they want. In either case, you should use a property like Design Pressure Factor to include unique information so that they can quickly filter to locate your nozzles.
Is there a simple way to have a single copy of my code configured to compile to different folders with different references based on the desired version of AutoCAD to be run ?
This is not strictly an AutoCAD API related query but it is very relevant to how we build plugins for AutoCAD. It is a common requirement that we add references from different paths based on the AutoCAD version for which we are building the plugin.
A simple way to get this working is to create separate build configurations in your Visual Studio solution.
Now, open the .csproj in a text editor and manually include the "Condition" for each of the references.
As an example, here is the change to include different versions of the interop assembly for Sheetset manager for each build configuration.
I am importing the DGN (not to the current drawing). This creates a new drawing and I want to switch to the new drawing and perform other commands. What is the best way to switch the current drawing to the other open drawing.
Here is a code snippet to do that.
The command that invokes the "DGNIMPORT", makes the newly imported document as Active. After the document is made active, you are in the new document’s context and to verify it, the editor.writemessage will output the message in the new document's command prompt.
When a material is assigned to the SubDMesh, the mapper (AcGiMapper) associated with the material takes care of most of the details involved in mapping the texture on to the SubDMesh. The mapper can be configured for its projection, scaling, translation, tiling and such parameters that affect the texture mapping. This approach is suitable for complex texture mapping and if your mapping fits one of the existing projection methods that AcGiMapper provides. In this blog post, we will look at another approach that relies on AcDbSubDMesh::setVertexTextureArray to set the texture mapping. If the SubDMesh was created using vertex and face information and you have an image that needs to be mapped, the "setVertexTextureArray" can be used to control the mapping.
Thanks to Erik Larsen from our AutoCAD engineering team for his help in getting the mapping right. Please note that the texture coordinates are normalized and does not depend on the actual image dimensions.
Here is a screenshot of an image mapped on to a box shaped SubDMesh.
Here is the code :
static void SubDMeshWithTexture()
AcDbSubDMesh *ptrMesh = new AcDbSubDMesh();
int imgWidth = 320;
int imgHeight = 160;
// We will create a box shaped SubDMesh
// The four side put together match the
// image width.
// This will let us fully wrap the image on the box shaped mesh.
int cx = imgWidth / 4;
int cy = cx;
int cz = imgHeight;
points=0.0; points=0.0; points=0.0;
points=cx; points=0.0; points=0.0;
points=cx; points=cy; points=0.0;
points=0.0; points=cy; points=5.0;
points=0.0; points=0.0; points=cz;
points=cx; points=0.0; points=cz;
points=cx; points=cy; points=cz;
points=0.0; points=cy; points=cz;
points=0.0; points=0.0; points=0.0;
points=0.0; points=0.0; points=cz;
// Face information as triangles
triangles=0; triangles=1; triangles=5;
triangles=0; triangles=5; triangles=4;
triangles=7; triangles=6; triangles=2;
triangles=7; triangles=2; triangles=3;
triangles=6; triangles=5; triangles=1;
triangles=6; triangles=1; triangles=2;
triangles=8; triangles=7; triangles=3;
triangles=8; triangles=9; triangles=7;
// Create the subDMesh using vertex and face data
Acad::ErrorStatus es = CreateSubDMesh
(10, 8, points,triangles, ptrMesh);
if(es == Acad::eOk)
AcDbObjectId meshId = AcDbObjectId::kNull;
AcDbDatabase *pDb = acdbCurDwg();
es = pSpaceRecord->appendAcDbEntity(
es = ptrMesh->close();
es = pSpaceRecord->close();
es = pBlockTable->close();
AcDbEntity* pEnt = NULL;
acdbOpenAcDbEntity(pEnt, meshId, AcDb::kForWrite);
// Create a material with our custom image
// as its texture
AcDbSubDMesh *pSubDMesh = AcDbSubDMesh::cast(pEnt);
if(pSubDMesh != NULL)
// Assign the material to the subDMesh
// Ensure the material mapping is as we expect
// fully wrapped around the faces
textureArray.append(AcGePoint3d(0.0, 0.0, 0.0));
textureArray.append(AcGePoint3d(0.25, 0.0, 0.0));
textureArray.append(AcGePoint3d(0.5, 0.0, 0.0));
textureArray.append(AcGePoint3d(0.75, 0.0, 0.0));
textureArray.append(AcGePoint3d(0.0, 1.0, 0.0));
textureArray.append(AcGePoint3d(0.25, 1.0, 0.0));
textureArray.append(AcGePoint3d(0.5, 1.0, 0.0));
textureArray.append(AcGePoint3d(0.75, 1.0, 0.0));
textureArray.append(AcGePoint3d(1.0, 0.0, 0.0));
textureArray.append(AcGePoint3d(1.0, 1.0, 0.0));
static Acad::ErrorStatus CreateSubDMesh(int numberOfPoints,
if((numberOfPoints <3) || (numberOfTriangles<1))
= new AcGePoint3dArray ();
= new AcArray();
for(int i=0; iappend(pt);
for(int i=0; iinsertAt(id,3);
es = ptrMesh->setSubDMesh(*vertexArray,*faceArray, 0);
static void CreateMaterial(
const ACHAR* name, AcDbDatabase *pDb)
es = pDb->getMaterialDictionary(
if (es == Acad::eOk)
double uScale = 1.0;
double vScale = 1.0;
double uOffset = 0;
double vOffset = 0;
mx(0, 0) = uScale;
mx(0, 1) = 0;
mx(0, 2) = 0;
mx(0, 3) = uScale * uOffset;
mx(1, 0) = 0;
mx(1, 1) = vScale;
mx(1, 2) = 0;
mx(1, 3) = vScale * vOffset;
mx(2, 0) = 0;
mx(2, 1) = 0;
mx(2, 2) = 1;
mx(2, 3) = 0;
mx(3, 0) = 0;
mx(3, 1) = 0;
mx(3, 2) = 0;
mx(3, 3) = 1;
= new AcDbMaterial();
es = pMaterialDict->setAt
(name, pMaterialObj, materialId);
Here is a short code snippet that creates an MLeader based on an existing MLeaderStyle. For the MLeader's text to correctly follow any changes made to the MLeaderStyle, it is necessary to clone the MLeaderStyle.DefaultText and use it as the MLeader's text. Creating a new MText without relying on the DefaultText can cause the text to not reflect any later changes that are made to the MLeaderStyle.
Thanks to Xin Xu from the AutoCAD engineering team for providing this tip.
public void MLTestMethod()
Editor ed = default(Editor);
ed = Application.DocumentManager.MdiActiveDocument.Editor;
PromptPointResult ppr1 = default(PromptPointResult);
ppr1 = ed.GetPoint(
new PromptPointOptions("Select start point"));
if (ppr1.Status != PromptStatus.OK)
PromptPointResult ppr2 = default(PromptPointResult);
ppr2 = ed.GetPoint(
new PromptPointOptions("Select end point"));
if (ppr2.Status != PromptStatus.OK)
Database db = HostApplicationServices.WorkingDatabase;
ObjectId myMLeaderId = ObjectId.Null;
using (Transaction trans
ObjectId myleaderStyleId = db.MLeaderstyle;
OpenMode.ForRead) as MLeaderStyle;
using (MLeader myMLeader = new MLeader())
myMLeaderId = myMLeader.ObjectId;
myMLeader.MLeaderStyle = db.MLeaderstyle;
int leaderIndex = myMLeader.AddLeader();
= mlstyle.DefaultMText.Clone() as MText;
if (myMText != null)
myMLeader.MText = myMText;
A PolygonMesh is an M x N mesh, where M represents the number of vertices in a row of the mesh and N represents the number of vertices in a column of the mesh.
A mesh can be open or closed in either or both the M and N directions. A mesh that is closed in a given direction is considered to be continuous from the last row or column on to the first row or column.
All the vertices in the mesh are stored in a single list. For a non-surface-fit mesh, the first N vertices are used to make up the first column, the second N vertices are used to make up the second column, and so on until all the vertices are used up (there do not have to be enough vertices to fully fill the M x N mesh).
For a surface-fit mesh, the surface density values are used in place of M and N for the vertex row x column sizes.
Mclosed This function sets the PolygonMesh to be closed in the M direction. This means that the mesh will be treated as continuous from the last row on to the first row. N Closed This function sets the PolygonMesh to be closed in the N direction. This means that the mesh will be treated as continuous from the last column on to the first column.
I can generate following mesh with 4 rows of vertices and 4 coloumns of vertices with M open and N open
To get Box like Polygon Mesh
public void TestSimpleMesh()
// Get the current document and database, and start a transaction
Database _database = HostApplicationServices.WorkingDatabase;
Document acDoc = Autodesk.AutoCAD.ApplicationServices.Application.DocumentManager.MdiActiveDocument;
Database acCurDb = acDoc.Database;
using (Transaction acTrans = acCurDb.TransactionManager.StartTransaction())
BlockTable acBlkTbl = acTrans.GetObject(_database.BlockTableId, OpenMode.ForRead) as BlockTable;
// Open the Block table record for read
BlockTableRecord acBlkTblRec = acTrans.GetObject(acBlkTbl[BlockTableRecord.ModelSpace], OpenMode.ForWrite) as BlockTableRecord;
// Open the Block table record Model space for write
// Create a polygon mesh
PolygonMesh acPolyMesh = new PolygonMesh();
/*M indicates No of rows and N indicates No of columns, visualize it as Grid
So to have cube, we need two rows of vertices and 4 colomns of vertices
No we need to close last column of vertices with first column of vertices that makes a simple cube or else planar surface with facets.
acPolyMesh.MSize = 2;
acPolyMesh.NSize = 4;
//What is N???
//What is M???
This function sets the PolygonMesh to be closed in the M direction.
This means that the mesh will be treated as continuous from the last row on to the first row.
// Add the new object to the block table record and the transaction
//Creating collection of points to add to the mesh
Point3dCollection acPts3dPMesh = new Point3dCollection();
acPts3dPMesh.Add(new Point3d(100, 100, 0));
acPts3dPMesh.Add(new Point3d(200, 100, 0));
acPts3dPMesh.Add(new Point3d(200, 200, 0));
acPts3dPMesh.Add(new Point3d(100, 200, 0));
acPts3dPMesh.Add(new Point3d(100, 100, 100));
acPts3dPMesh.Add(new Point3d(200, 100, 100));
acPts3dPMesh.Add(new Point3d(200, 200, 100));
acPts3dPMesh.Add(new Point3d(100, 200, 100));
//Converting those points to PolygonMeshVertecies and appending them to the PolygonMesh
foreach (Point3d acPt3d in acPts3dPMesh)
PolygonMeshVertex acPMeshVer = new PolygonMeshVertex(acPt3d);
// Save the new objects to the database
And following mesh is generated when makeNClosed, for details check in comments of above code;