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