mirror of
https://gitlab.com/kicad/code/kicad.git
synced 2025-09-14 02:03:12 +02:00
Recommendation is to avoid using the year nomenclature as this information is already encoded in the git repo. Avoids needing to repeatly update. Also updates AUTHORS.txt from current repo with contributor names
975 lines
26 KiB
C++
975 lines
26 KiB
C++
/*
|
|
* This program source code file is part of KiCad, a free EDA CAD application.
|
|
*
|
|
* Copyright (C) 2014-2017 Cirilo Bernardo
|
|
* Copyright The KiCad Developers, see AUTHORS.txt for contributors.
|
|
*
|
|
* 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
|
|
*/
|
|
|
|
/*
|
|
* This program takes an IDF base name, loads the board outline
|
|
* and component outine files, and creates a single VRML file.
|
|
* The VRML file can be used to visually verify the IDF files
|
|
* before sending them to a mechanical designer. The output scale
|
|
* is 10:1; this scale was chosen because VRML was originally
|
|
* intended to describe large virtual worlds and rounding errors
|
|
* would be more likely if we used a 1:1 scale.
|
|
*/
|
|
|
|
#include <wx/app.h>
|
|
#include <wx/cmdline.h>
|
|
#include <wx/log.h>
|
|
#include <wx/string.h>
|
|
|
|
#include <iostream>
|
|
#include <iomanip>
|
|
#include <fstream>
|
|
#include <string>
|
|
#include <sstream>
|
|
#include <cmath>
|
|
#include <cstdio>
|
|
#include <cerrno>
|
|
#include <list>
|
|
#include <utility>
|
|
#include <vector>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <algorithm>
|
|
#include <boost/ptr_container/ptr_map.hpp>
|
|
|
|
#include <wx_filename.h>
|
|
|
|
#include "idf_helpers.h"
|
|
#include "idf_common.h"
|
|
#include "idf_parser.h"
|
|
#include "streamwrapper.h"
|
|
#include "vrml_layer.h"
|
|
|
|
#ifndef MIN_ANG
|
|
#define MIN_ANG 0.01
|
|
#endif
|
|
|
|
class IDF2VRML : public wxAppConsole
|
|
{
|
|
public:
|
|
virtual bool OnInit() override;
|
|
virtual int OnRun() override;
|
|
virtual void OnInitCmdLine(wxCmdLineParser& parser) override;
|
|
virtual bool OnCmdLineParsed(wxCmdLineParser& parser) override;
|
|
|
|
private:
|
|
double m_ScaleFactor;
|
|
bool m_Compact;
|
|
bool m_NoOutlineSubs;
|
|
wxString m_filename;
|
|
};
|
|
|
|
static const wxCmdLineEntryDesc cmdLineDesc[] =
|
|
{
|
|
{ wxCMD_LINE_OPTION, "f", NULL, "input file name",
|
|
wxCMD_LINE_VAL_STRING, wxCMD_LINE_OPTION_MANDATORY },
|
|
{ wxCMD_LINE_OPTION, "s", NULL, "scale factor",
|
|
wxCMD_LINE_VAL_DOUBLE, wxCMD_LINE_PARAM_OPTIONAL },
|
|
{ wxCMD_LINE_SWITCH, "k", NULL, "produce KiCad-friendly VRML output; default is compact VRML",
|
|
wxCMD_LINE_VAL_NONE, wxCMD_LINE_PARAM_OPTIONAL },
|
|
{ wxCMD_LINE_SWITCH, "d", NULL, "suppress substitution of default outlines",
|
|
wxCMD_LINE_VAL_NONE, wxCMD_LINE_PARAM_OPTIONAL },
|
|
{ wxCMD_LINE_SWITCH, "z", NULL, "suppress rendering of zero-height outlines",
|
|
wxCMD_LINE_VAL_NONE, wxCMD_LINE_PARAM_OPTIONAL },
|
|
{ wxCMD_LINE_SWITCH, "m", NULL, "print object mapping to stdout for debugging",
|
|
wxCMD_LINE_VAL_NONE, wxCMD_LINE_PARAM_OPTIONAL },
|
|
{ wxCMD_LINE_SWITCH, "h", NULL, "display this message",
|
|
wxCMD_LINE_VAL_NONE, wxCMD_LINE_OPTION_HELP },
|
|
{ wxCMD_LINE_NONE, nullptr, nullptr, nullptr, wxCMD_LINE_VAL_NONE, 0 }
|
|
};
|
|
|
|
|
|
wxIMPLEMENT_APP_CONSOLE( IDF2VRML );
|
|
|
|
bool nozeroheights;
|
|
bool showObjectMapping;
|
|
|
|
bool IDF2VRML::OnInit()
|
|
{
|
|
m_ScaleFactor = 1.0;
|
|
m_Compact = true;
|
|
m_NoOutlineSubs = false;
|
|
nozeroheights = false;
|
|
showObjectMapping = false;
|
|
|
|
if( !wxAppConsole::OnInit() )
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
void IDF2VRML::OnInitCmdLine( wxCmdLineParser& parser )
|
|
{
|
|
parser.SetDesc( cmdLineDesc );
|
|
parser.SetSwitchChars( wxT( "-" ) );
|
|
return;
|
|
}
|
|
|
|
|
|
bool IDF2VRML::OnCmdLineParsed( wxCmdLineParser& parser )
|
|
{
|
|
if( parser.Found( wxT( "k" ) ) )
|
|
m_Compact = false;
|
|
|
|
double scale;
|
|
|
|
if( parser.Found( wxT( "s" ), &scale ) )
|
|
m_ScaleFactor = scale;
|
|
|
|
wxString fname;
|
|
|
|
if( parser.Found( wxT( "f" ), &fname ) )
|
|
m_filename = fname;
|
|
|
|
if( parser.Found( wxT( "d" ) ) )
|
|
m_NoOutlineSubs = true;
|
|
|
|
if( parser.Found( wxT( "z" ) ) )
|
|
nozeroheights = true;
|
|
|
|
if( parser.Found( wxT( "m" ) ) )
|
|
showObjectMapping = true;
|
|
|
|
return true;
|
|
}
|
|
|
|
using namespace boost;
|
|
|
|
// define colors
|
|
struct VRML_COLOR
|
|
{
|
|
double diff[3];
|
|
double emis[3];
|
|
double spec[3];
|
|
double ambi;
|
|
double tran;
|
|
double shin;
|
|
};
|
|
|
|
struct VRML_IDS
|
|
{
|
|
int colorIndex;
|
|
std::string objectName;
|
|
bool used;
|
|
bool bottom;
|
|
double dX, dY, dZ, dA;
|
|
|
|
VRML_IDS()
|
|
{
|
|
colorIndex = 0;
|
|
used = false;
|
|
bottom = false;
|
|
dX = 0.0;
|
|
dY = 0.0;
|
|
dZ = 0.0;
|
|
dA = 0.0;
|
|
}
|
|
};
|
|
|
|
#define NCOLORS 7
|
|
VRML_COLOR colors[NCOLORS] =
|
|
{
|
|
{ { 0, 0.82, 0.247 }, { 0, 0, 0 }, { 0, 0.82, 0.247 }, 0.9, 0, 0.1 },
|
|
{ { 1, 0, 0 }, { 1, 0, 0 }, { 1, 0, 0 }, 0.9, 0, 0.1 },
|
|
{ { 0.659, 0, 0.463 }, { 0, 0, 0 }, { 0.659, 0, 0.463 }, 0.9, 0, 0.1 },
|
|
{ { 0.659, 0.294, 0 }, { 0, 0, 0 }, { 0.659, 0.294, 0 }, 0.9, 0, 0.1 },
|
|
{ { 0, 0.918, 0.659 }, { 0, 0, 0 }, { 0, 0.918, 0.659 }, 0.9, 0, 0.1 },
|
|
{ { 0.808, 0.733, 0.071 }, { 0, 0, 0 }, { 0.808, 0.733 , 0.071 }, 0.9, 0, 0.1 },
|
|
{ { 0.102, 1, 0.984 }, { 0, 0, 0 }, { 0.102, 1, 0.984 }, 0.9, 0, 0.1 }
|
|
};
|
|
|
|
bool WriteHeader( IDF3_BOARD& board, std::ostream& file );
|
|
bool MakeBoard( IDF3_BOARD& board, std::ostream& file );
|
|
bool MakeComponents( IDF3_BOARD& board, std::ostream& file, bool compact );
|
|
bool MakeOtherOutlines( IDF3_BOARD& board, std::ostream& file );
|
|
bool PopulateVRML( VRML_LAYER& model, const std::list< IDF_OUTLINE* >* items, bool bottom,
|
|
double scale, double dX = 0.0, double dY = 0.0, double angle = 0.0 );
|
|
bool AddSegment( VRML_LAYER& model, IDF_SEGMENT* seg, int icont, int iseg );
|
|
bool WriteTriangles( std::ostream& file, VRML_IDS* vID, VRML_LAYER* layer, bool plane,
|
|
bool top, double top_z, double bottom_z, int precision, bool compact );
|
|
inline void TransformPoint( IDF_SEGMENT& seg, double frac, bool bottom,
|
|
double dX, double dY, double angle );
|
|
VRML_IDS* GetColor( boost::ptr_map<const std::string, VRML_IDS>& cmap,
|
|
int& index, const std::string& uid );
|
|
|
|
|
|
int IDF2VRML::OnRun()
|
|
{
|
|
// Essential inputs:
|
|
// 1. IDF file
|
|
// 2. Output scale: internal IDF units are mm, so 1 = 1mm per VRML unit,
|
|
// 0.1 = 1cm per VRML unit, 0.01 = 1m per VRML unit,
|
|
// 1/25.4 = 1in per VRML unit, 1/2.54 = 0.1in per VRML unit (KiCad model)
|
|
// 3. KiCad-friendly output (do not reuse features via DEF+USE)
|
|
// Render each component to VRML; if the user wants
|
|
// a KiCad friendly output then we must avoid DEF+USE;
|
|
// otherwise we employ DEF+USE to minimize file size
|
|
|
|
if( m_ScaleFactor < 0.001 || m_ScaleFactor > 10.0 )
|
|
{
|
|
wxLogMessage( wxT( "scale factor out of range (%d); range is 0.001 to 10.0" ),
|
|
m_ScaleFactor);
|
|
return -1;
|
|
}
|
|
|
|
IDF3_BOARD pcb( IDF3::CAD_ELEC );
|
|
|
|
wxLogMessage( wxT( "Reading file: '%s'" ), m_filename );
|
|
|
|
if( !pcb.ReadFile( m_filename, m_NoOutlineSubs ) )
|
|
{
|
|
wxLogMessage( wxT( "Failed to read IDF data: %s" ), pcb.GetError() );
|
|
return -1;
|
|
}
|
|
|
|
// set the scale and output precision ( scale 1 == precision 5)
|
|
pcb.SetUserScale( m_ScaleFactor );
|
|
|
|
if( m_ScaleFactor < 0.01 )
|
|
pcb.SetUserPrecision( 8 );
|
|
else if( m_ScaleFactor < 0.1 )
|
|
pcb.SetUserPrecision( 7 );
|
|
else if( m_ScaleFactor < 1.0 )
|
|
pcb.SetUserPrecision( 6 );
|
|
else if( m_ScaleFactor < 10.0 )
|
|
pcb.SetUserPrecision( 5 );
|
|
else
|
|
pcb.SetUserPrecision( 4 );
|
|
|
|
// Create the VRML file and write the header
|
|
wxFileName fname( m_filename );
|
|
fname.SetExt( wxT( "wrl" ) );
|
|
fname.Normalize( FN_NORMALIZE_FLAGS | wxPATH_NORM_ENV_VARS );
|
|
wxLogMessage( wxT( "Writing file: '%s'" ), fname.GetFullName() );
|
|
|
|
OPEN_OSTREAM( ofile, fname.GetFullPath().ToUTF8() );
|
|
|
|
if( ofile.fail() )
|
|
wxLogMessage( wxT( "Could not open file: '%s'" ), fname.GetFullName() );
|
|
|
|
ofile.imbue( std::locale( "C" ) );
|
|
ofile << std::fixed; // do not use exponents in VRML output
|
|
WriteHeader( pcb, ofile );
|
|
|
|
// STEP 1: Render the PCB alone
|
|
MakeBoard( pcb, ofile );
|
|
|
|
// STEP 2: Render the components
|
|
MakeComponents( pcb, ofile, m_Compact );
|
|
|
|
// STEP 3: Render the OTHER outlines
|
|
MakeOtherOutlines( pcb, ofile );
|
|
|
|
ofile << "]\n}\n";
|
|
CLOSE_STREAM( ofile );
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
bool WriteHeader( IDF3_BOARD& board, std::ostream& file )
|
|
{
|
|
std::string bname = board.GetBoardName();
|
|
|
|
if( bname.empty() )
|
|
{
|
|
bname = "BoardWithNoName";
|
|
}
|
|
else
|
|
{
|
|
std::string::iterator ss = bname.begin();
|
|
std::string::iterator se = bname.end();
|
|
|
|
while( ss != se )
|
|
{
|
|
if( *ss == '/' || *ss == ' ' || *ss == ':' )
|
|
*ss = '_';
|
|
|
|
++ss;
|
|
}
|
|
}
|
|
|
|
file << "#VRML V2.0 utf8\n\n";
|
|
file << "WorldInfo {\n";
|
|
file << " title \"" << bname << "\"\n}\n\n";
|
|
file << "Transform {\n";
|
|
file << "children [\n";
|
|
|
|
return !file.fail();
|
|
}
|
|
|
|
|
|
bool MakeBoard( IDF3_BOARD& board, std::ostream& file )
|
|
{
|
|
VRML_LAYER vpcb;
|
|
|
|
if( board.GetBoardOutlinesSize() < 1 )
|
|
{
|
|
wxLogMessage( wxT( "Cannot proceed; no board outline in IDF object" ) );
|
|
return false;
|
|
}
|
|
|
|
double scale = board.GetUserScale();
|
|
|
|
// set the arc parameters according to output scale
|
|
int tI;
|
|
double tMin, tMax;
|
|
vpcb.GetArcParams( tI, tMin, tMax );
|
|
vpcb.SetArcParams( tI, tMin * scale, tMax * scale );
|
|
|
|
if( !PopulateVRML( vpcb, board.GetBoardOutline()->GetOutlines(), false, board.GetUserScale() ) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
vpcb.EnsureWinding( 0, false );
|
|
|
|
int nvcont = vpcb.GetNContours() - 1;
|
|
|
|
while( nvcont > 0 )
|
|
vpcb.EnsureWinding( nvcont--, true );
|
|
|
|
// Add the drill holes
|
|
const std::list<IDF_DRILL_DATA*>* drills = &board.GetBoardDrills();
|
|
|
|
std::list<IDF_DRILL_DATA*>::const_iterator sd = drills->begin();
|
|
std::list<IDF_DRILL_DATA*>::const_iterator ed = drills->end();
|
|
|
|
while( sd != ed )
|
|
{
|
|
vpcb.AddCircle( (*sd)->GetDrillXPos() * scale, (*sd)->GetDrillYPos() * scale,
|
|
(*sd)->GetDrillDia() * scale / 2.0, true );
|
|
++sd;
|
|
}
|
|
|
|
std::map< std::string, IDF3_COMPONENT* >*const comp = board.GetComponents();
|
|
std::map< std::string, IDF3_COMPONENT* >::const_iterator sc = comp->begin();
|
|
std::map< std::string, IDF3_COMPONENT* >::const_iterator ec = comp->end();
|
|
|
|
while( sc != ec )
|
|
{
|
|
drills = sc->second->GetDrills();
|
|
sd = drills->begin();
|
|
ed = drills->end();
|
|
|
|
while( sd != ed )
|
|
{
|
|
vpcb.AddCircle( (*sd)->GetDrillXPos() * scale, (*sd)->GetDrillYPos() * scale,
|
|
(*sd)->GetDrillDia() * scale / 2.0, true );
|
|
++sd;
|
|
}
|
|
|
|
++sc;
|
|
}
|
|
|
|
// tesselate and write out
|
|
vpcb.Tesselate( NULL );
|
|
|
|
double thick = board.GetBoardThickness() / 2.0 * scale;
|
|
|
|
VRML_IDS tvid;
|
|
tvid.colorIndex = 0;
|
|
|
|
WriteTriangles( file, &tvid, &vpcb, false, false,
|
|
thick, -thick, board.GetUserPrecision(), false );
|
|
|
|
return true;
|
|
}
|
|
|
|
bool PopulateVRML( VRML_LAYER& model, const std::list< IDF_OUTLINE* >* items, bool bottom, double scale,
|
|
double dX, double dY, double angle )
|
|
{
|
|
// empty outlines are not unusual so we fail quietly
|
|
if( items->size() < 1 )
|
|
return false;
|
|
|
|
int nvcont = 0;
|
|
int iseg = 0;
|
|
|
|
std::list< IDF_OUTLINE* >::const_iterator scont = items->begin();
|
|
std::list< IDF_OUTLINE* >::const_iterator econt = items->end();
|
|
std::list<IDF_SEGMENT*>::iterator sseg;
|
|
std::list<IDF_SEGMENT*>::iterator eseg;
|
|
|
|
IDF_SEGMENT lseg;
|
|
|
|
while( scont != econt )
|
|
{
|
|
nvcont = model.NewContour();
|
|
|
|
if( nvcont < 0 )
|
|
{
|
|
wxLogMessage( wxT( "Cannot create an outline" ) );
|
|
return false;
|
|
}
|
|
|
|
if( (*scont)->size() < 1 )
|
|
{
|
|
wxLogMessage( wxT( "Invalid contour: no vertices" ) );
|
|
return false;
|
|
}
|
|
|
|
sseg = (*scont)->begin();
|
|
eseg = (*scont)->end();
|
|
|
|
iseg = 0;
|
|
while( sseg != eseg )
|
|
{
|
|
lseg = **sseg;
|
|
TransformPoint( lseg, scale, bottom, dX, dY, angle );
|
|
|
|
if( !AddSegment( model, &lseg, nvcont, iseg ) )
|
|
return false;
|
|
|
|
++iseg;
|
|
++sseg;
|
|
}
|
|
|
|
++scont;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool AddSegment( VRML_LAYER& model, IDF_SEGMENT* seg, int icont, int iseg )
|
|
{
|
|
// note: in all cases we must add all but the last point in the segment
|
|
// to avoid redundant points
|
|
|
|
if( seg->angle != 0.0 )
|
|
{
|
|
if( seg->IsCircle() )
|
|
{
|
|
if( iseg != 0 )
|
|
{
|
|
wxLogMessage( wxT( "Adding a circle to an existing vertex list" ) );
|
|
return false;
|
|
}
|
|
|
|
return model.AppendCircle( seg->center.x, seg->center.y, seg->radius, icont );
|
|
}
|
|
else
|
|
{
|
|
return model.AppendArc( seg->center.x, seg->center.y, seg->radius,
|
|
seg->offsetAngle, seg->angle, icont );
|
|
}
|
|
}
|
|
|
|
if( !model.AddVertex( icont, seg->startPoint.x, seg->startPoint.y ) )
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool WriteTriangles( std::ostream& file, VRML_IDS* vID, VRML_LAYER* layer, bool plane,
|
|
bool top, double top_z, double bottom_z, int precision, bool compact )
|
|
{
|
|
if( vID == NULL || layer == NULL )
|
|
return false;
|
|
|
|
file << "Transform {\n";
|
|
|
|
if( compact && !vID->objectName.empty() )
|
|
{
|
|
file << "translation " << std::setprecision( precision ) << vID->dX;
|
|
file << " " << vID->dY << " ";
|
|
|
|
if( vID->bottom )
|
|
{
|
|
file << -vID->dZ << "\n";
|
|
|
|
double tx, ty;
|
|
|
|
// calculate the rotation axis and angle
|
|
tx = cos( M_PI2 - vID->dA / 2.0 );
|
|
ty = sin( M_PI2 - vID->dA / 2.0 );
|
|
|
|
file << "rotation " << std::setprecision( precision );
|
|
file << tx << " " << ty << " 0 ";
|
|
file << std::setprecision(5) << M_PI << "\n";
|
|
}
|
|
else
|
|
{
|
|
file << vID->dZ << "\n";
|
|
file << "rotation 0 0 1 " << std::setprecision(5) << vID->dA << "\n";
|
|
}
|
|
|
|
file << "children [\n";
|
|
|
|
if( vID->used )
|
|
{
|
|
file << "USE " << vID->objectName << "\n";
|
|
file << "]\n";
|
|
file << "}\n";
|
|
return true;
|
|
}
|
|
|
|
file << "DEF " << vID->objectName << " Transform {\n";
|
|
|
|
if( !plane && top_z <= bottom_z )
|
|
{
|
|
// the height specification is faulty; make the component
|
|
// a bright red to highlight it
|
|
vID->colorIndex = 1;
|
|
// we don't know the scale, but 5 units is huge in most situations
|
|
top_z = bottom_z + 5.0;
|
|
}
|
|
|
|
}
|
|
|
|
VRML_COLOR* color = &colors[vID->colorIndex];
|
|
|
|
vID->used = true;
|
|
|
|
file << "children [\n";
|
|
file << "Group {\n";
|
|
file << "children [\n";
|
|
file << "Shape {\n";
|
|
file << "appearance Appearance {\n";
|
|
file << "material Material {\n";
|
|
|
|
// material definition
|
|
file << "diffuseColor " << std::setprecision(3) << color->diff[0] << " ";
|
|
file << color->diff[1] << " " << color->diff[2] << "\n";
|
|
file << "specularColor " << color->spec[0] << " " << color->spec[1];
|
|
file << " " << color->spec[2] << "\n";
|
|
file << "emissiveColor " << color->emis[0] << " " << color->emis[1];
|
|
file << " " << color->emis[2] << "\n";
|
|
file << "ambientIntensity " << color->ambi << "\n";
|
|
file << "transparency " << color->tran << "\n";
|
|
file << "shininess " << color->shin << "\n";
|
|
|
|
file << "}\n";
|
|
file << "}\n";
|
|
file << "geometry IndexedFaceSet {\n";
|
|
file << "solid TRUE\n";
|
|
file << "coord Coordinate {\n";
|
|
file << "point [\n";
|
|
|
|
// Coordinates (vertices)
|
|
if( plane )
|
|
{
|
|
if( !layer->WriteVertices( top_z, file, precision ) )
|
|
{
|
|
wxLogMessage( wxT( "Errors writing planar vertices to %s\n%s" ),
|
|
vID->objectName, layer->GetError() );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if( !layer->Write3DVertices( top_z, bottom_z, file, precision ) )
|
|
{
|
|
wxLogMessage( wxT( "Errors writing 3D vertices to %s\n%s" ),
|
|
vID->objectName, layer->GetError() );
|
|
}
|
|
}
|
|
|
|
file << "\n";
|
|
|
|
file << "]\n";
|
|
file << "}\n";
|
|
file << "coordIndex [\n";
|
|
|
|
// Indices
|
|
if( plane )
|
|
layer->WriteIndices( top, file );
|
|
else
|
|
layer->Write3DIndices( file );
|
|
|
|
file << "\n";
|
|
file << "]\n";
|
|
file << "}\n";
|
|
file << "}\n";
|
|
file << "]\n";
|
|
file << "}\n";
|
|
file << "]\n";
|
|
file << "}\n";
|
|
|
|
if( compact && !vID->objectName.empty() )
|
|
{
|
|
file << "]\n";
|
|
file << "}\n";
|
|
}
|
|
|
|
return !file.fail();
|
|
}
|
|
|
|
inline void TransformPoint( IDF_SEGMENT& seg, double frac, bool bottom,
|
|
double dX, double dY, double angle )
|
|
{
|
|
dX *= frac;
|
|
dY *= frac;
|
|
|
|
if( bottom )
|
|
{
|
|
// mirror points on the Y axis
|
|
seg.startPoint.x = -seg.startPoint.x;
|
|
seg.endPoint.x = -seg.endPoint.x;
|
|
seg.center.x = -seg.center.x;
|
|
angle = -angle;
|
|
}
|
|
|
|
seg.startPoint.x *= frac;
|
|
seg.startPoint.y *= frac;
|
|
seg.endPoint.x *= frac;
|
|
seg.endPoint.y *= frac;
|
|
seg.center.x *= frac;
|
|
seg.center.y *= frac;
|
|
|
|
double tsin = 0.0;
|
|
double tcos = 0.0;
|
|
|
|
if( angle > MIN_ANG || angle < -MIN_ANG )
|
|
{
|
|
double ta = angle * M_PI / 180.0;
|
|
double tx, ty;
|
|
|
|
tsin = sin( ta );
|
|
tcos = cos( ta );
|
|
|
|
tx = seg.startPoint.x * tcos - seg.startPoint.y * tsin;
|
|
ty = seg.startPoint.x * tsin + seg.startPoint.y * tcos;
|
|
seg.startPoint.x = tx;
|
|
seg.startPoint.y = ty;
|
|
|
|
tx = seg.endPoint.x * tcos - seg.endPoint.y * tsin;
|
|
ty = seg.endPoint.x * tsin + seg.endPoint.y * tcos;
|
|
seg.endPoint.x = tx;
|
|
seg.endPoint.y = ty;
|
|
|
|
if( seg.angle != 0 )
|
|
{
|
|
tx = seg.center.x * tcos - seg.center.y * tsin;
|
|
ty = seg.center.x * tsin + seg.center.y * tcos;
|
|
seg.center.x = tx;
|
|
seg.center.y = ty;
|
|
}
|
|
}
|
|
|
|
seg.startPoint.x += dX;
|
|
seg.startPoint.y += dY;
|
|
seg.endPoint.x += dX;
|
|
seg.endPoint.y += dY;
|
|
seg.center.x += dX;
|
|
seg.center.y += dY;
|
|
|
|
if( seg.angle != 0 )
|
|
{
|
|
seg.radius *= frac;
|
|
|
|
if( bottom )
|
|
{
|
|
if( !seg.IsCircle() )
|
|
{
|
|
seg.angle = -seg.angle;
|
|
if( seg.offsetAngle > 0.0 )
|
|
seg.offsetAngle = 180 - seg.offsetAngle;
|
|
else
|
|
seg.offsetAngle = -seg.offsetAngle - 180;
|
|
}
|
|
}
|
|
|
|
if( angle > MIN_ANG || angle < -MIN_ANG )
|
|
seg.offsetAngle += angle;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
bool MakeComponents( IDF3_BOARD& board, std::ostream& file, bool compact )
|
|
{
|
|
int cidx = 2; // color index; start at 2 since 0,1 are special (board, NOGEOM_NOPART)
|
|
|
|
VRML_LAYER vpcb;
|
|
|
|
double scale = board.GetUserScale();
|
|
double thick = board.GetBoardThickness() / 2.0;
|
|
|
|
// set the arc parameters according to output scale
|
|
int tI;
|
|
double tMin, tMax;
|
|
vpcb.GetArcParams( tI, tMin, tMax );
|
|
vpcb.SetArcParams( tI, tMin * scale, tMax * scale );
|
|
|
|
// Add the component outlines
|
|
const std::map< std::string, IDF3_COMPONENT* >*const comp = board.GetComponents();
|
|
std::map< std::string, IDF3_COMPONENT* >::const_iterator sc = comp->begin();
|
|
std::map< std::string, IDF3_COMPONENT* >::const_iterator ec = comp->end();
|
|
|
|
std::list< IDF3_COMP_OUTLINE_DATA* >::const_iterator so;
|
|
std::list< IDF3_COMP_OUTLINE_DATA* >::const_iterator eo;
|
|
|
|
double vX, vY, vA;
|
|
double tX, tY, tZ, tA;
|
|
double top, bot;
|
|
bool bottom;
|
|
IDF3::IDF_LAYER lyr;
|
|
|
|
boost::ptr_map< const std::string, VRML_IDS> cmap; // map colors by outline UID
|
|
VRML_IDS* vcp;
|
|
IDF3_COMP_OUTLINE* pout;
|
|
|
|
while( sc != ec )
|
|
{
|
|
sc->second->GetPosition( vX, vY, vA, lyr );
|
|
|
|
if( lyr == IDF3::LYR_BOTTOM )
|
|
bottom = true;
|
|
else
|
|
bottom = false;
|
|
|
|
so = sc->second->GetOutlinesData()->begin();
|
|
eo = sc->second->GetOutlinesData()->end();
|
|
|
|
while( so != eo )
|
|
{
|
|
if( (*so)->GetOutline()->GetThickness() < 0.00000001 && nozeroheights )
|
|
{
|
|
vpcb.Clear();
|
|
++so;
|
|
continue;
|
|
}
|
|
|
|
(*so)->GetOffsets( tX, tY, tZ, tA );
|
|
tX += vX;
|
|
tY += vY;
|
|
tA += vA;
|
|
|
|
if( ( pout = (IDF3_COMP_OUTLINE*)((*so)->GetOutline()) ) != nullptr )
|
|
{
|
|
vcp = GetColor( cmap, cidx, pout->GetUID() );
|
|
}
|
|
else
|
|
{
|
|
vpcb.Clear();
|
|
++so;
|
|
continue;
|
|
}
|
|
|
|
if( !compact )
|
|
{
|
|
if( !PopulateVRML( vpcb, (*so)->GetOutline()->GetOutlines(), bottom,
|
|
board.GetUserScale(), tX, tY, tA ) )
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if( !vcp->used && !PopulateVRML( vpcb, (*so)->GetOutline()->GetOutlines(), false,
|
|
board.GetUserScale() ) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
vcp->dX = tX * scale;
|
|
vcp->dY = tY * scale;
|
|
vcp->dZ = tZ * scale;
|
|
vcp->dA = tA * M_PI / 180.0;
|
|
}
|
|
|
|
if( !compact || !vcp->used )
|
|
{
|
|
vpcb.EnsureWinding( 0, false );
|
|
|
|
int nvcont = vpcb.GetNContours() - 1;
|
|
|
|
while( nvcont > 0 )
|
|
vpcb.EnsureWinding( nvcont--, true );
|
|
|
|
vpcb.Tesselate( NULL );
|
|
}
|
|
|
|
if( !compact )
|
|
{
|
|
if( bottom )
|
|
{
|
|
top = -thick - tZ;
|
|
bot = (top - (*so)->GetOutline()->GetThickness() ) * scale;
|
|
top *= scale;
|
|
}
|
|
else
|
|
{
|
|
bot = thick + tZ;
|
|
top = (bot + (*so)->GetOutline()->GetThickness() ) * scale;
|
|
bot *= scale;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
bot = thick;
|
|
top = (bot + (*so)->GetOutline()->GetThickness() ) * scale;
|
|
bot *= scale;
|
|
}
|
|
|
|
vcp = GetColor( cmap, cidx, ((IDF3_COMP_OUTLINE*)((*so)->GetOutline()))->GetUID() );
|
|
vcp->bottom = bottom;
|
|
|
|
// note: this can happen because IDF allows some negative heights/thicknesses
|
|
if( bot > top )
|
|
std::swap( bot, top );
|
|
|
|
WriteTriangles( file, vcp, &vpcb, false,
|
|
false, top, bot, board.GetUserPrecision(), compact );
|
|
|
|
vpcb.Clear();
|
|
++so;
|
|
}
|
|
|
|
++sc;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
VRML_IDS* GetColor( boost::ptr_map<const std::string, VRML_IDS>& cmap, int& index, const std::string& uid )
|
|
{
|
|
static int refnum = 0;
|
|
|
|
if( index < 2 )
|
|
index = 2; // 0 and 1 are special (BOARD, UID=NOGEOM_NOPART)
|
|
|
|
boost::ptr_map<const std::string, VRML_IDS>::iterator cit = cmap.find( uid );
|
|
|
|
if( cit == cmap.end() )
|
|
{
|
|
VRML_IDS* id = new VRML_IDS;
|
|
|
|
if( !uid.compare( "NOGEOM_NOPART" ) )
|
|
id->colorIndex = 1;
|
|
else
|
|
id->colorIndex = index++;
|
|
|
|
std::ostringstream ostr;
|
|
ostr << "OBJECTn" << refnum++;
|
|
id->objectName = ostr.str();
|
|
|
|
if( showObjectMapping )
|
|
wxLogMessage( wxT( "* %s = '%s'" ), ostr.str(), uid );
|
|
|
|
cmap.insert( uid, id );
|
|
|
|
if( index >= NCOLORS )
|
|
index = 2;
|
|
|
|
return id;
|
|
}
|
|
|
|
return cit->second;
|
|
}
|
|
|
|
|
|
bool MakeOtherOutlines( IDF3_BOARD& board, std::ostream& file )
|
|
{
|
|
int cidx = 2; // color index; start at 2 since 0,1 are special (board, NOGEOM_NOPART)
|
|
|
|
VRML_LAYER vpcb;
|
|
|
|
double scale = board.GetUserScale();
|
|
double thick = board.GetBoardThickness() / 2.0;
|
|
|
|
// set the arc parameters according to output scale
|
|
int tI;
|
|
double tMin, tMax;
|
|
vpcb.GetArcParams( tI, tMin, tMax );
|
|
vpcb.SetArcParams( tI, tMin * scale, tMax * scale );
|
|
|
|
// Add the component outlines
|
|
const std::map< std::string, OTHER_OUTLINE* >*const comp = board.GetOtherOutlines();
|
|
std::map< std::string, OTHER_OUTLINE* >::const_iterator sc = comp->begin();
|
|
std::map< std::string, OTHER_OUTLINE* >::const_iterator ec = comp->end();
|
|
|
|
double top, bot;
|
|
bool bottom;
|
|
int nvcont;
|
|
|
|
boost::ptr_map< const std::string, VRML_IDS> cmap; // map colors by outline UID
|
|
VRML_IDS* vcp;
|
|
OTHER_OUTLINE* pout;
|
|
|
|
while( sc != ec )
|
|
{
|
|
pout = sc->second;
|
|
|
|
if( pout->GetThickness() < 0.00000001 && nozeroheights )
|
|
{
|
|
vpcb.Clear();
|
|
++sc;
|
|
continue;
|
|
}
|
|
|
|
vcp = GetColor( cmap, cidx, pout->GetOutlineIdentifier() );
|
|
|
|
if( !PopulateVRML( vpcb, pout->GetOutlines(), false,
|
|
board.GetUserScale(), 0, 0, 0 ) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
vpcb.EnsureWinding( 0, false );
|
|
|
|
nvcont = vpcb.GetNContours() - 1;
|
|
|
|
while( nvcont > 0 )
|
|
vpcb.EnsureWinding( nvcont--, true );
|
|
|
|
vpcb.Tesselate( NULL );
|
|
|
|
if( pout->GetSide() == IDF3::LYR_BOTTOM )
|
|
bottom = true;
|
|
else
|
|
bottom = false;
|
|
|
|
if( bottom )
|
|
{
|
|
top = -thick;
|
|
bot = ( top - pout->GetThickness() ) * scale;
|
|
top *= scale;
|
|
}
|
|
else
|
|
{
|
|
bot = thick;
|
|
top = (bot + pout->GetThickness() ) * scale;
|
|
bot *= scale;
|
|
}
|
|
|
|
// note: this can happen because IDF allows some negative heights/thicknesses
|
|
if( bot > top )
|
|
std::swap( bot, top );
|
|
|
|
vcp->bottom = bottom;
|
|
WriteTriangles( file, vcp, &vpcb, false,
|
|
false, top, bot, board.GetUserPrecision(), false );
|
|
|
|
vpcb.Clear();
|
|
++sc;
|
|
}
|
|
|
|
return true;
|
|
}
|