KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_dxf_plotter.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright The KiCad Developers, see AUTHORS.TXT for contributors.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
20#include <boost/test/unit_test.hpp>
21
22#include <string>
23
24#include <wx/filename.h>
25#include <wx/ffile.h>
26
29
30
31BOOST_AUTO_TEST_SUITE( DxfPlotter )
32
33
34// Regression test for https://gitlab.com/kicad/code/kicad/-/issues/24405
35// Locks in the R2000-mandated structure AutoCAD requires when we use the 420
36// true-color group on LAYER records.
37BOOST_AUTO_TEST_CASE( R2000HandlesAndTableSkeleton )
38{
39 DXF_PLOTTER plotter;
40 SIMPLE_RENDER_SETTINGS renderSettings;
41
42 plotter.SetRenderSettings( &renderSettings );
43
44 wxString dxfPath = wxFileName::CreateTempFileName( wxT( "kicad_dxf_r2000" ) );
45 BOOST_REQUIRE( !dxfPath.IsEmpty() );
46 BOOST_TEST_MESSAGE( "DXF output: " << dxfPath.ToStdString() );
47 BOOST_REQUIRE( plotter.OpenFile( dxfPath ) );
48
49 plotter.SetViewport( VECTOR2I( 0, 0 ), 2540.0, 1.0, false );
50 BOOST_REQUIRE( plotter.StartPlot( wxT( "1" ) ) );
51
52 // LINE exercises the inline emit path; CIRCLE exercises emitEntityHandle.
53 plotter.MoveTo( VECTOR2I( 0, 0 ) );
54 plotter.FinishTo( VECTOR2I( 1000000, 1000000 ) );
55 plotter.Circle( VECTOR2I( 2000000, 0 ), 500000, FILL_T::NO_FILL, 0 );
56
57 BOOST_REQUIRE( plotter.EndPlot() );
58
59 wxFFile file( dxfPath, wxT( "rb" ) );
60 BOOST_REQUIRE( file.IsOpened() );
61
62 wxFileOffset len = file.Length();
63 BOOST_REQUIRE_GT( len, 0 );
64
65 std::string buffer;
66 buffer.resize( static_cast<size_t>( len ) );
67 BOOST_REQUIRE_EQUAL( file.Read( buffer.data(), len ), static_cast<size_t>( len ) );
68 file.Close();
69
70 // AC1018 makes the 420 true-color tag legal; $HANDSEED must exist for strict
71 // R2000+ readers.
72 BOOST_CHECK_MESSAGE( buffer.find( "$ACADVER\n 1\nAC1018\n" ) != std::string::npos,
73 "Expected $ACADVER = AC1018 in DXF header" );
74 BOOST_CHECK_MESSAGE( buffer.find( "$HANDSEED\n" ) != std::string::npos,
75 "Expected $HANDSEED in DXF header" );
76
77 // APPID/ACAD is required by R2000+.
78 BOOST_CHECK_MESSAGE( buffer.find( "TABLE\n 2\nAPPID\n" ) != std::string::npos,
79 "Expected APPID table in TABLES section" );
80 BOOST_CHECK_MESSAGE( buffer.find( "APPID\n" ) != std::string::npos
81 && buffer.find( "\n 2\nACAD\n" ) != std::string::npos,
82 "Expected ACAD entry in APPID table" );
83
84 // Canonical *Model_Space BLOCK_RECORD plus the BLOCKS section that backs it.
85 BOOST_CHECK_MESSAGE( buffer.find( "TABLE\n 2\nBLOCK_RECORD\n" ) != std::string::npos,
86 "Expected BLOCK_RECORD table" );
87 BOOST_CHECK_MESSAGE( buffer.find( "*Model_Space" ) != std::string::npos,
88 "Expected *Model_Space layout block" );
89 BOOST_CHECK_MESSAGE( buffer.find( "SECTION\n 2\nBLOCKS\n" ) != std::string::npos,
90 "Expected BLOCKS section" );
91
92 // Every entity carries handle (5), owner (330) and AcDbEntity. LINE additionally
93 // has the AcDbLine subclass and a 6/linetype inside the AcDbEntity scope.
94 BOOST_CHECK_MESSAGE( buffer.find( "0\nLINE\n" ) != std::string::npos,
95 "Expected LINE entity in output" );
96 BOOST_CHECK_MESSAGE( buffer.find( "100\nAcDbEntity\n" ) != std::string::npos,
97 "Expected AcDbEntity subclass marker on entities" );
98 BOOST_CHECK_MESSAGE( buffer.find( "100\nAcDbLine\n" ) != std::string::npos,
99 "Expected AcDbLine subclass marker on LINE entity" );
100 BOOST_CHECK_MESSAGE( buffer.find( "100\nAcDbCircle\n" ) != std::string::npos,
101 "Expected AcDbCircle subclass marker on CIRCLE entity" );
102
103 // Strict R2000+ readers require a 74 group after each 49 in complex linetypes.
104 BOOST_CHECK_MESSAGE( buffer.find( " 49\n1.25\n 74\n0\n" ) != std::string::npos,
105 "Expected 74 group code following 49 in DASHDOT pattern" );
106
107 // Spec page 59 mandates three empty layout blocks (*Model_Space, *Paper_Space,
108 // *Paper_Space0); each BLOCK_RECORD entry has a 340 pointer to its LAYOUT.
109 BOOST_CHECK_MESSAGE( buffer.find( "\n*Paper_Space0\n" ) != std::string::npos,
110 "Expected *Paper_Space0 layout (spec mandates three empty blocks)" );
111 BOOST_CHECK_MESSAGE( buffer.find( "100\nAcDbBlockTableRecord\n 2\n*Paper_Space0\n340\n" )
112 != std::string::npos,
113 "Expected 340 LAYOUT pointer in *Paper_Space0 BLOCK_RECORD" );
114
115 // OBJECTS section with the root NOD, ACAD_LAYOUT/ACAD_GROUP and three LAYOUT
116 // objects that close the 330 back-loop to each BLOCK_RECORD.
117 BOOST_CHECK_MESSAGE( buffer.find( "SECTION\n 2\nOBJECTS\n" ) != std::string::npos,
118 "Expected OBJECTS section" );
119 BOOST_CHECK_MESSAGE( buffer.find( "ACAD_LAYOUT\n350\n" ) != std::string::npos,
120 "Expected ACAD_LAYOUT entry in root dictionary" );
121 BOOST_CHECK_MESSAGE( buffer.find( "ACAD_GROUP\n350\n" ) != std::string::npos,
122 "Expected ACAD_GROUP entry in root dictionary" );
123 BOOST_CHECK_MESSAGE( buffer.find( "0\nLAYOUT\n" ) != std::string::npos,
124 "Expected LAYOUT objects in OBJECTS section" );
125 BOOST_CHECK_MESSAGE( buffer.find( "100\nAcDbLayout\n 1\nModel\n" ) != std::string::npos,
126 "Expected Model layout object" );
127 BOOST_CHECK_MESSAGE( buffer.find( "100\nAcDbLayout\n 1\nLayout1\n" ) != std::string::npos,
128 "Expected Layout1 (Paper_Space) layout object" );
129 BOOST_CHECK_MESSAGE( buffer.find( "100\nAcDbLayout\n 1\nLayout2\n" ) != std::string::npos,
130 "Expected Layout2 (Paper_Space0) layout object" );
131
132 // Every LAYER's 390 must resolve to a real plot-style object; without the
133 // ACDBPLACEHOLDER "Normal" the LAYER table fails to load.
134 BOOST_CHECK_MESSAGE( buffer.find( "ACAD_PLOTSTYLENAME\n350\n" ) != std::string::npos,
135 "Expected ACAD_PLOTSTYLENAME entry in root dictionary" );
136 BOOST_CHECK_MESSAGE( buffer.find( "ACDBDICTIONARYWDFLT\n" ) != std::string::npos,
137 "Expected ACDBDICTIONARYWDFLT for ACAD_PLOTSTYLENAME" );
138 BOOST_CHECK_MESSAGE( buffer.find( "ACDBPLACEHOLDER\n" ) != std::string::npos,
139 "Expected ACDBPLACEHOLDER (the 'Normal' plot style)" );
140 BOOST_CHECK_MESSAGE( buffer.find( " 6\nCONTINUOUS\n390\n" ) != std::string::npos,
141 "Expected 390 plot-style handle on every LAYER record" );
142
143 // VPORT/VIEW/UCS must exist (empty is OK). DIMSTYLE needs a Standard entry
144 // whose handle uses group code 105 instead of 5 (spec page 35).
145 BOOST_CHECK_MESSAGE( buffer.find( "TABLE\n 2\nVPORT\n" ) != std::string::npos,
146 "Expected VPORT table" );
147 BOOST_CHECK_MESSAGE( buffer.find( "TABLE\n 2\nVIEW\n" ) != std::string::npos,
148 "Expected VIEW table" );
149 BOOST_CHECK_MESSAGE( buffer.find( "TABLE\n 2\nUCS\n" ) != std::string::npos,
150 "Expected UCS table" );
151 BOOST_CHECK_MESSAGE( buffer.find( "TABLE\n 2\nDIMSTYLE\n" ) != std::string::npos,
152 "Expected DIMSTYLE table" );
153 BOOST_CHECK_MESSAGE( buffer.find( " 0\nDIMSTYLE\n105\n" ) != std::string::npos,
154 "Expected DIMSTYLE record handle on group code 105 (not 5)" );
155 BOOST_CHECK_MESSAGE( buffer.find( "AcDbDimStyleTableRecord\n 2\nStandard\n" )
156 != std::string::npos,
157 "Expected Standard DIMSTYLE entry" );
158
159 // Default layer "0" must exist in the LAYER table even if no entity uses it.
160 BOOST_CHECK_MESSAGE( buffer.find( "AcDbLayerTableRecord\n 2\n0\n" ) != std::string::npos,
161 "Expected default layer \"0\" entry in LAYER table" );
162
163 // ByBlock and ByLayer are required LTYPE entries.
164 BOOST_CHECK_MESSAGE( buffer.find( "AcDbLinetypeTableRecord\n 2\nByBlock\n" )
165 != std::string::npos,
166 "Expected ByBlock linetype entry" );
167 BOOST_CHECK_MESSAGE( buffer.find( "AcDbLinetypeTableRecord\n 2\nByLayer\n" )
168 != std::string::npos,
169 "Expected ByLayer linetype entry" );
170
171 // AutoCAD requires group 2 (printer name) in AcDbPlotSettings. ODA File
172 // Converter inserts "none_device" on load when it's missing.
173 BOOST_CHECK_MESSAGE( buffer.find( "AcDbPlotSettings\n 1\n\n 2\nnone_device\n" )
174 != std::string::npos,
175 "Expected group 2 (none_device) in AcDbPlotSettings" );
176
177 // ModelType (1024) in AcDbPlotSettings 70 is only valid on the Model layout.
178 BOOST_CHECK_MESSAGE( buffer.find( "AcDbLayout\n 1\nModel\n" ) != std::string::npos,
179 "Expected Model layout (precondition for the next check)" );
180 BOOST_CHECK_MESSAGE( buffer.find( " 70\n1024\n" ) != std::string::npos,
181 "Expected ModelType (1024) flag on the Model layout" );
182
183 MaybeRemoveFile( dxfPath );
184}
185
186
virtual void SetViewport(const VECTOR2I &aOffset, double aIusPerDecimil, double aScale, bool aMirror) override
Set the scale/position for the DXF plot.
virtual bool StartPlot(const wxString &aPageNumber) override
Open the DXF plot with a skeleton header.
virtual void Circle(const VECTOR2I &pos, int diametre, FILL_T fill, int width) override
DXF circle: full functionality; it even does 'fills' drawing a circle with a dual-arc polyline wide a...
virtual bool EndPlot() override
virtual bool OpenFile(const wxString &aFullFilename)
Open or create the plot file aFullFilename.
Definition plotter.cpp:73
void SetRenderSettings(RENDER_SETTINGS *aSettings)
Definition plotter.h:163
void MoveTo(const VECTOR2I &pos)
Definition plotter.h:305
void FinishTo(const VECTOR2I &pos)
Definition plotter.h:315
Minimal concrete render settings suitable for plotters in tests.
@ NO_FILL
Definition eda_shape.h:60
void MaybeRemoveFile(const wxString &aPath, const wxString &aEnvVar=wxT("KICAD_KEEP_TEST_PDF"))
Remove a file unless the given environment variable is set (defaults to KICAD_KEEP_TEST_PDF).
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_AUTO_TEST_SUITE(CadstarPartParser)
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
BOOST_CHECK_MESSAGE(totalMismatches==0, std::to_string(totalMismatches)+" board(s) with strategy disagreements")
BOOST_TEST_MESSAGE("\n=== Real-World Polygon PIP Benchmark ===\n"<< formatTable(table))
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683