Task 159: .DWG File Format

Task 159: .DWG File Format

The .DWG file format is a proprietary binary format developed by Autodesk for storing 2D and 3D design data and metadata in AutoCAD and compatible applications. Detailed specifications are documented in the "Open Design Specification for .dwg files" published by the Open Design Alliance, which provides a reverse-engineered description covering versions from R13 (1994) onward, including header structures, data sections, and error-checking mechanisms.

The following is a list of key properties intrinsic to the .DWG file format, derived from its header and summary information sections. These represent embedded metadata and structural fields that define the file's version, integrity, and content attributes. Properties are grouped by section for clarity, with data types and brief descriptions:

File Header Section (Common Across Versions):

  • Version String: String (6 bytes) – Identifies the AutoCAD release version (e.g., "AC1018" for R2004, "AC1027" for R2013-2017).
  • Maintenance Version: Integer (1 byte) – Indicates the maintenance release level (typically 0).
  • Preview/Image Seeker Address: Integer (4 bytes) – Pointer to the thumbnail preview image data.
  • DWG Code Page: Integer (2 bytes) – Specifies the drawing's code page for character encoding (e.g., 1252 for ANSI Latin 1).
  • Security Flags: Integer (4 bytes) – Flags for encryption or password protection (0 if none).
  • Summary Information Address: Integer (4 bytes) – Pointer to the summary information section.
  • Section Locator Records Count: Integer (4 bytes) – Number of records pointing to data sections in the file.
  • CRC (Cyclic Redundancy Check): Integer (2 bytes) – Error-detection value for the header integrity.

Summary Information Section (Metadata Properties):

  • Title: String – The drawing's title.
  • Subject: String – The drawing's subject description.
  • Author: String – The creator's name.
  • Keywords: String – Associated keywords.
  • Comments: String – Additional notes or comments.
  • Last Saved By: String – Name of the last user who saved the file.
  • Revision Number: String – Revision identifier.
  • Hyperlink Base: String – Base URL for hyperlinks in the drawing.
  • Total Editing Time: Integer (4 bytes) – Cumulative editing time in minutes.
  • Creation Time: Date/Time (8 bytes) – Timestamp of file creation.
  • Modified Time: Date/Time (8 bytes) – Timestamp of last modification.

These properties are intrinsic as they are embedded within the file's binary structure and do not depend on external file system attributes like external timestamps or permissions.

Two direct download links for sample .DWG files are:

The following is an embedded HTML/JavaScript snippet suitable for a Ghost blog post. It creates a drag-and-drop interface that reads a .DWG file, parses the header and summary information sections to extract the listed properties, and displays them on the screen. Note that full parsing of complex sections (e.g., compressed data) is simplified here for demonstration; production use may require handling version-specific variations and errors.

Drag and drop a .DWG file here

The following Python class handles opening, decoding, reading, writing, and printing the listed properties. It uses the struct module for binary parsing and focuses on basic header and summary info extraction (simplified for common versions; full implementation would require version-specific handling and error correction).

import struct
import os
import datetime

class DWGParser:
    def __init__(self, filepath):
        self.filepath = filepath
        self.data = None
        self.properties = {}

    def read(self):
        with open(self.filepath, 'rb') as f:
            self.data = f.read()
        self.decode()

    def decode(self):
        if not self.data:
            raise ValueError("No data loaded.")
        # Header parsing (R2000+ example)
        self.properties['Version'] = self.data[0:6].decode('ascii')
        self.properties['Maintenance Version'] = self.data[6]
        (self.properties['Preview Address'],) = struct.unpack('<I', self.data[0x0D:0x11])
        (self.properties['Code Page'],) = struct.unpack('<H', self.data[0x13:0x15])
        (self.properties['Security Flags'],) = struct.unpack('<I', self.data[0x15:0x19])
        (self.properties['Summary Address'],) = struct.unpack('<I', self.data[0x19:0x1D])
        # Summary Info (simplified seek; assumes no encryption)
        offset = self.properties['Summary Address']
        if offset > 0:
            (title_len,) = struct.unpack('<I', self.data[offset + 0x04:offset + 0x08])
            self.properties['Title'] = self.data[offset + 0x08:offset + 0x08 + title_len].decode('utf-8', errors='ignore')
            offset += 0x08 + title_len
            (subject_len,) = struct.unpack('<I', self.data[offset:offset + 0x04])
            self.properties['Subject'] = self.data[offset + 0x04:offset + 0x04 + subject_len].decode('utf-8', errors='ignore')
            # Similar unpacking for Author, Keywords, etc.
            # For times (example offsets)
            creation_ms = struct.unpack('<Q', self.data[offset + 0x100:offset + 0x108])[0]
            self.properties['Creation Time'] = datetime.datetime.fromtimestamp(creation_ms / 1000)
            modified_ms = struct.unpack('<Q', self.data[offset + 0x108:offset + 0x110])[0]
            self.properties['Modified Time'] = datetime.datetime.fromtimestamp(modified_ms / 1000)

    def print_properties(self):
        for key, value in self.properties.items():
            print(f"{key}: {value}")

    def write(self, new_filepath=None):
        if not self.data:
            raise ValueError("No data to write.")
        filepath = new_filepath or self.filepath
        with open(filepath, 'wb') as f:
            f.write(self.data)  # Writes original; to modify, update self.data bytes first (e.g., change version)

