Processing DXF files and generating layer images using Python

In this tutorial, we will explain how to process layer names, entities, and coordinate information found in DXF files using Python and how to draw layer images from this information in a DXF file.

This process consists of two stages:

  • Saving the information from the DXF file to a dictionary structer using the ‘ezdxf’ library,which provides tools for reading and creating DXF files.
  • Generating images using the information from the dictionary with the ‘matplotlib’ library.

You’ll need to install this libraries if you haven’t already. You can install it using pip:

pip install ezdxf regex

‘Regex’ (Regular Expressions) is a powerful character string matching technique used to define, find, and manipulate specific patterns within text. The reason we use the Regex library is to remove unnecessary characters other than the layer names in dictionary format.

For more information about ‘ezdxf’ and ‘regex’:

Let’s examine the information in a simple DXF drawing.

In the DXF drawing, the layer names are set as ‘<entity>_layer.’ For example, ‘Circle_layer.’ Each entity has been assigned a unique color. The description created using MTEXT specifies which entity corresponds to which color.

DXF drawing created with basic shapes. Layer names are visible in the top left. 

First, let’s import the necessary libraries.

import ezdxf
import re
import matplotlib
import numpy as np

We perform the reading process of the DXF file using the ‘ezdxf’ library.

file = 'Drawing.dxf'

try:
  doc = ezdxf.readfile(file)
except IOError:
  print(f'Not a DXF file or a generic I/O error.')
  sys.exit(1)
except ezdxf.DXFStructureError:
  print(f'Invalid or corrupted DXF file.')
  sys.exit(2)

After performing the file reading process, we will move on to the stage of creating a dictionary. But first, let’s talk about the format of the dictionary.

Dictionary Format

For the DXF Dict we will create, first, we will define the layer names. After defining the layer names, we will define the ‘Context’ and ‘Info’ values in the Content section. ‘Context’ will contain information about how many of each entity is inside the layer. ‘Info’ will contain further details about the entities , including their coordinate information.

To read the layer names, we iterate through the ‘doc.layers’ object in a for loop and add the names of each layer to the dictionary.

dxf_dict = {}
for layer in doc.layers:          
  name = layer.dxf.name
  dxf_dict[name]={"Context": 0,"Info": 0}

The output of the code is as follows:

{'0': {'Context': 0, 'Info': 0},
'Circle_layer': {'Context': 0, 'Info': 0},
'Polyline_layer': {'Context': 0, 'Info': 0},
'Spline_layer': {'Context': 0, 'Info': 0},
'Arc_layer': {'Context': 0, 'Info': 0},
'Lwpolyline_layer': {'Context': 0, 'Info': 0},
'Line_layer': {'Context': 0, 'Info': 0},
'Text_layer': {'Context': 0, 'Info': 0},
'----': {'Context': 0, 'Info': 0},
'TUB-UTQG-E37-NO': {'Context': 0, 'Info': 0},
'Mtext_layer': {'Context': 0, 'Info': 0},
'Defpoints': {'Context': 0, 'Info': 0}}

We have retrieved the layer names. The next step is to query the entities within the layers.

The entities to be queried are as follows:

  • Circle
  • Spline
  • Line
  • Lwpolyline
  • Polyline
  • Arc
  • Text
  • Mtext

We have retrieved the layer names. The next step is to query the entities within the layers.

The entities to be queried are as follows:

  • Circle
  • Spline
  • Line
  • Lwpolyline
  • Polyline
  • Arc
  • Text
  • Mtext

First, let’s create the contents of ‘Context.’ ‘Context’ will contain information about how many of each entities there are. We will use the ‘layer_query’ function to perform this process.

# Define a function named "layer_query." This function can be used to
# query objects on a specific layer in AutoCAD drawings.

def layer_query(layer):
  # Create an empty list. This list will be used to store the query results.
  list_query = []

  # Create an empty dictionary. This dictionary will be used to calculate how many times each query result is repeated.
  query_number = {}

  # Get the modelspace for AutoCAD drawings.
  msp = doc.modelspace()

  # Create a query that queries objects in the specified layer.
  query = msp.query('*[layer==\'' + layer + '\']i')

  # Create a loop to iterate through the query results.
  for i in range(len(query)):
      # Use a regular expression to remove parentheses and square brackets from the text in the query result.
      string = re.sub("[\(\[].*?[\)\]]", "", str(query[i]))

      # Add the cleaned text to the "list_query" list.
      list_query.append(string)

  # Create another loop to iterate through the query results in the list.
  for i in list_query:
      # Use a dictionary to count how many times each query result appears.
      query_number[i] = list_query.count(i)

  # Finally, return a dictionary containing the counts of query results.
  return query_number

