;;; DeleteDimensions_Batch3_R10_Color.lsp
;; %%%%%%%%%%%%%%%%%%% Folder dialog helper %%%%%%%%%%%%%%%%%%%
;; Opens a true Windows Explorer-style folder picker (with search bar,
;; navigation pane, address bar) via PowerShell + OpenFileDialog trick.
;; Returns selected folder path string, or nil if cancelled.
(defun getfolder (prompt / wsh tmpfile psfile fh result) 
  (vl-load-com)
  (setq tmpfile (strcat (getenv "TEMP") "\\fixdxf_folder.txt"))
  (setq psfile (strcat (getenv "TEMP") "\\fixdxf_picker.ps1"))
  ;; Clean up any leftover temp files
  (if (findfile tmpfile) (vl-file-delete tmpfile))
  (if (findfile psfile) (vl-file-delete psfile))
  ;; Write PowerShell script to temp file (avoids quoting nightmares)
  ;; Uses OpenFileDialog with ValidateNames=false — gives full Explorer UI with search
  (setq fh (open psfile "w"))
  (write-line "Add-Type -AssemblyName System.Windows.Forms" fh)
  (write-line "$d = New-Object System.Windows.Forms.OpenFileDialog" fh)
  (write-line "$d.Title           = 'Select Folder'" fh)
  (write-line "$d.ValidateNames   = $false" fh)
  (write-line "$d.CheckFileExists = $false" fh)
  (write-line "$d.CheckPathExists = $true" fh)
  (write-line "$d.FileName        = 'Select Folder.'" fh)
  (write-line "if ($d.ShowDialog() -eq 'OK') {" fh)
  (write-line "  $path = Split-Path $d.FileName" fh)
  (write-line (strcat "  [System.IO.File]::WriteAllText('" tmpfile "', $path)") fh)
  (write-line "}" fh)
  (close fh)
  ;; Run PowerShell synchronously (hidden window, waits for dialog to close)
  (setq wsh (vlax-create-object "WScript.Shell"))
  (vlax-invoke wsh 
               'Run
               (strcat "powershell -NoProfile -ExecutionPolicy Bypass -File \"" 
                       psfile
                       "\""
               )
               0 ; window style: 0 = hidden
               :vlax-true ; wait for process to finish before returning
  )
  (vlax-release-object wsh)
  ;; Read selected path from temp file
  (setq result nil)
  (if (findfile tmpfile) 
    (progn 
      (setq fh (open tmpfile "r"))
      (if fh 
        (progn 
          (setq result (read-line fh))
          (close fh)
        )
      )
      (vl-file-delete tmpfile)
    )
  )
  (if (findfile psfile) (vl-file-delete psfile))
  ;; Return nil if cancelled/empty, otherwise return the path string
  (if (and result (> (strlen result) 0)) 
    result
    nil
  )
)
;;; BricsCAD / AutoCAD-compatible AutoLISP
;;; Command: FixDXF
;;;
;;; What this script does (in order, per file):
;;;   1. Deletes all DIMENSION entities
;;;   2. Moves all CIRCLE and ARC entities to layer "4"
;;;   3. Moves all TEXT, MTEXT, and ATTDEF entities to layer "5" (comemented out)
;;;   4. Color-codes ALL existing layers (invalid layers are colored cyan by default, but you can customize the mapping in *LAYER-COLOR-MAP*)
;;;   5. Runs PURGE (all, no prompts)
;;;   6. Saves the file as DXF **R10**
;;;   7. Writes a log file summarizing every action and lists any files with potential issues (e.g. polylines on non-3 layers, invalid layers after color-coding + purge)
;;;
;;; Entities already on the correct layer are logged but NOT modified.

(vl-load-com)

;; %%%%%%%%%%%%%%%%%%% Utilities %%%%%%%%%%%%%%%%%%%

(defun _itoa (v) 
  (if (numberp v) 
    (itoa (fix v))
    "0"
  )
)

