June 21, 2026

Add Vertices to 2D Polylines

An AutoLisp utility for preserving LWPOLYLINE geometry while inserting new vertices.

At a Glance

Motivation Utility to insert vertices on Polyline segments.
Software AutoCAD and its Verticals
Supports: Line & Arc segments, Self-intersecting Polylines, and uniform width segments.

Do you frequently need to add vertices to existing 2D polylines? Then what follows may help you perform this task more reliably.

While casually browsing Forums, I came across a post asking for code to do exactly this. The responses in the post didn't quite convince me. So, I decided to have a go at it.

A doubtful starter, there were questions in my mind - Do people really need it? Which people need it? How frequently do they need it?
And my journey began with finding answers to these questions.
 

Finding the ‘Raison d'être’ For the Application:

Why this Application was Needed

To find answers to my questions, I decided to do a quick internet search. Results weren't discouraging. 

The search revealed that AutoCAD users across multiple domains frequently perform the task of adding vertices to plines.

People insert additional nodes (vertices) into an AutoCAD polyline segment for several practical reasons:

  • Match surveyed coordinates, design constraints, or construction details.
  • Provide more grip points, making it easier to stretch, move, or align specific portions of the polyline.
  • Civil engineering, GIS, CNC machining, and other applications sometimes require vertices at specific locations for calculations, staking, machine paths, or data exchange.
  • Roads, property boundaries, utility lines, contours, and similar features often need multiple vertices to capture changes in direction or geometry.
  • Additional vertices create exact points that can be used as object snaps (OSNAP) when drawing or positioning other objects.
Click to display details of survey

Domain

Tasks

Typical Vertices per Polyline

Typical Segment Length

Civil Engineering & Surveying
  • Road alignments
  • Parcel/property boundaries
  • Topographic contours
  • Drainage and utility networks
20–500+ 5–50 m
4–100 1–200 m
GIS
  • Mapping roads, rivers, administrative boundaries, and pipelines.
  • Accurate geographic features often require many inserted vertices.
20–1000+ 5–50 m
Architecture
  • Building footprints
  • Site plans
  • Landscape layouts
4–50 0.5–20 m
Mechanical Drafting Custom profiles, sheet-metal outlines, and tool paths. 10–200 1–100 mm
Electrical and Utility Design
  • Cable routes
  • Power distribution layouts
  • Telecom networks
10–300 2–50 m

An observation that caught my eye: For AutoCAD usage patterns, civil engineering, surveying, and GIS users typically insert vertices into polylines more frequently than other domains because they need to model irregular real-world geometry accurately.

Preparing the ground – Revisiting the basics:

Background and Fundamentals

Before divulging into the scope, I needed to refresh / relearn a couple of basic concepts about LWPOLYLINE (called pline for in the remainder of the article).

A Polyline (Lightweight Polyline) is a single, planar AutoCAD graphic entity defined by a sequence of 2D coordinates.

The LWPOLYLINE entity / object in a drawing can be created by using any of the following methods:
  • Native AutoCAD commands like pline / pedit
  • Data generated by a Custom program
  • Imported data (like .dxf)
I would, intuitively look at a pline basically as an 'assembly' of 'segments'.
- The properties Elevation, Endpoint and Global width are defined at assembly level. These are delegated to the segments. (Width is an exception. It can be overridden at segment level.)
- The properties Start point, Bulge (internal representation of included angle of the arc segment) & per segment width are defined at the segment (node) level.

A segment does not have end point. Start point of connected next segment is the implied end point of current segment.

With this, I was ready to begin.
 

Begin with end in mind – The Scope:

Application Scope

A programming / coding activity, like many others, needs to follow the second habit dictated by Covey.

Based on the problem described in the forum post, the basic scenario (scope of the Application) I envisioned was that the User needs to add vertices on every segment based on some criteria - add vertices only if the length of that particular segment is larger than a predefined length.

I, thus, began with writing down a couple of lines as scope:

