1
/***************************************************************************
2
* Copyright (c) 2017 Ian Rees <ian.rees@gmail.com> *
4
* This file is part of the FreeCAD CAx development system. *
6
* This library is free software; you can redistribute it and/or *
7
* modify it under the terms of the GNU Library General Public *
8
* License as published by the Free Software Foundation; either *
9
* version 2 of the License, or (at your option) any later version. *
11
* This library 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 Library General Public License for more details. *
16
* You should have received a copy of the GNU Library General Public *
17
* License along with this library; see the file COPYING.LIB. If not, *
18
* write to the Free Software Foundation, Inc., 59 Temple Place, *
19
* Suite 330, Boston, MA 02111-1307, USA *
21
***************************************************************************/
23
#include "PreCompiled.h"
26
#include <boost/algorithm/string/replace.hpp>
27
#include <boost/core/ignore_unused.hpp>
31
#include <App/Application.h>
32
#include <App/ComplexGeoData.h>
33
#include <App/ComplexGeoDataPy.h>
34
#include <App/DocumentObject.h>
35
#include <Base/Exception.h>
36
#include <Base/FileInfo.h>
37
#include <Base/Interpreter.h>
38
#include <Base/Sequencer.h>
39
#include <Base/Stream.h>
40
#include <Base/Tools.h>
41
#include "Core/Iterator.h"
42
#include "Core/IO/Writer3MF.h"
43
#include <zipios++/zipoutputstream.h>
49
using namespace MeshCore;
51
static std::vector<std::string> expandSubObjectNames(
52
const App::DocumentObject* obj,
53
std::map<const App::DocumentObject*, std::vector<std::string>>& subObjectNameCache,
56
if (!App::GetApplication().checkLinkDepth(depth)) {
60
auto subs = obj->getSubObjects();
62
subs.emplace_back("");
66
std::vector<std::string> res;
67
for (auto& sub : subs) {
68
int vis = sub.empty() ? 1 : obj->isElementVisible(sub.c_str());
72
auto sobj = obj->getSubObject(sub.c_str());
73
if (!sobj || (vis < 0 && !sobj->Visibility.getValue())) {
76
auto linked = sobj->getLinkedObject(true);
77
auto it = subObjectNameCache.find(linked);
78
if (it == subObjectNameCache.end()) {
79
it = subObjectNameCache
80
.emplace(linked, expandSubObjectNames(linked, subObjectNameCache, depth + 1))
83
for (auto& ssub : it->second) {
84
res.push_back(sub + ssub);
90
Exporter::Exporter() = default;
93
std::string Exporter::xmlEscape(const std::string& input)
95
std::string out(input);
96
boost::replace_all(out, "&", "&");
97
boost::replace_all(out, "\"", """);
98
boost::replace_all(out, "'", "'");
99
boost::replace_all(out, "<", "<");
100
boost::replace_all(out, ">", ">");
104
int Exporter::addObject(App::DocumentObject* obj, float tol)
107
for (std::string& sub : expandSubObjectNames(obj, subObjectNameCache, 0)) {
108
Base::Matrix4D matrix;
109
auto sobj = obj->getSubObject(sub.c_str(), nullptr, &matrix);
110
auto linked = sobj->getLinkedObject(true, &matrix, false);
111
auto it = meshCache.find(linked);
112
if (it == meshCache.end()) {
113
if (linked->isDerivedFrom(Mesh::Feature::getClassTypeId())) {
114
it = meshCache.emplace(linked, static_cast<Mesh::Feature*>(linked)->Mesh.getValue())
116
it->second.setTransform(matrix);
119
Base::PyGILStateLocker lock;
120
PyObject* pyobj = nullptr;
121
linked->getSubObject("", &pyobj, nullptr, false);
125
if (PyObject_TypeCheck(pyobj, &Data::ComplexGeoDataPy::Type)) {
126
std::vector<Base::Vector3d> aPoints;
127
std::vector<Data::ComplexGeoData::Facet> aTopo;
129
static_cast<Data::ComplexGeoDataPy*>(pyobj)->getComplexGeoDataPtr();
130
geoData->getFaces(aPoints, aTopo, tol);
131
it = meshCache.emplace(linked, MeshObject()).first;
132
it->second.setFacets(aTopo, aPoints);
133
it->second.setTransform(matrix);
138
else if (it->second.getTransform() != matrix) {
139
it->second.setTransform(matrix);
143
if (it != meshCache.end()) {
144
if (addMesh(sobj->Label.getValue(), it->second)) {
152
void Exporter::throwIfNoPermission(const std::string& filename)
154
// ask for write permission
155
Base::FileInfo fi(filename);
156
Base::FileInfo di(fi.dirPath());
157
if ((fi.exists() && !fi.isWritable()) || !di.exists() || !di.isWritable()) {
158
throw Base::FileException("No write permission for file", fi);
162
// ----------------------------------------------------------------------------
164
MergeExporter::MergeExporter(std::string fileName, MeshIO::Format)
165
: fName(std::move(fileName))
168
MergeExporter::~MergeExporter()
173
void MergeExporter::write()
175
// if we have more than one segment set the 'save' flag
176
if (mergingMesh.countSegments() > 1) {
177
for (unsigned long i = 0; i < mergingMesh.countSegments(); ++i) {
178
mergingMesh.getSegment(i).save(true);
183
mergingMesh.save(fName.c_str());
185
catch (const Base::Exception& e) {
186
std::cerr << "Saving mesh failed: " << e.what() << std::endl;
190
bool MergeExporter::addMesh(const char* name, const MeshObject& mesh)
192
auto kernel = mesh.getKernel();
193
kernel.Transform(mesh.getTransform());
194
auto countFacets(mergingMesh.countFacets());
195
if (countFacets == 0) {
196
mergingMesh.setKernel(kernel);
199
mergingMesh.addMesh(kernel);
202
// if the mesh already has persistent segments then use them instead
203
unsigned long numSegm = mesh.countSegments();
204
unsigned long canSave = 0;
205
for (unsigned long i = 0; i < numSegm; i++) {
206
if (mesh.getSegment(i).isSaved()) {
212
for (unsigned long i = 0; i < numSegm; i++) {
213
const Segment& segm = mesh.getSegment(i);
214
if (segm.isSaved()) {
215
std::vector<FacetIndex> indices = segm.getIndices();
216
std::for_each(indices.begin(), indices.end(), [countFacets](FacetIndex& v) {
219
Segment new_segm(&mergingMesh, indices, true);
220
new_segm.setName(segm.getName());
221
mergingMesh.addSegment(new_segm);
226
// now create a segment for the added mesh
227
std::vector<FacetIndex> indices;
228
indices.resize(mergingMesh.countFacets() - countFacets);
229
std::generate(indices.begin(), indices.end(), Base::iotaGen<FacetIndex>(countFacets));
230
Segment segm(&mergingMesh, indices, true);
232
mergingMesh.addSegment(segm);
238
// ----------------------------------------------------------------------------
240
AbstractFormatExtensionPtr GuiExtension3MFProducer::create() const
245
void GuiExtension3MFProducer::initialize()
247
Base::PyGILStateLocker lock;
248
PyObject* module = PyImport_ImportModule("MeshGui");
257
void Extension3MFFactory::addProducer(Extension3MFProducer* ext)
259
producer.emplace_back(ext);
262
void Extension3MFFactory::initialize()
264
std::vector<Extension3MFProducerPtr> ext = producer;
265
for (const auto& it : ext) {
270
std::vector<Extension3MFPtr> Extension3MFFactory::createExtensions()
272
std::vector<Extension3MFPtr> ext;
273
for (const auto& it : producer) {
274
Extension3MFPtr ptr = std::dynamic_pointer_cast<Extension3MF>(it->create());
282
std::vector<Extension3MFProducerPtr> Extension3MFFactory::producer;
284
class Exporter3MF::Private
287
explicit Private(const std::string& filename, std::vector<Extension3MFPtr> ext)
288
: writer3mf(filename)
289
, ext(std::move(ext))
291
MeshCore::Writer3MF writer3mf;
292
std::vector<Extension3MFPtr> ext;
295
Exporter3MF::Exporter3MF(std::string fileName, const std::vector<Extension3MFPtr>& ext)
297
throwIfNoPermission(fileName);
298
d = std::make_unique<Private>(fileName, ext);
301
Exporter3MF::~Exporter3MF()
306
bool Exporter3MF::addMesh(const char* name, const MeshObject& mesh)
308
boost::ignore_unused(name);
309
bool ok = d->writer3mf.AddMesh(mesh.getKernel(), mesh.getTransform());
311
for (const auto& it : d->ext) {
312
d->writer3mf.AddResource(it->addMesh(mesh));
319
void Exporter3MF::setForceModel(bool model)
321
d->writer3mf.SetForceModel(model);
324
void Exporter3MF::write()
329
// ----------------------------------------------------------------------------
331
ExporterAMF::ExporterAMF(std::string fileName,
332
const std::map<std::string, std::string>& meta,
335
// ask for write permission
336
throwIfNoPermission(fileName);
338
Base::FileInfo fi(fileName);
340
auto* zipStreamPtr(new zipios::ZipOutputStream(fi.filePath()));
342
// ISO 52915 specifies that compressed AMF files are zip-compressed and
343
// must contain the AMF XML in an entry with the same name as the
344
// compressed file. It's OK to have other files in the zip too.
345
zipStreamPtr->putNextEntry(zipios::ZipCDirEntry(fi.fileName()));
347
// Default compression seems to work fine.
348
outputStreamPtr = zipStreamPtr;
351
outputStreamPtr = new Base::ofstream(fi, std::ios::out | std::ios::binary);
354
if (outputStreamPtr) {
355
*outputStreamPtr << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
356
<< "<amf unit=\"millimeter\">\n";
357
for (auto const& metaEntry : meta) {
358
*outputStreamPtr << "\t<metadata type=\"" << metaEntry.first << "\">"
359
<< metaEntry.second << "</metadata>\n";
364
ExporterAMF::~ExporterAMF()
369
void ExporterAMF::write()
371
if (outputStreamPtr) {
372
*outputStreamPtr << "\t<constellation id=\"0\">\n";
373
for (auto objId(0); objId < nextObjectIndex; ++objId) {
374
*outputStreamPtr << "\t\t<instance objectid=\"" << objId << "\">\n"
375
<< "\t\t\t<deltax>0</deltax>\n"
376
<< "\t\t\t<deltay>0</deltay>\n"
377
<< "\t\t\t<rz>0</rz>\n"
378
<< "\t\t</instance>\n";
380
*outputStreamPtr << "\t</constellation>\n"
382
delete outputStreamPtr;
386
class ExporterAMF::VertLess
389
bool operator()(const Base::Vector3f& a, const Base::Vector3f& b) const
410
bool ExporterAMF::addMesh(const char* name, const MeshObject& mesh)
412
auto kernel = mesh.getKernel();
413
kernel.Transform(mesh.getTransform());
415
if (!outputStreamPtr || outputStreamPtr->bad()) {
419
auto numFacets(kernel.CountFacets());
421
if (numFacets == 0) {
425
MeshCore::MeshFacetIterator clIter(kernel), clEnd(kernel);
427
Base::SequencerLauncher seq("Saving...", 2 * numFacets + 1);
429
*outputStreamPtr << "\t<object id=\"" << nextObjectIndex << "\">\n";
430
*outputStreamPtr << "\t\t<metadata type=\"name\">" << xmlEscape(name) << "</metadata>\n";
431
*outputStreamPtr << "\t\t<mesh>\n"
432
<< "\t\t\t<vertices>\n";
434
const MeshCore::MeshGeomFacet* facet {};
436
// Iterate through all facets of the mesh, and construct a:
437
// * Cache (map) of used vertices, outputting each new unique vertex to
438
// the output stream as we find it
439
// * Vector of the vertices, referred to by the indices from 1
440
std::map<Base::Vector3f, unsigned long, ExporterAMF::VertLess> vertices;
441
auto vertItr(vertices.begin());
442
auto vertexCount(0UL);
444
// {facet1A, facet1B, facet1C, facet2A, ..., facetNC}
445
std::vector<unsigned long> facets;
447
// For each facet in mesh
448
for (clIter.Begin(), clEnd.End(); clIter < clEnd; ++clIter) {
451
// For each vertex in facet
452
for (auto pnt : facet->_aclPoints) {
453
vertItr = vertices.find(pnt);
455
if (vertItr == vertices.end()) {
456
facets.push_back(vertexCount);
458
vertices[pnt] = vertexCount++;
461
*outputStreamPtr << "\t\t\t\t<vertex>\n"
462
<< "\t\t\t\t\t<coordinates>\n";
463
for (auto j(0); j < 3; ++j) {
465
*outputStreamPtr << "\t\t\t\t\t\t<" << axis << '>' << pnt[j] << "</" << axis
468
*outputStreamPtr << "\t\t\t\t\t</coordinates>\n"
469
<< "\t\t\t\t</vertex>\n";
472
facets.push_back(vertItr->second);
476
seq.next(true); // allow to cancel
479
*outputStreamPtr << "\t\t\t</vertices>\n"
480
<< "\t\t\t<volume>\n";
482
// Now that we've output all the vertices, we can
483
// output the facets that refer to them!
484
for (auto triItr(facets.begin()); triItr != facets.end();) {
485
*outputStreamPtr << "\t\t\t\t<triangle>\n";
486
for (auto i(1); i < 4; ++i) {
487
*outputStreamPtr << "\t\t\t\t\t<v" << i << '>' << *(triItr++) << "</v" << i << ">\n";
489
*outputStreamPtr << "\t\t\t\t</triangle>\n";
490
seq.next(true); // allow to cancel
493
*outputStreamPtr << "\t\t\t</volume>\n"