(defun _isdigit-string (s / i ch valid) 
  (if (not (and s (not (= s "")))) 
    nil
    (progn 
      (setq valid T
            i     1
      )
      (while (and valid (<= i (strlen s))) 
        (setq ch (substr s i 1))
        (if (not (member ch '("0" "1" "2" "3" "4" "5" "6" "7" "8" "9"))) 
          (setq valid nil)
        )
        (setq i (1+ i))
      )
      valid
    )
  )
)

(defun _path-join (a b / sep) 
  (setq sep (if (wcmatch a "*[\\/]") 
              ""
              "\\"
            )
  )
  (strcat a sep b)
)

(defun _list-dxf-files (root recurse / dxfs subs) 
  (setq dxfs (mapcar '(lambda (f) (_path-join root f)) 
                     (vl-directory-files root "*.dxf" 0)
             )
  )
  (if recurse 
    (progn 
      (setq subs (vl-remove-if 
                   '(lambda (d) (member d '("." "..")))
                   (vl-directory-files root nil 1)
                 )
      )
      (foreach d subs 
        (setq dxfs (append dxfs (_list-dxf-files (_path-join root d) T)))
      )
    )
  )
  dxfs
)

;; Append a line to the running log list (stored in global *LOG-LINES*)
(defun _log (msg) 
  (setq *LOG-LINES* (append *LOG-LINES* (list msg)))
)

;; Write *LOG-LINES* out to a text file
(defun _write-log (logpath / fh) 
  (setq fh (open logpath "w"))
  (if fh 
    (progn 
      (foreach ln *LOG-LINES* (write-line ln fh))
      (close fh)
      (princ (strcat "\nLog written to: " logpath))
    )
    (princ "\n  ! Could not write log file.")
  )
)




;; %%%%%%%%%%%%%%%%%%% Layer helpers %%%%%%%%%%%%%%%%%%%

(defun _ensure-layer (layname / layers) 
  (setq layers (vla-get-Layers (vla-get-ActiveDocument (vlax-get-acad-object))))
  (if 
    (vl-catch-all-error-p 
      (vl-catch-all-apply 'vla-item (list layers layname))
    )
    (vl-catch-all-apply 'vla-add (list layers layname))
  )
)

;; %%%%%%%%%%%%%%%%%%% Layer color coding (custom + fallback) %%%%%%%%%%%%%%%%%%%
;; ACI quick reference: 1=red, 2=yellow, 3=green, 4=cyan, 5=blue, 6=magenta, 7=white.
;; This module will:
;;   - Use explicit user mapping first (exact names or wildcards via WCMATCH)
;;   - Fall back to a deterministic palette so other layers are consistent

;; --- 1) Valid Layeres edit thsi to fit your needs ) ---
;; Case-insensitive. Wildcards allowed: * ? [ ] ~
(setq *LAYER-COLOR-MAP* '(("4" . 1) ;;Inner Contour Red
                          ("11" . 3) ;;Etching Green
                          ("5" . 2) ;;Info Yellow
                          ("3" . 7) ;;Outer Contour
                          ;; Examples (add/remove as you like):
                          ;; ("CUT"      . 1)      ; red
                          ;; ("CUT_*"    . 1)      ; red for any layer starting CUT_
                          ;; ("ETCH"     . 3)      ; green
                          ;; ("MARK"     . 5)      ; blue
                          ;; ("SCORE"    . 2)      ; yellow
                          ;; ("ENGRAVE"  . 4)      ; cyan
                          ;; ("HATCH*"   . 8)      ; gray
                         )
)

;; All other layers are invalid and will get this color by default (unless overridden by the user map above)
(defun _layer-color-auto (layname /) 
  4 ; ACI 4 = cyan
)

;; --- 3) Resolve a layer's color using user map (wildcards) then fallback ---
;; Returns a dotted pair: (color . "custom"|"auto")
(defun _resolve-layer-color (layname / up col src pair pat) 
  (setq up (strcase layname))
  (setq col nil
        src "auto"
  )
  (foreach pair *LAYER-COLOR-MAP* 
    (if (null col) 
      (progn 
        (setq pat (strcase (car pair)))
        (if (wcmatch up pat) 
          (progn 
            (setq col (cdr pair))
            (setq src "custom")
          )
        )
      )
    )
  )
  (if (or (null col) (not (and (numberp col) (<= 1 col 255)))) 
    (setq col (_layer-color-auto layname)
          src "auto"
    )
  )
  (cons col src)
)

