;;; ---------------------------------------------------------------------------
;;; MinExplode.lsp
;;; code compiled by YZ August 2026
;;; ---------------------------------------------------------------------------
;;; EXPLODE A MINSERT
;;;
;;; PURPOSE
;;;   Breaks a MINSERT - a block inserted as a rectangular array in one object -
;;;   into the separate block references it stands for.
;;;
;;;   AutoCAD will not do this. EXPLODE refuses a MINSERT outright, and always
;;;   has. So a drawing that arrived with its grid of columns, its car parking
;;;   bays or its ceiling tiles as one MINSERT cannot be edited a bay at a time
;;;   without redrawing the lot.
;;;
;;; HOW IT IS DONE
;;;   A MINSERT is an ordinary INSERT with four extra numbers on it: how many
;;;   columns (group 70), how many rows (71), and the spacing of each (44 and
;;;   45). Set all four back to zero and the object stops being a MINSERT and
;;;   becomes a plain block reference at the first position.
;;;
;;;   Then ARRAY rebuilds the rest as real, separate insertions. The rotation
;;;   matters here: a MINSERT's array runs along the block's own rotation rather
;;;   than along the drawing's X axis, so SNAPANG is set to the block's angle
;;;   for the duration - that is what ARRAY measures its rows and columns
;;;   against - and put back afterwards.
;;;
;;; WHAT WAS FIXED
;;;   - Every variable was global.
;;;   - It returned nil rather than finishing with (princ), so nil was printed
;;;     to the command line each time.
;;;   - There was no error handler. Cancelling after the object had been
;;;     converted but before the ARRAY finished left a single block reference
;;;     where the array had been - the other positions simply gone, with no way
;;;     back but U, and no undo group to make that one step.
;;;   - SNAPANG was set from the block and restored, but only on the path where
;;;     everything worked. Any failure in between left the crosshairs rotated.
;;;
;;;   MINEXPLODE  - break a MINSERT into separate blocks
;;; ---------------------------------------------------------------------------

(defun c:MINEXPLODE ( / *error* vars vals sel ent data cols rows dx dy ang
                        name total n done snapWas )

    (setq vars '("CMDECHO" "HIGHLIGHT" "SNAPANG") vals (mapcar 'getvar vars)
          snapWas (getvar "SNAPANG"))

    (defun Min:Restore ( )
        (mapcar 'setvar vars vals)
        (while (= 8 (logand 8 (getvar 'undoctl))) (command "_.UNDO" "_End"))
        (vl-catch-all-apply '(lambda ( ) (*pop-error-mode*)) '())
        (princ)
    )

    (defun *error* ( msg )
        (Min:Restore)
        (if (and msg (not (wcmatch (strcase msg t) "*break*,*cancel*,*exit*")))
            (princ (strcat "\n** MINEXPLODE error: " msg " **")))
        (princ)
    )

    (setvar "CMDECHO" 0)
    (setvar "HIGHLIGHT" 0)
    ;; AutoCAD 2015 and later refuse (command) inside an *error* handler unless
    ;; the routine says up front that it will use one.
    (vl-catch-all-apply '(lambda ( ) (*push-error-using-command*)) '())
    (command "_.UNDO" "_Begin")

    (setq n 0 done nil)

    (while (not done)
        (setq sel (entsel "\nMINSERT to break up <Enter to finish>: "))

        (if (null sel)
            (setq done t)
            (progn
                (setq ent  (car sel)
                      data (entget ent))

                (cond
                    ((/= "INSERT" (cdr (assoc 0 data)))
                     (princ (strcat "\n  That is a " (cdr (assoc 0 data))
                                    ", not a block.")))

                    (t
                     (setq cols (cond ((cdr (assoc 70 data))) (t 0))
                           rows (cond ((cdr (assoc 71 data))) (t 0))
                           dx   (cond ((cdr (assoc 44 data))) (t 0.0))
                           dy   (cond ((cdr (assoc 45 data))) (t 0.0))
                           ang  (cond ((cdr (assoc 50 data))) (t 0.0))
                           name (cdr (assoc 2 data)))

                     (if (<= (* cols rows) 1)
                         (princ (strcat "\n  \"" name "\" is an ordinary block"
                                        " insertion - EXPLODE handles that."))
                         (progn
                             (setq total (* cols rows))

                             ;; Strip the array off, leaving a plain insertion
                             ;; at the first position.
                             (entmod (list (assoc -1 data)
                                           '(70 . 0) '(71 . 0)
                                           '(44 . 0.0) '(45 . 0.0)))
                             (entupd ent)

                             ;; ARRAY works along the current snap angle, and a
                             ;; MINSERT's grid runs along the block's rotation.
                             ;; Put back from the value saved on entry, not by
                             ;; index into the saved list - counting positions
                             ;; is how the wrong variable gets restored.
                             (setvar "SNAPANG" ang)
                             (command "_.ARRAY" ent "" "_R" rows cols dy dx)
                             (setvar "SNAPANG" snapWas)

                             (setq n (+ n total))
                             (princ (strcat "\n  \"" name "\" broken into "
                                            (itoa total) " blocks - "
                                            (itoa rows) " rows of "
                                            (itoa cols) "."))))))))
    )

    (princ (strcat "\n" (itoa n) " block reference"
                   (if (= n 1) "" "s") " now stand separately."))

    (Min:Restore)
    (princ)
)

(princ)
