Files
secondo/Algebras/Spatial3D/Spatial3DSTLfileops.cpp

588 lines
16 KiB
C++
Raw Normal View History

2026-01-23 17:03:45 +08:00
/*
//paragraph [1] Title: [{\Large \bf \begin{center}] [\end{center}}]
//paragraph [10] Footnote: [{\footnote{] [}}]
//[TOC] [\tableofcontents]
//[NP] [\newpage]
//[ue] [\"u]
//[e] [\'e]
//[lt] [\verb+<+]
//[gt] [\verb+>+]
----
SECONDO 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with SECONDO; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
----
[1] 01590 Fachpraktikum "Erweiterbare Datenbanksysteme"
[1] WS 2014 / 2015
Jens Breit, Joachim Dechow, Daniel Fuchs, Simon Jacobi, G[ue]nther Milosits,
Daijun Nagamine, Hans-Joachim Klauke.
Betreuer: Dr. Thomas Behr, Fabio Vald[e]s
[1] Implementation of a Spatial3D algebra: SetOps
[TOC]
[NP]
1 Includes and Defines
*/
#include "NestedList.h"
#include "TypeMapUtils.h"
#include "Spatial3D.h"
#include "Spatial3DSTLfileops.h"
#include <iostream>
#include <fstream>
#include <stdint.h>
#include <cctype>
#include "Algebras/FText/FTextAlgebra.h" //FText
#include "StandardTypes.h" //CcString
#include "ListUtils.h"
#include "Spatial3D.h"
#include "Operator.h"
#include "QueryProcessor.h"
extern NestedList* nl;
using namespace std;
namespace spatial3DSTLfileops {
/*
1 Helper functions
1.1 readNextStringToken
Consumes the next token from the provided stream and compares it
to the provided token. If they do not match an exception will be thrown.
*/
void readNextStringToken(istream& is, const string& token) {
string token_from_stream = "";
is >> token_from_stream;
if (token_from_stream != token) throw 1;
}
/*
1.1 readNextDouble
Consumes and returns the next double from the input stream. If the next token
in the stream is no double an exception will be thrown.
*/
double readNextDouble(istream& is) {
char c;
is >> c; //read the next char skipping whitespace
is.unget(); // and put it back, so we can later read it using operator>>
if (!isdigit(c) && !(c == '.')&& !(c == '-')) { // no double...
throw 1;
}
double from_stream = 0;
is >> from_stream;
return from_stream;
}
/*
1.1 readFromTextStl
Read an STL text file ("filename") into a volume3D. If reading the file fails
(technical or syntactical problems) volume3D will be undefined and
false will be returned. The provided BulkLoadOptions will be called on the to
be constructed volume3D.
*/
bool readFromTextStl (const string filename,
const BulkLoadOptions options, Volume3d* vol) {
vol->startBulkLoad();
ifstream ifs;
try { // read STL text file. Parsing func will throw upon invalid tokens.
ifs.open(filename.c_str(), ios::in);
readNextStringToken(ifs, "solid");
string solid_name = "";
ifs >> solid_name; // we'll later check if we have the same closing name
// with the endsolid tag
while (true) { // we'll break if we are done and throw upon errors
string token = "";
ifs >> token;
if (token == "endsolid") break; // we're done
if (token != "facet") throw 1; // only facet allowed here
readNextStringToken(ifs, "normal");
//float norm_vec_coord; // will be discarded
for (int i = 0; i <3; i++) {
//norm_vec_coord = readNextDouble(ifs);
readNextDouble(ifs);
}
readNextStringToken(ifs, "outer");
readNextStringToken(ifs, "loop");
SimplePoint3d pt[3];
for (int p = 0; p<3; p++) {
readNextStringToken(ifs, "vertex");
float x = readNextDouble(ifs);
float y = readNextDouble(ifs);
float z = readNextDouble(ifs);
pt[p].set(x,y,z);
}
Triangle tri(pt[0],pt[1],pt[2]);
vol->add(tri);
readNextStringToken(ifs, "endloop");
readNextStringToken(ifs, "endfacet");
}
// we've read the endsolid token. Now lets check if there is just
// additional whitespace and possibly the name of the solid
string endsolid_name = "";
ifs >> endsolid_name;
if (endsolid_name != "" && endsolid_name != solid_name) {
throw 1;
}
// now make sure we really reached the end of the file
char some_char = 0;
ifs >> some_char;
if (some_char !=0) {
throw 1;
}
ifs.close();
vol->endBulkLoad(options);
return true;
} catch(int a) {
ifs.close();
vol->endBulkLoad(options);
vol->clear();
vol->SetDefined(false);
return false;
}
return false;
}
/*
1.1 readFromBinaryStl
Read an STL binary file ("filename") into a volume3D. If reading the file fails
(technical or syntactical problems) volume3D will be undefined and
false will be returned. The provided BulkLoadOptions will be called on the to
be constructed volume3D. Comparing the encoded number of triangles with the
file size allows early detection of invalid files.
*/
bool readFromBinaryStl (const string filename,
const BulkLoadOptions options, Volume3d* vol) {
ifstream ifs;
ifs.open(filename.c_str(), ios::in | ios::binary);
// get length of file:
ifs.seekg (0, ifs.end);
int file_size = ifs.tellg();
//discard first 80 bytes
ifs.seekg (80, ifs.beg);
//read 32 bit unsigned int -> number of facets
uint32_t num_of_facets = 0;
ifs.read((char*)&num_of_facets, sizeof(num_of_facets));
float normal_vec[3];
float tri_points[9];
uint16_t attr_size = 0;
int expected_size = 80 + sizeof(num_of_facets)+ num_of_facets * (
sizeof(normal_vec) + sizeof(tri_points) + sizeof(attr_size));
// do a quick check if filesize is ok to prevent costly reading
if (file_size != expected_size || file_size < 82) {
/* // uncomment this to get details of file size check
// removed to prevent error messages in TTY
cerr << "Binary file size mismatch. Expected "
<< expected_size << " bytes, but got "
<< file_size << " bytes." << endl;
*/
ifs.close();
vol->clear();
vol->SetDefined(false);
return false;
}
vol->startBulkLoad();
for (unsigned int t = 0; t < num_of_facets; t++) {
ifs.read((char*)&normal_vec, sizeof(normal_vec)); // normal vector
ifs.read((char*)&tri_points, sizeof(tri_points)); // 3*3 Points3D
ifs.read((char*)&attr_size, sizeof(attr_size)); // attribute size
if (ifs.gcount() != sizeof(attr_size)) {
ifs.setstate(ifstream::failbit);
break; //we have reached the end of the stream too early
}
Point3d pt[3];
for (int p = 0; p<3; p++) {
float x = tri_points[p*3];
float y = tri_points[p*3+1];
float z = tri_points[p*3+2];
pt[p].set(x,y,z);
pt[p].SetDefined(true);
}
vol->add(Triangle(pt[0],pt[1],pt[2]));
}
if (ifs.fail()) {
cerr << "failed\n";
ifs.close();
vol->endBulkLoad(options);
vol->clear();
vol->SetDefined(false);
return false;
}
char c;
ifs.get(c); // just read one char to check for eof
if (!ifs.eof()) {
cerr << "extra bytes at end of file\n";
ifs.close();
vol->endBulkLoad(options);
vol->clear();
vol->SetDefined(false);
return false;
}
ifs.close();
vol->endBulkLoad(options);
return true;
}
/*
1.1 writeToBinaryStl
Export to binary STL files.
*/
bool writeToBinaryStl(const Volume3d* const vol,
const string objectName, const string fileName) {
ofstream ofs;
ofs.open(fileName.c_str(), ios::out | ios::binary);
// not very elegant, but should avoid fiddling with multibyte chars
// blow up object name with spaces, so we have at least 80 bytes...
// ... then only write 80 bytes
size_t headerLen = 80;
string header = string (headerLen, ' ');
header.insert(0, objectName);
ofs.write((char*)header.c_str(), headerLen);
//TODO check for potential overflows
uint32_t num_of_facets = vol->size(); // get num of triangles...
ofs.write((char*)&num_of_facets, sizeof(num_of_facets));
for (uint32_t facet_index = 0; facet_index < num_of_facets; facet_index++){
float normal_vec[3];
float tri_points[9];
uint16_t attr_size = 0;
Triangle tri = vol->get(facet_index);
Vector3d norm_vec = tri.getNormalVector();
Point3d a = tri.getA();
Point3d b = tri.getB();
Point3d c = tri.getC();
normal_vec[0] = norm_vec.getX();
normal_vec[1] = norm_vec.getY();
normal_vec[2] = norm_vec.getZ();
tri_points[0] = a.getX();
tri_points[1] = a.getY();
tri_points[2] = a.getZ();
tri_points[3] = b.getX();
tri_points[4] = b.getY();
tri_points[5] = b.getZ();
tri_points[6] = c.getX();
tri_points[7] = c.getY();
tri_points[8] = c.getZ();
ofs.write((char*)&normal_vec, sizeof(normal_vec)); // normal vector
ofs.write((char*)&tri_points, sizeof(tri_points)); // 3*3 Points3D
ofs.write((char*)&attr_size, sizeof(attr_size)); // attribute size
}
ofs.close();
return true;
}
/*
1.1 writeToTextStl
Export to text STL files.
*/
bool writeToTextStl(const Volume3d* const vol,
string objectName, string fileName) {
ofstream ofs;
ofs.open(fileName.c_str(), ios::out);
ofs << "solid " << objectName << endl;
//TODO check for potential overflows
uint32_t num_of_facets = vol->size(); // get num of triangles...
for (uint32_t facet_index = 0; facet_index < num_of_facets; facet_index++){
ofs << " facet" << endl;
Triangle tri = vol->get(facet_index);
Vector3d norm_vec = tri.getNormalVector();
Point3d a = tri.getA();
Point3d b = tri.getB();
Point3d c = tri.getC();
ofs << " normal ";
ofs << fixed << norm_vec.getX() << " ";
ofs << fixed << norm_vec.getY() << " ";
ofs << fixed << norm_vec.getZ() << endl;
ofs << " outer loop" << endl;
ofs << " vertex ";
ofs << fixed << a.getX() << " ";
ofs << fixed << a.getY() << " ";
ofs << fixed << a.getZ() << endl;
ofs << " vertex ";
ofs << fixed << b.getX() << " ";
ofs << fixed << b.getY() << " ";
ofs << fixed << b.getZ() << endl;
ofs << " vertex ";
ofs << fixed << c.getX() << " ";
ofs << fixed << c.getY() << " ";
ofs << fixed << c.getZ() << endl;
ofs << " endloop" << endl;
ofs << " endfacet" << endl;
}
ofs << "endsolid " << objectName << endl;
ofs.close();
return true;
}
/*
1 Operator Specifcations
1.1 importSTL
Imports a volume3d object from an STL file.
Both binary and text STL files are accepted.
The import automatically choses the correct import format based on file.
A simple correction (i.e. filling of small holes, removal of triangles
inside of the volume) will be performed.
If anything goes wrong an undefined volume3d object will be returned.
*/
OperatorSpec importSTLSpec (
"text -> volume3d",
"importSTL ( <filename> )",
"imports a volume3D object from a valid STL file\n"
"Both text and binary STL files are accepted.\n"
"Will return undefined object in case of problems",
"query importSTL ('cube.stl');"
);
/*
1.1 exportSTL
exports a volume3d object into an STL file. The format (binary or text)
can be chosen.
*/
OperatorSpec exportSTLSpec (
"volume3d x string x text x bool -> bool",
"<volume3d> exportSTL [ <objectname>, <filename>, <export_to_binary> ]",
"exports a volume3D object to STL file\n"
"Will return FALSE in case of error.\n"
"Set export_to_binary to true if you want to use the binary format.",
"query mySTLcube exportSTL ['my Cube', 'cube.stl', FALSE];"
);
/*
1 Type Mappings
1.1 importSTL
*/
ListExpr importSTLTM(ListExpr args) {
if(!nl->HasLength(args,1)){
return listutils::typeError("one argument expected");
}
if(!FText::checkType(nl->First(args))){
string err = "expected argument of type text but got "
+ nl->ToString(nl->First(args));
return listutils::typeError(err);
}
return listutils::basicSymbol<Volume3d>();
}
/*
1.1 exportSTL
*/
ListExpr exportSTLTM(ListExpr args) {
if(!nl->HasLength(args,4)){
return listutils::typeError("four arguments expected");
}
if(!Volume3d::checkType(nl->First(args))){
string err =
"expected argument of type volume3d as first argument but got "
+ nl->ToString(nl->First(args));
return listutils::typeError(err);
}
if(!CcString::checkType(nl->Second(args))){
string err =
"expected argument of type string as second argument but got "
+ nl->ToString(nl->Second(args));
return listutils::typeError(err);
}
if(!FText::checkType(nl->Third(args))){
string err =
"expected argument of type text as third argument but got "
+ nl->ToString(nl->Third(args));
return listutils::typeError(err);
}
if(!CcBool::checkType(nl->Fourth(args))){
string err =
"expected argument of type bool as fourth argument but got "
+ nl->ToString(nl->Fourth(args));
return listutils::typeError(err);
}
return listutils::basicSymbol<CcBool>();
}
/*
1 Value Mappings
1.1 importSTL
The respective helper function for binary and text import are called.
The binary format is tried first, as it allows a quick check based on filesize
to determine if it is valid. Only if the file is not in binary format,
the text format will be tried. Which might make it necessary to read through
the whole file just to discover it is corrupt.
*/
int importSTLVM( Word* args, Word& result, int message,
Word& local, Supplier s ) {
FText* fileName = (FText*) args[0].addr;
result = qp->ResultStorage(s);
Volume3d* res = (Volume3d*) result.addr;
BulkLoadOptions opts = REPAIR;
//BulkLoadOptions opts = NO_REPAIR;
string filename = fileName->GetValue();
// First try to read binary file, as we can quickly check if valid
bool importresult = readFromBinaryStl(filename, opts, res);
importresult = importresult || readFromTextStl(filename, opts, res);
return 0;
}
/*
1.1 exportSTL
exports a volume3d object to an STL file (format to be chosen by the user).
Undefined volume3d objects will not be exported at all.
*/
int exportSTLVM( Word* args, Word& result, int message,
Word& local, Supplier s ) {
Volume3d* vol = (Volume3d*) args[0].addr;
CcString* objectName = (CcString*) args[1].addr;
FText* fileName = (FText*) args[2].addr;
CcBool* toBinary = (CcBool*) args[3].addr;
result = qp->ResultStorage(s);
CcBool* res = (CcBool*) result.addr;
if ( !vol->IsDefined()
|| !objectName->IsDefined()
|| !fileName->IsDefined()
|| !toBinary->IsDefined()) {
cerr << "Got some undefined argument";
res->Set(true, false);
return 0;
}
// TODO:
// Here we assume to get valid file and object names
// which may cause some trouble...
bool exportSuccesful = true;
if (toBinary->GetValue()) {
exportSuccesful = writeToBinaryStl
(vol, objectName->GetValue(), fileName->GetValue());
}else{
exportSuccesful = writeToTextStl
(vol, objectName->GetValue(), fileName->GetValue());
}
res->Set(true, exportSuccesful);
return 0;
}
/*
1 Operator Pointers
Provide Operator Pointers for embedding in Algebra
*/
Operator* getImportSTLptr(){
return new Operator(
"importSTL",
importSTLSpec.getStr(),
importSTLVM,
Operator::SimpleSelect,
importSTLTM
);
}
Operator* getExportSTLptr(){
return new Operator(
"exportSTL",
exportSTLSpec.getStr(),
exportSTLVM,
Operator::SimpleSelect,
exportSTLTM
);
}
} //namespace spatial3DSTLfileops