An example of using the ‘layer_query’ function:

dxf_dict['Arc_layer']['Context'] = layer_query('Arc_layer')

Output of dxf_dict[‘Arc_layer’][‘Context’]:

{'ARC': 3}

We can see ‘Arc_layer’ has 3 ARC entities.

Now, let’s create the ‘Info’ part. For this part, we first use the ‘layer_query’ function to determine which entities are inside the layer. Once the entities are identified, since each entity has its own unique objects and parameters for drawing, we extract coordinate information using separate functions for each entity that we will use in the drawing.

Below are functions specific to each entity.

def find_polyline(layer):
    # Initialize an empty dictionary to store polyline information.
    dics_element = {}
    # Initialize an empty list to store polyline vertex lists.
    polylines_list = []
    # Get the modelspace of the document.
    msp = doc.modelspace()
    # Query all polylines in the modelspace.
    polylines = msp.query('POLYLINE')
    # Filter polylines based on the provided layer.
    query = polylines.query('*[layer==\'' + layer + '\']i')
    k = 0
    # Iterate through the filtered polylines.
    for i in range(query.__len__()):
        corner_point = []
        for poly in query:
            # Get the vertices of the current polyline.
            polylines_list = (query[i].vertices)
        # Iterate through the vertices of the polyline.
        for j in polylines_list:
            corner_point.append(j.dxf.location)
        # Add the corner points to the dictionary.
        dics_element[k] = corner_point
        k += 1
    # Return the dictionary containing polyline information.
    return dics_element

def find_circle(layer):
    # Initialize an empty list to store circle information.
    circle_info = []
    # Get the modelspace of the document.
    msp = doc.modelspace()
    # Query all circles in the modelspace.
    circles = msp.query('CIRCLE')
    # Filter circles based on the provided layer.
    query = circles.query('*[layer==\'' + layer + '\']i')
    
    # Iterate through the filtered circles.
    for circle in query:
        # Get the center point and radius of each circle.
        center_point = circle.dxf.center
        radius = circle.dxf.radius
        # Append the center point and radius to the circle_info list.
        circle_info.append([center_point, radius])

    # Return the list containing circle information.
    return circle_info

def find_line(layer):
    # Initialize an empty list to store line information.
    line_info = []
    # Get the modelspace of the document.
    msp = doc.modelspace()
    # Query all lines in the modelspace.
    lines = msp.query('LINE')
    # Filter lines based on the provided layer.
    query = lines.query('*[layer==\'' + layer + '\']i')
    
    # Iterate through the filtered lines.
    for line in query:
        # Get the start and end points of each line.
        start_point = line.dxf.start
        end_point = line.dxf.end
        # Append the start and end points to the line_info list.
        line_info.append([start_point, end_point])

    # Return the list containing line information.
    return line_info


def find_text(layer):
    # Initialize an empty list to store text information.
    text_info = []
    # Get the modelspace of the document.
    msp = doc.modelspace()
    # Query all text entities in the modelspace.
    texts = msp.query('TEXT')
    # Filter text entities based on the provided layer.
    query = texts.query('*[layer==\'' + layer + '\']i')
    
    # Iterate through the filtered text entities.
    for text in query:
        # Get the text content and position of each text entity.
        text_content = text.dxf.text
        text_position = text.get_pos()
        # Append the text content and position to the text_info list.
        text_info.append([text_content, text_position])

    # Return the list containing text information.
    return text_info

def find_mtext(layer):
    # Initialize an empty list to store MTEXT information.
    text_info = []
    # Get the modelspace of the document.
    msp = doc.modelspace()
    # Query all MTEXT entities in the modelspace.
    texts = msp.query('MTEXT')
    # Filter MTEXT entities based on the provided layer.
    query = texts.query('*[layer==\'' + layer + '\']i')
    
    # Iterate through the filtered MTEXT entities.
    for text in query:
        # Get the text content and insertion point of each MTEXT entity.
        text_content = text.text
        insertion_point = text.dxf.insert
        # Append the text content and insertion point to the text_info list.
        text_info.append([text_content, insertion_point])

    # Return the list containing MTEXT information.
    return text_info

