/*************************************************************************** * Copyright (c) 2015 Stefan Tröger * * * * This file is part of the FreeCAD CAx development system. * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Library General Public * * License as published by the Free Software Foundation; either * * version 2 of the License, or (at your option) any later version. * * * * This library is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU Library General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this library; see the file COPYING.LIB. If not, * * write to the Free Software Foundation, Inc., 59 Temple Place, * * Suite 330, Boston, MA 02111-1307, USA * * * ***************************************************************************/ #include "PreCompiled.h" #ifndef _PreComp_ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #endif #include #include #include #include #include #include #include #include #ifdef _MSC_VER #include #endif #include #include #include "PropertyPostDataObject.h" using namespace Fem; TYPESYSTEM_SOURCE(Fem::PropertyPostDataObject, App::Property) PropertyPostDataObject::PropertyPostDataObject() = default; PropertyPostDataObject::~PropertyPostDataObject() = default; void PropertyPostDataObject::scaleDataObject(vtkDataObject* dataObject, double s) { auto scalePoints = [](vtkPoints* points, double s) { for (vtkIdType i = 0; i < points->GetNumberOfPoints(); i++) { double xyz[3]; points->GetPoint(i, xyz); for (double& j : xyz) { j *= s; } points->SetPoint(i, xyz); } }; if (dataObject->GetDataObjectType() == VTK_POLY_DATA) { vtkPolyData* dataSet = vtkPolyData::SafeDownCast(dataObject); scalePoints(dataSet->GetPoints(), s); } else if (dataObject->GetDataObjectType() == VTK_STRUCTURED_GRID) { vtkStructuredGrid* dataSet = vtkStructuredGrid::SafeDownCast(dataObject); scalePoints(dataSet->GetPoints(), s); } else if (dataObject->GetDataObjectType() == VTK_UNSTRUCTURED_GRID) { vtkUnstructuredGrid* dataSet = vtkUnstructuredGrid::SafeDownCast(dataObject); scalePoints(dataSet->GetPoints(), s); } else if (dataObject->GetDataObjectType() == VTK_MULTIBLOCK_DATA_SET) { vtkMultiBlockDataSet* dataSet = vtkMultiBlockDataSet::SafeDownCast(dataObject); for (unsigned int i = 0; i < dataSet->GetNumberOfBlocks(); i++) { scaleDataObject(dataSet->GetBlock(i), s); } } else if (dataObject->GetDataObjectType() == VTK_MULTIPIECE_DATA_SET) { vtkMultiPieceDataSet* dataSet = vtkMultiPieceDataSet::SafeDownCast(dataObject); for (unsigned int i = 0; i < dataSet->GetNumberOfPieces(); i++) { scaleDataObject(dataSet->GetPiece(i), s); } } } void PropertyPostDataObject::scale(double s) { if (m_dataObject) { aboutToSetValue(); scaleDataObject(m_dataObject, s); hasSetValue(); } } void PropertyPostDataObject::setValue(const vtkSmartPointer& ds) { aboutToSetValue(); if (ds) { createDataObjectByExternalType(ds); m_dataObject->DeepCopy(ds); } else { m_dataObject = nullptr; } hasSetValue(); } const vtkSmartPointer& PropertyPostDataObject::getValue() const { return m_dataObject; } bool PropertyPostDataObject::isComposite() { return m_dataObject && !m_dataObject->IsA("vtkDataSet"); } bool PropertyPostDataObject::isDataSet() { return m_dataObject && m_dataObject->IsA("vtkDataSet"); } int PropertyPostDataObject::getDataType() { if (!m_dataObject) { return -1; } return m_dataObject->GetDataObjectType(); } PyObject* PropertyPostDataObject::getPyObject() { // TODO: fetch the vtk python object from the data set and return it return Py::new_reference_to(Py::None()); } void PropertyPostDataObject::setPyObject(PyObject* /*value*/) {} App::Property* PropertyPostDataObject::Copy() const { PropertyPostDataObject* prop = new PropertyPostDataObject(); if (m_dataObject) { prop->createDataObjectByExternalType(m_dataObject); prop->m_dataObject->DeepCopy(m_dataObject); } return prop; } void PropertyPostDataObject::createDataObjectByExternalType(vtkSmartPointer ex) { switch (ex->GetDataObjectType()) { case VTK_POLY_DATA: m_dataObject = vtkSmartPointer::New(); break; case VTK_STRUCTURED_GRID: m_dataObject = vtkSmartPointer::New(); break; case VTK_RECTILINEAR_GRID: m_dataObject = vtkSmartPointer::New(); break; case VTK_UNSTRUCTURED_GRID: m_dataObject = vtkSmartPointer::New(); break; case VTK_UNIFORM_GRID: m_dataObject = vtkSmartPointer::New(); break; case VTK_COMPOSITE_DATA_SET: m_dataObject = vtkCompositeDataSet::New(); break; case VTK_MULTIBLOCK_DATA_SET: m_dataObject = vtkSmartPointer::New(); break; case VTK_MULTIPIECE_DATA_SET: m_dataObject = vtkSmartPointer::New(); break; default: break; }; } void PropertyPostDataObject::Paste(const App::Property& from) { aboutToSetValue(); m_dataObject = dynamic_cast(from).m_dataObject; hasSetValue(); } unsigned int PropertyPostDataObject::getMemSize() const { return m_dataObject ? m_dataObject->GetActualMemorySize() : 0; } void PropertyPostDataObject::getPaths(std::vector& /*paths*/) const { /* paths.push_back(App::ObjectIdentifier(getContainer()) << App::ObjectIdentifier::Component::SimpleComponent(getName()) << App::ObjectIdentifier::Component::SimpleComponent( App::ObjectIdentifier::String("ShapeType"))); paths.push_back(App::ObjectIdentifier(getContainer()) << App::ObjectIdentifier::Component::SimpleComponent(getName()) << App::ObjectIdentifier::Component::SimpleComponent( App::ObjectIdentifier::String("Orientation"))); paths.push_back(App::ObjectIdentifier(getContainer()) << App::ObjectIdentifier::Component::SimpleComponent(getName()) << App::ObjectIdentifier::Component::SimpleComponent( App::ObjectIdentifier::String("Length"))); paths.push_back(App::ObjectIdentifier(getContainer()) << App::ObjectIdentifier::Component::SimpleComponent(getName()) << App::ObjectIdentifier::Component::SimpleComponent( App::ObjectIdentifier::String("Area"))); paths.push_back(App::ObjectIdentifier(getContainer()) << App::ObjectIdentifier::Component::SimpleComponent(getName()) << App::ObjectIdentifier::Component::SimpleComponent( App::ObjectIdentifier::String("Volume"))); */ } void PropertyPostDataObject::Save(Base::Writer& writer) const { if (!m_dataObject) { return; } std::string extension; switch (m_dataObject->GetDataObjectType()) { case VTK_POLY_DATA: extension = "vtp"; break; case VTK_STRUCTURED_GRID: extension = "vts"; break; case VTK_RECTILINEAR_GRID: extension = "vtr"; break; case VTK_UNSTRUCTURED_GRID: extension = "vtu"; break; case VTK_UNIFORM_GRID: extension = "vti"; // image data break; case VTK_MULTIBLOCK_DATA_SET: extension = "zip"; break; default: break; }; if (!writer.isForceXML()) { std::string file = "Data." + extension; writer.Stream() << writer.ind() << "" << std::endl; } } void PropertyPostDataObject::Restore(Base::XMLReader& reader) { reader.readElement("Data"); if (!reader.hasAttribute("file")) { return; } std::string file(reader.getAttribute("file")); if (!file.empty()) { // initiate a file read reader.addFile(file.c_str(), this); } } void add_to_zip(Base::FileInfo path, int zip_path_idx, zipios::ZipOutputStream& ZipWriter) { if (path.isDir()) { for(auto file : path.getDirectoryContent()) { add_to_zip(file, zip_path_idx, ZipWriter); } } else { ZipWriter.putNextEntry(path.filePath().substr(zip_path_idx)); Base::ifstream file(path, std::ios::in | std::ios::binary); if (file) { std::streambuf* buf = file.rdbuf(); ZipWriter << buf; } } } void PropertyPostDataObject::SaveDocFile(Base::Writer& writer) const { // If the shape is empty we simply store nothing. The file size will be 0 which // can be checked when reading in the data. if (!m_dataObject) { return; } // create a temporary file and copy the content to the zip stream // once the tmp. filename is known use always the same because otherwise // we may run into some problems on the Linux platform static Base::FileInfo fi = Base::FileInfo(App::Application::getTempFileName()); bool success = false; Base::FileInfo datafolder; vtkSmartPointer xmlWriter; if (m_dataObject->IsA("vtkMultiBlockDataSet")) { // create a tmp directory to write in datafolder = Base::FileInfo(App::Application::getTempPath() + "vtk_datadir"); datafolder.createDirectories(); auto datafile = Base::FileInfo(datafolder.filePath() + "/datafile.vtm"); //create the data: vtm file and subfolder with the subsequent data files xmlWriter = vtkSmartPointer::New(); xmlWriter->SetInputDataObject(m_dataObject); xmlWriter->SetFileName(datafile.filePath().c_str()); xmlWriter->SetDataModeToBinary(); } else { xmlWriter = vtkSmartPointer::New(); xmlWriter->SetInputDataObject(m_dataObject); xmlWriter->SetFileName(fi.filePath().c_str()); xmlWriter->SetDataModeToBinary(); #ifdef VTK_CELL_ARRAY_V2 // Looks like an invalid data object that causes a crash with vtk9 vtkUnstructuredGrid* dataGrid = vtkUnstructuredGrid::SafeDownCast(m_dataObject); if (dataGrid && (dataGrid->GetPiece() < 0 || dataGrid->GetNumberOfPoints() <= 0)) { std::cerr << "PropertyPostDataObject::SaveDocFile: ignore empty vtkUnstructuredGrid\n"; return; } #endif } if (xmlWriter->Write() != 1) { // Note: Do NOT throw an exception here because if the tmp. file could // not be created we should not abort. // We only print an error message but continue writing the next files to the // stream... App::PropertyContainer* father = this->getContainer(); if (father && father->isDerivedFrom()) { App::DocumentObject* obj = static_cast(father); Base::Console().Error("Dataset of '%s' cannot be written to vtk file '%s'\n", obj->Label.getValue(), fi.filePath().c_str()); } else { Base::Console().Error("Cannot save vtk file '%s'\n", fi.filePath().c_str()); } std::stringstream ss; ss << "Cannot save vtk file '" << fi.filePath() << "'"; writer.addError(ss.str()); } else if (m_dataObject->IsA("vtkMultiBlockDataSet")) { // ZIP file we store all data in zipios::ZipOutputStream ZipWriter(fi.filePath()); ZipWriter.putNextEntry("dummy"); //need to add a dummy first, as the read stream preloads the first entry, and we cannot get the file name... add_to_zip(datafolder, datafolder.filePath().length(), ZipWriter); ZipWriter.close(); datafolder.deleteDirectoryRecursive(); } Base::ifstream file(fi, std::ios::in | std::ios::binary); if (file) { std::streambuf* buf = file.rdbuf(); writer.Stream() << buf; } file.close(); // remove temp file fi.deleteFile(); } void PropertyPostDataObject::RestoreDocFile(Base::Reader& reader) { Base::FileInfo xml(reader.getFileName()); // create a temporary file and copy the content from the zip stream Base::FileInfo fi(App::Application::getTempFileName()); Base::FileInfo fo; // read in the ASCII file and write back to the file stream Base::ofstream file(fi, std::ios::out | std::ios::binary); unsigned long ulSize = 0; if (reader) { std::streambuf* buf = file.rdbuf(); reader >> buf; file.flush(); ulSize = buf->pubseekoff(0, std::ios::cur, std::ios::in); } file.close(); // Read the data from the temp file if (ulSize > 0) { std::string extension = xml.extension(); // TODO: read in of composite data structures need to be coded, // including replace of "GetOutputAsDataSet()" vtkSmartPointer xmlReader; if (extension == "vtp") { xmlReader = vtkSmartPointer::New(); } else if (extension == "vts") { xmlReader = vtkSmartPointer::New(); } else if (extension == "vtr") { xmlReader = vtkSmartPointer::New(); } else if (extension == "vtu") { xmlReader = vtkSmartPointer::New(); } else if (extension == "vti") { xmlReader = vtkSmartPointer::New(); } else if (extension == "zip") { // first unzip the file into a datafolder zipios::ZipInputStream ZipReader(fi.filePath()); fo = Base::FileInfo(App::Application::getTempPath() + "vtk_extract_datadir"); fo.createDirectories(); try { zipios::ConstEntryPointer entry = ZipReader.getNextEntry(); while(entry->isValid()) { Base::FileInfo entry_path(fo.filePath() + entry->getName()); if (entry->isDirectory()) { // seems not to be called entry_path.createDirectories(); } else { auto entry_dir = Base::FileInfo(entry_path.dirPath()); if(!entry_dir.exists()) { entry_dir.createDirectories(); } Base::ofstream file(entry_path, std::ios::out | std::ios::binary); std::streambuf* buf = file.rdbuf(); ZipReader >> buf; file.flush(); file.close(); } entry = ZipReader.getNextEntry(); } } catch (const std::exception&) { // there is no further entry } // create the reader, and change the file for it to read. Also delete zip file, not needed anymore fi.deleteFile(); fi = Base::FileInfo(fo.filePath() + "/datafile.vtm"); xmlReader = vtkSmartPointer::New(); } xmlReader->SetFileName(fi.filePath().c_str()); xmlReader->Update(); if (!xmlReader->GetOutputDataObject(0)) { // Note: Do NOT throw an exception here because if the tmp. created file could // not be read it's NOT an indication for an invalid input stream 'reader'. // We only print an error message but continue reading the next files from the // stream... App::PropertyContainer* father = this->getContainer(); if (father && father->isDerivedFrom()) { App::DocumentObject* obj = static_cast(father); Base::Console().Error("Dataset file '%s' with data of '%s' seems to be empty\n", fi.filePath().c_str(), obj->Label.getValue()); } else { Base::Console().Warning("Loaded Dataset file '%s' seems to be empty\n", fi.filePath().c_str()); } } else { aboutToSetValue(); createDataObjectByExternalType(xmlReader->GetOutputDataObject(0)); m_dataObject->DeepCopy(xmlReader->GetOutputDataObject(0)); hasSetValue(); } } // delete the temp file fi.deleteFile(); if (xml.extension() == "zip") { fo.deleteDirectoryRecursive(); } }