# Usage example:
# parser = DWGParser('sample.dwg')
# parser.read()
# parser.print_properties()
# parser.write('modified.dwg')

The following Java class performs similar operations. It uses ByteBuffer for parsing and RandomAccessFile for read/write.

import java.io.*;
import java.nio.*;
import java.nio.channels.FileChannel;
import java.util.*;
import java.time.*;

public class DWGParser {
    private String filepath;
    private byte[] data;
    private Map<String, Object> properties = new HashMap<>();

    public DWGParser(String filepath) {
        this.filepath = filepath;
    }

    public void read() throws IOException {
        try (RandomAccessFile raf = new RandomAccessFile(filepath, "r");
             FileChannel channel = raf.getChannel()) {
            ByteBuffer buffer = ByteBuffer.allocate((int) raf.length());
            channel.read(buffer);
            buffer.flip();
            this.data = buffer.array();
        }
        decode();
    }

    private void decode() {
        ByteBuffer bb = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN);
        byte[] versionBytes = new byte[6];
        bb.get(versionBytes);
        properties.put("Version", new String(versionBytes));
        properties.put("Maintenance Version", bb.get(6));
        properties.put("Preview Address", bb.getInt(0x0D));
        properties.put("Code Page", bb.getShort(0x13));
        properties.put("Security Flags", bb.getInt(0x15));
        properties.put("Summary Address", bb.getInt(0x19));
        int offset = (int) properties.get("Summary Address");
        if (offset > 0) {
            int titleLen = bb.getInt(offset + 0x04);
            byte[] titleBytes = new byte[titleLen];
            bb.position(offset + 0x08);
            bb.get(titleBytes);
            properties.put("Title", new String(titleBytes));
            offset += 0x08 + titleLen;
            int subjectLen = bb.getInt(offset);
            byte[] subjectBytes = new byte[subjectLen];
            bb.position(offset + 0x04);
            bb.get(subjectBytes);
            properties.put("Subject", new String(subjectBytes));
            // Similar for other fields
            long creationMs = bb.getLong(offset + 0x100);
            properties.put("Creation Time", Instant.ofEpochMilli(creationMs));
            long modifiedMs = bb.getLong(offset + 0x108);
            properties.put("Modified Time", Instant.ofEpochMilli(modifiedMs));
        }
    }

    public void printProperties() {
        properties.forEach((key, value) -> System.out.println(key + ": " + value));
    }

    public void write(String newFilepath) throws IOException {
        String outPath = (newFilepath != null) ? newFilepath : filepath;
        try (FileOutputStream fos = new FileOutputStream(outPath)) {
            fos.write(data);  // Writes original; modify data array for changes
        }
    }

    // Usage example in main method:
    // public static void main(String[] args) throws IOException {
    //     DWGParser parser = new DWGParser("sample.dwg");
    //     parser.read();
    //     parser.printProperties();
    //     parser.write("modified.dwg");
    // }
}

The following JavaScript class (Node.js compatible) uses fs for file operations and Buffer for parsing.

const fs = require('fs');

class DWGParser {
    constructor(filepath) {
        this.filepath = filepath;
        this.data = null;
        this.properties = {};
    }

    read() {
        this.data = fs.readFileSync(this.filepath);
        this.decode();
    }

    decode() {
        if (!this.data) throw new Error('No data loaded.');
        const view = new DataView(this.data.buffer);
        let version = '';
        for (let i = 0; i < 6; i++) version += String.fromCharCode(view.getUint8(i));
        this.properties.Version = version;
        this.properties['Maintenance Version'] = view.getUint8(6);
        this.properties['Preview Address'] = view.getUint32(0x0D, true);
        this.properties['Code Page'] = view.getUint16(0x13, true);
        this.properties['Security Flags'] = view.getUint32(0x15, true);
        this.properties['Summary Address'] = view.getUint32(0x19, true);
        let offset = this.properties['Summary Address'];
        if (offset > 0) {
            const titleLen = view.getUint32(offset + 0x04, true);
            let title = '';
            for (let i = 0; i < titleLen; i++) title += String.fromCharCode(view.getUint8(offset + 0x08 + i));
            this.properties.Title = title;
            offset += 0x08 + titleLen;
            const subjectLen = view.getUint32(offset, true);
            let subject = '';
            for (let i = 0; i < subjectLen; i++) subject += String.fromCharCode(view.getUint8(offset + 0x04 + i));
            this.properties.Subject = subject;
            // Similar for other fields
            const creationMs = Number(view.getBigUint64(offset + 0x100, true));
            this.properties['Creation Time'] = new Date(creationMs);
            const modifiedMs = Number(view.getBigUint64(offset + 0x108, true));
            this.properties['Modified Time'] = new Date(modifiedMs);
        }
    }