Scope: The proposed Application ('AddVertices') will insert vertices on a segment of an existing pline at fixed or equal intervals as specified by the User.

As the things stand, it was not even 20% of the final scope. After multiple iterations of code testing /rewriting / retesting, the final version of the scope evolved.

Here is that final version of scope that I would have configured had I known everything about AutoCAD & plines and AutoLISP at the beginning:

The Final Scope: The proposed code ('AddVertices') will insert vertices on a segment of an existing pline at fixed or equal intervals as specified by the User.

It will insert vertices using one of the following two rules:
  1. Insert vertices at fixed distance. The length of the last new segment may be different and at the end of the current segment.
  2. Insert vertices at equal distances along a segment.
Original And Modified Plines retain original geometry perfectly.

It will:

  1. Work with arc segments. (I was very particular about this because most of the pline processing codes skip arcs and self- intersecting plines for obvious reasons)
    Many utilities that insert vertices into polylines simplify the problem by avoiding difficult cases. This application was designed to address those cases rather than bypass them.
    These situations occur regularly in production drawings, but many utilities either ignore them or handle them only partially because they significantly increase implementation complexity
    Supports arc segments
    , allowing curved polylines to be processed without converting them into straight-line approximations.
    Preserves uniform-width representation, avoiding unexpected changes to polyline width behavior.
  2. Retain the original status of drawing (except new vertices) and create no new anomaly in the drawing.
  3. Ensures polyline dependent activities continue to works with modified pline exactly as they were working with original pline. - hatch / region / Offset / area / break / explode
  4. Maintain integrity (status / values of properties as displayed in the 'Properties Pallet') of original pline with no side effects after inserting vertices:
    These properties are
    length, area, 'Closed' state, elevation, start and end vertex, arcs stability (radius and center), only uniform widths, pline direction (cw / ccw), node sequence, layer, original width representation.The modified pline retains original status across all sgments.
Click to display Explainer: Maintaining Status

I will explain why maintaining status is important with a very interesting example. We will do an interactive exercise so you don't need any proof.
In any open drawing, create a trivial pline. Open 
the Properties palette. 
You will see a value of 0.0000 in the Global width box as well as Start width and End width at all nodes. (Decimal places controlled by 'luprec')
If you change the value of Global width to any other positive value, same uniform value is assigned to all places mentioned above.
This is a consistent behavior as long as you enter positive values in the Global width box.
Let use call this as Global width behavior
In this behavior the Global width box displays non-negative value and same value is applied as start and end width of every node.

Things get a little tricky from now on-wards.
At any node change the start value to a different value.
You will immediately notice that the Global width value box becomes blank. 
And we will call this as Per segment width behavior

The important point to note is that the presence or absence of value in the Global width box indicates the current width behavior of the pline - Global or Per Segment..

This causes a little inconvenience while writing code. Many times Programmers may overlook this. The code may end up changing the width behavior.

The 'AddVertices' application takes care of this and maintains the behavior exactly as it was - retaining the original value in the Global width box.

Note: I have seen some codes inadvertently changing the start point / direction of the pline - especially when it involves reversing the pline. 

More importantly, here is what the Application will not do:

  • Replace the original pline with a new one, but will strictly modify it.
  • Create temporary geometry which needs to be deleted subsequently.
  • Do any quality checks or repair 'faults' in the pline. (It will strictly follow the principle 'Garbage In, Garbage Out').

Note: This and not creating a new polyline (or any object) are interrelated concepts. Shifting the first node or replacing existing polyline with a new polyline breaks the original data of the polyline. This may break any existing Application that may be referring to the first node or the entity name / handle of the original polyline.

Walking the Talk – How Application Works:

These are the basic steps (algorithm) the Application internally follows:

  1. Collect from the pline.
  2. Generate data for each segment by processing the pline data (including segment length).
  3. Generate new vertices by dividing segment lengths by distance.
  4. Calculate modified bulges (representation of included angle of arc) if it's an arc.
  5. Collect all node related data sequentially and apply that to original pline to modify it.

