; MPLOT v1.5.4 CLEAN GUI
; Rewritten GUI path handling to eliminate stringp nil errors.
; Command: MPLOT

(vl-load-com)

(setq *mplot-margin* 3.0)
(setq *mplot-mode* "Combine")

(defun mp:str (x fallback)
  (if (and (= (type x) 'STR) (> (strlen x) 0)) x fallback)
)

(defun mp:safe-name (s / bad)
  (setq s (mp:str s "MPLOT"))
  (setq bad '("\\" "/" ":" "*" "?" "\"" "<" ">" "|"))
  (foreach x bad (setq s (vl-string-subst "_" x s)))
  s
)

(defun mp:dwg-folder (/ p)
  (setq p (getvar "DWGPREFIX"))
  (if (and (= (type p) 'STR) (> (strlen p) 0))
    p
    (progn
      (setq p (getenv "TEMP"))
      (if (and (= (type p) 'STR) (> (strlen p) 0))
        p
        "C:\\"
      )
    )
  )
)

(defun mp:base-name (/ n)
  (setq n (getvar "DWGNAME"))
  (if (and (= (type n) 'STR) (> (strlen n) 0))
    (mp:safe-name (vl-filename-base n))
    "MPLOT"
  )
)

(defun mp:join-path (folder file)
  (setq folder (mp:str folder (mp:dwg-folder)))
  (setq file   (mp:str file "MPLOT.pdf"))
  (if (member (substr folder (strlen folder) 1) '("\\" "/"))
    (strcat folder file)
    (strcat folder "\\" file)
  )
)

(defun mp:closed-frame-p (e / d typ)
  (setq d (entget e)
        typ (cdr (assoc 0 d)))
  (cond
    ((= typ "LWPOLYLINE")
     (= 1 (logand 1 (cdr (assoc 70 d)))))
    ((= typ "POLYLINE")
     (= 1 (logand 1 (cdr (assoc 70 d)))))
    (T nil)
  )
)

(defun mp:bbox (e / o mn mx a b)
  (setq o (vlax-ename->vla-object e))
  (vla-getboundingbox o 'mn 'mx)
  (setq a (vlax-safearray->list mn)
        b (vlax-safearray->list mx))
  (list (list (car a) (cadr a))
        (list (car b) (cadr b)))
)

(defun mp:center (bb)
  (list (/ (+ (caar bb) (caadr bb)) 2.0)
        (/ (+ (cadar bb) (cadadr bb)) 2.0))
)

(defun mp:2d-point (pt / sa)
  (setq sa (vlax-make-safearray vlax-vbDouble '(0 . 1)))
  (vlax-safearray-fill sa (list (car pt) (cadr pt)))
  sa
)

(defun mp:width (bb) (- (caadr bb) (caar bb)))
(defun mp:height (bb) (- (cadadr bb) (cadar bb)))

(defun mp:frame-rec (e / bb c)
  (setq bb (mp:bbox e)
        c (mp:center bb))
  (list e bb (car c) (cadr c))
)

(defun mp:sort-frames (lst / avgH tol)
  (setq avgH (/ (apply '+ (mapcar '(lambda (r) (mp:height (cadr r))) lst))
                (float (length lst))))
  (setq tol (* avgH 0.35))
  (vl-sort lst
    (function
      (lambda (a b / ya yb xa xb)
        (setq ya (nth 3 a) yb (nth 3 b)
              xa (nth 2 a) xb (nth 2 b))
        (if (> (abs (- ya yb)) tol)
          (> ya yb)
          (< xa xb)
        )
      )
    )
  )
)

(defun mp:get-frames (/ ss i e out)
  (prompt "\nSelect CLOSED polyline sheet frames: ")
  (setq ss (ssget '((0 . "LWPOLYLINE,POLYLINE"))))
  (if ss
    (progn
      (setq i 0 out '())
      (repeat (sslength ss)
        (setq e (ssname ss i))
        (if (mp:closed-frame-p e)
          (setq out (cons (mp:frame-rec e) out)))
        (setq i (1+ i))
      )
      (if out (mp:sort-frames out))
    )
  )
)

(defun mp:expand-bbox (bb pct / c hx hy f)
  (setq c (mp:center bb)
        hx (/ (mp:width bb) 2.0)
        hy (/ (mp:height bb) 2.0)
        f (+ 1.0 (/ (* 2.0 pct) 100.0)))
  (setq hx (* hx f)
        hy (* hy f))
  (list
    (list (- (car c) hx) (- (cadr c) hy))
    (list (+ (car c) hx) (+ (cadr c) hy)))
)

(defun mp:dcl-path ()
  (mp:join-path (mp:dwg-folder) "__mplot_v15_tmp.dcl")
)

(defun mp:write-dcl (/ fn f)
  (setq fn (mp:dcl-path))
  (setq f (open fn "w"))
  (if f
    (progn
      (foreach ln
        '(
          "mplot : dialog { label = \"MPLOT v1.5.4\";"
          "  : boxed_column { label = \"Output\";"
          "    : edit_box { key=\"folder\"; label=\"Folder:\"; edit_width=52; }"
          "    : row {"
          "      : edit_box { key=\"pdfname\"; label=\"PDF name:\"; edit_width=34; }"
          "      : button { key=\"browse\"; label=\"Browse...\"; width=12; }"
          "    }"
          "  }"
          "  : boxed_column { label = \"Mode\";"
          "    : radio_button { key=\"combine\"; label=\"Combine all sheets into ONE multi-page PDF\"; }"
          "    : radio_button { key=\"separate\"; label=\"Separate PDF for each sheet\"; }"
          "  }"
          "  : boxed_column { label = \"Fit\";"
          "    : edit_box { key=\"margin\"; label=\"Margin (% per side):\"; edit_width=8; }"
          "    : text { label=\"Keeps aspect ratio, centers frame and fits it inside paper.\"; }"
          "  }"
          "  spacer;"
          "  ok_cancel;"
          "}"
        )
        (write-line ln f))
      (close f)
      fn
    )
    nil
  )
)

(defun mp:browse-pdf (/ initial picked dir name)
  (setq initial (mp:join-path
                  (mp:str *mp-gui-folder* (mp:dwg-folder))
                  (strcat (mp:safe-name *mp-gui-name*) ".pdf")))
  (setq picked (getfiled "Choose output PDF" initial "pdf" 1))
  (if (and picked (= (type picked) 'STR) (> (strlen picked) 0))
    (progn
      (setq dir (vl-filename-directory picked))
      (setq name (vl-filename-base picked))
      (if (and dir (= (type dir) 'STR)) (setq *mp-gui-folder* dir))
      (if (and name (= (type name) 'STR)) (setq *mp-gui-name* name))
      T
    )
    nil
  )
)

(defun mp:gui (/ fn id ok ret)
  (setq *mp-gui-folder* (mp:dwg-folder))
  (setq *mp-gui-name* (mp:base-name))
  (setq *mp-gui-margin* (if (numberp *mplot-margin*) *mplot-margin* 3.0))
  (setq *mp-gui-mode* (if (= *mplot-mode* "Separate") "Separate" "Combine"))
  (setq ok nil)

  (prompt "\n[MPLOT] Opening settings...")
  (setq fn (mp:write-dcl))
  (if (not fn)
    (progn
      (prompt "\n[MPLOT] Could not create dialog file.")
      nil
    )
    (progn
      (setq id (load_dialog fn))
      (if (or (not (numberp id)) (<= id 0))
        (progn
          (prompt "\n[MPLOT] Could not load dialog.")
          nil
        )
        (progn
          (if (not (new_dialog "mplot" id))
            (progn
              (prompt "\n[MPLOT] Could not open dialog.")
              nil
            )
            (progn
              (set_tile "folder" *mp-gui-folder*)
              (set_tile "pdfname" *mp-gui-name*)
              (set_tile "margin" (rtos *mp-gui-margin* 2 1))
              (if (= *mp-gui-mode* "Separate")
                (set_tile "separate" "1")
                (set_tile "combine" "1"))

              (action_tile "browse"
                "(setq *mp-gui-folder* (get_tile \"folder\")) (setq *mp-gui-name* (get_tile \"pdfname\")) (if (mp:browse-pdf) (progn (set_tile \"folder\" *mp-gui-folder*) (set_tile \"pdfname\" *mp-gui-name*)))")

              (action_tile "accept"
                "(setq *mp-gui-folder* (get_tile \"folder\")) (setq *mp-gui-name* (get_tile \"pdfname\")) (setq *mp-gui-margin* (atof (get_tile \"margin\"))) (setq *mp-gui-mode* (if (= (get_tile \"separate\") \"1\") \"Separate\" \"Combine\")) (setq ok T) (done_dialog 1)")

              (action_tile "cancel"
                "(setq ok nil) (done_dialog 0)")

              (start_dialog)
            )
          )
          (unload_dialog id)

          (if ok
            (progn
              (setq *mp-gui-folder* (mp:str *mp-gui-folder* (mp:dwg-folder)))
              (setq *mp-gui-name* (mp:safe-name *mp-gui-name*))
              (if (not (numberp *mp-gui-margin*)) (setq *mp-gui-margin* 3.0))
              (if (< *mp-gui-margin* 0.0) (setq *mp-gui-margin* 0.0))
              (if (not (member *mp-gui-mode* '("Combine" "Separate")))
                (setq *mp-gui-mode* "Combine"))

              (setq *mplot-margin* *mp-gui-margin*)
              (setq *mplot-mode* *mp-gui-mode*)

              (prompt (strcat "\n[MPLOT] Folder = " *mp-gui-folder*))
              (prompt (strcat "\n[MPLOT] Name   = " *mp-gui-name*))
              (prompt (strcat "\n[MPLOT] Mode   = " *mp-gui-mode*))
              (prompt (strcat "\n[MPLOT] Margin = " (rtos *mp-gui-margin* 2 1) "%"))

              (setq ret
                (list *mp-gui-folder*
                      *mp-gui-name*
                      *mp-gui-margin*
                      *mp-gui-mode*))
            )
            (setq ret nil)
          )
          ret
        )
      )
    )
  )
)

(defun mp:copy-layout-basic (src dst)
  (foreach p '(ConfigName CanonicalMediaName PlotRotation
                PlotWithPlotStyles StyleSheet PlotWithLineweights)
    (if (and (vlax-property-available-p src p)
             (vlax-property-available-p dst p T))
      (vl-catch-all-apply
        'vlax-put-property
        (list dst p (vlax-get-property src p)))))
)

(defun mp:make-temp-layouts (frames margin / acad doc lays model names i nm lay blk vp bb c w h whres vw vh obj)
  (prompt "\n[MPLOT] Stage 1/3: creating temporary sheets...")
  (setq acad (vlax-get-acad-object)
        doc (vla-get-ActiveDocument acad)
        lays (vla-get-Layouts doc)
        model (vla-item lays "Model")
        names '()
        i 1)

  (foreach r frames
    (prompt (strcat "\n[MPLOT] Preparing sheet " (itoa i) "/" (itoa (length frames)) "..."))

    (setq nm (strcat "MPLOT_TMP_" (itoa i)))
    (while (not (vl-catch-all-error-p
                  (vl-catch-all-apply 'vla-item (list lays nm))))
      (setq nm (strcat nm "_")))

    (setq lay (vla-add lays nm))
    (mp:copy-layout-basic model lay)
    (setvar "CTAB" nm)

    ;; Remove default paper-space entities/viewports so our viewport is the only one.
    (setq blk (vla-get-block lay))
    (vlax-for obj blk
      (vl-catch-all-apply 'vla-delete (list obj)))

    ;; Query actual paper size.
    (setq w nil h nil)
    (setq whres
      (vl-catch-all-apply
        '(lambda () (vla-GetPaperSize lay 'w 'h))
        '()))

    (if (or (vl-catch-all-error-p whres)
            (not (numberp w))
            (not (numberp h))
            (<= w 0.0)
            (<= h 0.0))
      (progn
        (prompt "\n[MPLOT] Paper size query unavailable; using safe 17x11 viewport basis.")
        (setq w 17.0 h 11.0))
    )

    (prompt (strcat "\n[MPLOT] Paper basis = "
                    (rtos w 2 2) " x " (rtos h 2 2)))

    (setq vw (* w 0.94)
          vh (* h 0.94)
          c  (list (/ w 2.0) (/ h 2.0)))

    (setq vp
      (vla-AddPViewport
        blk
        (vlax-3d-point (list (car c) (cadr c) 0.0))
        vw vh))

    (prompt "\n[MPLOT] Viewport created.")
    (vla-Display vp :vlax-true)
    (prompt "\n[MPLOT] Viewport display enabled.")

    ;; LT-safe fitting: activate the single viewport and use normal ZOOM Window.
    (setq bb (mp:expand-bbox (cadr r) margin))

    (command "_.MSPACE")
    (command "_.ZOOM" "_W" (car bb) (cadr bb))
    (command "_.PSPACE")

    (prompt "\n[MPLOT] Frame fitted in viewport.")

    (vl-catch-all-apply 'vla-put-DisplayLocked (list vp :vlax-true))
    (vl-catch-all-apply 'vla-put-PlotType (list lay 5))

    (setq names (append names (list nm)))
    (setq i (1+ i))
  )
  names
)



(defun mp:delete-layouts (names / doc lays obj)
  (setq doc (vla-get-ActiveDocument (vlax-get-acad-object))
        lays (vla-get-Layouts doc))
  (setvar "CTAB" "Model")
  (foreach nm names
    (setq obj (vl-catch-all-apply 'vla-item (list lays nm)))
    (if (not (vl-catch-all-error-p obj))
      (vl-catch-all-apply 'vla-delete (list obj))))
)

(defun mp:write-dsd (names outfile / fn f dwg i)
  (setq fn (mp:join-path (mp:dwg-folder) "__mplot_v15_tmp.dsd"))
  (setq dwg (strcat (getvar "DWGPREFIX") (getvar "DWGNAME")))
  (setq f (open fn "w"))
  (if (not f)
    nil
    (progn
      (write-line "[DWF6Version]" f)
      (write-line "Ver=1" f)
      (setq i 1)
      (foreach nm names
        (write-line (strcat "[DWF6Sheet:" (itoa i) "]") f)
        (write-line (strcat "DWG=" dwg) f)
        (write-line (strcat "Layout=" nm) f)
        (write-line "Setup=" f)
        (write-line (strcat "OriginalSheetPath=" dwg) f)
        (write-line "Has Plot Port=0" f)
        (write-line "Has3DDWF=0" f)
        (setq i (1+ i)))
      (write-line "[Target]" f)
      (write-line "Type=6" f)
      (write-line (strcat "DWF=" outfile) f)
      (write-line (strcat "OUT=" (vl-filename-directory outfile)) f)
      (write-line "PWD=" f)
      (write-line "[PdfOptions]" f)
      (write-line "IncludeHyperlinks=TRUE" f)
      (write-line "CreateBookmarks=TRUE" f)
      (write-line "CaptureFontsInDrawing=TRUE" f)
      (write-line "ConvertTextToGeometry=FALSE" f)
      (write-line "VectorResolution=600" f)
      (write-line "RasterResolution=400" f)
      (write-line "[SheetSet Properties]" f)
      (write-line "IsSheetSet=FALSE" f)
      (write-line "IsHomogeneous=FALSE" f)
      (write-line "SheetSet Name=MPLOT" f)
      (write-line "NoOfCopies=1" f)
      (write-line "PlotStampOn=FALSE" f)
      (write-line "ViewFile=FALSE" f)
      (write-line "PromptForDwfName=FALSE" f)
      (close f)
      fn
    )
  )
)

(defun mp:publish-combined (frames folder name margin / oldtab oldbg oldfd layouts outfile dsd)
  (setq oldtab (getvar "CTAB")
        oldbg (getvar "BACKGROUNDPLOT")
        oldfd (getvar "FILEDIA"))

  (setq outfile (mp:join-path folder (strcat name ".pdf")))
  (prompt "\n[MPLOT] Entering temporary-layout engine...")
  (setq layouts (mp:make-temp-layouts frames margin))

  (if layouts
    (progn
      (prompt "\n[MPLOT] Stage 2/3: building publish job...")
      (setq dsd (mp:write-dsd layouts outfile))
      (if dsd
        (progn
          (prompt "\n[MPLOT] Stage 3/3: publishing...")
          (setvar "BACKGROUNDPLOT" 0)
          (setvar "FILEDIA" 0)
          (command "_.-PUBLISH" dsd)
          (prompt (strcat "\n[MPLOT] Publish finished -> " outfile))
        )
        (prompt "\n[MPLOT] Could not create DSD file.")
      )
      (mp:delete-layouts layouts)
    )
  )

  (setvar "FILEDIA" oldfd)
  (setvar "BACKGROUNDPLOT" oldbg)
  (setvar "CTAB" oldtab)
)

(defun mp:plot-separate (frames folder name margin / i bb p1 p2 out)
  (setq i 1)
  (foreach r frames
    (setq bb (mp:expand-bbox (cadr r) margin)
          p1 (car bb)
          p2 (cadr bb)
          out (mp:join-path folder
                (strcat name "-Sheet-"
                        (if (< i 10) "0" "")
                        (itoa i) ".pdf")))
    (prompt (strcat "\n[MPLOT] Separate sheet " (itoa i) ": " out))
    ;; Keep separate engine intentionally simple and based on current plot settings.
    (command "_.-PLOT" "_Yes" "Model" "" "" "" "_Window" p1 p2 "_Fit" "_Center"
             "_Yes" "" "_Yes" "_No" out "_No" "_Yes")
    (setq i (1+ i)))
)

(defun c:MPLOT (/ *error* frames cfg folder name margin mode oldcmd oldtab)
  (setq oldcmd (getvar "CMDECHO")
        oldtab (getvar "CTAB"))

  (defun *error* (msg)
    (if oldtab (vl-catch-all-apply 'setvar (list "CTAB" oldtab)))
    (if oldcmd (vl-catch-all-apply 'setvar (list "CMDECHO" oldcmd)))
    (if (and msg (not (wcmatch (strcase msg) "*CANCEL*,*QUIT*,*EXIT*")))
      (prompt (strcat "\n[MPLOT] Error: " msg)))
    (princ))

  (setvar "CMDECHO" 0)
  (if (/= (getvar "CTAB") "Model") (setvar "CTAB" "Model"))

  (setq frames (mp:get-frames))
  (if (not frames)
    (prompt "\n[MPLOT] No valid closed frames selected.")
    (progn
      (prompt (strcat "\n[MPLOT] " (itoa (length frames)) " sheet(s) detected."))
      (setq cfg (mp:gui))
      (if cfg
        (progn
          (setq folder (nth 0 cfg)
                name   (nth 1 cfg)
                margin (nth 2 cfg)
                mode   (nth 3 cfg))
          (if (= mode "Combine")
            (mp:publish-combined frames folder name margin)
            (mp:plot-separate frames folder name margin))
        )
        (prompt "\n[MPLOT] Cancelled.")
      )
    )
  )

  (setvar "CTAB" oldtab)
  (setvar "CMDECHO" oldcmd)
  (princ)
)

(prompt "\nMPLOT v1.5.4 CLEAN loaded. Type MPLOT.")
(princ)
