XData Refactor Plan — Eliminate Text File Serialization¶
Created: February 24, 2026
Status: Planned — ready to implement
Priority: High
Affects: inspanel.lsp, tiltup.lsp, savelay.lsp, layout.lsp
Problem Statement¶
The panel workflow currently stores entity transformation data in text files (tiltlist.txt, conslist.txt) using prin1/read serialization. This approach is fundamentally broken:
ENAME serialization failure (Bug 14):
entgetreturns ENAME objects (DXF group 330 owner pointer) thatprin1serializes as<Entity name: 7ef73060>— a string thatreadcannot parse back. Even with filtering, this is fragile.Index mismatch bugs (Bug 15): The text file stores a flat list with no entity identity. Matching saved data back to live entities requires block-name lookup loops that are error-prone (
nnvsxnindex confusion).File deletion race (Bug 13 investigation):
inspanelline 33 runsdel *list.txtwhich destroys all*list.txtfiles in the project folder before any recovery code can read them.No atomicity: If AutoCAD crashes mid-workflow, the text files may be corrupt or absent, with no recovery path.
AutoCAD 2000 (R15) has supported Extended Entity Data (XData) since its AutoCAD R11 roots (1990). XData stores application-specific data directly on entities in the drawing database — no external files, no serialization, no parsing. It survives save/load cycles, copy/paste, and WBLOCK operations.
Design¶
Storage Approach: XData on INSERT Entities¶
Each panel is an INSERT (xref) entity on layer 0. We store transformation snapshots as XData directly on these entities using registered application names.
App Name |
Purpose |
Written By |
Read By |
|---|---|---|---|
|
Pre-layout (construction) transformation |
inspanel.lsp |
tiltup.lsp |
|
Pre-tiltup (laid-flat) transformation |
savelay.lsp |
layout.lsp |
XData Structure¶
Each app’s XData stores the complete transformation state needed to restore an INSERT entity:
(-3 ("CSV_TILT"
(1002 . "{")
(1010 . (x y z)) ;; insertion point (group 10)
(1040 . x_scale) ;; X scale factor (group 41)
(1040 . y_scale) ;; Y scale factor (group 42)
(1040 . z_scale) ;; Z scale factor (group 43)
(1040 . rotation) ;; rotation angle in radians (group 50)
(1010 . (nx ny nz)) ;; extrusion direction / OCS normal (group 210)
(1002 . "}")
))
XData type codes used:
Code |
Type |
Purpose |
|---|---|---|
1002 |
Control string |
|
1010 |
3D point |
Insertion point, extrusion |
1040 |
Real |
Scale factors, rotation |
Why XData (Not Dictionaries or XRecords)¶
XData travels with the entity — if the INSERT is copied, moved, or WBLOCKed, its XData goes with it. Dictionary XRecords are drawing-global and don’t follow entities.
AutoCAD 2000 full support — XData predates R15 by a decade.
regapp,entgetwith app filter,entmodwith XData all work.No file I/O — eliminates
open/close/read/prin1and all the failure modes that come with text file round-tripping.Survives crashes — XData is saved with the drawing. If AutoCAD crashes after
entmodbut before a text file write completes, the text file approach loses data. XData persists in the undo stack and in the saved.dwg.
Current Code Analysis¶
inspanel.lsp — Writes tiltlist.txt¶
Normal path (lines 470–500): After attaching all panel xrefs, collects all INSERT entities on layer 0, strips ENAME/XData from each entity’s DXF data, writes the filtered list to tiltlist.txt via prin1.
Fast path (lines 105–145): When panels are already attached (no wall lines, no dictionary, but INSERT entities exist), does the same ENAME-filtering write to generate tiltlist.txt.
What it captures: The entity state after xref attachment — panels are in their 3D construction position along walls. This is the state that Tilt-Up needs to restore after Layout flattens them.
tiltup.lsp — Reads tiltlist.txt¶
Reads tiltlist.txt via (read (read-line f)). Builds pnllst from live INSERT entities. Matches by block name (DXF group 2). Substitutes live entity name (group -1) into saved data. Calls (entmod) on each entry to restore the construction position.
Key insight: Tiltup doesn’t need the entire association list — it only needs groups 10 (insertion pt), 41/42/43 (scale), 50 (rotation), and 210 (extrusion) to restore the transformation. The current approach carries ~20 DXF groups per entity when only 6 matter.
savelay.lsp — Writes conslist.txt¶
10 lines. Collects all INSERT entities on layer 0 via ssget. Strips entity names via (mapcar '(lambda (x) (cdr x)) pnllst) — removes the (-1 . ename) pair. Writes to conslist.txt via prin1.
Called by: layout.lsp first pass (after rotating panels flat).
What it captures: The entity state after Layout rotates panels from 3D wall position to flat layout position. This is what Layout’s second pass restores to undo the layout operation.
layout.lsp — Reads conslist.txt¶
Two-pass design based on file existence:
Pass 1 (conslist.txt does NOT exist): First-time layout. Rotates each INSERT from 3D construction position to flat. Transforms: rotation = angle of OCS normal + 90°, insertion point translated from OCS to WCS, extrusion set to (0 0 1). Then calls
(savelay)to snapshot the result.Pass 2 (conslist.txt EXISTS): Undo layout. Reads saved data, matches by block name (same loop pattern as tiltup), substitutes live entity names, calls
(entmod)to restore.
Same bug pattern as tiltup: Uses (cons (assoc -1 (nth nn pnllst)) (nth nn conslst)) — the nn vs xn index bug likely exists here too (untested, but the code structure is identical).
Implementation Plan¶
Step 1: Create XData Helper Functions¶
Create a small utility (or add to an existing utility file) with reusable functions:
;;;------------------------------------------------------------
;;; csv_xdata.lsp - XData save/restore helpers
;;;
;;; Provides functions to store and retrieve panel
;;; transformation snapshots as XData on INSERT entities.
;;;------------------------------------------------------------
(defun csv_save_xdata (ename appname / e ins xs ys zs rot ext xd)
;;
;; Save the current transformation of entity ename
;; as XData under the given application name.
;;
(regapp appname)
(set 'e (entget ename))
(set 'ins (cdr (assoc 10 e))) ;; insertion point
(set 'xs (cdr (assoc 41 e))) ;; x scale
(set 'ys (cdr (assoc 42 e))) ;; y scale
(set 'zs (cdr (assoc 43 e))) ;; z scale
(set 'rot (cdr (assoc 50 e))) ;; rotation
(set 'ext (cdr (assoc 210 e))) ;; extrusion direction
(set 'xd
(list -3
(list appname
'(1002 . "{")
(cons 1010 ins)
(cons 1040 xs)
(cons 1040 ys)
(cons 1040 zs)
(cons 1040 rot)
(cons 1010 ext)
'(1002 . "}")
)
)
)
;; If entity already has XData for this app, remove it first
(set 'e (entget ename (list appname)))
(if (assoc -3 e)
(set 'e (subst xd (assoc -3 e) e))
(set 'e (append e (list xd)))
)
(entmod e)
)
(defun csv_restore_xdata (ename appname / e xd vals ins xs ys zs rot ext)
;;
;; Read XData from entity and restore the saved
;; transformation groups via entmod.
;; Returns T if successful, nil if no XData found.
;;
(set 'e (entget ename (list appname)))
(set 'xd (assoc -3 e))
(if (not xd)
nil
(progn
;; Extract values from XData group list
;; Structure: (-3 ("APPNAME" (1002 . "{") (1010 . ins) (1040 . xs) (1040 . ys) (1040 . zs) (1040 . rot) (1010 . ext) (1002 . "}")))
(set 'vals (cdadr xd)) ;; skip appname string
;; Walk the XData values in order
(set 'vals (cdr vals)) ;; skip opening "{"
(set 'ins (cdr (car vals))) (set 'vals (cdr vals))
(set 'xs (cdr (car vals))) (set 'vals (cdr vals))
(set 'ys (cdr (car vals))) (set 'vals (cdr vals))
(set 'zs (cdr (car vals))) (set 'vals (cdr vals))
(set 'rot (cdr (car vals))) (set 'vals (cdr vals))
(set 'ext (cdr (car vals)))
;; Apply to entity
(set 'e (entget ename))
(set 'e (subst (cons 10 ins) (assoc 10 e) e))
(set 'e (subst (cons 41 xs) (assoc 41 e) e))
(set 'e (subst (cons 42 ys) (assoc 42 e) e))
(set 'e (subst (cons 43 zs) (assoc 43 e) e))
(set 'e (subst (cons 50 rot) (assoc 50 e) e))
(set 'e (subst (cons 210 ext) (assoc 210 e) e))
(entmod e)
T
)
)
)
(defun csv_has_xdata (ename appname / e)
;;
;; Check if entity has XData for the given app.
;; Returns T or nil.
;;
(set 'e (entget ename (list appname)))
(if (assoc -3 e) T nil)
)
Step 2: Rewrite inspanel.lsp — Save XData Instead of tiltlist.txt¶
Replace the tiltlist.txt write block (both normal and fast paths) with:
;; Save construction-position transformation as XData
;; on each panel INSERT entity
(set 'xn (sslength xls))
(while (> xn 0)
(set 'xn (1- xn))
(csv_save_xdata (ssname xls xn) "CSV_TILT")
)
Also remove the del *list.txt shell command (line 33) or change it to only delete specific non-XData files if any are still needed.
Fast path: Same change — call csv_save_xdata instead of writing tiltlist.txt.
Step 3: Rewrite tiltup.lsp — Read XData Instead of tiltlist.txt¶
Replace the entire file read + block-name matching + entity-name substitution with:
(defun tiltup ()
(set 'xls
(ssget "x"
'((-4 . "<and") (0 . "INSERT") (8 . "0") (-4 . "and>"))
)
)
(if (not xls)
(alert "No panel entities found in the drawing.")
(progn
(set 'xn (sslength xls))
(set 'count 0)
(while (> xn 0)
(set 'xn (1- xn))
(if (csv_restore_xdata (ssname xls xn) "CSV_TILT")
(set 'count (1+ count))
)
)
(if (= count 0)
(alert
(strcat
"No CSV_TILT data found on panel entities.\n\n"
"You must Attach Panels before Tilt-Up."
)
)
)
)
)
)
Benefits:
No file I/O at all
No block-name matching loops
No entity-name substitution gymnastics
Each entity carries its own restoration data
Works even if panels are added/removed between Layout and Tilt-Up
Step 4: Rewrite savelay.lsp — Save XData Instead of conslist.txt¶
(defun savelay ()
(set 'xls
(ssget "x"
'((-4 . "<and") (0 . "INSERT") (8 . "0") (-4 . "and>"))
)
)
(if xls
(progn
(set 'xn (sslength xls))
(while (> xn 0)
(set 'xn (1- xn))
(csv_save_xdata (ssname xls xn) "CSV_LAYOUT")
)
)
)
)
Step 5: Rewrite layout.lsp — Check XData Instead of conslist.txt¶
Pass detection: Instead of (findfile "conslist.txt"), check if any INSERT entity has CSV_LAYOUT XData:
;; Check if layout has already been run (entities have CSV_LAYOUT XData)
(set 'xls
(ssget "x"
'((-4 . "<and") (0 . "INSERT") (8 . "0") (-4 . "and>"))
)
)
(set 'has_layout nil)
(if xls
(if (csv_has_xdata (ssname xls 0) "CSV_LAYOUT")
(set 'has_layout T)
)
)
Pass 1 (first time): Same rotation logic as current code, then call (savelay) which now writes XData.
Pass 2 (undo layout): Replace file read + matching with:
(set 'xn (sslength xls))
(while (> xn 0)
(set 'xn (1- xn))
(csv_restore_xdata (ssname xls xn) "CSV_LAYOUT")
)
Step 6: Module Loading¶
Add csv_xdata.lsp to the module load list in csv.lsp so the helper functions are available. It needs to load before inspanel, tiltup, savelay, and layout.
Migration / Backward Compatibility¶
Existing Drawings¶
Drawings saved before this refactor will have no XData on their INSERT entities. The code must handle this gracefully:
tiltup with no
CSV_TILTXData: Show alert “You must Attach Panels before Tilt-Up” (same UX as current “tiltlist.txt not found” message).layout with no
CSV_LAYOUTXData: Treat as first-time layout (Pass 1) — same as current behavior whenconslist.txtdoesn’t exist.inspanel on already-attached panels: The fast path calls
csv_save_xdataon all existing INSERTs, addingCSV_TILTXData for future Tilt-Up use.
Text Files¶
After this refactor, tiltlist.txt and conslist.txt are no longer written or read. The del *list.txt command in inspanel can be removed entirely (or kept as cleanup for legacy files in existing project folders).
Files Modified¶
File |
Change |
|---|---|
csv_xdata.lsp |
NEW — XData helper functions |
inspanel.lsp |
Replace tiltlist.txt write with csv_save_xdata |
tiltup.lsp |
Replace tiltlist.txt read with csv_restore_xdata |
savelay.lsp |
Replace conslist.txt write with csv_save_xdata |
layout.lsp |
Replace conslist.txt read with csv_restore_xdata |
csv.lsp |
Add csv_xdata.lsp to module load list |
Risks¶
XData size limit: AutoCAD limits XData to 16 KB per entity per app. Each panel stores ~6 values (~100 bytes). Even with 500 panels, this is well under the limit.
XData and XREF reloads: When an xref is detached and re-attached, its INSERT entity is recreated — XData is lost. This is fine because
inspanel(which attaches xrefs) is also what writesCSV_TILTXData.Undo:
entmodwith XData participates in AutoCAD’s undo system.UNDOafterinspanelwould remove the XData. This is the same risk as the current text file approach (undo doesn’t un-delete a text file either).
Testing Plan¶
Attach Panels on CSBsite1 → verify
CSV_TILTXData appears on INSERT entities (check via(entget (ssname xls 0) '("CSV_TILT")))Layout Panels → verify entities rotate flat,
CSV_LAYOUTXData written,CSV_TILTXData preservedLayout Panels again (Pass 2) → verify entities return to laid-flat position from
CSV_LAYOUTXDataTilt-Up Panels → verify entities return to 3D construction position from
CSV_TILTXDataSave and reopen → verify XData survives drawing save/load cycle
No tiltlist.txt or conslist.txt should exist in the project folder after any operation