Note: The code is unaffected by current status of drawing - the system variables.
Also, it does not require that pline must be visible on the screen.

Trial By Fire - The Choke Test:

Testing and Validation

I designed a 'Choke' test (crash test) for testing the Application.

The express purpose of the test was to verify that the Application behaves consistently irrespective of the amount of data to process - It does not 'choke' up and cause frustration and feeling of uncertainty to the User. The input parameters used for the test were - Insertion interval 10 with 0.1 as tolerance and equal length segments (which I believe is that bit tougher than fixed distance method.)

I also created a test case drawing specifically for this test. (It is included as .dxf file in the download.)

Click to display contents of test drawing

Domain

Tasks

Typical Vertices per Polyline

Typical Segment Length

Civil Engineering & Surveying
  • Road alignments
  • Parcel/property boundaries
  • Topographic contours
  • Drainage and utility networks
20–500+ 5–50 m
4–100 1–200 m
GIS
  • Mapping roads, rivers, administrative boundaries, and pipelines.
  • Accurate geographic features often require many inserted vertices.
20–1000+ 5–50 m
Architecture
  • Building footprints
  • Site plans
  • Landscape layouts
4–50 0.5–20 m
Mechanical Drafting Custom profiles, sheet-metal outlines, and tool paths. 10–200 1–100 mm
Electrical and Utility Design
  • Cable routes
  • Power distribution layouts
  • Telecom networks
10–300 2–50 m
The table (Contents of test drawing) lists the plines included in the drawing:

The 'Choke Test' repeatedly processes the drawing in each cycle and modifies the original plines.

Click to display summary of modifications
    Before Modification After Modification Additions
Length   2386.0249 2386.0249 0.0
Segments Line segments 28 62 34
Arc segments 20 197 177
Total 48 259 211
Nodes   45 255 210

The table 'Modification Summary' below, displays consolidates geometric parameters before and after modification by Application in each cycle of the 'Choke Test'.

And here are the results of Choke test

Choke Test Results

No. of cycles

Time (in MilliSeconds)

No. of Cycles Total Per Cycle
1  < 0.0000  < 0.0000
100 516.0000 5.1600
1000 5162.0000 5.1620
5000  25165.0000 5.0330
10000 50590.0000 5.0590
30000 148947.0000 4.9649