def find_lwpolyline(layer):
    # Initialize an empty dictionary to store LWPOLYLINE information.
    dics_element = {}
    # Initialize an empty list to store LWPOLYLINE vertex lists.
    polylines_list = []
    # Get the modelspace of the document.
    msp = doc.modelspace()
    # Query all LWPOLYLINE entities in the modelspace.
    polylines = msp.query('LWPOLYLINE')
    # Filter LWPOLYLINE entities based on the provided layer.
    query = polylines.query('*[layer==\'' + layer + '\']i')
    k = 0
    # Iterate through the filtered LWPOLYLINE entities.
    for i in range(query.__len__()):
        corner_point = []
        for poly in query:
            # Get the vertices of the current LWPOLYLINE in world coordinates.
            polylines_list = (query[i].vertices_in_wcs())
        
        # Iterate through the vertices of the LWPOLYLINE.
        for j in polylines_list:
            corner_point.append(j)
        # Add the corner points to the dictionary.
        dics_element[k] = corner_point
        k += 1
    # Return the dictionary containing LWPOLYLINE information.
    return dics_element

def find_arc(layer):
    # Initialize an empty list to store ARC information.
    arcs_info = []
    # Get the modelspace of the document.
    msp = doc.modelspace()
    # Query all ARC entities in the modelspace.
    arcs = msp.query('ARC')
    # Filter ARC entities based on the provided layer.
    query = arcs.query('*[layer==\'' + layer + '\']i')
    
    # Iterate through the filtered ARC entities.
    for arc in query:
        # Get the center point, radius, start angle, and end angle of each ARC.
        center_point = arc.dxf.center
        radius = arc.dxf.radius
        start_angle = arc.dxf.start_angle
        end_angle = arc.dxf.end_angle
        # Append the arc information to the arcs_info list.
        arcs_info.append([center_point, radius, start_angle, end_angle])

    # Return the list containing ARC information.
    return arcs_info

def find_spline(layer):
    # Initialize an empty dictionary to store SPLINE information.
    dics_element = {}
    # Initialize an empty list to store SPLINE control point lists.
    sp_info = []
    # Get the modelspace of the document.
    msp = doc.modelspace()
    # Query all SPLINE entities in the modelspace.
    splines = msp.query('SPLINE')
    # Filter SPLINE entities based on the provided layer.
    query = splines.query('*[layer==\'' + layer + '\']i')
    k = 0
    # Iterate through the filtered SPLINE entities.
    for sp in query:
        # Get the control points of each SPLINE.
        sp_info.append([sp.control_points])
    # Iterate through the SPLINEs with control point information.
    for i in range(len(sp_info)):
        control_point = []
        # Iterate through the control points of each SPLINE.
        for j in range(len(sp_info[i][0])):
            control_point.append(sp_info[i][0][j])
        # Add the control points to the dictionary.
        dics_element[k] = control_point
        k += 1
    # Return the dictionary containing SPLINE information.
    return dics_element

Let’s create a simple example for the ‘Arc_layer.’ We had previously created the dxf_dict[‘Arc_layer’][‘Info’] value and assigned it as 0. Now, we will assign the coordinate information of the entities within the layer.

# Create a list of entity types to search for in the layers.
entities_to_find = ['POLYLINE', 'CIRCLE', 'LINE', 'TEXT', 'MTEXT',
                'LWPOLYLINE', 'ARC', 'SPLINE']

# Query information for the 'Arc_layer' and store it in dxf_dict.
dxf_dict['Arc_layer']["Info"] = layer_query('Arc_layer')

# Create a list of entity types to search for in the layers.
entities_to_find = ['POLYLINE', 'CIRCLE', 'LINE', 'TEXT', 'MTEXT',
                'LWPOLYLINE', 'ARC', 'SPLINE']

# Query information for the 'Arc_layer' and store it in dxf_dict.
dxf_dict['Arc_layer']["Info"] = layer_query('Arc_layer')

# Loop through the entity types to find and process each one in the 'Arc_layer'.
for entity in entities_to_find:
  # Check if the current entity type exists in the layer's information dictionary.
  if entity in dxf_dict['Arc_layer']["Info"].keys():
      # Call the corresponding 'find_' function dynamically and store the result.
      dxf_dict['Arc_layer']["Info"][entity] = globals()['find_' + entity.lower()]('Arc_layer')

The output of dxf_dict[‘Arc_layer’][‘Info’] is as follows:

# Create a list of entity types to search for in the layers.
entities_to_find = ['POLYLINE', 'CIRCLE', 'LINE', 'TEXT', 'MTEXT',
                'LWPOLYLINE', 'ARC', 'SPLINE']

# Query information for the 'Arc_layer' and store it in dxf_dict.
dxf_dict['Arc_layer']["Info"] = layer_query('Arc_layer')

# Create a list of entity types to search for in the layers.
entities_to_find = ['POLYLINE', 'CIRCLE', 'LINE', 'TEXT', 'MTEXT',
                'LWPOLYLINE', 'ARC', 'SPLINE']

# Query information for the 'Arc_layer' and store it in dxf_dict.
dxf_dict['Arc_layer']["Info"] = layer_query('Arc_layer')

# Loop through the entity types to find and process each one in the 'Arc_layer'.
for entity in entities_to_find:
  # Check if the current entity type exists in the layer's information dictionary.
  if entity in dxf_dict['Arc_layer']["Info"].keys():
      # Call the corresponding 'find_' function dynamically and store the result.
      dxf_dict['Arc_layer']["Info"][entity] = globals()['find_' + entity.lower()]('Arc_layer')

The entity information and the list of coordinates for arcs are displayed.


dxf_dict = {}

# Iterate through the layers in the 'doc' object and collect information for each layer.
for layer in doc.layers:
   # Get the name of the current layer.
   name = layer.dxf.name
   # Initialize a dictionary to store context and information for the layer.
   dxf_dict[name] = {"Context": 0, "Info": 0}

# Define a list of entity types to search for in each layer.
entities_to_find = ['POLYLINE', 'CIRCLE', 'LINE', 'TEXT', 'MTEXT',
                 'LWPOLYLINE', 'ARC', 'SPLINE']

# Iterate through each layer in the 'dxf_dict'.
for i in dxf_dict:
   # Query and store context information for the current layer.
   dxf_dict[i]["Context"] = layer_query(i)
   # Copy context information to the "Info" field for the current layer.
   dxf_dict[i]["Info"] = layer_query(i)

   # Iterate through the list of entity types to find and process each one in the layer.
   for entity in entities_to_find:
       # Check if the current entity type exists in the layer's information dictionary.
       if entity in dxf_dict[i]["Info"].keys():
           # Call the corresponding 'find_' function dynamically and store the result.
           dxf_dict[i]["Info"][entity] = globals()['find_' + entity.lower()](i)

In the end, we have gathered all the necessary information from the DXF file into a dictionary. Now, let’s move on to the part where we create layer images.

TEXT

We record the content, location information, and alignment side information of TEXT entities in a dictionary during the query of TEXT entities.

dxf_dict[‘Text_layer’][‘Info’][‘TEXT’] output:

[['Python', ('LEFT', Vec3(9296.24788282276, 7096.060899422574, 0.0), None)]]

MTEXT

We record the content and location information of MTEXT entities in a dictionary during the query of MTEXT entities.

dxf_dict[‘Mtext_layer’][‘Info’][‘MTEXT’] output:

[['-Red represent SPLINE\\P-Purple represent LINE\\P-White represent CIRCLE\\P-Yellow represent LWPOLYLINE\\P-Blue represent ARC\\P-Grenn represent POLYLINE\\P-Orange represent TEXT',
  Vec3(-3905.380210506795, 9638.475573190603, 0.0)]]

Layer Images

We will use the matplotlib library to create a layer image. We will use specific drawing methods for each entity in the layer.

SPLINE

We are saving the corner point information for the ‘SPLINE’ entity in a dictionary using the ‘ezdxf’ library. Let’s take a look at the ‘SPLINE’ entity within the ‘Spline_layer’ layer, for example.

dxf_dict[‘Spline_layer’][‘Info’][‘SPLINE’] output :

{0: [(2241.583293238488, 9987.0253792767, 0.0),
  (3151.50263668419, 10257.88012833276, 0.0),
  (4762.591543377077, 10737.45129546154, 0.0),
  (4981.630699827892, 7526.755718842934, 0.0),
  (1753.330011533589, 8016.563714802856, 0.0),
  (2103.068470600002, 9425.008477912386, 0.0),
  (2243.523835380089, 9990.641504097784, 0.0)]}

The coordinate information of the corner points is being displayed. Now, we will use this coordinate information to create an image using matplotlib.

# Create a new figure and axis for the plot
fig, axs = plt.subplots()

