kicad/utils/kicad2step/pcb/kicadfootprint.cpp

483 lines
13 KiB
C++

/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2016 Cirilo Bernardo <cirilo.bernardo@gmail.com>
* Copyright 2018-2022 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
*/
#include "kicadfootprint.h"
#include "3d_resolver.h"
#include "kicadcurve.h"
#include "kicadmodel.h"
#include "kicadpad.h"
#include "oce_utils.h"
#include <sexpr/sexpr.h>
#include <wx/log.h>
#include <wx/filename.h>
#include <iostream>
#include <limits>
#include <memory>
#include <sstream>
#include <Standard_Failure.hxx>
KICADFOOTPRINT::KICADFOOTPRINT( KICADPCB* aParent )
{
m_parent = aParent;
m_side = LAYER_NONE;
m_rotation = 0.0;
m_smd = false;
m_tht = false;
m_virtual = false;
return;
}
KICADFOOTPRINT::~KICADFOOTPRINT()
{
for( KICADPAD* i : m_pads )
delete i;
for( KICADCURVE* i : m_curves )
delete i;
for( KICADMODEL* i : m_models )
delete i;
return;
}
bool KICADFOOTPRINT::Read( SEXPR::SEXPR* aEntry )
{
if( !aEntry )
return false;
if( aEntry->IsList() )
{
size_t nc = aEntry->GetNumberOfChildren();
SEXPR::SEXPR* child = aEntry->GetChild( 0 );
std::string name = child->GetSymbol();
if( name != "module" && name != "footprint" )
{
std::ostringstream ostr;
ostr << "* BUG: module parser invoked for type '" << name << "'\n";
wxLogMessage( wxT( "%s\n" ), ostr.str().c_str() );
return false;
}
bool result = true;
for( size_t i = 2; i < nc && result; ++i )
{
child = aEntry->GetChild( i );
std::string symname;
// skip the optional locked and/or placed attributes; due to the vagaries of the
// KiCad version of sexpr, the attribute may be a Symbol or a String
if( child->IsSymbol() || child->IsString() )
{
if( child->IsSymbol() )
symname = child->GetSymbol();
else if( child->IsString() )
symname = child->GetString();
if( symname == "locked" || symname == "placed" )
continue;
wxLogMessage( wxT( "* module descr in PCB file at line %d: unexpected keyword "
"'%s'\n" ),
child->GetLineNumber(),
symname.c_str() );
return false;
}
if( !child->IsList() )
{
wxLogMessage( wxT( "* corrupt module in PCB file at line %d\n" ),
child->GetLineNumber() );
return false;
}
symname = child->GetChild( 0 )->GetSymbol();
if( symname == "layer" )
result = parseLayer( child );
else if( symname == "at" )
result = parsePosition( child );
else if( symname == "attr" )
result = parseAttribute( child );
else if( symname == "fp_text" )
result = parseText( child );
else if( symname == "fp_arc" )
result = parseCurve( child, CURVE_ARC );
else if( symname == "fp_line" )
result = parseCurve( child, CURVE_LINE );
else if( symname == "fp_circle" )
result = parseCurve( child, CURVE_CIRCLE );
else if( symname == "fp_rect" )
result = parseRect( child );
else if( symname == "fp_curve" )
result = parseCurve( child, CURVE_BEZIER );
else if( symname == "pad" )
result = parsePad( child );
else if( symname == "model" )
result = parseModel( child );
}
if( !m_smd && !m_tht )
m_virtual = true;
return result;
}
std::ostringstream ostr;
ostr << "* data is not a valid PCB module\n";
wxLogMessage( wxT( "%s\n" ), ostr.str().c_str() );
return false;
}
bool KICADFOOTPRINT::parseRect( SEXPR::SEXPR* data )
{
std::unique_ptr<KICADCURVE> rect = std::make_unique<KICADCURVE>();
if( !rect->Read( data, CURVE_LINE ) )
return false;
// reject any curves not on the Edge.Cuts layer
if( rect->GetLayer() != LAYER_EDGE )
return true;
KICADCURVE* top = new KICADCURVE( *rect );
KICADCURVE* right = new KICADCURVE( *rect );
KICADCURVE* bottom = new KICADCURVE( *rect );
KICADCURVE* left = new KICADCURVE( *rect );
top->m_end.y = right->m_start.y;
m_curves.push_back( top );
right->m_start.x = bottom->m_end.x;
m_curves.push_back( right );
bottom->m_start.y = left->m_end.y;
m_curves.push_back( bottom );
left->m_end.x = top->m_start.x;
m_curves.push_back( left );
return true;
}
bool KICADFOOTPRINT::parseModel( SEXPR::SEXPR* data )
{
KICADMODEL* mp = new KICADMODEL();
if( !mp->Read( data ) )
{
delete mp;
return false;
}
if( mp->Hide() )
delete mp;
else
m_models.push_back( mp );
return true;
}
bool KICADFOOTPRINT::parseCurve( SEXPR::SEXPR* data, CURVE_TYPE aCurveType )
{
KICADCURVE* mp = new KICADCURVE();
if( !mp->Read( data, aCurveType ) )
{
delete mp;
return false;
}
// NOTE: for now we are only interested in glyphs on the outline layer
if( LAYER_EDGE != mp->GetLayer() )
{
delete mp;
return true;
}
m_curves.push_back( mp );
return true;
}
bool KICADFOOTPRINT::parseLayer( SEXPR::SEXPR* data )
{
SEXPR::SEXPR* val = data->GetChild( 1 );
std::string layername;
if( val->IsSymbol() )
layername = val->GetSymbol();
else if( val->IsString() )
layername = val->GetString();
else
{
std::ostringstream ostr;
ostr << "* corrupt module in PCB file (line ";
ostr << val->GetLineNumber() << "); layer cannot be parsed\n";
wxLogMessage( wxT( "%s\n" ), ostr.str().c_str() );
return false;
}
int layerId = m_parent->GetLayerId( layername );
if( layerId == 31 )
m_side = LAYER_BOTTOM;
else
m_side = LAYER_TOP;
return true;
}
bool KICADFOOTPRINT::parsePosition( SEXPR::SEXPR* data )
{
return Get2DPositionAndRotation( data, m_position, m_rotation );
}
bool KICADFOOTPRINT::parseAttribute( SEXPR::SEXPR* data )
{
if( data->GetNumberOfChildren() < 2 )
{
std::ostringstream ostr;
ostr << "* corrupt module in PCB file (line ";
ostr << data->GetLineNumber() << "); attribute cannot be parsed\n";
wxLogMessage( wxT( "%s\n" ), ostr.str().c_str() );
return false;
}
SEXPR::SEXPR* child = data->GetChild( 1 );
std::string text;
if( child->IsSymbol() )
text = child->GetSymbol();
else if( child->IsString() )
text = child->GetString();
// The "virtual" attribute token is obsolete but kicad2step can still be run against older
// board files. It has been replaced with "exclude_from_bom" so any footprint with this
// attribute will behave the same as the "virtual" attribute.
if( text == "smd" )
m_smd = true;
else if( text == "through_hole" )
m_tht = true;
else if( text == "virtual" )
m_virtual = true;
else if( text == "exclude_from_bom" )
m_virtual = true;
return true;
}
bool KICADFOOTPRINT::parseText( SEXPR::SEXPR* data )
{
// we're only interested in the Reference Designator
if( data->GetNumberOfChildren() < 3 )
return true;
SEXPR::SEXPR* child = data->GetChild( 1 );
std::string text;
if( child->IsSymbol() )
text = child->GetSymbol();
else if( child->IsString() )
text = child->GetString();
if( text != "reference" )
return true;
child = data->GetChild( 2 );
if( child->IsSymbol() )
text = child->GetSymbol();
else if( child->IsString() )
text = child->GetString();
m_refdes = text;
return true;
}
bool KICADFOOTPRINT::parsePad( SEXPR::SEXPR* data )
{
KICADPAD* mp = new KICADPAD();
if( !mp->Read( data ) )
{
delete mp;
return false;
}
// NOTE: for now we only accept thru-hole pads
// for the MCAD description
if( !mp->IsThruHole() )
{
delete mp;
return true;
}
m_pads.push_back( mp );
return true;
}
bool KICADFOOTPRINT::ComposePCB( class PCBMODEL* aPCB, S3D_RESOLVER* resolver,
DOUBLET aOrigin, bool aComposeVirtual, bool aSubstituteModels )
{
// translate pads and curves to final position and append to PCB.
double dlim = (double)std::numeric_limits< float >::epsilon();
double vsin = sin( m_rotation );
double vcos = cos( m_rotation );
bool hasdata = false;
double posX = m_position.x - aOrigin.x;
double posY = m_position.y - aOrigin.y;
for( KICADCURVE* i : m_curves )
{
if( i->m_layer != LAYER_EDGE || CURVE_NONE == i->m_form )
continue;
KICADCURVE lcurve = *i;
lcurve.m_start.y = -lcurve.m_start.y;
lcurve.m_end.y = -lcurve.m_end.y;
lcurve.m_angle = -lcurve.m_angle;
if( m_rotation < -dlim || m_rotation > dlim )
{
double x = lcurve.m_start.x * vcos - lcurve.m_start.y * vsin;
double y = lcurve.m_start.x * vsin + lcurve.m_start.y * vcos;
lcurve.m_start.x = x;
lcurve.m_start.y = y;
x = lcurve.m_end.x * vcos - lcurve.m_end.y * vsin;
y = lcurve.m_end.x * vsin + lcurve.m_end.y * vcos;
lcurve.m_end.x = x;
lcurve.m_end.y = y;
}
lcurve.m_start.x += posX;
lcurve.m_start.y -= posY;
lcurve.m_end.x += posX;
lcurve.m_end.y -= posY;
if( aPCB->AddOutlineSegment( &lcurve ) )
hasdata = true;
}
for( KICADPAD* i : m_pads )
{
if( !i->IsThruHole() )
continue;
KICADPAD lpad = *i;
lpad.m_position.y = -lpad.m_position.y;
if( m_rotation < -dlim || m_rotation > dlim )
{
double x = lpad.m_position.x * vcos - lpad.m_position.y * vsin;
double y = lpad.m_position.x * vsin + lpad.m_position.y * vcos;
lpad.m_position.x = x;
lpad.m_position.y = y;
}
lpad.m_position.x += posX;
lpad.m_position.y -= posY;
if( aPCB->AddPadHole( &lpad ) )
hasdata = true;
}
if( m_virtual && !aComposeVirtual )
return hasdata;
DOUBLET newpos( posX, posY );
for( KICADMODEL* i : m_models )
{
wxString mname = wxString::FromUTF8Unchecked( i->m_modelname.c_str() );
if( mname.empty() )
continue;
std::vector<wxString> searchedPaths;
mname = resolver->ResolvePath( mname, searchedPaths );
if( !wxFileName::FileExists( mname ) )
{
wxString paths;
for( const wxString& path : searchedPaths )
paths += " " + path + "\n";
ReportMessage( wxString::Format( wxT( "Could not add 3D model to %s.\n"
"File not found: %s\n"
"Searched paths:\n"
"%s" ),
m_refdes,
mname,
paths) );
continue;
}
std::string fname( mname.ToUTF8() );
try
{
if( aPCB->AddComponent( fname, m_refdes, LAYER_BOTTOM == m_side ? true : false,
newpos, m_rotation, i->m_offset, i->m_rotation, i->m_scale,
aSubstituteModels ) )
{
hasdata = true;
}
}
catch( const Standard_Failure& e)
{
ReportMessage( wxString::Format( wxT( "Could not add 3D model to %s.\n"
"OpenCASCADE error: %s\n" ),
m_refdes,
e.GetMessageString() ) );
}
}
return hasdata;
}