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#include <qa_utils/file_utils.h>
22
23#include <algorithm>
24#include <string>
25
26#include <wx/filename.h>
27#include <wx/ffile.h>
28
31
32
33BOOST_AUTO_TEST_SUITE( DxfPlotter )
34
35
36// Regression test for https://gitlab.com/kicad/code/kicad/-/issues/24405
37// Locks in the R2000-mandated structure AutoCAD requires when we use the 420
38// true-color group on LAYER records.
39BOOST_AUTO_TEST_CASE( R2000HandlesAndTableSkeleton )
40{
41 DXF_PLOTTER plotter;
42 SIMPLE_RENDER_SETTINGS renderSettings;
43
44 plotter.SetRenderSettings( &renderSettings );
45
46 KI_TEST::SCOPED_TEMP_DIR tempDir( "kicad_dxf_r2000" );
47 const wxString dxfPath = tempDir.CreateChildFileStr( "plot.dxf" );
48
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 buffer.erase( std::remove( buffer.begin(), buffer.end(), '\r' ), buffer.end() );
75
76 // AC1018 makes the 420 true-color tag legal; $HANDSEED must exist for strict
77 // R2000+ readers.
78 BOOST_CHECK_MESSAGE( buffer.find( "$ACADVER\n 1\nAC1018\n" ) != std::string::npos,
79 "Expected $ACADVER = AC1018 in DXF header" );
80 BOOST_CHECK_MESSAGE( buffer.find( "$HANDSEED\n" ) != std::string::npos,
81 "Expected $HANDSEED in DXF header" );
82
83 // APPID/ACAD is required by R2000+.
84 BOOST_CHECK_MESSAGE( buffer.find( "TABLE\n 2\nAPPID\n" ) != std::string::npos,
85 "Expected APPID table in TABLES section" );
86 BOOST_CHECK_MESSAGE( buffer.find( "APPID\n" ) != std::string::npos
87 && buffer.find( "\n 2\nACAD\n" ) != std::string::npos,
88 "Expected ACAD entry in APPID table" );
89
90 // Canonical *Model_Space BLOCK_RECORD plus the BLOCKS section that backs it.
91 BOOST_CHECK_MESSAGE( buffer.find( "TABLE\n 2\nBLOCK_RECORD\n" ) != std::string::npos,
92 "Expected BLOCK_RECORD table" );
93 BOOST_CHECK_MESSAGE( buffer.find( "*Model_Space" ) != std::string::npos,
94 "Expected *Model_Space layout block" );
95 BOOST_CHECK_MESSAGE( buffer.find( "SECTION\n 2\nBLOCKS\n" ) != std::string::npos,
96 "Expected BLOCKS section" );
97
98 // Every entity carries handle (5), owner (330) and AcDbEntity. LINE additionally
99 // has the AcDbLine subclass and a 6/linetype inside the AcDbEntity scope.
100 BOOST_CHECK_MESSAGE( buffer.find( "0\nLINE\n" ) != std::string::npos,
101 "Expected LINE entity in output" );
102 BOOST_CHECK_MESSAGE( buffer.find( "100\nAcDbEntity\n" ) != std::string::npos,
103 "Expected AcDbEntity subclass marker on entities" );
104 BOOST_CHECK_MESSAGE( buffer.find( "100\nAcDbLine\n" ) != std::string::npos,
105 "Expected AcDbLine subclass marker on LINE entity" );
106 BOOST_CHECK_MESSAGE( buffer.find( "100\nAcDbCircle\n" ) != std::string::npos,
107 "Expected AcDbCircle subclass marker on CIRCLE entity" );
108
109 // Strict R2000+ readers require a 74 group after each 49 in complex linetypes.
110 BOOST_CHECK_MESSAGE( buffer.find( " 49\n1.25\n 74\n0\n" ) != std::string::npos,
111 "Expected 74 group code following 49 in DASHDOT pattern" );
112
113 // Spec page 59 mandates three empty layout blocks (*Model_Space, *Paper_Space,
114 // *Paper_Space0); each BLOCK_RECORD entry has a 340 pointer to its LAYOUT.
115 BOOST_CHECK_MESSAGE( buffer.find( "\n*Paper_Space0\n" ) != std::string::npos,
116 "Expected *Paper_Space0 layout (spec mandates three empty blocks)" );
117 BOOST_CHECK_MESSAGE( buffer.find( "100\nAcDbBlockTableRecord\n 2\n*Paper_Space0\n340\n" )
118 != std::string::npos,
119 "Expected 340 LAYOUT pointer in *Paper_Space0 BLOCK_RECORD" );
120
121 // OBJECTS section with the root NOD, ACAD_LAYOUT/ACAD_GROUP and three LAYOUT
122 // objects that close the 330 back-loop to each BLOCK_RECORD.
123 BOOST_CHECK_MESSAGE( buffer.find( "SECTION\n 2\nOBJECTS\n" ) != std::string::npos,
124 "Expected OBJECTS section" );
125 BOOST_CHECK_MESSAGE( buffer.find( "ACAD_LAYOUT\n350\n" ) != std::string::npos,
126 "Expected ACAD_LAYOUT entry in root dictionary" );
127 BOOST_CHECK_MESSAGE( buffer.find( "ACAD_GROUP\n350\n" ) != std::string::npos,
128 "Expected ACAD_GROUP entry in root dictionary" );
129 BOOST_CHECK_MESSAGE( buffer.find( "0\nLAYOUT\n" ) != std::string::npos,
130 "Expected LAYOUT objects in OBJECTS section" );
131 BOOST_CHECK_MESSAGE( buffer.find( "100\nAcDbLayout\n 1\nModel\n" ) != std::string::npos,
132 "Expected Model layout object" );
133 BOOST_CHECK_MESSAGE( buffer.find( "100\nAcDbLayout\n 1\nLayout1\n" ) != std::string::npos,
134 "Expected Layout1 (Paper_Space) layout object" );
135 BOOST_CHECK_MESSAGE( buffer.find( "100\nAcDbLayout\n 1\nLayout2\n" ) != std::string::npos,
136 "Expected Layout2 (Paper_Space0) layout object" );
137
138 // Every LAYER's 390 must resolve to a real plot-style object; without the
139 // ACDBPLACEHOLDER "Normal" the LAYER table fails to load.
140 BOOST_CHECK_MESSAGE( buffer.find( "ACAD_PLOTSTYLENAME\n350\n" ) != std::string::npos,
141 "Expected ACAD_PLOTSTYLENAME entry in root dictionary" );
142 BOOST_CHECK_MESSAGE( buffer.find( "ACDBDICTIONARYWDFLT\n" ) != std::string::npos,
143 "Expected ACDBDICTIONARYWDFLT for ACAD_PLOTSTYLENAME" );
144 BOOST_CHECK_MESSAGE( buffer.find( "ACDBPLACEHOLDER\n" ) != std::string::npos,
145 "Expected ACDBPLACEHOLDER (the 'Normal' plot style)" );
146 BOOST_CHECK_MESSAGE( buffer.find( " 6\nCONTINUOUS\n390\n" ) != std::string::npos,
147 "Expected 390 plot-style handle on every LAYER record" );
148
149 // VPORT/VIEW/UCS must exist (empty is OK). DIMSTYLE needs a Standard entry
150 // whose handle uses group code 105 instead of 5 (spec page 35).
151 BOOST_CHECK_MESSAGE( buffer.find( "TABLE\n 2\nVPORT\n" ) != std::string::npos,
152 "Expected VPORT table" );
153 BOOST_CHECK_MESSAGE( buffer.find( "TABLE\n 2\nVIEW\n" ) != std::string::npos,
154 "Expected VIEW table" );
155 BOOST_CHECK_MESSAGE( buffer.find( "TABLE\n 2\nUCS\n" ) != std::string::npos,
156 "Expected UCS table" );
157 BOOST_CHECK_MESSAGE( buffer.find( "TABLE\n 2\nDIMSTYLE\n" ) != std::string::npos,
158 "Expected DIMSTYLE table" );
159 BOOST_CHECK_MESSAGE( buffer.find( " 0\nDIMSTYLE\n105\n" ) != std::string::npos,
160 "Expected DIMSTYLE record handle on group code 105 (not 5)" );
161 BOOST_CHECK_MESSAGE( buffer.find( "AcDbDimStyleTableRecord\n 2\nStandard\n" )
162 != std::string::npos,
163 "Expected Standard DIMSTYLE entry" );
164
165 // Default layer "0" must exist in the LAYER table even if no entity uses it.
166 BOOST_CHECK_MESSAGE( buffer.find( "AcDbLayerTableRecord\n 2\n0\n" ) != std::string::npos,
167 "Expected default layer \"0\" entry in LAYER table" );
168
169 // ByBlock and ByLayer are required LTYPE entries.
170 BOOST_CHECK_MESSAGE( buffer.find( "AcDbLinetypeTableRecord\n 2\nByBlock\n" )
171 != std::string::npos,
172 "Expected ByBlock linetype entry" );
173 BOOST_CHECK_MESSAGE( buffer.find( "AcDbLinetypeTableRecord\n 2\nByLayer\n" )
174 != std::string::npos,
175 "Expected ByLayer linetype entry" );
176
177 // AutoCAD requires group 2 (printer name) in AcDbPlotSettings. ODA File
178 // Converter inserts "none_device" on load when it's missing.
179 BOOST_CHECK_MESSAGE( buffer.find( "AcDbPlotSettings\n 1\n\n 2\nnone_device\n" )
180 != std::string::npos,
181 "Expected group 2 (none_device) in AcDbPlotSettings" );
182
183 // ModelType (1024) in AcDbPlotSettings 70 is only valid on the Model layout.
184 BOOST_CHECK_MESSAGE( buffer.find( "AcDbLayout\n 1\nModel\n" ) != std::string::npos,
185 "Expected Model layout (precondition for the next check)" );
186 BOOST_CHECK_MESSAGE( buffer.find( " 70\n1024\n" ) != std::string::npos,
187 "Expected ModelType (1024) flag on the Model layout" );
188}
189
190
void SetViewport(const VECTOR2I &aOffset, double aIusPerDecimil, double aScale, bool aMirror) override
Set the scale/position for the DXF plot.
bool StartPlot(const wxString &aPageNumber) override
Open the DXF plot with a skeleton header.
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...
bool EndPlot() override
wxString CreateChildFileStr(const wxString &aName) const
Create and return the path to a direct child file as a wxString.
virtual bool OpenFile(const wxString &aFullFilename)
Open or create the plot file aFullFilename.
Definition plotter.cpp:75
void SetRenderSettings(RENDER_SETTINGS *aSettings)
Definition plotter.h:168
void MoveTo(const VECTOR2I &pos)
Definition plotter.h:325
void FinishTo(const VECTOR2I &pos)
Definition plotter.h:335
Minimal concrete render settings suitable for plotters in tests.
@ NO_FILL
Definition eda_fill.h:30
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_TEST_MESSAGE("Polyline has "<< chain.PointCount()<< " points")
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:708