# Access the 'Spline_layer' data from the 'dxf_dict' dictionary and retrieve the 'SPLINE' information
inputs = dxf_dict['Spline_layer']["Info"]["SPLINE"]

# Create an empty list to store spline data
list_ent = []

# Iterate through the spline data and append it to the list
for i in range(len(inputs)):
    list_ent.append(inputs[i])

# Convert the list to a numpy array with dtype 'object'
nparray = np.array(list_ent, dtype=object)

# Set the line width for plotting
linewidth = 1

# Iterate through each spline in the numpy array
for corner in nparray[:]:
    x = list()
    y = list()

    # Extract x and y coordinates from the corner data
    for i in corner:
        x.append(i[0])
        y.append(i[1])

    # Convert x and y coordinates to numpy arrays
    x = np.array(x)
    y = np.array(y)

    # Plot the spline with solid black lines and the specified line width
    plt.plot(x, y, linestyle='solid', color='black', linewidth=linewidth)

# Set the aspect ratio of the plot to be equal
axs.set_aspect('equal')

# Turn off the axis labels and ticks
plt.axis('off')

# Display the plot
plt.show()

Output of this code :

Spline

POLYLINE

The drawing of POLYLINE is identical to SPLINE. The corner points are connected to create an image.

dxf_dict[‘Polyline_layer’][‘Info’][‘POLYLINE’] output:

{0: [Vec3(7485.800443135727, 7492.620127992838, 0.0),
  Vec3(6252.142275415681, 5013.314385502197, 0.0),
  Vec3(10011.35107668269, 5007.893603037317, 0.0),
  Vec3(8754.134525719237, 7496.292648878055, 0.0),
  Vec3(7483.1435306778, 7496.292648878055, 0.0)]}

Let’s draw…

import matplotlib.pyplot as plt  # Import the matplotlib library for plotting
import numpy as np  # Import the numpy library for numerical operations

# Create a new figure and axis for the plot
fig, axs = plt.subplots()

# Access the 'Polyline_layer' data from the 'dxf_dict' dictionary and retrieve the 'POLYLINE' information
inputs = dxf_dict['Polyline_layer']["Info"]["POLYLINE"]

# Create an empty list to store polyline data
list_ent = []

# Iterate through the polyline data and append it to the list
for i in range(len(inputs)):
    list_ent.append(inputs[i])

# Convert the list to a numpy array with dtype 'object'
nparray = np.array(list_ent, dtype=object)

# Set the line width for plotting
linewidth = 1

# Iterate through each polyline in the numpy array
for corner in nparray[:]:
    x = list()
    y = list()

    # Extract x and y coordinates from the corner data
    for i in corner:
        x.append(i[0])
        y.append(i[1])

    # Convert x and y coordinates to numpy arrays
    x = np.array(x)
    y = np.array(y)

    # Plot the polyline with solid black lines and the specified line width
    plt.plot(x, y, linestyle='solid', color='black', linewidth=linewidth)

# Set the aspect ratio of the plot to be equal
axs.set_aspect('equal')

# Turn off the axis labels and ticks
plt.axis('off')

# Display the plot
plt.show()

Output of this code :

Polyline

LINE

For the ‘LINE’ drawing, we extract the coordinate information of the starting and ending points of the line from the DXF file to create the image.

dxf_dict[‘Line_layer’][‘Info’][‘LINE’]  output :

[[Vec3(5001.869254742749, 9995.341526157315, 0.0),
  Vec3(7488.027472530069, 9995.341526157315, 0.0)],
[Vec3(7488.027472530069, 9995.341526157315, 0.0),
  Vec3(5001.869254742749, 8748.540753238309, 0.0)],
[Vec3(5001.869254742749, 8748.540753238309, 0.0),
  Vec3(7488.027472530069, 8766.876053896378, 0.0)]]

For LINE drawing we use to below code:

fig, axs = plt.subplots()

# Create empty lists to store x and y coordinates of lines
xs = []
ys = []

# Access the 'Line_layer' data from the 'dxf_dict' dictionary and retrieve the 'LINE' information
lines = dxf_dict['Line_layer']['Info']['LINE']

# Iterate through each line
for i in lines:
    # Extract x coordinates of both line endpoints and append them to the 'xs' list
    xs.append(i[0].x)
    xs.append(i[1].x)

    # Extract y coordinates of both line endpoints and append them to the 'ys' list
    ys.append(i[0].y)
    ys.append(i[1].y)

    # Plot the line segment using the x and y coordinates, with black color and the specified line width
    axs.plot(xs, ys, color="black", linewidth=linewidth)

