;; Description: AutoCAD script that saves each layer from layout into a new pdf document. ;; Tested on AutoCAD (Civil 3D) 2017, AutoCAD 2022 (Civil 3D) and AutoCAD (Civil 3D) 2024 Windows. ;; License: This work is licensed under a Creative Commons Attribution-NoDerivatives 4.0 International License. (https://creativecommons.org/licenses/by-nd/4.0/) ;; Copyright (c) 2024, Michel Simons ;; https://www.no-nonsens.nl ;; ;; 2.16 November 18th 2024 ;; 2.15 November 4th 2024 ;; 1.1017 November 2018 ;; You are free to: ;; ;; Share ;; copy and redistribute the material in any medium or format for any purpose, even commercially. ;; The licensor cannot revoke these freedoms as long as you follow the license terms. ;; Under the following terms: ;; Attribution: ;; You must give appropriate credit , provide a link to the license, and indicate if changes were made . You may do so in any reasonable manner, but not in any way that suggests the licensor endorses you or your use. ;; NoDerivatives: ;; If you remix, transform, or build upon the material, you may not distribute the modified material. ;; No additional restrictions: ;; You may not apply legal terms or technological measures that legally restrict others from doing anything the license permits. ;; Notices: ;; You do not have to comply with the license for elements of the material in the public domain or where your use is permitted by an applicable exception or limitation. ;; No warranties are given. The license may not give you all of the permissions necessary for your intended use. For example, other rights such as publicity, privacy, or moral rights may limit how you use the material. ;; ******************************************************************************************************** ;; ** ** ;; ** To Do list ** ;; ** ** ;; ******************************************************************************************************** ;; ;; - add as much as possible options from the page setup dialog to the printer ;; - Make the script also compatible with other PDF-writers ;; - better error handling, can't we put that in an function? ;; ;; ******************************************************************************************************** ;; ** ** ;; ** Known issues ** ;; ** ** ;; ******************************************************************************************************** ;; ;; - Due to the usage of Active X and Visual Lisp this script will not run op Apple platforms ;; - Each created pdf-document will be opened automatically by the default pdf-reader. There is no script fix for this. ;; - the script uses limits to detect the page orientation as this is for now the only fullproof detection methode ignoring the page setup Landscape and Portait radio buttons. ;; ;; ******************************************************************************************************** ;; ** ** ;; ** Tested pdf-printers ** ;; ** ** ;; ******************************************************************************************************** ;; ;; - AutoCAD PDF (General Documentation).pc3 ;; - AutoCAD PDF (High Quality Print).pc3 ;; - AutoCAD PDF (Smallest File).pc3 ;; - AutoCAD PDF (Web and Mobile).pc3 ;; - DWG To PDF.pc3 ;; ;; TEST WAITING ;; ;; - Adobe PDF - Installed with Adobe Acrobat. ;; - Microsoft Print to PDF - Standard in Windows. ;; - CutePDF Writer - Installed with CutePDF. ;; - PDF-XChange Printer - Part of PDF-XChange suite. ;; - Bluebeam PDF - Installed with Bluebeam Revu. ;; - Foxit PDF Printer - From Foxit PDF software. ;; - Nitro PDF Creator - Installed with Nitro PDF. ;; ;; ******************************************************************************************* ;; ** ** ;; ** function to show an confirmation message dialog ** ;; ** source code: https://lee-mac.com/popup.html ** ;; ** ** ;; ******************************************************************************************* (defun popup ( ttl msg bit / wsh rtn ) (if (setq wsh (vlax-create-object "wscript.shell")) (progn (setq rtn (vl-catch-all-apply 'vlax-invoke-method (list wsh 'popup msg 0 ttl bit))) (vlax-release-object wsh) (if (not (vl-catch-all-error-p rtn)) rtn) ) ) ) ;; ******************************************************************************************* ;; ** ** ;; ** function to check if a file exists and to create a new unqique filename ** ;; ** source code derived from ChatGTP ** ;; ** ** ;; ******************************************************************************************* (defun get-unique-filename (filepath / baseName extension counter newFilename directory) ;; Helper function to check if a file exists (defun file-exists-p (filename) (if (findfile filename) T nil ) ) ;; Check if the filepath has a directory (setq directory (vl-filename-directory filepath)) (if (not directory) (setq directory (getenv "TEMP")) ;; If no directory, use the system temp folder ) ;; Extract the base name and extension from the full path (setq baseName (vl-filename-base filepath)) (setq extension (vl-filename-extension filepath)) ;; Initialize counter and create a new filename (setq counter 1) (setq newFilename filepath) ;; Loop until a unique filename is found (while (file-exists-p newFilename) (setq newFilename (strcat directory "\\" baseName "_" (itoa counter) extension)) (setq counter (1+ counter)) ) ;; Return the full path of the unique filename newFilename ) ;; Example usage ;;(setq uniqueFilename (get-unique-filename "C:/path/to/yourfile.dwg")) ;;(princ uniqueFilename) ;; Will print the full unique filename path ;; ******************************************************************************************* ;; ** ** ;; ** function to regenerate the current layout ** ;; ** source code derived from AutoDesk help: ** ;; ** help.autodesk.com/view/OARX/2023/ENU/?guid=GUID-CCF21523-F711-4FA0-9D5B-4A3D3F61D37F ** ;; ** ** ;; ******************************************************************************************* (defun regenerate-layout () (setq acadObj (vlax-get-acad-object)) ; Get the AutoCAD application object (setq doc (vla-get-ActiveDocument acadObj)) ; Get the active document (vla-Regen doc acAllViewports) ; Regenerate all viewports in the active document ) ;; ******************************************************************************************* ;; ** ** ;; ** function to check if a layer contains objects and needs to be exported ** ;; ** source code derived from ChatGTP ** ;; ** ** ;; ******************************************************************************************* (defun CheckLayerObjects (layer) ;; Check if the layer contains objects (setq ss (ssget "X" (list (cons 8 layer)))) ;; Return 1 if the layer contains objects, else return 0 (if ss 1 ; Layer contains objects 0 ; Layer is empty ) ) ;; ******************************************************************************************* ;; ** ** ;; ** function to show Select Folder dialog ** ;; ** source code derived from ChatGTP ** ;; ** ** ;; ******************************************************************************************* (defun SelectFolderDialog (customMessage) (setq objShell (vlax-create-object "Shell.Application")) (setq objFolder (vlax-invoke-method objShell 'BrowseForFolder 0 customMessage 0)) ; Show the dialog (if objFolder (setq folderPath (vlax-get-property (vlax-get-property objFolder 'Self) 'Path)) ; Get the selected folder path (setq folderPath nil) ; Set folderPath to nil if no folder was selected (Cancel pressed) ) (if objFolder (vlax-release-object objFolder)) ; Release objFolder if it exists (vlax-release-object objShell) folderPath ; Return the folder path or nil if "Cancel" was pressed ) ;; ******************************************************************************************* ;; ** ** ;; ** function to sanitize a layer name ** ;; ** source code derived from ChatGTP ** ;; ** ** ;; ******************************************************************************************* (defun sanitize-layer-name (str) (setq invalid-chars "<>:\"/\\|?*") ; Invalid characters as a string (setq result "") ; Initialize the result string (foreach char (vl-string->list str) ; Loop through the string as a list of characters (if (vl-string-search (chr char) invalid-chars) ; If the character is invalid (setq result (strcat result "_")) ; Replace invalid character with "_" (setq result (strcat result (chr char))) ; Otherwise, keep the character ) ) result ; Return the modified string ) ;; ******************************************************************************************* ;; ** ** ;; ** function to detect the page orientation based on the page limits ** ;; ** source code derived from ChatGTP ** ;; ** ** ;; ******************************************************************************************* (defun GetPaperOrientation (/ limmin limmax width height orientation) ;; Get the current paper limits (setq limmin (getvar "LIMMIN")) ; Lower-left corner of the paper (setq limmax (getvar "LIMMAX")) ; Upper-right corner of the paper ;; Calculate the width and height of the paper (setq width (- (car limmax) (car limmin))) (setq height (- (cadr limmax) (cadr limmin))) ;; Determine orientation (setq orientation (if (> width height) "A" "Portrait")) ; A for pAysage instead of Landscape français ;; Return the orientation as a string orientation ) ;; ******************************************************************************************* ;; ******************************************************************************************* ;; ******************************************************************************************* ;; ** ** ;; ** MAIN SCRIPT ** ;; ** ** ;; ** ** ;; ******************************************************************************************* ;; ******************************************************************************************* ;; ******************************************************************************************* (defun c:layers2PDF ( / *error* ) (vl-load-com) (defun *error* ( msg ) (princ "Script error: ") (princ msg) (princ) ) ;; ******************************************************************************************* ;; ** ** ;; ** Check if script is executed from Layout, if not then exit ** ;; ** source code derived from ChatGTP ** ;; ** ** ;; ******************************************************************************************* (cond ;; Check if TILEMODE is 0 and CVPORT > 1 (inside a viewport on a layout tab) ((and (= (getvar "TILEMODE") 0) (> (getvar "CVPORT") 1)) (progn (princ "\nError: Wrong environment. You are currently working inside a Viewport on a Layout tab. Please leave the Viewport and run the script again.") (alert "Error: Wrong environment.\n\nYou are currently working inside a Viewport on a Layout tab.\n\nPlease leave the Viewport and run the script again.") (vl-exit-with-value nil) ) ) ;; Check if TILEMODE is 1 (full model space or tiled viewports) ((= (getvar "TILEMODE") 1) (progn (princ "\nError: Wrong environment. You are currently working in Model Space. Please choose a Layout to export and run the script again.") (alert "Error: Wrong environment\n\nYou are currently working in Model Space.\nPlease choose a Layout to export and run the script again.") (vl-exit-with-value nil) ) ) ) ; cond ;; ******************************************************************************************* ;; ** ** ;; ** Check if we can get the layout name, if not then exit ** ;; ** source code derived from ChatGTP ** ;; ** ** ;; ******************************************************************************************* (setq layoutName (getvar "CTAB")) (if (not LayoutName) (progn ((princ "\nError: Layout not detected\n\nUnable to detect a name for the current Layout. Unable to continue.") alert "Error: Layout not detected\n\nUnable to detect a name for the current Layout.\n\nUnable to continue.") ((vl-exit-with-value nil) ) ) ) (if (= LayoutName "") (progn (princ "\nError: Layout not detected\n\nUnable to detect a name for the current Layout. Unable to continue.") (alert "Error: Layout not detected\n\nUnable to detect a name for the current Layout.\n\nUnable to continue.") ((vl-exit-with-value nil) ) ) ) ;; ******************************************************************************************* ;; ** ** ;; ** Check if a pdf printer/plotter has been assigned to the current layout ** ;; ** source code derived from ChatGTP ** ;; ** ** ;; ******************************************************************************************* (if (and (setq currentLayout (vla-get-ActiveLayout (vla-get-ActiveDocument (vlax-get-acad-object)))) (setq CurrentPlotConfig (vla-get-ConfigName currentLayout))) (if (vl-string-search "PDF" CurrentPlotConfig) T (progn (princ "\nError: Wrong plotter assigned.\n\nPlease assign a PDF printer/plotter to the current Layout. Unable to continue.") (alert "Error: Wrong plotter assigned.\n\nPlease assign a PDF printer/plotter to the current Layout.\n\nUnable to continue.") ((vl-exit-with-value nil) ) ) ) ) ;; ******************************************************************************************* ;; ** ** ;; ** Check what type of PDF printer is assigned and add a value to it ** ;; ** value 0 for AutoCAD default printers, value 1 to thirth party printers ** ;; ** ** ;; ******************************************************************************************* (setq PlotterConfigCheck 1) (setq PossibleValues '("AutoCAD PDF (General Documentation).pc3" "AutoCAD PDF (High Quality Print).pc3" "AutoCAD PDF (Smallest File).pc3" "AutoCAD PDF (Web and Mobile).pc3" "DWG To PDF.pc3")) (if (member CurrentPlotConfig PossibleValues) ; List of values to check against (progn (setq PlotterConfigCheck 0) ) ) (if (= PlotterConfigCheck 1) (progn (setq choice (popup "PDF plotter support issue" "The selected PDF plotter can not automatically assign a file name for plot outputs. If you continue you need to manually type a name for each exported layer.\n\nDo you like to continue?" (+ 4 64 4096))) (if (= choice 7) (vl-exit-with-value nil)) ) ) ;; ******************************************************************************************* ;; ** ** ;; ** Check if the page setup manager uses Inches or Millimeters for units ** ;; ** source code derived from ChatGTP ** ;; ** ** ;; ******************************************************************************************* ;; Get the current document object (setq doc (vla-get-activedocument (vlax-get-acad-object))) ;; Get the first layout (setq pageSetup (vla-get-activelayout doc)) ;; Get the plot configuration (this will give you the page setup name) (setq plotCfgName (vla-get-configname pageSetup)) ;; Check if it's in inches or millimeters (returns 0 for inches, 1 for millimeters) (setq units (vla-get-paperunits pageSetup)) ;; Store the units in a variable (cond ((= units 0) (setq PaperUnits "P")) ;P for Pouces instead of I for Inches français ((= units 1) (setq PaperUnits "M")) ; M for millimètres instead on Millimeters français (t (setq PaperUnits "Unknown")) ) ;; ******************************************************************************************* ;; ** ** ;; ** Get the plot scale from the page setup manager ** ;; ** source code derived from ChatGTP ** ;; ** ** ;; ******************************************************************************************* ;(setq CurrentPlotScale "1:1") ;(setq layoutName (getvar "CTAB")) ; Get the current layout name ;; Locate the ACAD_LAYOUT dictionary and search for the current layout's dictionary entry ;(setq dict_search (cdr (assoc -1 (dictsearch (namedobjdict) "ACAD_LAYOUT")))) ;(setq layout_dict (dictsearch dict_search layoutName)) ;; Find the plot settings section ;(setq plot_settings (member '(100 . "AcDbPlotSettings") layout_dict)) ;; Extract the plot scale from the associated code 143 ;(setq get_plotscale (cdr (assoc 143 plot_settings))) ;; Display the plot scale ;(if get_plotscale ; (progn ; (setq CurrentPlotScale (strcat "1:" (rtos (/ 1.0 get_plotscale) 2 4))) ; (princ (strcat "1:" (rtos (/ 1.0 get_plotscale) 2 4))) ; Display plot scale ;) ;) (setq CurrentPlotScale "1000:1") ;; ******************************************************************************************* ;; ** ** ;; ** Detect the name of the current plot style assigned to the page setup ** ;; ** source code derived from ChatGTP ** ;; ** ** ;; ******************************************************************************************* (setq layoutName (getvar "CTAB")) ; Get current layout name (setq pageSetup (vla-get-ActiveLayout (vla-get-ActiveDocument (vlax-get-acad-object)))) ; Get the active page setup (setq plotStyleTable (vla-get-StyleSheet pageSetup)) ; Retrieve plot style table (setq plotStyleTableName (if plotStyleTable plotStyleTable "None")) ; Handle if no plot style is set ;; ******************************************************************************************* ;; ** ** ;; ** Detect the layout paper size format ** ;; ** source code derived from ChatGTP ** ;; ** ** ;; ******************************************************************************************* ;; Get the active document (setq doc (vla-get-ActiveDocument (vlax-get-Acad-Object))) ;; Try to get the layouts collection from the document (setq layouts (vla-get-Layouts doc)) ;; Get the current layout object using its name (setq layoutObj (vla-Item layouts layoutName)) ;; Get the plot configuration name (printer/plotter name) (setq plotConfigName (vla-get-ConfigName layoutObj)) ;; Get the paper size (Canonical media name) (setq paperSize (vla-get-CanonicalMediaName layoutObj)) (if (not PaperSize) (progn (princ "\nError: Document dimensions not detected. Unable to detect the current plot document width and height. Unable to continue.")(alert "Error: Document dimensions not detected.\n\nUnable to detect the current plot document width and height. Unable to continue.") ((vl-exit-with-value nil) ) ) ) (if (= PaperSize "") (progn (princ "\nError: Document dimensions not detected. Unable to detect the current plot document width and height. Unable to continue.")(alert "Error: Document dimensions not detected.\n\nUnable to detect the current plot document width and height. Unable to continue.") ((vl-exit-with-value nil) ) ) ) ;; ******************************************************************************************* ;; ** ** ;; ** Detect the layout paper orientation ** ;; ** source code written by ChatGTP, based on an idea by me ** ;; ** ** ;; ******************************************************************************************* (setq orientation (GetPaperOrientation)) ; Store orientation in a variable ;; ******************************************************************************************* ;; ** ** ;; ** create a list of all layers that are on and a list for on / thawed / plottable ** ;; ** source code derived from ChatGTP ** ;; ** ** ;; ******************************************************************************************* (setq AcadObject (vlax-get-acad-object)) (setq ActiveDocument (vla-get-activedocument AcadObject)) (setq LayerTable (vla-get-layers ActiveDocument)) (setq NewLayerTable nil) ; Initialize as an empty list (setq RestoreLayerTable nil) ; List for layers that are only ON (vlax-for layer LayerTable (if (and (= (vla-get-LayerOn layer) :vlax-true) ; Layer is on (= (vla-get-Freeze layer) :vlax-false) ; Layer is thawed (= (vla-get-Plottable layer) :vlax-true) ; Layer is plottable ) (setq NewLayerTable (cons layer NewLayerTable)) ; Add the layer to NewLayerTable ) ; Add to 'otherlist' if the layer is ON (ignoring other properties) (if (= (vla-get-LayerOn layer) :vlax-true) ; Layer is on (setq RestoreLayerTable (cons layer RestoreLayerTable)) ; Add the layer to RestoreLayerTable ) ) ; flax-for ;; ******************************************************************************************* ;; ** ** ;; ** get folder storage location ** ;; ** source code derived from CoPilot ** ;; ** ** ;; ******************************************************************************************* (setq plotOffset "0,0") ; Default value in case of error (setq actlayout (vl-catch-all-apply 'vlax-get (list (vla-Get-ActiveDocument (vlax-Get-Acad-Object)) 'ActiveLayout))) (if (not (vl-catch-all-error-p actlayout)) (progn (setq offset (vl-catch-all-apply 'vlax-get (list actlayout 'PlotOrigin))) (if (not (vl-catch-all-error-p offset)) (setq plotOffset (strcat (rtos (car offset) 2 2) "," (rtos (cadr offset) 2 2))) ) ) ) ;; ******************************************************************************************* ;; ** ** ;; ** get folder storage location ** ;; ** source code derived from ChatGTP ** ;; ** ** ;; ******************************************************************************************* (setq selectedFolder (SelectFolderDialog "Select destination folder for PDF document output")) (if (not selectedFolder) ; Check if "Cancel" was pressed (no folder selected) (progn (princ "\nOperation canceled by the user.") (princ) (vl-exit-with-value nil) ) ) ;; ******************************************************************************************* ;; ** ** ;; ** turn off all layers that needs to be exported to PDF ** ;; ** source code derived from ChatGTP ** ;; ** ** ;; ******************************************************************************************* (foreach layer NewLayerTable (vla-put-LayerOn layer :vlax-false) ; Turn off the layer ) ;; ******************************************************************************************* ;; ** ** ;; ** Regenerate the drawing ** ;; ** ** ;; ******************************************************************************************* (regenerate-layout) ;; ******************************************************************************************* ;; ******************************************************************************************* ;; ** FRENCH VERSION OF THE AUTOCAD COMMANDS : 'français' in the comments ** ;; ** start folder save dialog and export layers ** ;; ** ** ;; ******************************************************************************************* (foreach layer NewLayerTable ;; make a pdf for each layer (vla-put-LayerOn layer :vlax-true) ;; Make layer to process visible. (regenerate-layout) ;; check if layer contains objects (setq layerName (vla-get-Name layer)) (setq LayerNotEmpty (CheckLayerObjects layerName)) ; 1 Layer contains objects / 0 Layer is empty (setq LayerFileName (sanitize-layer-name layerName)) ;; first make sure layer name contains no illegal windows file characters (setq FullPathFileName (strcat selectedFolder "\\" LayerFileName ".pdf")) (setq UniqueFullPathFileName (get-unique-filename FullPathFileName)) ;; plot to PDF by using default syntax (if (and (= LayerNotEmpty 1) (= PlotterConfigCheck 0) );; 1 only export layer if the layer contains objects (progn (command "-traceur" ; français "O" ; Detailed plot configuration? Yes/: français layoutName ; LayoutName ; plot current layout: CurrentPlotConfig ; Enter an output device name or [?] : paperSize ; PaperSize ; Enter paper size or [?] : PaperUnits ; Enter paper units Inches/: orientation ; Enter drawing orientation Portrait/: "N" ; Plot upside down? Yes/: français "P" ; Enter plot area Display//Layout/View/Window: français p for Présentation instead of Layout CurrentPlotScale ; Enter plot scale (Plotted millimeters = Drawing Units) or Fit <1:1>: plotOffset ; Enter plot offset (x,y) or
: "n" ; Plot with plot styles? Yes/: français plotStyleTableName ; Enter plot style table name or [?] (enter . for none) : "o" ; Plot with lineweights? /No: français "n" ; Scale lineweights with plot scale? Yes/: français "n" ; Plot paper space first? Yes/: français "n" ; Hide paperspace objects : français UniqueFullPathFileName ; Layer Name as File Name "n" ; Save changes to page setup? Yes/: français "o" ; Proceed with plot? /No: français ) ) ;; progn ) ;; if LayernotEmpty ;; plot to PDF by using Adobe PDF syntax (if (and (= LayerNotEmpty 1) (= PlotterConfigCheck 1) );; 1 only export layer if the layer contains objects (progn (command "-PLOT" "o" ; Detailed plot configuration? Yes/: français layoutName ; LayoutName ; plot current layout: CurrentPlotConfig ; Enter an output device name or [?] : paperSize ; PaperSize ; Enter paper size or [?] : PaperUnits ; Enter paper units Inches/: orientation ; Enter drawing orientation Portrait/: "n" ; Plot upside down? Yes/: français "p" ; Enter plot area Display//Layout/View/Window: français p for Présentation instead of Layout CurrentPlotScale ; Enter plot scale (Plotted millimeters = Drawing Units) or Fit <1:1>: plotOffset ; Enter plot offset (x,y) or
: "n" ; Plot with plot styles? Yes/: français plotStyleTableName ; Enter plot style table name or [?] (enter . for none) : "o" ; Plot with lineweights? /No: français "n" ; Scale lineweights with plot scale? Yes/: français "n" ; Plot paper space first? Yes/: français "n" ; Hide paperspace objects : français "n" ; Write the plot to a file [Yes/No] : français "n" ; Save changes to page setup [Yes/No]? : français "o" ; Proceed with plot [Yes/No] : français ) ) ;; progn ) ;; if LayernotEmpty (vla-put-LayerOn layer :vlax-false) ;; Turn the layer off again ) ; foreach layer ;; ******************************************************************************************* ;; ** ** ;; ** restore layer status for all layers now the script has finished ** ;; ** source code derived from ChatGTP ** ;; ** ** ;; ******************************************************************************************* (setq AcadObject (vlax-get-acad-object)) (setq ActiveDocument (vla-get-activedocument AcadObject)) (setq LayerTable (vla-get-layers ActiveDocument)) ; Iterate through all layers and turn them off (vlax-for layer LayerTable (vla-put-LayerOn layer :vlax-false) ; Turn off every layer initially ) ; Iterate through 'otherlist' and turn each layer ON (foreach layer-name RestoreLayerTable (vlax-for layer LayerTable (if (= (vla-get-name layer) (vla-get-Name layer-name)) ; If the layer is in 'otherlist' (vla-put-LayerOn layer :vlax-true) ; Turn it ON ) ) ) (regenerate-layout) (princ (strcat "\n[LAYERS2PDF finished. Exported PDF files can be found in: " selectedFolder "\\]\n")) (princ) ) ;; defun end main routine ;; ******************************************************************************************* ;; ** ** ;; ** Show command layers2pdf on command line ** ;; ** ** ;; ******************************************************************************************* (princ "\nScript layers2pdf.lsp loaded successfully!\n") (princ "\nThis script exports each layer from the current layout into a separate PDF document. Type layers2pdf to begin.\n") (princ)