New upstream version 5.1.5~ds0
IOhannes m zmölnig (Debian/GNU)
2 years ago
245 | 245 | ENDIF() |
246 | 246 | |
247 | 247 | # Grouped compiler settings ######################################## |
248 | IF ((CMAKE_C_COMPILER_ID MATCHES "GNU") AND NOT CMAKE_COMPILER_IS_MINGW) | |
248 | IF ((CMAKE_C_COMPILER_ID MATCHES "GNU") AND NOT MINGW) | |
249 | 249 | IF(NOT ASSIMP_HUNTER_ENABLED) |
250 | 250 | SET(CMAKE_CXX_STANDARD 11) |
251 | 251 | SET(CMAKE_POSITION_INDEPENDENT_CODE ON) |
282 | 282 | ENDIF() |
283 | 283 | SET(CMAKE_CXX_FLAGS "-fvisibility=hidden -fno-strict-aliasing -Wall -Wno-long-long ${CMAKE_CXX_FLAGS}" ) |
284 | 284 | SET(CMAKE_C_FLAGS "-fno-strict-aliasing ${CMAKE_C_FLAGS}") |
285 | ELSEIF( CMAKE_COMPILER_IS_MINGW ) | |
285 | ELSEIF( MINGW ) | |
286 | 286 | IF (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 7.0) |
287 | 287 | message(FATAL_ERROR "MinGW is too old to be supported. Please update MinGW and try again.") |
288 | 288 | ELSEIF(CMAKE_CXX_COMPILER_VERSION VERSION_LESS 7.3) |
48 | 48 | #include "BlenderDNA.h" |
49 | 49 | #include "BlenderSceneGen.h" |
50 | 50 | |
51 | using namespace Assimp; | |
52 | using namespace Assimp::Blender; | |
51 | namespace Assimp { | |
52 | namespace Blender { | |
53 | 53 | |
54 | 54 | //-------------------------------------------------------------------------------- |
55 | 55 | template <> |
884 | 884 | converters["CollectionObject"] = DNA::FactoryPair(&Structure::Allocate<CollectionObject>, &Structure::Convert<CollectionObject>); |
885 | 885 | } |
886 | 886 | |
887 | } // namespace Blender | |
888 | } //namespace Assimp | |
889 | ||
887 | 890 | #endif // ASSIMP_BUILD_NO_BLEND_IMPORTER |
2069 | 2069 | TrySetTextureProperties(out_mat, layeredTextures, "ShininessExponent", aiTextureType_SHININESS, mesh); |
2070 | 2070 | TrySetTextureProperties(out_mat, layeredTextures, "EmissiveFactor", aiTextureType_EMISSIVE, mesh); |
2071 | 2071 | TrySetTextureProperties(out_mat, layeredTextures, "TransparencyFactor", aiTextureType_OPACITY, mesh); |
2072 | TrySetTextureProperties(out_mat, layeredTextures, "ReflectionFactor", aiTextureType_METALNESS, mesh); | |
2072 | 2073 | } |
2073 | 2074 | |
2074 | 2075 | aiColor3D FBXConverter::GetColorPropertyFactored(const PropertyTable &props, const std::string &colorName, |
536 | 536 | |
537 | 537 | // get file format version and print to log |
538 | 538 | ++it; |
539 | ||
540 | if ((*it).tokens[0].empty()) { | |
541 | ASSIMP_LOG_ERROR("Invalid LWS file detectedm abort import."); | |
542 | return; | |
543 | } | |
539 | 544 | unsigned int version = strtoul10((*it).tokens[0].c_str()); |
540 | 545 | ASSIMP_LOG_INFO("LWS file format version is ", (*it).tokens[0]); |
541 | 546 | first = 0.; |
462 | 462 | ASSIMP_LOG_WARN("Found a reference to an embedded DDS texture, " |
463 | 463 | "but texture height is not equal to 1, which is not supported by MED"); |
464 | 464 | } |
465 | ||
466 | pcNew.reset(new aiTexture()); | |
465 | if (iWidth == 0) { | |
466 | ASSIMP_LOG_ERROR("Found a reference to an embedded DDS texture, but texture width is zero, aborting import."); | |
467 | return; | |
468 | } | |
469 | ||
470 | pcNew.reset(new aiTexture); | |
467 | 471 | pcNew->mHeight = 0; |
468 | 472 | pcNew->mWidth = iWidth; |
469 | 473 |
132 | 132 | aiString textureSpecularity; |
133 | 133 | aiString textureOpacity; |
134 | 134 | aiString textureDisp; |
135 | aiString textureRoughness; | |
136 | aiString textureMetallic; | |
137 | aiString textureSheen; | |
138 | aiString textureRMA; | |
135 | 139 | |
136 | 140 | enum TextureType { |
137 | 141 | TextureDiffuseType = 0, |
150 | 154 | TextureSpecularityType, |
151 | 155 | TextureOpacityType, |
152 | 156 | TextureDispType, |
157 | TextureRoughnessType, | |
158 | TextureMetallicType, | |
159 | TextureSheenType, | |
160 | TextureRMAType, | |
153 | 161 | TextureTypeCount |
154 | 162 | }; |
155 | 163 | bool clamp[TextureTypeCount]; |
173 | 181 | //! Transparency color |
174 | 182 | aiColor3D transparent; |
175 | 183 | |
184 | //! PBR Roughness | |
185 | ai_real roughness; | |
186 | //! PBR Metallic | |
187 | ai_real metallic; | |
188 | //! PBR Metallic | |
189 | aiColor3D sheen; | |
190 | //! PBR Clearcoat Thickness | |
191 | ai_real clearcoat_thickness; | |
192 | //! PBR Clearcoat Rougness | |
193 | ai_real clearcoat_roughness; | |
194 | //! PBR Anisotropy | |
195 | ai_real anisotropy; | |
196 | ||
176 | 197 | //! Constructor |
177 | 198 | Material() : |
178 | 199 | diffuse(ai_real(0.6), ai_real(0.6), ai_real(0.6)), |
180 | 201 | shineness(ai_real(0.0)), |
181 | 202 | illumination_model(1), |
182 | 203 | ior(ai_real(1.0)), |
183 | transparent(ai_real(1.0), ai_real(1.0), ai_real(1.0)) { | |
204 | transparent(ai_real(1.0), ai_real(1.0), ai_real(1.0)), | |
205 | roughness(ai_real(1.0)), | |
206 | metallic(ai_real(0.0)), | |
207 | sheen(ai_real(1.0), ai_real(1.0), ai_real(1.0)), | |
208 | clearcoat_thickness(ai_real(0.0)), | |
209 | clearcoat_roughness(ai_real(0.0)), | |
210 | anisotropy(ai_real(0.0)) { | |
184 | 211 | std::fill_n(clamp, static_cast<unsigned int>(TextureTypeCount), false); |
185 | 212 | } |
186 | 213 |
617 | 617 | mat->AddProperty(&pCurrentMaterial->shineness, 1, AI_MATKEY_SHININESS); |
618 | 618 | mat->AddProperty(&pCurrentMaterial->alpha, 1, AI_MATKEY_OPACITY); |
619 | 619 | mat->AddProperty(&pCurrentMaterial->transparent, 1, AI_MATKEY_COLOR_TRANSPARENT); |
620 | mat->AddProperty(&pCurrentMaterial->roughness, 1, AI_MATKEY_ROUGHNESS_FACTOR); | |
621 | mat->AddProperty(&pCurrentMaterial->metallic, 1, AI_MATKEY_METALLIC_FACTOR); | |
622 | mat->AddProperty(&pCurrentMaterial->sheen, 1, AI_MATKEY_SHEEN_COLOR_FACTOR); | |
623 | mat->AddProperty(&pCurrentMaterial->clearcoat_thickness, 1, AI_MATKEY_CLEARCOAT_FACTOR); | |
624 | mat->AddProperty(&pCurrentMaterial->clearcoat_roughness, 1, AI_MATKEY_CLEARCOAT_ROUGHNESS_FACTOR); | |
625 | mat->AddProperty(&pCurrentMaterial->anisotropy, 1, AI_MATKEY_ANISOTROPY_FACTOR); | |
620 | 626 | |
621 | 627 | // Adding refraction index |
622 | 628 | mat->AddProperty(&pCurrentMaterial->ior, 1, AI_MATKEY_REFRACTI); |
708 | 714 | } |
709 | 715 | } |
710 | 716 | |
717 | if (0 != pCurrentMaterial->textureRoughness.length) { | |
718 | mat->AddProperty(&pCurrentMaterial->textureRoughness, _AI_MATKEY_TEXTURE_BASE, aiTextureType_DIFFUSE_ROUGHNESS, 0); | |
719 | mat->AddProperty(&uvwIndex, 1, _AI_MATKEY_UVWSRC_BASE, aiTextureType_DIFFUSE_ROUGHNESS, 0 ); | |
720 | if (pCurrentMaterial->clamp[ObjFile::Material::TextureRoughnessType]) { | |
721 | addTextureMappingModeProperty(mat, aiTextureType_DIFFUSE_ROUGHNESS); | |
722 | } | |
723 | } | |
724 | ||
725 | if (0 != pCurrentMaterial->textureMetallic.length) { | |
726 | mat->AddProperty(&pCurrentMaterial->textureMetallic, _AI_MATKEY_TEXTURE_BASE, aiTextureType_METALNESS, 0); | |
727 | mat->AddProperty(&uvwIndex, 1, _AI_MATKEY_UVWSRC_BASE, aiTextureType_METALNESS, 0 ); | |
728 | if (pCurrentMaterial->clamp[ObjFile::Material::TextureMetallicType]) { | |
729 | addTextureMappingModeProperty(mat, aiTextureType_METALNESS); | |
730 | } | |
731 | } | |
732 | ||
733 | if (0 != pCurrentMaterial->textureSheen.length) { | |
734 | mat->AddProperty(&pCurrentMaterial->textureSheen, _AI_MATKEY_TEXTURE_BASE, aiTextureType_SHEEN, 0); | |
735 | mat->AddProperty(&uvwIndex, 1, _AI_MATKEY_UVWSRC_BASE, aiTextureType_SHEEN, 0 ); | |
736 | if (pCurrentMaterial->clamp[ObjFile::Material::TextureSheenType]) { | |
737 | addTextureMappingModeProperty(mat, aiTextureType_SHEEN); | |
738 | } | |
739 | } | |
740 | ||
741 | if (0 != pCurrentMaterial->textureRMA.length) { | |
742 | // NOTE: glTF importer places Rough/Metal/AO texture in Unknown so doing the same here for consistency. | |
743 | mat->AddProperty(&pCurrentMaterial->textureRMA, _AI_MATKEY_TEXTURE_BASE, aiTextureType_UNKNOWN, 0); | |
744 | mat->AddProperty(&uvwIndex, 1, _AI_MATKEY_UVWSRC_BASE, aiTextureType_UNKNOWN, 0 ); | |
745 | if (pCurrentMaterial->clamp[ObjFile::Material::TextureRMAType]) { | |
746 | addTextureMappingModeProperty(mat, aiTextureType_UNKNOWN); | |
747 | } | |
748 | } | |
749 | ||
711 | 750 | // Store material property info in material array in scene |
712 | 751 | pScene->mMaterials[pScene->mNumMaterials] = mat; |
713 | 752 | pScene->mNumMaterials++; |
66 | 66 | static const std::string DisplacementTexture1 = "map_disp"; |
67 | 67 | static const std::string DisplacementTexture2 = "disp"; |
68 | 68 | static const std::string SpecularityTexture = "map_ns"; |
69 | static const std::string RoughnessTexture = "map_Pr"; | |
70 | static const std::string MetallicTexture = "map_Pm"; | |
71 | static const std::string SheenTexture = "map_Ps"; | |
72 | static const std::string RMATexture = "map_Ps"; | |
69 | 73 | |
70 | 74 | // texture option specific token |
71 | 75 | static const std::string BlendUOption = "-blendu"; |
177 | 181 | case 'e': // New material |
178 | 182 | createMaterial(); |
179 | 183 | break; |
184 | case 'o': // Norm texture | |
185 | --m_DataIt; | |
186 | getTexture(); | |
187 | break; | |
180 | 188 | } |
181 | 189 | m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine); |
182 | 190 | } break; |
191 | ||
192 | case 'P': | |
193 | { | |
194 | ++m_DataIt; | |
195 | switch(*m_DataIt) | |
196 | { | |
197 | case 'r': | |
198 | ++m_DataIt; | |
199 | getFloatValue(m_pModel->m_pCurrentMaterial->roughness); | |
200 | break; | |
201 | case 'm': | |
202 | ++m_DataIt; | |
203 | getFloatValue(m_pModel->m_pCurrentMaterial->metallic); | |
204 | break; | |
205 | case 's': | |
206 | ++m_DataIt; | |
207 | getColorRGBA(&m_pModel->m_pCurrentMaterial->sheen); | |
208 | break; | |
209 | case 'c': | |
210 | ++m_DataIt; | |
211 | if (*m_DataIt == 'r') { | |
212 | ++m_DataIt; | |
213 | getFloatValue(m_pModel->m_pCurrentMaterial->clearcoat_roughness); | |
214 | } else { | |
215 | getFloatValue(m_pModel->m_pCurrentMaterial->clearcoat_thickness); | |
216 | } | |
217 | break; | |
218 | } | |
219 | m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine); | |
220 | } | |
221 | break; | |
183 | 222 | |
184 | 223 | case 'm': // Texture |
185 | 224 | case 'b': // quick'n'dirty - for 'bump' sections |
193 | 232 | { |
194 | 233 | m_DataIt = getNextToken<DataArrayIt>(m_DataIt, m_DataItEnd); |
195 | 234 | getIlluminationModel(m_pModel->m_pCurrentMaterial->illumination_model); |
235 | m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine); | |
236 | } break; | |
237 | ||
238 | case 'a': // Anisotropy | |
239 | { | |
240 | getFloatValue(m_pModel->m_pCurrentMaterial->anisotropy); | |
196 | 241 | m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine); |
197 | 242 | } break; |
198 | 243 | |
333 | 378 | // Specularity scaling (glossiness) |
334 | 379 | out = &m_pModel->m_pCurrentMaterial->textureSpecularity; |
335 | 380 | clampIndex = ObjFile::Material::TextureSpecularityType; |
381 | } else if ( !ASSIMP_strincmp( pPtr, RoughnessTexture.c_str(), static_cast<unsigned int>(RoughnessTexture.size()))) { | |
382 | // PBR Roughness texture | |
383 | out = & m_pModel->m_pCurrentMaterial->textureRoughness; | |
384 | clampIndex = ObjFile::Material::TextureRoughnessType; | |
385 | } else if ( !ASSIMP_strincmp( pPtr, MetallicTexture.c_str(), static_cast<unsigned int>(MetallicTexture.size()))) { | |
386 | // PBR Metallic texture | |
387 | out = & m_pModel->m_pCurrentMaterial->textureMetallic; | |
388 | clampIndex = ObjFile::Material::TextureMetallicType; | |
389 | } else if (!ASSIMP_strincmp( pPtr, SheenTexture.c_str(), static_cast<unsigned int>(SheenTexture.size()))) { | |
390 | // PBR Sheen (reflectance) texture | |
391 | out = & m_pModel->m_pCurrentMaterial->textureSheen; | |
392 | clampIndex = ObjFile::Material::TextureSheenType; | |
393 | } else if (!ASSIMP_strincmp( pPtr, RMATexture.c_str(), static_cast<unsigned int>(RMATexture.size()))) { | |
394 | // PBR Rough/Metal/AO texture | |
395 | out = & m_pModel->m_pCurrentMaterial->textureRMA; | |
396 | clampIndex = ObjFile::Material::TextureRMAType; | |
336 | 397 | } else { |
337 | 398 | ASSIMP_LOG_ERROR("OBJ/MTL: Encountered unknown texture type"); |
338 | 399 | return; |
556 | 556 | if (ASSIMP_BUILD_IFC_IMPORTER) |
557 | 557 | if (MSVC) |
558 | 558 | set_source_files_properties(Importer/IFC/IFCReaderGen1_2x3.cpp Importer/IFC/IFCReaderGen2_2x3.cpp PROPERTIES COMPILE_FLAGS "/bigobj") |
559 | elseif(CMAKE_COMPILER_IS_MINGW) | |
559 | elseif(MINGW) | |
560 | 560 | set_source_files_properties(Importer/IFC/IFCReaderGen1_2x3.cpp Importer/IFC/IFCReaderGen2_2x3.cpp PROPERTIES COMPILE_FLAGS "-O2 -Wa,-mbig-obj") |
561 | 561 | endif() |
562 | 562 | endif () |
861 | 861 | # optimizations that take up extra space. Given that the issue is a string table overflowing, -Os seemed appropriate |
862 | 862 | # Also, I'm not positive if both link & compile flags are needed, but this hopefully ensures that the issue should not |
863 | 863 | # recur for edge cases such as static builds. |
864 | if ((CMAKE_COMPILER_IS_MINGW) AND (CMAKE_BUILD_TYPE MATCHES Debug)) | |
864 | if ((MINGW) AND (CMAKE_BUILD_TYPE MATCHES Debug)) | |
865 | 865 | message("-- Applying MinGW StepFileGen1.cpp Debug Workaround") |
866 | 866 | SET_SOURCE_FILES_PROPERTIES(Importer/StepFile/StepFileGen1.cpp PROPERTIES COMPILE_FLAGS -Os ) |
867 | 867 | SET_SOURCE_FILES_PROPERTIES(Importer/StepFile/StepFileGen1.cpp PROPERTIES LINK_FLAGS -Os ) |
64 | 64 | // Constructor to be privately used by Importer |
65 | 65 | BaseImporter::BaseImporter() AI_NO_EXCEPT |
66 | 66 | : m_progress() { |
67 | // empty | |
67 | 68 | } |
68 | 69 | |
69 | 70 | // ------------------------------------------------------------------------------------------------ |
371 | 372 | |
372 | 373 | // UTF 16 BE with BOM |
373 | 374 | if (*((uint16_t *)&data.front()) == 0xFFFE) { |
374 | ||
375 | // Check to ensure no overflow can happen | |
376 | if(data.size() % 2 != 0) { | |
377 | return; | |
378 | } | |
375 | 379 | // swap the endianness .. |
376 | 380 | for (uint16_t *p = (uint16_t *)&data.front(), *end = (uint16_t *)&data.back(); p <= end; ++p) { |
377 | 381 | ByteSwap::Swap2(p); |
2 | 2 | ---------------------------------------------------------------------- |
3 | 3 | |
4 | 4 | Copyright (c) 2006-2021, assimp team |
5 | ||
6 | 5 | |
7 | 6 | All rights reserved. |
8 | 7 | |
47 | 46 | |
48 | 47 | #include <assimp/ProgressHandler.hpp> |
49 | 48 | |
50 | namespace Assimp { | |
49 | namespace Assimp { | |
51 | 50 | |
52 | 51 | // ------------------------------------------------------------------------------------ |
53 | /** @brief Internal default implementation of the #ProgressHandler interface. */ | |
52 | /** | |
53 | * @brief Internal default implementation of the #ProgressHandler interface. | |
54 | */ | |
54 | 55 | class DefaultProgressHandler : public ProgressHandler { |
55 | ||
56 | virtual bool Update(float /*percentage*/) { | |
56 | public: | |
57 | /// @brief Ignores the update callback. | |
58 | bool Update(float) override { | |
57 | 59 | return false; |
58 | 60 | } |
61 | }; | |
59 | 62 | |
60 | ||
61 | }; // !class DefaultProgressHandler | |
62 | 63 | } // Namespace Assimp |
63 | 64 | |
64 | #endif | |
65 | #endif // INCLUDED_AI_DEFAULTPROGRESSHANDLER_H |
328 | 328 | |
329 | 329 | // ------------------------------------------------------------------------------------------------ |
330 | 330 | // Supplies a custom progress handler to get regular callbacks during importing |
331 | void Importer::SetProgressHandler ( ProgressHandler* pHandler ) { | |
331 | void Importer::SetProgressHandler(ProgressHandler* pHandler) { | |
332 | 332 | ai_assert(nullptr != pimpl); |
333 | 333 | |
334 | 334 | ASSIMP_BEGIN_EXCEPTION_REGION(); |
1615 | 1615 | if( CMAKE_GENERATOR MATCHES "Ninja" AND CMAKE_HOST_WIN32 ) |
1616 | 1616 | # it is a bad hack after all |
1617 | 1617 | # CMake generates Ninja makefiles with UNIX paths only if it thinks that we are going to build with MinGW |
1618 | set( MINGW TRUE ) # tell CMake that we are MinGW | |
1618 | 1619 | set( CMAKE_COMPILER_IS_MINGW TRUE ) # tell CMake that we are MinGW |
1619 | 1620 | set( CMAKE_CROSSCOMPILING TRUE ) # stop recursion |
1620 | 1621 | enable_language( C ) |
73 | 73 | public: |
74 | 74 | /// @brief Virtual destructor. |
75 | 75 | virtual ~ProgressHandler () { |
76 | // empty | |
76 | 77 | } |
77 | 78 | |
78 | 79 | // ------------------------------------------------------------------- |
988 | 988 | // Roughness factor. 0.0 = Perfectly Smooth, 1.0 = Completely Rough |
989 | 989 | #define AI_MATKEY_ROUGHNESS_FACTOR "$mat.roughnessFactor", 0, 0 |
990 | 990 | #define AI_MATKEY_ROUGHNESS_TEXTURE aiTextureType_DIFFUSE_ROUGHNESS, 0 |
991 | // Anisotropy factor. 0.0 = isotropic, 1.0 = anisotropy along tangent direction, | |
992 | // -1.0 = anisotropy along bitangent direction | |
993 | #define AI_MATKEY_ANISOTROPY_FACTOR "$mat.anisotropyFactor", 0, 0 | |
991 | 994 | |
992 | 995 | // Specular/Glossiness Workflow |
993 | 996 | // --------------------------- |
3 | 3 | --------------------------------------------------------------------------- |
4 | 4 | |
5 | 5 | Copyright (c) 2006-2021, assimp team |
6 | ||
7 | ||
8 | 6 | |
9 | 7 | All rights reserved. |
10 | 8 | |
46 | 44 | |
47 | 45 | #include "Main.h" |
48 | 46 | |
47 | #include <assimp/ProgressHandler.hpp> | |
48 | #include <iostream> | |
49 | ||
50 | class ConsoleProgressHandler : public ProgressHandler { | |
51 | public: | |
52 | ConsoleProgressHandler() : | |
53 | ProgressHandler() { | |
54 | // empty | |
55 | } | |
56 | ||
57 | ~ConsoleProgressHandler() override { | |
58 | // empty | |
59 | } | |
60 | ||
61 | bool Update(float percentage) override { | |
62 | std::cout << percentage * 100.0f << " %\n"; | |
63 | return true; | |
64 | } | |
65 | }; | |
49 | 66 | const char* AICMD_MSG_ABOUT = |
50 | 67 | "------------------------------------------------------ \n" |
51 | 68 | "Open Asset Import Library (\"Assimp\", https://github.com/assimp/assimp) \n" |
72 | 89 | "\n Use \'assimp <verb> --help\' for detailed help on a command.\n" |
73 | 90 | ; |
74 | 91 | |
75 | /*extern*/ Assimp::Importer* globalImporter = NULL; | |
92 | /*extern*/ Assimp::Importer* globalImporter = nullptr; | |
76 | 93 | |
77 | 94 | #ifndef ASSIMP_BUILD_NO_EXPORT |
78 | /*extern*/ Assimp::Exporter* globalExporter = NULL; | |
95 | /*extern*/ Assimp::Exporter* globalExporter = nullptr; | |
79 | 96 | #endif |
80 | 97 | |
81 | 98 | // ------------------------------------------------------------------------------ |
285 | 302 | |
286 | 303 | // do the actual import, measure time |
287 | 304 | const clock_t first = clock(); |
305 | ConsoleProgressHandler *ph = new ConsoleProgressHandler; | |
306 | globalImporter->SetProgressHandler(ph); | |
307 | ||
288 | 308 | const aiScene* scene = globalImporter->ReadFile(path,imp.ppFlags); |
289 | 309 | |
290 | 310 | if (imp.showLog) { |
304 | 324 | if (imp.log) { |
305 | 325 | FreeLogStreams(); |
306 | 326 | } |
327 | globalImporter->SetProgressHandler(nullptr); | |
328 | delete ph; | |
329 | ||
307 | 330 | return scene; |
308 | 331 | } |
309 | 332 |