/* * file: vrml_layer.cpp * * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2013 Cirilo Bernardo * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program 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 this program; if not, you may find one here: * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html * or you may search the http://www.gnu.org website for the version 2 license, * or you may write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ // Wishlist: // 1. crop anything outside the board outline on PTH, silk, and copper layers // 2. on the PTH layer, handle cropped holes differently from others; // these are assumed to be castellated edges and the profile is not // a closed loop as assumed for all other outlines. // 3. a scheme is needed to tell a castellated edge from a plain board edge #include #include #include #include #include #ifndef CALLBACK #define CALLBACK #endif #define GLCALLBACK(x) (( void (CALLBACK*)() )&(x)) // minimum sides to a circle #define MIN_NSIDES 6 static void FormatDoublet( double x, double y, int precision, std::string& strx, std::string& stry ) { std::ostringstream ostr; ostr << std::fixed << std::setprecision( precision ); ostr << x; strx = ostr.str(); ostr.str( "" ); ostr << y; stry = ostr.str(); while( *strx.rbegin() == '0' ) strx.erase( strx.size() - 1 ); while( *stry.rbegin() == '0' ) stry.erase( stry.size() - 1 ); } static void FormatSinglet( double x, int precision, std::string& strx ) { std::ostringstream ostr; ostr << std::fixed << std::setprecision( precision ); ostr << x; strx = ostr.str(); while( *strx.rbegin() == '0' ) strx.erase( strx.size() - 1 ); } int VRML_LAYER::calcNSides( double aRadius, double aAngle ) { // check #segments on ends of arc int maxSeg = maxArcSeg * aAngle / M_PI; if( maxSeg < 3 ) maxSeg = 3; int csides = aRadius * M_PI / minSegLength; if( csides < 0 ) csides = -csides; if( csides > maxSeg ) { if( csides < 2 * maxSeg ) csides /= 2; else csides = (((double) csides) * minSegLength / maxSegLength ); } if( csides < 3 ) csides = 3; if( (csides & 1) == 0 ) csides += 1; return csides; } static void CALLBACK vrml_tess_begin( GLenum cmd, void* user_data ) { VRML_LAYER* lp = (VRML_LAYER*) user_data; lp->glStart( cmd ); } static void CALLBACK vrml_tess_end( void* user_data ) { VRML_LAYER* lp = (VRML_LAYER*) user_data; lp->glEnd(); } static void CALLBACK vrml_tess_vertex( void* vertex_data, void* user_data ) { VRML_LAYER* lp = (VRML_LAYER*) user_data; lp->glPushVertex( (VERTEX_3D*) vertex_data ); } static void CALLBACK vrml_tess_err( GLenum errorID, void* user_data ) { VRML_LAYER* lp = (VRML_LAYER*) user_data; lp->Fault = true; lp->SetGLError( errorID ); } static void CALLBACK vrml_tess_combine( GLdouble coords[3], VERTEX_3D* vertex_data[4], GLfloat weight[4], void** outData, void* user_data ) { VRML_LAYER* lp = (VRML_LAYER*) user_data; // the plating is set to true only if all are plated bool plated = vertex_data[0]->pth; if( !vertex_data[1]->pth ) plated = false; if( vertex_data[2] && !vertex_data[2]->pth ) plated = false; if( vertex_data[3] && !vertex_data[3]->pth ) plated = false; *outData = lp->AddExtraVertex( coords[0], coords[1], plated ); } VRML_LAYER::VRML_LAYER() { // arc parameters suitable to mm measurements maxArcSeg = 48; minSegLength = 0.1; maxSegLength = 0.5; offsetX = 0.0; offsetY = 0.0; fix = false; Fault = false; idx = 0; hidx = 0; eidx = 0; ord = 0; glcmd = 0; pholes = NULL; tess = gluNewTess(); if( !tess ) return; // set up the tesselator callbacks gluTessCallback( tess, GLU_TESS_BEGIN_DATA, GLCALLBACK( vrml_tess_begin ) ); gluTessCallback( tess, GLU_TESS_VERTEX_DATA, GLCALLBACK( vrml_tess_vertex ) ); gluTessCallback( tess, GLU_TESS_END_DATA, GLCALLBACK( vrml_tess_end ) ); gluTessCallback( tess, GLU_TESS_ERROR_DATA, GLCALLBACK( vrml_tess_err ) ); gluTessCallback( tess, GLU_TESS_COMBINE_DATA, GLCALLBACK( vrml_tess_combine ) ); gluTessProperty( tess, GLU_TESS_WINDING_RULE, GLU_TESS_WINDING_POSITIVE ); gluTessNormal( tess, 0, 0, 1 ); } VRML_LAYER::~VRML_LAYER() { Clear(); if( tess ) { gluDeleteTess( tess ); tess = NULL; } } void VRML_LAYER::GetArcParams( int& aMaxSeg, double& aMinLength, double& aMaxLength ) { aMaxSeg = maxArcSeg; aMinLength = minSegLength; aMaxLength = maxSegLength; } bool VRML_LAYER::SetArcParams( int aMaxSeg, double aMinLength, double aMaxLength ) { if( aMaxSeg < 8 ) aMaxSeg = 8; if( aMinLength <= 0 || aMaxLength <= aMinLength ) return false; maxArcSeg = aMaxSeg; minSegLength = aMinLength; maxSegLength = aMaxLength; return true; } // clear all data void VRML_LAYER::Clear( void ) { int i; fix = false; idx = 0; for( i = contours.size(); i > 0; --i ) { delete contours.back(); contours.pop_back(); } pth.clear(); areas.clear(); for( i = vertices.size(); i > 0; --i ) { delete vertices.back(); vertices.pop_back(); } clearTmp(); } // clear ephemeral data in between invocations of the tesselation routine void VRML_LAYER::clearTmp( void ) { unsigned int i; Fault = false; hidx = 0; eidx = 0; ord = 0; glcmd = 0; triplets.clear(); solid.clear(); for( i = outline.size(); i > 0; --i ) { delete outline.back(); outline.pop_back(); } ordmap.clear(); for( i = extra_verts.size(); i > 0; --i ) { delete extra_verts.back(); extra_verts.pop_back(); } // note: unlike outline and extra_verts, // vlist is not responsible for memory management vlist.clear(); // go through the vertex list and reset ephemeral parameters for( i = 0; i < vertices.size(); ++i ) { vertices[i]->o = -1; } } // create a new contour to be populated; returns an index // into the contour list or -1 if there are problems int VRML_LAYER::NewContour( bool aPlatedHole ) { if( fix ) return -1; std::list* contour = new std::list; if( !contour ) return -1; contours.push_back( contour ); areas.push_back( 0.0 ); pth.push_back( aPlatedHole ); return contours.size() - 1; } // adds a vertex to the existing list and places its index in // an existing contour; returns true if OK, // false otherwise (indexed contour does not exist) bool VRML_LAYER::AddVertex( int aContourID, double aXpos, double aYpos ) { if( fix ) { error = "AddVertex(): no more vertices may be added (Tesselate was previously executed)"; return false; } if( aContourID < 0 || (unsigned int) aContourID >= contours.size() ) { error = "AddVertex(): aContour is not within a valid range"; return false; } VERTEX_3D* vertex = new VERTEX_3D; if( !vertex ) { error = "AddVertex(): a new vertex could not be allocated"; return false; } vertex->x = aXpos; vertex->y = aYpos; vertex->i = idx++; vertex->o = -1; vertex->pth = pth[ aContourID ]; VERTEX_3D* v2 = NULL; if( contours[aContourID]->size() > 0 ) v2 = vertices[ contours[aContourID]->back() ]; vertices.push_back( vertex ); contours[aContourID]->push_back( vertex->i ); if( v2 ) areas[aContourID] += ( aXpos - v2->x ) * ( aYpos + v2->y ); return true; } // ensure the winding of a contour with respect to the normal (0, 0, 1); // set 'hole' to true to ensure a hole (clockwise winding) bool VRML_LAYER::EnsureWinding( int aContourID, bool aHoleFlag ) { if( aContourID < 0 || (unsigned int) aContourID >= contours.size() ) { error = "EnsureWinding(): aContour is outside the valid range"; return false; } std::list* cp = contours[aContourID]; if( cp->size() < 3 ) { error = "EnsureWinding(): there are fewer than 3 vertices"; return false; } double dir = areas[aContourID]; VERTEX_3D* vp0 = vertices[ cp->back() ]; VERTEX_3D* vp1 = vertices[ cp->front() ]; dir += ( vp1->x - vp0->x ) * ( vp1->y + vp0->y ); // if dir is positive, winding is CW if( ( aHoleFlag && dir < 0 ) || ( !aHoleFlag && dir > 0 ) ) { cp->reverse(); areas[aContourID] = -areas[aContourID]; } return true; } bool VRML_LAYER::AppendCircle( double aXpos, double aYpos, double aRadius, int aContourID, bool aHoleFlag ) { if( aContourID < 0 || (unsigned int) aContourID >= contours.size() ) { error = "AppendCircle(): invalid contour (out of range)"; return false; } int nsides = M_PI * 2.0 * aRadius / minSegLength; if( nsides > maxArcSeg ) { if( nsides > 2 * maxArcSeg ) { // use segments approx. maxAr nsides = M_PI * 2.0 * aRadius / maxSegLength; } else { nsides /= 2; } } if( nsides < MIN_NSIDES ) nsides = MIN_NSIDES; // even numbers give prettier results for circles if( nsides & 1 ) nsides += 1; double da = M_PI * 2.0 / nsides; bool fail = false; if( aHoleFlag ) { fail |= !AddVertex( aContourID, aXpos + aRadius, aYpos ); for( double angle = da; angle < M_PI * 2; angle += da ) fail |= !AddVertex( aContourID, aXpos + aRadius * cos( angle ), aYpos - aRadius * sin( angle ) ); } else { fail |= !AddVertex( aContourID, aXpos + aRadius, aYpos ); for( double angle = da; angle < M_PI * 2; angle += da ) fail |= !AddVertex( aContourID, aXpos + aRadius * cos( angle ), aYpos + aRadius * sin( angle ) ); } return !fail; } // adds a circle the existing list; if 'hole' is true the contour is // a hole. Returns true if OK. bool VRML_LAYER::AddCircle( double aXpos, double aYpos, double aRadius, bool aHoleFlag, bool aPlatedHole ) { int pad; if( aHoleFlag && aPlatedHole ) pad = NewContour( true ); else pad = NewContour( false ); if( pad < 0 ) { error = "AddCircle(): failed to add a contour"; return false; } return AppendCircle( aXpos, aYpos, aRadius, pad, aHoleFlag ); } // adds a slotted pad with orientation given by angle; if 'hole' is true the // contour is a hole. Returns true if OK. bool VRML_LAYER::AddSlot( double aCenterX, double aCenterY, double aSlotLength, double aSlotWidth, double aAngle, bool aHoleFlag, bool aPlatedHole ) { aAngle *= M_PI / 180.0; if( aSlotWidth > aSlotLength ) { aAngle += M_PI2; std::swap( aSlotLength, aSlotWidth ); } aSlotWidth /= 2.0; aSlotLength = aSlotLength / 2.0 - aSlotWidth; int csides = calcNSides( aSlotWidth, M_PI ); double capx, capy; capx = aCenterX + cos( aAngle ) * aSlotLength; capy = aCenterY + sin( aAngle ) * aSlotLength; double ang, da; int i; int pad; if( aHoleFlag && aPlatedHole ) pad = NewContour( true ); else pad = NewContour( false ); if( pad < 0 ) { error = "AddCircle(): failed to add a contour"; return false; } da = M_PI / csides; bool fail = false; if( aHoleFlag ) { for( ang = aAngle + M_PI2, i = 0; i < csides; ang -= da, ++i ) fail |= !AddVertex( pad, capx + aSlotWidth * cos( ang ), capy + aSlotWidth * sin( ang ) ); ang = aAngle - M_PI2; fail |= !AddVertex( pad, capx + aSlotWidth * cos( ang ), capy + aSlotWidth * sin( ang ) ); capx = aCenterX - cos( aAngle ) * aSlotLength; capy = aCenterY - sin( aAngle ) * aSlotLength; for( ang = aAngle - M_PI2, i = 0; i < csides; ang -= da, ++i ) fail |= !AddVertex( pad, capx + aSlotWidth * cos( ang ), capy + aSlotWidth * sin( ang ) ); ang = aAngle + M_PI2; fail |= !AddVertex( pad, capx + aSlotWidth * cos( ang ), capy + aSlotWidth * sin( ang ) ); } else { for( ang = aAngle - M_PI2, i = 0; i < csides; ang += da, ++i ) fail |= !AddVertex( pad, capx + aSlotWidth * cos( ang ), capy + aSlotWidth * sin( ang ) ); ang = aAngle + M_PI2; fail |= !AddVertex( pad, capx + aSlotWidth * cos( ang ), capy + aSlotWidth * sin( ang ) ); capx = aCenterX - cos( aAngle ) * aSlotLength; capy = aCenterY - sin( aAngle ) * aSlotLength; for( ang = aAngle + M_PI2, i = 0; i < csides; ang += da, ++i ) fail |= !AddVertex( pad, capx + aSlotWidth * cos( ang ), capy + aSlotWidth * sin( ang ) ); ang = aAngle - M_PI2; fail |= !AddVertex( pad, capx + aSlotWidth * cos( ang ), capy + aSlotWidth * sin( ang ) ); } return !fail; } // adds an arc to the given center, start point, pen width, and angle (degrees). bool VRML_LAYER::AppendArc( double aCenterX, double aCenterY, double aRadius, double aStartAngle, double aAngle, int aContourID ) { if( aContourID < 0 || (unsigned int) aContourID >= contours.size() ) { error = "AppendArc(): invalid contour (out of range)"; return false; } aAngle = aAngle / 180.0 * M_PI; aStartAngle = aStartAngle / 180.0 * M_PI; int nsides = calcNSides( aRadius, aAngle ); double da = aAngle / nsides; bool fail = false; if( aAngle > 0 ) { aAngle += aStartAngle; for( double ang = aStartAngle; ang < aAngle; ang += da ) fail |= !AddVertex( aContourID, aCenterX + aRadius * cos( ang ), aCenterY + aRadius * sin( ang ) ); } else { aAngle += aStartAngle; for( double ang = aStartAngle; ang > aAngle; ang += da ) fail |= !AddVertex( aContourID, aCenterX + aRadius * cos( ang ), aCenterY + aRadius * sin( ang ) ); } return !fail; } // adds an arc with the given center, start point, pen width, and angle (degrees). bool VRML_LAYER::AddArc( double aCenterX, double aCenterY, double aStartX, double aStartY, double aArcWidth, double aAngle, bool aHoleFlag, bool aPlatedHole ) { aAngle *= M_PI / 180.0; // we don't accept small angles; in fact, 1 degree ( 0.01745 ) is already // way too small but we must set a limit somewhere if( aAngle < 0.01745 && aAngle > -0.01745 ) { error = "AddArc(): angle is too small: abs( angle ) < 1 degree"; return false; } double rad = sqrt( (aStartX - aCenterX) * (aStartX - aCenterX) + (aStartY - aCenterY) * (aStartY - aCenterY) ); aArcWidth /= 2.0; // this is the radius of the caps // we will not accept an arc with an inner radius close to zero so we // set a limit here. the end result will vary somewhat depending on // the output units if( aArcWidth >= ( rad * 1.01 ) ) { error = "AddArc(): width/2 exceeds radius*1.01"; return false; } // calculate the radii of the outer and inner arcs double orad = rad + aArcWidth; double irad = rad - aArcWidth; int osides = calcNSides( orad, aAngle ); int isides = calcNSides( irad, aAngle ); int csides = calcNSides( aArcWidth, M_PI ); double stAngle = atan2( aStartY - aCenterY, aStartX - aCenterX ); double endAngle = stAngle + aAngle; // calculate ends of inner and outer arc double oendx = aCenterX + orad* cos( endAngle ); double oendy = aCenterY + orad* sin( endAngle ); double ostx = aCenterX + orad* cos( stAngle ); double osty = aCenterY + orad* sin( stAngle ); double iendx = aCenterX + irad* cos( endAngle ); double iendy = aCenterY + irad* sin( endAngle ); double istx = aCenterX + irad* cos( stAngle ); double isty = aCenterY + irad* sin( stAngle ); if( ( aAngle < 0 && !aHoleFlag ) || ( aAngle > 0 && aHoleFlag ) ) { aAngle = -aAngle; std::swap( stAngle, endAngle ); std::swap( oendx, ostx ); std::swap( oendy, osty ); std::swap( iendx, istx ); std::swap( iendy, isty ); } int arc; if( aHoleFlag && aPlatedHole ) arc = NewContour( true ); else arc = NewContour( false ); if( arc < 0 ) { error = "AddArc(): could not create a contour"; return false; } // trace the outer arc: int i; double ang; double da = aAngle / osides; for( ang = stAngle, i = 0; i < osides; ang += da, ++i ) AddVertex( arc, aCenterX + orad * cos( ang ), aCenterY + orad * sin( ang ) ); // trace the first cap double capx = ( iendx + oendx ) / 2.0; double capy = ( iendy + oendy ) / 2.0; if( aHoleFlag ) da = -M_PI / csides; else da = M_PI / csides; for( ang = endAngle, i = 0; i < csides; ang += da, ++i ) AddVertex( arc, capx + aArcWidth * cos( ang ), capy + aArcWidth * sin( ang ) ); // trace the inner arc: da = -aAngle / isides; for( ang = endAngle, i = 0; i < isides; ang += da, ++i ) AddVertex( arc, aCenterX + irad * cos( ang ), aCenterY + irad * sin( ang ) ); // trace the final cap capx = ( istx + ostx ) / 2.0; capy = ( isty + osty ) / 2.0; if( aHoleFlag ) da = -M_PI / csides; else da = M_PI / csides; for( ang = stAngle + M_PI, i = 0; i < csides; ang += da, ++i ) AddVertex( arc, capx + aArcWidth * cos( ang ), capy + aArcWidth * sin( ang ) ); return true; } // tesselates the contours in preparation for a 3D output; // returns true if all was fine, false otherwise bool VRML_LAYER::Tesselate( VRML_LAYER* holes, bool aHolesOnly ) { if( !tess ) { error = "Tesselate(): GLU tesselator was not initialized"; return false; } pholes = holes; Fault = false; if( aHolesOnly ) gluTessProperty( tess, GLU_TESS_WINDING_RULE, GLU_TESS_WINDING_NEGATIVE ); else gluTessProperty( tess, GLU_TESS_WINDING_RULE, GLU_TESS_WINDING_POSITIVE ); if( contours.size() < 1 || vertices.size() < 3 ) { error = "Tesselate(): not enough vertices"; return false; } // finish the winding calculation on all vertices prior to setting 'fix' if( !fix ) { for( unsigned int i = 0; i < contours.size(); ++i ) { if( contours[i]->size() < 3 ) continue; VERTEX_3D* vp0 = vertices[ contours[i]->back() ]; VERTEX_3D* vp1 = vertices[ contours[i]->front() ]; areas[i] += ( vp1->x - vp0->x ) * ( vp1->y + vp0->y ); } } // prevent the addition of any further contours and contour vertices fix = true; // clear temporary internals which may have been used in a previous run clearTmp(); // request an outline gluTessProperty( tess, GLU_TESS_BOUNDARY_ONLY, GL_TRUE ); // adjust internal indices for extra points and holes if( holes ) hidx = holes->GetSize(); else hidx = 0; eidx = idx + hidx; if( aHolesOnly && ( checkNContours( true ) == 0 ) ) { error = "tesselate(): no hole contours"; return false; } else if( !aHolesOnly && ( checkNContours( false ) == 0 ) ) { error = "tesselate(): no solid contours"; return false; } // open the polygon gluTessBeginPolygon( tess, this ); if( aHolesOnly ) { pholes = NULL; // do not accept foreign holes hidx = 0; eidx = idx; // add holes pushVertices( true ); gluTessEndPolygon( tess ); if( Fault ) return false; return true; } // add solid outlines pushVertices( false ); // close the polygon gluTessEndPolygon( tess ); if( Fault ) return false; // if there are no outlines we cannot proceed if( outline.empty() ) { error = "tesselate(): no points in result"; return false; } // at this point we have a solid outline; add it to the tesselator gluTessBeginPolygon( tess, this ); if( !pushOutline( NULL ) ) return false; // add the holes contained by this object pushVertices( true ); // import external holes (if any) if( hidx && ( holes->Import( idx, tess ) < 0 ) ) { std::ostringstream ostr; ostr << "Tesselate():FAILED: " << holes->GetError(); error = ostr.str(); return false; } if( Fault ) return false; // erase the previous outline data and vertex order // but preserve the extra vertices while( !outline.empty() ) { delete outline.back(); outline.pop_back(); } ordmap.clear(); ord = 0; // go through the vertex lists and reset ephemeral parameters for( unsigned int i = 0; i < vertices.size(); ++i ) { vertices[i]->o = -1; } for( unsigned int i = 0; i < extra_verts.size(); ++i ) { extra_verts[i]->o = -1; } // close the polygon; this creates the outline points // and the point ordering list 'ordmap' solid.clear(); gluTessEndPolygon( tess ); // repeat the last operation but request a tesselated surface // rather than an outline; this creates the triangles list. gluTessProperty( tess, GLU_TESS_BOUNDARY_ONLY, GL_FALSE ); gluTessBeginPolygon( tess, this ); if( !pushOutline( holes ) ) return false; gluTessEndPolygon( tess ); if( Fault ) return false; return true; } bool VRML_LAYER::pushOutline( VRML_LAYER* holes ) { // traverse the outline list to push all used vertices if( outline.size() < 1 ) { error = "pushOutline() failed: no vertices to push"; return false; } std::list*>::const_iterator obeg = outline.begin(); std::list*>::const_iterator oend = outline.end(); int nc = 0; // number of contours pushed int pi; std::list::const_iterator begin; std::list::const_iterator end; GLdouble pt[3]; VERTEX_3D* vp; while( obeg != oend ) { if( (*obeg)->size() < 3 ) { ++obeg; continue; } gluTessBeginContour( tess ); begin = (*obeg)->begin(); end = (*obeg)->end(); while( begin != end ) { pi = *begin; if( pi < 0 || (unsigned int) pi > ordmap.size() ) { gluTessEndContour( tess ); error = "pushOutline():BUG: *outline.begin() is not a valid index to ordmap"; return false; } // retrieve the actual index pi = ordmap[pi]; vp = getVertexByIndex( pi, holes ); if( !vp ) { gluTessEndContour( tess ); error = "pushOutline():: BUG: ordmap[n] is not a valid index to vertices[]"; return false; } pt[0] = vp->x; pt[1] = vp->y; pt[2] = 0.0; gluTessVertex( tess, pt, vp ); ++begin; } gluTessEndContour( tess ); ++obeg; ++nc; } if( !nc ) { error = "pushOutline():: no valid contours available"; return false; } return true; } // writes out the vertex list for a planar feature bool VRML_LAYER::WriteVertices( double aZcoord, std::ofstream& aOutFile, int aPrecision ) { if( ordmap.size() < 3 ) { error = "WriteVertices(): not enough vertices"; return false; } if( aPrecision < 4 ) aPrecision = 4; int i, j; VERTEX_3D* vp = getVertexByIndex( ordmap[0], pholes ); if( !vp ) return false; std::string strx, stry, strz; FormatDoublet( vp->x + offsetX, vp->y + offsetY, aPrecision, strx, stry ); FormatSinglet( aZcoord, aPrecision, strz ); aOutFile << strx << " " << stry << " " << strz; for( i = 1, j = ordmap.size(); i < j; ++i ) { vp = getVertexByIndex( ordmap[i], pholes ); if( !vp ) return false; FormatDoublet( vp->x + offsetX, vp->y + offsetY, aPrecision, strx, stry ); if( i & 1 ) aOutFile << ", " << strx << " " << stry << " " << strz; else aOutFile << ",\n" << strx << " " << stry << " " << strz; } return !aOutFile.fail(); } // writes out the vertex list for a 3D feature; top and bottom are the // Z values for the top and bottom; top must be > bottom bool VRML_LAYER::Write3DVertices( double aTopZ, double aBottomZ, std::ofstream& aOutFile, int aPrecision ) { if( ordmap.size() < 3 ) { error = "Write3DVertices(): insufficient vertices"; return false; } if( aPrecision < 4 ) aPrecision = 4; if( aTopZ <= aBottomZ ) { error = "Write3DVertices(): top <= bottom"; return false; } int i, j; VERTEX_3D* vp = getVertexByIndex( ordmap[0], pholes ); if( !vp ) return false; std::string strx, stry, strz; FormatDoublet( vp->x + offsetX, vp->y + offsetY, aPrecision, strx, stry ); FormatSinglet( aTopZ, aPrecision, strz ); aOutFile << strx << " " << stry << " " << strz; for( i = 1, j = ordmap.size(); i < j; ++i ) { vp = getVertexByIndex( ordmap[i], pholes ); if( !vp ) return false; FormatDoublet( vp->x + offsetX, vp->y + offsetY, aPrecision, strx, stry ); if( i & 1 ) aOutFile << ", " << strx << " " << stry << " " << strz; else aOutFile << ",\n" << strx << " " << stry << " " << strz; } // repeat for the bottom layer vp = getVertexByIndex( ordmap[0], pholes ); FormatDoublet( vp->x + offsetX, vp->y + offsetY, aPrecision, strx, stry ); FormatSinglet( aBottomZ, aPrecision, strz ); bool endl; if( i & 1 ) { aOutFile << ", " << strx << " " << stry << " " << strz; endl = false; } else { aOutFile << ",\n" << strx << " " << stry << " " << strz; endl = true; } for( i = 1, j = ordmap.size(); i < j; ++i ) { vp = getVertexByIndex( ordmap[i], pholes ); FormatDoublet( vp->x + offsetX, vp->y + offsetY, aPrecision, strx, stry ); if( endl ) { aOutFile << ", " << strx << " " << stry << " " << strz; endl = false; } else { aOutFile << ",\n" << strx << " " << stry << " " << strz; endl = true; } } return !aOutFile.fail(); } // writes out the index list; // 'top' indicates the vertex ordering and should be // true for a polygon visible from above the PCB bool VRML_LAYER::WriteIndices( bool aTopFlag, std::ofstream& aOutFile ) { if( triplets.empty() ) { error = "WriteIndices(): no triplets (triangular facets) to write"; return false; } // go through the triplet list and write out the indices based on order std::list::const_iterator tbeg = triplets.begin(); std::list::const_iterator tend = triplets.end(); int i = 1; if( aTopFlag ) aOutFile << tbeg->i1 << ", " << tbeg->i2 << ", " << tbeg->i3 << ", -1"; else aOutFile << tbeg->i2 << ", " << tbeg->i1 << ", " << tbeg->i3 << ", -1"; ++tbeg; while( tbeg != tend ) { if( (i++ & 7) == 4 ) { i = 1; if( aTopFlag ) aOutFile << ",\n" << tbeg->i1 << ", " << tbeg->i2 << ", " << tbeg->i3 << ", -1"; else aOutFile << ",\n" << tbeg->i2 << ", " << tbeg->i1 << ", " << tbeg->i3 << ", -1"; } else { if( aTopFlag ) aOutFile << ", " << tbeg->i1 << ", " << tbeg->i2 << ", " << tbeg->i3 << ", -1"; else aOutFile << ", " << tbeg->i2 << ", " << tbeg->i1 << ", " << tbeg->i3 << ", -1"; } ++tbeg; } return !aOutFile.fail(); } // writes out the index list for a 3D feature bool VRML_LAYER::Write3DIndices( std::ofstream& aOutFile, bool aIncludePlatedHoles ) { if( outline.empty() ) { error = "WriteIndices(): no outline available"; return false; } char mark; bool holes_only = triplets.empty(); int i = 1; int idx2 = ordmap.size(); // index to the bottom vertices if( !holes_only ) { mark = ','; // go through the triplet list and write out the indices based on order std::list::const_iterator tbeg = triplets.begin(); std::list::const_iterator tend = triplets.end(); // print out the top vertices aOutFile << tbeg->i1 << ", " << tbeg->i2 << ", " << tbeg->i3 << ", -1"; ++tbeg; while( tbeg != tend ) { if( (i++ & 7) == 4 ) { i = 1; aOutFile << ",\n" << tbeg->i1 << ", " << tbeg->i2 << ", " << tbeg->i3 << ", -1"; } else { aOutFile << ", " << tbeg->i1 << ", " << tbeg->i2 << ", " << tbeg->i3 << ", -1"; } ++tbeg; } // print out the bottom vertices tbeg = triplets.begin(); while( tbeg != tend ) { if( (i++ & 7) == 4 ) { i = 1; aOutFile << ",\n" << (tbeg->i2 + idx2) << ", " << (tbeg->i1 + idx2) << ", " << (tbeg->i3 + idx2) << ", -1"; } else { aOutFile << ", " << (tbeg->i2 + idx2) << ", " << (tbeg->i1 + idx2) << ", " << (tbeg->i3 + idx2) << ", -1"; } ++tbeg; } } else mark = ' '; // print out indices for the walls joining top to bottom int lastPoint; int curPoint; int curContour = 0; std::list*>::const_iterator obeg = outline.begin(); std::list*>::const_iterator oend = outline.end(); std::list* cp; std::list::const_iterator cbeg; std::list::const_iterator cend; i = 2; while( obeg != oend ) { cp = *obeg; if( cp->size() < 3 ) { ++obeg; ++curContour; continue; } cbeg = cp->begin(); cend = cp->end(); lastPoint = *(cbeg++); // skip all PTH vertices which are not in a solid outline if( !aIncludePlatedHoles && !solid[curContour] && getVertexByIndex( ordmap[lastPoint], pholes )->pth ) { ++obeg; ++curContour; continue; } while( cbeg != cend ) { curPoint = *(cbeg++); if( !holes_only ) { if( (i++ & 3) == 2 ) { i = 1; aOutFile << mark << "\n" << curPoint << ", " << lastPoint << ", " << curPoint + idx2; aOutFile << ", -1, " << curPoint + idx2 << ", " << lastPoint << ", " << lastPoint + idx2 << ", -1"; } else { aOutFile << mark << " " << curPoint << ", " << lastPoint << ", " << curPoint + idx2; aOutFile << ", -1, " << curPoint + idx2 << ", " << lastPoint << ", " << lastPoint + idx2 << ", -1"; } } else { if( (i++ & 3) == 2 ) { i = 1; aOutFile << mark << "\n" << curPoint << ", " << curPoint + idx2 << ", " << lastPoint; aOutFile << ", -1, " << curPoint + idx2 << ", " << lastPoint + idx2 << ", " << lastPoint << ", -1"; } else { aOutFile << mark << " " << curPoint << ", " << curPoint + idx2 << ", " << lastPoint; aOutFile << ", -1, " << curPoint + idx2 << ", " << lastPoint + idx2 << ", " << lastPoint << ", -1"; } } mark = ','; lastPoint = curPoint; } // check if the loop needs to be closed cbeg = cp->begin(); cend = --cp->end(); curPoint = *(cbeg); lastPoint = *(cend); if( !holes_only ) { if( (i++ & 3) == 2 ) { aOutFile << ",\n" << curPoint << ", " << lastPoint << ", " << curPoint + idx2; aOutFile << ", -1, " << curPoint + idx2 << ", " << lastPoint << ", " << lastPoint + idx2 << ", -1"; } else { aOutFile << ", " << curPoint << ", " << lastPoint << ", " << curPoint + idx2; aOutFile << ", -1, " << curPoint + idx2 << ", " << lastPoint << ", " << lastPoint + idx2 << ", -1"; } } else { if( (i++ & 3) == 2 ) { aOutFile << ",\n" << curPoint << ", " << curPoint + idx2 << ", " << lastPoint; aOutFile << ", -1, " << curPoint + idx2 << ", " << lastPoint + idx2 << ", " << lastPoint << ", -1"; } else { aOutFile << ", " << curPoint << ", " << curPoint + idx2 << ", " << lastPoint; aOutFile << ", -1, " << curPoint + idx2 << ", " << lastPoint + idx2 << ", " << lastPoint << ", -1"; } } ++obeg; ++curContour; } return !aOutFile.fail(); } // add a triangular facet (triplet) to the ouptut index list bool VRML_LAYER::addTriplet( VERTEX_3D* p0, VERTEX_3D* p1, VERTEX_3D* p2 ) { double dx0 = p1->x - p0->x; double dx1 = p2->x - p0->x; double dy0 = p1->y - p0->y; double dy1 = p2->y - p0->y; // this number is chosen because we shall only write 9 decimal places // at most on the VRML output double err = 0.000000001; // test if the triangles are degenerate (parallel sides) if( dx0 < err && dx0 > -err && dx1 < err && dx1 > -err ) return false; if( dy0 < err && dy0 > -err && dy1 < err && dy1 > -err ) return false; double sl0 = dy0 / dx0; double sl1 = dy1 / dx1; double dsl = sl1 - sl0; if( dsl < err && dsl > -err ) return false; triplets.push_back( TRIPLET_3D( p0->o, p1->o, p2->o ) ); return true; } // add an extra vertex (to be called only by the COMBINE callback) VERTEX_3D* VRML_LAYER::AddExtraVertex( double aXpos, double aYpos, bool aPlatedHole ) { VERTEX_3D* vertex = new VERTEX_3D; if( !vertex ) { error = "AddExtraVertex(): could not allocate a new vertex"; return NULL; } if( eidx == 0 ) eidx = idx + hidx; vertex->x = aXpos; vertex->y = aYpos; vertex->i = eidx++; vertex->o = -1; vertex->pth = aPlatedHole; extra_verts.push_back( vertex ); return vertex; } // start a GL command list void VRML_LAYER::glStart( GLenum cmd ) { glcmd = cmd; while( !vlist.empty() ) vlist.pop_back(); } // process a vertex void VRML_LAYER::glPushVertex( VERTEX_3D* vertex ) { if( vertex->o < 0 ) { vertex->o = ord++; ordmap.push_back( vertex->i ); } vlist.push_back( vertex ); } // end a GL command list void VRML_LAYER::glEnd( void ) { switch( glcmd ) { case GL_LINE_LOOP: { // add the loop to the list of outlines std::list* loop = new std::list; if( !loop ) break; double firstX = 0.0; double firstY = 0.0; double lastX = 0.0; double lastY = 0.0; double curX, curY; double area = 0.0; if( vlist.size() > 0 ) { loop->push_back( vlist[0]->o ); firstX = vlist[0]->x; firstY = vlist[0]->y; lastX = firstX; lastY = firstY; } for( size_t i = 1; i < vlist.size(); ++i ) { loop->push_back( vlist[i]->o ); curX = vlist[i]->x; curY = vlist[i]->y; area += ( curX - lastX ) * ( curY + lastY ); lastX = curX; lastY = curY; } area += ( firstX - lastX ) * ( firstY + lastY ); outline.push_back( loop ); if( area <= 0.0 ) solid.push_back( true ); else solid.push_back( false ); } break; case GL_TRIANGLE_FAN: processFan(); break; case GL_TRIANGLE_STRIP: processStrip(); break; case GL_TRIANGLES: processTri(); break; default: break; } while( !vlist.empty() ) vlist.pop_back(); glcmd = 0; } // set the error message void VRML_LAYER::SetGLError( GLenum errorID ) { const char * msg = (const char*)gluErrorString( errorID ); // If errorID is an illegal id, gluErrorString returns NULL if( msg ) error = msg; else error.clear(); if( error.empty() ) { std::ostringstream ostr; ostr << "Unknown OpenGL error: " << errorID; error = ostr.str(); } } // process a GL_TRIANGLE_FAN list void VRML_LAYER::processFan( void ) { if( vlist.size() < 3 ) return; VERTEX_3D* p0 = vlist[0]; int i; int end = vlist.size(); for( i = 2; i < end; ++i ) { addTriplet( p0, vlist[i - 1], vlist[i] ); } } // process a GL_TRIANGLE_STRIP list void VRML_LAYER::processStrip( void ) { // note: (source: http://www.opengl.org/wiki/Primitive) // GL_TRIANGLE_STRIP​: Every group of 3 adjacent vertices forms a triangle. // The face direction of the strip is determined by the winding of the // first triangle. Each successive triangle will have its effective face // order reverse, so the system compensates for that by testing it in the // opposite way. A vertex stream of n length will generate n-2 triangles. if( vlist.size() < 3 ) return; int i; int end = vlist.size(); bool flip = false; for( i = 2; i < end; ++i ) { if( flip ) { addTriplet( vlist[i - 1], vlist[i - 2], vlist[i] ); flip = false; } else { addTriplet( vlist[i - 2], vlist[i - 1], vlist[i] ); flip = true; } } } // process a GL_TRIANGLES list void VRML_LAYER::processTri( void ) { // notes: // 1. each successive group of 3 vertices is a triangle // 2. as per OpenGL specification, any incomplete triangles are to be ignored if( vlist.size() < 3 ) return; int i; int end = vlist.size(); for( i = 2; i < end; i += 3 ) addTriplet( vlist[i - 2], vlist[i - 1], vlist[i] ); } int VRML_LAYER::checkNContours( bool holes ) { int nc = 0; // number of contours if( contours.empty() ) return 0; for( size_t i = 0; i < contours.size(); ++i ) { if( contours[i]->size() < 3 ) continue; if( ( holes && areas[i] <= 0.0 ) || ( !holes && areas[i] > 0.0 ) ) continue; ++nc; } return nc; } // push the internally held vertices void VRML_LAYER::pushVertices( bool holes ) { // push the internally held vertices unsigned int i; std::list::const_iterator begin; std::list::const_iterator end; GLdouble pt[3]; VERTEX_3D* vp; for( i = 0; i < contours.size(); ++i ) { if( contours[i]->size() < 3 ) continue; if( ( holes && areas[i] <= 0.0 ) || ( !holes && areas[i] > 0.0 ) ) continue; gluTessBeginContour( tess ); begin = contours[i]->begin(); end = contours[i]->end(); while( begin != end ) { vp = vertices[ *begin ]; pt[0] = vp->x; pt[1] = vp->y; pt[2] = 0.0; gluTessVertex( tess, pt, vp ); ++begin; } gluTessEndContour( tess ); } return; } VERTEX_3D* VRML_LAYER::getVertexByIndex( int aPointIndex, VRML_LAYER* holes ) { if( aPointIndex < 0 || (unsigned int) aPointIndex >= ( idx + hidx + extra_verts.size() ) ) { error = "getVertexByIndex():BUG: invalid index"; return NULL; } if( aPointIndex < idx ) { // vertex is in the vertices[] list return vertices[ aPointIndex ]; } else if( aPointIndex >= idx + hidx ) { // vertex is in the extra_verts[] list return extra_verts[aPointIndex - idx - hidx]; } // vertex is in the holes object if( !holes ) { error = "getVertexByIndex():BUG: invalid index"; return NULL; } VERTEX_3D* vp = holes->GetVertexByIndex( aPointIndex ); if( !vp ) { std::ostringstream ostr; ostr << "getVertexByIndex():FAILED: " << holes->GetError(); error = ostr.str(); return NULL; } return vp; } // retrieve the total number of vertices int VRML_LAYER::GetSize( void ) { return vertices.size(); } // Inserts all contours into the given tesselator; this results in the // renumbering of all vertices from 'start'. Returns the end number. // Take care when using this call since tesselators cannot work on // the internal data concurrently int VRML_LAYER::Import( int start, GLUtesselator* tess ) { if( start < 0 ) { error = "Import(): invalid index ( start < 0 )"; return -1; } if( !tess ) { error = "Import(): NULL tesselator pointer"; return -1; } unsigned int i, j; // renumber from 'start' for( i = 0, j = vertices.size(); i < j; ++i ) { vertices[i]->i = start++; vertices[i]->o = -1; } // push each contour to the tesselator VERTEX_3D* vp; GLdouble pt[3]; std::list::const_iterator cbeg; std::list::const_iterator cend; for( i = 0; i < contours.size(); ++i ) { if( contours[i]->size() < 3 ) continue; cbeg = contours[i]->begin(); cend = contours[i]->end(); gluTessBeginContour( tess ); while( cbeg != cend ) { vp = vertices[ *cbeg++ ]; pt[0] = vp->x; pt[1] = vp->y; pt[2] = 0.0; gluTessVertex( tess, pt, vp ); } gluTessEndContour( tess ); } return start; } // return the vertex identified by index VERTEX_3D* VRML_LAYER::GetVertexByIndex( int aPointIndex ) { int i0 = vertices[0]->i; if( aPointIndex < i0 || aPointIndex >= ( i0 + (int) vertices.size() ) ) { error = "GetVertexByIndex(): invalid index"; return NULL; } return vertices[aPointIndex - i0]; } // return the error string const std::string& VRML_LAYER::GetError( void ) { return error; } void VRML_LAYER::SetVertexOffsets( double aXoffset, double aYoffset ) { offsetX = aXoffset; offsetY = aYoffset; return; }