;; --- 4) Apply colors to all layers, logging the source (custom/auto) ---
;; Returns a list of layer names that were auto-colored (e.g. cyan fallback)
(defun _color-code-all-layers (/ doc lays lay lname res col src autoLayers) 
  (setq doc (vla-get-ActiveDocument (vlax-get-acad-object)))
  (setq lays (vla-get-Layers doc))
  (setq autoLayers '())
  (vlax-for lay lays 
    (setq lname (vla-get-Name lay))
    (setq res (_resolve-layer-color lname))
    (setq col (car res)
          src (cdr res)
    )
    (vl-catch-all-apply 
      '(lambda () (vla-put-Color lay col))
    )
    (_log 
      (strcat "  Layer color: \"" 
              lname
              "\" -> ACI "
              (_itoa col)
              " ("
              src
              ")"
      )
    )
    (if (equal src "auto") 
      (setq autoLayers (cons lname autoLayers))
    )
  )
  (reverse autoLayers)
)

;; --- Helper: collect layers that have polylines but are not on layer "3" ---
(defun _polyline-layers-not-3 () 
  (setq ss (ssget "X" '((0 . "LWPOLYLINE,POLYLINE"))))
  (if (not ss) 
    nil
    (progn 
      (setq layers '()
            i      0
      )
      (while (< i (sslength ss)) 
        (setq ent (ssname ss i))
        (setq obj (vlax-ename->vla-object ent))
        (setq lay (vla-get-Layer obj))
        (if (and lay (not (equal lay "3"))) 
          (if (not (member lay layers)) 
            (setq layers (cons lay layers))
          )
        )
        (setq i (1+ i))
      )
      (reverse layers)
    )
  )
)
;; %%%%%%%%%%%%%%%%%%% Move layers 3,4,11 so minimum XY = 0,0 %%%%%%%%%%%%%%%%%%%
(defun _move-layers-to-zero (/ ss ss5 minx miny maxy minpt maxpt ent obj i disp) 

  (setq margin 1.0)
  ;; -------------------------------
  ;; Layers 3,4,11 → move to (0,0)
  ;; -------------------------------
  (setq ss (ssget "_X" '((8 . "3,4,11"))))

  (if ss 
    (progn 
      (setq minx 1e99
            miny 1e99
      )

      (repeat (setq i (sslength ss)) 
        (setq ent (ssname ss (setq i (1- i))))
        (setq obj (vlax-ename->vla-object ent))
        (vla-getboundingbox obj 'minpt 'maxpt)
        (setq minpt (vlax-safearray->list minpt))

        (setq minx (min minx (car minpt)))
        (setq miny (min miny (cadr minpt)))
      )

      ;; Move BL corner to origin
      (setq disp (list (- minx) (- miny) 0))
      (command "_.MOVE" ss "" '(0 0 0) disp)
    )
    (princ "\nNo entities on layers 3,4,11.")
  )

  ;; --------------------------------
  ;; Layer 5 → move to Quadrant IV
  ;; (+X, -Y), touching axes
  ;; --------------------------------
  (setq ss5 (ssget "_X" '((8 . "5"))))

  (if ss5 
    (progn 
      (setq minx 1e99
            maxy -1e99
      )

      (repeat (setq i (sslength ss5)) 
        (setq ent (ssname ss5 (setq i (1- i))))
        (setq obj (vlax-ename->vla-object ent))
        (vla-getboundingbox obj 'minpt 'maxpt)
        (setq minpt (vlax-safearray->list minpt))
        (setq maxpt (vlax-safearray->list maxpt))

        (setq minx (min minx (car minpt)))
        (setq maxy (max maxy (cadr maxpt)))
      )

      ;; Place left edge at X=0, top edge at Y=0 → Quad IV
      (setq disp (list 
                   (+ (- minx) margin)
                   (- (- maxy) margin)
                   0
                 )
      )
      (command "_.MOVE" ss5 "" '(0 0 0) disp)
    )
    (princ "\nNo entities on layer 5.")
  )

  (princ)
)

;; --- Helper: get filename without extension (for logging)
(defun _filename (path / base ext) 
  (setq base (vl-filename-base path)
        ext  (vl-filename-extension path)
  )
  (if (and ext (/= ext "")) 
    (if (= (substr ext 1 1) ".") 
      (strcat base ext)
      (strcat base "." ext)
    )
    base
  )
)

;; --- Helper: get base name without extension (for SaveAs naming)
(defun _basename (path / base) 
  (vl-filename-base path)
)

;; Helper: collect layers that have any entities on them (after purge, to check for invalid layers that still have objects)
(defun _layers-in-use (/ ss layers i ent obj lay) 
  (setq ss (ssget "X"))
  (if (not ss) 
    nil
    (progn 
      (setq layers '()
            i      0
      )
      (while (< i (sslength ss)) 
        (setq ent (ssname ss i))
        (setq obj (vlax-ename->vla-object ent))
        (setq lay (vla-get-Layer obj))
        (if (and lay (not (member lay layers))) 
          (setq layers (cons lay layers))
        )
        (setq i (1+ i))
      )
      (reverse layers)
    )
  )
)

;; Helper: find non-text entities on layer "5" (for QA)
(defun _non-text-on-layer-5 (/ ss badTypes i ent entdata typ) 
  (setq ss (ssget "X" '((8 . "5"))))
  (if (not ss) 
    nil
    (progn 
      (setq badTypes '()
            i        0
      )
      (while (< i (sslength ss)) 
        (setq ent     (ssname ss i)
              entdata (entget ent)
              typ     (cdr (assoc 0 entdata))
        )
        (if 
          (and typ 
               (not (member typ '("TEXT" "MTEXT" "ATTDEF")))
               (not (member typ badTypes))
          )
          (setq badTypes (cons typ badTypes))
        )
        (setq i (1+ i))
      )
      (reverse badTypes)
    )
  )
)

;; Helper: case-insensitive intersection of two lists of strings (returns items from list A that match any item in list B, ignoring case)
(defun _intersect-ci (a b / b-upper res item) 
  (setq b-upper (mapcar 'strcase b)
        res     '()
  )
  (foreach item a 
    (if (member (strcase item) b-upper) 
      (setq res (cons item res))
    )
  )
  (reverse res)
)

;; Helper: join a list of strings with a separator (e.g. ", ")
(defun _join (lst sep / out) 
  (setq out "")
  (foreach item lst 
    (setq out (strcat out item sep))
  )
  (if (> (strlen out) (strlen sep)) 
    (substr out 1 (- (strlen out) (strlen sep)))
    out
  )
)

;; %%%%%%%%%%%%%%%%%%% Delete DIMENSION entities %%%%%%%%%%%%%%%%%%%
(defun _delete-dimensions-in-current (/ ss n) 
  (setq ss (ssget "X" (list (cons 0 "DIMENSION"))))
  (setq n (if ss 
            (sslength ss)
            0
          )
  )
  (if (> n 0) 
    (progn 
      (command "._UNDO" "_Begin")
      (command "._ERASE" ss "")
      (command "._UNDO" "_End")
    )
  )
  n
)

;; %%%%%%%%%%%%%%%%%%% Move entities to a target layer %%%%%%%%%%%%%%%%%%%
;; entTypes : string of entity type(s) for ssget, e.g. "CIRCLE,ARC"
;; targetLayer : string layer name, e.g. "4"
;; Returns: list (moved-count already-correct-count)
(defun _fix-layer (entTypes targetLayer / ss total moved already ent i obj curLayer) 
  (setq ss (ssget "X" (list (cons 0 entTypes))))
  (setq total   (if ss 
                  (sslength ss)
                  0
                )
        moved   0
        already 0
  )
  (if (> total 0) 
    (progn 
      (_ensure-layer targetLayer)
      (setq i 0)
      (while (< i total) 
        (setq ent (ssname ss i))
        (setq obj (vlax-ename->vla-object ent))
        (setq curLayer (vla-get-Layer obj))
        (if (= curLayer targetLayer) 
          (setq already (1+ already)) ;; already correct  skip
          (progn 
            (vl-catch-all-apply 
              '(lambda () (vla-put-Layer obj targetLayer))
            )
            (setq moved (1+ moved))
          )
        )
        (setq i (1+ i))
      )
    )
  )
  (list moved already)
)

;; %%%%%%%%%%%%%%%%%%% Strip degree symbols from text entities %%
(defun _remove-degree-symbols (str /) 
  "Return STR with degree symbols removed (various AutoCAD/Unicode forms)."
  (if (or (not str) (= str "")) 
    ""
    (progn 
      (while (vl-string-search "%%d" str) 
        (setq str (vl-string-subst "" "%%d" str))
      )
      (while (vl-string-search "%%D" str) 
        (setq str (vl-string-subst "" "%%D" str))
      )
      (while (vl-string-search "°" str) 
        (setq str (vl-string-subst "" "°" str))
      )
      (while (vl-string-search "º" str) 
        (setq str (vl-string-subst "" "º" str))
      )
      (while (vl-string-search "\\U+00B0" str) 
        (setq str (vl-string-subst "" "\\U+00B0" str))
      )
      (while (vl-string-search "\\u+00b0" str) 
        (setq str (vl-string-subst "" "\\u+00b0" str))
      )
      str
    )
  )
)
(defun _strip-degree-symbols (/ ss i ent entdata txt newtxt count) 
  (setq ss (ssget "X" '((0 . "TEXT,MTEXT,ATTDEF"))))
  (setq count 0)
  (if ss 
    (progn 
      (setq i 0)
      (while (< i (sslength ss)) 
        (setq ent     (ssname ss i)
              entdata (entget ent)
              txt     (cdr (assoc 1 entdata))
        )
        (setq newtxt (_remove-degree-symbols txt))
        (if (and txt (not (equal txt newtxt))) 
          (progn 
            (setq entdata (subst (cons 1 newtxt) (assoc 1 entdata) entdata))
            (entmod entdata)
            (entupd ent)
            (setq count (1+ count))
          )
        )
        (setq i (1+ i))
      )
    )
  )
  count
)
;; %%%%%%%%%%%%%%%%%%% Save helpers (DXF R10 only) %%%%%%%%%%%%%%%%%%%

(defun _saveas-dxf-r10 (savepath / ok) 
  (setq ok (not 
             (vl-catch-all-error-p 
               (vl-catch-all-apply 
                 '(lambda () (command "._SAVEAS" "R10" savepath))
               )
             )
           )
  )
  (if ok 
    (_log (strcat "  Save: DXF R10 (OK) -> " savepath))
    (_log (strcat "  Save: DXF R10 failed -> " savepath))
  )
  ok
)

;; %%%%%%%%%%%%%%%%%%% Batch command %%%%%%%%%%%%%%%%%%%
(defun c:FixDXF (/ folder files app docs curDoc path openErr oldCE oldFD oldEX 
                 processed totalDeleted changed n dimResult circResult textResult 
                 movedCirc alreadyCirc alreadyText fileIssues problemFiles 
                 problemFileCount fileChanged logPath saveOk
                ) 

  (princ "\n FixDXF: Fix layers, delete dimensions, color-code layers, purge, and save DXF files (R10).")

  ;; 1) Folder (use file dialog)
  (setq folder (getfolder "Select folder containing .DXF files:"))
  (if (not (vl-file-directory-p folder)) 
    (progn 
      (princ "\nInvalid folder. Aborting.")
      (princ)
      (exit)
    )
  )

  ;; 2) Output folder (use file dialog, default to input folder if blank)
  (setq outputFolder (getfolder 
                       "Select output folder for saved files (Cancel = use source folder):"
                     )
  )
  (if (or (not outputFolder) (= outputFolder "")) 
    (setq outputFolder folder)
  )
  (if (not (vl-file-directory-p outputFolder)) 
    (progn 
      (if (vl-catch-all-error-p (vl-mkdir outputFolder)) 
        (progn 
          (princ "\nCould not create output folder. Aborting.")
          (princ)
          (exit)
        )
      )
    )
  )

  ;; 3) Collect files
  (setq files (_list-dxf-files folder nil))
  (if (not files) 
    (progn 
      (princ "\nNo .DXF files found. Nothing to do.")
      (princ)
      (exit)
    )
  )

  ;; 4) Init log
  (setq *LOG-LINES* '())
  (setq logPath (_path-join outputFolder "FixDXF_log.txt"))
  (_log 
    (strcat "FixDXF log" 
            (menucmd "M=$(edtime,$(getvar,date),DD-MON-YYYY HH:MM:SS)")
    )
  )
  (_log (strcat "Folder : " folder))
  (_log (strcat "Job    : " (if jobnum jobnum "(none)")))
  (_log (strcat "Phase  : " (if phase phase "(none)")))
  (_log (strcat "Output : " outputFolder))
  (_log "")
  (_log "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%")

  ;; 5) Suppress UI prompts
  (setq oldCE (getvar "CMDECHO")
        oldFD (getvar "FILEDIA")
        oldEX (getvar "EXPERT")
  )
  (setvar "CMDECHO" 0)
  (setvar "FILEDIA" 0)
  (setvar "EXPERT" 5)

  ;; 6) COM objects
  (setq app (vlax-get-acad-object))
  (setq docs (vla-get-Documents app))

  (setq processed        0
        totalDeleted     0
        changed          0
        problemFileCount 0
        problemFiles     '()
  )

  (foreach path files 
    (princ (strcat "\nProcessing: " path))
    (_log (strcat "FILE: " path))

    (setq openErr (vl-catch-all-apply '(lambda () (vla-open docs path :VLAX-False))))

    (if (vl-catch-all-error-p openErr) 
      (progn 
        (princ (strcat "\n  ! Failed to open: " path))
        (_log "  ERROR: Could not open file  skipped.")
      )

      (progn 
        (setq curDoc (vla-item docs (1- (vla-get-Count docs))))
        (vla-Activate curDoc)

        ;; Per-file issue tracking (for summary output)
        (setq fileIssues '())

        ;; Log if any polylines exist on layers other than "3" (for QA)
        (setq polyLayers (_polyline-layers-not-3))
        (if polyLayers 
          (progn 
            (setq fileIssues (cons 
                               (strcat "Polylines on invalid layer(s): " 
                                       (_join polyLayers ", ")
                               )
                               fileIssues
                             )
            )
            (_log 
              (strcat "  Polylines on invalid layer(s) " (_join polyLayers ", "))
            )
          )
        )

        ;; Log if any non-text entities are on layer 5
        (setq badLayer5 (_non-text-on-layer-5))
        (if badLayer5 
          (progn 
            (setq fileIssues (cons 
                               (strcat "Non-text entities on layer 5: " 
                                       (_join badLayer5 ", ")
                               )
                               fileIssues
                             )
            )
            (_log 
              (strcat "  Non-text entities on layer 5: " (_join badLayer5 ", "))
            )
          )
        )

        ;; %% a) Delete dimensions %%
        (setq n (_delete-dimensions-in-current))
        (setq totalDeleted (+ totalDeleted n))
        (_log (strcat "  Dimensions deleted  : " (_itoa n)))

        ;; %% a1) Remove degree symbols from all text entities %%
        (setq textResult (_strip-degree-symbols))
        (if (> textResult 0) 
          (progn 
            (_log 
              (strcat "  Removed degree symbol from " 
                      (_itoa textResult)
                      " text(s)"
              )
            )
          )
        )

        ;; %% b) Fix CIRCLE + ARC  layer "4" %%
        (setq circResult (_fix-layer "CIRCLE,ARC" "4"))
        (setq movedCirc   (car circResult)
              alreadyCirc (cadr circResult)
        )
        (_log (strcat "  Circles/Arcs moved to layer 4   : " (_itoa movedCirc)))
        (if (> alreadyCirc 0) 
          (_log 
            (strcat "  Circles/Arcs already on layer 4 : " 
                    (_itoa alreadyCirc)
                    " (skipped)"
            )
          )
        )

        ;; Moves everything to 0,0 and sets UCS to World
        (_move-layers-to-zero)
        (_log "  Applied coordinate normalization to layers 3,4,11")
        (command "UCS" "W")

        ;; %% d) Color-code all layers (deterministic by name) %%
        (setq autoLayers (_color-code-all-layers))

        ;; %% d1) Force all entities to use layer color (ByLayer) %%
        (command "._CHPROP" "ALL" "" "Color" "ByLayer" "")
        (_log "  Set all entities to Color=ByLayer")

        ;; %% e) Decide if file changed %%
        (setq fileChanged (or (> n 0) 
                              (> movedCirc 0)
                              (> fixedLineCount 0)
                              T ;; color-coding always ensures a save is warranted
                          )
        )

        (if fileChanged 
          (progn 
            ;; Backup
            (if doBackup 
              (vl-catch-all-apply 
                '(lambda () (vl-file-copy path (strcat path ".bak")))
              )
            )

            ;; %% f) PURGE (all unused, no prompts) %%
            (vl-catch-all-apply 
              '(lambda () (command "._PURGE" "_All" "" "_No"))
            )
            (_log "  Purge              : done")

            ;; Record invalid layers after purge (so we only list layers that still exist and have objects)
            (setq usedLayers (_layers-in-use))
            (setq invalidLayers (_intersect-ci autoLayers usedLayers))
            (if invalidLayers 
              (setq fileIssues (cons 
                                 (strcat "Invalid layers: " 
                                         (_join invalidLayers ", ")
                                 )
                                 fileIssues
                               )
              )
            )
            ;; Record this file's issues after purge
            (if fileIssues 
              (progn 
                (setq problemFileCount (1+ problemFileCount))
                (setq problemFiles (cons 
                                     (strcat (_filename path) 
                                             " : "
                                             (_join (reverse fileIssues) " | ")
                                     )
                                     problemFiles
                                   )
                )
              )
            )

            ;; %% g) Save as DXF R10 with original filename %%
            (setq saveName (strcat (_basename path) ".dxf"))
            (setq savePath (_path-join outputFolder saveName))
            (setq saveOk (_saveas-dxf-r10 savePath))
            (if saveOk 
              (progn 
                (setq changed (1+ changed))
                (princ 
                  (strcat "  -> " 
                          (_itoa n)
                          " dim(s) del, "
                          (_itoa movedCirc)
                          " circ/arc(s) re-layered; "
                          "saved to "
                          savePath
                          "."
                  )
                )
                (_log (strcat "  Saved OK -> " savePath))
              )
              (progn 
                (princ (strcat "\n  ! SAVE failed: " path))
                (_log "  SAVE ERROR  file may not have been updated.")
              )
            )
          )
          (progn 
            (princ "  -> no changes needed (skipped save).")
            (_log "  No changes save skipped.")
            ;; Record invalid layers (only those that actually have objects)
            (setq usedLayers (_layers-in-use))
            (setq invalidLayers (_intersect-ci autoLayers usedLayers))
            (if invalidLayers 
              (setq fileIssues (cons 
                                 (strcat "Invalid layers: " 
                                         (_join invalidLayers ", ")
                                 )
                                 fileIssues
                               )
              )
            )
            ;; Record issues even when no changes were made (no purge performed)
            (if fileIssues 
              (progn 
                (setq problemFileCount (1+ problemFileCount))
                (setq problemFiles (cons 
                                     (strcat (_filename path) 
                                             " : "
                                             (_join (reverse fileIssues) " | ")
                                     )
                                     problemFiles
                                   )
                )
              )
            )
          )
        )
        ;; Close file ONLY if no issues were detected
        (if (not fileIssues)
          (vla-Close curDoc :VLAX-False)
          (progn
            (princ "\n  ! Issues detected  file left OPEN for review.")
            (_log "  File left OPEN due to detected issues.")
          )
        )
        (setq processed (1+ processed))
        (_log "")
      )
    )
  ) ;; end foreach

  ;; Restore sysvars
  (if (numberp oldCE) 
    (setvar "CMDECHO" oldCE)
  )
  (if (numberp oldFD) 
    (setvar "FILEDIA" 1)
  )
  (if (numberp oldEX) 
    (setvar "EXPERT" oldEX)
  )

  ;; Summary
  (_log "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%")
  (_log 
    (strcat "SUMMARY  Files processed: " 
            (_itoa processed)
            "  |  Changed: "
            (_itoa changed)
            "  |  Dimensions removed: "
            (_itoa totalDeleted)
    )
  )
  (_log (strcat "SUMMARY  Problem files: " (_itoa problemFileCount)))
  (if problemFiles 
    (progn 
      (_log "  Files:")
      (foreach f problemFiles 
        (_log (strcat "    " f))
      )
    )
  )
  (_write-log logPath)
  (princ 
    (strcat 
      "\n\nDone.  Processed "
      (_itoa processed)
      " file(s).  "
      "Changed: "
      (_itoa changed)
      ".  "
      "Total dimensions deleted: "
      (_itoa totalDeleted)
      "."
    )
  )
  (princ)
)
(princ "\n====================================================")
(princ "\n  DXF R10 Batch Cleaner Loaded")
(princ "\n  Command available: FixDXF")
(princ "\n  Description: Delete dimensions, fix layers,")
(princ "\n               color‑code, purge, save R10 DXF, log.")
(princ "\n====================================================")
(princ)