Beastt1992 Posted 15 hours ago Posted 15 hours ago Hi CADTutor, I'm an architect in Taiwan and wrote a free AutoLISP tool to solve a problem we face daily - batch exporting all title block frames in Model Space to individual PDFs. For those of us who work with all drawings arranged in one Model Space (common workflow in Taiwan/Asia), AutoCAD's built-in Batch Plot doesn't help much since it works at the Layout level. Features: - Click on a title block frame to detect the Block name automatically - Option to plot just that 1 frame, or all frames with the same name - Sorts output top-to-bottom, left-to-right - Dynamically loads plot styles and paper sizes from your machine - Remembers last settings - Compatible with AutoCAD 2014 and above GitHub: https://github.com/beastt1992/autocad-batch-plot Already tested by users on Reddit r/AutoCAD with good feedback. A couple of bugs were found and fixed based on community input (AC_WINDOW constant varies by version, RefreshPlotDeviceInfo order). Would love feedback from the AutoLISP community here - especially if anyone tests it on different AutoCAD versions or workflows! Quote
Steven P Posted 4 hours ago Posted 4 hours ago (edited) So post the LISP so we can give feedback - it is easier to reference if the LISP is in the same thread as the comments and questions Though I might be tempted to say change the system and get the draughters to use paperspace for what it is meant for. (haven't had the paperspace / modespace discussion on here for a while now....) Edited 4 hours ago by Steven P 1 Quote
Beastt1992 Posted 4 hours ago Author Posted 4 hours ago (edited) ;;; BATCH-PDF ;;; Function: Batch export all title block frames to PDF ;;; Command: BPDF ;;; Compatible: AutoCAD 2014+ ;;; v2.0 - Single-frame mode + cross-version compatibility ;;; AC_WINDOW auto-detected (3 or 4) ;;; RefreshPlotDeviceInfo order fixed for fresh sessions (setq AC_0DEG 0) (setq AC_FIT 0) (defun BPDF-cfgpath () (strcat (getenv "APPDATA") "\\bpdf_settings.cfg") ) (defun BPDF-load-cfg ( / f line kv cfg) (setq cfg (list (cons "blockname" "") (cons "prefix" "frame") (cons "outpath" (strcat (getenv "USERPROFILE") "\\Desktop")) (cons "styleNum" "1") (cons "paperNum" "1") (cons "scaleStr" "") )) (setq f (open (BPDF-cfgpath) "r")) (if f (progn (while (setq line (read-line f)) (setq kv (vl-string-search "=" line)) (if kv (setq cfg (subst (cons (substr line 1 kv) (substr line (+ kv 2))) (assoc (substr line 1 kv) cfg) cfg )) ) ) (close f) ) ) cfg ) (defun BPDF-save-cfg (blockname prefix outpath styleNum paperNum scaleStr / f) (setq f (open (BPDF-cfgpath) "w")) (if f (progn (write-line (strcat "blockname=" blockname) f) (write-line (strcat "prefix=" prefix) f) (write-line (strcat "outpath=" outpath) f) (write-line (strcat "styleNum=" (itoa styleNum)) f) (write-line (strcat "paperNum=" (itoa paperNum)) f) (write-line (strcat "scaleStr=" scaleStr) f) (close f) ) ) ) (defun c:BPDF ( / blockname outpath ss i ent obj sel entdata plotMode minpoint maxpoint pt1 pt2 counter fname adoc alayout aplot scaleStr scaleVal useFit styleSheet prefix frameList frame fx fy fx2 fy2 rowHeight plotterName styleList styleNum allMedia mediaShort mediaIdx m j item paperNum paperSize cfg lastVal inp confirm AC_WINDOW err) (vl-load-com) (setq cfg (BPDF-load-cfg)) ;; 1. Click to select title block, or type name (princ "\nClick on a title block frame (or press Enter to type block name): ") (setq sel (entsel "")) (if sel (progn (setq ent (car sel)) (setq entdata (entget ent)) (if (= (cdr (assoc 0 entdata)) "INSERT") (progn (setq blockname (cdr (assoc 2 entdata))) (princ (strcat "\nBlock detected: " blockname "\n")) (initget "1 A") (setq plotMode (getkword "\nPlot [1=This frame only / A=All frames with this name] <A>: ") ) (if (null plotMode) (setq plotMode "A")) ) (progn (alert "Please click on a Block (INSERT) entity.") (exit) ) ) ) (progn (setq lastVal (cdr (assoc "blockname" cfg))) (if (/= lastVal "") (setq inp (getstring (strcat "\nBlock Name <" lastVal ">: "))) (setq inp (getstring "\nBlock Name: ")) ) (setq blockname (if (= inp "") lastVal inp)) (if (= blockname "") (progn (princ "\nCancelled.") (exit))) (setq plotMode "A") ) ) ;; 2. PDF Prefix (setq lastVal (cdr (assoc "prefix" cfg))) (setq inp (getstring (strcat "\nPDF Prefix <" lastVal ">: "))) (setq prefix (if (= inp "") lastVal inp)) ;; 3. Output Folder (setq lastVal (cdr (assoc "outpath" cfg))) (setq inp (getstring (strcat "\nOutput Folder <" lastVal ">: "))) (setq outpath (if (= inp "") lastVal inp)) (if (/= (substr outpath (strlen outpath)) "\\") (setq outpath (strcat outpath "\\")) ) (vl-mkdir outpath) (princ (strcat "Output: " outpath "\n")) ;; 4. Build frame list (if (= plotMode "1") (progn ;; Single mode: get bbox of the clicked entity directly (setq obj (vlax-ename->vla-object ent)) (vla-getboundingbox obj 'minpoint 'maxpoint) (setq frameList (list (list (vlax-safearray-get-element minpoint 0) (vlax-safearray-get-element minpoint 1) (vlax-safearray-get-element maxpoint 0) (vlax-safearray-get-element maxpoint 1) ))) (setq counter 1) (princ "Mode: Single frame\n") ) (progn ;; All mode: find all matching blocks (setq ss (ssget "X" (list (cons 0 "INSERT") (cons 2 blockname) (cons 410 "Model")) )) (if (null ss) (progn (alert (strcat "Block not found: " blockname)) (exit)) ) (setq counter (sslength ss)) (setq frameList nil i 0) (repeat counter (setq ent (ssname ss i)) (setq obj (vlax-ename->vla-object ent)) (vla-getboundingbox obj 'minpoint 'maxpoint) (setq fx (vlax-safearray-get-element minpoint 0)) (setq fy (vlax-safearray-get-element minpoint 1)) (setq fx2 (vlax-safearray-get-element maxpoint 0)) (setq fy2 (vlax-safearray-get-element maxpoint 1)) (setq frameList (append frameList (list (list fx fy fx2 fy2)))) (setq i (1+ i)) ) ;; Sort: top-to-bottom rows, left-to-right within row (setq rowHeight (* (- (cadddr (car frameList)) (cadr (car frameList))) 0.5)) (setq frameList (vl-sort frameList (function (lambda (a b) (if (> (abs (- (cadr a) (cadr b))) rowHeight) (> (cadr a) (cadr b)) (< (car a) (car b)) ) )) ) ) (princ (strcat "Mode: All frames (" (itoa counter) " found)\n")) ) ) ;; 5. VLA setup ;; Fix from forum: RefreshPlotDeviceInfo BEFORE ConfigName, ;; then ConfigName, then RefreshPlotDeviceInfo again. ;; This prevents "Invalid input" on fresh CAD sessions. (setq adoc (vla-get-activedocument (vlax-get-acad-object))) (setq alayout (vla-get-activelayout adoc)) (setq aplot (vla-get-plot adoc)) (setq plotterName "DWG To PDF.pc3") (vla-RefreshPlotDeviceInfo alayout) (vla-put-configname alayout plotterName) (vla-RefreshPlotDeviceInfo alayout) ;; Detect AC_WINDOW value: try 4 first (works on more setups), ;; fall back to 3 only if 4 is rejected. ;; Note: plottype=3 can silently "accept" but not actually work. (setq AC_WINDOW 4) (setq err (vl-catch-all-apply (function (lambda () (vla-put-plottype alayout 4))) )) (if (vl-catch-all-error-p err) (progn (vla-put-plottype alayout 3) (setq AC_WINDOW 3) ) ) ;; 6. Plot Style (setq styleList (vlax-safearray->list (vlax-variant-value (vla-GetPlotStyleTableNames alayout)) ) ) (setq styleList (append (list "None (Color)") styleList)) (setq lastVal (atoi (cdr (assoc "styleNum" cfg)))) (if (or (< lastVal 1) (> lastVal (length styleList))) (setq lastVal 1)) (princ "\nPlot Style:\n") (setq i 1) (foreach s styleList (princ (strcat " " (itoa i) ". " s (if (= i lastVal) " <- last" "") "\n")) (setq i (1+ i)) ) (setq inp (getint (strcat "Select <" (itoa lastVal) ">: "))) (setq styleNum (if (or (null inp) (< inp 1) (> inp (length styleList))) lastVal inp)) (setq styleSheet (if (= styleNum 1) "" (nth (1- styleNum) styleList))) (princ (strcat "Style: " (if (= styleSheet "") "None" styleSheet) "\n")) ;; 7. Paper Size (setq allMedia (vlax-safearray->list (vlax-variant-value (vla-GetCanonicalMediaNames alayout)) ) ) (setq mediaShort nil mediaIdx 0) (foreach m allMedia (if (or (vl-string-search "A0" m) (vl-string-search "A1" m) (vl-string-search "A2" m) (vl-string-search "A3" m) (vl-string-search "A4" m)) (setq mediaShort (append mediaShort (list (list mediaIdx m)))) ) (setq mediaIdx (1+ mediaIdx)) ) (setq lastVal (atoi (cdr (assoc "paperNum" cfg)))) (if (or (< lastVal 1) (> lastVal (length mediaShort))) (setq lastVal 1)) (princ "\nPaper Size:\n") (setq j 1) (foreach item mediaShort (princ (strcat " " (itoa j) ". " (cadr item) (if (= j lastVal) " <- last" "") "\n")) (setq j (1+ j)) ) (setq inp (getint (strcat "Select <" (itoa lastVal) ">: "))) (setq paperNum (if (or (null inp) (< inp 1) (> inp (length mediaShort))) lastVal inp)) (setq paperSize (cadr (nth (1- paperNum) mediaShort))) (princ (strcat "Paper: " paperSize "\n")) ;; 8. Scale (setq lastVal (cdr (assoc "scaleStr" cfg))) (setq inp (getstring (strcat "\nScale denominator (100=1:100, 0 or F=Fit, Enter=last setting)" (if (/= lastVal "") (strcat " <" lastVal ">") " <Fit>") ": ") )) (setq scaleStr (if (= inp "") lastVal inp)) ;; 0 or F or empty = Fit to paper (if (or (= scaleStr "") (= scaleStr "0") (= (strcase scaleStr) "F") (= (strcase scaleStr) "FIT")) (progn (setq useFit T scaleVal 0) (setq scaleStr "")) (setq useFit nil scaleVal (atof scaleStr)) ) (BPDF-save-cfg blockname prefix outpath styleNum paperNum scaleStr) ;; 9. Apply plot settings ONCE before loop (not inside loop!) (setvar "BACKGROUNDPLOT" 0) (vla-put-canonicalmedianame alayout paperSize) (vla-put-plotrotation alayout AC_0DEG) (vla-put-centerplot alayout :vlax-true) (vla-put-plotwithlineweights alayout :vlax-true) (if (/= styleSheet "") (progn (vla-put-plotwithplotstyles alayout :vlax-true) (vla-put-stylesheet alayout styleSheet) ) (vla-put-plotwithplotstyles alayout :vlax-false) ) (if useFit (progn (vla-put-useStandardScale alayout :vlax-true) (vla-put-standardscale alayout AC_FIT) ) (progn (vla-put-useStandardScale alayout :vlax-false) (vla-SetCustomScale alayout 1.0 scaleVal) ) ) ;; 10. Confirm (alert (strcat "===== Confirm Plot =====\n\n" "Frames: " (itoa counter) "\n" "Prefix: " prefix "\n" "Output: " outpath "\n" "Paper: " paperSize "\n" "Style: " (if (= styleSheet "") "None" styleSheet) "\n" "Scale: " (if useFit "Fit" (strcat "1:" (rtos scaleVal 2 0))) "\n\n" "Click OK to continue" )) (initget "Y N") (setq confirm (getkword "\nConfirm [Y=Yes / N=Cancel] <Y>: ")) (if (= confirm "N") (progn (princ "\nCancelled.") (exit)) ) ;; 11. Plot loop ;; SetWindowToPlot THEN put-plottype (per Autodesk docs) ;; No RefreshPlotDeviceInfo inside the loop (that was breaking it) (princ "Plotting...\n") (setq i 0) (foreach frame frameList (setq pt1 (vlax-make-safearray vlax-vbdouble '(0 . 1))) (vlax-safearray-put-element pt1 0 (car frame)) (vlax-safearray-put-element pt1 1 (cadr frame)) (setq pt2 (vlax-make-safearray vlax-vbdouble '(0 . 1))) (vlax-safearray-put-element pt2 0 (caddr frame)) (vlax-safearray-put-element pt2 1 (cadddr frame)) (vla-SetWindowToPlot alayout pt1 pt2) (vla-put-plottype alayout AC_WINDOW) (setq fname (strcat outpath prefix "_" (itoa (1+ i)) ".pdf")) (vla-PlotToFile aplot fname) (princ (strcat " [" (itoa (1+ i)) "/" (itoa counter) "] Done\n")) (setq i (1+ i)) ) (setvar "BACKGROUNDPLOT" 2) (alert (strcat "Done! " (itoa counter) " PDFs exported\nLocation: " outpath)) (princ) ) GitHub link with README and installation instructions: https://github.com/beastt1992/autocad-batch-plot Happy to answer any questions! Edited 4 hours ago by Beastt1992 Quote
Beastt1992 Posted 4 hours ago Author Posted 4 hours ago 16 minutes ago, Steven P said: So post the LISP so we can give feedback - it is easier to reference if the LISP is in the same thread as the comments and questions Though I might be tempted to say change the system and get the draughters to use paperspace for what it is meant for. (haven't had the paperspace / modespace discussion on here for a while now....) Thanks Steven! Code is posted above. Regarding Paper Space - completely agree it's the better long-term approach, but changing an entire office workflow is harder than writing a tool to work around it! Quote
pkenewell Posted 3 hours ago Posted 3 hours ago (edited) 1 hour ago, Steven P said: So post the LISP so we can give feedback @Steven P Not easy to notice, but he did give a link to the routine: https://github.com/beastt1992/autocad-batch-plot I agree that layouts should be used. Edited 3 hours ago by pkenewell Quote
mhupp Posted 2 hours ago Posted 2 hours ago My 0.02¢ Instead of outputting the status to command line (princ (strcat " [" (itoa (1+ i)) "/" (itoa counter) "] Done\n")) Output to the Status Bar (setvar "MODEMACRO" (strcat "Processing: [" (itoa (1+ i)) "/" (itoa counter) "] Layouts")) Then you don't have clutter/spam in the command prompt but still have the current stats. Just have to clear it after complete. Quote
Beastt1992 Posted 1 hour ago Author Posted 1 hour ago 1 hour ago, mhupp said: My 0.02¢ Instead of outputting the status to command line (princ (strcat " [" (itoa (1+ i)) "/" (itoa counter) "] Done\n")) Output to the Status Bar (setvar "MODEMACRO" (strcat "Processing: [" (itoa (1+ i)) "/" (itoa counter) "] Layouts")) Then you don't have clutter/spam in the command prompt but still have the current stats. Just have to clear it after complete. That's a great tip, thank you! Much cleaner than spamming the command line. I'll add this to the next version and clear the status bar when done. Quote
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.