# Set the aspect ratio of the plot to be equal
axs.set_aspect('equal')

# Turn off the axis labels and ticks
plt.axis('off')
# Display the plot
plt.show()

Output of this code:

Line

CIRCLE

For the ‘CIRCLE’ drawing, use the radius and center point information of the circle.

dxf_dict[‘Circle_layer’][‘Info’][‘CIRCLE’] output:

[[Vec3(9652.110176850088, 9042.887859484448, 0.0), 1702.259004105073]]

Vec3(9652.110176850088, 9042.887859484448, 0.0) ⇒ center point

1702.259 ⇒ radius

To Circle drawings using Matplotlib, we will use ‘plt.Circle’ function.

fig, axs = plt.subplots()

# Access circles data from a dictionary named dxf_dict
circles = dxf_dict['Circle_layer']['Info']['CIRCLE']

# Set the line width for plotting circles
linewidth = 1

# Get the keys from the 'Info' dictionary
keys = dxf_dict['Circle_layer']['Info'].keys()

# Loop through the circles data
for i in range(len(circles)):
    # Check if 'CIRCLE' is one of the keys in the 'Info' dictionary
    if 'CIRCLE' in keys:
        # Print the coordinates of the circle's center
        print((circles[i][0].x, circles[i][0].y))
        
        # Create a Circle patch with the specified center, radius, and properties
        c = plt.Circle((circles[i][0].x, circles[i][0].y), circles[i][1], fill=False, linewidth=linewidth)
        
        # Set the x and y axis limits to fit the circle
        axs.set_xlim((circles[i][0].x - circles[i][1], circles[i][0].x + circles[i][1]))
        axs.set_ylim((circles[i][0].y - circles[i][1], circles[i][0].y + circles[i][1]))
        
        # Add the circle patch to the axis
        axs.add_patch(c)

# Set the aspect ratio of the plot to be equal
axs.set_aspect('equal')

# Turn off axis labels and ticks
plt.axis('off')
plt.show()

Output of code:

Circle

This code is valid if there is only a single CIRCLE in the layer. If there are multiple CIRCLES in the layer, limit values should be found and adjusted to encompass the extreme points.

ARC

For the ARC drawing, the information we extract from the DXF file includes the center point, radius, starting point, and ending point.

However, it is not possible to draw an arc in Matplotlib using only this information. Therefore, we will use a function named ‘arc_patch.’ This function will generate points using the center point, radius, starting point, and ending point information.

def arc_patch(center, radius, theta1, theta2, axs=None, resolution=360, **kwargs):
    # make sure ax is not empty
    if axs is None:
        axs = plt.gca()
    # generate the points
    theta = np.linspace(np.radians(theta1), np.radians(theta2), resolution)
    points = np.vstack((radius * np.cos(theta) + center[0],
                        radius * np.sin(theta) + center[1]))
    # build the polygon and add it to the axes
    poly = matplotlib.patches.Polygon(points.T, closed=False, **kwargs)
    axs.add_patch(poly)
    return poly

Now we are drawing ARC:

fig, axs = plt.subplots()
arc = dxf_dict['Arc_layer']['Info']['ARC']
linewidth = 1

for i in range(len(arc)):
    # Check if the start angle is less than the end angle
    if arc[i][2] < arc[i][3]:
        # Draw an arc patch with specified parameters
        arc_patch((arc[i][0].x, arc[i][0].y), arc[i][1], arc[i][2], arc[i][3], axs=axs, fill=False,
                  color="black", linewidth=linewidth)
        axs.plot()
    else:
        # Adjust start and end angles for cases where the arc extends beyond 360 degrees
        startangle = arc[i][2] - 360
        endangle = arc[i][3]
        
        # Draw an arc patch with adjusted angles and other parameters
        arc_patch((arc[i][0].x, arc[i][0].y), arc[i][1], startangle, endangle, axs=axs, fill=False,
                  color="black", linewidth=linewidth)
        axs.plot()
axs.set_aspect('equal')
plt.axis('off')
plt.show()

Output:

Arc

LWPOLYLINE

We obtain the ‘xyseb’ information of the entity to draw an ‘LWPOLYLINE’ and determine whether it is closed or open.

‘xyseb’ means:   

  • x = x coordinate  
  • y = y coordinate  
  • s = start width  
  • e = end width  
  • b = bulge value  

