/src/assimp/code/AssetLib/3MF/D3MFExporter.cpp
Line | Count | Source |
1 | | /* |
2 | | Open Asset Import Library (assimp) |
3 | | ---------------------------------------------------------------------- |
4 | | |
5 | | Copyright (c) 2006-2026, assimp team |
6 | | |
7 | | All rights reserved. |
8 | | |
9 | | Redistribution and use of this software in source and binary forms, |
10 | | with or without modification, are permitted provided that the |
11 | | following conditions are met: |
12 | | |
13 | | * Redistributions of source code must retain the above |
14 | | copyright notice, this list of conditions and the |
15 | | following disclaimer. |
16 | | |
17 | | * Redistributions in binary form must reproduce the above |
18 | | copyright notice, this list of conditions and the |
19 | | following disclaimer in the documentation and/or other |
20 | | materials provided with the distribution. |
21 | | |
22 | | * Neither the name of the assimp team, nor the names of its |
23 | | contributors may be used to endorse or promote products |
24 | | derived from this software without specific prior |
25 | | written permission of the assimp team. |
26 | | |
27 | | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
28 | | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
29 | | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
30 | | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
31 | | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
32 | | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
33 | | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
34 | | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
35 | | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
36 | | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
37 | | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
38 | | |
39 | | ---------------------------------------------------------------------- |
40 | | */ |
41 | | #ifndef ASSIMP_BUILD_NO_EXPORT |
42 | | #ifndef ASSIMP_BUILD_NO_3MF_EXPORTER |
43 | | |
44 | | #include "D3MFExporter.h" |
45 | | |
46 | | #include <assimp/Exceptional.h> |
47 | | #include <assimp/StringUtils.h> |
48 | | #include <assimp/scene.h> |
49 | | #include <assimp/DefaultLogger.hpp> |
50 | | #include <assimp/Exporter.hpp> |
51 | | #include <assimp/IOSystem.hpp> |
52 | | |
53 | | #include "3MFXmlTags.h" |
54 | | #include "D3MFOpcPackage.h" |
55 | | |
56 | | #ifdef ASSIMP_USE_HUNTER |
57 | | #include <zip/zip.h> |
58 | | #else |
59 | | #include <contrib/zip/src/zip.h> |
60 | | #endif |
61 | | |
62 | | namespace Assimp { |
63 | | |
64 | 1.81k | void ExportScene3MF(const char *pFile, IOSystem *pIOSystem, const aiScene *pScene, const ExportProperties * /*pProperties*/) { |
65 | 1.81k | if (nullptr == pIOSystem) { |
66 | 0 | throw DeadlyExportError("Could not export 3MP archive: " + std::string(pFile)); |
67 | 0 | } |
68 | 1.81k | D3MF::D3MFExporter myExporter(pFile, pScene); |
69 | 1.81k | if (myExporter.validate()) { |
70 | 1.81k | if (pIOSystem->Exists(pFile)) { |
71 | 0 | if (!pIOSystem->DeleteFile(pFile)) { |
72 | 0 | throw DeadlyExportError("File exists, cannot override : " + std::string(pFile)); |
73 | 0 | } |
74 | 0 | } |
75 | 1.81k | bool ok = myExporter.exportArchive(pFile); |
76 | 1.81k | if (!ok) { |
77 | 0 | throw DeadlyExportError("Could not export 3MP archive: " + std::string(pFile)); |
78 | 0 | } |
79 | 1.81k | } |
80 | 1.81k | } |
81 | | |
82 | | namespace D3MF { |
83 | | |
84 | | D3MFExporter::D3MFExporter(const char *pFile, const aiScene *pScene) : |
85 | 1.81k | mArchiveName(pFile), mScene(pScene) { |
86 | | // empty |
87 | 1.81k | } |
88 | | |
89 | 1.81k | D3MFExporter::~D3MFExporter() { |
90 | 3.63k | for (size_t i = 0; i < mRelations.size(); ++i) { |
91 | 1.81k | delete mRelations[i]; |
92 | 1.81k | } |
93 | 1.81k | mRelations.clear(); |
94 | 1.81k | } |
95 | | |
96 | 1.81k | bool D3MFExporter::validate() const { |
97 | 1.81k | if (mArchiveName.empty()) { |
98 | 0 | return false; |
99 | 0 | } |
100 | | |
101 | 1.81k | if (nullptr == mScene) { |
102 | 0 | return false; |
103 | 0 | } |
104 | | |
105 | 1.81k | return true; |
106 | 1.81k | } |
107 | | |
108 | 1.81k | bool D3MFExporter::exportArchive(const char *file) { |
109 | 1.81k | bool ok(true); |
110 | | |
111 | 1.81k | mZipArchive = zip_open(file, ZIP_DEFAULT_COMPRESSION_LEVEL, 'w'); |
112 | 1.81k | if (nullptr == mZipArchive) { |
113 | 0 | return false; |
114 | 0 | } |
115 | | |
116 | 1.81k | ok |= exportContentTypes(); |
117 | 1.81k | ok |= export3DModel(); |
118 | 1.81k | ok |= exportRelations(); |
119 | | |
120 | 1.81k | zip_close(mZipArchive); |
121 | 1.81k | mZipArchive = nullptr; |
122 | | |
123 | 1.81k | return ok; |
124 | 1.81k | } |
125 | | |
126 | 1.81k | bool D3MFExporter::exportContentTypes() { |
127 | 1.81k | mContentOutput.clear(); |
128 | | |
129 | 1.81k | mContentOutput << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"; |
130 | 1.81k | mContentOutput << std::endl; |
131 | 1.81k | mContentOutput << "<Types xmlns = \"http://schemas.openxmlformats.org/package/2006/content-types\">"; |
132 | 1.81k | mContentOutput << std::endl; |
133 | 1.81k | mContentOutput << "<Default Extension = \"rels\" ContentType = \"application/vnd.openxmlformats-package.relationships+xml\" />"; |
134 | 1.81k | mContentOutput << std::endl; |
135 | 1.81k | mContentOutput << "<Default Extension = \"model\" ContentType = \"application/vnd.ms-package.3dmanufacturing-3dmodel+xml\" />"; |
136 | 1.81k | mContentOutput << std::endl; |
137 | 1.81k | mContentOutput << "</Types>"; |
138 | 1.81k | mContentOutput << std::endl; |
139 | 1.81k | zipContentType(XmlTag::CONTENT_TYPES_ARCHIVE); |
140 | | |
141 | 1.81k | return true; |
142 | 1.81k | } |
143 | | |
144 | 1.81k | bool D3MFExporter::exportRelations() { |
145 | 1.81k | mRelOutput.clear(); |
146 | | |
147 | 1.81k | mRelOutput << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"; |
148 | 1.81k | mRelOutput << std::endl; |
149 | 1.81k | mRelOutput << "<Relationships xmlns=\"http://schemas.openxmlformats.org/package/2006/relationships\">"; |
150 | | |
151 | 3.63k | for (size_t i = 0; i < mRelations.size(); ++i) { |
152 | 1.81k | if (mRelations[i]->target[0] == '/') { |
153 | 1.81k | mRelOutput << "<Relationship Target=\"" << mRelations[i]->target << "\" "; |
154 | 1.81k | } else { |
155 | 0 | mRelOutput << "<Relationship Target=\"/" << mRelations[i]->target << "\" "; |
156 | 0 | } |
157 | 1.81k | mRelOutput << "Id=\"" << mRelations[i]->id << "\" "; |
158 | 1.81k | mRelOutput << "Type=\"" << mRelations[i]->type << "\" />"; |
159 | 1.81k | mRelOutput << std::endl; |
160 | 1.81k | } |
161 | 1.81k | mRelOutput << "</Relationships>"; |
162 | 1.81k | mRelOutput << std::endl; |
163 | | |
164 | 1.81k | zipRelInfo("_rels", ".rels"); |
165 | 1.81k | mRelOutput.flush(); |
166 | | |
167 | 1.81k | return true; |
168 | 1.81k | } |
169 | | |
170 | 1.81k | bool D3MFExporter::export3DModel() { |
171 | 1.81k | mModelOutput.clear(); |
172 | | |
173 | 1.81k | writeHeader(); |
174 | 1.81k | mModelOutput << "<" << XmlTag::model << " " << XmlTag::model_unit << "=\"millimeter\"" |
175 | 1.81k | << " xmlns=\"http://schemas.microsoft.com/3dmanufacturing/core/2015/02\">" |
176 | 1.81k | << std::endl; |
177 | 1.81k | mModelOutput << "<" << XmlTag::resources << ">"; |
178 | 1.81k | mModelOutput << std::endl; |
179 | | |
180 | 1.81k | writeMetaData(); |
181 | | |
182 | 1.81k | writeBaseMaterials(); |
183 | | |
184 | 1.81k | writeObjects(); |
185 | | |
186 | 1.81k | mModelOutput << "</" << XmlTag::resources << ">"; |
187 | 1.81k | mModelOutput << std::endl; |
188 | 1.81k | writeBuild(); |
189 | | |
190 | 1.81k | mModelOutput << "</" << XmlTag::model << ">\n"; |
191 | | |
192 | 1.81k | OpcPackageRelationship *info = new OpcPackageRelationship; |
193 | 1.81k | info->id = "rel0"; |
194 | 1.81k | info->target = "/3D/3DModel.model"; |
195 | 1.81k | info->type = XmlTag::PACKAGE_START_PART_RELATIONSHIP_TYPE; |
196 | 1.81k | mRelations.push_back(info); |
197 | | |
198 | 1.81k | zipModel("3D", "3DModel.model"); |
199 | 1.81k | mModelOutput.flush(); |
200 | | |
201 | 1.81k | return true; |
202 | 1.81k | } |
203 | | |
204 | 1.81k | void D3MFExporter::writeHeader() { |
205 | 1.81k | mModelOutput << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"; |
206 | 1.81k | mModelOutput << std::endl; |
207 | 1.81k | } |
208 | | |
209 | 1.81k | void D3MFExporter::writeMetaData() { |
210 | 1.81k | if (nullptr == mScene->mMetaData) { |
211 | 0 | return; |
212 | 0 | } |
213 | | |
214 | 1.81k | const unsigned int numMetaEntries(mScene->mMetaData->mNumProperties); |
215 | 1.81k | if (0 == numMetaEntries) { |
216 | 0 | return; |
217 | 0 | } |
218 | | |
219 | 1.81k | const aiString *key = nullptr; |
220 | 1.81k | const aiMetadataEntry *entry(nullptr); |
221 | 11.8k | for (size_t i = 0; i < numMetaEntries; ++i) { |
222 | 10.0k | mScene->mMetaData->Get(i, key, entry); |
223 | 10.0k | std::string k(key->C_Str()); |
224 | 10.0k | aiString value; |
225 | 10.0k | mScene->mMetaData->Get(k, value); |
226 | 10.0k | mModelOutput << "<" << XmlTag::meta << " " << XmlTag::meta_name << "=\"" << key->C_Str() << "\">"; |
227 | 10.0k | mModelOutput << value.C_Str(); |
228 | 10.0k | mModelOutput << "</" << XmlTag::meta << ">" << std::endl; |
229 | 10.0k | } |
230 | 1.81k | } |
231 | | |
232 | 1.81k | void D3MFExporter::writeBaseMaterials() { |
233 | 1.81k | mModelOutput << "<basematerials id=\"1\">\n"; |
234 | 1.81k | std::string strName, hexDiffuseColor, tmp; |
235 | 3.82k | for (size_t i = 0; i < mScene->mNumMaterials; ++i) { |
236 | 2.00k | aiMaterial *mat = mScene->mMaterials[i]; |
237 | 2.00k | aiString name; |
238 | 2.00k | if (mat->Get(AI_MATKEY_NAME, name) != aiReturn_SUCCESS) { |
239 | 359 | strName = "basemat_" + ai_to_string(i); |
240 | 1.65k | } else { |
241 | 1.65k | strName = name.C_Str(); |
242 | 1.65k | } |
243 | 2.00k | aiColor4D color; |
244 | 2.00k | if (mat->Get(AI_MATKEY_COLOR_DIFFUSE, color) == aiReturn_SUCCESS) { |
245 | 1.80k | hexDiffuseColor.clear(); |
246 | 1.80k | tmp.clear(); |
247 | | // rgbs % |
248 | 1.80k | if (color.r <= 1 && color.g <= 1 && color.b <= 1 && color.a <= 1) { |
249 | | |
250 | 1.77k | hexDiffuseColor = ai_rgba2hex( |
251 | 1.77k | (int)(((ai_real)color.r) * 255), |
252 | 1.77k | (int)(((ai_real)color.g) * 255), |
253 | 1.77k | (int)(((ai_real)color.b) * 255), |
254 | 1.77k | (int)(((ai_real)color.a) * 255), |
255 | 1.77k | true); |
256 | | |
257 | 1.77k | } else { |
258 | 25 | hexDiffuseColor = "#"; |
259 | 25 | tmp = ai_decimal_to_hexa((ai_real)color.r); |
260 | 25 | hexDiffuseColor += tmp; |
261 | 25 | tmp = ai_decimal_to_hexa((ai_real)color.g); |
262 | 25 | hexDiffuseColor += tmp; |
263 | 25 | tmp = ai_decimal_to_hexa((ai_real)color.b); |
264 | 25 | hexDiffuseColor += tmp; |
265 | 25 | tmp = ai_decimal_to_hexa((ai_real)color.a); |
266 | 25 | hexDiffuseColor += tmp; |
267 | 25 | } |
268 | 1.80k | } else { |
269 | 205 | hexDiffuseColor = "#FFFFFFFF"; |
270 | 205 | } |
271 | | |
272 | 2.00k | mModelOutput << "<base name=\"" + strName + "\" " + " displaycolor=\"" + hexDiffuseColor + "\" />\n"; |
273 | 2.00k | } |
274 | 1.81k | mModelOutput << "</basematerials>\n"; |
275 | 1.81k | } |
276 | | |
277 | 1.81k | void D3MFExporter::writeObjects() { |
278 | 1.81k | if (nullptr == mScene->mRootNode) { |
279 | 0 | return; |
280 | 0 | } |
281 | | |
282 | 1.81k | aiNode *root = mScene->mRootNode; |
283 | 91.4k | for (unsigned int i = 0; i < root->mNumChildren; ++i) { |
284 | 89.6k | aiNode *currentNode(root->mChildren[i]); |
285 | 89.6k | if (nullptr == currentNode) { |
286 | 0 | continue; |
287 | 0 | } |
288 | 89.6k | mModelOutput << "<" << XmlTag::object << " id=\"" << i + 2 << "\" type=\"model\">"; |
289 | 89.6k | mModelOutput << std::endl; |
290 | 108k | for (unsigned int j = 0; j < currentNode->mNumMeshes; ++j) { |
291 | 18.9k | aiMesh *currentMesh = mScene->mMeshes[currentNode->mMeshes[j]]; |
292 | 18.9k | if (nullptr == currentMesh) { |
293 | 0 | continue; |
294 | 0 | } |
295 | 18.9k | writeMesh(currentMesh); |
296 | 18.9k | } |
297 | 89.6k | mBuildItems.push_back(i); |
298 | | |
299 | 89.6k | mModelOutput << "</" << XmlTag::object << ">"; |
300 | 89.6k | mModelOutput << std::endl; |
301 | 89.6k | } |
302 | 1.81k | } |
303 | | |
304 | 18.9k | void D3MFExporter::writeMesh(aiMesh *mesh) { |
305 | 18.9k | if (nullptr == mesh) { |
306 | 0 | return; |
307 | 0 | } |
308 | | |
309 | 18.9k | mModelOutput << "<" |
310 | 18.9k | << XmlTag::mesh |
311 | 18.9k | << ">" << "\n"; |
312 | 18.9k | mModelOutput << "<" |
313 | 18.9k | << XmlTag::vertices |
314 | 18.9k | << ">" << "\n"; |
315 | 293k | for (unsigned int i = 0; i < mesh->mNumVertices; ++i) { |
316 | 274k | writeVertex(mesh->mVertices[i]); |
317 | 274k | } |
318 | 18.9k | mModelOutput << "</" |
319 | 18.9k | << XmlTag::vertices << ">" |
320 | 18.9k | << "\n"; |
321 | | |
322 | 18.9k | const unsigned int matIdx(mesh->mMaterialIndex); |
323 | | |
324 | 18.9k | writeFaces(mesh, matIdx); |
325 | | |
326 | 18.9k | mModelOutput << "</" |
327 | 18.9k | << XmlTag::mesh << ">" |
328 | 18.9k | << "\n"; |
329 | 18.9k | } |
330 | | |
331 | 274k | void D3MFExporter::writeVertex(const aiVector3D &pos) { |
332 | 274k | mModelOutput << "<" << XmlTag::vertex << " x=\"" << pos.x << "\" y=\"" << pos.y << "\" z=\"" << pos.z << "\" />"; |
333 | 274k | mModelOutput << std::endl; |
334 | 274k | } |
335 | | |
336 | 18.9k | void D3MFExporter::writeFaces(aiMesh *mesh, unsigned int matIdx) { |
337 | 18.9k | if (nullptr == mesh) { |
338 | 0 | return; |
339 | 0 | } |
340 | | |
341 | 18.9k | if (!mesh->HasFaces()) { |
342 | 0 | return; |
343 | 0 | } |
344 | 18.9k | mModelOutput << "<" |
345 | 18.9k | << XmlTag::triangles << ">" |
346 | 18.9k | << "\n"; |
347 | 303k | for (unsigned int i = 0; i < mesh->mNumFaces; ++i) { |
348 | 284k | aiFace ¤tFace = mesh->mFaces[i]; |
349 | | // A 3MF triangle has exactly three vertices; points and lines have no |
350 | | // representation in it, and reading three indices from them overruns. |
351 | 284k | if (currentFace.mNumIndices != 3) { |
352 | 7.88k | continue; |
353 | 7.88k | } |
354 | 276k | mModelOutput << "<" << XmlTag::triangle << " v1=\"" << currentFace.mIndices[0] << "\" v2=\"" |
355 | 276k | << currentFace.mIndices[1] << "\" v3=\"" << currentFace.mIndices[2] |
356 | 276k | << "\" pid=\"1\" p1=\"" + ai_to_string(matIdx) + "\" />"; |
357 | 276k | mModelOutput << "\n"; |
358 | 276k | } |
359 | 18.9k | mModelOutput << "</" |
360 | 18.9k | << XmlTag::triangles |
361 | 18.9k | << ">"; |
362 | 18.9k | mModelOutput << "\n"; |
363 | 18.9k | } |
364 | | |
365 | 1.81k | void D3MFExporter::writeBuild() { |
366 | 1.81k | mModelOutput << "<" |
367 | 1.81k | << XmlTag::build |
368 | 1.81k | << ">" |
369 | 1.81k | << "\n"; |
370 | | |
371 | 91.4k | for (size_t i = 0; i < mBuildItems.size(); ++i) { |
372 | 89.6k | mModelOutput << "<" << XmlTag::item << " objectid=\"" << i + 2 << "\"/>"; |
373 | 89.6k | mModelOutput << "\n"; |
374 | 89.6k | } |
375 | 1.81k | mModelOutput << "</" << XmlTag::build << ">"; |
376 | 1.81k | mModelOutput << "\n"; |
377 | 1.81k | } |
378 | | |
379 | 1.81k | void D3MFExporter::zipContentType(const std::string &filename) { |
380 | 1.81k | addFileInZip(filename, mContentOutput.str()); |
381 | 1.81k | } |
382 | | |
383 | 1.81k | void D3MFExporter::zipModel(const std::string &folder, const std::string &modelName) { |
384 | 1.81k | const std::string entry = folder + "/" + modelName; |
385 | 1.81k | addFileInZip(entry, mModelOutput.str()); |
386 | 1.81k | } |
387 | | |
388 | 1.81k | void D3MFExporter::zipRelInfo(const std::string &folder, const std::string &relName) { |
389 | 1.81k | const std::string entry = folder + "/" + relName; |
390 | 1.81k | addFileInZip(entry, mRelOutput.str()); |
391 | 1.81k | } |
392 | | |
393 | 5.45k | void D3MFExporter::addFileInZip(const std::string& entry, const std::string& content) { |
394 | 5.45k | if (nullptr == mZipArchive) { |
395 | 0 | throw DeadlyExportError("3MF-Export: Zip archive not valid, nullptr."); |
396 | 0 | } |
397 | | |
398 | 5.45k | zip_entry_open(mZipArchive, entry.c_str()); |
399 | 5.45k | zip_entry_write(mZipArchive, content.c_str(), content.size()); |
400 | 5.45k | zip_entry_close(mZipArchive); |
401 | 5.45k | } |
402 | | |
403 | | } // Namespace D3MF |
404 | | } // Namespace Assimp |
405 | | |
406 | | #endif // ASSIMP_BUILD_NO_3MF_EXPORTER |
407 | | #endif // ASSIMP_BUILD_NO_EXPORT |