Files
OpenSceneGraph/src/osgPlugins/dxf/DXFWriterNodeVisitor.cpp
Robert Osfield e1b7de4b3d Martin Beckett, "Here's a first attempt at a DXF writer plugin
At the moment it outputs DXF for whatever geometry is contained in the node it would be nice to draw the model as it is rendered (points/lines/surface)

If people could also test against other apps that need to read DXF, the format is a bit of a black art and not all importers support all features so it might need some options to tweak the output.

It has some rather clever colour lookup stuff to match real colours against the limited DXF palette. I cracked the code of the Autocad indexed colours!"
2009-06-08 14:06:58 +00:00

593 lines
20 KiB
C++

// -*-c++-*-
/*
* Autcad DXF writer for Open Scene Graph
*
* Copyright (C) 2009 Martin Beckett mgb@mgbeckett.com
*
* Based on OBJ writer plugin by Ulrich Hertlein
*
* The Open Scene Graph (OSG) is a cross platform C++/OpenGL library for
* real-time rendering of large 3D photo-realistic models.
* The OSG homepage is http://www.openscenegraph.org/
*/
#include <osg/io_utils>
#include <iomanip>
#include "DXFWriterNodeVisitor.h"
// ROBERT - is there any need for a value visitor like this or is it just overkill?
/** writes all values of an array out to a stream, applies a matrix beforehand if necessary */
// I think this is a bit over the top for just a simple vertex array - but if anyone knwos different?
/*
class ValueVisitor : public osg::ValueVisitor {
public:
ValueVisitor(std::ostream& fout, const Layer &layer,const osg::Matrix& m = osg::Matrix::identity()) :
osg::ValueVisitor(),
_fout(fout),
_layer(layer),
_m(m)
{
//_applyMatrix = (_m != osg::Matrix::identity());
}
virtual void apply(osg::Vec3 & inv)
{
osg::Vec3 point(inv) ;
point = point * _m;
_fout << "0 \nVERTEX\n 8\n"<<_layer._name<<"\n";
if ( _layer._color ) {
_fout << "62\n"<<_layer._color<<"\n";
}
_fout <<" 10\n"<<point.x()<<"\n 20\n"<<point.y()<<"\n 30\n"<<point.z()<<"\n";
}
private:
ValueVisitor& operator = (const ValueVisitor&) { return *this; }
std::ostream& _fout;
osg::Matrix _m;
const Layer _layer;
};
*/
/** writes all primitives of a primitive-set out to a stream, decomposes quads to triangles, line-strips to lines etc */
class PrimitiveIndexWriter : public osg::PrimitiveIndexFunctor {
public:
PrimitiveIndexWriter(std::ostream& fout,osg::Geometry* geo,const Layer &layer,AcadColor &acad,const osg::Matrix& m = osg::Matrix::identity()) :
osg::PrimitiveIndexFunctor(),
_fout(fout),
_geo(geo),
_layer(layer),
_acad(acad),
_m(m)
{
}
virtual void setVertexArray(unsigned int,const osg::Vec2*) {}
virtual void setVertexArray(unsigned int ,const osg::Vec3* ) {}
virtual void setVertexArray(unsigned int,const osg::Vec4* ) {}
virtual void setVertexArray(unsigned int,const osg::Vec2d*) {}
virtual void setVertexArray(unsigned int ,const osg::Vec3d* ) {}
virtual void setVertexArray(unsigned int,const osg::Vec4d* ) {}
void write(unsigned int i,int c)
{
const osg::Vec3 point = ((osg::Vec3Array *)_geo->getVertexArray())->at(i) * _m;
_fout <<c+10<<"\n "<<point.x()<<"\n"<<20+c<<"\n "<<point.y()<<"\n"<<30+c<<"\n "<<point.z()<<"\n";
}
// operator for facets - need to distinguish from triangles
void writeFace(unsigned int i1, unsigned int i2, unsigned int i3)
{
_fout << "0 \n3DFACE\n 8\n"<<_layer._name<<"\n";
if ( _layer._color ) {
_fout << "62\n"<<_layer._color<<"\n";
} else {
_fout << "62\n"<<_acad.findColor(DXFWriterNodeVisitor::getNodeRGB(_geo,i1))<<"\n";
// Acad2000 supports 24bit color but most dxf importers don't
//_fout << "420\n"<<DXFWriterNodeVisitor::getNodeRGB(_geo,i1)<<"\n";
}
write(i1,0);
write(i2,1);
write(i3,2);
write(i1,0); // yes you have to write the first point again
}
// operator for triangles
void writeTriangle(unsigned int i1, unsigned int i2, unsigned int i3)
{
_fout << "0 \nLINE\n 8\n"<<_layer._name<<"\n";
if ( _layer._color ) {
_fout << "62\n"<<_layer._color<<"\n";
} else {
_fout << "62\n"<<_acad.findColor(DXFWriterNodeVisitor::getNodeRGB(_geo,i1))<<"\n";
}
write(i1,0);
write(i2,1);
_fout << "0 \nLINE\n 8\n"<<_layer._name<<"\n";
if ( _layer._color ) {
_fout << "62\n"<<_layer._color<<"\n";
} else {
_fout << "62\n"<<_acad.findColor(DXFWriterNodeVisitor::getNodeRGB(_geo,i2))<<"\n";
}
write(i2,0);
write(i3,1);
_fout << "0 \nLINE\n 8\n"<<_layer._name<<"\n";
if ( _layer._color ) {
_fout << "62\n"<<_layer._color<<"\n";
} else {
_fout << "62\n"<<_acad.findColor(DXFWriterNodeVisitor::getNodeRGB(_geo,i3))<<"\n";
}
write(i3,0);
write(i1,1);
}
// operator for lines
void writeLine(unsigned int i1, unsigned int i2)
{
_fout << "0 \nLINE\n 8\n"<<_layer._name<<"\n";
if ( _layer._color ) {
_fout << "62\n"<<_layer._color<<"\n";
} else {
_fout << "62\n"<<_acad.findColor(DXFWriterNodeVisitor::getNodeRGB(_geo,i1))<<"\n";
}
write(i1,0);
write(i2,1);
}
// operator for points
void writePoint(unsigned int i1)
{
_fout << "0 \nPOINT\n 8\n"<<_layer._name<<"\n";
if ( _layer._color ) {
_fout << "62\n"<<_layer._color<<"\n";
} else {
_fout << "62\n"<<_acad.findColor(DXFWriterNodeVisitor::getNodeRGB(_geo,i1))<<"\n";
//_fout << "420\n"<<DXFWriterNodeVisitor::getNodeRGB(_geo,i1)<<"\n";
}
write(i1,0);
}
virtual void begin(GLenum mode)
{
_modeCache = mode;
_indexCache.clear();
}
virtual void vertex(unsigned int vert)
{
_indexCache.push_back(vert);
}
virtual void end()
{
if (!_indexCache.empty())
{
drawElements(_modeCache,_indexCache.size(),&_indexCache.front());
}
}
virtual void drawArrays(GLenum mode,GLint first,GLsizei count);
virtual void drawElements(GLenum mode,GLsizei count,const GLubyte* indices)
{
drawElementsImplementation<GLubyte>(mode, count, indices);
}
virtual void drawElements(GLenum mode,GLsizei count,const GLushort* indices)
{
drawElementsImplementation<GLushort>(mode, count, indices);
}
virtual void drawElements(GLenum mode,GLsizei count,const GLuint* indices)
{
drawElementsImplementation<GLuint>(mode, count, indices);
}
protected:
template<typename T>void drawElementsImplementation(GLenum mode, GLsizei count, const T* indices)
{
if (indices==0 || count==0) return;
typedef const T* IndexPointer;
switch(mode)
{
case(GL_TRIANGLES):
{
IndexPointer ilast = &indices[count];
for(IndexPointer iptr=indices;iptr<ilast;iptr+=3)
writeTriangle(*iptr,*(iptr+1),*(iptr+2));
break;
}
case(GL_TRIANGLE_STRIP):
{
IndexPointer iptr = indices;
for(GLsizei i=2;i<count;++i,++iptr)
{
if ((i%2)) writeTriangle(*(iptr),*(iptr+2),*(iptr+1));
else writeTriangle(*(iptr),*(iptr+1),*(iptr+2));
}
break;
}
case(GL_QUADS):
{
IndexPointer iptr = indices;
for(GLsizei i=3;i<count;i+=4,iptr+=4)
{
writeTriangle(*(iptr),*(iptr+1),*(iptr+2));
writeTriangle(*(iptr),*(iptr+2),*(iptr+3));
}
break;
}
case(GL_QUAD_STRIP):
{
IndexPointer iptr = indices;
for(GLsizei i=3;i<count;i+=2,iptr+=2)
{
writeTriangle(*(iptr),*(iptr+1),*(iptr+2));
writeTriangle(*(iptr+1),*(iptr+3),*(iptr+2));
}
break;
}
case(GL_POLYGON): // treat polygons as GL_TRIANGLE_FAN
case(GL_TRIANGLE_FAN):
{
IndexPointer iptr = indices;
unsigned int first = *iptr;
++iptr;
for(GLsizei i=2;i<count;++i,++iptr)
{
writeTriangle(first,*(iptr),*(iptr+1));
}
break;
}
case(GL_POINTS):
{
IndexPointer ilast = &indices[count];
for(IndexPointer iptr=indices;iptr<ilast;++iptr)
{
writePoint(*iptr);
}
break;
}
case(GL_LINES):
{
IndexPointer ilast = &indices[count];
for(IndexPointer iptr=indices;iptr<ilast;iptr+=2)
{
writeLine(*iptr, *(iptr+1));
}
break;
}
case(GL_LINE_STRIP):
{
IndexPointer ilast = &indices[count];
for(IndexPointer iptr=indices+1;iptr<ilast;iptr+=2)
{
writeLine(*(iptr-1), *iptr);
}
break;
}
case(GL_LINE_LOOP):
{
IndexPointer ilast = &indices[count];
for(IndexPointer iptr=indices+1;iptr<ilast;iptr+=2)
{
writeLine(*(iptr-1), *iptr);
}
writeLine(*ilast, *indices);
break;
}
default:
// uhm should never come to this point :)
break;
}
}
private:
PrimitiveIndexWriter& operator = (const PrimitiveIndexWriter&) { return *this; }
std::ostream& _fout;
GLenum _modeCache;
std::vector<GLuint> _indexCache;
osg::Geometry* _geo;
Layer _layer;
AcadColor _acad; // needed to lookup new colors
osg::Matrix _m;
};
void PrimitiveIndexWriter::drawArrays(GLenum mode,GLint first,GLsizei count)
{
switch(mode)
{
case(GL_TRIANGLES):
{
unsigned int pos=first;
for(GLsizei i=2;i<count;i+=3,pos+=3)
{
writeTriangle(pos,pos+1,pos+2);
}
break;
}
case(GL_TRIANGLE_STRIP):
{
unsigned int pos=first;
for(GLsizei i=2;i<count;++i,++pos)
{
if ((i%2)) writeTriangle(pos,pos+2,pos+1);
else writeTriangle(pos,pos+1,pos+2);
}
break;
}
case(GL_QUADS):
{
unsigned int pos=first;
for(GLsizei i=3;i<count;i+=4,pos+=4)
{
writeTriangle(pos,pos+1,pos+2);
writeTriangle(pos,pos+2,pos+3);
}
break;
}
case(GL_QUAD_STRIP):
{
unsigned int pos=first;
for(GLsizei i=3;i<count;i+=2,pos+=2)
{
writeTriangle(pos,pos+1,pos+2);
writeTriangle(pos+1,pos+3,pos+2);
}
break;
}
case(GL_POLYGON): // treat polygons as GL_TRIANGLE_FAN
case(GL_TRIANGLE_FAN):
{
unsigned int pos=first+1;
for(GLsizei i=2;i<count;++i,++pos)
{
writeTriangle(first,pos,pos+1);
}
break;
}
case(GL_POINTS):
{
for(GLsizei i=0;i<count;++i)
{
writePoint(i);
}
break;
}
case(GL_LINES):
{
for(GLsizei i=0;i<count;i+=2)
{
writeLine(i, i+1);
}
break;
}
case(GL_LINE_STRIP):
{
for(GLsizei i=1;i<count;++i)
{
writeLine(i-1, i);
}
break;
}
case(GL_LINE_LOOP):
{
for(GLsizei i=1;i<count;++i)
{
writeLine(i-1, i);
}
writeLine(count-1, 0);
break;
}
default:
osg::notify(osg::WARN) << "DXFWriterNodeVisitor :: can't handle mode " << mode << std::endl;
break;
}
}
// TODO - illegal acad characters
std::string DXFWriterNodeVisitor::getLayerName(const std::string& defaultvalue)
{
std::string layerName=defaultvalue;
std::transform(layerName.begin(), layerName.end(), layerName.begin(), toupper);
// remove illegal ACAD characters
size_t found=0;
const std::string allowed("ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890_-");
while ( (found=layerName.find_first_not_of(allowed)) != std::string::npos) {
layerName[found] = '-';
}
// TODO check that changed value isn't also a dupe
for (std::vector<Layer>::iterator itr=_layers.begin();itr!=_layers.end();itr++) {
if (itr->_name == layerName ) {
std::stringstream ss;
ss << defaultvalue<< "_" << _layers.size();
layerName = ss.str();
break;
}
}
return layerName;
}
// Now deal with VertexArray directly
//void DXFWriterNodeVisitor::processArray(osg::Array* array, const Layer &layer,const osg::Matrix& m)
//{
// if (array == NULL)
// return;
//
// ValueVisitor vv(_fout, layer,m);
// for(unsigned int i = 0; i < array->getNumElements(); ++i) {
// array->accept(i, vv);
// }
//
// osg::notify(osg::DEBUG_INFO) << "processArray "<<layer._name<<"\n";
// osg::notify(osg::DEBUG_INFO) << "# " << array->getNumElements() << " elements written" << std::endl;
//
//}
void DXFWriterNodeVisitor::processStateSet(osg::StateSet* ss)
{
// anything to do if no material/texture?
// could detect polygon mode and output in that form?
}
void DXFWriterNodeVisitor::processGeometry(osg::Geometry* geo, osg::Matrix& m)
{
// We only want to create a new layer for geometry with something to draw
if (geo->getVertexArray() && geo->getVertexArray()->getNumElements() ) {
processStateSet(_currentStateSet.get());
if ( _firstPass ) {
// Must have unique layer names
_layer._name = getLayerName( geo->getName().empty() ? geo->getParent(0)->getName() : geo->getName() );
osg::notify(osg::DEBUG_INFO) << "adding Layer " << _layer._name << std::endl;
// if single colour include in header
if ( osg::Geometry::BIND_OVERALL == geo->getColorBinding() ) {
_layer._color = _acadColor.findColor(getNodeRGB(geo)); // per layer color
} else if ( osg::Geometry::BIND_OFF== geo->getColorBinding() ) {
_layer._color = 0xff; // use white - or can we easily lookup in texture?
} else {
_layer._color = 0; // per point color
}
_layers.push_back(_layer);
} else {
_layer = _layers[_count++];
osg::notify(osg::DEBUG_INFO) << "writing Layer " << _layer._name << std::endl;
if ( geo->getNumPrimitiveSets() ) {
for(unsigned int i = 0; i < geo->getNumPrimitiveSets(); ++i)
{
osg::PrimitiveSet* ps = geo->getPrimitiveSet(i);
PrimitiveIndexWriter pif(_fout, geo,_layer,_acadColor,m);
ps->accept(pif);
}
} else {
// Is array visitor necessary for only dealing with vertex arrays?
//processArray(geo->getVertexArray(), _layer,m);
if ( geo->getVertexArray() ) {
osg::Vec3Array* data=static_cast<osg::Vec3Array*>(geo->getVertexArray());
for (unsigned int ii=0;ii<data->getNumElements();ii++)
{
osg::Vec3 point = data->at(ii) * m;
_fout << "0 \nVERTEX\n 8\n"<<_layer._name<<"\n";
if ( _layer._color ) {
_fout << "62\n"<<_layer._color<<"\n";
} else {
_fout << "62\n"<<_acadColor.findColor(getNodeRGB(geo,ii))<<"\n";
}
_fout<<" 10\n"<<point.x()<<"\n 20\n"<<point.y()<<"\n 30\n"<<point.z()<<"\n";
}
}
}
}
}
}
void DXFWriterNodeVisitor::apply( osg::Geode &node )
{
pushStateSet(node.getStateSet());
osg::Matrix m = osg::computeLocalToWorld(getNodePath());
unsigned int count = node.getNumDrawables();
for ( unsigned int i = 0; i < count; i++ )
{
osg::Geometry *g = node.getDrawable( i )->asGeometry();
if ( g != NULL )
{
pushStateSet(g->getStateSet());
processGeometry(g,m);
popStateSet(g->getStateSet());
}
}
popStateSet(node.getStateSet());
}
bool DXFWriterNodeVisitor::writeHeader(const osg::BoundingSphere &bound)
{
if ( _layers.empty() ) {
return false;
}
_fout << "999\n written by OpenSceneGraph" << std::endl;
_fout << "0\nSECTION\n2\nHEADER\n";
_fout << "9\n$ACADVER\n1\nAC1006\n"; // specify minimum autocad version AC1006=R10
_fout << "9\n$EXTMIN\n10\n"<<bound.center().x()-bound.radius()<<"\n20\n"<<bound.center().y()-bound.radius()<<"\n30\n"<<bound.center().z()-bound.radius()<<"\n";
_fout << "9\n$EXTMAX\n10\n"<<bound.center().x()+bound.radius()<<"\n20\n"<<bound.center().y()+bound.radius()<<"\n30\n"<<bound.center().z()+bound.radius()<<"\n";
_fout << "0\nENDSEC\n0\nSECTION\n2\nTABLES\n";
_fout << "0\nTABLE\n2\nLAYER\n";
for (std::vector<Layer>::iterator itr=_layers.begin();itr!=_layers.end();itr++) {
if ( itr->_color ) {
_fout<<"0\nLAYER\n2\n"<<itr->_name<<"\n70\n0\n62\n"<<itr->_color<<"\n6\nContinuous\n"; // color by layer
} else {
_fout<<"0\nLAYER\n2\n"<<itr->_name<<"\n70\n0\n62\n255\n6\nContinuous\n"; // most apps won't read 24bit color without a color value in header
}
}
_fout << "0\nENDTAB\n0\nENDSEC\n";
_fout << "0\nSECTION\n2\nENTITIES\n";
_firstPass=false;
_count=0;
return true;
}
void DXFWriterNodeVisitor::writeFooter()
{
_fout << "0\nENDSEC\n0\nEOF";
_fout << std::endl;
}