The important informations for us are x and y coordinates and  bulge values. For more information about bulge and lwpoyline you can check:  

https://ezdxf.readthedocs.io/en/stable/dxfentities/lwpolyline.html

First, let’s examine the output of dxf_dict[‘Lwpolyline_layer’][‘Info’][‘LWPOLYLINE’]:  

{0: [[(3747.04374997929, 7491.933316803872, 0.0, 0.0, 0.0),  
(2486.32528232372, 6259.879909549656, 0.0, 0.0, 0.0),  
(3737.773775793569, 4944.454463023436, 0.0, 0.0, 0.0),  
(4970.682282845875, 6278.407025741547, 0.0, 0.0, 0.0),  
(3745.443225975512, 7495.456175628871, 0.0, 0.0, 0.0)],  
0],  
1: [[(3197.523813337444, 6439.375552584195, 0.0, 0.0, -0.3598433278103378),  
(4287.399188913624, 6575.01195024556, 0.0, 0.0, 0.0),  
(4262.722769342007, 5974.923668548727, 0.0, 0.0, -0.4543539444244007),  
(3144.058237598933, 6110.560046657174, 0.0, 0.0, 0.0),  
(3193.411076742174, 6443.485751785335, 0.0, 0.0, 0.0)],  
0]}  

From this output, it can be seen that there are two LWPOLYLINE entities, and they are represented by a tuple with a length of 5 and one integer value in list format. 

(3197.523813337444, 6439.375552584195, 0.0, 0.0, -0.3598433278103378) 

This tuble values represent ‘xyseb’. Bulge value ‘ -0.3598433278103378’.  This means that the next point is connected to a corner with a non-linear arc format. If the bulge value is 0, it means that the next point is connected in a linear format. 

We will use this function to draw a straight arc with two points and a bulge value: 

def convert_arc(pt1, pt2, bulge): 
    # extract point coordinates 
    x1, y1 = pt1 
    x2, y2 = pt2 
 
    dist = math.sqrt((pt1[0] - pt2[0]) ** 2 + (pt1[1] - pt2[1]) ** 2) 
    sagitta = dist / 2.0 * bulge 
    # find normal from midpoint, follow by length sagitta 
    n = np.array([y2 - y1, x1 - x2]) 
    n_dist = np.sqrt(np.sum(n ** 2)) 
 
    if np.isclose(n_dist, 0): 
        # catch error here, d(pt1, pt2) ~ 0 
        print("Error: The distance between pt1 and pt2 is too small.") 
 
    n = n / n_dist 
    x3, y3 = (np.array(pt1) + np.array(pt2)) / 2 + sagitta * n 
 
    # calculate the circle from three points 
    # see https://math.stackexchange.com/a/1460096/246399 
    A = np.array([ 
        [x1 ** 2 + y1 ** 2, x1, y1, 1], 
        [x2 ** 2 + y2 ** 2, x2, y2, 1], 
        [x3 ** 2 + y3 ** 2, x3, y3, 1]]) 
    M11 = np.linalg.det(A[:, (1, 2, 3)]) 
    M12 = np.linalg.det(A[:, (0, 2, 3)]) 
    M13 = np.linalg.det(A[:, (0, 1, 3)]) 
    M14 = np.linalg.det(A[:, (0, 1, 2)]) 
 
    if np.isclose(M11, 0): 
        # catch error here, the points are collinear (sagitta ~ 0) 
        print("Error: The third point is collinear.") 
 
    cx = 0.5 * M12 / M11 
    cy = -0.5 * M13 / M11 
    radius = np.sqrt(cx ** 2 + cy ** 2 + M14 / M11) 
 
    # calculate angles of pt1 and pt2 from center of circle 
    pt1_angle = 180 * np.arctan2(y1 - cy, x1 - cx) / np.pi 
    pt2_angle = 180 * np.arctan2(y2 - cy, x2 - cx) / np.pi 
    if pt1_angle < 0: 
        pt1_angle = 360 + pt1_angle 
    if pt2_angle < 0: 
        pt2_angle = 360 + pt2_angle 
    return cx, cy, radius, pt1_angle, pt2_angle, bulge 

This function takes two points and a bulge value as input. After specific operations, it returns the center points of the arc, the radius value of the arc, and the start and end points of the arc, which are required for drawing the arc. 

Now we will draw Lwpolyline:

import matplotlib.pyplot as plt

# Set the line width for plotting
linewidth = 1

# Create an empty dictionary to store LWPOLY data
lwpoly = {}

# Access LWPOLYLINE layer data from a DXF dictionary
layer_data = dxf_dict["Lwpolyline_layer"]

# Loop through each LWPOLYLINE in the layer
for j in range(len(layer_data["Info"]["LWPOLYLINE"])):
    
    # Initialize a list to store arc data
    arc_list = []
    
    # Get the flag for the LWPOLYLINE
    flag = layer_data["Info"]["LWPOLYLINE"][j][1]
    print(flag)
    
    # Loop through each vertex in the LWPOLYLINE
    for i in range(len(layer_data["Info"]["LWPOLYLINE"][j][0])):
        
        # Get the vertex list
        liste = layer_data["Info"]["LWPOLYLINE"][j][0]
        print(liste)
        
        # Check if the current vertex is not the last one
        if i != len(liste) - 1:
            
            # Check if the bulge value is not 0
            if liste[i][4] != 0:
                p0 = [liste[i][0], liste[i][1]]
                p1 = [liste[i + 1][0], liste[i + 1][1]]
                arc_list.append(convert_arc(p0, p1, liste[i][4]))
            else:
                p0 = [liste[i][0], liste[i][1]]
                p1 = [liste[i + 1][0], liste[i + 1][1]]
                arc_list.append([p0, p1, liste[i][4]])

        else:
            if liste[i][4] != 0:
                p0 = [liste[i][0], liste[i][1]]
                p1 = [liste[0][0], liste[0][1]]
                arc_list.append(convert_arc(p0, p1, liste[i][4]))
            else:
                if liste[i][4] == 0:
                    p0 = [liste[i][0], liste[i][1]]
                    p1 = [liste[0][0], liste[0][1]]
                    arc_list.append([p0, p1, liste[i][4]])
                else:
                    continue

    # Store the arc data and flag in the dictionary
    lwpoly[j] = [arc_list, flag]

# Loop through the LWPOLY data
for j in lwpoly:
    arc = lwpoly[j][0]
    flag_closed = lwpoly[j][1]
    
    # Loop through the arcs
    for i in range(len(arc)):
        if i == len(arc) - 1 and flag_closed == 0:
            break
        
        # Check if the arc has more than 4 elements
        if len(arc[i]) > 4:
            
            # Check the bulge value and angles to determine arc properties
            if arc[i][5] > 0:
                if arc[i][3] < 180:
                    if arc[i][3] < 180:
                        arc_patch((arc[i][0], arc[i][1]), arc[i][2], arc[i][3], arc[i][4], axs=axs, fill=False,
                                  color="black", linewidth=linewidth)
                        axs.plot()
                    else:
                        arc_patch((arc[i][0], arc[i][1]), arc[i][2], arc[i][3], -(360 - arc[i][4]), axs=axs,
                                  fill=False, color="black", linewidth=linewidth)
                        axs.plot()
                else:
                    if arc[i][4] < 180:
                        arc_patch((arc[i][0], arc[i][1]), arc[i][2], -(360 - arc[i][3]), arc[i][4], axs=axs,
                                  fill=False, color="black", linewidth=linewidth)
                        axs.plot()
                    else:
                        arc_patch((arc[i][0], arc[i][1]), arc[i][2], -(360 - arc[i][3]), -(360 - arc[i][4]),
                                  axs=axs, fill=False, color="black", linewidth=linewidth)
                        axs.plot()
            else:
                if arc[i][3] < arc[i][4]:
                    arc_patch((arc[i][0], arc[i][1]), arc[i][2], arc[i][3], -(360 - arc[i][4]), axs=axs,
                              fill=False, color="black", linewidth=linewidth)
                    axs.plot()
                else:
                    arc_patch((arc[i][0], arc[i][1]), arc[i][2], arc[i][3], arc[i][4], axs=axs, fill=False,
                              color="black", linewidth=linewidth)
                    axs.plot()
        else:
            # Plot a line segment if it's not an arc
            x_values = [arc[i][0][0], arc[i][1][0]]
            y_values = [arc[i][0][1], arc[i][1][1]]
            axs.plot(x_values, y_values, color="black", linewidth=linewidth)

# Set plot aspect ratio to equal, turn off axes, and save the plot as an image
axs.set_aspect('equal')
plt.axis('off')

plt.show()

Output of this code:

Lwpolyline