kicad/plugins/3d/idf/s3d_plugin_idf.cpp

932 lines
23 KiB
C++

/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2015-2017 Cirilo Bernardo <cirilo.bernardo@gmail.com>
* Copyright (C) 2021 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
*/
// Note: the board's bottom side is at Z = 0
#include <cmath>
#include <string>
#include <map>
#include <wx/filename.h>
#include <wx/log.h>
#include <wx/string.h>
#include "plugins/3d/3d_plugin.h"
#include "plugins/3dapi/ifsg_all.h"
#include "idf_parser.h"
#include "vrml_layer.h"
#define PLUGIN_3D_IDF_MAJOR 1
#define PLUGIN_3D_IDF_MINOR 0
#define PLUGIN_3D_IDF_PATCH 0
#define PLUGIN_3D_IDF_REVNO 0
// number of colors in the palette; cycles from 1..NCOLORS;
// number 0 is special (the PCB board color)
#define NCOLORS 6
/**
* Flag to enable IDF plugin trace output.
*
* @ingroup trace_env_vars
*/
const wxChar* const traceIdfPlugin = wxT( "KICAD_IDF_PLUGIN" );
// read and instantiate an IDF component outline
static SCENEGRAPH* loadIDFOutline( const wxString& aFileName );
// read and render an IDF board assembly
static SCENEGRAPH* loadIDFBoard( const wxString& aFileName );
// model a single extruded outline
// idxColor = color index to use
// aParent = parent SCENEGRAPH object, if any
static SCENEGRAPH* addOutline( IDF3_COMP_OUTLINE* outline, int idxColor, SGNODE* aParent );
// model the board extrusion
static SCENEGRAPH* makeBoard( IDF3_BOARD& brd, SGNODE* aParent );
// model all included components
static bool makeComponents( IDF3_BOARD& brd, SGNODE* aParent );
// model any .OTHER_OUTLINE items
static bool makeOtherOutlines( IDF3_BOARD& brd, SGNODE* aParent );
// convert the IDF outline to VRML intermediate data
static bool getOutlineModel( VRML_LAYER& model, const std::list< IDF_OUTLINE* >* items );
// convert IDF segment data to VRML segment data
static bool addSegment( VRML_LAYER& model, IDF_SEGMENT* seg, int icont, int iseg );
// convert the VRML intermediate data into SG* data
static SCENEGRAPH* vrmlToSG( VRML_LAYER& vpcb, int idxColor, SGNODE* aParent, double top,
double bottom );
class LOCALESWITCH
{
public:
LOCALESWITCH()
{
setlocale( LC_NUMERIC, "C" );
}
~LOCALESWITCH()
{
setlocale( LC_NUMERIC, "" );
}
};
static SGNODE* getColor( IFSG_SHAPE& shape, int colorIdx )
{
IFSG_APPEARANCE material( shape );
static int cidx = 1;
int idx;
if( colorIdx == -1 )
idx = cidx;
else
idx = colorIdx;
switch( idx )
{
case 0:
// green for PCB
material.SetSpecular( 0.13f, 0.81f, 0.22f );
material.SetDiffuse( 0.13f, 0.81f, 0.22f );
// default ambient intensity
material.SetShininess( 0.3f );
break;
case 1:
// magenta
material.SetSpecular( 0.8f, 0.0f, 0.8f );
material.SetDiffuse( 0.6f, 0.0f, 0.6f );
// default ambient intensity
material.SetShininess( 0.3f );
break;
case 2:
// red
material.SetSpecular( 0.69f, 0.14f, 0.14f );
material.SetDiffuse( 0.69f, 0.14f, 0.14f );
// default ambient intensity
material.SetShininess( 0.3f );
break;
case 3:
// orange
material.SetSpecular( 1.0f, 0.44f, 0.0f );
material.SetDiffuse( 1.0f, 0.44f, 0.0f );
// default ambient intensity
material.SetShininess( 0.3f );
break;
case 4:
// yellow
material.SetSpecular( 0.93f, 0.94f, 0.16f );
material.SetDiffuse( 0.93f, 0.94f, 0.16f );
// default ambient intensity
material.SetShininess( 0.3f );
break;
case 5:
// blue
material.SetSpecular( 0.1f, 0.11f, 0.88f );
material.SetDiffuse( 0.1f, 0.11f, 0.88f );
// default ambient intensity
material.SetShininess( 0.3f );
break;
default:
// violet
material.SetSpecular( 0.32f, 0.07f, 0.64f );
material.SetDiffuse( 0.32f, 0.07f, 0.64f );
// default ambient intensity
material.SetShininess( 0.3f );
break;
}
if( ( colorIdx == -1 ) && ( ++cidx > NCOLORS ) )
cidx = 1;
return material.GetRawPtr();
}
const char* GetKicadPluginName( void )
{
return "PLUGIN_3D_IDF";
}
void GetPluginVersion( unsigned char* Major, unsigned char* Minor, unsigned char* Patch,
unsigned char* Revision )
{
if( Major )
*Major = PLUGIN_3D_IDF_MAJOR;
if( Minor )
*Minor = PLUGIN_3D_IDF_MINOR;
if( Patch )
*Patch = PLUGIN_3D_IDF_PATCH;
if( Revision )
*Revision = PLUGIN_3D_IDF_REVNO;
}
// number of extensions supported
#ifdef _WIN32
#define NEXTS 2
#else
#define NEXTS 4
#endif
// number of filter sets supported
#define NFILS 2
static char ext0[] = "idf";
static char ext1[] = "emn";
#ifdef _WIN32
static char fil0[] = "IDF (*.idf)|*.idf";
static char fil1[] = "IDF BRD v2/v3 (*.emn)|*.emn";
#else
static char ext2[] = "IDF";
static char ext3[] = "EMN";
static char fil0[] = "IDF (*.idf;*.IDF)|*.idf;*.IDF";
static char fil1[] = "IDF BRD (*.emn;*.EMN)|*.emn;*.EMN";
#endif
static struct FILE_DATA
{
char const* extensions[NEXTS];
char const* filters[NFILS];
FILE_DATA()
{
extensions[0] = ext0;
extensions[1] = ext1;
filters[0] = fil0;
filters[1] = fil1;
#ifndef _WIN32
extensions[2] = ext2;
extensions[3] = ext3;
#endif
}
} file_data;
int GetNExtensions( void )
{
return NEXTS;
}
char const* GetModelExtension( int aIndex )
{
if( aIndex < 0 || aIndex >= NEXTS )
return nullptr;
return file_data.extensions[aIndex];
}
int GetNFilters( void )
{
return NFILS;
}
char const* GetFileFilter( int aIndex )
{
if( aIndex < 0 || aIndex >= NFILS )
return nullptr;
return file_data.filters[aIndex];
}
bool CanRender( void )
{
// this plugin supports rendering of IDF component outlines
return true;
}
SCENEGRAPH* Load( char const* aFileName )
{
if( nullptr == aFileName )
return nullptr;
wxFileName fname;
fname.Assign( wxString::FromUTF8Unchecked( aFileName ) );
wxString ext = fname.GetExt();
SCENEGRAPH* data = nullptr;
if( !ext.Cmp( wxT( "idf" ) ) || !ext.Cmp( wxT( "IDF" ) ) )
{
data = loadIDFOutline( fname.GetFullPath() );
}
if( !ext.Cmp( wxT( "emn" ) ) || !ext.Cmp( wxT( "EMN" ) ) )
{
data = loadIDFBoard( fname.GetFullPath() );
}
// DEBUG: WRITE OUT IDF FILE TO CONFIRM NORMALS
#if defined( DEBUG_IDF ) && DEBUG_IDF > 3
if( data )
{
wxFileName fn( aFileName );
wxString output = wxT( "_idf-" );
output.append( fn.GetName() );
output.append( wxT( ".wrl" ) );
S3D::WriteVRML( output.ToUTF8(), true, (SGNODE*) ( data ), true, true );
}
#endif
return data;
}
static bool getOutlineModel( VRML_LAYER& model, const std::list< IDF_OUTLINE* >* items )
{
// 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 )
{
wxLogTrace( traceIdfPlugin, wxT( "%s:%s:%s\n"
"* [INFO] cannot create an outline" ),
__FILE__, __FUNCTION__, __LINE__ );
return false;
}
if( (*scont)->size() < 1 )
{
wxLogTrace( traceIdfPlugin, wxT( "%s:%s:%s\n "
"* [INFO] invalid contour: no vertices" ),
__FILE__, __FUNCTION__, __LINE__ );
return false;
}
sseg = (*scont)->begin();
eseg = (*scont)->end();
iseg = 0;
while( sseg != eseg )
{
lseg = **sseg;
if( !addSegment( model, &lseg, nvcont, iseg ) )
{
wxLogTrace( traceIdfPlugin, wxT( "%s:%s:%s\n"
"* [BUG] cannot add segment" ),
__FILE__, __FUNCTION__, __LINE__ );
return false;
}
++iseg;
++sseg;
}
++scont;
}
return true;
}
static 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 )
{
wxLogTrace( traceIdfPlugin,
wxT( "%s:%s:%s\n"
"* [INFO] adding a circle to an existing vertex list" ),
__FILE__, __FUNCTION__, __LINE__ );
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;
}
static SCENEGRAPH* vrmlToSG( VRML_LAYER& vpcb, int idxColor, SGNODE* aParent, double top,
double bottom )
{
vpcb.Tesselate( nullptr );
std::vector< double > vertices;
std::vector< int > idxPlane;
std::vector< int > idxSide;
if( top < bottom )
{
double tmp = top;
top = bottom;
bottom = tmp;
}
if( !vpcb.Get3DTriangles( vertices, idxPlane, idxSide, top, bottom ) )
{
wxLogTrace( traceIdfPlugin, wxT( "%s:%s:%s\n"
"* [INFO] no vertex data" ),
__FILE__, __FUNCTION__, __LINE__ );
return nullptr;
}
if( ( idxPlane.size() % 3 ) || ( idxSide.size() % 3 ) )
{
wxLogTrace( traceIdfPlugin,
wxT( "%s:%s:%s\n"
"* [BUG] index lists are not a multiple of 3 (not a triangle list)" ),
__FILE__, __FUNCTION__, __LINE__ );
return nullptr;
}
std::vector< SGPOINT > vlist;
size_t nvert = vertices.size() / 3;
size_t j = 0;
for( size_t i = 0; i < nvert; ++i, j+= 3 )
vlist.emplace_back( vertices[j], vertices[j+1], vertices[j+2] );
// create the intermediate scenegraph
IFSG_TRANSFORM* tx0 = new IFSG_TRANSFORM( aParent ); // tx0 = Transform for this outline
// shape will hold (a) all vertices and (b) a local list of normals
IFSG_SHAPE* shape = new IFSG_SHAPE( *tx0 );
// this face shall represent the top and bottom planes
IFSG_FACESET* face = new IFSG_FACESET( *shape );
// coordinates for all faces
IFSG_COORDS* cp = new IFSG_COORDS( *face );
cp->SetCoordsList( nvert, &vlist[0] );
// coordinate indices for top and bottom planes only.
IFSG_COORDINDEX* coordIdx = new IFSG_COORDINDEX( *face );
coordIdx->SetIndices( idxPlane.size(), &idxPlane[0] );
// normals for the top and bottom planes.
IFSG_NORMALS* norms = new IFSG_NORMALS( *face );
// number of TOP (and bottom) vertices
j = nvert / 2;
// set the TOP normals
for( size_t i = 0; i < j; ++i )
norms->AddNormal( 0.0, 0.0, 1.0 );
// set the BOTTOM normals
for( size_t i = 0; i < j; ++i )
norms->AddNormal( 0.0, 0.0, -1.0 );
// assign a color from the palette
SGNODE* modelColor = getColor( *shape, idxColor );
// create a second shape describing the vertical walls of the IDF extrusion
// using per-vertex-per-face-normals
shape->NewNode( *tx0 );
shape->AddRefNode( modelColor ); // set the color to be the same as the top/bottom
face->NewNode( *shape );
cp->NewNode( *face ); // new vertex list
norms->NewNode( *face ); // new normals list
coordIdx->NewNode( *face ); // new index list
// populate the new per-face vertex list and its indices and normals
std::vector< int >::iterator sI = idxSide.begin();
std::vector< int >::iterator eI = idxSide.end();
size_t sidx = 0; // index to the new coord set
SGPOINT p1, p2, p3;
SGVECTOR vnorm;
while( sI != eI )
{
p1 = vlist[*sI];
cp->AddCoord( p1 );
++sI;
p2 = vlist[*sI];
cp->AddCoord( p2 );
++sI;
p3 = vlist[*sI];
cp->AddCoord( p3 );
++sI;
vnorm.SetVector( S3D::CalcTriNorm( p1, p2, p3 ) );
norms->AddNormal( vnorm );
norms->AddNormal( vnorm );
norms->AddNormal( vnorm );
coordIdx->AddIndex( (int)sidx );
++sidx;
coordIdx->AddIndex( (int)sidx );
++sidx;
coordIdx->AddIndex( (int)sidx );
++sidx;
}
SCENEGRAPH* data = (SCENEGRAPH*)tx0->GetRawPtr();
// delete the API wrappers
delete shape;
delete face;
delete coordIdx;
delete cp;
delete tx0;
return data;
}
static SCENEGRAPH* addOutline( IDF3_COMP_OUTLINE* outline, int idxColor, SGNODE* aParent )
{
VRML_LAYER vpcb;
if( !getOutlineModel( vpcb, outline->GetOutlines() ) )
{
wxLogTrace( traceIdfPlugin, wxT( "%s:%s:%s\n"
"* [INFO] no valid outline data" ),
__FILE__, __FUNCTION__, __LINE__ );
return nullptr;
}
vpcb.EnsureWinding( 0, false );
double top = outline->GetThickness();
double bot = 0.0;
// note: some IDF entities permit negative heights
if( top < bot )
{
bot = top;
top = 0.0;
}
SCENEGRAPH* data = vrmlToSG( vpcb, idxColor, aParent, top, bot );
return data;
}
static SCENEGRAPH* loadIDFOutline( const wxString& aFileName )
{
LOCALESWITCH switcher;
IDF3_BOARD brd( IDF3::CAD_ELEC );
IDF3_COMP_OUTLINE* outline = nullptr;
outline = brd.GetComponentOutline( aFileName );
if( nullptr == outline )
{
wxLogTrace( traceIdfPlugin, wxT( "%s:%s:%s\n"
"* [INFO] Failed to read IDF data:\n"
"%s\n"
"* [INFO] no outline for file '%s'" ),
__FILE__, __FUNCTION__, __LINE__,
brd.GetError(),
aFileName );
return nullptr;
}
SCENEGRAPH* data = addOutline( outline, -1, nullptr );
return data;
}
static SCENEGRAPH* loadIDFBoard( const wxString& aFileName )
{
LOCALESWITCH switcher;
IDF3_BOARD brd( IDF3::CAD_ELEC );
// note: if the IDF model is defective no outline substitutes shall be made
if( !brd.ReadFile( aFileName, true ) )
{
wxLogTrace( traceIdfPlugin, wxT( "%s:%s:%s\n"
"* [INFO] Error '%s' occurred reading IDF file: %s" ),
__FILE__, __FUNCTION__, __LINE__,
brd.GetError(),
aFileName );
return nullptr;
}
IFSG_TRANSFORM tx0( true );
SGNODE* topNode = tx0.GetRawPtr();
bool noBoard = false;
bool noComp = false;
bool noOther = false;
if( nullptr == makeBoard( brd, topNode ) )
noBoard = true;
if( !makeComponents( brd, topNode ) )
noComp = true;
if( !makeOtherOutlines( brd, topNode ) )
noOther = true;
if( noBoard && noComp && noOther )
{
tx0.Destroy();
return nullptr;
}
return (SCENEGRAPH*) topNode;
}
static SCENEGRAPH* makeBoard( IDF3_BOARD& brd, SGNODE* aParent )
{
if( nullptr == aParent )
return nullptr;
VRML_LAYER vpcb;
// check if no board outline
if( brd.GetBoardOutlinesSize() < 1 )
return nullptr;
if( !getOutlineModel( vpcb, brd.GetBoardOutline()->GetOutlines() ) )
return nullptr;
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 = &brd.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(), (*sd)->GetDrillYPos(),
(*sd)->GetDrillDia() / 2.0, true );
++sd;
}
std::map< std::string, IDF3_COMPONENT* >*const comp = brd.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(), (*sd)->GetDrillYPos(),
(*sd)->GetDrillDia() / 2.0, true );
++sd;
}
++sc;
}
double top = brd.GetBoardThickness();
SCENEGRAPH* data = vrmlToSG( vpcb, 0, aParent, top, 0.0 );
return data;
}
static bool makeComponents( IDF3_BOARD& brd, SGNODE* aParent )
{
if( nullptr == aParent )
return false;
int ncomponents = 0;
double brdTop = brd.GetBoardThickness();
// Add the component outlines
const std::map< std::string, IDF3_COMPONENT* >*const comp = brd.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;
bool bottom;
IDF3::IDF_LAYER lyr;
std::map< std::string, SGNODE* > dataMap; // map data by UID
std::map< std::string, SGNODE* >::iterator dataItem;
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( std::abs( (*so)->GetOutline()->GetThickness() ) < 0.001 )
{
++so;
continue;
}
(*so)->GetOffsets( tX, tY, tZ, tA );
tX += vX;
tY += vY;
tA += vA;
pout = (IDF3_COMP_OUTLINE*)((*so)->GetOutline());
if( nullptr == pout )
{
++so;
continue;
}
dataItem = dataMap.find( pout->GetUID() );
SCENEGRAPH* sg = nullptr;
if( dataItem == dataMap.end() )
{
sg = addOutline( pout, -1, nullptr );
if( nullptr == sg )
{
++so;
continue;
}
++ncomponents;
dataMap.insert( std::pair< std::string, SGNODE* >( pout->GetUID(), (SGNODE*)sg ) );
}
else
{
sg = (SCENEGRAPH*) dataItem->second;
}
IFSG_TRANSFORM tx0( aParent );
IFSG_TRANSFORM txN( false );
txN.Attach( (SGNODE*)sg );
if( nullptr == txN.GetParent() )
tx0.AddChildNode( txN );
else
tx0.AddRefNode( txN );
if( bottom )
{
tx0.SetTranslation( SGPOINT( tX, tY, -tZ ) );
// for an item on the back of the board we have a compounded rotation,
// first a flip on the Y axis as per the IDF spec and then a rotation
// of -tA degrees on the Z axis. The resultant rotation axis is an
// XY vector equivalent to (0,1) rotated by -(tA/2) degrees
//
double ang = -tA * M_PI / 360.0;
double sinA = sin( ang );
double cosA = cos( ang );
tx0.SetRotation( SGVECTOR( -sinA, cosA , 0 ), M_PI );
}
else
{
tx0.SetTranslation( SGPOINT( tX, tY, tZ + brdTop ) );
tx0.SetRotation( SGVECTOR( 0, 0, 1 ), tA * M_PI / 180.0 );
}
++so;
}
++sc;
}
if( 0 == ncomponents )
return false;
return true;
}
static bool makeOtherOutlines( IDF3_BOARD& brd, SGNODE* aParent )
{
if( nullptr == aParent )
return false;
VRML_LAYER vpcb;
int ncomponents = 0;
double brdTop = brd.GetBoardThickness();
double top, bot;
// Add the component outlines
const std::map< std::string, OTHER_OUTLINE* >*const comp = brd.GetOtherOutlines();
std::map< std::string, OTHER_OUTLINE* >::const_iterator sc = comp->begin();
std::map< std::string, OTHER_OUTLINE* >::const_iterator ec = comp->end();
int nvcont;
OTHER_OUTLINE* pout;
while( sc != ec )
{
pout = sc->second;
if( std::abs( pout->GetThickness() ) < 0.001 )
{
++sc;
continue;
}
if( !getOutlineModel( vpcb, pout->GetOutlines() ) )
{
vpcb.Clear();
++sc;
continue;
}
vpcb.EnsureWinding( 0, false );
nvcont = vpcb.GetNContours() - 1;
while( nvcont > 0 )
vpcb.EnsureWinding( nvcont--, true );
if( pout->GetSide() == IDF3::LYR_BOTTOM )
{
top = 0.0;
bot = -pout->GetThickness();
}
else
{
bot = brdTop;
top = bot + pout->GetThickness();
}
if( nullptr == vrmlToSG( vpcb, -1, aParent, top, bot ) )
{
vpcb.Clear();
++sc;
continue;
}
++ncomponents;
vpcb.Clear();
++sc;
}
if( 0 == ncomponents )
return false;
return true;
}