Post script custom paper size

Hey guys!

I am currently creating a solution for batch printing. I’ve got 32 layouts in Revit project, each has different name, paper szie and orientation. The script makes it possible to assign special parameters to each layout during the printing instantly, instead of making it manually. Currently i’ve got 10 general paper sizes, each is not standard. I’ve created them through “Print server setting” manually.

Now i want to make the script being able to grab width and height parameters directly from the Title Block and provide them to PostScript Custom Paper Size. When i specify the paper szie name “PostScript Custom Paper Size” the script finds it properly, but next it, hypotheticly, should ask the user to provide the W and H values. So that the script crashes.

`       currentformat = printManager.PrintSetup.CurrentPrintSetting.PrintParameters.PaperSize
        print(currentformat.Name)
        
        #set paper size
        pss = printManager.PaperSizes
        for ps in pss:
            if ps.Name.ToString() == SheetFormat[i] and currentformat.Name != ps.Name:
                printManager.PrintSetup.CurrentPrintSetting.PrintParameters.PaperSize = ps
                currentformat = ps
                print("Format is found: " + SheetFormat[i])
                printSetup.Save()

Is there any kind of API to get “PostScript Custom Paper Size” class/object and set its members through some method? I couldn’t find anything clear about it. So i hope someone has an idea how to deal with it :smiley:

batch_printing.gh (17.6 KB)

pyRevit has two python functions that look at the titleblock size and determine the WxH and orientation, and will select the appropriate paper size from the ones defined on the system. Take a look at these and see if this helps:

Thank you for your reply!

Well, the problem was that it is not comfortable sometimes to create a new paper size manually (using Printer Server Settings) each time i’ve got new layout. The question is how to set custom paper size programmaticaly (via api, etc)

I think this is gonna be your entry point:

Thank you for your reply :smiley:

The solution you have sent just appends an another item to an Array of paperSizes, but does not initiates a new paper size in the system. But thank you anyway, this is good entrypoint :smiley: Hope in a few days i will find a solution!

1 Like

Hey there again!

Here is the proper solution! In order to interact with windows’ printer settings the win32print library is needed. Here is the peace of code to Add a new custom paper size to printer server:

import win32print
hPrinter = win32print.OpenPrinter("PDF24")

form_name = "my_silly_paper_size"
tForm = ({'Flags': 2, 'Name': form_name, 'Size': {'cx': 215900, 'cy': 279400}, 'ImageableArea': {'left': 0, 'top': 0, 'right': 215900, 'bottom': 279400}})
win32print.AddForm(hPrinter,tForm)

and here is a string to remove it:

win32print.DeleteForm(hPrinter,form_name)

I’ve tested it via Anaconda’s Jupyter notebook, now trying to solve it throug Grasshopper’s GhPython shell. The problem is that win32print is not being actually installed by default into either Anaconda and IronPython. And now i’ve got troubles with installing a new library to IronPython. The way described here doesn’t work:

https://ironpython.net/blog/2014/12/07/pip-in-ironpython-275.html

I’ve got an exception every time: :dizzy_face:
‘ipy’ is not recognized as an internal or external command,
operable program or batch file.

Maybe there is someone, who could help me with implementing new library into GhPython?

win32printing is a Cpython library only due to the pywin32 dependency. I’d search for how to add a printer form thru C#. Then you can use the same api from IronPython

Well, currently i’ve made it work through a such wokraround. I realize this to be pretty ridiculous, but it works properly :crazy_face: :crazy_face: :crazy_face:

def create_format(printer_name, path_to_files, x_size, y_size):
    paper_x = str(x_size)
    paper_y = str(y_size)
    path_to_files = path_to_files +"\TEMP.py"
    f = open(path_to_files, "w")   # 'r' for reading and 'w' for writing
    f.write("from win32 import win32print \n") 
    f.write("hPrinter = win32print.OpenPrinter('"+printer_name+"') \n")  
    f.write("form_name = 'my_silly_size' \n")
    f.write("try: \n")
    f.write("   win32print.DeleteForm(hPrinter,form_name) \n")  
    f.write("except: \n")
    f.write(" pass \n")
    f.write("tForm = ({'Flags': 0, 'Name': form_name, 'Size': {'cx': "+paper_x+", 'cy': "+paper_y+"}, 'ImageableArea': {'left': 0, 'top': 0, 'right': "+paper_x+", 'bottom': "+paper_y+"}}) \n")  
    f.write("win32print.AddForm(hPrinter,tForm) \n")  
    f.write("win32print.ClosePrinter(hPrinter) \n")  
    f.close()
    cmdpath = 'cmd /c'+path_to_files
    stream = os.system(cmdpath)

The code creates a *.py file with win32 library imported and adds a new form; then the script opens a command prompt and runs the file, i’ve just created. Regular Python 3.x with win32 pre-installed should be available in PATH.

1 Like

Hello,
Here is the IronPython compatible version. However, the paper size is only created if the user is an administrator. Does anyone have a solution to apply it to any user?

#!/usr/bin/python
# encoding: utf-8
import sys
import clr
clr.AddReference('System.Drawing')
from System.Drawing.Printing import PrinterSettings, PrintDocument, PaperSize

import ctypes
from ctypes import wintypes
from ctypes.wintypes import DWORD, LPCWSTR, SIZEL, RECTL, UINT

kernel32 = ctypes.WinDLL('kernel32.dll', use_last_error=True)
winspool = ctypes.WinDLL('winspool.drv', use_last_error=True)

# define LPHANDLE, PDWORD, and PWORD for Python 2
if not hasattr(wintypes, 'LPHANDLE'):
    setattr(wintypes, 'LPHANDLE', ctypes.POINTER(wintypes.HANDLE))
if not hasattr(wintypes, 'PDWORD'):
    setattr(wintypes, 'PDWORD', ctypes.POINTER(wintypes.DWORD))
if not hasattr(wintypes, 'PWORD'):
    setattr(wintypes, 'PWORD', ctypes.POINTER(wintypes.WORD))

class PRINTER_INFO_1(ctypes.Structure):
    _fields_ = [
        ("Flags", DWORD),
        ("pDescription", LPCWSTR),
        ("pName", LPCWSTR),
        ("pComment", LPCWSTR),
    ]

class PRINTER_DEFAULTS(ctypes.Structure):
    _fields_ = [
        ("pDatatype", LPCWSTR),      
        ("pDevMode", LPCWSTR),
        ("DesiredAccess", DWORD),
    ]

class FORM_INFO_1(ctypes.Structure):
    _fields_ = [
        ("Flags", DWORD),
        ("pName", LPCWSTR),
        ("Size", SIZEL),
        ("ImageableArea", RECTL),
    ]

class PRINTER_OPTIONS(ctypes.Structure):
    _fields_ = [
        ("cbSize", UINT),
        ("dwFlags", DWORD),
    ]

def createPaperSize(name, x, y):    
    printerDef = PRINTER_DEFAULTS(None , "1" , DWORD(4))
    printerOpt = PRINTER_OPTIONS(1, DWORD(2))
    hPrinter = wintypes.HANDLE()    
    pd = PrintDocument()
    sPrinterName = pd.PrinterSettings.PrinterName
    bOpenPrinter = winspool.OpenPrinterW("PDFCreator", ctypes.byref(hPrinter), printerDef)
    formName = name

    # delete the form incase it already exists
    try:
        bDeleteForm = winspool.DeleteFormW(hPrinter, formName)
    except Exception as e:
        print (e)
  
    # create and initialize the FORM_INFO_1 structure

    sizeX = y*1000
    sizeY = x*1000
    formInfo = FORM_INFO_1(DWORD(0), formName, (sizeX, sizeY), (0, 0, sizeX, sizeY))    
    bFormAdded = winspool.AddFormW(hPrinter, DWORD(1), formInfo)
    bClosePrinter = winspool.ClosePrinter(hPrinter)
    return formName

All I had to do was post a request and I would have found the solution! Here is the solution for a non-admin user to create the papersize:

#!/usr/bin/python
# encoding: utf-8
import sys
import clr
clr.AddReference('System.Drawing')
from System.Drawing.Printing import PrinterSettings, PrintDocument, PaperSize

import ctypes
from ctypes import wintypes
from ctypes.wintypes import DWORD, LPCWSTR, SIZEL, RECTL, UINT

kernel32 = ctypes.WinDLL('kernel32.dll', use_last_error=True)
winspool = ctypes.WinDLL('winspool.drv', use_last_error=True)

# define LPHANDLE, PDWORD, and PWORD for Python 2
if not hasattr(wintypes, 'LPHANDLE'):
    setattr(wintypes, 'LPHANDLE', ctypes.POINTER(wintypes.HANDLE))
if not hasattr(wintypes, 'PDWORD'):
    setattr(wintypes, 'PDWORD', ctypes.POINTER(wintypes.DWORD))
if not hasattr(wintypes, 'PWORD'):
    setattr(wintypes, 'PWORD', ctypes.POINTER(wintypes.WORD))

class PRINTER_INFO_1(ctypes.Structure):
    _fields_ = [
        ("Flags", DWORD),
        ("pDescription", LPCWSTR),
        ("pName", LPCWSTR),
        ("pComment", LPCWSTR),
    ]

class PRINTER_DEFAULTS(ctypes.Structure):
    _fields_ = [
        ("pDatatype", LPCWSTR),      
        ("pDevMode", LPCWSTR),
        ("DesiredAccess", DWORD),
    ]

class FORM_INFO_1(ctypes.Structure):
    _fields_ = [
        ("Flags", DWORD),
        ("pName", LPCWSTR),
        ("Size", SIZEL),
        ("ImageableArea", RECTL),
    ]

class PRINTER_OPTIONS(ctypes.Structure):
    _fields_ = [
        ("cbSize", UINT),
        ("dwFlags", DWORD),
    ]

def createPaperSize(name, x, y):    
    printerDef = PRINTER_DEFAULTS(None , "1" , 0x20000000)
    printerOpt = PRINTER_OPTIONS(1, DWORD(2))
    hPrinter = wintypes.HANDLE()    
    pd = PrintDocument()
    sPrinterName = pd.PrinterSettings.PrinterName
    bOpenPrinter = winspool.OpenPrinterW("PDFCreator", ctypes.byref(hPrinter), printerDef)
    formName = name

    # delete the form incase it already exists
    try:
        bDeleteForm = winspool.DeleteFormW(hPrinter, formName)
    except Exception as e:
        print (e)
  
    # create and initialize the FORM_INFO_1 structure

    sizeX = y*1000
    sizeY = x*1000
    formInfo = FORM_INFO_1(DWORD(0), formName, (sizeX, sizeY), (0, 0, sizeX, sizeY))    
    bFormAdded = winspool.AddFormW(hPrinter, DWORD(1), formInfo)
    bClosePrinter = winspool.ClosePrinter(hPrinter)
    return formName

I changed the value of PRINTER_DEFAULT “DesiredAccess” to 0x20000000. This is the value for a GENERIC_EXECUTE access request, shown on this page: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb/27f99d29-7784-4684-b6dd-264e9025b286

1 Like