Print Sheets: pass print settings to the internal PDF exporter and support combined export - #3612
Print Sheets: pass print settings to the internal PDF exporter and support combined export#3612hvacengi wants to merge 3 commits into
Conversation
When exporting sheets to PDF through the built-in "Revit Internal Printer" (Revit 2022+), the tool built a fixed PDFExportOptions and ignored the selected print setting, so choices such as color depth, raster quality and hidden-line view rendering were never applied to the output. Pass the selected print setting's PrintParameters into pdf_opts() and copy ColorDepth and RasterQuality onto the PDFExportOptions. PDF export exposes no hidden line views option of its own, so a print setting that asks for raster processing is mapped onto AlwaysUseRaster instead. Each value is applied defensively, so a parameter that a given Revit version does not expose is skipped rather than aborting the export. When variable paper sizes are in use the per-sheet print settings apply, otherwise the single selected setting does. Refs pyrevitlabs#2970 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Selecting "Revit Internal Printer" together with "Combine into one file" failed with an error that the printer does not support one of the requested options. The combined path drove the Revit print manager and called SelectNewPrintDriver on the "Revit Internal Printer" entry, but that entry is a synthetic list item rather than a real print driver, so it can never be selected. Single-sheet export already avoids the print manager for this case; the combined path did not. Route the combined case through Document.Export with PDFExportOptions.Combine instead, mirroring the single-sheet internal export. The sheets are exported in the order they appear (already adjusted for the reverse-print option), the print setting's parameters are honored through pdf_opts(), and the result is a single "Combined Sheet Set.pdf" in the pyRevit print folder. Refs pyrevitlabs#2970 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The internal PDF export hardcoded PDFExportOptions.PaperFormat to Default, which means "use sheet size", and never set paper orientation, placement, origin offsets or zoom. Every export therefore rendered at the titleblock's own dimensions using default placement, so the print setting's paper size and the white space between the sheet border and the paper edge were both lost. Revit's own print output preserves them. Map the remaining PrintParameters onto their PDFExportOptions counterparts: PageOrientation, PaperPlacement, OriginOffsetX/Y, ZoomType and Zoom transfer directly, while PaperSize is resolved to the matching ExportPaperFormat member. Resolving the paper size needs care, because PaperSize reports only a name and that name comes from whichever print driver was active when the page setup was created. A standard name is matched first, allowing for the shorthands and spellings drivers use, from "ANSI D" and "A1" to Microsoft's "Architecture DSheet". Failing that, a measurement pair in the name is compared against the nominal size of each standard format, in either unit and either orientation, within a tolerance that absorbs the rounding drivers apply. The pair is read tolerantly of separators and decimals, so both Bluebeam's "ARCH_D_(24.00_x_36.00_Inches)" and a plotter driver's "D+ (24x36 in)" resolve. Deliberately, a name is never matched on a leading standard alone. Bluebeam offers both "ARCH_E1_(30.00_x_42.00_Inches)" and "ARCH_E1_H(15.00_x_21.00_Inches)", and the half-size sheet must not be exported as a full one. A size with no standard equivalent keeps the previous sheet-size behavior and is reported once per size, so the user knows the size was not applied. Sheet size also remains the default when no print setting is supplied, so a variable paper setting still resolves per sheet. Refs pyrevitlabs#2970 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
PR Summary:
- Maps print setting parameters (
PaperSize,PageOrientation,PaperPlacement,OriginOffsetX/Y,ZoomType,Zoom,ColorDepth,RasterQuality) ontoPDFExportOptionsfor the Revit Internal Printer path (2022+). - Adds paper size resolution via name matching and dimension-based fallback to standard
ExportPaperFormatvalues. - Adds combined PDF export support for the internal exporter using the user's selected sheet order, bypassing the print manager which cannot address the synthetic printer.
Review Summary:
The implementation is well-structured with thorough paper size resolution logic (name-based and dimension-based matching with aliases and tolerance). Each export option assignment is defensively wrapped in try/except for cross-version compatibility. The review identified six issues: empty except Exception: pass blocks that swallow errors without logging (violating the repo's exception handling guidelines), a module-level mutable WARNED_PAPER_SIZES set that persists across Revit sessions, a potential int/float type mismatch on ZoomPercentage, an undocumented coupling between the combined export path and the combine-checkbox UI guard, missing progress feedback for combined exports, and an inconsistent output filename between the two combined-print paths. Knowledge applied: repo coding guidelines on exception handling, module-level state, and IronPython 2.7 compatibility patterns.
Suggestions
- Run Black on the Print Sheets script.py in a separate follow-up PR to address the ~935 pre-existing formatting deviations. Apply
- Add unit tests for the paper format resolution helpers (paper_format_name, paper_format_size, paper_dimensions_mm) covering alias, measurement, and non-standard fallback cases. Apply
| if print_params: | ||
| try: | ||
| opts.ColorDepth = print_params.ColorDepth | ||
| except Exception: | ||
| pass | ||
| try: | ||
| opts.RasterQuality = print_params.RasterQuality | ||
| except Exception: | ||
| pass | ||
| # PDF export has no hidden line views option of its own; asking | ||
| # for raster processing rasterizes the whole view instead. | ||
| try: | ||
| opts.AlwaysUseRaster = ( | ||
| print_params.HiddenLineViews | ||
| == DB.HiddenLineViewsType.RasterProcessing | ||
| ) | ||
| except Exception: | ||
| pass | ||
| # Paper size, placement and zoom decide whether the page follows | ||
| # the print setting or collapses onto the sheet's own extents. | ||
| try: | ||
| opts.PaperFormat = \ | ||
| PrintUtils.paper_format(print_params.PaperSize) | ||
| except Exception: | ||
| pass | ||
| try: | ||
| opts.PaperOrientation = print_params.PageOrientation | ||
| except Exception: | ||
| pass | ||
| try: | ||
| opts.PaperPlacement = print_params.PaperPlacement | ||
| except Exception: | ||
| pass | ||
| try: | ||
| opts.OriginOffsetX = print_params.OriginOffsetX | ||
| opts.OriginOffsetY = print_params.OriginOffsetY | ||
| except Exception: | ||
| pass | ||
| try: | ||
| opts.ZoomType = print_params.ZoomType | ||
| opts.ZoomPercentage = print_params.Zoom | ||
| except Exception: | ||
| pass |
There was a problem hiding this comment.
The six try: ... except Exception: pass blocks added to pdf_opts silently swallow all exceptions with no logging. While the intent is to skip parameters not exposed by a given Revit version (e.g. AttributeError), catching all Exception means a genuine bug — an invalid value, a type mismatch, or a Revit API error — is also silently ignored. This is especially problematic here because the entire purpose of this PR is to make these parameter mappings work; if one silently fails, there is no diagnostic trail to explain why the output doesn't match the page setup.
Per the repo's exception handling guidelines: "No empty catch/except blocks — at minimum log the exception or use a specific exception type."
Consider logging at debug level so troubleshooting is possible without changing behavior:
try:
opts.ColorDepth = print_params.ColorDepth
except Exception as ex:
logger.debug('Could not map ColorDepth: %s', ex)Or use AttributeError for the "not exposed in this Revit version" case and log other exceptions:
try:
opts.ColorDepth = print_params.ColorDepth
except AttributeError:
pass
except Exception as ex:
logger.debug('Could not map ColorDepth: %s', ex)actions
Feedback: Rate this comment to help me improve future code reviews:
- 👍 Good - Helpful and accurate
- 👎 Poor - Wrong, unclear, or unhelpful
- Skip if you don't have any strong opinions either way.
|
|
||
| # Sizes already reported as unsupported, so that the warning is issued once | ||
| # per size rather than once per sheet. | ||
| WARNED_PAPER_SIZES = set() |
There was a problem hiding this comment.
WARNED_PAPER_SIZES is a module-level mutable set() used as global state to deduplicate warnings. Per the repo's coding guidelines, module-level globals for state should be avoided in pyRevit scripts/modules because they persist across executions in the same Revit session.
For this specific case the practical concern is minor: the set only ever grows and is only used to suppress duplicate warnings, so a stale entry wouldn't cause a functional bug — just a missed warning on a subsequent run. However, if a user changes their paper size between runs, the old size remains in the set forever, so the warning is permanently suppressed for it even if the paper size later becomes relevant again (e.g. after changing print drivers).
Consider clearing this set at the start of each print operation (e.g. WARNED_PAPER_SIZES.clear() in _export_combined_pdf_internal / _print_sheets_in_order entry points), or moving it to instance state on the print window class so it resets naturally when the window is re-opened.
actions
Feedback: Rate this comment to help me improve future code reviews:
- 👍 Good - Helpful and accurate
- 👎 Poor - Wrong, unclear, or unhelpful
- Skip if you don't have any strong opinions either way.
| with revit.Transaction('Reload Keynote File', doc=doc): | ||
| DB.KeynoteTable.GetKeynoteTable(doc).Reload(None) | ||
|
|
||
| optspdf = PrintUtils.pdf_opts( |
There was a problem hiding this comment.
The _export_combined_pdf_internal method creates a new PDFExportOptions with print_params from self.selected_print_setting.print_params, but unlike the single-sheet path (_print_sheets_in_order), it does not handle per_sheet_psettings (variable paper size mode).
When allows_variable_paper is true, each sheet may have its own print_settings with different paper sizes. The single-sheet path (lines 1370-1377 and 1422-1429) correctly branches on per_sheet_psettings to pick per-sheet PrintParameters. But the combined path always uses self.selected_print_setting.print_params for all sheets.
Looking at the call chain: print_sheets → _print_combined_sheets_in_order → _export_combined_pdf_internal. The combine checkbox is disabled when allows_variable_paper is true (see _update_combine_option at line 1124), so in practice the combined path should only be reached with a single shared print setting.
However, _export_combined_pdf_internal is called directly from _print_combined_sheets_in_order without re-checking allows_variable_paper. If the guard in _update_combine_option is ever bypassed (e.g. the user changes print settings after the combine checkbox state was set, or programmatic use), the combined path would silently apply the wrong paper size to all sheets. Consider adding a defensive comment documenting this dependency:
# Combined export is only reached when allows_variable_paper is False
# (combine_cb is disabled otherwise), so a single print setting covers all sheets.actions
Feedback: Rate this comment to help me improve future code reviews:
- 👍 Good - Helpful and accurate
- 👎 Poor - Wrong, unclear, or unhelpful
- Skip if you don't have any strong opinions either way.
|
|
||
| optspdf = PrintUtils.pdf_opts( | ||
| print_params=self.selected_print_setting.print_params) | ||
| try: |
There was a problem hiding this comment.
The _export_combined_pdf_internal method does not show a progress bar, unlike the single-sheet _print_sheets_in_order path which uses forms.ProgressBar. For a large sheet set being combined into one PDF, the UI will appear frozen during the doc.Export call, which could take a significant amount of time.
The single-sheet paths show progress per sheet (e.g. 'Exporting PDFs... {value} of {max_value}'), but the combined export is a single API call that processes all sheets at once, so per-sheet progress isn't possible. However, consider at least showing a status message or non-cancellable progress indicator before the export begins so the user knows work is in progress. The print-manager combined path at least goes through print_mgr.Apply() / SubmitPrint() which shows Revit's built-in progress dialog.
actions
Feedback: Rate this comment to help me improve future code reviews:
- 👍 Good - Helpful and accurate
- 👎 Poor - Wrong, unclear, or unhelpful
- Skip if you don't have any strong opinions either way.
| print_params=self.selected_print_setting.print_params) | ||
| try: | ||
| PrintUtils.export_combined_pdf( | ||
| dirPath, sheet_ids, optspdf, doc, "Combined Sheet Set") |
There was a problem hiding this comment.
The export_combined_pdf method and export_sheet_pdf both use op.splitext(filename)[0] to derive the output name. For the combined path, the hardcoded "Combined Sheet Set" is passed as the filename, producing a PDF named Combined Sheet Set.pdf.
However, the non-internal combined path (via print manager) uses op.join('C:', 'Ordered Sheet Set.pdf') as the print file name (line 1271). The two paths produce differently-named output files: Ordered Sheet Set.pdf vs Combined Sheet Set.pdf. This inconsistency means users switching between the internal printer and a real printer for combined output will get differently-named files.
Consider using a consistent name across both paths.
actions
Feedback: Rate this comment to help me improve future code reviews:
- 👍 Good - Helpful and accurate
- 👎 Poor - Wrong, unclear, or unhelpful
- Skip if you don't have any strong opinions either way.
| pass | ||
| try: | ||
| opts.ZoomType = print_params.ZoomType | ||
| opts.ZoomPercentage = print_params.Zoom |
There was a problem hiding this comment.
The ZoomPercentage is mapped from print_params.Zoom:
opts.ZoomType = print_params.ZoomType
opts.ZoomPercentage = print_params.ZoomPer the Revit API, PDFExportOptions.ZoomPercentage expects an int. If print_params.Zoom (from PrintParameters) returns a float (e.g. 50.0), assigning it to ZoomPercentage may raise an exception in some Revit versions — which would be silently swallowed by the surrounding except Exception: pass, causing the zoom to be silently dropped without any diagnostic.
Consider explicitly casting to int:
opts.ZoomPercentage = int(print_params.Zoom)actions
Feedback: Rate this comment to help me improve future code reviews:
- 👍 Good - Helpful and accurate
- 👎 Poor - Wrong, unclear, or unhelpful
- Skip if you don't have any strong opinions either way.
There was a problem hiding this comment.
🔵 Needs a closer look
The behavioral correctness depends on Revit PDFExportOptions/enum members and per-version print-parameter mapping that cannot be verified in this environment and warrants human confirmation against a live Revit host.
Pull request overview
This PR fixes pyRevit's Print Sheets tool so that, when exporting through the built-in "Revit Internal Printer" (Revit 2022+), the selected page setup's print options are actually converted into PDFExportOptions. Previously a fixed options object was used, so the page rendered at the titleblock's own size (issue #2970) and ignored margins, placement, and color settings. It also enables the previously-failing "Combine into one file" case for the internal exporter by bypassing the print manager (which cannot select the synthetic internal-printer entry).
Changes:
- Added paper-size resolution helpers plus a
print_paramsmapping inpdf_optsto translatePaperSize, orientation, placement, offsets, zoom, color depth, raster quality, and hidden-line raster processing intoPDFExportOptions. - Added
export_combined_pdfand_export_combined_pdf_internalto support combined PDF output for the internal exporter using the user's chosen sheet order. - Routed the single-sheet and linked-sheet internal-printer paths through the new
print_params-awarepdf_opts.
File summaries
| File | Description |
|---|---|
| extensions/pyRevitTools.extension/pyRevit.tab/Drawing Set.panel/Print Sheets.pushbutton/script.py | Adds paper-format lookup tables/helpers, maps print setup parameters onto PDFExportOptions, adds combined internal PDF export, and updates internal-printer export call sites to pass the print parameters. |
Review details
- Files reviewed: 1/1 changed files
- Comments generated: 0
- Review effort level: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
|
Most of the automated comments look pretty straight forward, I should be able to push updates this weekend. The only exception there is the Sheet Name comment, which may need some coordination. My first instinct is to turn that into something the user can configure instead of a hard coded string. I've never worked with devloai before, But I'll either address it's comments in code or comment on those specifically if needed. |
Description
When Print Sheets exports through the built-in "Revit Internal Printer" (Revit 2022+), it builds a fixed
PDFExportOptionsand never converts the selected page setup's print options into export options. Nothing from the print setup reaches Revit's PDF exporter, so the page size does not shift to the setup's paper size (output renders at the titleblock's own dimensions, losing the white space between the sheet border and the paper edge), margins and placement are not applied, and colors appear even when the setup asks for black lines.Selecting "Revit Internal Printer" together with Combine into one file also fails outright, with an error reporting that the print setting is incompatible with the printer. The error is a little misleading: the entry is a synthetic item this tool injects into the printer list rather than a real print driver, so the print manager cannot select it at all. The single sheet path already bypasses the print manager for this case, but the combined path did not.
This pull request converts the page setup's parameters into export options, and adds combined export support for the internal exporter using the selected sheet order.
PaperSize,PageOrientation,PaperPlacement,OriginOffsetX/OriginOffsetY,ZoomType,Zoom,ColorDepthandRasterQualityare mapped onto theirPDFExportOptionscounterparts.PDFExportOptionsexposes no hidden line views option of its own, so a page setup asking for raster processing is mapped ontoAlwaysUseRaster. Each assignment is applied defensively, so a parameter that a given Revit version does not expose is skipped rather than aborting the export.Resolving the paper size
This is the only part of the mapping that is not mechanical.
PaperSizeexposes only aNameand carries no dimensions, and that name comes from whichever print driver was active when the page setup was created, so it varies considerably between drivers. Please see the code comments for more info, or I'm happy to share additional information if needed.Checklist
Before submitting your pull request, ensure the following requirements are met:
pipenv run black {source_file_or_directory}Related Issues
If applicable, link the issues resolved by this pull request:
Additional Notes
Tested in Revit 2024 and 2026 with the Revit Internal Printer selected, covering defined page setups, variable size, paper smaller than the sheet, a 50% reduction onto a half size sheet, combined output, and a non-standard paper size.
Paper size matching was also checked against the full paper lists reported by three drivers: Bluebeam PS, an Oce PlotWave 340, and Microsoft Print to PDF. All standard sizes resolve, and non-standard entries such as
ANSI_J4,ISO_A5, theBIND_*bind margin variants,Legaland the envelope sizes fall through to the warning rather than being mapped to a near neighbor.Black was not run on this file. The file is not currently Black formatted: running Black on the unmodified upstream copy rewrites about 935 lines, which would grow this pull request from roughly 250 changed lines to over 1200, with most of that unrelated to the fix. New statements that ran long were wrapped in Black's style by hand, and the remainder of the new code follows the file's existing conventions for quoting and line continuations. I am happy to reformat the file, either here or separately, if maintainers prefer. In that case, I would request that you perform a review of the code as submitted, and after those issues are addressed I can run a pass through Black for continued review.
Five new lines still exceed 79 columns. They sit inside an existing block indented to 52 columns and consist of attribute chains that cannot be split any further, so Black's own output leaves them at 92 to 101 columns as well. Bringing them within 79 would mean extracting the body of that block into a helper method. I'm happy to do that, but since it would lengthen and complicate the PR, I elected not to do so immediately.
While working on this issue, I noticed that the currently selected printer in Revit's print dialog affects the list of valid page sizes presented. That's beyond the scope of this update, and I don't even know that it's actually an issue. I just wanted to include the comment for context.