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, you may find one here:
18 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
19 * or you may search the http://www.gnu.org website for the version 2 license,
20 * or you may write to the Free Software Foundation, Inc.,
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
22 */
23
24#include <boost/test/unit_test.hpp>
25
26#include <string>
27
28#include <wx/filename.h>
29#include <wx/ffile.h>
30
33
34
35BOOST_AUTO_TEST_SUITE( DxfPlotter )
36
37
38// Regression test for https://gitlab.com/kicad/code/kicad/-/issues/24405
39// Locks in the R2000-mandated structure AutoCAD requires when we use the 420
40// true-color group on LAYER records.
41BOOST_AUTO_TEST_CASE( R2000HandlesAndTableSkeleton )
42{
43 DXF_PLOTTER plotter;
44 SIMPLE_RENDER_SETTINGS renderSettings;
45
46 plotter.SetRenderSettings( &renderSettings );
47
48 wxString dxfPath = wxFileName::CreateTempFileName( wxT( "kicad_dxf_r2000" ) );
49 BOOST_REQUIRE( !dxfPath.IsEmpty() );
50 BOOST_TEST_MESSAGE( "DXF output: " << dxfPath.ToStdString() );
51 BOOST_REQUIRE( plotter.OpenFile( dxfPath ) );
52
53 plotter.SetViewport( VECTOR2I( 0, 0 ), 2540.0, 1.0, false );
54 BOOST_REQUIRE( plotter.StartPlot( wxT( "1" ) ) );
55
56 // LINE exercises the inline emit path; CIRCLE exercises emitEntityHandle.
57 plotter.MoveTo( VECTOR2I( 0, 0 ) );
58 plotter.FinishTo( VECTOR2I( 1000000, 1000000 ) );
59 plotter.Circle( VECTOR2I( 2000000, 0 ), 500000, FILL_T::NO_FILL, 0 );
60
61 BOOST_REQUIRE( plotter.EndPlot() );
62
63 wxFFile file( dxfPath, wxT( "rb" ) );
64 BOOST_REQUIRE( file.IsOpened() );
65
66 wxFileOffset len = file.Length();
67 BOOST_REQUIRE_GT( len, 0 );
68
69 std::string buffer;
70 buffer.resize( static_cast<size_t>( len ) );
71 BOOST_REQUIRE_EQUAL( file.Read( buffer.data(), len ), static_cast<size_t>( len ) );
72 file.Close();
73
74 // AC1018 makes the 420 true-color tag legal; $HANDSEED must exist for strict
75 // R2000+ readers.
76 BOOST_CHECK_MESSAGE( buffer.find( "$ACADVER\n 1\nAC1018\n" ) != std::string::npos,
77 "Expected $ACADVER = AC1018 in DXF header" );
78 BOOST_CHECK_MESSAGE( buffer.find( "$HANDSEED\n" ) != std::string::npos,
79 "Expected $HANDSEED in DXF header" );
80
81 // APPID/ACAD is required by R2000+.
82 BOOST_CHECK_MESSAGE( buffer.find( "TABLE\n 2\nAPPID\n" ) != std::string::npos,
83 "Expected APPID table in TABLES section" );
84 BOOST_CHECK_MESSAGE( buffer.find( "APPID\n" ) != std::string::npos
85 && buffer.find( "\n 2\nACAD\n" ) != std::string::npos,
86 "Expected ACAD entry in APPID table" );
87
88 // Canonical *Model_Space BLOCK_RECORD plus the BLOCKS section that backs it.
89 BOOST_CHECK_MESSAGE( buffer.find( "TABLE\n 2\nBLOCK_RECORD\n" ) != std::string::npos,
90 "Expected BLOCK_RECORD table" );
91 BOOST_CHECK_MESSAGE( buffer.find( "*Model_Space" ) != std::string::npos,
92 "Expected *Model_Space layout block" );
93 BOOST_CHECK_MESSAGE( buffer.find( "SECTION\n 2\nBLOCKS\n" ) != std::string::npos,
94 "Expected BLOCKS section" );
95
96 // Every entity carries handle (5), owner (330) and AcDbEntity. LINE additionally
97 // has the AcDbLine subclass and a 6/linetype inside the AcDbEntity scope.
98 BOOST_CHECK_MESSAGE( buffer.find( "0\nLINE\n" ) != std::string::npos,
99 "Expected LINE entity in output" );
100 BOOST_CHECK_MESSAGE( buffer.find( "100\nAcDbEntity\n" ) != std::string::npos,
101 "Expected AcDbEntity subclass marker on entities" );
102 BOOST_CHECK_MESSAGE( buffer.find( "100\nAcDbLine\n" ) != std::string::npos,
103 "Expected AcDbLine subclass marker on LINE entity" );
104 BOOST_CHECK_MESSAGE( buffer.find( "100\nAcDbCircle\n" ) != std::string::npos,
105 "Expected AcDbCircle subclass marker on CIRCLE entity" );
106
107 // Strict R2000+ readers require a 74 group after each 49 in complex linetypes.
108 BOOST_CHECK_MESSAGE( buffer.find( " 49\n1.25\n 74\n0\n" ) != std::string::npos,
109 "Expected 74 group code following 49 in DASHDOT pattern" );
110
111 // Spec page 59 mandates three empty layout blocks (*Model_Space, *Paper_Space,
112 // *Paper_Space0); each BLOCK_RECORD entry has a 340 pointer to its LAYOUT.
113 BOOST_CHECK_MESSAGE( buffer.find( "\n*Paper_Space0\n" ) != std::string::npos,
114 "Expected *Paper_Space0 layout (spec mandates three empty blocks)" );
115 BOOST_CHECK_MESSAGE( buffer.find( "100\nAcDbBlockTableRecord\n 2\n*Paper_Space0\n340\n" )
116 != std::string::npos,
117 "Expected 340 LAYOUT pointer in *Paper_Space0 BLOCK_RECORD" );
118
119 // OBJECTS section with the root NOD, ACAD_LAYOUT/ACAD_GROUP and three LAYOUT
120 // objects that close the 330 back-loop to each BLOCK_RECORD.
121 BOOST_CHECK_MESSAGE( buffer.find( "SECTION\n 2\nOBJECTS\n" ) != std::string::npos,
122 "Expected OBJECTS section" );
123 BOOST_CHECK_MESSAGE( buffer.find( "ACAD_LAYOUT\n350\n" ) != std::string::npos,
124 "Expected ACAD_LAYOUT entry in root dictionary" );
125 BOOST_CHECK_MESSAGE( buffer.find( "ACAD_GROUP\n350\n" ) != std::string::npos,
126 "Expected ACAD_GROUP entry in root dictionary" );
127 BOOST_CHECK_MESSAGE( buffer.find( "0\nLAYOUT\n" ) != std::string::npos,
128 "Expected LAYOUT objects in OBJECTS section" );
129 BOOST_CHECK_MESSAGE( buffer.find( "100\nAcDbLayout\n 1\nModel\n" ) != std::string::npos,
130 "Expected Model layout object" );
131 BOOST_CHECK_MESSAGE( buffer.find( "100\nAcDbLayout\n 1\nLayout1\n" ) != std::string::npos,
132 "Expected Layout1 (Paper_Space) layout object" );
133 BOOST_CHECK_MESSAGE( buffer.find( "100\nAcDbLayout\n 1\nLayout2\n" ) != std::string::npos,
134 "Expected Layout2 (Paper_Space0) layout object" );
135
136 // Every LAYER's 390 must resolve to a real plot-style object; without the
137 // ACDBPLACEHOLDER "Normal" the LAYER table fails to load.
138 BOOST_CHECK_MESSAGE( buffer.find( "ACAD_PLOTSTYLENAME\n350\n" ) != std::string::npos,
139 "Expected ACAD_PLOTSTYLENAME entry in root dictionary" );
140 BOOST_CHECK_MESSAGE( buffer.find( "ACDBDICTIONARYWDFLT\n" ) != std::string::npos,
141 "Expected ACDBDICTIONARYWDFLT for ACAD_PLOTSTYLENAME" );
142 BOOST_CHECK_MESSAGE( buffer.find( "ACDBPLACEHOLDER\n" ) != std::string::npos,
143 "Expected ACDBPLACEHOLDER (the 'Normal' plot style)" );
144 BOOST_CHECK_MESSAGE( buffer.find( " 6\nCONTINUOUS\n390\n" ) != std::string::npos,
145 "Expected 390 plot-style handle on every LAYER record" );
146
147 // VPORT/VIEW/UCS must exist (empty is OK). DIMSTYLE needs a Standard entry
148 // whose handle uses group code 105 instead of 5 (spec page 35).
149 BOOST_CHECK_MESSAGE( buffer.find( "TABLE\n 2\nVPORT\n" ) != std::string::npos,
150 "Expected VPORT table" );
151 BOOST_CHECK_MESSAGE( buffer.find( "TABLE\n 2\nVIEW\n" ) != std::string::npos,
152 "Expected VIEW table" );
153 BOOST_CHECK_MESSAGE( buffer.find( "TABLE\n 2\nUCS\n" ) != std::string::npos,
154 "Expected UCS table" );
155 BOOST_CHECK_MESSAGE( buffer.find( "TABLE\n 2\nDIMSTYLE\n" ) != std::string::npos,
156 "Expected DIMSTYLE table" );
157 BOOST_CHECK_MESSAGE( buffer.find( " 0\nDIMSTYLE\n105\n" ) != std::string::npos,
158 "Expected DIMSTYLE record handle on group code 105 (not 5)" );
159 BOOST_CHECK_MESSAGE( buffer.find( "AcDbDimStyleTableRecord\n 2\nStandard\n" )
160 != std::string::npos,
161 "Expected Standard DIMSTYLE entry" );
162
163 // Default layer "0" must exist in the LAYER table even if no entity uses it.
164 BOOST_CHECK_MESSAGE( buffer.find( "AcDbLayerTableRecord\n 2\n0\n" ) != std::string::npos,
165 "Expected default layer \"0\" entry in LAYER table" );
166
167 // ByBlock and ByLayer are required LTYPE entries.
168 BOOST_CHECK_MESSAGE( buffer.find( "AcDbLinetypeTableRecord\n 2\nByBlock\n" )
169 != std::string::npos,
170 "Expected ByBlock linetype entry" );
171 BOOST_CHECK_MESSAGE( buffer.find( "AcDbLinetypeTableRecord\n 2\nByLayer\n" )
172 != std::string::npos,
173 "Expected ByLayer linetype entry" );
174
175 // AutoCAD requires group 2 (printer name) in AcDbPlotSettings. ODA File
176 // Converter inserts "none_device" on load when it's missing.
177 BOOST_CHECK_MESSAGE( buffer.find( "AcDbPlotSettings\n 1\n\n 2\nnone_device\n" )
178 != std::string::npos,
179 "Expected group 2 (none_device) in AcDbPlotSettings" );
180
181 // ModelType (1024) in AcDbPlotSettings 70 is only valid on the Model layout.
182 BOOST_CHECK_MESSAGE( buffer.find( "AcDbLayout\n 1\nModel\n" ) != std::string::npos,
183 "Expected Model layout (precondition for the next check)" );
184 BOOST_CHECK_MESSAGE( buffer.find( " 70\n1024\n" ) != std::string::npos,
185 "Expected ModelType (1024) flag on the Model layout" );
186
187 MaybeRemoveFile( dxfPath );
188}
189
190
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:77
void SetRenderSettings(RENDER_SETTINGS *aSettings)
Definition plotter.h:167
void MoveTo(const VECTOR2I &pos)
Definition plotter.h:309
void FinishTo(const VECTOR2I &pos)
Definition plotter.h:319
Minimal concrete render settings suitable for plotters in tests.
@ NO_FILL
Definition eda_shape.h:64
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:687