KiCad PCB EDA Suite
Loading...
Searching...
No Matches
model_import.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 as published by
8 * the Free Software Foundation; either version 3 of the License, or (at your option)
9 * 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-3.0.html
19 */
20
22
25
26#include "oce/loadmodel.h"
27#include "vrml/loadmodel.h"
29
30#include <wx/filename.h>
31#include <wx/log.h>
32
33#ifdef _WIN32
34#include <shlobj.h>
35#endif
36
37#include <algorithm>
38#include <cmath>
39#include <exception>
40#include <limits>
41#include <new>
42#include <string>
43#include <vector>
44
45#include <decompress.hpp>
46
47#include <Standard_Failure.hxx>
48
49#ifdef _WIN32
50namespace
51{
52class TOKEN_HANDLE
53{
54public:
55 ~TOKEN_HANDLE()
56 {
57 if( value )
58 CloseHandle( value );
59 }
60
61 HANDLE value = nullptr;
62};
63
64class COM_ALLOCATED_PATH
65{
66public:
67 ~COM_ALLOCATED_PATH() { CoTaskMemFree( value ); }
68
69 PWSTR value = nullptr;
70};
71
72bool IsLowIntegrityProcess()
73{
74 TOKEN_HANDLE token;
75
76 if( !OpenProcessToken( GetCurrentProcess(), TOKEN_QUERY, &token.value ) )
77 return false;
78
79 DWORD size = 0;
80 GetTokenInformation( token.value, TokenIntegrityLevel, nullptr, 0, &size );
81 std::vector<BYTE> buffer( size );
82 bool lowIntegrity = false;
83
84 if( size > 0 && GetTokenInformation( token.value, TokenIntegrityLevel, buffer.data(), size, &size ) )
85 {
86 const auto* label = reinterpret_cast<const TOKEN_MANDATORY_LABEL*>( buffer.data() );
87 PSID sid = label->Label.Sid;
88 DWORD count = *GetSidSubAuthorityCount( sid );
89
90 if( count > 0 )
91 lowIntegrity = *GetSidSubAuthority( sid, count - 1 ) < SECURITY_MANDATORY_MEDIUM_RID;
92 }
93
94 return lowIntegrity;
95}
96} // namespace
97#endif
98
99
100wxString CreateModelImportTempFileName( const wxString& aPrefix )
101{
102#ifdef _WIN32
103 if( !IsLowIntegrityProcess() )
104 return wxFileName::CreateTempFileName( aPrefix );
105
106 COM_ALLOCATED_PATH localLowPath;
107 HRESULT result = SHGetKnownFolderPath( FOLDERID_LocalAppDataLow, KF_FLAG_CREATE, nullptr, &localLowPath.value );
108
109 if( FAILED( result ) )
110 return {};
111
112 wxFileName directory( localLowPath.value, wxEmptyString );
113 directory.AppendDir( wxS( "KiCadPreview" ) );
114
115 if( !directory.DirExists() && !directory.Mkdir( wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL )
116 && !directory.DirExists() )
117 return {};
118
119 return wxFileName::CreateTempFileName( wxFileName( directory.GetFullPath(), aPrefix ).GetFullPath() );
120#else
121 return wxFileName::CreateTempFileName( aPrefix );
122#endif
123}
124
125MODEL_IMPORT_GZIP_RESULT DecompressBoundedModel( const char* aData, std::size_t aSize,
126 std::uint64_t aMaxExpandedBytes, std::string& aExpanded )
127{
128 const std::size_t cap = static_cast<std::size_t>(
129 std::min<std::uint64_t>( aMaxExpandedBytes, std::numeric_limits<std::size_t>::max() ) );
130
131 try
132 {
133 aExpanded = gzip::decompress( aData, aSize, cap, std::min<std::size_t>( cap, 64U * 1024U ) );
134 }
135 catch( const gzip::limit_error& )
136 {
138 }
139 catch( ... )
140 {
142 }
143
145}
146
147
148namespace
149{
150class SCENE_OWNER
151{
152public:
153 explicit SCENE_OWNER( SCENEGRAPH* aScene ) :
154 m_scene( aScene )
155 {
156 }
157 ~SCENE_OWNER() { S3D::DestroyNode( reinterpret_cast<SGNODE*>( m_scene ) ); }
158
159 SCENEGRAPH* Get() const { return m_scene; }
160
161private:
162 SCENEGRAPH* m_scene;
163};
164
165enum class VALIDATION_RESULT
166{
167 VALID,
168 INVALID,
170};
171
172bool addBudget( std::uint64_t& aTotal, std::uint64_t aCount, std::uint64_t aElementSize, std::uint64_t aLimit )
173{
174 if( aTotal > aLimit || ( aCount && aElementSize > ( aLimit - aTotal ) / aCount ) )
175 return false;
176
177 aTotal += aCount * aElementSize;
178 return true;
179}
180
181
182bool isFinite( const SFVEC3F& aValue )
183{
184 return std::isfinite( aValue.x ) && std::isfinite( aValue.y ) && std::isfinite( aValue.z );
185}
186
187
188VALIDATION_RESULT validateModel( S3DMODEL& aModel, const S3D::MODEL_IMPORT_OPTIONS& aOptions )
189{
190 std::uint64_t total = 0;
191 bool hasValidFaces = false;
192 unsigned int outputMesh = 0;
193
194 if( !aModel.m_Meshes || !aModel.m_MeshesSize || ( aModel.m_MaterialsSize && !aModel.m_Materials ) )
195 return VALIDATION_RESULT::INVALID;
196
197 if( !addBudget( total, aModel.m_MeshesSize, sizeof( SMESH ), aOptions.maxMeshBytes )
198 || !addBudget( total, aModel.m_MaterialsSize, sizeof( SMATERIAL ), aOptions.maxMeshBytes ) )
199 return VALIDATION_RESULT::TOO_LARGE;
200
201 for( unsigned int i = 0; i < aModel.m_MaterialsSize; ++i )
202 {
203 const SMATERIAL& material = aModel.m_Materials[i];
204
205 if( !isFinite( material.m_Ambient ) || !isFinite( material.m_Diffuse ) || !isFinite( material.m_Emissive )
206 || !isFinite( material.m_Specular ) || !std::isfinite( material.m_Shininess )
207 || !std::isfinite( material.m_Transparency ) || material.m_Shininess < 0.0f || material.m_Shininess > 1.0f
208 || material.m_Transparency < 0.0f || material.m_Transparency > 1.0f )
209 return VALIDATION_RESULT::INVALID;
210 }
211
212 for( unsigned int i = 0; i < aModel.m_MeshesSize; ++i )
213 {
214 SMESH& mesh = aModel.m_Meshes[i];
215
216 if( !mesh.m_Positions || !mesh.m_Normals || !mesh.m_VertexSize || !mesh.m_FaceIdx || mesh.m_FaceIdxSize % 3
217 || mesh.m_MaterialIdx >= aModel.m_MaterialsSize )
218 {
219 S3D::Free3DMesh( mesh );
220 continue;
221 }
222
223 if( !addBudget( total, mesh.m_VertexSize, sizeof( SFVEC3F ) * 2, aOptions.maxMeshBytes )
224 || !addBudget( total, mesh.m_FaceIdxSize, sizeof( unsigned int ), aOptions.maxMeshBytes )
225 || ( mesh.m_Color && !addBudget( total, mesh.m_VertexSize, sizeof( SFVEC3F ), aOptions.maxMeshBytes ) )
226 || ( mesh.m_Texcoords
227 && !addBudget( total, mesh.m_VertexSize, sizeof( SFVEC2F ), aOptions.maxMeshBytes ) ) )
228 return VALIDATION_RESULT::TOO_LARGE;
229
230 bool finiteVertices = true;
231
232 for( unsigned int vertex = 0; vertex < mesh.m_VertexSize; ++vertex )
233 {
234 if( !isFinite( mesh.m_Positions[vertex] ) || !isFinite( mesh.m_Normals[vertex] )
235 || ( mesh.m_Color && !isFinite( mesh.m_Color[vertex] ) )
236 || ( mesh.m_Texcoords
237 && ( !std::isfinite( mesh.m_Texcoords[vertex].x )
238 || !std::isfinite( mesh.m_Texcoords[vertex].y ) ) ) )
239 {
240 finiteVertices = false;
241 break;
242 }
243 }
244
245 if( !finiteVertices )
246 {
247 S3D::Free3DMesh( mesh );
248 continue;
249 }
250
251 unsigned int output = 0;
252
253 for( unsigned int triangle = 0; triangle + 3 <= mesh.m_FaceIdxSize; triangle += 3 )
254 {
255 if( IsTriangleInRange( mesh.m_FaceIdx, triangle, mesh.m_VertexSize ) )
256 {
257 mesh.m_FaceIdx[output++] = mesh.m_FaceIdx[triangle];
258 mesh.m_FaceIdx[output++] = mesh.m_FaceIdx[triangle + 1];
259 mesh.m_FaceIdx[output++] = mesh.m_FaceIdx[triangle + 2];
260 }
261 }
262
263 mesh.m_FaceIdxSize = output;
264 hasValidFaces = hasValidFaces || output > 0;
265
266 if( output == 0 )
267 {
268 S3D::Free3DMesh( mesh );
269 continue;
270 }
271
272 if( outputMesh != i )
273 {
274 aModel.m_Meshes[outputMesh] = mesh;
275 S3D::Init3DMesh( mesh );
276 }
277
278 ++outputMesh;
279 }
280
281 aModel.m_MeshesSize = outputMesh;
282
283 return hasValidFaces && outputMesh > 0 ? VALIDATION_RESULT::VALID : VALIDATION_RESULT::INVALID;
284}
285} // namespace
286
287
289 m_model( aOther.m_model ),
290 m_error( aOther.m_error ),
291 m_diagnostic( std::move( aOther.m_diagnostic ) )
292{
293 aOther.m_model = nullptr;
294}
295
296
298{
299 if( this != &aOther )
300 {
302 m_model = aOther.m_model;
303 m_error = aOther.m_error;
304 m_diagnostic = std::move( aOther.m_diagnostic );
305 aOther.m_model = nullptr;
306 }
307
308 return *this;
309}
310
311
316
317
318S3D::MODEL_IMPORT_RESULT::operator bool() const noexcept
319{
320 return m_model;
321}
322
323
325{
326 return m_model;
327}
328
329
334
335
336std::string_view S3D::MODEL_IMPORT_RESULT::GetDiagnostic() const noexcept
337{
338 return m_diagnostic;
339}
340
341
342S3D::MODEL_IMPORT_RESULT S3D::ImportModel( const std::string& aFileName, const MODEL_IMPORT_OPTIONS& aOptions ) noexcept
343{
345
346 try
347 {
349 MODEL_IMPORT_NUMERIC_LOCALE numericLocale;
350
351 // Preview imports must not reach wxWidgets' GUI log target.
352 wxLogNull logGuard;
353
354 if( aOptions.IsCanceled() )
355 {
357 return result;
358 }
359
360 wxFileName fileName( wxString::FromUTF8( aFileName ) );
361
362 if( !fileName.FileExists() || !fileName.IsFileReadable() )
363 {
365 result.m_diagnostic = "Model file is missing or unreadable";
366 return result;
367 }
368
370 || !std::isfinite( aOptions.stepLinearDeflection ) || aOptions.stepLinearDeflection <= 0.0
371 || !std::isfinite( aOptions.stepAngularDeflectionDegrees ) || aOptions.stepAngularDeflectionDegrees <= 0.0 )
372 {
374 result.m_diagnostic = "Invalid model import options";
375 return result;
376 }
377
378 wxULongLong inputSize = fileName.GetSize();
379
380 if( inputSize == wxInvalidSize || inputSize.GetValue() > aOptions.maxExpandedBytes )
381 {
383 result.m_diagnostic = "Model input exceeds the configured limit";
384 return result;
385 }
386
387 std::string extension = fileName.GetExt().Lower().ToStdString();
388 std::string lowerName = fileName.GetFullName().Lower().ToStdString();
389 MODEL_IMPORT_FORMAT format = aOptions.format;
390
391 if( format == MODEL_IMPORT_FORMAT::AUTO )
392 {
393 if( extension == "stp" || extension == "step" || extension == "stpz" || extension == "stepz"
394 || lowerName.ends_with( ".stp.gz" ) || lowerName.ends_with( ".step.gz" ) )
396 else if( extension == "igs" || extension == "iges" )
398 else if( extension == "wrl" || extension == "wrz" )
400 else if( extension == "x3d" )
402 else
403 {
405 result.m_diagnostic = "Unsupported model format";
406 return result;
407 }
408 }
409
410 MODEL_IMPORT_STATUS status;
411 SCENEGRAPH* scene = nullptr;
412
413 if( format == MODEL_IMPORT_FORMAT::STEP || format == MODEL_IMPORT_FORMAT::IGES )
414 scene = LoadModel( aFileName.c_str(), &aOptions, &status );
415 else if( format == MODEL_IMPORT_FORMAT::VRML || format == MODEL_IMPORT_FORMAT::X3D )
416 scene = LoadVRMLModel( aFileName.c_str(), &aOptions, &status );
417 else
418 {
420 return result;
421 }
422
423 if( !scene )
424 {
425 result.m_error = status.error;
426 result.m_diagnostic = std::move( status.diagnostic );
427
428 if( result.m_diagnostic.empty() )
429 result.m_diagnostic = "Model parser rejected the file";
430
431 return result;
432 }
433
434 SCENE_OWNER sceneOwner( scene );
435 result.m_model = GetModel( sceneOwner.Get() );
436
437 if( aOptions.IsCanceled() )
438 {
439 Destroy3DModel( &result.m_model );
441 return result;
442 }
443
444 VALIDATION_RESULT validation =
445 result.m_model ? validateModel( *result.m_model, aOptions ) : VALIDATION_RESULT::INVALID;
446
447 if( validation != VALIDATION_RESULT::VALID )
448 {
449 Destroy3DModel( &result.m_model );
450 result.m_error = validation == VALIDATION_RESULT::TOO_LARGE ? MODEL_IMPORT_ERROR::TOO_LARGE
452 result.m_diagnostic = validation == VALIDATION_RESULT::TOO_LARGE
453 ? "Model exceeds the configured mesh limit"
454 : "Model contains invalid or no renderable geometry";
455 return result;
456 }
457
459 return result;
460 }
461 catch( const std::bad_alloc& )
462 {
463 Destroy3DModel( &result.m_model );
465 }
466 catch( const MODEL_IMPORT_CANCELED& )
467 {
468 Destroy3DModel( &result.m_model );
470 }
471 catch( const Standard_Failure& e )
472 {
473 Destroy3DModel( &result.m_model );
475
476 try
477 {
478 result.m_diagnostic = e.GetMessageString() ? e.GetMessageString() : "OCCT import failure";
479 }
480 catch( ... )
481 {
482 }
483 }
484 catch( const std::exception& e )
485 {
486 Destroy3DModel( &result.m_model );
488
489 try
490 {
491 result.m_diagnostic = e.what();
492 }
493 catch( ... )
494 {
495 }
496 }
497 catch( ... )
498 {
499 Destroy3DModel( &result.m_model );
501 }
502
503 return result;
504}
bool IsTriangleInRange(const unsigned int *aFaceIdx, unsigned int aTriangleIdx, unsigned int aVertexCount)
Test whether the three face indices of a triangle all reference valid vertices.
Definition c3dmodel.h:100
Scoped holder for the recursive model import lock.
Definition ifsg_api.h:59
MODEL_IMPORT_ERROR m_error
const S3DMODEL * GetModel() const noexcept
MODEL_IMPORT_ERROR GetError() const noexcept
std::string_view GetDiagnostic() const noexcept
MODEL_IMPORT_RESULT(MODEL_IMPORT_RESULT &&aOther) noexcept
MODEL_IMPORT_RESULT() noexcept=default
MODEL_IMPORT_RESULT & operator=(MODEL_IMPORT_RESULT &&aOther) noexcept
Define the basic data set required to represent a 3D model.
Definition scenegraph.h:41
collects header files for all SG* wrappers and the API
defines the API calls for the manipulation of SG* classes
SCENEGRAPH * LoadModel(char const *filename, const S3D::MODEL_IMPORT_OPTIONS *aOptions, MODEL_IMPORT_STATUS *aStatus)
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.
MODEL_IMPORT_GZIP_RESULT
KICAD_3D_IMPORT_API MODEL_IMPORT_RESULT ImportModel(const std::string &aFileName, const MODEL_IMPORT_OPTIONS &aOptions={}) noexcept
Import and flatten one model without invoking GUI facilities.
SGLIB_API void Free3DMesh(SMESH &aMesh)
Free memory used internally by an SMESH structure.
Definition ifsg_api.cpp:445
SGLIB_API void Init3DMesh(SMESH &aMesh)
Create and initialize an SMESH structure.
Definition ifsg_api.cpp:465
MODEL_IMPORT_FORMAT
SGLIB_API void DestroyNode(SGNODE *aNode) noexcept
Delete the given SG* class node.
Definition ifsg_api.cpp:169
SGLIB_API S3DMODEL * GetModel(SCENEGRAPH *aNode)
Create an S3DMODEL representation of aNode (raw data, no transforms).
Definition ifsg_api.cpp:362
MODEL_IMPORT_ERROR
SGLIB_API void Destroy3DModel(S3DMODEL **aModel)
Free memory used by an S3DMODEL structure and sets the pointer to the structure to NULL.
Definition ifsg_api.cpp:427
S3D::MODEL_IMPORT_ERROR error
Store the a model based on meshes and materials.
Definition c3dmodel.h:111
SMATERIAL * m_Materials
The materials list of this model.
Definition c3dmodel.h:116
unsigned int m_MeshesSize
Number of meshes in the array.
Definition c3dmodel.h:112
SMESH * m_Meshes
The meshes list of this model.
Definition c3dmodel.h:113
unsigned int m_MaterialsSize
Number of materials in the material array.
Definition c3dmodel.h:115
bool IsCanceled() const noexcept
MODEL_IMPORT_FORMAT format
std::uint64_t maxExpandedBytes
float m_Shininess
Definition c3dmodel.h:39
SFVEC3F m_Specular
Definition c3dmodel.h:38
SFVEC3F m_Ambient
Definition c3dmodel.h:35
float m_Transparency
1.0 is completely transparent, 0.0 completely opaque
Definition c3dmodel.h:40
SFVEC3F m_Emissive
Definition c3dmodel.h:37
SFVEC3F m_Diffuse
Default diffuse color if m_Color is NULL.
Definition c3dmodel.h:36
Per-vertex normal/color/texcoors structure.
Definition c3dmodel.h:77
unsigned int * m_FaceIdx
Triangle Face Indexes.
Definition c3dmodel.h:84
SFVEC3F * m_Normals
Vertex normals array.
Definition c3dmodel.h:80
unsigned int m_MaterialIdx
Material Index to be used in this mesh (must be < m_MaterialsSize )
Definition c3dmodel.h:85
unsigned int m_VertexSize
Number of vertex in the arrays.
Definition c3dmodel.h:78
SFVEC2F * m_Texcoords
Vertex texture coordinates array, can be NULL.
Definition c3dmodel.h:81
unsigned int m_FaceIdxSize
Number of elements of the m_FaceIdx array.
Definition c3dmodel.h:83
SFVEC3F * m_Color
Vertex color array, can be NULL.
Definition c3dmodel.h:82
SFVEC3F * m_Positions
Vertex position array.
Definition c3dmodel.h:79
Result of a validation operation.
wxString result
Test unit parsing edge cases and error handling.
KICAD_3D_IMPORT_API SCENEGRAPH * LoadVRMLModel(const char *aFileName, const S3D::MODEL_IMPORT_OPTIONS *aOptions=nullptr)
Definition vrml.cpp:330
typedef HANDLE
glm::vec2 SFVEC2F
Definition xv3d_types.h:38
glm::vec3 SFVEC3F
Definition xv3d_types.h:40