    printProperties() {
        for (const [key, value] of Object.entries(this.properties)) {
            console.log(`${key}: ${value}`);
        }
    }

    write(newFilepath = this.filepath) {
        fs.writeFileSync(newFilepath, this.data);  // Writes original; modify this.data for changes
    }
}

// Usage example:
// const parser = new DWGParser('sample.dwg');
// parser.read();
// parser.printProperties();
// parser.write('modified.dwg');

The following C implementation uses a struct for properties and standard file I/O for reading/writing. It is not a "class" in the object-oriented sense but a structured equivalent with functions.

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <time.h>
#include <string.h>

typedef struct {
    char version[7];
    uint8_t maintenance_version;
    uint32_t preview_address;
    uint16_t code_page;
    uint32_t security_flags;
    uint32_t summary_address;
    char title[256];  // Assuming max lengths
    char subject[256];
    // Add similar for other strings
    time_t creation_time;
    time_t modified_time;
} DWGProperties;

typedef struct {
    const char* filepath;
    uint8_t* data;
    size_t size;
    DWGProperties properties;
} DWGParser;

void read_dwg(DWGParser* parser) {
    FILE* f = fopen(parser->filepath, "rb");
    if (!f) return;
    fseek(f, 0, SEEK_END);
    parser->size = ftell(f);
    fseek(f, 0, SEEK_SET);
    parser->data = malloc(parser->size);
    fread(parser->data, 1, parser->size, f);
    fclose(f);
    decode_dwg(parser);
}

void decode_dwg(DWGParser* parser) {
    memcpy(parser->properties.version, parser->data, 6);
    parser->properties.version[6] = '\0';
    parser->properties.maintenance_version = parser->data[6];
    memcpy(&parser->properties.preview_address, &parser->data[0x0D], 4);  // Little-endian assumed
    memcpy(&parser->properties.code_page, &parser->data[0x13], 2);
    memcpy(&parser->properties.security_flags, &parser->data[0x15], 4);
    memcpy(&parser->properties.summary_address, &parser->data[0x19], 4);
    uint32_t offset = parser->properties.summary_address;
    if (offset > 0) {
        uint32_t title_len;
        memcpy(&title_len, &parser->data[offset + 0x04], 4);
        memcpy(parser->properties.title, &parser->data[offset + 0x08], title_len);
        parser->properties.title[title_len] = '\0';
        offset += 0x08 + title_len;
        uint32_t subject_len;
        memcpy(&subject_len, &parser->data[offset], 4);
        memcpy(parser->properties.subject, &parser->data[offset + 0x04], subject_len);
        parser->properties.subject[subject_len] = '\0';
        // Similar for others
        uint64_t creation_ms;
        memcpy(&creation_ms, &parser->data[offset + 0x100], 8);
        parser->properties.creation_time = creation_ms / 1000;
        uint64_t modified_ms;
        memcpy(&modified_ms, &parser->data[offset + 0x108], 8);
        parser->properties.modified_time = modified_ms / 1000;
    }
}

void print_properties(const DWGParser* parser) {
    printf("Version: %s\n", parser->properties.version);
    printf("Maintenance Version: %u\n", parser->properties.maintenance_version);
    printf("Preview Address: %u\n", parser->properties.preview_address);
    printf("Code Page: %u\n", parser->properties.code_page);
    printf("Security Flags: %u\n", parser->properties.security_flags);
    printf("Summary Address: %u\n", parser->properties.summary_address);
    printf("Title: %s\n", parser->properties.title);
    printf("Subject: %s\n", parser->properties.subject);
    // Print others similarly
    printf("Creation Time: %s", ctime(&parser->properties.creation_time));
    printf("Modified Time: %s", ctime(&parser->properties.modified_time));
}

void write_dwg(const DWGParser* parser, const char* new_filepath) {
    FILE* f = fopen(new_filepath ? new_filepath : parser->filepath, "wb");
    if (!f) return;
    fwrite(parser->data, 1, parser->size, f);
    fclose(f);  // Writes original; modify data for changes
}

void free_dwg(DWGParser* parser) {
    free(parser->data);
}

// Usage example:
// int main() {
//     DWGParser parser = {.filepath = "sample.dwg"};
//     read_dwg(&parser);
//     print_properties(&parser);
//     write_dwg(&parser, "modified.dwg");
//     free_dwg(&parser);
//     return 0;
// }