2014-01-02 09:26:03 +00:00
|
|
|
/*
|
|
|
|
* This program source code file is part of KiCad, a free EDA CAD application.
|
|
|
|
*
|
|
|
|
* Copyright (C) 2013 Cirilo Bernardo
|
2023-03-30 11:49:23 +00:00
|
|
|
* Copyright (C) 2018-2023 KiCad Developers, see AUTHORS.txt for contributors.
|
2014-01-02 09:26:03 +00:00
|
|
|
*
|
|
|
|
* 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 <list>
|
2020-10-24 01:38:50 +00:00
|
|
|
#include <locale_io.h>
|
2021-03-20 15:35:37 +00:00
|
|
|
#include <macros.h>
|
2018-01-29 20:58:58 +00:00
|
|
|
#include <pcb_edit_frame.h>
|
2020-11-12 20:19:22 +00:00
|
|
|
#include <board.h>
|
2021-06-06 19:03:10 +00:00
|
|
|
#include <board_design_settings.h>
|
2020-11-12 20:19:22 +00:00
|
|
|
#include <footprint.h>
|
2022-09-26 03:40:25 +00:00
|
|
|
#include <fp_lib_table.h>
|
2014-06-05 18:37:04 +00:00
|
|
|
#include <idf_parser.h>
|
2021-06-06 19:03:10 +00:00
|
|
|
#include <pad.h>
|
2023-03-30 11:49:23 +00:00
|
|
|
#include <pcb_shape.h>
|
2014-06-05 18:37:04 +00:00
|
|
|
#include <build_version.h>
|
2023-09-28 03:15:54 +00:00
|
|
|
#include <project_pcb.h>
|
2021-06-06 12:41:16 +00:00
|
|
|
#include <wx/msgdlg.h>
|
2016-07-19 17:35:25 +00:00
|
|
|
#include "project.h"
|
|
|
|
#include "kiway.h"
|
|
|
|
#include "3d_cache/3d_cache.h"
|
2018-03-07 13:05:12 +00:00
|
|
|
#include "filename_resolver.h"
|
2014-01-02 09:26:03 +00:00
|
|
|
|
2022-09-16 11:33:56 +00:00
|
|
|
|
2022-09-17 01:07:36 +00:00
|
|
|
#include <base_units.h> // to define pcbIUScale.FromMillimeter(x)
|
2015-05-15 12:49:11 +00:00
|
|
|
|
2021-07-19 23:56:05 +00:00
|
|
|
|
2015-05-15 12:49:11 +00:00
|
|
|
// assumed default graphical line thickness: == 0.1mm
|
2022-09-16 11:33:56 +00:00
|
|
|
#define LINE_WIDTH (pcbIUScale.mmToIU( 0.1 ))
|
2014-01-02 09:26:03 +00:00
|
|
|
|
2021-07-19 23:56:05 +00:00
|
|
|
|
2018-03-07 13:05:12 +00:00
|
|
|
static FILENAME_RESOLVER* resolver;
|
2016-07-19 17:35:25 +00:00
|
|
|
|
2021-07-19 23:56:05 +00:00
|
|
|
|
2014-01-02 09:26:03 +00:00
|
|
|
/**
|
2021-07-19 23:56:05 +00:00
|
|
|
* Retrieve line segment information from the edge layer and compiles the data into a form
|
|
|
|
* which can be output as an IDFv3 compliant #BOARD_OUTLINE section.
|
2014-01-02 09:26:03 +00:00
|
|
|
*/
|
2014-06-05 18:37:04 +00:00
|
|
|
static void idf_export_outline( BOARD* aPcb, IDF3_BOARD& aIDFBoard )
|
2014-01-02 09:26:03 +00:00
|
|
|
{
|
2014-06-05 18:37:04 +00:00
|
|
|
double scale = aIDFBoard.GetUserScale();
|
2014-01-02 09:26:03 +00:00
|
|
|
IDF_POINT sp, ep; // start and end points from KiCad item
|
|
|
|
std::list< IDF_SEGMENT* > lines; // IDF intermediate form of KiCad graphical item
|
2023-03-30 11:49:23 +00:00
|
|
|
IDF_OUTLINE* outline = nullptr; // graphical items forming an outline or cutout
|
2014-01-02 09:26:03 +00:00
|
|
|
|
|
|
|
// NOTE: IMPLEMENTATION
|
|
|
|
// If/when component cutouts are allowed, we must implement them separately. Cutouts
|
|
|
|
// must be added to the board outline section and not to the Other Outline section.
|
2020-11-13 11:17:15 +00:00
|
|
|
// The footprint cutouts should be handled via the idf_export_footprint() routine.
|
2014-01-02 09:26:03 +00:00
|
|
|
|
|
|
|
double offX, offY;
|
2014-06-05 18:37:04 +00:00
|
|
|
aIDFBoard.GetUserOffset( offX, offY );
|
2014-01-02 09:26:03 +00:00
|
|
|
|
|
|
|
// Retrieve segments and arcs from the board
|
2020-10-13 17:46:48 +00:00
|
|
|
for( BOARD_ITEM* item : aPcb->Drawings() )
|
2014-01-02 09:26:03 +00:00
|
|
|
{
|
2020-10-04 14:19:33 +00:00
|
|
|
if( item->Type() != PCB_SHAPE_T || item->GetLayer() != Edge_Cuts )
|
2014-01-02 09:26:03 +00:00
|
|
|
continue;
|
|
|
|
|
2023-03-30 11:49:23 +00:00
|
|
|
PCB_SHAPE* graphic = static_cast<PCB_SHAPE*>( item );
|
2014-01-02 09:26:03 +00:00
|
|
|
|
|
|
|
switch( graphic->GetShape() )
|
|
|
|
{
|
2021-07-21 18:31:25 +00:00
|
|
|
case SHAPE_T::SEGMENT:
|
2021-07-19 23:56:05 +00:00
|
|
|
{
|
2021-07-17 19:56:18 +00:00
|
|
|
if( graphic->GetStart() == graphic->GetEnd() )
|
2021-07-19 23:56:05 +00:00
|
|
|
break;
|
2014-12-28 15:09:35 +00:00
|
|
|
|
2021-07-19 23:56:05 +00:00
|
|
|
sp.x = graphic->GetStart().x * scale + offX;
|
|
|
|
sp.y = -graphic->GetStart().y * scale + offY;
|
|
|
|
ep.x = graphic->GetEnd().x * scale + offX;
|
|
|
|
ep.y = -graphic->GetEnd().y * scale + offY;
|
|
|
|
IDF_SEGMENT* seg = new IDF_SEGMENT( sp, ep );
|
|
|
|
|
|
|
|
if( seg )
|
|
|
|
lines.push_back( seg );
|
2014-01-02 09:26:03 +00:00
|
|
|
|
|
|
|
break;
|
2021-07-19 23:56:05 +00:00
|
|
|
}
|
2014-01-02 09:26:03 +00:00
|
|
|
|
2023-07-24 16:07:56 +00:00
|
|
|
case SHAPE_T::RECTANGLE:
|
2021-07-19 23:56:05 +00:00
|
|
|
{
|
2021-07-17 19:56:18 +00:00
|
|
|
if( graphic->GetStart() == graphic->GetEnd() )
|
2021-07-19 23:56:05 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
double top = graphic->GetStart().y * scale + offY;
|
|
|
|
double left = graphic->GetStart().x * scale + offX;
|
|
|
|
double bottom = graphic->GetEnd().y * scale + offY;
|
|
|
|
double right = graphic->GetEnd().x * scale + offX;
|
|
|
|
|
|
|
|
IDF_POINT corners[4];
|
|
|
|
corners[0] = IDF_POINT( left, top );
|
|
|
|
corners[1] = IDF_POINT( right, top );
|
|
|
|
corners[2] = IDF_POINT( right, bottom );
|
|
|
|
corners[3] = IDF_POINT( left, bottom );
|
|
|
|
|
|
|
|
lines.push_back( new IDF_SEGMENT( corners[0], corners[1] ) );
|
|
|
|
lines.push_back( new IDF_SEGMENT( corners[1], corners[2] ) );
|
|
|
|
lines.push_back( new IDF_SEGMENT( corners[2], corners[3] ) );
|
|
|
|
lines.push_back( new IDF_SEGMENT( corners[3], corners[0] ) );
|
2020-06-15 19:50:20 +00:00
|
|
|
break;
|
2021-07-19 23:56:05 +00:00
|
|
|
}
|
2020-06-15 19:50:20 +00:00
|
|
|
|
2021-07-21 18:31:25 +00:00
|
|
|
case SHAPE_T::ARC:
|
2021-07-19 23:56:05 +00:00
|
|
|
{
|
2021-07-17 19:56:18 +00:00
|
|
|
if( graphic->GetCenter() == graphic->GetStart() )
|
2021-07-19 23:56:05 +00:00
|
|
|
break;
|
2014-12-28 15:09:35 +00:00
|
|
|
|
2021-07-19 23:56:05 +00:00
|
|
|
sp.x = graphic->GetCenter().x * scale + offX;
|
|
|
|
sp.y = -graphic->GetCenter().y * scale + offY;
|
2021-07-17 19:56:18 +00:00
|
|
|
ep.x = graphic->GetStart().x * scale + offX;
|
|
|
|
ep.y = -graphic->GetStart().y * scale + offY;
|
2022-01-14 15:34:41 +00:00
|
|
|
IDF_SEGMENT* seg = new IDF_SEGMENT( sp, ep, -graphic->GetArcAngle().AsDegrees(), true );
|
2021-07-19 23:56:05 +00:00
|
|
|
|
|
|
|
if( seg )
|
|
|
|
lines.push_back( seg );
|
2014-01-02 09:26:03 +00:00
|
|
|
|
|
|
|
break;
|
2021-07-19 23:56:05 +00:00
|
|
|
}
|
2014-01-02 09:26:03 +00:00
|
|
|
|
2021-07-21 18:31:25 +00:00
|
|
|
case SHAPE_T::CIRCLE:
|
2021-07-19 23:56:05 +00:00
|
|
|
{
|
|
|
|
if( graphic->GetRadius() == 0 )
|
|
|
|
break;
|
|
|
|
|
|
|
|
sp.x = graphic->GetCenter().x * scale + offX;
|
|
|
|
sp.y = -graphic->GetCenter().y * scale + offY;
|
|
|
|
ep.x = sp.x - graphic->GetRadius() * scale;
|
|
|
|
ep.y = sp.y;
|
|
|
|
|
|
|
|
// Circles must always have an angle of +360 deg. to appease
|
|
|
|
// quirky MCAD implementations of IDF.
|
|
|
|
IDF_SEGMENT* seg = new IDF_SEGMENT( sp, ep, 360.0, true );
|
|
|
|
|
|
|
|
if( seg )
|
|
|
|
lines.push_back( seg );
|
|
|
|
|
2014-01-02 09:26:03 +00:00
|
|
|
break;
|
2021-07-19 23:56:05 +00:00
|
|
|
}
|
2014-01-02 09:26:03 +00:00
|
|
|
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// if there is no outline then use the bounding box
|
|
|
|
if( lines.empty() )
|
|
|
|
{
|
|
|
|
goto UseBoundingBox;
|
|
|
|
}
|
|
|
|
|
|
|
|
// get the board outline and write it out
|
|
|
|
// note: we do not use a try/catch block here since we intend
|
|
|
|
// to simply ignore unclosed loops and continue processing
|
|
|
|
// until we're out of segments to process
|
2014-06-05 18:37:04 +00:00
|
|
|
outline = new IDF_OUTLINE;
|
|
|
|
IDF3::GetOutline( lines, *outline );
|
2014-01-02 09:26:03 +00:00
|
|
|
|
2014-06-05 18:37:04 +00:00
|
|
|
if( outline->empty() )
|
2014-01-02 09:26:03 +00:00
|
|
|
goto UseBoundingBox;
|
|
|
|
|
2014-06-05 18:37:04 +00:00
|
|
|
aIDFBoard.AddBoardOutline( outline );
|
2021-07-19 23:56:05 +00:00
|
|
|
outline = nullptr;
|
2014-01-02 09:26:03 +00:00
|
|
|
|
|
|
|
// get all cutouts and write them out
|
|
|
|
while( !lines.empty() )
|
|
|
|
{
|
2014-06-05 18:37:04 +00:00
|
|
|
if( !outline )
|
|
|
|
outline = new IDF_OUTLINE;
|
2014-01-02 09:26:03 +00:00
|
|
|
|
2014-06-05 18:37:04 +00:00
|
|
|
IDF3::GetOutline( lines, *outline );
|
|
|
|
|
|
|
|
if( outline->empty() )
|
|
|
|
{
|
|
|
|
outline->Clear();
|
2014-01-02 09:26:03 +00:00
|
|
|
continue;
|
2014-06-05 18:37:04 +00:00
|
|
|
}
|
2014-01-02 09:26:03 +00:00
|
|
|
|
2014-06-05 18:37:04 +00:00
|
|
|
aIDFBoard.AddBoardOutline( outline );
|
2021-07-19 23:56:05 +00:00
|
|
|
outline = nullptr;
|
2014-01-02 09:26:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
UseBoundingBox:
|
|
|
|
|
|
|
|
// clean up if necessary
|
|
|
|
while( !lines.empty() )
|
|
|
|
{
|
|
|
|
delete lines.front();
|
|
|
|
lines.pop_front();
|
|
|
|
}
|
|
|
|
|
2014-06-05 18:37:04 +00:00
|
|
|
if( outline )
|
|
|
|
outline->Clear();
|
|
|
|
else
|
|
|
|
outline = new IDF_OUTLINE;
|
2014-01-02 09:26:03 +00:00
|
|
|
|
2020-11-13 11:17:15 +00:00
|
|
|
// Fetch a rectangular bounding box for the board; there is always some uncertainty in the
|
|
|
|
// board dimensions computed via ComputeBoundingBox() since this depends on the individual
|
|
|
|
// footprint entities.
|
2022-08-30 23:28:18 +00:00
|
|
|
BOX2I bbbox = aPcb->GetBoardEdgesBoundingBox();
|
2014-01-02 09:26:03 +00:00
|
|
|
|
|
|
|
// convert to mm and compensate for an assumed LINE_WIDTH line thickness
|
|
|
|
double x = ( bbbox.GetOrigin().x + LINE_WIDTH / 2 ) * scale + offX;
|
|
|
|
double y = ( bbbox.GetOrigin().y + LINE_WIDTH / 2 ) * scale + offY;
|
|
|
|
double dx = ( bbbox.GetSize().x - LINE_WIDTH ) * scale;
|
|
|
|
double dy = ( bbbox.GetSize().y - LINE_WIDTH ) * scale;
|
|
|
|
|
|
|
|
double px[4], py[4];
|
|
|
|
px[0] = x;
|
|
|
|
py[0] = y;
|
|
|
|
|
|
|
|
px[1] = x;
|
|
|
|
py[1] = y + dy;
|
|
|
|
|
|
|
|
px[2] = x + dx;
|
|
|
|
py[2] = y + dy;
|
|
|
|
|
|
|
|
px[3] = x + dx;
|
|
|
|
py[3] = y;
|
|
|
|
|
|
|
|
IDF_POINT p1, p2;
|
|
|
|
|
|
|
|
p1.x = px[3];
|
|
|
|
p1.y = py[3];
|
|
|
|
p2.x = px[0];
|
|
|
|
p2.y = py[0];
|
|
|
|
|
2014-06-05 18:37:04 +00:00
|
|
|
outline->push( new IDF_SEGMENT( p1, p2 ) );
|
2014-01-02 09:26:03 +00:00
|
|
|
|
|
|
|
for( int i = 1; i < 4; ++i )
|
|
|
|
{
|
|
|
|
p1.x = px[i - 1];
|
|
|
|
p1.y = py[i - 1];
|
|
|
|
p2.x = px[i];
|
|
|
|
p2.y = py[i];
|
2014-01-26 14:20:58 +00:00
|
|
|
|
2014-06-05 18:37:04 +00:00
|
|
|
outline->push( new IDF_SEGMENT( p1, p2 ) );
|
2014-01-02 09:26:03 +00:00
|
|
|
}
|
|
|
|
|
2014-06-05 18:37:04 +00:00
|
|
|
aIDFBoard.AddBoardOutline( outline );
|
2014-01-02 09:26:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2021-07-19 23:56:05 +00:00
|
|
|
* Retrieve information from all board footprints, adds drill holes to the DRILLED_HOLES or
|
|
|
|
* BOARD_OUTLINE section as appropriate, Compiles data for the PLACEMENT section and compiles
|
|
|
|
* data for the library ELECTRICAL section.
|
2014-01-02 09:26:03 +00:00
|
|
|
*/
|
2024-05-08 14:22:25 +00:00
|
|
|
static void idf_export_footprint( BOARD* aPcb, FOOTPRINT* aFootprint, IDF3_BOARD& aIDFBoard,
|
|
|
|
bool aIncludeUnspecified, bool aIncludeDNP )
|
2014-01-02 09:26:03 +00:00
|
|
|
{
|
|
|
|
// Reference Designator
|
2023-05-05 13:21:56 +00:00
|
|
|
std::string crefdes = TO_UTF8( aFootprint->Reference().GetShownText( false ) );
|
2014-01-02 09:26:03 +00:00
|
|
|
|
2022-09-26 03:40:25 +00:00
|
|
|
wxString libraryName = aFootprint->GetFPID().GetLibNickname();
|
|
|
|
wxString footprintBasePath = wxEmptyString;
|
|
|
|
|
|
|
|
if( aPcb->GetProject() )
|
|
|
|
{
|
2022-10-15 08:27:00 +00:00
|
|
|
const FP_LIB_TABLE_ROW* fpRow = nullptr;
|
|
|
|
|
|
|
|
try
|
|
|
|
{
|
2023-09-28 03:15:54 +00:00
|
|
|
fpRow = PROJECT_PCB::PcbFootprintLibs( aPcb->GetProject() )->FindRow( libraryName, false );
|
2022-10-15 08:27:00 +00:00
|
|
|
}
|
|
|
|
catch( ... )
|
|
|
|
{
|
|
|
|
// Not found: do nothing
|
|
|
|
}
|
2022-09-26 03:40:25 +00:00
|
|
|
|
|
|
|
if( fpRow )
|
|
|
|
footprintBasePath = fpRow->GetFullURI( true );
|
|
|
|
}
|
|
|
|
|
2014-01-02 09:26:03 +00:00
|
|
|
if( crefdes.empty() || !crefdes.compare( "~" ) )
|
|
|
|
{
|
2023-05-05 13:21:56 +00:00
|
|
|
std::string cvalue = TO_UTF8( aFootprint->Value().GetShownText( false ) );
|
2014-01-02 09:26:03 +00:00
|
|
|
|
|
|
|
// if both the RefDes and Value are empty or set to '~' the board owns the part,
|
2020-11-13 11:17:15 +00:00
|
|
|
// otherwise associated parts of the footprint must be marked NOREFDES.
|
2014-01-02 09:26:03 +00:00
|
|
|
if( cvalue.empty() || !cvalue.compare( "~" ) )
|
|
|
|
crefdes = "BOARD";
|
|
|
|
else
|
|
|
|
crefdes = "NOREFDES";
|
|
|
|
}
|
|
|
|
|
2020-11-13 11:17:15 +00:00
|
|
|
// TODO: If footprint cutouts are supported we must add code here
|
2020-11-13 01:33:30 +00:00
|
|
|
// for( EDA_ITEM* item = aFootprint->GraphicalItems(); item != NULL; item = item->Next() )
|
2014-01-02 09:26:03 +00:00
|
|
|
// {
|
2023-03-30 11:49:23 +00:00
|
|
|
// if( item->Type() != PCB_SHAPE_T || item->GetLayer() != Edge_Cuts )
|
2020-10-13 17:46:48 +00:00
|
|
|
// continue;
|
|
|
|
// code to export cutouts
|
2014-01-02 09:26:03 +00:00
|
|
|
// }
|
|
|
|
|
|
|
|
// Export pads
|
|
|
|
double drill, x, y;
|
2014-06-05 18:37:04 +00:00
|
|
|
double scale = aIDFBoard.GetUserScale();
|
2014-01-02 09:26:03 +00:00
|
|
|
IDF3::KEY_PLATING kplate;
|
|
|
|
std::string pintype;
|
|
|
|
std::string tstr;
|
|
|
|
|
|
|
|
double dx, dy;
|
|
|
|
|
2014-06-05 18:37:04 +00:00
|
|
|
aIDFBoard.GetUserOffset( dx, dy );
|
2014-01-02 09:26:03 +00:00
|
|
|
|
2020-11-13 01:33:30 +00:00
|
|
|
for( auto pad : aFootprint->Pads() )
|
2014-01-02 09:26:03 +00:00
|
|
|
{
|
|
|
|
drill = (double) pad->GetDrillSize().x * scale;
|
|
|
|
x = pad->GetPosition().x * scale + dx;
|
|
|
|
y = -pad->GetPosition().y * scale + dy;
|
|
|
|
|
|
|
|
// Export the hole on the edge layer
|
|
|
|
if( drill > 0.0 )
|
|
|
|
{
|
|
|
|
// plating
|
2021-05-01 14:46:50 +00:00
|
|
|
if( pad->GetAttribute() == PAD_ATTRIB::NPTH )
|
2014-01-02 09:26:03 +00:00
|
|
|
kplate = IDF3::NPTH;
|
|
|
|
else
|
|
|
|
kplate = IDF3::PTH;
|
|
|
|
|
|
|
|
// hole type
|
2021-08-23 23:10:21 +00:00
|
|
|
tstr = TO_UTF8( pad->GetNumber() );
|
2014-01-02 09:26:03 +00:00
|
|
|
|
|
|
|
if( tstr.empty() || !tstr.compare( "0" ) || !tstr.compare( "~" )
|
2014-01-26 14:20:58 +00:00
|
|
|
|| ( kplate == IDF3::NPTH )
|
2024-04-07 22:01:08 +00:00
|
|
|
|| ( pad->GetDrillShape() == PAD_DRILL_SHAPE::OBLONG ) )
|
2014-01-02 09:26:03 +00:00
|
|
|
pintype = "MTG";
|
|
|
|
else
|
|
|
|
pintype = "PIN";
|
|
|
|
|
|
|
|
// fields:
|
|
|
|
// 1. hole dia. : float
|
|
|
|
// 2. X coord : float
|
|
|
|
// 3. Y coord : float
|
|
|
|
// 4. plating : PTH | NPTH
|
|
|
|
// 5. Assoc. part : BOARD | NOREFDES | PANEL | {"refdes"}
|
|
|
|
// 6. type : PIN | VIA | MTG | TOOL | { "other" }
|
|
|
|
// 7. owner : MCAD | ECAD | UNOWNED
|
2024-04-07 22:01:08 +00:00
|
|
|
if( ( pad->GetDrillShape() == PAD_DRILL_SHAPE::OBLONG )
|
2014-01-02 09:26:03 +00:00
|
|
|
&& ( pad->GetDrillSize().x != pad->GetDrillSize().y ) )
|
|
|
|
{
|
|
|
|
// NOTE: IDF does not have direct support for slots;
|
|
|
|
// slots are implemented as a board cutout and we
|
|
|
|
// cannot represent plating or reference designators
|
|
|
|
|
|
|
|
double dlength = pad->GetDrillSize().y * scale;
|
|
|
|
|
2020-10-21 03:48:06 +00:00
|
|
|
// NOTE: The orientation of footprints and pads have
|
2014-01-02 09:26:03 +00:00
|
|
|
// the opposite sense due to KiCad drawing on a
|
|
|
|
// screen with a LH coordinate system
|
2022-01-13 13:45:48 +00:00
|
|
|
double angle = pad->GetOrientation().AsDegrees();
|
2014-01-02 09:26:03 +00:00
|
|
|
|
2015-07-04 16:32:37 +00:00
|
|
|
// NOTE: Since this code assumes the scenario where
|
|
|
|
// GetDrillSize().y is the length but idf_parser.cpp
|
|
|
|
// assumes a length along the X axis, the orientation
|
|
|
|
// must be shifted +90 deg when GetDrillSize().y is
|
|
|
|
// the major axis.
|
|
|
|
|
2014-01-02 09:26:03 +00:00
|
|
|
if( dlength < drill )
|
|
|
|
{
|
|
|
|
std::swap( drill, dlength );
|
2015-07-04 16:32:37 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
angle += 90.0;
|
2014-01-02 09:26:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// NOTE: KiCad measures a slot's length from end to end
|
|
|
|
// rather than between the centers of the arcs
|
|
|
|
dlength -= drill;
|
|
|
|
|
|
|
|
aIDFBoard.AddSlot( drill, dlength, angle, x, y );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2014-06-05 18:37:04 +00:00
|
|
|
IDF_DRILL_DATA *dp = new IDF_DRILL_DATA( drill, x, y, kplate, crefdes,
|
|
|
|
pintype, IDF3::ECAD );
|
|
|
|
|
|
|
|
if( !aIDFBoard.AddDrill( dp ) )
|
|
|
|
{
|
|
|
|
delete dp;
|
|
|
|
|
|
|
|
std::ostringstream ostr;
|
|
|
|
ostr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__;
|
|
|
|
ostr << "(): could not add drill";
|
|
|
|
|
|
|
|
throw std::runtime_error( ostr.str() );
|
|
|
|
}
|
2014-01-02 09:26:03 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-05-08 14:22:25 +00:00
|
|
|
if( ( !(aFootprint->GetAttributes() & (FP_THROUGH_HOLE|FP_SMD)) ) && !aIncludeUnspecified )
|
|
|
|
return;
|
|
|
|
|
|
|
|
if( aFootprint->IsDNP() && !aIncludeDNP )
|
|
|
|
return;
|
|
|
|
|
2014-01-25 12:23:29 +00:00
|
|
|
// add any valid models to the library item list
|
|
|
|
std::string refdes;
|
|
|
|
|
2021-07-19 23:56:05 +00:00
|
|
|
IDF3_COMPONENT* comp = nullptr;
|
2014-06-05 18:37:04 +00:00
|
|
|
|
2020-11-13 01:33:30 +00:00
|
|
|
auto sM = aFootprint->Models().begin();
|
|
|
|
auto eM = aFootprint->Models().end();
|
2016-07-19 17:35:25 +00:00
|
|
|
wxFileName idfFile;
|
|
|
|
wxString idfExt;
|
|
|
|
|
|
|
|
while( sM != eM )
|
2014-01-25 12:23:29 +00:00
|
|
|
{
|
2021-09-19 12:07:36 +00:00
|
|
|
if( !sM->m_Show )
|
|
|
|
{
|
|
|
|
++sM;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2022-09-26 03:40:25 +00:00
|
|
|
idfFile.Assign( resolver->ResolvePath( sM->m_Filename, footprintBasePath ) );
|
2016-07-19 17:35:25 +00:00
|
|
|
idfExt = idfFile.GetExt();
|
|
|
|
|
|
|
|
if( idfExt.Cmp( wxT( "idf" ) ) && idfExt.Cmp( wxT( "IDF" ) ) )
|
|
|
|
{
|
|
|
|
++sM;
|
2014-01-25 12:23:29 +00:00
|
|
|
continue;
|
2016-07-19 17:35:25 +00:00
|
|
|
}
|
2014-01-25 12:23:29 +00:00
|
|
|
|
2014-01-29 16:42:21 +00:00
|
|
|
if( refdes.empty() )
|
|
|
|
{
|
2023-05-05 13:21:56 +00:00
|
|
|
refdes = TO_UTF8( aFootprint->Reference().GetShownText( false ) );
|
2014-01-29 16:42:21 +00:00
|
|
|
|
2014-06-05 18:37:04 +00:00
|
|
|
// NOREFDES cannot be used or else the software gets confused
|
|
|
|
// when writing out the placement data due to conflicting
|
|
|
|
// placement and layer specifications; to work around this we
|
|
|
|
// create a (hopefully) unique refdes for our exported part.
|
2014-01-29 16:42:21 +00:00
|
|
|
if( refdes.empty() || !refdes.compare( "~" ) )
|
2014-06-05 18:37:04 +00:00
|
|
|
refdes = aIDFBoard.GetNewRefDes();
|
2014-01-29 16:42:21 +00:00
|
|
|
}
|
|
|
|
|
2014-06-05 18:37:04 +00:00
|
|
|
IDF3_COMP_OUTLINE* outline;
|
|
|
|
|
2016-07-19 17:35:25 +00:00
|
|
|
outline = aIDFBoard.GetComponentOutline( idfFile.GetFullPath() );
|
2014-06-05 18:37:04 +00:00
|
|
|
|
|
|
|
if( !outline )
|
|
|
|
throw( std::runtime_error( aIDFBoard.GetError() ) );
|
|
|
|
|
2022-01-13 17:27:36 +00:00
|
|
|
double rotz = aFootprint->GetOrientation().AsDegrees();
|
2016-07-19 17:35:25 +00:00
|
|
|
double locx = sM->m_Offset.x * 25.4; // part offsets are in inches
|
|
|
|
double locy = sM->m_Offset.y * 25.4;
|
|
|
|
double locz = sM->m_Offset.z * 25.4;
|
|
|
|
double lrot = sM->m_Rotation.z;
|
2014-01-25 12:23:29 +00:00
|
|
|
|
2020-11-13 01:33:30 +00:00
|
|
|
bool top = ( aFootprint->GetLayer() == B_Cu ) ? false : true;
|
2014-01-25 12:23:29 +00:00
|
|
|
|
|
|
|
if( top )
|
|
|
|
{
|
|
|
|
locy = -locy;
|
2020-11-13 01:33:30 +00:00
|
|
|
RotatePoint( &locx, &locy, aFootprint->GetOrientation() );
|
2014-01-25 12:23:29 +00:00
|
|
|
locy = -locy;
|
|
|
|
}
|
2014-06-05 18:37:04 +00:00
|
|
|
|
2014-01-25 12:23:29 +00:00
|
|
|
if( !top )
|
|
|
|
{
|
2015-12-07 09:22:09 +00:00
|
|
|
lrot = -lrot;
|
2020-11-13 01:33:30 +00:00
|
|
|
RotatePoint( &locx, &locy, aFootprint->GetOrientation() );
|
2014-01-25 12:23:29 +00:00
|
|
|
locy = -locy;
|
|
|
|
|
|
|
|
rotz = 180.0 - rotz;
|
|
|
|
|
|
|
|
if( rotz >= 360.0 )
|
|
|
|
while( rotz >= 360.0 ) rotz -= 360.0;
|
|
|
|
|
|
|
|
if( rotz <= -360.0 )
|
|
|
|
while( rotz <= -360.0 ) rotz += 360.0;
|
|
|
|
}
|
|
|
|
|
2021-07-19 23:56:05 +00:00
|
|
|
if( comp == nullptr )
|
2014-06-05 18:37:04 +00:00
|
|
|
comp = aIDFBoard.FindComponent( refdes );
|
2014-01-25 12:23:29 +00:00
|
|
|
|
2021-07-19 23:56:05 +00:00
|
|
|
if( comp == nullptr )
|
2014-06-05 18:37:04 +00:00
|
|
|
{
|
|
|
|
comp = new IDF3_COMPONENT( &aIDFBoard );
|
|
|
|
|
2021-07-19 23:56:05 +00:00
|
|
|
if( comp == nullptr )
|
2014-06-05 18:37:04 +00:00
|
|
|
throw( std::runtime_error( aIDFBoard.GetError() ) );
|
|
|
|
|
|
|
|
comp->SetRefDes( refdes );
|
|
|
|
|
|
|
|
if( top )
|
2020-11-16 00:04:55 +00:00
|
|
|
{
|
2020-11-13 01:33:30 +00:00
|
|
|
comp->SetPosition( aFootprint->GetPosition().x * scale + dx,
|
|
|
|
-aFootprint->GetPosition().y * scale + dy,
|
2014-06-05 18:37:04 +00:00
|
|
|
rotz, IDF3::LYR_TOP );
|
2020-11-16 00:04:55 +00:00
|
|
|
}
|
2014-06-05 18:37:04 +00:00
|
|
|
else
|
2020-11-16 00:04:55 +00:00
|
|
|
{
|
2020-11-13 01:33:30 +00:00
|
|
|
comp->SetPosition( aFootprint->GetPosition().x * scale + dx,
|
|
|
|
-aFootprint->GetPosition().y * scale + dy,
|
2014-06-05 18:37:04 +00:00
|
|
|
rotz, IDF3::LYR_BOTTOM );
|
2020-11-16 00:04:55 +00:00
|
|
|
}
|
2014-06-05 18:37:04 +00:00
|
|
|
|
|
|
|
comp->SetPlacement( IDF3::PS_ECAD );
|
|
|
|
|
|
|
|
aIDFBoard.AddComponent( comp );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
double refX, refY, refA;
|
|
|
|
IDF3::IDF_LAYER side;
|
|
|
|
|
|
|
|
if( ! comp->GetPosition( refX, refY, refA, side ) )
|
|
|
|
{
|
|
|
|
// place the item
|
|
|
|
if( top )
|
2020-11-16 00:04:55 +00:00
|
|
|
{
|
2020-11-13 01:33:30 +00:00
|
|
|
comp->SetPosition( aFootprint->GetPosition().x * scale + dx,
|
|
|
|
-aFootprint->GetPosition().y * scale + dy,
|
2014-06-05 18:37:04 +00:00
|
|
|
rotz, IDF3::LYR_TOP );
|
2020-11-16 00:04:55 +00:00
|
|
|
}
|
2014-11-13 11:29:05 +00:00
|
|
|
else
|
2020-11-16 00:04:55 +00:00
|
|
|
{
|
2020-11-13 01:33:30 +00:00
|
|
|
comp->SetPosition( aFootprint->GetPosition().x * scale + dx,
|
|
|
|
-aFootprint->GetPosition().y * scale + dy,
|
2014-11-13 11:29:05 +00:00
|
|
|
rotz, IDF3::LYR_BOTTOM );
|
2020-11-16 00:04:55 +00:00
|
|
|
}
|
2014-11-13 11:29:05 +00:00
|
|
|
|
|
|
|
comp->SetPlacement( IDF3::PS_ECAD );
|
|
|
|
|
2014-06-05 18:37:04 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// check that the retrieved component matches this one
|
2020-11-13 01:33:30 +00:00
|
|
|
refX = refX - ( aFootprint->GetPosition().x * scale + dx );
|
|
|
|
refY = refY - ( -aFootprint->GetPosition().y * scale + dy );
|
2014-06-05 18:37:04 +00:00
|
|
|
refA = refA - rotz;
|
|
|
|
refA *= refA;
|
|
|
|
refX *= refX;
|
|
|
|
refY *= refY;
|
|
|
|
refX += refY;
|
|
|
|
|
|
|
|
// conditions: same side, X,Y coordinates within 10 microns,
|
|
|
|
// angle within 0.01 degree
|
|
|
|
if( ( top && side == IDF3::LYR_BOTTOM ) || ( !top && side == IDF3::LYR_TOP )
|
|
|
|
|| ( refA > 0.0001 ) || ( refX > 0.0001 ) )
|
|
|
|
{
|
|
|
|
comp->GetPosition( refX, refY, refA, side );
|
|
|
|
|
|
|
|
std::ostringstream ostr;
|
|
|
|
ostr << "* " << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "():\n";
|
|
|
|
ostr << "* conflicting Reference Designator '" << refdes << "'\n";
|
2020-11-13 01:33:30 +00:00
|
|
|
ostr << "* X loc: " << ( aFootprint->GetPosition().x * scale + dx);
|
2014-06-05 18:37:04 +00:00
|
|
|
ostr << " vs. " << refX << "\n";
|
2020-11-13 01:33:30 +00:00
|
|
|
ostr << "* Y loc: " << ( -aFootprint->GetPosition().y * scale + dy);
|
2014-06-05 18:37:04 +00:00
|
|
|
ostr << " vs. " << refY << "\n";
|
|
|
|
ostr << "* angle: " << rotz;
|
|
|
|
ostr << " vs. " << refA << "\n";
|
|
|
|
|
|
|
|
if( top )
|
|
|
|
ostr << "* TOP vs. ";
|
|
|
|
else
|
|
|
|
ostr << "* BOTTOM vs. ";
|
|
|
|
|
|
|
|
if( side == IDF3::LYR_TOP )
|
|
|
|
ostr << "TOP";
|
|
|
|
else
|
|
|
|
ostr << "BOTTOM";
|
2014-06-24 16:17:18 +00:00
|
|
|
|
2014-06-05 18:37:04 +00:00
|
|
|
throw( std::runtime_error( ostr.str() ) );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// create the local data ...
|
|
|
|
IDF3_COMP_OUTLINE_DATA* data = new IDF3_COMP_OUTLINE_DATA( comp, outline );
|
|
|
|
|
|
|
|
data->SetOffsets( locx, locy, locz, lrot );
|
|
|
|
comp->AddOutlineData( data );
|
2016-07-19 17:35:25 +00:00
|
|
|
++sM;
|
2014-01-25 12:23:29 +00:00
|
|
|
}
|
2014-01-02 09:26:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2021-07-19 23:56:05 +00:00
|
|
|
* Generate IDFv3 compliant board (*.emn) and library (*.emp) files representing the user's
|
|
|
|
* PCB design.
|
2014-01-02 09:26:03 +00:00
|
|
|
*/
|
2016-07-19 17:35:25 +00:00
|
|
|
bool PCB_EDIT_FRAME::Export_IDF3( BOARD* aPcb, const wxString& aFullFileName,
|
2024-05-08 14:22:25 +00:00
|
|
|
bool aUseThou, double aXRef, double aYRef,
|
|
|
|
bool aIncludeUnspecified, bool aIncludeDNP )
|
2014-01-02 09:26:03 +00:00
|
|
|
{
|
2014-06-05 18:37:04 +00:00
|
|
|
IDF3_BOARD idfBoard( IDF3::CAD_ELEC );
|
2014-01-02 09:26:03 +00:00
|
|
|
|
2016-05-10 06:56:03 +00:00
|
|
|
// Switch the locale to standard C (needed to print floating point numbers)
|
|
|
|
LOCALE_IO toggle;
|
2014-01-02 09:26:03 +00:00
|
|
|
|
2023-09-28 03:15:54 +00:00
|
|
|
resolver = PROJECT_PCB::Get3DCacheManager( &Prj() )->GetResolver();
|
2016-07-19 17:35:25 +00:00
|
|
|
|
2014-06-05 18:37:04 +00:00
|
|
|
bool ok = true;
|
2022-09-16 22:56:24 +00:00
|
|
|
double scale = pcbIUScale.MM_PER_IU; // we must scale internal units to mm for IDF
|
2014-06-05 18:37:04 +00:00
|
|
|
IDF3::IDF_UNIT idfUnit;
|
|
|
|
|
|
|
|
if( aUseThou )
|
|
|
|
{
|
|
|
|
idfUnit = IDF3::UNIT_THOU;
|
|
|
|
idfBoard.SetUserPrecision( 1 );
|
|
|
|
}
|
|
|
|
else
|
2014-01-29 16:42:21 +00:00
|
|
|
{
|
2014-06-05 18:37:04 +00:00
|
|
|
idfUnit = IDF3::UNIT_MM;
|
|
|
|
idfBoard.SetUserPrecision( 5 );
|
|
|
|
}
|
|
|
|
|
|
|
|
wxFileName brdName = aPcb->GetFileName();
|
|
|
|
|
|
|
|
idfBoard.SetUserScale( scale );
|
|
|
|
idfBoard.SetBoardThickness( aPcb->GetDesignSettings().GetBoardThickness() * scale );
|
|
|
|
idfBoard.SetBoardName( TO_UTF8( brdName.GetFullName() ) );
|
|
|
|
idfBoard.SetBoardVersion( 0 );
|
|
|
|
idfBoard.SetLibraryVersion( 0 );
|
2014-01-02 09:26:03 +00:00
|
|
|
|
2014-06-05 18:37:04 +00:00
|
|
|
std::ostringstream ostr;
|
2015-01-05 21:51:47 +00:00
|
|
|
ostr << "KiCad " << TO_UTF8( GetBuildVersion() );
|
2014-06-05 18:37:04 +00:00
|
|
|
idfBoard.SetIDFSource( ostr.str() );
|
|
|
|
|
|
|
|
try
|
|
|
|
{
|
2015-08-13 13:13:34 +00:00
|
|
|
// set up the board reference point
|
|
|
|
idfBoard.SetUserOffset( -aXRef, aYRef );
|
2014-01-02 09:26:03 +00:00
|
|
|
|
2014-01-29 16:42:21 +00:00
|
|
|
// Export the board outline
|
|
|
|
idf_export_outline( aPcb, idfBoard );
|
2014-01-02 09:26:03 +00:00
|
|
|
|
2020-11-13 11:17:15 +00:00
|
|
|
// Output the drill holes and footprint (library) data.
|
2020-11-13 15:15:52 +00:00
|
|
|
for( FOOTPRINT* footprint : aPcb->Footprints() )
|
2024-05-08 14:22:25 +00:00
|
|
|
idf_export_footprint( aPcb, footprint, idfBoard, aIncludeUnspecified, aIncludeDNP );
|
2014-01-02 09:26:03 +00:00
|
|
|
|
2014-06-05 18:37:04 +00:00
|
|
|
if( !idfBoard.WriteFile( aFullFileName, idfUnit, false ) )
|
|
|
|
{
|
|
|
|
wxString msg;
|
2023-09-09 04:10:57 +00:00
|
|
|
msg << _( "IDF Export Failed:\n" ) << From_UTF8( idfBoard.GetError().c_str() );
|
2014-06-05 18:37:04 +00:00
|
|
|
wxMessageBox( msg );
|
|
|
|
|
|
|
|
ok = false;
|
|
|
|
}
|
2014-01-29 16:42:21 +00:00
|
|
|
}
|
2014-04-09 13:33:04 +00:00
|
|
|
catch( const IO_ERROR& ioe )
|
2014-01-29 16:42:21 +00:00
|
|
|
{
|
2014-06-05 18:37:04 +00:00
|
|
|
wxString msg;
|
2016-09-14 22:36:45 +00:00
|
|
|
msg << _( "IDF Export Failed:\n" ) << ioe.What();
|
2014-06-05 18:37:04 +00:00
|
|
|
wxMessageBox( msg );
|
|
|
|
|
|
|
|
ok = false;
|
|
|
|
}
|
2014-06-08 10:35:42 +00:00
|
|
|
catch( const std::exception& e )
|
2014-06-05 18:37:04 +00:00
|
|
|
{
|
|
|
|
wxString msg;
|
2023-09-09 04:10:57 +00:00
|
|
|
msg << _( "IDF Export Failed:\n" ) << From_UTF8( e.what() );
|
2014-06-05 18:37:04 +00:00
|
|
|
wxMessageBox( msg );
|
|
|
|
ok = false;
|
2014-01-29 16:42:21 +00:00
|
|
|
}
|
2014-01-02 09:26:03 +00:00
|
|
|
|
2014-06-05 18:37:04 +00:00
|
|
|
return ok;
|
2014-01-02 09:26:03 +00:00
|
|
|
}
|