From e1b7de4b3d4b16f7baa89049920ec6808db23769 Mon Sep 17 00:00:00 2001 From: Robert Osfield Date: Mon, 8 Jun 2009 14:06:58 +0000 Subject: [PATCH] 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!" --- src/osgPlugins/dxf/CMakeLists.txt | 2 + src/osgPlugins/dxf/DXFWriterNodeVisitor.cpp | 592 ++++++++++++++++++++ src/osgPlugins/dxf/DXFWriterNodeVisitor.h | 282 ++++++++++ src/osgPlugins/dxf/ReaderWriterDXF.cpp | 62 +- 4 files changed, 937 insertions(+), 1 deletion(-) create mode 100644 src/osgPlugins/dxf/DXFWriterNodeVisitor.cpp create mode 100644 src/osgPlugins/dxf/DXFWriterNodeVisitor.h diff --git a/src/osgPlugins/dxf/CMakeLists.txt b/src/osgPlugins/dxf/CMakeLists.txt index b41db5ce9..860e78cb5 100644 --- a/src/osgPlugins/dxf/CMakeLists.txt +++ b/src/osgPlugins/dxf/CMakeLists.txt @@ -1,5 +1,6 @@ SET(TARGET_SRC ReaderWriterDXF.cpp + DXFWriterNodeVisitor.cpp aci.cpp dxfBlock.cpp dxfEntity.cpp @@ -22,6 +23,7 @@ SET(TARGET_H dxfSectionBase.h dxfTable.h scene.h + DXFWriterNodeVisitor.h ) SET(TARGET_ADDED_LIBRARIES osgText ) diff --git a/src/osgPlugins/dxf/DXFWriterNodeVisitor.cpp b/src/osgPlugins/dxf/DXFWriterNodeVisitor.cpp new file mode 100644 index 000000000..fce2de4d0 --- /dev/null +++ b/src/osgPlugins/dxf/DXFWriterNodeVisitor.cpp @@ -0,0 +1,592 @@ +// -*-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 +#include + +#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"<getVertexArray())->at(i) * _m; + _fout <(mode, count, indices); + } + virtual void drawElements(GLenum mode,GLsizei count,const GLushort* indices) + { + drawElementsImplementation(mode, count, indices); + } + + virtual void drawElements(GLenum mode,GLsizei count,const GLuint* indices) + { + drawElementsImplementation(mode, count, indices); + } + + protected: + + templatevoid 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 _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::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 "<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(geo->getVertexArray()); + for (unsigned int ii=0;iigetNumElements();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"<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"<::iterator itr=_layers.begin();itr!=_layers.end();itr++) { + if ( itr->_color ) { + _fout<<"0\nLAYER\n2\n"<_name<<"\n70\n0\n62\n"<_color<<"\n6\nContinuous\n"; // color by layer + } else { + _fout<<"0\nLAYER\n2\n"<_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; +} + diff --git a/src/osgPlugins/dxf/DXFWriterNodeVisitor.h b/src/osgPlugins/dxf/DXFWriterNodeVisitor.h new file mode 100644 index 000000000..4768ab3d4 --- /dev/null +++ b/src/osgPlugins/dxf/DXFWriterNodeVisitor.h @@ -0,0 +1,282 @@ +// -*-c++-*- + +/* + * Wavefront DXF loader for Open Scene Graph + * + * Copyright (C) 2001 Ulrich Hertlein + * + * Modified by Robert Osfield to support per Drawable coord, normal and + * texture coord arrays, bug fixes, and support for texture mapping. + * + * Writing support added 2007 by Stephan Huber, http://digitalmind.de, + * some ideas taken from the dae-plugin + * + * 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/ + */ + + #ifndef DXF_WRITER_NODE_VISITOR_HEADER__ + #define DXF_WRITER_NODE_VISITOR_HEADER__ + + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + + +#include +#include +#include +struct Layer +{ +public: + Layer(const std::string name="",unsigned int color=7) : _name(name),_color(color) { } + std::string _name; + unsigned int _color; +}; + +// reuse aci class for autocad colors, see http://bitsy.sub-atomic.com/~moses/acadcolors.html for samples +#include "aci.h" +class AcadColor +{ +public: + AcadColor() + { + int index=10; + for (int ii=10*3;ii<256*3; ) { + // find RGB for each Autocad index colour + unsigned int red = (int)floor(aci::table[ii++]*255.0f); + unsigned int green = (int)floor(aci::table[ii++]*255.0f); + unsigned int blue = (int)floor(aci::table[ii++]*255.0f); + unsigned int rgb = (red<<16) + (green<<8) + blue; + _indexColors[rgb]=index++; + } + // + } + + // returns Autocad index color for supplied RGB. + // if no exact match is found returns nearest color based on hue + // also adds match to cache for future lookups. + int findColor(unsigned int rgb) + { + int aci = 255; + itr = _indexColors.find(rgb); + if (itr != _indexColors.end() ) { + aci = itr->second; + } else { + // not found - match based on hue + aci = nearestColor(rgb); + + // add matching colour to list to cache + _indexColors[rgb]=aci; + } + return aci; + } + +protected: + // returns hue as an angle in range 0-360, saturation and value as 0-1 + void hsv(unsigned int rgb,float &hue,float &sat,float &value) + { + int red = rgb>>16; + int green = (0x0000ff00&rgb)>>8; + int blue = 0x000000ff&rgb; + int H=std::max(std::max(red,green),blue); + int L=std::min(std::min(red,green),blue); + + value = (float)H/255.0f; // note hsv and hsl define v differently! + sat=(float)(H-L)/(float)H; + + if (H==L) { + hue=0.0; + }else if (H==red) { + hue=360.0 + (60.0 * (float)(green-blue)/(float)(H-L)); + if ( hue > 360 ) { hue-=360; } + } else if (H==green) { + hue=120.0 + (60.0 * (float)(blue-red)/(float)(H-L)); + } else if (H==blue) { + hue=240.0 + (60.0 * (float)(red-green)/(float)(H-L)); + } else { + hue = 0.0; + } + } + + int nearestColor(unsigned int rgb) + { + //- match based on hue + float h; + float s; + float v; + hsv(rgb,h,s,v); + + // aci index format is + // last digit odd = 50% sat, even=100% + // last digit 0,1 = 100% value, 2,3=80%, 4,5=60% 6,7=50%, 8,9=30% + // first two sigits are hue angle /1.5 but count starts at 10, first 9 values are dummy named colours + int aci=10 + (int)(h/1.5); + aci -= (aci%10); // ensure last digit is zero + + if ( v < 0.3 ) { + aci += 9; + } else if ( v < 0.5 ) { + aci += 6; + } else if ( v < 0.6 ) { + aci += 4; + } else if ( v < 0.8 ) { + aci += 2; + } else { + // last digit=0; + } + + if ( s<0.5 ) { + aci += 1; + } + + return aci; + } + + + +protected: + + std::map _indexColors; // maps RGB to autocad index colour + std::map _hueColors; // maps hue angle to autocad index colour + + typedef std::pair ColorPair; + std::map::iterator itr; +}; + +class DXFWriterNodeVisitor: public osg::NodeVisitor { + + public: + DXFWriterNodeVisitor(std::ostream& fout) : + osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN), + _fout(fout), + _currentStateSet(new osg::StateSet()), + _firstPass(true) + { + + + } + + static unsigned int getNodeRGB(osg::Geometry *geo,unsigned int index=0) + { + osg::Vec4Array* data=static_cast(geo->getColorArray()); + if ( data && indexsize() ) { + return (data->at(index).asABGR())>>8; + } + return 0; + } + + + bool writeHeader(const osg::BoundingSphere &bound);// call after first pass to trigger draw pass + void writeFooter(); + + void buildColorMap(); + + virtual void apply(osg::Geode &node); + + virtual void apply(osg::Group &node) + { + osg::NodeVisitor::traverse( node ); + + } + + void traverse (osg::Node &node) + { + pushStateSet(node.getStateSet()); + + osg::NodeVisitor::traverse( node ); + + popStateSet(node.getStateSet()); + } + + void pushStateSet(osg::StateSet* ss) + { + if (NULL!=ss) { + // Save our current stateset + _stateSetStack.push(_currentStateSet.get()); + + // merge with node stateset + _currentStateSet = static_cast(_currentStateSet->clone(osg::CopyOp::SHALLOW_COPY)); + _currentStateSet->merge(*ss); + } + } + + + void popStateSet(osg::StateSet* ss) + { + if (NULL!=ss) { + // restore the previous stateset + _currentStateSet = _stateSetStack.top(); + _stateSetStack.pop(); + } + } + + int getNodeAcadColor(osg::Geometry *geo,int index=0) { return 0;} + + protected: + struct CompareStateSet + { + bool operator()(const osg::ref_ptr& ss1, const osg::ref_ptr& ss2) const + { + return ss1->compare(*ss2, true) < 0; + } + }; + + + private: + + DXFWriterNodeVisitor& operator = (const DXFWriterNodeVisitor&) { return *this; } + + // first pass get layer names and draw types + void makeGeometryLayer(osg::Geometry* geo); + + // second pass - output data + void processGeometry(osg::Geometry* geo, osg::Matrix& m); + + + void processArray(osg::Array* array, const Layer& layer,const osg::Matrix& m = osg::Matrix::identity()); + + void processStateSet(osg::StateSet* stateset); + + std::string getLayerName(const std::string& defaultValue = ""); + + typedef std::stack > StateSetStack; + + + + + std::ostream& _fout; + std::list _nameStack; + StateSetStack _stateSetStack; + osg::ref_ptr _currentStateSet; + + unsigned int _count; + std::vector _layers; + bool _firstPass; + Layer _layer; + + AcadColor _acadColor; + + +}; + +#endif diff --git a/src/osgPlugins/dxf/ReaderWriterDXF.cpp b/src/osgPlugins/dxf/ReaderWriterDXF.cpp index d7c5801dc..098de8d49 100644 --- a/src/osgPlugins/dxf/ReaderWriterDXF.cpp +++ b/src/osgPlugins/dxf/ReaderWriterDXF.cpp @@ -23,6 +23,7 @@ #include #include "dxfFile.h" +#include "DXFWriterNodeVisitor.h" using namespace osg; using namespace osgDB; @@ -37,8 +38,67 @@ public: supportsExtension("dxf","Autodesk DXF format"); } - virtual const char* className() { return "Autodesk DXF Reader"; } + virtual const char* className() { return "Autodesk DXF Reader/Writer"; } virtual ReadResult readNode(const std::string& fileName, const osgDB::ReaderWriter::Options*) const; + + + virtual WriteResult writeObject(const osg::Object& obj,const std::string& fileName,const Options* options=NULL) const + { + const osg::Node* node = dynamic_cast(&obj); + if (node) + return writeNode(*node, fileName, options); + else + return WriteResult(WriteResult::FILE_NOT_HANDLED); + } + + + virtual WriteResult writeObject(const osg::Object& obj,std::ostream& fout,const Options* options=NULL) const + { + const osg::Node* node = dynamic_cast(&obj); + if (node) + return writeNode(*node, fout, options); + else + return WriteResult(WriteResult::FILE_NOT_HANDLED); + } + + virtual WriteResult writeNode(const osg::Node& node,std::ostream& fout,const Options* =NULL) const + { + + + DXFWriterNodeVisitor nv(fout); + + (const_cast(&node))->accept(nv); // first pass is to get all node names and types -> layers + + if ( nv.writeHeader(node.getBound()) ) { + (const_cast(&node))->accept(nv); // second pass outputs data + nv.writeFooter(); + } + + return WriteResult(WriteResult::FILE_SAVED); + } + + virtual WriteResult writeNode(const osg::Node& node,const std::string& fileName,const Options* options =NULL) const + { + if (!acceptsExtension(osgDB::getFileExtension(fileName))) + return WriteResult(WriteResult::FILE_NOT_HANDLED); + + osgDB::ofstream f(fileName.c_str()); + + if (!f.is_open() ) { + return WriteResult(WriteResult::ERROR_IN_WRITING_FILE); + } + DXFWriterNodeVisitor nv(f); + + (const_cast(&node))->accept(nv); // first pass is to get all node names and types -> layers + + if ( nv.writeHeader(node.getBound()) ) { + (const_cast(&node))->accept(nv); // second pass outputs data + nv.writeFooter(); + } + + return WriteResult(WriteResult::FILE_SAVED); + } + protected: };