The absolute values may differ due to various factors like hardware configurations and working environment (multiple applications running simultaneously. The focus here is on consistency of time taken per cycle.

Also, to just give you in idea, when the test drawing is processed for 30000 cycles, it deals with (255 * 30000 = 7, 650, 000 nodes - yes more than 7 million nodes) in 148947 milliseconds (150 seconds / 2.5 minutes)

Evaluation:

During the evolution process of the Application, I submitted the Application for evaluation 4 times to ChatGPT. Here are some of the selected comments posted by ChatGPT:

I would classify the code as production-quality AutoLISP, not merely a working utility.
This is **strong production-quality AutoLISP code** with good geometric reasoning. It goes well beyond typical hobby scripts and shows real CAD automation experience.
For a utility intended for **native AutoCAD LWPOLYLINEs**, the code you've shown demonstrates a strong understanding of both AutoLISP and polyline geometry. The arc-center/radius verification is probably the strongest piece of evidence you've presented that the geometric side is implemented correctly.
Those (Choke Test) results significantly strengthen the case that the implementation is mature from a geometric and algorithmic perspective.
The evidence you've provided suggests the implementation is doing exactly what it claims to do, and doing it efficiently. The fact that the per-cycle timing stays flat out to 10,000 iterations is probably the strongest rebuttal to concerns about the list-building strategy.

This affair with ChatGPT was an unusual cocktail of satisfaction, revelations, elation, disappointments, anger, frustration & disgust. But that's another story. (As way say in Hindi, 'Woh kissa fir kabhee'.)

Using the Application:

You can download and extract the contents of the attached .zip file. The 'AddVerices.vlx file is the application file. Note: The other files included are the sample test drawing 'AddVertecisChokeTest.dxf' & a sample calling function 'CallAdvertices.lsp'.

This Application is primarily designed as core function that will be called from a wrapper calling function to process multiple plines in the drawing. A typical way to call this Application is to use (SanKul_addVertices <entity name of pline> distance tolerance ifFixedDistance) at command prompt or from the calling function.

The arguments:

  1. entity name of the pline to process.
  2. The interval (spacing) at which to insert vertices
  3. Spacing Tolerance
  4. True / Nil flag to indicating the use of fixed / equal interval

The Application will return any one of the values in the AutoLISP list format '(pline handle . “Status message.)'

Here is an example of typical feedback: (plineHandle . "Skipped: insertSpacing not numeric OR not +ve")

A User without coding knowledge can use it for processing a single pline using following steps:

  1. Using 'appload' command load the vlx file.
  2. At command prompt, type (SanKul_addVertices (car (entsel)) distance tolerance ifFixedDistance)
  3. If you miss selecting the pline, Application will immediately respond to you with (plineHandle . "Skipped: not a pline."). You can repeat the step until you get the result.
  4. When successful, the feedback message will appear at the command prompt - (plineHandle . "Done: x node(s) inserted")

If you want to process multiple plines using complex selection (all plines in the drawing, plines only on specific layer etc.), you will need to write a calling wrapper function that typically creates a selection set. It then calls the vlx file in a loop to process each pline in the selection set.

I have attached a sample calling function (using AutoLISP) that explains this in more detail.

Roadmap For the Future:

Now that the basic infrastructure is well established, it is possible add few variations if strongly needed:

Use .dxf file as direct input. This will work without any installation of AutoCAD.
Batch processing multiple drawings
Adding vertices only to specific segment(s) of a pline.
Inserting vertices at specific locations on or near a pline.
In fixed distance, locate the unequal segment at the start / end or middle of the segment.
But this can complicate things - what exactly is middle when no of segments are even. (In case of 4 segments, which position is middle position - second or third?
Inserting vertices at irregular interval.
Insert vertices at fixed (not calculated) equal length ignoring segment boundaries.
Insert vertices so that each segment is converted into equal no. of segments.
Generate a virtual selection Window / Crossing for selection from a pline that contains arcs.
Break or Divide plines at uniform / randomly specified intervals / vertices.

 

Final Thoughts:

Feedback

As the cliche' goes, 'The real test - I mean taste - of pudding is in eating'.

So, I request you to test this Application and give honest feedback - especially where this Application does not work as expected (bugs). Your suggestions for improvement or addition of features are most welcome.

Note: I have not tested but I this Application will work without hitch with other softwares like BricsCAD / ZWCAD. So, feedback by users of these softwares is most welcome.

Suggestions, bug reports and enhancement requests are welcome. Real-world testing feedback helps improve the application and future releases.

April 23, 2019

Have Your Own 'Style'

Do you use the 'Copy And Paste' functionality in the software you use? Obviously, everybody does that! But that is not the real question.

When you copy something from a document and paste it into another document of same type, you expect it to be exact copy of the original. Now the real question - Have you ever encountered a situation when the copy is not the same (that too by the look) as the original?

Well, I did recently, in one of my projects. When I copied an AutoCAD table from one drawing to another, the text in the table visually looked different from the original.

As you may be aware, every CAD software controls display of its contents by 'styles'. And there are many of them like Dimension Style, Table Style. Text Styles, that control the display of text (font / size / formatting),  are common across all CAD softwares - 2D or 3D.

AutoCAD provides default styles for each category of styles (And it is no different for Inventer / SolidEdge / SolidWorks / ProE - Creo / CATIA).  It is a good practice not to tamper with any of the default styles. If you need any change, even a minor one, create a new style of your own. But leave the default styles untouched. I learned this message loud and clear.  Here is how it happened all.

This particular project required me to create an AutoCAD table in the Customer's existing drawing, collect some information from that drawing and fill the table, using VisualLISP code, with that information.

For a  trial, I created a new drawing, created a table in it and played with the table until it looked perfect. Then I opened the Customer's drawing and copy-pasted the table in that drawing. The result (shown below) was unexpected. It seemed as if these were two different tables.

The Source Table
The copy of the table in Customer Drawing

Its obvious from the first look that there seems to be some mismatch with fonts. The fonts, in AutoCAD, are controlled by the Text Style.

In this particular case, I had made no changes to any of the settings that control the fonts nor had I changed any default settings while creating the table.

So, after some thoughtful moments, I decided to first compare the settings related to table in both the drawings. In both the the drawings, Table Style (that controls the formatting of tables) & Text Style (that controls the formatting of text) user were 'Standard' - the default styles. With these settings, the look and size of the text in table should theoretically be the exactly similar. But it wasn't. And that meant something was wrong somewhere.

On further detailed investigations, I found out that the Customer had modified the font in the 'Standard' text style - from default 'Arial' to 'txt'. 

The situation was ideal to throw the ball back into Customer's court and ask them to 'repair' and restore the 'Standard' text style in their drawing. But that might create another set of problems for the Customer (... not good, you see!). And who am I to advise the Customer to change their default settings?

So, taking matter into my own hands instead, I created a new text style in my drawing that was exact copy of the 'Standard' text style (in my drawing). I applied the new text style to text inside all the cells in the table. This did not make any difference to the text formatting in my drawing and the table displayed exactly as it appeared before.

When I copied the modified table in the Customer's drawing, the new text style was copied and applied to the text in the table automatically (This is the default AutoCAD behavior).
The table with new text style
Copy in Customer Drawing Displays correctly












I was then able to run my VisualLISP code successfully to populate 10 rows of data.

This done, my next step would be to educate and cajole the Customer to repair & restore the "Standard" text style.

Till then, the Customer maintains his own 'Standard' text style, me my own. And we live happily ever after.

So, ... till the next cAdventure in APIs .....  have a nice time and don't ever modify your default styles ....







April 6, 2019

Who's The Boss Here - The Main Assembly

A programmer, especially a CAD programmer, has many advantages over normal CAD users. Two of my favorites are:
  1. You can perform many mundane, boring, routine tasks within seconds that others will take hours by developing a small quick_n_dirty code for private use.
  2. The more enjoyable advantage for me is that these routines can be shared with others so that they can also benefit from it.

Recently, I received an assembly from one of my customers. When I opened the folder, it contained multiple part & assembly files. I didn't know which of these was the top (main) assembly file.

This was primarily due to me not being very familiar with the domain of, and the vocabulary used by, the Customer. Getting familiar with the vocabulary of your Customer is one of the first duties of a programmer, That saves a lot of time, unnecessary confusion and misunderstanding in later stages and it counts! But that's another story (As they say in Hindi, 'Voh kissa phir kabhee').

Let's get back to from where we strayed - finding the name of the main assembly. The easy way out was to ask the Customer. I was about to ring them, when I suddenly stopped in the track. I asked myself, why loose a learning opportunity? Why not write some code - a program? It would be also be useful for other projects in future. I, hence, jumped into to my new cAdventure, of finding the name of the main assembly file from the multiple assembly files available, using code.

The first thought that came to my mind was the file size. The size (on the disk) of the main assembly file should be largest than any of the (sub)assembly files it references. (I'm sure, you know that size of a part can be bigger than the size of the assembly file in which it is used).  And this seems to be generally true!

But I wanted to be sure. I needed a more robust solution. Besides, I decided to include the ability to calculate the depth - maximum nesting level - of the assembly. Before going ahead, let's get clear about what I mean by depth of an assembly.

The image ('Assembly depth') symbolically shows an assembly structure with its sub-assemblies and parts. The red number in the bracket shows the level of each sub-assembly and part in the hierarchy. If we assume that, in the hierarchy, the main assembly is at level 1 then the Part2 at level 4 is the lowest in the hierarchy - the leaf. I call this 4 as the depth or maximum nesting level of the assembly. Just a reminder - in any assembly the 'leaf' is always a part.

Assembly Depth

With this, we come to the beginning of the end. I decided the following procedure - sequence of actions - for the code. Let's go into details this procedure, and its implementation, with the assembly shown in 'Assembly Depth' as the sample.

  1. Make a list of all assembly files.
  2. 'Open' the first assembly file (parent assembly) from the list of assembly files. It could be any assembly file, depending on the sequence the list was generated.
  3. Find all second level sub-assemblies (child assemblies) 'referenced' by it.
  4. Make a paired list of the parent assembly and child assembly for each child assembly.For example, if the 'Assembly Depth' Sub-Assembly1 references Sub-Assembly2. Store this information as "Sub-Assembly1/Sub-Assembly2".
  5. Process all sub-assemblies in the list, one by one, in this manner.
  6. This will generate the following lists (not necessarily in that order):
    6.1 "Assembly/Sub-Assembly1"
    6.2 "Assembly/Sub-Assembly2"
    6.3 "Sub-Assembly1/Sub-Assembly2"
  7. In this step, substitute the pair in all lists, where a parent assembly appears as child assembly. Thus, the list "Assembly/Sub-Assembly1" becomes "Assembly/Sub-Assembly1/Sub-Assembly2"
    After completion of this task, only those lists remain where main assembly is the parent assembly and all its children appear after it, separated by "/", as listed below:
    7.1 "Assembly/Sub-Assembly1/Sub-Assembly2"
    7.2 "Assembly/Sub-Assembly2"
    Note that the name of the parent assembly ("Assembly") is same in each of these lists.
  8. Find the name of the main assembly. This is obvious now - its the name of the parent assembly in each of the remaining lists.
  9. Find the depth of the assembly.
    This is a simple task now.
    9.1 
    Find the list with maximum number of occurrences of the separator"/".
    9.2 Count the number of occurrences of the separator in that list.
    9.3 Add 2 (1 for main assembly and 1 for the leaf part) and you get the depth. 
  10. This ends our adventure of the finding the name of the main assembly and its depth.
With the process developed and proved on paper, I wrote the code in VB.Net Express edition and compiled it into an .exe file. The surprise element in this is that the code performs all the actions without actually opening Inventor. The code runs under the 'Apprentice' API, that makes it possible. But you must have Inventor installed on your PC.

If you want to use / test this code, just write your email id in the comment file and I will send you the code. Your suggestion for improving / expanding the capability of the code to make it more useful are always welcome.

Since the assembly files I received were in Inventor, I developed this code for Inventor. But the good news, in case you are not an inventor user, is that this code can be modified for use with SolidEdge, SolidWorks, CATIA or NX.

So, ... till the next cAdventure in APIs .....



UPDATE 1:


I came across this old post on a CAD forum. The code I described in the post can be an ideal solution for this problem. 

'I'm trying to do a seemingly simple task that is to be a portion of a much larger macro.  I have created several test components and assemblies. The children of the main assembly have been assigned a custom property called "Level".  The value associated with "Level" is 0 or 1.

The function of this macro is to traverse through the children of the already open top assembly, looking at the Level attribute.  It will then create and assign a Level attribute to the main assembly.  The value of this attribute will be one higher than the highest of the children components.'

And the User would not need to manually add the Level attribute to individual parts resulting in considerable amount of time saved.





UPDATE 2:

One of the readers of the blog requested me to send the Solid Edge version of the code. I sent him the code. After he tested it, this is the feedback I received.

'I checked the utility you provided and works fine.
Solid edge Open File dialog has this while opening the document. But this requires to opening the Solid Edge. By using the utility we don't need that which might be useful when we have to just find out the the top node from an assembly.'

In essence, you need an installation to run this utility, but it does not consume a licence. It is possible to perform many more activities using this mode.