KiCad PCB EDA Suite
Loading...
Searching...
No Matches
vrml.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 (C) 2015, 2016 Cirilo Bernardo <[email protected]>
5 * Copyright The KiCad Developers, see AUTHORS.TXT for contributors.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <https://www.gnu.org/licenses/>.
19 */
20
21/*
22 * Description:
23 * This plugin implements the legacy KiCad VRML1/VRML2 and X3D parsers
24 * The plugin will invoke a VRML1 or VRML2 parser depending on the
25 * identifying information in the file header:
26 *
27 * #VRML V1.0 ASCII
28 * #VRML V2.0 utf8
29 */
30
33#include "vrml_line_reader.h"
34#include "vrml1_base.h"
35#include "vrml2_base.h"
36#include "wrlproc.h"
37#include "x3d.h"
38#include <wx/filename.h>
39#include <wx/stdpaths.h>
40#include <wx/string.h>
41#include <wx/wfstream.h>
42#include <wx/log.h>
43
44#include <algorithm>
45#include <limits>
46#include <memory>
47#include <vector>
48#include "loadmodel.h"
50
56const wxChar* const traceVrmlPlugin = wxT( "KICAD_VRML_PLUGIN" );
57
58namespace
59{
60thread_local std::vector<wxString> inlineLoadStack;
61
62class INLINE_LOAD_GUARD
63{
64public:
65 explicit INLINE_LOAD_GUARD( wxString aPath ) { inlineLoadStack.emplace_back( std::move( aPath ) ); }
66
67 ~INLINE_LOAD_GUARD() { inlineLoadStack.pop_back(); }
68};
69} // namespace
70
71
72SCENEGRAPH* LoadVRML( const wxString& aFileName, const S3D::MODEL_IMPORT_OPTIONS& aOptions,
73 MODEL_IMPORT_STATUS* aStatus )
74{
75 wxFileName sourcePath( aFileName );
76 sourcePath.Normalize( wxPATH_NORM_DOTS | wxPATH_NORM_TILDE | wxPATH_NORM_ABSOLUTE | wxPATH_NORM_ENV_VARS );
77 wxString canonicalPath = sourcePath.GetFullPath();
78
79 if( inlineLoadStack.size() >= 64 )
80 {
81 if( aStatus )
82 aStatus->Set( S3D::MODEL_IMPORT_ERROR::TOO_LARGE, "VRML Inline nesting exceeds the configured limit" );
83
84 return nullptr;
85 }
86
87 if( std::find( inlineLoadStack.begin(), inlineLoadStack.end(), canonicalPath ) != inlineLoadStack.end() )
88 {
89 if( aStatus )
90 aStatus->Set( S3D::MODEL_IMPORT_ERROR::INVALID, "VRML Inline dependency cycle detected" );
91
92 return nullptr;
93 }
94
95 INLINE_LOAD_GUARD inlineGuard( canonicalPath );
97 std::unique_ptr<VRML_LINE_READER> modelFile;
98 SCENEGRAPH* scene = nullptr;
99 wxString filename = aFileName;
100 wxFileName tmpfilename;
101
102 if( aFileName.Upper().EndsWith( wxT( "WRZ" ) ) )
103 {
104 wxFFileInputStream ifile( aFileName );
105 tmpfilename = wxFileName( CreateModelImportTempFileName( wxS( "kicad-vrml-" ) ) );
106 cleanup.path = tmpfilename.GetFullPath();
107
108 if( cleanup.path.empty() || !ifile.IsOk() )
109 {
110 if( aStatus )
111 aStatus->Set( S3D::MODEL_IMPORT_ERROR::UNREADABLE, "Unable to open compressed VRML" );
112
113 return nullptr;
114 }
115
116 wxFileOffset size = ifile.GetLength();
117
118 if( size == wxInvalidOffset )
119 return nullptr;
120
121 if( static_cast<std::uint64_t>( size ) > aOptions.maxCompressedBytes
122 || static_cast<std::uint64_t>( size ) > std::numeric_limits<std::size_t>::max() )
123 {
124 if( aStatus )
125 aStatus->Set( S3D::MODEL_IMPORT_ERROR::TOO_LARGE, "Compressed VRML exceeds the configured limit" );
126
127 return nullptr;
128 }
129
130 {
131 wxFFileOutputStream ofile( tmpfilename.GetFullPath() );
132
133 if( !ofile.IsOk() )
134 return nullptr;
135
136 std::vector<char> buffer( static_cast<size_t>( size ) );
137
138 ifile.Read( buffer.data(), size );
139
140 if( ifile.LastRead() != static_cast<size_t>( size ) )
141 return nullptr;
142
143 std::string expanded;
144
145 switch( DecompressBoundedModel( buffer.data(), static_cast<size_t>( size ),
146 aOptions.maxExpandedBytes, expanded ) )
147 {
149 break;
150
152 if( aStatus )
153 aStatus->Set( S3D::MODEL_IMPORT_ERROR::TOO_LARGE, "Expanded VRML exceeds the configured limit" );
154
155 return nullptr;
156
158 if( aStatus )
159 aStatus->Set( S3D::MODEL_IMPORT_ERROR::INVALID, "Compressed VRML archive is corrupt" );
160
161 wxLogTrace( traceVrmlPlugin, wxS( " * [INFO] wrz load failed" ) );
162
163 return nullptr;
164 }
165
166 ofile.Write( expanded.data(), expanded.size() );
167
168 if( ofile.LastWrite() != expanded.size() || !ofile.Close() )
169 return nullptr;
170 }
171
172 filename = tmpfilename.GetFullPath();
173 }
174
175 try
176 {
177 // set the max char limit to 8MB; if a VRML file contains
178 // longer lines then perhaps it shouldn't be used
179 modelFile = std::make_unique<VRML_LINE_READER>( filename, 0, 8388608, aOptions );
180 }
181 catch( const std::exception& e )
182 {
183 wxLogTrace( traceVrmlPlugin, wxS( " * [INFO] load failed: %s" ), wxString::FromUTF8Unchecked( e.what() ) );
184
185 return nullptr;
186 }
187
188
189 // VRML file processor
190 WRLPROC proc( modelFile.get() );
191
192 if( proc.GetVRMLType() == WRLVERSION::VRML_V1 )
193 {
194 wxLogTrace( traceVrmlPlugin, wxT( " * [INFO] Processing VRML 1.0 file" ) );
195
196 auto bp = std::make_unique<WRL1BASE>();
197
198 if( !bp->Read( proc ) )
199 {
200 wxLogTrace( traceVrmlPlugin, wxT( " * [INFO] load failed" ) );
201 }
202 else
203 {
204 wxLogTrace( traceVrmlPlugin, wxT( " * [INFO] load completed" ) );
205
206 scene = (SCENEGRAPH*)bp->TranslateToSG( nullptr, nullptr );
207 }
208 }
209 else if( proc.GetVRMLType() == WRLVERSION::VRML_V2 )
210 {
211 wxLogTrace( traceVrmlPlugin, wxT( " * [INFO] Processing VRML 2.0 file" ) );
212
213 auto bp = std::make_unique<WRL2BASE>();
214
215 // Inline{} inclusion is disabled for nested loads to bound recursion.
216 bp->SetEnableInline( aOptions.vrmlEnableInline );
217
218 // Top-level files start in legacy mode and drop out of it when a top-level scale
219 // transform is found. Inline submodels inherit the parent's setting.
220 bp->SetApplyUnitConversion( aOptions.vrmlApplyLegacyUnitConversion );
221 bp->SetImportOptions( aOptions, aStatus );
222
223 if( !bp->Read( proc ) )
224 {
225 wxLogTrace( traceVrmlPlugin, wxT( " * [INFO] load failed" ) );
226 }
227 else
228 {
229 wxLogTrace( traceVrmlPlugin, wxT( " * [INFO] load completed" ) );
230
231 // for now we recalculate all normals per-vertex per-face
232 scene = (SCENEGRAPH*)bp->TranslateToSG( nullptr );
233
234 if( bp->HadIncompleteInline() )
235 {
236 if( aStatus && aStatus->diagnostic.empty() )
238 "VRML Inline dependency was denied or failed; geometry would be incomplete" );
239
240 bp.reset();
241
242 if( scene )
243 S3D::DestroyNode( reinterpret_cast<SGNODE*>( scene ) );
244
245 return nullptr;
246 }
247 }
248 }
249
250 // DEBUG: WRITE OUT VRML2 FILE TO CONFIRM STRUCTURE
251#if ( defined( DEBUG_VRML1 ) && DEBUG_VRML1 > 3 ) \
252 || ( defined( DEBUG_VRML2 ) && DEBUG_VRML2 > 3 )
253 if( scene )
254 {
255 wxFileName fn( wxString::FromUTF8Unchecked( aFileName ) );
256 wxString output;
257
258 if( proc.GetVRMLType() == VRML_V1 )
259 output = wxT( "_vrml1-" );
260 else
261 output = wxT( "_vrml2-" );
262
263 output.append( fn.GetName() );
264 output.append( wxT(".wrl") );
265 S3D::WriteVRML( output.ToUTF8(), true, (SGNODE*)(scene), true, true );
266 }
267#endif
268
269 return scene;
270}
271
272
273SCENEGRAPH* LoadX3D( const wxString& aFileName, const S3D::MODEL_IMPORT_OPTIONS& aOptions,
274 MODEL_IMPORT_STATUS* aStatus )
275{
276 if( aOptions.IsCanceled() )
277 throw MODEL_IMPORT_CANCELED();
278
279 SCENEGRAPH* scene = nullptr;
281 scene = model.Load( aFileName );
282
283 if( aOptions.IsCanceled() )
284 {
285 if( scene )
286 S3D::DestroyNode( reinterpret_cast<SGNODE*>( scene ) );
287
288 throw MODEL_IMPORT_CANCELED();
289 }
290
291 if( !scene && aStatus )
292 aStatus->Set( S3D::MODEL_IMPORT_ERROR::INVALID, "X3D parser rejected the file" );
293
294 return scene;
295}
296
297
298SCENEGRAPH* LoadVRMLModel( const char* aFileName, const S3D::MODEL_IMPORT_OPTIONS* aOptions,
299 MODEL_IMPORT_STATUS* aStatus )
300{
301 if( nullptr == aFileName )
302 return nullptr;
303
304 wxString fname = wxString::FromUTF8Unchecked( aFileName );
305
306 if( !wxFileName::FileExists( fname ) )
307 return nullptr;
308
310
311 if( aOptions )
312 options = *aOptions;
313
314 SCENEGRAPH* scene = nullptr;
315 wxString ext = wxFileName( fname ).GetExt();
316
318 || ( options.format == S3D::MODEL_IMPORT_FORMAT::AUTO && ( ext == wxT( "x3d" ) || ext == wxT( "X3D" ) ) ) )
319 scene = LoadX3D( fname, options, aStatus );
321 scene = LoadVRML( fname, options, aStatus );
322
323 if( !scene && aStatus && aStatus->diagnostic.empty() )
324 aStatus->Set( S3D::MODEL_IMPORT_ERROR::IMPORT_FAILED, "VRML parser rejected the file" );
325
326 return scene;
327}
328
329
330SCENEGRAPH* LoadVRMLModel( const char* aFileName, const S3D::MODEL_IMPORT_OPTIONS* aOptions )
331{
334 MODEL_IMPORT_STATUS status;
335
336 try
337 {
338 if( SCENEGRAPH* scene = LoadVRMLModel( aFileName, aOptions, &status ) )
339 return scene;
340 }
341 catch( ... )
342 {
343 return nullptr;
344 }
345
346 wxLogTrace( traceVrmlPlugin, wxT( "Model import failed: %s" ), status.diagnostic.c_str() );
347 return nullptr;
348}
Describe the runtime-loadable interface to support loading and parsing of 3D models.
Scoped holder for the recursive model import lock.
Definition ifsg_api.h:59
Define the basic data set required to represent a 3D model.
Definition scenegraph.h:41
The base class of all Scene Graph nodes.
Definition sg_node.h:71
WRLVERSION GetVRMLType(void)
Definition wrlproc.cpp:232
const wxChar *const traceVrmlPlugin
Flag to enable VRML plugin trace output.
Definition vrml.cpp:56
collects header files for all SG* wrappers and the API
wxString CreateModelImportTempFileName(const wxString &aPrefix)
MODEL_IMPORT_GZIP_RESULT DecompressBoundedModel(const char *aData, std::size_t aSize, std::uint64_t aMaxExpandedBytes, std::string &aExpanded)
Expand a gzip stream into aExpanded, refusing anything over aMaxExpandedBytes.
SGLIB_API bool WriteVRML(const char *filename, bool overwrite, SGNODE *aTopNode, bool reuse, bool renameNodes)
Write out the given node and its subnodes to a VRML2 file.
Definition ifsg_api.cpp:91
SGLIB_API void DestroyNode(SGNODE *aNode) noexcept
Delete the given SG* class node.
Definition ifsg_api.cpp:169
void Set(S3D::MODEL_IMPORT_ERROR aError, const char *aDiagnostic)
Remove the scratch file an importer expanded a compressed model into.
bool IsCanceled() const noexcept
MODEL_IMPORT_FORMAT format
std::uint64_t maxCompressedBytes
std::uint64_t maxExpandedBytes
KIBIS_MODEL * model
Define the base class for VRML1.0 nodes.
SCENEGRAPH * LoadVRMLModel(const char *aFileName, const S3D::MODEL_IMPORT_OPTIONS *aOptions, MODEL_IMPORT_STATUS *aStatus)
Definition vrml.cpp:298
SCENEGRAPH * LoadVRML(const wxString &aFileName, const S3D::MODEL_IMPORT_OPTIONS &aOptions, MODEL_IMPORT_STATUS *aStatus)
Definition vrml.cpp:72
SCENEGRAPH * LoadX3D(const wxString &aFileName, const S3D::MODEL_IMPORT_OPTIONS &aOptions, MODEL_IMPORT_STATUS *aStatus)
Definition vrml.cpp:273
defines the basic input class for VRML