Map a full hierarchy during PDF plotting

Re-create the schematic hierarchy in PDF plots to ease navigation
relative to the on screen schematic

Fixes https://gitlab.com/kicad/code/kicad/-/issues/12154

(cherry picked from commit 4a3b33df4e8a95ce3cafe8dc810642501f929d72)
This commit is contained in:
Seth Hillbrand 2025-03-05 13:26:41 -08:00
parent 27b55eb32f
commit 49d8fc900f
5 changed files with 99 additions and 15 deletions

View File

@ -29,6 +29,7 @@
#include <algorithm> #include <algorithm>
#include <cstdio> // snprintf #include <cstdio> // snprintf
#include <stack>
#include <wx/filename.h> #include <wx/filename.h>
#include <wx/mstream.h> #include <wx/mstream.h>
@ -658,13 +659,19 @@ void PDF_PLOTTER::closePdfStream()
} }
void PDF_PLOTTER::StartPage( const wxString& aPageNumber, const wxString& aPageName ) void PDF_PLOTTER::StartPage( const wxString& aPageNumber, const wxString& aPageName,
const wxString& aParentPageNumber, const wxString& aParentPageName )
{ {
wxASSERT( m_outputFile ); wxASSERT( m_outputFile );
wxASSERT( !m_workFile ); wxASSERT( !m_workFile );
m_pageNumbers.push_back( aPageNumber ); m_pageNumbers.push_back( aPageNumber );
m_pageName = aPageName; m_pageName = aPageName.IsEmpty()
? wxString::Format( _( "Page %s" ), aPageNumber )
: wxString::Format( _( "%s (Page %s)" ), aPageName, aPageNumber );
m_parentPageName = aParentPageName.IsEmpty()
? wxString::Format( _( "Page %s" ), aParentPageNumber )
: wxString::Format( _( "%s (Page %s)" ), aParentPageName, aParentPageNumber );
// Compute the paper size in IUs // Compute the paper size in IUs
m_paperSize = m_pageInfo.GetSizeMils(); m_paperSize = m_pageInfo.GetSizeMils();
@ -889,20 +896,34 @@ void PDF_PLOTTER::ClosePage()
// Mark the page stream as idle // Mark the page stream as idle
m_pageStreamHandle = 0; m_pageStreamHandle = 0;
wxString pageOutlineName = wxEmptyString; int actionHandle = emitGoToAction( pageHandle );
PDF_PLOTTER::OUTLINE_NODE* parent_node = m_outlineRoot.get();
if( m_pageName.IsEmpty() ) if( !m_parentPageName.IsEmpty() )
{ {
pageOutlineName = wxString::Format( _( "Page %s" ), m_pageNumbers.back() ); // Search for the parent node iteratively through the entire tree
} std::stack<OUTLINE_NODE*> nodes;
else nodes.push( m_outlineRoot.get() );
{
pageOutlineName = wxString::Format( _( "%s (Page %s)" ), m_pageName, m_pageNumbers.back() ); while( !nodes.empty() )
{
OUTLINE_NODE* node = nodes.top();
nodes.pop();
// Check if this node matches
if( node->title == m_parentPageName )
{
parent_node = node;
break;
}
// Add all children to the stack
for( OUTLINE_NODE* child : node->children )
nodes.push( child );
}
} }
int actionHandle = emitGoToAction( pageHandle ); OUTLINE_NODE* pageOutlineNode = addOutlineNode( parent_node, actionHandle, m_pageName );
OUTLINE_NODE* pageOutlineNode =
addOutlineNode( m_outlineRoot.get(), actionHandle, pageOutlineName );
// let's reorg the symbol bookmarks under a page handle // let's reorg the symbol bookmarks under a page handle
// let's reorg the symbol bookmarks under a page handle // let's reorg the symbol bookmarks under a page handle

View File

@ -122,7 +122,7 @@ void SCH_PLOTTER::createPDFFile( const SCH_PLOT_OPTS& aPlotOpts,
if( aPlotOpts.m_plotAll || aPlotOpts.m_plotPages.size() > 0 ) if( aPlotOpts.m_plotAll || aPlotOpts.m_plotPages.size() > 0 )
{ {
sheetList.BuildSheetList( &m_schematic->Root(), true ); sheetList.BuildSheetList( &m_schematic->Root(), true );
sheetList.SortByPageNumbers(); sheetList.SortByHierarchicalPageNumbers();
// remove the non-selected pages if we are in plot pages mode // remove the non-selected pages if we are in plot pages mode
if( aPlotOpts.m_plotPages.size() > 0 ) if( aPlotOpts.m_plotPages.size() > 0 )
@ -226,7 +226,20 @@ void SCH_PLOTTER::createPDFFile( const SCH_PLOT_OPTS& aPlotOpts,
* reconfigure, and then start a new one */ * reconfigure, and then start a new one */
plotter->ClosePage(); plotter->ClosePage();
setupPlotPagePDF( plotter, screen, aPlotOpts ); setupPlotPagePDF( plotter, screen, aPlotOpts );
plotter->StartPage( sheetList[i].GetPageNumber(), sheetName ); SCH_SHEET_PATH parentSheet = sheetList[i];
if( parentSheet.size() > 1 )
{
// The sheet path is the full path to the sheet, so we need to remove the last
// sheet name to get the parent sheet path
parentSheet.pop_back();
}
wxString parentSheetName =
parentSheet.Last()->GetName();
plotter->StartPage( sheetList[i].GetPageNumber(), sheetName,
parentSheet.GetPageNumber(), parentSheetName );
} }
plotOneSheetPDF( plotter, screen, aPlotOpts ); plotOneSheetPDF( plotter, screen, aPlotOpts );

View File

@ -816,6 +816,44 @@ void SCH_SHEET_LIST::BuildSheetList( SCH_SHEET* aSheet, bool aCheckIntegrity )
} }
void SCH_SHEET_LIST::SortByHierarchicalPageNumbers( bool aUpdateVirtualPageNums )
{
for( const SCH_SHEET_PATH& path : *this )
path.CachePageNumber();
std::sort( begin(), end(),
[]( const SCH_SHEET_PATH& a, const SCH_SHEET_PATH& b ) -> bool
{
if( a.size() != b.size() )
return a.size() < b.size();
int retval = SCH_SHEET::ComparePageNum( a.GetCachedPageNumber(),
b.GetCachedPageNumber() );
if( retval < 0 )
return true;
else if( retval > 0 )
return false;
if( a.GetVirtualPageNumber() < b.GetVirtualPageNumber() )
return true;
else if( a.GetVirtualPageNumber() > b.GetVirtualPageNumber() )
return false;
// Enforce strict ordering. If the page numbers are the same, use UUIDs
return a.GetCurrentHash() < b.GetCurrentHash();
} );
if( aUpdateVirtualPageNums )
{
int virtualPageNum = 1;
for( SCH_SHEET_PATH& sheet : *this )
sheet.SetVirtualPageNumber( virtualPageNum++ );
}
}
void SCH_SHEET_LIST::SortByPageNumbers( bool aUpdateVirtualPageNums ) void SCH_SHEET_LIST::SortByPageNumbers( bool aUpdateVirtualPageNums )
{ {
for( const SCH_SHEET_PATH& path : *this ) for( const SCH_SHEET_PATH& path : *this )

View File

@ -617,6 +617,13 @@ public:
*/ */
void SortByPageNumbers( bool aUpdateVirtualPageNums = true ); void SortByPageNumbers( bool aUpdateVirtualPageNums = true );
/**
* This works like #SortByPageNumbers, but it sorts the sheets first by their hierarchical
* depth and then by their page numbers. This ensures that printouts follow the
* hierarchical structure of the schematic.
*/
void SortByHierarchicalPageNumbers( bool aUpdateVirtualPageNums = true );
bool NameExists( const wxString& aSheetName ) const; bool NameExists( const wxString& aSheetName ) const;
bool PageNumberExists( const wxString& aPageNumber ) const; bool PageNumberExists( const wxString& aPageNumber ) const;

View File

@ -289,7 +289,9 @@ public:
* Start a new page in the PDF document. * Start a new page in the PDF document.
*/ */
virtual void StartPage( const wxString& aPageNumber, virtual void StartPage( const wxString& aPageNumber,
const wxString& aPageName = wxEmptyString ); const wxString& aPageName = wxEmptyString,
const wxString& aParentPageNumber = wxEmptyString,
const wxString& aParentPageName = wxEmptyString );
/** /**
* Close the current page in the PDF document (and emit its compressed stream). * Close the current page in the PDF document (and emit its compressed stream).
@ -498,8 +500,11 @@ protected:
std::vector<int> m_pageHandles; ///< Handles to the page objects. std::vector<int> m_pageHandles; ///< Handles to the page objects.
int m_pageStreamHandle; ///< Handle of the page content object. int m_pageStreamHandle; ///< Handle of the page content object.
int m_streamLengthHandle; ///< Handle to the deferred stream length. int m_streamLengthHandle; ///< Handle to the deferred stream length.
wxString m_workFilename; wxString m_workFilename;
wxString m_pageName; wxString m_pageName;
wxString m_parentPageName;
FILE* m_workFile; ///< Temporary file to construct the stream before zipping. FILE* m_workFile; ///< Temporary file to construct the stream before zipping.
std::vector<long> m_xrefTable; ///< The PDF xref offset table. std::vector<long> m_xrefTable; ///< The PDF xref offset table.